rat_menu/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3use crate::_private::NonExhaustive;
4use crate::menuitem::{MenuItem, Separator};
5use rat_popup::PopupStyle;
6use ratatui::style::Style;
7use ratatui::widgets::Block;
8use std::fmt::Debug;
9use std::ops::Range;
10
11pub mod menubar;
12pub mod menuitem;
13pub mod menuline;
14pub mod popup_menu;
15mod util;
16
17pub mod event {
18    //!
19    //! Event-handler traits and Keybindings.
20    //!
21    pub use rat_event::*;
22    use rat_popup::event::PopupOutcome;
23
24    /// Outcome for menuline.
25    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26    pub enum MenuOutcome {
27        /// The given event was not handled at all.
28        Continue,
29        /// The event was handled, no repaint necessary.
30        Unchanged,
31        /// The event was handled, repaint necessary.
32        Changed,
33
34        /// Popup should be hidden.
35        ///
36        /// Used by PopupMenu.
37        Hide,
38
39        /// A menuitem was select.
40        ///
41        /// Used by MenuLine and PopupMenu.
42        /// Used by Menubar for results from the main menu.
43        Selected(usize),
44
45        /// A menuitem was activated.
46        ///
47        /// Used by MenuLine and PopupMenu.
48        /// Used by Menubar for results from the main menu.
49        Activated(usize),
50
51        /// A popup-menuitem was selected.
52        ///
53        /// Used by Menubar for results from a popup-menu. Is (main-idx, popup-idx).
54        MenuSelected(usize, usize),
55
56        /// A popup-menuitem was activated.
57        ///
58        /// Used by Menubar for results from a popup-menu. Is (main-idx, popup-idx);
59        MenuActivated(usize, usize),
60    }
61
62    impl ConsumedEvent for MenuOutcome {
63        fn is_consumed(&self) -> bool {
64            *self != MenuOutcome::Continue
65        }
66    }
67
68    impl From<MenuOutcome> for Outcome {
69        fn from(value: MenuOutcome) -> Self {
70            match value {
71                MenuOutcome::Continue => Outcome::Continue,
72                MenuOutcome::Unchanged => Outcome::Unchanged,
73                MenuOutcome::Changed => Outcome::Changed,
74                MenuOutcome::Selected(_) => Outcome::Changed,
75                MenuOutcome::Activated(_) => Outcome::Changed,
76                MenuOutcome::MenuSelected(_, _) => Outcome::Changed,
77                MenuOutcome::MenuActivated(_, _) => Outcome::Changed,
78                MenuOutcome::Hide => Outcome::Changed,
79            }
80        }
81    }
82
83    impl From<PopupOutcome> for MenuOutcome {
84        fn from(value: PopupOutcome) -> Self {
85            match value {
86                PopupOutcome::Continue => MenuOutcome::Continue,
87                PopupOutcome::Unchanged => MenuOutcome::Unchanged,
88                PopupOutcome::Changed => MenuOutcome::Changed,
89                PopupOutcome::Hide => MenuOutcome::Hide,
90            }
91        }
92    }
93
94    impl From<Outcome> for MenuOutcome {
95        fn from(value: Outcome) -> Self {
96            match value {
97                Outcome::Continue => MenuOutcome::Continue,
98                Outcome::Unchanged => MenuOutcome::Unchanged,
99                Outcome::Changed => MenuOutcome::Changed,
100            }
101        }
102    }
103}
104
105/// Combined styles.
106#[derive(Debug, Clone)]
107pub struct MenuStyle {
108    /// Base style.
109    pub style: Style,
110    /// Menuline title style.
111    pub title: Option<Style>,
112    /// Style for the _ highlight/nav-char
113    pub highlight: Option<Style>,
114    /// Style for a disabled item.
115    pub disabled: Option<Style>,
116    /// Style for the hotkey
117    pub right: Option<Style>,
118    /// Select style.
119    #[deprecated(since = "1.1.0", note = "merged with focus style")]
120    pub select: Option<Style>,
121    /// Focus style
122    pub focus: Option<Style>,
123
124    /// Styling for the popup menus.
125    pub popup_style: Option<Style>,
126    /// Block for the popup menus.
127    pub block: Option<Block<'static>>,
128    /// Popup itself
129    pub popup: PopupStyle,
130    /// Border style
131    pub popup_border: Option<Style>,
132
133    pub non_exhaustive: NonExhaustive,
134}
135
136impl Default for MenuStyle {
137    fn default() -> Self {
138        Self {
139            style: Default::default(),
140            title: Default::default(),
141            highlight: Default::default(),
142            disabled: Default::default(),
143            right: Default::default(),
144            #[allow(deprecated)]
145            select: Default::default(),
146            focus: Default::default(),
147            popup_style: Default::default(),
148            block: Default::default(),
149            popup: Default::default(),
150            popup_border: Default::default(),
151            non_exhaustive: NonExhaustive,
152        }
153    }
154}
155
156/// Trait for the structural data of the MenuBar.
157pub trait MenuStructure<'a>: Debug {
158    /// Main menu.
159    fn menus(&'a self, menu: &mut MenuBuilder<'a>);
160    /// Submenus.
161    fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
162}
163
164/// Builder to fill a menu with items.
165#[derive(Debug, Default, Clone)]
166pub struct MenuBuilder<'a> {
167    pub(crate) items: Vec<MenuItem<'a>>,
168}
169
170impl<'a> MenuBuilder<'a> {
171    pub fn new() -> Self {
172        Self::default()
173    }
174
175    /// Add a menu-item.
176    pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
177        self.items.push(item);
178        self
179    }
180
181    /// Parse the text.
182    ///
183    /// __See__
184    ///
185    /// [MenuItem::new_parsed]
186    pub fn item_parsed(&mut self, text: &'a str) -> &mut Self {
187        let item = MenuItem::new_parsed(text);
188        if let Some(separator) = item.separator {
189            if let Some(last) = self.items.last_mut() {
190                last.separator = Some(separator);
191            } else {
192                self.items.push(item);
193            }
194        } else {
195            self.items.push(item);
196        }
197        self
198    }
199
200    /// New item.
201    pub fn item_str(&mut self, text: &'a str) -> &mut Self {
202        self.items.push(MenuItem::new_str(text));
203        self
204    }
205
206    /// New item with owned text.
207    pub fn item_string(&mut self, text: String) -> &mut Self {
208        self.items.push(MenuItem::new_string(text));
209        self
210    }
211
212    /// New item with navigation.
213    pub fn item_nav_str(
214        &mut self,
215        text: &'a str,
216        highlight: Range<usize>,
217        navchar: char,
218    ) -> &mut Self {
219        self.items
220            .push(MenuItem::new_nav_str(text, highlight, navchar));
221        self
222    }
223
224    /// New item with navigation.
225    pub fn item_nav_string(
226        &mut self,
227        text: String,
228        highlight: Range<usize>,
229        navchar: char,
230    ) -> &mut Self {
231        self.items
232            .push(MenuItem::new_nav_string(text, highlight, navchar));
233        self
234    }
235
236    /// Sets the separator for the last item added.
237    /// If there is none adds this as an empty menu-item.
238    pub fn separator(&mut self, separator: Separator) -> &mut Self {
239        if let Some(last) = self.items.last_mut() {
240            last.separator = Some(separator);
241        } else {
242            self.items.push(MenuItem::new().separator(separator));
243        }
244        self
245    }
246
247    /// Sets the last item to disabled.
248    /// If there is no last item does nothing.
249    pub fn disabled(&mut self, disable: bool) -> &mut Self {
250        if let Some(last) = self.items.last_mut() {
251            last.disabled = disable;
252        }
253        self
254    }
255
256    /// Build and deconstruct.
257    pub fn items(self) -> Vec<MenuItem<'a>> {
258        self.items
259    }
260}
261
262/// Static menu structure.
263#[derive(Debug)]
264pub struct StaticMenu {
265    /// Array of menus + array of items.
266    ///
267    /// __MenuItems__
268    ///
269    /// The first '_' marks the navigation-char.
270    ///
271    /// __Separator__
272    ///
273    /// This uses `_` (underscore) as prefix and
274    /// a fixed string to identify the separator:
275    ///
276    /// * `_   ` - three blanks -> empty separator
277    /// * `____` - three underscores -> plain line
278    /// * `_______` - six underscore -> thick line
279    /// * `_===` - three equals -> double line
280    /// * `_---` - three hyphen -> dashed line
281    /// * `_...` - three dots -> dotted line
282    ///
283    pub menu: &'static [(&'static str, &'static [&'static str])],
284}
285
286impl MenuStructure<'static> for StaticMenu {
287    fn menus(&'static self, menu: &mut MenuBuilder<'static>) {
288        for (s, _) in self.menu.iter() {
289            menu.item_parsed(s);
290        }
291    }
292
293    fn submenu(&'static self, n: usize, submenu: &mut MenuBuilder<'static>) {
294        for s in self.menu[n].1 {
295            submenu.item_parsed(s);
296        }
297    }
298}
299
300mod _private {
301    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
302    pub struct NonExhaustive;
303}