1use crate::Language;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum MenuAction {
14 SaveConfig,
16 LoadConfig,
17 Exit,
18
19 NewSession,
21 DuplicateSession,
22 RenameSession,
23 CloseSession,
24
25 ViewSingle,
27 ViewSplitHorizontal,
28 ViewSplitVertical,
29 ViewGrid2x2,
30 ViewNextPane,
31 ViewPrevPane,
32
33 ToggleLanguage,
35
36 ShowShortcuts,
38 ShowAbout,
39
40 Separator, }
43
44impl MenuAction {
45 pub fn label_key(&self) -> &'static str {
47 match self {
48 MenuAction::SaveConfig => "menu.file.save_config",
49 MenuAction::LoadConfig => "menu.file.load_config",
50 MenuAction::Exit => "menu.file.exit",
51 MenuAction::NewSession => "menu.session.new",
52 MenuAction::DuplicateSession => "menu.session.duplicate",
53 MenuAction::RenameSession => "menu.session.rename",
54 MenuAction::CloseSession => "menu.session.close",
55 MenuAction::ViewSingle => "menu.view.single",
56 MenuAction::ViewSplitHorizontal => "menu.view.split_h",
57 MenuAction::ViewSplitVertical => "menu.view.split_v",
58 MenuAction::ViewGrid2x2 => "menu.view.grid_2x2",
59 MenuAction::ViewNextPane => "menu.view.next_pane",
60 MenuAction::ViewPrevPane => "menu.view.prev_pane",
61 MenuAction::ToggleLanguage => "menu.settings.toggle_language",
62 MenuAction::ShowShortcuts => "menu.help.shortcuts",
63 MenuAction::ShowAbout => "menu.help.about",
64 MenuAction::Separator => "",
65 }
66 }
67
68 pub fn is_separator(&self) -> bool {
70 matches!(self, MenuAction::Separator)
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct Menu {
77 pub label_key: &'static str,
78 pub items: &'static [MenuAction],
79}
80
81impl Menu {
82 pub fn item_count(&self) -> usize {
84 self.items.len()
85 }
86
87 pub fn get_item(&self, index: usize) -> Option<MenuAction> {
89 self.items.get(index).copied()
90 }
91}
92
93pub struct MenuBar {
95 pub menus: &'static [Menu],
96}
97
98impl MenuBar {
99 pub fn menu_count(&self) -> usize {
101 self.menus.len()
102 }
103
104 pub fn get_menu(&self, index: usize) -> Option<&Menu> {
106 self.menus.get(index)
107 }
108
109 pub fn get_menu_label_key(&self, index: usize) -> Option<&'static str> {
111 self.menus.get(index).map(|m| m.label_key)
112 }
113
114 pub fn get_item_count(&self, menu_index: usize) -> usize {
116 self.menus
117 .get(menu_index)
118 .map(|m| m.item_count())
119 .unwrap_or(0)
120 }
121
122 pub fn get_action(&self, menu_index: usize, item_index: usize) -> Option<MenuAction> {
124 self.menus
125 .get(menu_index)
126 .and_then(|m| m.get_item(item_index))
127 }
128}
129
130const FILE_MENU_ITEMS: &[MenuAction] = &[
132 MenuAction::SaveConfig,
133 MenuAction::LoadConfig,
134 MenuAction::Separator,
135 MenuAction::Exit,
136];
137
138const SESSION_MENU_ITEMS: &[MenuAction] = &[
139 MenuAction::NewSession,
140 MenuAction::DuplicateSession,
141 MenuAction::RenameSession,
142 MenuAction::Separator,
143 MenuAction::CloseSession,
144];
145
146const VIEW_MENU_ITEMS: &[MenuAction] = &[
147 MenuAction::ViewSingle,
148 MenuAction::ViewSplitHorizontal,
149 MenuAction::ViewSplitVertical,
150 MenuAction::ViewGrid2x2,
151 MenuAction::Separator,
152 MenuAction::ViewNextPane,
153 MenuAction::ViewPrevPane,
154];
155
156const SETTINGS_MENU_ITEMS: &[MenuAction] = &[MenuAction::ToggleLanguage];
157
158const HELP_MENU_ITEMS: &[MenuAction] = &[
159 MenuAction::ShowShortcuts,
160 MenuAction::Separator,
161 MenuAction::ShowAbout,
162];
163
164const ALL_MENUS: &[Menu] = &[
166 Menu {
167 label_key: "menu.file",
168 items: FILE_MENU_ITEMS,
169 },
170 Menu {
171 label_key: "menu.session",
172 items: SESSION_MENU_ITEMS,
173 },
174 Menu {
175 label_key: "menu.view",
176 items: VIEW_MENU_ITEMS,
177 },
178 Menu {
179 label_key: "menu.settings",
180 items: SETTINGS_MENU_ITEMS,
181 },
182 Menu {
183 label_key: "menu.help",
184 items: HELP_MENU_ITEMS,
185 },
186];
187
188pub const MENU_BAR: MenuBar = MenuBar { menus: ALL_MENUS };
190
191pub fn calculate_menu_x_offset(menu_index: usize, lang: Language) -> u16 {
193 use crate::i18n::t;
194
195 let mut offset = 0u16;
196 for i in 0..menu_index.min(MENU_BAR.menu_count()) {
197 if let Some(label_key) = MENU_BAR.get_menu_label_key(i) {
198 let label = t(label_key, lang);
199 offset += label.chars().count() as u16 + 4;
201 }
202 }
203 offset
204}
205
206pub fn find_clicked_menu(x: u16, lang: Language) -> Option<usize> {
208 use crate::i18n::t;
209
210 let mut current_x = 0u16;
211 for (i, menu) in MENU_BAR.menus.iter().enumerate() {
212 let label = t(menu.label_key, lang);
213 let menu_width = label.chars().count() as u16 + 2; if x >= current_x && x < current_x + menu_width {
216 return Some(i);
217 }
218
219 current_x += menu_width + 2; }
221 None
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_menu_bar_structure() {
230 assert_eq!(MENU_BAR.menu_count(), 5);
231 assert_eq!(MENU_BAR.get_item_count(0), 4); assert_eq!(MENU_BAR.get_item_count(1), 5); assert_eq!(MENU_BAR.get_item_count(2), 7); assert_eq!(MENU_BAR.get_item_count(3), 1); assert_eq!(MENU_BAR.get_item_count(4), 3); }
237
238 #[test]
239 fn test_menu_actions() {
240 assert_eq!(MENU_BAR.get_action(0, 0), Some(MenuAction::SaveConfig));
241 assert_eq!(MENU_BAR.get_action(0, 3), Some(MenuAction::Exit));
242 assert_eq!(MENU_BAR.get_action(4, 0), Some(MenuAction::ShowShortcuts));
243 }
244
245 #[test]
246 fn test_separator_detection() {
247 assert!(MenuAction::Separator.is_separator());
248 assert!(!MenuAction::SaveConfig.is_separator());
249 }
250
251 #[test]
252 fn test_invalid_indices() {
253 assert_eq!(MENU_BAR.get_action(99, 0), None);
254 assert_eq!(MENU_BAR.get_action(0, 99), None);
255 }
256
257 #[test]
258 fn test_menu_label_keys() {
259 assert_eq!(MENU_BAR.get_menu_label_key(0), Some("menu.file"));
260 assert_eq!(MENU_BAR.get_menu_label_key(1), Some("menu.session"));
261 assert_eq!(MENU_BAR.get_menu_label_key(4), Some("menu.help"));
262 }
263}