Composable view (#394)

* docs: creating nested models

* docs: move nested model to example

* docs: add working nested model example

* refactor: use tea.Batch in nested model example

* refactor: switch to composable view example

* refactor: tab select, add padding to boxes, only focused has border

* fix: add padding to timer to remove UI shift
This commit is contained in:
bashbunni
2022-08-24 14:57:32 -07:00
committed by GitHub
parent 31800cd0a7
commit 30bb43e5ae

View File

@@ -0,0 +1,158 @@
package main
import (
"fmt"
"log"
"time"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/timer"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
/*
This example assumes an existing understanding of commands and messages. If you
haven't already read our tutorials on the basics of Bubble Tea and working
with commands, we recommend reading those first.
Find them at:
https://github.com/charmbracelet/bubbletea/tree/master/tutorials/commands
https://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics
*/
// sessionState is used to track which model is focused
type sessionState uint
const (
defaultTime = time.Minute
timerView sessionState = iota
spinnerView
)
var (
// Available spinners
spinners = []spinner.Spinner{
spinner.Line,
spinner.Dot,
spinner.MiniDot,
spinner.Jump,
spinner.Pulse,
spinner.Points,
spinner.Globe,
spinner.Moon,
spinner.Monkey,
}
modelStyle = lipgloss.NewStyle().
Padding(1, 2).
BorderStyle(lipgloss.HiddenBorder())
focusedModelStyle = lipgloss.NewStyle().
Padding(1, 2).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("69"))
spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
)
type mainModel struct {
state sessionState
timer timer.Model
spinner spinner.Model
index int
}
func newModel(timeout time.Duration) mainModel {
m := mainModel{state: timerView}
m.timer = timer.New(timeout)
m.spinner = spinner.New()
return m
}
func (m mainModel) Init() tea.Cmd {
// start the timer and spinner on program start
return tea.Batch(m.timer.Init(), m.spinner.Tick)
}
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "tab":
if m.state == timerView {
m.state = spinnerView
} else {
m.state = timerView
}
case "n":
if m.state == timerView {
m.timer = timer.New(defaultTime)
cmds = append(cmds, m.timer.Init())
} else {
m.Next()
m.resetSpinner()
cmds = append(cmds, spinner.Tick)
}
}
switch m.state {
// update whichever model is focused
case spinnerView:
m.spinner, cmd = m.spinner.Update(msg)
cmds = append(cmds, cmd)
default:
m.timer, cmd = m.timer.Update(msg)
cmds = append(cmds, cmd)
}
case spinner.TickMsg:
m.spinner, cmd = m.spinner.Update(msg)
cmds = append(cmds, cmd)
case timer.TickMsg:
m.timer, cmd = m.timer.Update(msg)
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
func (m mainModel) View() string {
var s string
model := m.currentFocusedModel()
if m.state == timerView {
s += lipgloss.JoinHorizontal(lipgloss.Top, focusedModelStyle.Render(fmt.Sprintf("%4s", m.timer.View())), modelStyle.Render( m.spinner.View()))
} else {
s += lipgloss.JoinHorizontal(lipgloss.Top, modelStyle.Render(fmt.Sprintf("%4s", m.timer.View())), focusedModelStyle.Render(m.spinner.View()))
}
s += helpStyle.Render(fmt.Sprintf("\ntab: change focused model • n: new %s • q: exit\n", model))
return s
}
func (m mainModel) currentFocusedModel() string {
if m.state == timerView {
return "timer"
}
return "spinner"
}
func (m *mainModel) Next() {
if m.index == len(spinners)-1 {
m.index = 0
} else {
m.index++
}
}
func (m *mainModel) resetSpinner() {
m.spinner = spinner.New()
m.spinner.Style = spinnerStyle
m.spinner.Spinner = spinners[m.index]
}
func main() {
p := tea.NewProgram(newModel(defaultTime))
if err := p.Start(); err != nil {
log.Fatal(err)
}
}