tui_vision/menus/
bar.rs

1use crate::menus::{Menu, MenuItem, MenuTheme};
2
3/// A menu bar widget that will be displayed at the top of the screen.
4///
5/// Only one menu can be open at a time. When a menu is opened, it displays its dropdown
6/// and becomes the active menu for keyboard navigation.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct MenuBar {
9    /// Collection of menus in the menu bar
10    pub menus: Vec<Menu>,
11
12    /// Index of the currently opened menu (None means no menu is open)
13    pub opened_menu: Option<usize>,
14
15    /// Theme configuration for rendering
16    pub theme: MenuTheme,
17}
18
19impl Default for MenuBar {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25// MenuBar builders
26impl MenuBar {
27    /// Creates a new empty menu bar.
28    pub fn new() -> Self {
29        Self {
30            menus: Vec::new(),
31            opened_menu: None,
32            theme: MenuTheme::default(),
33        }
34    }
35
36    /// Creates a new menu bar from menu definitions.
37    ///
38    /// # Example
39    ///
40    /// ```rust
41    /// use tui_vision::menus::{MenuBar, MenuItem};
42    ///
43    /// let menu_bar = MenuBar::from_menus([
44    ///     ("File", Some('F'), vec![
45    ///         MenuItem::new_action("New", "file.new"),
46    ///         MenuItem::separator(),
47    ///         MenuItem::new_action("Exit", "file.exit"),
48    ///     ]),
49    ///     ("Edit", Some('E'), vec![
50    ///         MenuItem::new_action("Undo", "edit.undo"),
51    ///         MenuItem::new_action("Redo", "edit.redo"),
52    ///     ]),
53    /// ]);
54    /// ```
55    pub fn from_menus<I, S>(menus: I) -> Self
56    where
57        I: IntoIterator<Item = (S, Option<char>, Vec<MenuItem>)>,
58        S: Into<String>,
59    {
60        let menus = menus
61            .into_iter()
62            .map(|(title, hotkey, items)| Menu::with_items(title, hotkey, items))
63            .collect();
64        Self {
65            menus,
66            opened_menu: None,
67            theme: MenuTheme::default(),
68        }
69    }
70
71    /// Creates a new menu bar with the given menus.
72    ///
73    /// # Example
74    ///
75    /// ```rust
76    /// use tui_vision::menus::{MenuBar, Menu};
77    ///
78    /// let menu_bar = MenuBar::with_menus(vec![
79    ///     Menu::new("File").hotkey('F'),
80    ///     Menu::new("Edit").hotkey('E'),
81    /// ]);
82    /// ```
83    pub fn with_menus(menus: Vec<Menu>) -> Self {
84        Self {
85            menus,
86            opened_menu: None,
87            theme: MenuTheme::default(),
88        }
89    }
90
91    /// Adds a menu to the menu bar (builder pattern).
92    pub fn add_menu(&mut self, menu: Menu) -> &mut Self {
93        self.menus.push(menu);
94        self
95    }
96
97    /// Sets the theme for the menu bar.
98    pub fn set_theme(&mut self, theme: MenuTheme) -> &mut Self {
99        self.theme = theme;
100        self
101    }
102
103    /// Sets the theme for the menu bar (builder pattern).
104    pub fn theme(mut self, theme: MenuTheme) -> Self {
105        self.theme = theme;
106        self
107    }
108
109    /// Gets a reference to the current theme.
110    pub fn get_theme(&self) -> &MenuTheme {
111        &self.theme
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn theme_switching_works() {
121        let mut menu_bar = MenuBar::new();
122
123        // Default theme should be classic
124        assert_eq!(menu_bar.get_theme(), &MenuTheme::classic());
125
126        // Switch to dark theme
127        menu_bar.set_theme(MenuTheme::dark());
128        assert_eq!(menu_bar.get_theme(), &MenuTheme::dark());
129
130        // Switch to light theme
131        menu_bar.set_theme(MenuTheme::light());
132        assert_eq!(menu_bar.get_theme(), &MenuTheme::light());
133
134        // Switch to terminal theme
135        menu_bar.set_theme(MenuTheme::terminal());
136        assert_eq!(menu_bar.get_theme(), &MenuTheme::terminal());
137    }
138
139    #[test]
140    fn theme_builder_pattern_works() {
141        let menu_bar = MenuBar::new().theme(MenuTheme::dark());
142        assert_eq!(menu_bar.get_theme(), &MenuTheme::dark());
143    }
144
145    #[test]
146    fn theme_switching_affects_rendering() {
147        use ratatui_core::{buffer::Buffer, layout::Rect, widgets::Widget};
148
149        let mut menu_bar = MenuBar::new();
150        let area = Rect::new(0, 0, 20, 1);
151
152        // Test with classic theme (default)
153        let mut buffer1 = Buffer::empty(area);
154        Widget::render(&menu_bar, area, &mut buffer1);
155        let classic_bg = buffer1[(0, 0)].bg;
156
157        // Switch to dark theme
158        menu_bar.set_theme(MenuTheme::dark());
159        let mut buffer2 = Buffer::empty(area);
160        Widget::render(&menu_bar, area, &mut buffer2);
161        let dark_bg = buffer2[(0, 0)].bg;
162
163        // The background colors should be different
164        assert_ne!(classic_bg, dark_bg);
165    }
166}