tui.go

  1package tui
  2
  3import (
  4	"github.com/charmbracelet/bubbles/key"
  5	tea "github.com/charmbracelet/bubbletea"
  6	"github.com/charmbracelet/lipgloss"
  7	"github.com/kujtimiihoxha/termai/internal/tui/components/core"
  8	"github.com/kujtimiihoxha/termai/internal/tui/layout"
  9	"github.com/kujtimiihoxha/termai/internal/tui/page"
 10	"github.com/kujtimiihoxha/termai/internal/tui/util"
 11)
 12
 13type keyMap struct {
 14	Logs   key.Binding
 15	Return key.Binding
 16	Back   key.Binding
 17	Quit   key.Binding
 18	Help   key.Binding
 19}
 20
 21var keys = keyMap{
 22	Logs: key.NewBinding(
 23		key.WithKeys("L"),
 24		key.WithHelp("L", "logs"),
 25	),
 26	Return: key.NewBinding(
 27		key.WithKeys("esc"),
 28		key.WithHelp("esc", "close"),
 29	),
 30	Back: key.NewBinding(
 31		key.WithKeys("backspace"),
 32		key.WithHelp("backspace", "back"),
 33	),
 34	Quit: key.NewBinding(
 35		key.WithKeys("ctrl+c", "q"),
 36		key.WithHelp("ctrl+c/q", "quit"),
 37	),
 38	Help: key.NewBinding(
 39		key.WithKeys("?"),
 40		key.WithHelp("?", "toggle help"),
 41	),
 42}
 43
 44type appModel struct {
 45	width, height int
 46	currentPage   page.PageID
 47	previousPage  page.PageID
 48	pages         map[page.PageID]tea.Model
 49	loadedPages   map[page.PageID]bool
 50	status        tea.Model
 51	help          core.HelpCmp
 52	showHelp      bool
 53}
 54
 55func (a appModel) Init() tea.Cmd {
 56	cmd := a.pages[a.currentPage].Init()
 57	a.loadedPages[a.currentPage] = true
 58	return cmd
 59}
 60
 61func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 62	switch msg := msg.(type) {
 63	case tea.WindowSizeMsg:
 64		msg.Height -= 1 // Make space for the status bar
 65		a.width, a.height = msg.Width, msg.Height
 66
 67		a.status, _ = a.status.Update(msg)
 68
 69		uh, _ := a.help.Update(msg)
 70		a.help = uh.(core.HelpCmp)
 71
 72		p, cmd := a.pages[a.currentPage].Update(msg)
 73		a.pages[a.currentPage] = p
 74		return a, cmd
 75	case util.InfoMsg:
 76		a.status, _ = a.status.Update(msg)
 77	case util.ErrorMsg:
 78		a.status, _ = a.status.Update(msg)
 79	case tea.KeyMsg:
 80		switch {
 81		case key.Matches(msg, keys.Quit):
 82			return a, tea.Quit
 83		case key.Matches(msg, keys.Back):
 84			if a.previousPage != "" {
 85				return a, a.moveToPage(a.previousPage)
 86			}
 87		case key.Matches(msg, keys.Return):
 88			if a.showHelp {
 89				a.ToggleHelp()
 90				return a, nil
 91			}
 92			return a, nil
 93		case key.Matches(msg, keys.Logs):
 94			return a, a.moveToPage(page.LogsPage)
 95		case key.Matches(msg, keys.Help):
 96			a.ToggleHelp()
 97			return a, nil
 98		}
 99	}
100	p, cmd := a.pages[a.currentPage].Update(msg)
101	a.pages[a.currentPage] = p
102	return a, cmd
103}
104
105func (a *appModel) ToggleHelp() {
106	if a.showHelp {
107		a.showHelp = false
108		a.height += a.help.Height()
109	} else {
110		a.showHelp = true
111		a.height -= a.help.Height()
112	}
113
114	if sizable, ok := a.pages[a.currentPage].(layout.Sizeable); ok {
115		sizable.SetSize(a.width, a.height)
116	}
117}
118
119func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
120	var cmd tea.Cmd
121	if _, ok := a.loadedPages[pageID]; !ok {
122		cmd = a.pages[pageID].Init()
123		a.loadedPages[pageID] = true
124	}
125	a.previousPage = a.currentPage
126	a.currentPage = pageID
127	if sizable, ok := a.pages[a.currentPage].(layout.Sizeable); ok {
128		sizable.SetSize(a.width, a.height)
129	}
130
131	return cmd
132}
133
134func (a appModel) View() string {
135	components := []string{
136		a.pages[a.currentPage].View(),
137	}
138
139	if a.showHelp {
140		bindings := layout.KeyMapToSlice(keys)
141		if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
142			bindings = append(bindings, p.BindingKeys()...)
143		}
144		a.help.SetBindings(bindings)
145		components = append(components, a.help.View())
146	}
147
148	components = append(components, a.status.View())
149
150	return lipgloss.JoinVertical(lipgloss.Top, components...)
151}
152
153func New() tea.Model {
154	return &appModel{
155		currentPage: page.ReplPage,
156		loadedPages: make(map[page.PageID]bool),
157		status:      core.NewStatusCmp(),
158		help:        core.NewHelpCmp(),
159		pages: map[page.PageID]tea.Model{
160			page.LogsPage: page.NewLogsPage(),
161			page.InitPage: page.NewInitPage(),
162			page.ReplPage: page.NewReplPage(),
163		},
164	}
165}