forked from Mirrors/bubbletea
Multi-view example with progress bar
This commit is contained in:
parent
edb6c58f1a
commit
b6c8792309
120
example/main.go
120
example/main.go
|
@ -2,35 +2,63 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
"tea"
|
"tea"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fogleman/ease"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model contains the data for our application.
|
// Model contains the data for our application.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Choice int
|
Choice int
|
||||||
|
Chosen bool
|
||||||
Ticks int
|
Ticks int
|
||||||
|
Frames int
|
||||||
|
Progress float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// TickMsg signals that the timer has ticked
|
type tickMsg struct{}
|
||||||
type TickMsg struct{}
|
|
||||||
|
type frameMsg struct{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p := tea.NewProgram(
|
p := tea.NewProgram(
|
||||||
Model{0, 10},
|
Model{0, false, 10, 0, 0},
|
||||||
update,
|
update,
|
||||||
view,
|
view,
|
||||||
[]tea.Sub{tick},
|
[]tea.Sub{tick, frame},
|
||||||
)
|
)
|
||||||
if err := p.Start(); err != nil {
|
if err := p.Start(); err != nil {
|
||||||
fmt.Println("could not start program:", err)
|
fmt.Println("could not start program:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update. Triggered when new messages arrive.
|
// SUBSCRIPTIONS
|
||||||
|
|
||||||
|
func tick(model tea.Model) tea.Msg {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
return tickMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func frame(model tea.Model) tea.Msg {
|
||||||
|
time.Sleep(time.Second / 16)
|
||||||
|
return frameMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATES
|
||||||
|
|
||||||
func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
|
func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
|
||||||
m, _ := model.(Model)
|
m, _ := model.(Model)
|
||||||
|
|
||||||
|
if !m.Chosen {
|
||||||
|
return updateChoices(msg, m)
|
||||||
|
}
|
||||||
|
return updateChosen(msg, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
|
||||||
case tea.KeyPressMsg:
|
case tea.KeyPressMsg:
|
||||||
|
@ -49,6 +77,9 @@ func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
|
||||||
if m.Choice < 0 {
|
if m.Choice < 0 {
|
||||||
m.Choice = 0
|
m.Choice = 0
|
||||||
}
|
}
|
||||||
|
case "enter":
|
||||||
|
m.Chosen = true
|
||||||
|
return m, nil
|
||||||
case "q":
|
case "q":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "esc":
|
case "esc":
|
||||||
|
@ -57,7 +88,7 @@ func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
case TickMsg:
|
case tickMsg:
|
||||||
if m.Ticks == 0 {
|
if m.Ticks == 0 {
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
@ -67,24 +98,51 @@ func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscription
|
func updateChosen(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
|
||||||
func tick(_ tea.Model) tea.Msg {
|
switch msg := msg.(type) {
|
||||||
time.Sleep(time.Second)
|
|
||||||
return TickMsg{}
|
case tea.KeyPressMsg:
|
||||||
|
switch msg {
|
||||||
|
case "q":
|
||||||
|
fallthrough
|
||||||
|
case "esc":
|
||||||
|
fallthrough
|
||||||
|
case "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
// View template
|
case frameMsg:
|
||||||
const tpl = `What to do today?
|
m.Frames += 1
|
||||||
|
m.Progress = ease.OutBounce(float64(m.Frames) / float64(120))
|
||||||
|
if m.Progress > 1 {
|
||||||
|
m.Progress = 1
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIEWS
|
||||||
|
|
||||||
|
func view(model tea.Model) string {
|
||||||
|
m, _ := model.(Model)
|
||||||
|
if !m.Chosen {
|
||||||
|
return choicesView(m)
|
||||||
|
}
|
||||||
|
return chosenView(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
const choicesTpl = `What to do today?
|
||||||
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
Program quits in %d seconds.
|
Program quits in %d seconds.
|
||||||
|
|
||||||
(press j/k or up/down to select, q or esc to quit)`
|
(press j/k or up/down to select, enter to choose, and q or esc to quit)`
|
||||||
|
|
||||||
// View function. Called after an Update().
|
func choicesView(m Model) string {
|
||||||
func view(model tea.Model) string {
|
|
||||||
m, _ := model.(Model)
|
|
||||||
c := m.Choice
|
c := m.Choice
|
||||||
|
|
||||||
choices := fmt.Sprintf(
|
choices := fmt.Sprintf(
|
||||||
|
@ -95,10 +153,26 @@ func view(model tea.Model) string {
|
||||||
checkbox("See friends", c == 3),
|
checkbox("See friends", c == 3),
|
||||||
)
|
)
|
||||||
|
|
||||||
return fmt.Sprintf(tpl, choices, m.Ticks)
|
return fmt.Sprintf(choicesTpl, choices, m.Ticks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func chosenView(m Model) string {
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
switch m.Choice {
|
||||||
|
case 0:
|
||||||
|
msg = "Carrot planting?\n\nCool, we'll need libgarden and vegeutils..."
|
||||||
|
case 1:
|
||||||
|
msg = "A trip to the market?\n\nOkay, then we should install marketkit and libshopping..."
|
||||||
|
case 2:
|
||||||
|
msg = "Reading time?\n\nOkay, cool, then we’ll need a library. Yes, a literal library..."
|
||||||
|
default:
|
||||||
|
msg = "It’s always good to see friends.\n\nFetching social-skills and conversationutils..."
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\n" + msg + "\n\n\n\n\n Downloading...\n" + progressbar(80, m.Progress) + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkbox widget
|
|
||||||
func checkbox(label string, checked bool) string {
|
func checkbox(label string, checked bool) string {
|
||||||
check := " "
|
check := " "
|
||||||
if checked {
|
if checked {
|
||||||
|
@ -106,3 +180,13 @@ func checkbox(label string, checked bool) string {
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s] %s", check, label)
|
return fmt.Sprintf("[%s] %s", check, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func progressbar(width int, percent float64) string {
|
||||||
|
metaChars := 7
|
||||||
|
w := float64(width - metaChars)
|
||||||
|
fullSize := int(math.Round(w * percent))
|
||||||
|
emptySize := int(w) - fullSize
|
||||||
|
fullCells := strings.Repeat("#", fullSize)
|
||||||
|
emptyCells := strings.Repeat(".", emptySize)
|
||||||
|
return fmt.Sprintf("|%s%s| %3.0f", fullCells, emptyCells, math.Round(percent*100))
|
||||||
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module tea
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776
|
||||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942
|
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942
|
||||||
github.com/tj/go-terminput v1.0.0
|
github.com/tj/go-terminput v1.0.0
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
||||||
|
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA=
|
||||||
|
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0=
|
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0=
|
||||||
|
|
6
key.go
6
key.go
|
@ -11,13 +11,17 @@ type KeyPressMsg string
|
||||||
|
|
||||||
// Control keys
|
// Control keys
|
||||||
const (
|
const (
|
||||||
keyETX = 3 // ctrl+c
|
keyETX = 3 // break, ctrl+c
|
||||||
|
keyLF = 9 // line-feed, \n
|
||||||
|
keyCR = 13 // carriage return, \r
|
||||||
keyESC = 27
|
keyESC = 27
|
||||||
keyUS = 31
|
keyUS = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
var controlKeyNames = map[int]string{
|
var controlKeyNames = map[int]string{
|
||||||
keyETX: "ctrl+c",
|
keyETX: "ctrl+c",
|
||||||
|
keyLF: "enter",
|
||||||
|
keyCR: "enter",
|
||||||
keyESC: "esc",
|
keyESC: "esc",
|
||||||
keyUS: "us",
|
keyUS: "us",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue