Multi-view example with progress bar

This commit is contained in:
Christian Rocha 2020-01-14 13:47:36 -05:00
parent edb6c58f1a
commit b6c8792309
No known key found for this signature in database
GPG Key ID: D6CC7A16E5878018
4 changed files with 112 additions and 21 deletions

View File

@ -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 well need a library. Yes, a literal library..."
default:
msg = "Its 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
View File

@ -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
View File

@ -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
View File

@ -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",
} }