128 lines
2.9 KiB
Go
128 lines
2.9 KiB
Go
// 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
|
|
}
|