diff --git a/breadcrumb/model.go b/breadcrumb/model.go index 9456180..b7e7644 100644 --- a/breadcrumb/model.go +++ b/breadcrumb/model.go @@ -1,3 +1,6 @@ +// Package Breadcrumb is a component that consumes a pointer ot a navstack.Model +// and renders a breadcrumb trail. It is used to give the user a sense of where +// they are in the application. package breadcrumb import ( diff --git a/bubbleo.go b/bubbleo.go index f937200..082653b 100644 --- a/bubbleo.go +++ b/bubbleo.go @@ -1 +1,5 @@ +// Bubbleo is a collection of [BubbleTea] components for building robust terminal user interfaces. +// Initially we are supporting hierarchical menus, via a navigation stack and supporting components such as +// breadcrumbs, menus, and a composit component called shell which puts them all together. +// [BubbleTea]: https://github.com/charmbracelet/bubbletea package bubbleo diff --git a/menu/model.go b/menu/model.go index 5e294c5..b6722eb 100644 --- a/menu/model.go +++ b/menu/model.go @@ -1,9 +1,14 @@ +// Package Menu takes a list of choices allowing the user to select a component +// to push onto the navigation stack. Each choice has a title and a description and +// a component model implementing [tea.Model]. +// [tea.Model] https://github.com/charmbracelet/bubbletea/blob/a256e76ff5ff142d747ad833c7aa784113f8558c/tea.go#L39 package menu import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/kevm/bubbleo/navstack" "github.com/kevm/bubbleo/styles" "github.com/kevm/bubbleo/utils" @@ -24,16 +29,31 @@ func (i choiceItem) Title() string { return i.title } func (i choiceItem) Description() string { return i.desc } func (i choiceItem) FilterValue() string { return i.title + i.desc } +// MenuStyles is a struct that holds the styles for the menu +// This mostly a passthrough for bubble/list component styles. +type MenuStyles struct { + ListTitleStyle lipgloss.Style + ListItemStyles list.DefaultItemStyles +} + type Model struct { Choices []Choice - list list.Model + list list.Model selected *Choice + delegate list.DefaultDelegate } // New setups up a new menu model func New(title string, choices []Choice, selected *Choice) Model { + + styles := MenuStyles{ + ListTitleStyle: styles.ListTitleStyle, + ListItemStyles: list.NewDefaultItemStyles(), + } + delegation := list.NewDefaultDelegate() + delegation.Styles = styles.ListItemStyles items := make([]list.Item, len(choices)) selectedIndex := -1 for i, choice := range choices { @@ -47,6 +67,7 @@ func New(title string, choices []Choice, selected *Choice) Model { Choices: choices, list: list.New(items, delegation, 120, 20), selected: selected, + delegate: delegation, } if selected != nil { @@ -62,9 +83,6 @@ func New(title string, choices []Choice, selected *Choice) Model { model.list.SetShowStatusBar(false) model.list.SetShowHelp(false) - //TODO: figure out height long term. - // model.list.SetSize(window.Width, window.Height-window.TopOffset) - chooseKeyBinding := key.NewBinding( key.WithKeys("enter"), key.WithHelp("enter", "choose"), @@ -83,6 +101,13 @@ func (m Model) Init() tea.Cmd { return nil } +// SetStyles allows you to customize the styles used by the menu. This is mostly a passthrough +// to the bubble/list component used by the menu. +func (m Model) SetStyles(s MenuStyles) { + m.list.Styles.Title = s.ListTitleStyle + m.delegate.Styles = s.ListItemStyles +} + func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { @@ -110,14 +135,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } +// SelectedChoice returns the currently selected menu choice func (m Model) SelectedChoice() *Choice { return m.selected } +// SetSize sets the size of the menu func (m *Model) SetSize(w tea.WindowSizeMsg) { m.list.SetSize(w.Width, w.Height) } +// View renders the menu. When no choices are present, nothing is rendered. func (m Model) View() string { // display menu if choices are present. if len(m.Choices) > 0 { diff --git a/navstack/model.go b/navstack/model.go index 6fc3553..f5d71ef 100644 --- a/navstack/model.go +++ b/navstack/model.go @@ -1,3 +1,10 @@ +// Package Navstack manages a stack of NavigationItems which can be pushed or popped from the stack. +// The top most stack navigation item is used by [BubbleTea] to Update and renders it's View. +// When pushing and popping items from the stack, the new view to be presented is sent a tea.WindowSizeMsg +// to ensure it's view can be presented correctly. When the last item is popped from the stack the application will quit. +// NavigationItem models which implement the Closable interface will have their Close method called when they are popped from the stack. +// This is useful for cleaning up resources that may not be garbage collected when a view a no longer needed. +// [BubbleTea]: https://github.com/charmbracelet/bubbletea package navstack import ( @@ -5,6 +12,8 @@ import ( "github.com/kevm/bubbleo/window" ) +// Closable is an interface for models that have resources that need to be cleaned up when +// they are no longer needed. The navigation stack checks for this interface when popping items. type Closable interface { Close() error } @@ -14,6 +23,8 @@ type Model struct { window *window.Model } +// New creates a new navigation stack model. The window is used to +// constrain the view within the container of the navigation stack. func New(w *window.Model) Model { model := Model{ stack: []NavigationItem{}, @@ -32,6 +43,10 @@ func (m Model) Init() tea.Cmd { return top.Init() } +// Phsh pushes a new navigation item onto the stack. +// The new navigation item is given a tea.WindowSizeMsg to ensure it's view can be presented correctly. +// The item's Init method is called and resulting command is processed by [BubbleTea]. +// This new item will be the top most item on the stack and thus will be rendered. func (m *Model) Push(item NavigationItem) tea.Cmd { wmsg := m.window.GetWindowSizeMsg() @@ -42,6 +57,10 @@ func (m *Model) Push(item NavigationItem) tea.Cmd { return tea.Batch(cmd, item.Init()) } +// Pop removes the top most navigation item from the stack. +// If the item implements the Closable interface the Close method is called. +// The new top most item on the stack is given a tea.WindowSizeMsg to ensure it's view can be presented correctly. +// If there are no more items on the stack the application will quit. func (m *Model) Pop() tea.Cmd { top := m.Top() if top == nil { @@ -66,6 +85,7 @@ func (m *Model) Pop() tea.Cmd { return tea.Batch(cmds...) } +// Top returns the top most navigation item on the stack. func (m Model) Top() *NavigationItem { if len(m.stack) == 0 { return nil @@ -75,6 +95,8 @@ func (m Model) Top() *NavigationItem { return &top } +// StackSummary returns a list of titles for each item on the stack. +// This is currently used by the breadcrumb component to render the breadcrumb trail. func (m Model) StackSummary() []string { summary := []string{} for _, item := range m.stack { @@ -84,6 +106,7 @@ func (m Model) StackSummary() []string { return summary } +// Update processes messages for the top most navigation item on the stack. func (m *Model) Update(msg tea.Msg) tea.Cmd { top := m.Top() switch msg := msg.(type) { @@ -119,6 +142,7 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { } } +// View renders the top most navigation item on the stack. func (m Model) View() string { top := m.Top() diff --git a/navstack/navitem.go b/navstack/navitem.go index 1476d17..27b0770 100644 --- a/navstack/navitem.go +++ b/navstack/navitem.go @@ -2,21 +2,26 @@ package navstack import tea "github.com/charmbracelet/bubbletea" +// NavigationItem is a component that represents an item in the navigation stack. +// The top most item on the stack is rendered. type NavigationItem struct { Title string Model tea.Model } +// Init is called when the item is pushed onto the stack. func (n NavigationItem) Init() tea.Cmd { return n.Model.Init() } +// Update receives messages when the item is on top of the stack. func (n NavigationItem) Update(msg tea.Msg) (tea.Model, tea.Cmd) { nm, cmd := n.Model.Update(msg) n.Model = nm return n, cmd } +// View is calledn when the item is on top of the stack. func (n NavigationItem) View() string { return n.Model.View() } diff --git a/shell/model.go b/shell/model.go index b1126da..15cef0b 100644 --- a/shell/model.go +++ b/shell/model.go @@ -1,3 +1,6 @@ +// Package Shell is a basic wrapper around the navstack and breadcrumb packages +// It provides a basic navigation mechanism while showing breadcrumb view of where the user is +// within the navigation stack. package shell import ( @@ -16,6 +19,7 @@ type Model struct { window *window.Model } +// New creates a new shell model func New() Model { w := window.New(120, 30, 0, 0) ns := navstack.New(&w) @@ -29,6 +33,7 @@ func New() Model { } } +// Init determines the size of the widow used by the navigation stack. func (m Model) Init() tea.Cmd { w, h := m.Breadcrumb.FrameStyle.GetFrameSize() @@ -38,11 +43,13 @@ func (m Model) Init() tea.Cmd { return utils.Cmdize(m.window.GetWindowSizeMsg()) } +// Update passes messages to the navigation stack. func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmd := m.Navstack.Update(msg) return m, cmd } +// View renders the breadcrumb and the navigation stack. func (m Model) View() string { bc := m.Breadcrumb.View() nav := m.Navstack.View() diff --git a/styles/styles.go b/styles/styles.go index 78a1784..89cf956 100644 --- a/styles/styles.go +++ b/styles/styles.go @@ -8,10 +8,6 @@ var ( foregroundColor = lipgloss.AdaptiveColor{Light: "#343433", Dark: "#C1C6B2"} backgroundColor = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#353533"} - AppStyle = lipgloss.NewStyle().Padding(2, 5) - ListStyle = lipgloss.NewStyle().Margin(1, 2) - ListItemStyle = lipgloss.NewStyle().PaddingLeft(4) - ListTitleStyle = lipgloss.NewStyle().MarginLeft(2).Background(backgroundColor).Foreground(foregroundColor).Bold(true) - + ListTitleStyle = lipgloss.NewStyle().MarginLeft(2).Background(backgroundColor).Foreground(foregroundColor).Bold(true) BreadCrumbFrameStyle = lipgloss.NewStyle().Background(backgroundColor).Foreground(foregroundColor).Margin(1) )