Implement subscriptions, and update example accordingly

This commit is contained in:
Christian Rocha 2020-01-13 17:10:23 -05:00
parent 97bbcdd5fb
commit 147c8df2ee
No known key found for this signature in database
GPG Key ID: D6CC7A16E5878018
2 changed files with 79 additions and 32 deletions

View File

@ -3,12 +3,26 @@ package main
import (
"fmt"
"tea"
"time"
)
type Model int
type Model struct {
Choice int
Ticks int
}
type TickMsg struct{}
const tpl = `What to do today?
%s
Elapsed: %d seconds.
(press j/k or up/down to select, q or esc to quit)`
func main() {
p := tea.NewProgram(0, update, view)
p := tea.NewProgram(Model{0, 0}, update, view, []tea.Sub{tick})
if err := p.Start(); err != nil {
fmt.Println("could not start program:", err)
}
@ -18,53 +32,57 @@ func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
m, _ := model.(Model)
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg {
case "j":
fallthrough
case "down":
m += 1
if m > 3 {
m = 3
m.Choice += 1
if m.Choice > 3 {
m.Choice = 3
}
case "k":
fallthrough
case "up":
m -= 1
if m < 0 {
m = 0
m.Choice -= 1
if m.Choice < 0 {
m.Choice = 0
}
case "q":
fallthrough
case "esc":
fallthrough
case "ctrl+c":
return m, tea.Quit
}
case TickMsg:
m.Ticks += 1
}
return m, nil
}
// Subscription
func tick(_ tea.Model) tea.Msg {
time.Sleep(time.Second * 1)
return TickMsg{}
}
func view(model tea.Model) string {
m, _ := model.(Model)
c := m.Choice
choices := fmt.Sprintf(
"%s\n%s\n%s\n%s",
checkbox("Plant carrots", m == 0),
checkbox("Go to the market", m == 1),
checkbox("Read something", m == 2),
checkbox("See friends", m == 3),
checkbox("Plant carrots", c == 0),
checkbox("Go to the market", c == 1),
checkbox("Read something", c == 2),
checkbox("See friends", c == 3),
)
return fmt.Sprintf(
"What to do today?\n\n%s\n\n(press j/k or up/down to select, q or esc to quit)",
choices,
)
return fmt.Sprintf(tpl, choices, m.Ticks)
}
func checkbox(label string, checked bool) string {

53
tea.go
View File

@ -20,6 +20,10 @@ type Model interface{}
// Cmd is an IO operation. If it's nil it's considered a no-op.
type Cmd func() Msg
// Sub is an event subscription. If it's nil it's considered a no-op. But why
// would you subscribe to nil?
type Sub func(Model) Msg
// Update is called when a message is received. It may update the model and/or
// send a command.
type Update func(Msg, Model) (Model, Cmd)
@ -29,10 +33,11 @@ type View func(Model) string
// Program is a terminal user interface
type Program struct {
model Model
update Update
view View
rw io.ReadWriter
model Model
update Update
view View
subscriptions []Sub
rw io.ReadWriter
}
// Quit command
@ -44,12 +49,12 @@ func Quit() Msg {
type quitMsg struct{}
// NewProgram creates a new Program
func NewProgram(model Model, update Update, view View) *Program {
func NewProgram(model Model, update Update, view View, subs []Sub) *Program {
return &Program{
model: model,
update: update,
view: view,
// TODO: subscriptions
model: model,
update: update,
view: view,
subscriptions: subs,
}
}
@ -81,8 +86,9 @@ func (p *Program) Start() error {
p.render(model, true)
// Subscribe to user input
// TODO: move to program struct to allow for subscriptions to other things,
// too, like timers, frames, download/upload progress and so on.
// TODO: should we move this to the end-user program level or just keep this
// here, since it blocks nicely and user input will probably be something
// users typically need?
go func() {
for {
select {
@ -109,6 +115,28 @@ func (p *Program) Start() error {
}
}()
// Subscriptions. Subscribe to user input.
// TODO: should the blocking `for` be here, or in the end-user portion
// of the program?
go func() {
select {
case <-done:
return
default:
if len(p.subscriptions) > 0 {
for _, sub := range p.subscriptions {
if sub != nil {
go func() {
for {
msgs <- sub(p.model)
}
}()
}
}
}
}
}()
// Handle updates and draw
for {
select {
@ -163,6 +191,7 @@ func clearLines(n int) {
}
}
func clearScreen() {
// ClearScreen clears the visible portion of the terminal
func ClearScreen() {
fmt.Printf(esc + "2J" + esc + "3J" + esc + "1;1H")
}