tui_vision/menus/
menu.rs

1use crate::{
2    command::Command,
3    menus::{ActionItem, MenuItem, SeparatorItem, SubMenuItem},
4};
5
6/// A menu containing a title and a collection of menu items.
7///
8/// Menus are displayed as dropdowns when opened and support keyboard navigation
9/// with hotkeys for quick access.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Menu {
12    /// Title text displayed in the menu bar
13    pub title: String,
14
15    /// Collection of menu items in this menu
16    pub items: Vec<super::MenuItem>,
17
18    /// Whether the menu is enabled
19    pub enabled: bool,
20
21    /// Optional hotkey for quick access to this menu
22    pub hotkey: Option<char>,
23
24    /// Index of the currently focused item when this menu is open
25    pub focused_item: Option<usize>,
26}
27
28// Menu builders
29impl Menu {
30    /// Creates a new menu with the given title.
31    pub fn new(title: impl Into<String>) -> Self {
32        Self {
33            title: title.into(),
34            items: Vec::new(),
35            enabled: true,
36            hotkey: None,
37            focused_item: None,
38        }
39    }
40
41    /// Creates a new menu with a hotkey.
42    pub fn with_hotkey(title: impl Into<String>, hotkey: char) -> Self {
43        Self {
44            title: title.into(),
45            items: Vec::new(),
46            enabled: true,
47            hotkey: Some(hotkey),
48            focused_item: None,
49        }
50    }
51
52    /// Creates a new menu with title, hotkey, and items.
53    ///
54    /// # Example
55    ///
56    /// ```rust
57    /// use tui_vision::menus::{Menu, MenuItem};
58    ///
59    /// let menu = Menu::with_items("File", Some('F'), vec![
60    ///     MenuItem::new_action("New", "file.new"),
61    ///     MenuItem::separator(),
62    /// ]);
63    /// ```
64    pub fn with_items<S: Into<String>>(
65        title: S,
66        hotkey: Option<char>,
67        items: Vec<MenuItem>,
68    ) -> Self {
69        Self {
70            title: title.into(),
71            items,
72            enabled: true,
73            hotkey,
74            focused_item: None,
75        }
76    }
77
78    /// Sets the hotkey for this menu (builder pattern).
79    pub fn hotkey(mut self, hotkey: char) -> Self {
80        self.hotkey = Some(hotkey);
81        self
82    }
83
84    /// Adds an item to this menu (builder pattern).
85    pub fn item(mut self, item: MenuItem) -> Self {
86        self.items.push(item);
87        self
88    }
89
90    /// Adds multiple items to this menu (builder pattern).
91    pub fn items(mut self, items: Vec<MenuItem>) -> Self {
92        self.items.extend(items);
93        self
94    }
95
96    /// Adds an action item as a convenience method.
97    pub fn add_action<S: Into<String>, C: Into<Command>>(
98        &mut self,
99        label: S,
100        command: C,
101        hotkey: Option<char>,
102    ) -> &mut Self {
103        let action = ActionItem::new(label, command);
104        let action = if let Some(hk) = hotkey {
105            action.hotkey(hk)
106        } else {
107            action
108        };
109        self.items.push(MenuItem::Action(action));
110        self
111    }
112
113    /// Adds a separator as a convenience method.
114    pub fn add_separator(&mut self) -> &mut Self {
115        self.items.push(MenuItem::Separator(SeparatorItem::new()));
116        self
117    }
118
119    /// Adds a submenu as a convenience method.
120    pub fn add_submenu<S: Into<String>>(&mut self, label: S, hotkey: Option<char>) -> &mut Self {
121        self.items.push(MenuItem::SubMenu(SubMenuItem::with_items(
122            label,
123            hotkey,
124            vec![],
125        )));
126        self
127    }
128
129    /// Adds a menu item to this menu.
130    pub fn add_item(&mut self, item: MenuItem) {
131        self.items.push(item);
132    }
133}