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