diff --git a/examples/composable-views/main.go b/examples/composable-views/main.go new file mode 100644 index 0000000..9a876c2 --- /dev/null +++ b/examples/composable-views/main.go @@ -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) + } +}