turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

menu_events.go · 181 lines · 4.2 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 14h ago1package ui
2
3// The keyboard and the mouse on the menu bar.
4
5import "github.com/gdamore/tcell/v2"
6
7// HandleKey drives the bar: F10 and Alt-letter open it, and once open the
8// arrow keys, Enter and Escape work it.
9func (b *MenuBar) HandleKey(ev *tcell.EventKey) bool {
10 if !b.Open() {
11 return b.handleClosedKey(ev)
12 }
13 return b.handleOpenKey(ev)
14}
15
16// handleClosedKey opens the bar on F10 or on the Alt-letter of a menu.
17func (b *MenuBar) handleClosedKey(ev *tcell.EventKey) bool {
18 if ev.Key() == tcell.KeyF10 {
19 b.OpenMenu(0)
20 return true
21 }
22 if ev.Modifiers()&tcell.ModAlt == 0 || ev.Key() != tcell.KeyRune {
23 return false
24 }
25
26 for i, menu := range b.menus {
27 if MatchesHotKey(menu.Label, ev.Rune()) {
28 b.OpenMenu(i)
29 return true
30 }
31 }
32 return false
33}
34
35// handleOpenKey works whichever drop-down the keyboard is in.
36func (b *MenuBar) handleOpenKey(ev *tcell.EventKey) bool {
37 if b.submenuOpen() {
38 return b.handleSubmenuKey(ev)
39 }
40
41 switch ev.Key() {
42 case tcell.KeyEscape:
43 b.Close()
44 case tcell.KeyLeft:
45 b.OpenMenu((b.openMenu - 1 + len(b.menus)) % len(b.menus))
46 case tcell.KeyRight:
47 b.stepRight()
48 case tcell.KeyUp:
49 b.highlight = b.nextUsableIn(b.menus[b.openMenu].Items, b.highlight, -1)
50 case tcell.KeyDown:
51 b.highlight = b.nextUsableIn(b.menus[b.openMenu].Items, b.highlight, +1)
52 case tcell.KeyEnter:
53 b.activate(b.highlight)
54 case tcell.KeyRune:
55 return b.selectByHotKey(ev.Rune())
56 default:
57 return false
58 }
59 return true
60}
61
62// selectByHotKey runs the drop-down item whose hot key was typed.
63func (b *MenuBar) selectByHotKey(key rune) bool {
64 for i, item := range b.menus[b.openMenu].Items {
65 if item.enabled() && MatchesHotKey(item.Label, key) {
66 b.activate(i)
67 return true
68 }
69 }
70 return true // an open menu swallows stray typing rather than letting it through
71}
72
73// activate opens item i's submenu, or closes the menu and runs its action.
74func (b *MenuBar) activate(i int) {
75 items := b.menus[b.openMenu].Items
76 if i < 0 || i >= len(items) || !items[i].enabled() {
77 return
78 }
79 if items[i].hasSubmenu() {
80 b.openSubmenu(i)
81 return
82 }
83
84 action := items[i].Action
85 b.Close()
86 action()
87}
88
89// nextUsableIn returns the index of the next item of a list that can be
90// chosen, walking in the given direction and wrapping round. It returns from
91// where it started if the list holds nothing usable at all.
92func (b *MenuBar) nextUsableIn(items []*MenuItem, from, step int) int {
93 if len(items) == 0 {
94 return 0
95 }
96
97 index := from
98 for range len(items) {
99 index = (index + step + len(items)) % len(items)
100 if items[index].enabled() {
101 return index
102 }
103 }
104 return max(from, 0)
105}
106
107// HandleMouse opens, walks and runs menus with the pointer.
108func (b *MenuBar) HandleMouse(ev *tcell.EventMouse) bool {
109 x, y := ev.Position()
110 pressed := ev.Buttons() == tcell.Button1
111
112 if b.Bounds().Contains(x, y) {
113 return b.handleBarMouse(x, pressed)
114 }
115 if !b.Open() {
116 return false
117 }
118 if b.submenuOpen() && b.submenuBounds().Contains(x, y) {
119 return b.handleSubmenuMouse(x, y, pressed)
120 }
121 return b.handleDropdownMouse(x, y, pressed)
122}
123
124// handleBarMouse reacts to the pointer on the row of titles.
125func (b *MenuBar) handleBarMouse(x int, pressed bool) bool {
126 for i, menu := range b.menus {
127 start := b.menuX(i)
128 if x < start-1 || x > start+LabelWidth(menu.Label) {
129 continue
130 }
131 if pressed {
132 b.toggle(i)
133 }
134 return true
135 }
136 if pressed {
137 b.Close()
138 }
139 return true
140}
141
142// toggle opens menu i, or closes it when it is already the open one.
143func (b *MenuBar) toggle(i int) {
144 if b.openMenu == i {
145 b.Close()
146 return
147 }
148 b.OpenMenu(i)
149}
150
151// handleDropdownMouse reacts to the pointer over an open drop-down.
152func (b *MenuBar) handleDropdownMouse(x, y int, pressed bool) bool {
153 bounds := b.dropdownBounds()
154 if !bounds.Contains(x, y) {
155 if pressed {
156 b.Close() // a click anywhere else puts the menu away
157 }
158 return pressed
159 }
160
161 index := y - bounds.Y - 1
162 items := b.menus[b.openMenu].Items
163 if index < 0 || index >= len(items) || !items[index].enabled() {
164 return true
165 }
166
167 b.highlight = index
168 // Moving onto a branch opens it and moving off it closes it, so the pointer
169 // never leaves a submenu showing for an item it has left.
170 switch {
171 case items[index].hasSubmenu():
172 b.openSubmenu(index)
173 case b.submenuOpen():
174 b.closeSubmenu()
175 }
176
177 if pressed {
178 b.activate(index)
179 }
180 return true
181}