forked from Mirrors/bubbletea
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:
parent
31800cd0a7
commit
30bb43e5ae
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue