1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
use egui::{Context, Pos2, Rect, Vec2};
use crate::{TitleBar, titlebar::render_bar::title_bar_height};
impl TitleBar {
/// Handle keyboard navigation for menus
///
/// This method handles arrow keys, Enter, and Escape for menu navigation.
///
/// # Arguments
/// * `ctx` - The egui context
pub fn handle_keyboard_navigation(&mut self, ctx: &Context) {
let current_time = ctx.input(|i| i.time);
// Check if Alt key or Ctrl+F2 is pressed to activate menu navigation
let should_activate = ctx.input(|i| i.modifiers.alt)
|| (ctx.input(|i| i.modifiers.ctrl) && ctx.input(|i| i.key_pressed(egui::Key::F2)));
if should_activate {
if !self.keyboard_navigation_active {
self.keyboard_navigation_active = true;
self.selected_menu_index = Some(0);
self.selected_submenu_index = None;
self.last_keyboard_nav_time = current_time;
}
}
// Handle navigation when active
if self.keyboard_navigation_active {
// Handle Escape to close menus
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
self.keyboard_navigation_active = false;
self.selected_menu_index = None;
self.selected_submenu_index = None;
self.open_submenu = None;
return;
}
// Handle clicks outside menu areas to close menus (but keep keyboard nav active)
if ctx.input(|i| i.pointer.primary_clicked()) {
let click_pos = ctx.input(|i| i.pointer.interact_pos()).unwrap_or_default();
let menu_bar_rect = Rect::from_min_size(
Pos2::new(0.0, 0.0),
Vec2::new(ctx.content_rect().width(), title_bar_height()),
);
// If click is outside menu bar and any submenu is open, close all menus
if !menu_bar_rect.contains(click_pos) && self.open_submenu.is_some() {
self.open_submenu = None;
self.force_open_child_subitem = None;
self.child_submenu_selections.clear();
// Keep keyboard_navigation_active = true (don't disable it)
}
}
// Handle left/right arrow keys for top-level menu navigation
// Disable only when we're on a highlighted submenu item that has a sidemenu
let current_highlighted_has_sidemenu = if let Some(open_submenu_index) =
self.open_submenu
{
if let Some(menu_item) = self.menu_items_with_submenus.get(open_submenu_index) {
if let Some(selected_index) = self.submenu_selections.get(&open_submenu_index) {
if let Some(selected_item) = menu_item.subitems.get(*selected_index) {
!selected_item.children.is_empty()
} else {
false
}
} else {
false
}
} else {
false
}
} else {
false
};
if !current_highlighted_has_sidemenu {
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft)) {
if let Some(current_index) = self.selected_menu_index {
if current_index > 0 {
self.selected_menu_index = Some(current_index - 1);
self.open_submenu = None;
self.selected_submenu_index = None;
}
}
}
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight)) {
let total_menus = self.menu_items.len() + self.menu_items_with_submenus.len();
if let Some(current_index) = self.selected_menu_index {
if current_index < total_menus - 1 {
self.selected_menu_index = Some(current_index + 1);
self.open_submenu = None;
self.selected_submenu_index = None;
}
}
}
}
// Handle Enter and Space keys - unified logic for all contexts
if ctx.input(|i| i.key_pressed(egui::Key::Enter))
|| ctx.input(|i| i.key_pressed(egui::Key::Space))
{
// Priority 1: If we're in a child sidemenu, handle that first
if let Some(open_submenu_index) = self.open_submenu {
if let Some(child_submenu_index) =
self.child_submenu_selections.get(&open_submenu_index)
{
if let Some(menu_item) =
self.menu_items_with_submenus.get(open_submenu_index)
{
if let Some(child_index) = self.force_open_child_subitem {
if let Some(child_item) = menu_item.subitems.get(child_index) {
if let Some(child_subitem) =
child_item.children.get(*child_submenu_index)
{
if child_subitem.enabled {
if let Some(ref callback) = child_subitem.callback {
callback();
}
// Close all submenus after action
self.open_submenu = None;
self.selected_submenu_index = None;
self.force_open_child_subitem = None;
self.child_submenu_selections.clear();
return;
}
}
}
}
}
}
}
// Priority 2: If we're in a submenu, handle submenu item
if let Some(open_submenu_index) = self.open_submenu {
if self.submenu_just_opened_frame {
// Skip if submenu was just opened this frame
self.submenu_just_opened_frame = false;
return;
}
if let Some(submenu_index) = self.submenu_selections.get(&open_submenu_index) {
if let Some(menu_item) =
self.menu_items_with_submenus.get(open_submenu_index)
{
if let Some(subitem) = menu_item.subitems.get(*submenu_index) {
if subitem.enabled && subitem.children.is_empty() {
// Only trigger if it has no children (no sidemenu)
if let Some(ref callback) = subitem.callback {
callback();
}
// Close submenu after action
self.open_submenu = None;
self.submenu_selections.remove(&open_submenu_index);
return;
}
}
}
}
}
// Priority 3: Handle main menu items
if let Some(menu_index) = self.selected_menu_index {
let total_simple_menus = self.menu_items.len();
if menu_index < total_simple_menus {
// Simple menu item - trigger callback
if let Some((_, callback)) = self.menu_items.get(menu_index) {
if let Some(callback) = callback {
callback();
}
}
} else {
// Menu with submenu
let submenu_index = menu_index - total_simple_menus;
if let Some(menu_item) = self.menu_items_with_submenus.get(submenu_index) {
if !menu_item.subitems.is_empty() {
self.open_submenu = Some(submenu_index);
self.submenu_selections.insert(submenu_index, 0);
// Mark as just opened to avoid immediately activating first item on Enter this frame
self.submenu_just_opened_frame = true;
// Only auto-open child submenu on keyboard navigation activation
if self.keyboard_navigation_active {
if let Some(first_item) = menu_item.subitems.get(0) {
if !first_item.children.is_empty() {
self.force_open_child_subitem = Some(0);
} else {
self.force_open_child_subitem = None;
}
} else {
self.force_open_child_subitem = None;
}
} else {
// Mouse-driven open: do not auto-open a child side menu
self.force_open_child_subitem = None;
}
}
}
}
}
}
// Handle up/down/left/right keys for submenu navigation and side menus
if let Some(open_submenu_index) = self.open_submenu {
if let Some(menu_item) = self.menu_items_with_submenus.get(open_submenu_index) {
// Handle up/down navigation in main submenu ONLY if no child sidemenu is open
// Child submenu navigation is handled separately below
if self.force_open_child_subitem.is_none()
&& !self
.child_submenu_selections
.contains_key(&open_submenu_index)
{
if ctx.input(|i| i.key_pressed(egui::Key::ArrowUp)) {
if let Some(current_submenu_index) =
self.submenu_selections.get(&open_submenu_index).copied()
{
if current_submenu_index > 0 {
self.submenu_selections
.insert(open_submenu_index, current_submenu_index - 1);
}
}
}
if ctx.input(|i| i.key_pressed(egui::Key::ArrowDown)) {
if let Some(current_submenu_index) =
self.submenu_selections.get(&open_submenu_index).copied()
{
if current_submenu_index < menu_item.subitems.len() - 1 {
self.submenu_selections
.insert(open_submenu_index, current_submenu_index + 1);
}
}
}
}
// Right arrow on a submenu item that has children -> force-open child sidemenu
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight)) {
if let Some(current_submenu_index) =
self.submenu_selections.get(&open_submenu_index)
{
if let Some(current_item) =
menu_item.subitems.get(*current_submenu_index)
{
if !current_item.children.is_empty() {
// Flag to force-open the child submenu in the renderer
self.force_open_child_subitem = Some(*current_submenu_index);
// Prevent immediate activation this frame
self.submenu_just_opened_frame = true;
}
}
}
}
// Left arrow: back out of child sidemenu first; if none, close this submenu
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft)) {
if self.force_open_child_subitem.is_some() {
self.force_open_child_subitem = None;
self.child_submenu_selections.remove(&open_submenu_index);
} else if self.keyboard_navigation_active
&& self
.child_submenu_selections
.contains_key(&open_submenu_index)
{
// Close child submenu navigation but keep parent submenu open
self.child_submenu_selections.remove(&open_submenu_index);
} else {
// Close current submenu, keep focus on the parent menu
self.open_submenu = None;
self.submenu_selections.remove(&open_submenu_index);
}
}
// Handle navigation within child submenu when it's open
// Check if any child submenu is currently open (either forced or hovered)
let mut active_child_index = self.force_open_child_subitem;
// If no forced child, check if we should enable child navigation
// This works for mouse hover, but NOT for keyboard navigation (keyboard needs explicit right arrow)
if active_child_index.is_none() && !self.keyboard_navigation_active {
// Find the first submenu item that has children and is currently selected
if let Some(selected_index) =
self.submenu_selections.get(&open_submenu_index)
{
if let Some(selected_item) = menu_item.subitems.get(*selected_index) {
if !selected_item.children.is_empty() {
active_child_index = Some(*selected_index);
}
}
}
}
if let Some(child_index) = active_child_index {
if let Some(child_item) = menu_item.subitems.get(child_index) {
if !child_item.children.is_empty() {
// Initialize child submenu selection if not set
if !self
.child_submenu_selections
.contains_key(&open_submenu_index)
{
self.child_submenu_selections.insert(open_submenu_index, 0);
}
// Handle up/down navigation in child submenu
if ctx.input(|i| i.key_pressed(egui::Key::ArrowUp)) {
if let Some(current_child_index) =
self.child_submenu_selections.get(&open_submenu_index)
{
if *current_child_index > 0 {
self.child_submenu_selections.insert(
open_submenu_index,
current_child_index - 1,
);
}
}
}
if ctx.input(|i| i.key_pressed(egui::Key::ArrowDown)) {
if let Some(current_child_index) =
self.child_submenu_selections.get(&open_submenu_index)
{
if *current_child_index < child_item.children.len() - 1 {
self.child_submenu_selections.insert(
open_submenu_index,
current_child_index + 1,
);
}
}
}
}
}
}
}
}
// Reset one-frame guards at the end of the keyboard nav cycle
if self.submenu_just_opened_frame {
self.submenu_just_opened_frame = false;
}
// force_open_child_subitem persists until user navigates away or presses Left; do not reset here
}
}
}