mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
Remove entire subscription model
It was a valiant effort, and the implementation was solid and dependable, but at the end of the day we can achieve the same functionality in a much simpler fashion with commands, especially because Go is not held to the same restrictions as Elm.
This commit is contained in:
168
boba.go
168
boba.go
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
@@ -29,41 +30,6 @@ func Batch(cmds ...Cmd) Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
// Sub is an event subscription; generally a recurring IO operation. If it
|
||||
// returns nil it's considered a no-op, but there's really no reason to have
|
||||
// a nil subscription.
|
||||
type Sub func() Msg
|
||||
|
||||
// Subs is a keyed set of subscriptions. The key should be a unique
|
||||
// identifier: two different subscriptions should not have the same key or
|
||||
// weird behavior will occur.
|
||||
type Subs map[string]Sub
|
||||
|
||||
// Subscriptions returns a map of subscriptions (Subs) our application will
|
||||
// subscribe to. If Subscriptions is nil it's considered a no-op.
|
||||
type Subscriptions func(Model) Subs
|
||||
|
||||
// subscription is an internal reference to a subscription used in subscription
|
||||
// management.
|
||||
type subscription struct {
|
||||
done chan struct{}
|
||||
sub Sub
|
||||
}
|
||||
|
||||
// subManager is used to manage active subscriptions, hence the pointers.
|
||||
type subManager map[string]*subscription
|
||||
|
||||
// endAll stops all subscriptions and remove subscription references from
|
||||
// subManager.
|
||||
func (m *subManager) endAll() {
|
||||
if m != nil {
|
||||
for key, sub := range *m {
|
||||
close(sub.done)
|
||||
delete(*m, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init is the first function that will be called. It returns your initial
|
||||
// model and runs an optional command
|
||||
type Init func() (Model, Cmd)
|
||||
@@ -77,10 +43,9 @@ type View func(Model) string
|
||||
|
||||
// Program is a terminal user interface
|
||||
type Program struct {
|
||||
init Init
|
||||
update Update
|
||||
view View
|
||||
subscriptions Subscriptions
|
||||
init Init
|
||||
update Update
|
||||
view View
|
||||
}
|
||||
|
||||
// Quit is a command that tells the program to exit
|
||||
@@ -95,12 +60,11 @@ type quitMsg struct{}
|
||||
type batchMsg []Cmd
|
||||
|
||||
// NewProgram creates a new Program
|
||||
func NewProgram(init Init, update Update, view View, subs Subscriptions) *Program {
|
||||
func NewProgram(init Init, update Update, view View) *Program {
|
||||
return &Program{
|
||||
init: init,
|
||||
update: update,
|
||||
view: view,
|
||||
subscriptions: subs,
|
||||
init: init,
|
||||
update: update,
|
||||
view: view,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +73,6 @@ func (p *Program) Start() error {
|
||||
var (
|
||||
model Model
|
||||
cmd Cmd
|
||||
subs = make(subManager)
|
||||
cmds = make(chan Cmd)
|
||||
msgs = make(chan Msg)
|
||||
errs = make(chan error)
|
||||
@@ -134,9 +97,7 @@ func (p *Program) Start() error {
|
||||
// Render initial view
|
||||
linesRendered = p.render(model, linesRendered)
|
||||
|
||||
// Subscribe to user input. We could move this out of here and offer it
|
||||
// as a subscription, but it blocks nicely and seems to be a common enough
|
||||
// need that we're enabling it by default.
|
||||
// Subscribe to user input
|
||||
go func() {
|
||||
for {
|
||||
msg, err := ReadKey(os.Stdin)
|
||||
@@ -147,9 +108,6 @@ func (p *Program) Start() error {
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize subscriptions
|
||||
subs = p.processSubs(msgs, model, subs)
|
||||
|
||||
// Process commands
|
||||
go func() {
|
||||
for {
|
||||
@@ -190,7 +148,6 @@ func (p *Program) Start() error {
|
||||
|
||||
model, cmd = p.update(msg, model) // run update
|
||||
cmds <- cmd // process command (if any)
|
||||
subs = p.processSubs(msgs, model, subs) // check for new and outdated subscriptions
|
||||
linesRendered = p.render(model, linesRendered) // render to terminal
|
||||
}
|
||||
}
|
||||
@@ -211,78 +168,6 @@ func (p *Program) render(model Model, linesRendered int) int {
|
||||
return strings.Count(view, "\r\n")
|
||||
}
|
||||
|
||||
// Manage subscriptions. Here we run the program's Subscription function and
|
||||
// inspect the functions it returns (a Subs map). If we notice existing
|
||||
// subscriptions have disappeared from the map we stop those subscriptions
|
||||
// by ending the Goroutines they run on. If we notice new subscriptions which
|
||||
// aren't currently running, we run them as loops in a new Goroutine.
|
||||
//
|
||||
// This function should be called on initialization and after every update.
|
||||
func (p *Program) processSubs(msgs chan Msg, model Model, activeSubs subManager) subManager {
|
||||
|
||||
// Nothing to do.
|
||||
if p.subscriptions == nil && activeSubs == nil {
|
||||
return activeSubs
|
||||
}
|
||||
|
||||
// There are no subscriptions. Cancel active ones and return.
|
||||
if p.subscriptions == nil && activeSubs != nil {
|
||||
activeSubs.endAll()
|
||||
return activeSubs
|
||||
}
|
||||
|
||||
newSubs := p.subscriptions(model)
|
||||
|
||||
// newSubs is an empty map. Cancel any active subscriptions and return.
|
||||
if newSubs == nil {
|
||||
activeSubs.endAll()
|
||||
return activeSubs
|
||||
}
|
||||
|
||||
// Stop subscriptions that don't exist in the new subscription map and
|
||||
// stop subscriptions where the new subscription is mapped to a nil.
|
||||
if len(activeSubs) > 0 {
|
||||
for key, sub := range activeSubs {
|
||||
_, exists := newSubs[key]
|
||||
if !exists || exists && newSubs[key] == nil {
|
||||
close(sub.done)
|
||||
delete(activeSubs, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start new subscriptions if they don't exist in the active subscription map
|
||||
if len(newSubs) > 0 {
|
||||
for key, sub := range newSubs {
|
||||
if _, exists := activeSubs[key]; !exists {
|
||||
|
||||
if sub == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
activeSubs[key] = &subscription{
|
||||
done: make(chan struct{}),
|
||||
sub: sub,
|
||||
}
|
||||
|
||||
go func(done chan struct{}, s Sub) {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case msgs <- s():
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(activeSubs[key].done, activeSubs[key].sub)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activeSubs
|
||||
}
|
||||
|
||||
// AltScreen exits the altscreen. This is just a wrapper around the termenv
|
||||
// function
|
||||
func AltScreen() {
|
||||
@@ -294,3 +179,38 @@ func AltScreen() {
|
||||
func ExitAltScreen() {
|
||||
termenv.ExitAltScreen()
|
||||
}
|
||||
|
||||
type EveryMsg time.Time
|
||||
|
||||
// Every is a command that ticks in sync with the system clock. So, if you
|
||||
// wanted to tick with the system clock every second, minute or hour you
|
||||
// could use this. It's also handy for having different things tick in sync.
|
||||
//
|
||||
// Note that because we're ticking with the system clock the tick will likely
|
||||
// not run for the entire specified duration. For example, if we're ticking for
|
||||
// one minute and the clock is at 12:34:20 then the next tick will happen at
|
||||
// 12:35:00, 40 seconds later.
|
||||
func Every(duration time.Duration, fn func(time.Time) Msg) Cmd {
|
||||
return func() Msg {
|
||||
n := time.Now()
|
||||
d := n.Truncate(duration).Add(duration).Sub(n)
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case now := <-t.C:
|
||||
return fn(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tick is a subscription that at an interval independent of the system clock
|
||||
// at the given duration. That is, the timer begins when precisely when invoked,
|
||||
// and runs for its entire duration.
|
||||
func Tick(d time.Duration, fn func(time.Time) Msg) Cmd {
|
||||
return func() Msg {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case now := <-t.C:
|
||||
return fn(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,21 +14,17 @@ type model int
|
||||
|
||||
type tickMsg time.Time
|
||||
|
||||
func newTickMsg(t time.Time) boba.Msg {
|
||||
return tickMsg(t)
|
||||
}
|
||||
|
||||
func main() {
|
||||
boba.AltScreen()
|
||||
defer boba.ExitAltScreen()
|
||||
err := boba.NewProgram(initialize, update, view, subscriptions).Start()
|
||||
err := boba.NewProgram(initialize, update, view).Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initialize() (boba.Model, boba.Cmd) {
|
||||
return model(5), nil
|
||||
return model(5), tick()
|
||||
}
|
||||
|
||||
func update(message boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
|
||||
@@ -51,19 +47,20 @@ func update(message boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
|
||||
if m <= 0 {
|
||||
return m, boba.Quit
|
||||
}
|
||||
return m, tick()
|
||||
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func subscriptions(_ boba.Model) boba.Subs {
|
||||
return boba.Subs{
|
||||
"tick": boba.Every(time.Second, newTickMsg),
|
||||
}
|
||||
}
|
||||
|
||||
func view(mdl boba.Model) string {
|
||||
m, _ := mdl.(model)
|
||||
return fmt.Sprintf("\n\n Hi. This program will exit in %d seconds...", m)
|
||||
}
|
||||
|
||||
func tick() boba.Cmd {
|
||||
return boba.Tick(time.Second, func(t time.Time) boba.Msg {
|
||||
return tickMsg(t)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type statusMsg int
|
||||
type errMsg error
|
||||
|
||||
func main() {
|
||||
p := boba.NewProgram(initialize, update, view, nil)
|
||||
p := boba.NewProgram(initialize, update, view)
|
||||
if err := p.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ func main() {
|
||||
initialize,
|
||||
update,
|
||||
view,
|
||||
subscriptions,
|
||||
)
|
||||
|
||||
if err := p.Start(); err != nil {
|
||||
@@ -38,7 +37,7 @@ func initialize() (boba.Model, boba.Cmd) {
|
||||
return Model{
|
||||
textInput: inputModel,
|
||||
err: nil,
|
||||
}, nil
|
||||
}, input.Blink(inputModel)
|
||||
}
|
||||
|
||||
func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
@@ -74,20 +73,6 @@ func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func subscriptions(model boba.Model) boba.Subs {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
sub, err := input.MakeSub(m.textInput)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return boba.Subs{
|
||||
"input": sub,
|
||||
}
|
||||
}
|
||||
|
||||
func view(model boba.Model) string {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
|
||||
@@ -18,7 +18,11 @@ func main() {
|
||||
|
||||
boba.AltScreen()
|
||||
defer boba.ExitAltScreen()
|
||||
if err := pager.NewProgram(string(content)).Start(); err != nil {
|
||||
if err := boba.NewProgram(
|
||||
pager.Init(string(content)),
|
||||
pager.Update,
|
||||
pager.View,
|
||||
).Start(); err != nil {
|
||||
fmt.Println("could not run program:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// A model can be more or less any type of data. It holds all the data for a
|
||||
// program, so often it's a struct. For this simple example, however, all
|
||||
// we'll need is a simple integer.
|
||||
type Model int
|
||||
type model int
|
||||
|
||||
// Messages are events that we respond to in our Update function. This
|
||||
// particular one indicates that the timer has ticked.
|
||||
@@ -21,22 +21,22 @@ type tickMsg time.Time
|
||||
|
||||
func main() {
|
||||
// Initialize our program
|
||||
p := boba.NewProgram(initialize, update, view, subscriptions)
|
||||
p := boba.NewProgram(initialize, update, view)
|
||||
if err := p.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initialize() (boba.Model, boba.Cmd) {
|
||||
return Model(5), nil
|
||||
return model(5), tick
|
||||
}
|
||||
|
||||
// Update is called when messages are recived. The idea is that you inspect
|
||||
// the message and update the model (or send back a new one) accordingly. You
|
||||
// can also return a commmand, which is a function that peforms I/O and
|
||||
// returns a message.
|
||||
func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
m, _ := model.(Model)
|
||||
func update(msg boba.Msg, mdl boba.Model) (boba.Model, boba.Cmd) {
|
||||
m, _ := mdl.(model)
|
||||
|
||||
switch msg.(type) {
|
||||
case boba.KeyMsg:
|
||||
@@ -46,23 +46,19 @@ func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
if m <= 0 {
|
||||
return m, boba.Quit
|
||||
}
|
||||
return m, tick
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Views take data from the model and return a string which will be rendered
|
||||
// to the terminal.
|
||||
func view(model boba.Model) string {
|
||||
m, _ := model.(Model)
|
||||
func view(mdl boba.Model) string {
|
||||
m, _ := mdl.(model)
|
||||
return fmt.Sprintf("Hi. This program will exit in %d seconds. To quit sooner press any key.\n", m)
|
||||
}
|
||||
|
||||
// This is a subscription which we setup in NewProgram(). It waits for one
|
||||
// second, sends a tick, and then restarts.
|
||||
func subscriptions(_ boba.Model) boba.Subs {
|
||||
return boba.Subs{
|
||||
"tick": boba.Every(time.Second, func(t time.Time) boba.Msg {
|
||||
return tickMsg(t)
|
||||
}),
|
||||
}
|
||||
func tick() boba.Msg {
|
||||
time.Sleep(time.Second)
|
||||
return tickMsg{}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type Model struct {
|
||||
type errMsg error
|
||||
|
||||
func main() {
|
||||
p := boba.NewProgram(initialize, update, view, subscriptions)
|
||||
p := boba.NewProgram(initialize, update, view)
|
||||
if err := p.Start(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -30,12 +30,12 @@ func main() {
|
||||
}
|
||||
|
||||
func initialize() (boba.Model, boba.Cmd) {
|
||||
m := spinner.NewModel()
|
||||
m.Type = spinner.Dot
|
||||
s := spinner.NewModel()
|
||||
s.Type = spinner.Dot
|
||||
|
||||
return Model{
|
||||
spinner: m,
|
||||
}, nil
|
||||
spinner: s,
|
||||
}, spinner.Tick(s)
|
||||
}
|
||||
|
||||
func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
@@ -64,8 +64,9 @@ func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
return m, nil
|
||||
|
||||
default:
|
||||
m.spinner, _ = spinner.Update(msg, m.spinner)
|
||||
return m, nil
|
||||
var cmd boba.Cmd
|
||||
m.spinner, cmd = spinner.Update(msg, m.spinner)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,18 +89,3 @@ func view(model boba.Model) string {
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func subscriptions(model boba.Model) boba.Subs {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
sub, err := spinner.MakeSub(m.spinner)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return boba.Subs{
|
||||
"tick": sub,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ func main() {
|
||||
initialize,
|
||||
update,
|
||||
view,
|
||||
subscriptions,
|
||||
).Start(); err != nil {
|
||||
fmt.Printf("could not start program: %s\n", err)
|
||||
os.Exit(1)
|
||||
@@ -53,7 +52,13 @@ func initialize() (boba.Model, boba.Cmd) {
|
||||
email.Placeholder = "Email"
|
||||
email.Prompt = blurredPrompt
|
||||
|
||||
return Model{0, name, nickName, email, blurredSubmitButton}, nil
|
||||
return Model{0, name, nickName, email, blurredSubmitButton},
|
||||
boba.Batch(
|
||||
input.Blink(name),
|
||||
input.Blink(nickName),
|
||||
input.Blink(email),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
@@ -62,6 +67,8 @@ func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
panic("could not perform assertion on model")
|
||||
}
|
||||
|
||||
var cmd boba.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case boba.KeyMsg:
|
||||
@@ -135,44 +142,29 @@ func update(msg boba.Msg, model boba.Model) (boba.Model, boba.Cmd) {
|
||||
|
||||
default:
|
||||
// Handle character input
|
||||
m = updateInputs(msg, m)
|
||||
return m, nil
|
||||
m, cmd = updateInputs(msg, m)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle blinks
|
||||
m = updateInputs(msg, m)
|
||||
return m, nil
|
||||
m, cmd = updateInputs(msg, m)
|
||||
return m, cmd
|
||||
}
|
||||
}
|
||||
|
||||
func updateInputs(msg boba.Msg, m Model) Model {
|
||||
m.nameInput, _ = input.Update(msg, m.nameInput)
|
||||
m.nickNameInput, _ = input.Update(msg, m.nickNameInput)
|
||||
m.emailInput, _ = input.Update(msg, m.emailInput)
|
||||
return m
|
||||
}
|
||||
|
||||
func subscriptions(model boba.Model) boba.Subs {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// It's a little hacky, but we're using the subscription from one
|
||||
// input element to handle the blinking for all elements. It doesn't
|
||||
// have to be this way, we're just feeling a bit lazy at the moment.
|
||||
inputSub, err := input.MakeSub(m.nameInput)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return boba.Subs{
|
||||
// It's a little hacky, but we're using the subscription from one
|
||||
// input element to handle the blinking for all elements. It doesn't
|
||||
// have to be this way, we're just feeling a bit lazy at the moment.
|
||||
"blink": inputSub,
|
||||
}
|
||||
func updateInputs(msg boba.Msg, m Model) (Model, boba.Cmd) {
|
||||
var (
|
||||
cmd boba.Cmd
|
||||
cmds []boba.Cmd
|
||||
)
|
||||
m.nameInput, cmd = input.Update(msg, m.nameInput)
|
||||
cmds = append(cmds, cmd)
|
||||
m.nickNameInput, cmd = input.Update(msg, m.nickNameInput)
|
||||
cmds = append(cmds, cmd)
|
||||
m.emailInput, cmd = input.Update(msg, m.emailInput)
|
||||
cmds = append(cmds, cmd)
|
||||
return m, boba.Batch(cmds...)
|
||||
}
|
||||
|
||||
func view(model boba.Model) string {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package main
|
||||
|
||||
// TODO: This code feels messy. Clean it up.
|
||||
// TODO: The views feel messy. Clean 'em up.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -17,7 +17,6 @@ func main() {
|
||||
initialize,
|
||||
update,
|
||||
view,
|
||||
subscriptions,
|
||||
)
|
||||
if err := p.Start(); err != nil {
|
||||
fmt.Println("could not start program:", err)
|
||||
@@ -26,17 +25,9 @@ func main() {
|
||||
|
||||
// MSG
|
||||
|
||||
type tickMsg time.Time
|
||||
type tickMsg struct{}
|
||||
|
||||
func newTickMsg(t time.Time) boba.Msg {
|
||||
return tickMsg(t)
|
||||
}
|
||||
|
||||
type frameMsg time.Time
|
||||
|
||||
func newFrameMsg(t time.Time) boba.Msg {
|
||||
return frameMsg(t)
|
||||
}
|
||||
type frameMsg struct{}
|
||||
|
||||
// MODEL
|
||||
|
||||
@@ -53,21 +44,19 @@ type Model struct {
|
||||
// INIT
|
||||
|
||||
func initialize() (boba.Model, boba.Cmd) {
|
||||
return Model{0, false, 10, 0, 0, false}, nil
|
||||
return Model{0, false, 10, 0, 0, false}, tick
|
||||
}
|
||||
|
||||
// SUBSCRIPTIONS
|
||||
// CMDS
|
||||
|
||||
func subscriptions(model boba.Model) boba.Subs {
|
||||
m, _ := model.(Model)
|
||||
if !m.Chosen || m.Loaded {
|
||||
return boba.Subs{
|
||||
"tick": boba.Every(time.Second, newTickMsg),
|
||||
}
|
||||
}
|
||||
return boba.Subs{
|
||||
"frame": boba.Every(time.Second/60, newFrameMsg),
|
||||
}
|
||||
func tick() boba.Msg {
|
||||
time.Sleep(time.Second)
|
||||
return tickMsg{}
|
||||
}
|
||||
|
||||
func frame() boba.Msg {
|
||||
time.Sleep(time.Second / 60)
|
||||
return frameMsg{}
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
@@ -118,7 +107,7 @@ func updateChoices(msg boba.Msg, m Model) (boba.Model, boba.Cmd) {
|
||||
m.Ticks -= 1
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return m, tick
|
||||
}
|
||||
|
||||
func updateChosen(msg boba.Msg, m Model) (boba.Model, boba.Cmd) {
|
||||
@@ -137,7 +126,7 @@ func updateChosen(msg boba.Msg, m Model) (boba.Model, boba.Cmd) {
|
||||
case frameMsg:
|
||||
if !m.Loaded {
|
||||
m.Frames += 1
|
||||
m.Progress = ease.OutBounce(float64(m.Frames) / float64(120))
|
||||
m.Progress = ease.OutBounce(float64(m.Frames) / float64(160))
|
||||
if m.Progress >= 1 {
|
||||
m.Progress = 1
|
||||
m.Loaded = true
|
||||
@@ -154,7 +143,7 @@ func updateChosen(msg boba.Msg, m Model) (boba.Model, boba.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return m, frame
|
||||
}
|
||||
|
||||
// VIEW
|
||||
|
||||
@@ -9,15 +9,6 @@ import (
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func NewProgram(initialContent string) *boba.Program {
|
||||
return boba.NewProgram(
|
||||
Init(initialContent),
|
||||
Update,
|
||||
View,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// MSG
|
||||
|
||||
type terminalSizeMsg struct {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package spinner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/boba"
|
||||
@@ -24,8 +23,6 @@ var (
|
||||
Dot: {"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
|
||||
}
|
||||
|
||||
assertionErr = errors.New("could not perform assertion on model to what the spinner expects. are you sure you passed the right value?")
|
||||
|
||||
color = termenv.ColorProfile().Color
|
||||
)
|
||||
|
||||
@@ -50,7 +47,7 @@ func NewModel() Model {
|
||||
}
|
||||
|
||||
// TickMsg indicates that the timer has ticked and we should render a frame
|
||||
type TickMsg time.Time
|
||||
type TickMsg struct{}
|
||||
|
||||
// Update is the Boba update function
|
||||
func Update(msg boba.Msg, m Model) (Model, boba.Cmd) {
|
||||
@@ -60,7 +57,7 @@ func Update(msg boba.Msg, m Model) (Model, boba.Cmd) {
|
||||
if m.frame >= len(spinners[m.Type]) {
|
||||
m.frame = 0
|
||||
}
|
||||
return m, nil
|
||||
return m, Tick(m)
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
@@ -86,15 +83,10 @@ func View(model Model) string {
|
||||
return str
|
||||
}
|
||||
|
||||
// GetSub creates the subscription that allows the spinner to spin. Remember
|
||||
// that you need to execute this function in order to get the subscription
|
||||
// you'll need.
|
||||
func MakeSub(model boba.Model) (boba.Sub, error) {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
return nil, assertionErr
|
||||
// Tick is the command used to advance the spinner one frame.
|
||||
func Tick(model Model) boba.Cmd {
|
||||
return func() boba.Msg {
|
||||
time.Sleep(time.Second / time.Duration(model.FPS))
|
||||
return TickMsg{}
|
||||
}
|
||||
return boba.Tick(time.Second/time.Duration(m.FPS), func(t time.Time) boba.Msg {
|
||||
return TickMsg(t)
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package boba
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Every is a subscription that ticks with the system clock at the given
|
||||
// duration, similar to cron. It's particularly useful if you have several
|
||||
// subscriptions that need to run in sync.
|
||||
func Every(duration time.Duration, newMsg func(time.Time) Msg) Sub {
|
||||
return func() Msg {
|
||||
n := time.Now()
|
||||
d := n.Truncate(duration).Add(duration).Sub(n)
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case now := <-t.C:
|
||||
return newMsg(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tick is a subscription that at an interval independent of the system clock
|
||||
// at the given duration. That is, it begins precisely when invoked.
|
||||
func Tick(d time.Duration, newMsg func(time.Time) Msg) Sub {
|
||||
return func() Msg {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case now := <-t.C:
|
||||
return newMsg(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package textinput
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/boba"
|
||||
@@ -77,8 +76,8 @@ func (m *Model) colorPlaceholder(s string) string {
|
||||
String()
|
||||
}
|
||||
|
||||
// CursorBlinkMsg is sent when the cursor should alternate it's blinking state
|
||||
type CursorBlinkMsg struct{}
|
||||
// BlinkMsg is sent when the cursor should alternate it's blinking state
|
||||
type BlinkMsg struct{}
|
||||
|
||||
// NewModel creates a new model with default settings
|
||||
func NewModel() Model {
|
||||
@@ -164,9 +163,9 @@ func Update(msg boba.Msg, m Model) (Model, boba.Cmd) {
|
||||
m.Err = msg
|
||||
return m, nil
|
||||
|
||||
case CursorBlinkMsg:
|
||||
case BlinkMsg:
|
||||
m.blink = !m.blink
|
||||
return m, nil
|
||||
return m, Blink(m)
|
||||
|
||||
default:
|
||||
return m, nil
|
||||
@@ -230,15 +229,10 @@ func cursorView(s string, m Model) string {
|
||||
String()
|
||||
}
|
||||
|
||||
// MakeSub return a subscription that lets us know when to alternate the
|
||||
// blinking of the cursor.
|
||||
func MakeSub(model boba.Model) (boba.Sub, error) {
|
||||
m, ok := model.(Model)
|
||||
if !ok {
|
||||
return nil, errors.New("could not assert given model to the model we expected; make sure you're passing as input model")
|
||||
}
|
||||
// Blink is a command used to time the cursor blinking.
|
||||
func Blink(model Model) boba.Cmd {
|
||||
return func() boba.Msg {
|
||||
time.Sleep(m.BlinkSpeed)
|
||||
return CursorBlinkMsg{}
|
||||
}, nil
|
||||
time.Sleep(model.BlinkSpeed)
|
||||
return BlinkMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user