tui_vision/menus/
item.rs

1use crate::command::Command;
2
3// Forward declaration for MenuItem enum (to avoid circular dependency)
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum MenuItem {
6    Action(ActionItem),
7    Separator(SeparatorItem),
8    SubMenu(SubMenuItem),
9}
10
11impl MenuItem {
12    /// Gets the text of the menu item (if applicable).
13    pub fn text(&self) -> Option<&str> {
14        match self {
15            Self::Action(action) => Some(&action.label),
16            Self::SubMenu(submenu) => Some(&submenu.label),
17            Self::Separator(_) => None,
18        }
19    }
20
21    /// Checks if the menu item is enabled.
22    pub fn is_enabled(&self) -> bool {
23        match self {
24            Self::Action(action) => action.enabled,
25            Self::SubMenu(submenu) => submenu.enabled,
26            Self::Separator(_) => false, // Separators are not selectable
27        }
28    }
29
30    /// Returns the label of this menu item, if any.
31    pub fn label(&self) -> Option<&str> {
32        match self {
33            Self::Action(action) => Some(&action.label),
34            Self::SubMenu(submenu) => Some(&submenu.label),
35            Self::Separator(_) => None,
36        }
37    }
38
39    /// Returns the hotkey of this menu item, if any.
40    pub fn hotkey(&self) -> Option<char> {
41        match self {
42            Self::Action(action) => action.hotkey,
43            Self::SubMenu(submenu) => submenu.hotkey,
44            Self::Separator(_) => None,
45        }
46    }
47
48    /// Returns whether this menu item is selectable.
49    pub fn is_selectable(&self) -> bool {
50        !matches!(self, Self::Separator(_))
51    }
52}
53
54/// An action menu item that executes a command when selected.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ActionItem {
57    /// Text label displayed to the user
58    pub label: String,
59
60    /// Command to execute when this item is selected
61    pub command: Command,
62
63    /// Whether the action is enabled
64    pub enabled: bool,
65
66    /// Optional hotkey for quick access
67    pub hotkey: Option<char>,
68
69    /// Optional keyboard shortcut display (e.g., "Ctrl+S")
70    pub shortcut: Option<String>,
71
72    /// Help context for additional information
73    pub help_context: Option<String>,
74}
75
76/// A separator menu item for visual grouping.
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct SeparatorItem;
79
80/// A submenu containing other menu items.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct SubMenuItem {
83    /// Text label displayed to the user
84    pub label: String,
85
86    /// Collection of menu items in the submenu
87    pub items: Vec<MenuItem>,
88
89    /// Whether the submenu is enabled
90    pub enabled: bool,
91
92    /// Optional hotkey for quick access
93    pub hotkey: Option<char>,
94
95    /// Help context for additional information
96    pub help_context: Option<String>,
97
98    /// Index of the currently focused item when this submenu is open
99    pub focused_item: Option<usize>,
100
101    /// Whether this submenu is currently open
102    pub is_open: bool,
103}
104
105impl Default for SeparatorItem {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl SeparatorItem {
112    /// Creates a new separator item.
113    pub fn new() -> Self {
114        Self
115    }
116}
117
118// MenuItem builders
119impl MenuItem {
120    /// Creates a new action menu item.
121    pub fn new_action<S: Into<String>, C: Into<Command>>(label: S, command: C) -> Self {
122        Self::Action(ActionItem::new(label, command))
123    }
124
125    /// Creates a new action menu item with hotkey.
126    pub fn action_with_hotkey<S: Into<String>, C: Into<Command>>(
127        label: S,
128        command: C,
129        hotkey: char,
130    ) -> Self {
131        Self::Action(ActionItem::with_hotkey(label, command, hotkey))
132    }
133
134    /// Creates a new action menu item with all properties.
135    pub fn action_with_all<S: Into<String>, C: Into<Command>>(
136        label: S,
137        command: C,
138        hotkey: Option<char>,
139        shortcut: Option<S>,
140    ) -> Self {
141        Self::Action(ActionItem::with_all(label, command, hotkey, shortcut))
142    }
143
144    /// Creates a new separator menu item.
145    pub fn separator() -> Self {
146        Self::Separator(SeparatorItem::new())
147    }
148
149    /// Creates a new submenu item.
150    pub fn new_submenu<S: Into<String>>(label: S) -> Self {
151        Self::SubMenu(SubMenuItem::new(label))
152    }
153
154    /// Creates a new submenu item with hotkey.
155    pub fn submenu_with_hotkey<S: Into<String>>(label: S, hotkey: char) -> Self {
156        Self::SubMenu(SubMenuItem::with_hotkey(label, hotkey))
157    }
158
159    /// Creates a new submenu item with all properties.
160    pub fn submenu_with_items<S: Into<String>>(
161        label: S,
162        hotkey: Option<char>,
163        items: Vec<MenuItem>,
164    ) -> Self {
165        Self::SubMenu(SubMenuItem::with_items(label, hotkey, items))
166    }
167
168    // Legacy compatibility methods
169    /// Creates a new action menu item (legacy).
170    pub fn action<S: Into<String>, C: Into<Command>>(text: S, command: C) -> Self {
171        Self::new_action(text, command)
172    }
173
174    /// Creates a new submenu (legacy).
175    pub fn submenu<S: Into<String>>(text: S, items: Vec<MenuItem>) -> Self {
176        Self::SubMenu(SubMenuItem::with_items(text, None, items))
177    }
178
179    /// Sets the hotkey for this menu item (legacy builder pattern).
180    pub fn with_hotkey(mut self, hotkey: char) -> Self {
181        match &mut self {
182            Self::Action(action) => action.hotkey = Some(hotkey),
183            Self::SubMenu(submenu) => submenu.hotkey = Some(hotkey),
184            Self::Separator(_) => {} // Separators don't have hotkeys
185        }
186        self
187    }
188
189    /// Sets the shortcut display for this menu item (legacy builder pattern).
190    pub fn with_shortcut<S: Into<String>>(mut self, shortcut: S) -> Self {
191        if let Self::Action(action) = &mut self {
192            action.shortcut = Some(shortcut.into());
193        }
194        self
195    }
196
197    /// Sets the help context for the menu item (legacy builder pattern).
198    pub fn with_help_context<S: Into<String>>(mut self, help_context: S) -> Self {
199        match &mut self {
200            Self::Action(action) => action.help_context = Some(help_context.into()),
201            Self::SubMenu(submenu) => submenu.help_context = Some(help_context.into()),
202            Self::Separator(_) => {} // Separators don't have help context
203        }
204        self
205    }
206
207    /// Sets the enabled state of the menu item (legacy builder pattern).
208    pub fn with_enabled(mut self, enabled: bool) -> Self {
209        match &mut self {
210            Self::Action(action) => action.enabled = enabled,
211            Self::SubMenu(submenu) => submenu.enabled = enabled,
212            Self::Separator(_) => {} // Separators don't have enabled state
213        }
214        self
215    }
216}
217
218// ActionItem builders
219impl ActionItem {
220    /// Creates a new action item with the given label and command.
221    pub fn new<S: Into<String>, C: Into<Command>>(label: S, command: C) -> Self {
222        Self {
223            label: label.into(),
224            command: command.into(),
225            enabled: true,
226            hotkey: None,
227            shortcut: None,
228            help_context: None,
229        }
230    }
231
232    /// Creates a new action item with label, command, and hotkey.
233    pub fn with_hotkey<S: Into<String>, C: Into<Command>>(
234        label: S,
235        command: C,
236        hotkey: char,
237    ) -> Self {
238        Self {
239            label: label.into(),
240            command: command.into(),
241            enabled: true,
242            hotkey: Some(hotkey),
243            shortcut: None,
244            help_context: None,
245        }
246    }
247
248    /// Creates a new action item with all properties.
249    pub fn with_all<S: Into<String>, C: Into<Command>>(
250        label: S,
251        command: C,
252        hotkey: Option<char>,
253        shortcut: Option<S>,
254    ) -> Self {
255        Self {
256            label: label.into(),
257            command: command.into(),
258            enabled: true,
259            hotkey,
260            shortcut: shortcut.map(|s| s.into()),
261            help_context: None,
262        }
263    }
264
265    /// Sets the hotkey for this action item (builder pattern).
266    pub fn hotkey(mut self, hotkey: char) -> Self {
267        self.hotkey = Some(hotkey);
268        self
269    }
270
271    /// Sets the keyboard shortcut for this action item (builder pattern).
272    pub fn shortcut<S: Into<String>>(mut self, shortcut: S) -> Self {
273        self.shortcut = Some(shortcut.into());
274        self
275    }
276
277    /// Sets the help context for this action item (builder pattern).
278    pub fn help_context<S: Into<String>>(mut self, help_context: S) -> Self {
279        self.help_context = Some(help_context.into());
280        self
281    }
282
283    /// Sets the enabled state for this action item (builder pattern).
284    pub fn enabled(mut self, enabled: bool) -> Self {
285        self.enabled = enabled;
286        self
287    }
288}
289
290// SeparatorItem builders (already implemented in items.rs)
291
292// SubMenuItem builders
293impl SubMenuItem {
294    /// Creates a new submenu item with the given label.
295    pub fn new<S: Into<String>>(label: S) -> Self {
296        Self {
297            label: label.into(),
298            items: Vec::new(),
299            enabled: true,
300            hotkey: None,
301            help_context: None,
302            focused_item: None,
303            is_open: false,
304        }
305    }
306
307    /// Creates a new submenu item with label and hotkey.
308    pub fn with_hotkey<S: Into<String>>(label: S, hotkey: char) -> Self {
309        Self {
310            label: label.into(),
311            items: Vec::new(),
312            enabled: true,
313            hotkey: Some(hotkey),
314            help_context: None,
315            focused_item: None,
316            is_open: false,
317        }
318    }
319
320    /// Creates a new submenu item with title, hotkey, and items.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use tui_vision::menus::{SubMenuItem, MenuItem};
326    ///
327    /// let submenu = SubMenuItem::with_items("Find", Some('F'), vec![
328    ///     MenuItem::new_action("Find", "edit.find"),
329    ///     MenuItem::new_action("Find Next", "edit.find_next"),
330    /// ]);
331    /// ```
332    pub fn with_items<S: Into<String>>(
333        label: S,
334        hotkey: Option<char>,
335        items: Vec<MenuItem>,
336    ) -> Self {
337        Self {
338            label: label.into(),
339            items,
340            enabled: true,
341            hotkey,
342            help_context: None,
343            focused_item: None,
344            is_open: false,
345        }
346    }
347
348    /// Sets the hotkey for this submenu item (builder pattern).
349    pub fn hotkey(mut self, hotkey: char) -> Self {
350        self.hotkey = Some(hotkey);
351        self
352    }
353
354    /// Adds an item to this submenu (builder pattern).
355    pub fn item(mut self, item: MenuItem) -> Self {
356        self.items.push(item);
357        self
358    }
359
360    /// Sets the help context for this submenu item (builder pattern).
361    pub fn help_context<S: Into<String>>(mut self, help_context: S) -> Self {
362        self.help_context = Some(help_context.into());
363        self
364    }
365
366    /// Sets the enabled state for this submenu item (builder pattern).
367    pub fn enabled(mut self, enabled: bool) -> Self {
368        self.enabled = enabled;
369        self
370    }
371
372    /// Add a menu item to this submenu.
373    pub fn add_item(&mut self, item: MenuItem) {
374        self.items.push(item);
375    }
376}