tui_vision/menus/
state.rs

1use super::{Menu, MenuBar, MenuItem};
2
3// MenuBar state management
4impl MenuBar {
5    /// Check if any menu is currently open.
6    pub fn has_open_menu(&self) -> bool {
7        self.opened_menu.is_some()
8    }
9
10    /// Get the currently opened menu.
11    pub fn opened_menu(&self) -> Option<&Menu> {
12        self.opened_menu.and_then(|index| self.menus.get(index))
13    }
14
15    /// Get the currently opened menu mutably.
16    pub fn opened_menu_mut(&mut self) -> Option<&mut Menu> {
17        self.opened_menu.and_then(|index| self.menus.get_mut(index))
18    }
19
20    /// Open a specific menu by index.
21    pub fn open_menu(&mut self, index: usize) {
22        if index < self.menus.len() {
23            // Close the previously opened menu
24            if let Some(current_index) = self.opened_menu {
25                if let Some(menu) = self.menus.get_mut(current_index) {
26                    menu.focused_item = None;
27                    menu.close_all_submenus();
28                }
29            }
30
31            // Open the new menu
32            self.opened_menu = Some(index);
33            if let Some(menu) = self.menus.get_mut(index) {
34                // Platform-specific behavior: some focus first item, others don't
35                menu.focused_item = None; // macOS/Electron style - can be configured
36            }
37        }
38    }
39
40    /// Close the currently opened menu.
41    pub fn close_menu(&mut self) {
42        if let Some(index) = self.opened_menu {
43            if let Some(menu) = self.menus.get_mut(index) {
44                menu.focused_item = None;
45                menu.close_all_submenus();
46            }
47        }
48        self.opened_menu = None;
49    }
50
51    /// Open the next menu.
52    pub fn open_next_menu(&mut self) {
53        if self.menus.is_empty() {
54            return;
55        }
56
57        let next_index = if let Some(current) = self.opened_menu {
58            (current + 1) % self.menus.len()
59        } else {
60            0
61        };
62        self.open_menu(next_index);
63    }
64
65    /// Open the previous menu.
66    pub fn open_previous_menu(&mut self) {
67        if self.menus.is_empty() {
68            return;
69        }
70
71        let prev_index = if let Some(current) = self.opened_menu {
72            if current == 0 {
73                self.menus.len() - 1
74            } else {
75                current - 1
76            }
77        } else {
78            self.menus.len() - 1
79        };
80        self.open_menu(prev_index);
81    }
82}
83
84// Menu state management
85impl Menu {
86    /// Sets the enabled state of the menu.
87    pub fn set_enabled(&mut self, enabled: bool) {
88        self.enabled = enabled;
89    }
90
91    /// Focus the next selectable item.
92    pub fn focus_next_item(&mut self) {
93        let next = if let Some(current) = self.focused_item {
94            self.find_next_selectable_item(current)
95        } else {
96            self.find_first_selectable_item()
97        };
98        self.focused_item = next;
99    }
100
101    /// Focus the previous selectable item.
102    pub fn focus_previous_item(&mut self) {
103        let previous = if let Some(current) = self.focused_item {
104            self.find_previous_selectable_item(current)
105        } else {
106            self.find_last_selectable_item()
107        };
108        self.focused_item = previous;
109    }
110
111    /// Get the currently focused menu item.
112    pub fn get_focused_item(&self) -> Option<&MenuItem> {
113        self.focused_item.and_then(|index| self.items.get(index))
114    }
115
116    /// Close all open submenus.
117    pub fn close_all_submenus(&mut self) {
118        for item in &mut self.items {
119            if let MenuItem::SubMenu(submenu) = item {
120                submenu.is_open = false;
121                submenu.focused_item = None;
122                // Recursively close nested submenus
123                Self::close_submenus_recursive(&mut submenu.items);
124            }
125        }
126    }
127
128    /// Recursively close submenus in a list of menu items.
129    fn close_submenus_recursive(items: &mut [MenuItem]) {
130        for item in items {
131            if let MenuItem::SubMenu(submenu) = item {
132                submenu.is_open = false;
133                submenu.focused_item = None;
134                Self::close_submenus_recursive(&mut submenu.items);
135            }
136        }
137    }
138
139    /// Find the first selectable (non-separator) menu item.
140    pub fn find_first_selectable_item(&self) -> Option<usize> {
141        self.items
142            .iter()
143            .position(|item| matches!(item, MenuItem::Action(_) | MenuItem::SubMenu(_)))
144    }
145
146    /// Find the last selectable menu item.
147    pub fn find_last_selectable_item(&self) -> Option<usize> {
148        self.items
149            .iter()
150            .rposition(|item| matches!(item, MenuItem::Action(_) | MenuItem::SubMenu(_)))
151    }
152
153    /// Find the next selectable menu item after the given index.
154    pub fn find_next_selectable_item(&self, current: usize) -> Option<usize> {
155        self.items
156            .iter()
157            .skip(current + 1)
158            .position(|item| matches!(item, MenuItem::Action(_) | MenuItem::SubMenu(_)))
159            .map(|pos| pos + current + 1)
160            .or_else(|| self.find_first_selectable_item())
161    }
162
163    /// Find the previous selectable menu item before the given index.
164    pub fn find_previous_selectable_item(&self, current: usize) -> Option<usize> {
165        self.items
166            .iter()
167            .take(current)
168            .rposition(|item| matches!(item, MenuItem::Action(_) | MenuItem::SubMenu(_)))
169            .or_else(|| self.find_last_selectable_item())
170    }
171}