1// internal/ui/menu.go
2package ui
3
4import (
5 "strings"
6 "github.com/The-Robin-Hood/ufWall/internal/keys"
7
8 "github.com/charmbracelet/bubbles/key"
9 tea "github.com/charmbracelet/bubbletea"
10)
11
12type MenuItem struct {
13 Label string
14 Action func() tea.Cmd
15}
16
17type Menu struct {
18 Options []MenuItem
19 Selected int
20 styles Styles
21}
22
23func NewMenu(options []MenuItem, styles Styles) Menu {
24 return Menu{
25 Options: options,
26 Selected: 0,
27 styles: styles,
28 }
29}
30
31func (m *Menu) Up() {
32 if m.Selected > 0 {
33 m.Selected--
34 }
35}
36
37func (m *Menu) Down() {
38 if m.Selected < len(m.Options)-1 {
39 m.Selected++
40 }
41}
42
43func (m Menu) ExecuteSelected() tea.Cmd {
44 if m.Selected >= 0 && m.Selected < len(m.Options) {
45 if m.Options[m.Selected].Action != nil {
46 return m.Options[m.Selected].Action()
47 }
48 }
49 return nil
50}
51
52func (m Menu) View(styles Styles) string {
53 var lines []string
54
55 for i, option := range m.Options {
56 prefix := " "
57 style := m.styles.Value
58
59 if i == m.Selected {
60 prefix = "▶ "
61 style = m.styles.ActiveStatus.Bold(true)
62 }
63
64 lines = append(lines, prefix+style.Render(option.Label))
65 }
66
67 content := strings.Join(lines, "\n")
68
69 return styles.Menu.Render(content)
70}
71
72func (m *Menu) Update(msg tea.Msg) (bool) {
73 switch msg := msg.(type) {
74
75 case tea.KeyMsg:
76 switch {
77 case key.Matches(msg, keys.Bindings.CursorUp):
78 m.Up()
79 case key.Matches(msg, keys.Bindings.CursorDown):
80 m.Down()
81 case key.Matches(msg, keys.Bindings.Execute):
82 m.ExecuteSelected()
83 return true
84 case key.Matches(msg, keys.Bindings.Quit):
85 return true
86 }
87 }
88 return false
89}
90
91func MakeMenuItems(labels []string, handler func(string) tea.Cmd) []MenuItem {
92 items := make([]MenuItem, len(labels))
93
94 for i, label := range labels {
95 l := label // capture loop variable!
96 items[i] = MenuItem{
97 Label: l,
98 Action: func() tea.Cmd {
99 return handler(l)
100 },
101 }
102 }
103
104 return items
105}