Skip to content

Instantly share code, notes, and snippets.

@vknabel
Last active June 3, 2022 10:37
Show Gist options
  • Save vknabel/e1fbd305a707a11a7c741bd43eba4675 to your computer and use it in GitHub Desktop.
Save vknabel/e1fbd305a707a11a7c741bd43eba4675 to your computer and use it in GitHub Desktop.
A stack navigation model for bubbletea
package nav
import tea "github.com/charmbracelet/bubbletea"
type NavPage struct {
windowSize *tea.WindowSizeMsg
previous tea.Model
current tea.Model
}
func NewPage(current tea.Model) NavPage {
return NavPage{
windowSize: nil,
previous: nil,
current: current,
}
}
func (m NavPage) Init() tea.Cmd {
return tea.Batch(m.current.Init(), func() tea.Msg {
if m.windowSize != nil {
return *m.windowSize
}
return nil
})
}
func (m NavPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds := []tea.Cmd{}
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.windowSize = &msg
case PopPageMsg:
m.current = m.previous
if m.windowSize != nil {
m.current, cmd = m.current.Update(*m.windowSize)
cmds = append(cmds, cmd)
}
case PushPageMsg:
new := NewPage(msg.Page)
new.previous = m
new.windowSize = m.windowSize
return new, new.Init()
}
m.current, cmd = m.current.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m NavPage) View() string {
return m.current.View()
}
package nav
import tea "github.com/charmbracelet/bubbletea"
type NavStack struct {
stack []tea.Model
windowSize *tea.WindowSizeMsg
disabledQuitKeyBindings bool
}
func New(root tea.Model) NavStack {
return NavStack{
stack: []tea.Model{root},
windowSize: nil,
}
}
func (m *NavStack) DisableQuitKeyBindings() {
m.disabledQuitKeyBindings = true
}
type popPageMsg struct{}
type pushPageMsg struct {
page tea.Model
}
func Pop() tea.Cmd {
return func() tea.Msg {
return popPageMsg{}
}
}
func Push(page tea.Model) tea.Cmd {
return func() tea.Msg {
return pushPageMsg{page}
}
}
func (m NavStack) Top() tea.Model {
if len(m.stack) == 0 {
return nil
}
return m.stack[len(m.stack)-1]
}
func (m *NavStack) Push(page tea.Model) tea.Cmd {
m.stack = append(m.stack, page)
return m.initTop()
}
func (m *NavStack) Pop() tea.Cmd {
if len(m.stack) == 1 {
if m.disabledQuitKeyBindings {
return nil
} else {
return tea.Quit
}
}
m.stack = m.stack[:len(m.stack)-1]
if m.windowSize == nil {
return nil
}
top, cmd := m.Top().Update(*m.windowSize)
m.stack[len(m.stack)-1] = top
return cmd
}
func (m *NavStack) Replace(page tea.Model) tea.Cmd {
m.stack[len(m.stack)-1] = page
return m.initTop()
}
func (m NavStack) Init() tea.Cmd {
return m.initTop()
}
func (m NavStack) initTop() tea.Cmd {
cmds := []tea.Cmd{m.Top().Init()}
if m.windowSize != nil {
top, cmd := m.Top().Update(*m.windowSize)
m.stack[len(m.stack)-1] = top
cmds = append(cmds, cmd)
}
return tea.Batch(cmds...)
}
func (m NavStack) Update(msg tea.Msg) (NavStack, tea.Cmd) {
switch msg := msg.(type) {
case popPageMsg:
return m, m.Pop()
case pushPageMsg:
return m, m.Push(msg.page)
case tea.KeyMsg:
switch msg.String() {
case "esc":
return m, m.Pop()
case "q":
if !m.disabledQuitKeyBindings && len(m.stack) < 2 {
return m, tea.Quit
}
}
case tea.WindowSizeMsg:
m.windowSize = &msg
}
var cmd tea.Cmd
m.stack[len(m.stack)-1], cmd = m.Top().Update(msg)
return m, cmd
}
func (m NavStack) View() string {
return m.Top().View()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment