// Package navigation implements the menu navigation stack for Nebara OS TUI. package navigation import "github.com/nebara/nebara-os/internal/model" // Level represents one level in the navigation stack. type Level struct { Items []model.MenuItem SelectedIdx int } // Navigator maintains a stack of menu levels and tracks the current position. type Navigator struct { stack []Level } // NewNavigator creates a Navigator rooted at the main menu. func NewNavigator(items []model.MenuItem, initialIdx int) *Navigator { if initialIdx < 0 || initialIdx >= len(items) { initialIdx = 0 } return &Navigator{ stack: []Level{ {Items: items, SelectedIdx: initialIdx}, }, } } // Current returns the active menu level. func (n *Navigator) Current() *Level { if len(n.stack) == 0 { return nil } return &n.stack[len(n.stack)-1] } // Depth returns the current nesting depth (0 = main menu). func (n *Navigator) Depth() int { return len(n.stack) - 1 } // IsAtRoot reports whether the navigator is at the main menu level. func (n *Navigator) IsAtRoot() bool { return len(n.stack) == 1 } // SelectedItem returns the currently highlighted menu item. func (n *Navigator) SelectedItem() *model.MenuItem { cur := n.Current() if cur == nil { return nil } return &cur.Items[cur.SelectedIdx] } // MoveUp moves the selection one row up, clamping at the first item. func (n *Navigator) MoveUp() { cur := n.Current() if cur == nil { return } if cur.SelectedIdx > 0 { cur.SelectedIdx-- } } // MoveDown moves the selection one row down, clamping at the last item. func (n *Navigator) MoveDown() { cur := n.Current() if cur == nil { return } if cur.SelectedIdx < len(cur.Items)-1 { cur.SelectedIdx++ } } // MoveFirst jumps to the first item. func (n *Navigator) MoveFirst() { cur := n.Current() if cur != nil { cur.SelectedIdx = 0 } } // MoveLast jumps to the last item. func (n *Navigator) MoveLast() { cur := n.Current() if cur != nil { cur.SelectedIdx = len(cur.Items) - 1 } } // Enter descends into the submenu of the currently selected item. // Returns false when the item has no children (leaf node). func (n *Navigator) Enter() bool { item := n.SelectedItem() if item == nil || len(item.Children) == 0 { return false } n.stack = append(n.stack, Level{ Items: item.Children, SelectedIdx: 0, }) return true } // Back pops the current level from the stack, returning to the parent. // Returns false when already at the root. func (n *Navigator) Back() bool { if len(n.stack) <= 1 { return false } n.stack = n.stack[:len(n.stack)-1] return true } // BreadcrumbTitle builds a display title for the current navigation depth. // It returns empty string when at the root. func (n *Navigator) BreadcrumbTitle() string { if len(n.stack) <= 1 { return "" } // Return the label of the parent item whose children we are browsing. parent := n.stack[len(n.stack)-2] return parent.Items[parent.SelectedIdx].Label }