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 for the main-menu.
109    pub style: Style,
110    /// Border for the main-menu.
111    pub menu_block: Option<Block<'static>>,
112    /// Border-style for the main-menu.
113    pub border_style: Option<Style>,
114    /// Title-style for the main-menu border.
115    pub title_style: Option<Style>,
116
117    /// Menuline title style.
118    pub title: Option<Style>,
119    /// Style for the _ highlight/nav-char
120    pub highlight: Option<Style>,
121    /// Style for a disabled item.
122    pub disabled: Option<Style>,
123    /// Style for the hotkey
124    pub right: Option<Style>,
125    /// Focus style
126    pub focus: Option<Style>,
127
128    /// Styling for the popup menus.
129    pub popup_style: Option<Style>,
130    /// Block for the popup-menu.
131    #[deprecated(since = "2.1.0", note = "use popup_block instead")]
132    pub block: Option<Block<'static>>,
133    /// Block for the popup-menu.
134    pub popup_block: Option<Block<'static>>,
135    /// Border style for the popup-menu.
136    pub popup_border: Option<Style>,
137    /// Border style for the popup-menu.
138    pub popup_title: Option<Style>,
139    /// Style for the _ highlight/nav-char for the popup-menu.
140    pub popup_highlight: Option<Style>,
141    /// Style for a disabled item for the popup-menu.
142    pub popup_disabled: Option<Style>,
143    /// Style for the hotkey for the popup-menu.
144    pub popup_right: Option<Style>,
145    /// Focus style for the popup-menu.
146    pub popup_focus: Option<Style>,
147    /// Style for separators for the popup-menu.
148    pub popup_separator: Option<Style>,
149    /// Popup itself
150    pub popup: PopupStyle,
151
152    pub non_exhaustive: NonExhaustive,
153}
154
155impl Default for MenuStyle {
156    #[allow(deprecated)]
157    fn default() -> Self {
158        Self {
159            style: Default::default(),
160            menu_block: Default::default(),
161            border_style: Default::default(),
162            title_style: Default::default(),
163            title: Default::default(),
164            highlight: Default::default(),
165            disabled: Default::default(),
166            right: Default::default(),
167            focus: Default::default(),
168            popup_style: Default::default(),
169            block: Default::default(),
170            popup_block: Default::default(),
171            popup_border: Default::default(),
172            popup_title: Default::default(),
173            popup_highlight: Default::default(),
174            popup_disabled: Default::default(),
175            popup_right: Default::default(),
176            popup_focus: Default::default(),
177            popup_separator: Default::default(),
178            popup: Default::default(),
179            non_exhaustive: NonExhaustive,
180        }
181    }
182}
183
184/// Trait for the structural data of the MenuBar.
185pub trait MenuStructure<'a>: Debug {
186    /// Main menu.
187    fn menus(&'a self, menu: &mut MenuBuilder<'a>);
188    /// Submenus.
189    fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
190}
191
192/// Builder to fill a menu with items.
193#[derive(Debug, Default, Clone)]
194pub struct MenuBuilder<'a> {
195    pub(crate) items: Vec<MenuItem<'a>>,
196}
197
198impl<'a> MenuBuilder<'a> {
199    pub fn new() -> Self {
200        Self::default()
201    }
202
203    /// Add a menu-item.
204    pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
205        self.items.push(item);
206        self
207    }
208
209    /// Parse the text.
210    ///
211    /// __See__
212    ///
213    /// [MenuItem::new_parsed]
214    pub fn item_parsed(&mut self, text: &'a str) -> &mut Self {
215        let item = MenuItem::new_parsed(text);
216        if let Some(separator) = item.separator {
217            if let Some(last) = self.items.last_mut() {
218                last.separator = Some(separator);
219            } else {
220                self.items.push(item);
221            }
222        } else {
223            self.items.push(item);
224        }
225        self
226    }
227
228    /// New item.
229    pub fn item_str(&mut self, text: &'a str) -> &mut Self {
230        self.items.push(MenuItem::new_str(text));
231        self
232    }
233
234    /// New item with owned text.
235    pub fn item_string(&mut self, text: String) -> &mut Self {
236        self.items.push(MenuItem::new_string(text));
237        self
238    }
239
240    /// New item with navigation.
241    pub fn item_nav_str(
242        &mut self,
243        text: &'a str,
244        highlight: Range<usize>,
245        navchar: char,
246    ) -> &mut Self {
247        self.items
248            .push(MenuItem::new_nav_str(text, highlight, navchar));
249        self
250    }
251
252    /// New item with navigation.
253    pub fn item_nav_string(
254        &mut self,
255        text: String,
256        highlight: Range<usize>,
257        navchar: char,
258    ) -> &mut Self {
259        self.items
260            .push(MenuItem::new_nav_string(text, highlight, navchar));
261        self
262    }
263
264    /// Sets the separator for the last item added.
265    /// If there is none adds this as an empty menu-item.
266    pub fn separator(&mut self, separator: Separator) -> &mut Self {
267        if let Some(last) = self.items.last_mut() {
268            last.separator = Some(separator);
269        } else {
270            self.items.push(MenuItem::new().separator(separator));
271        }
272        self
273    }
274
275    /// Sets the last item to disabled.
276    /// If there is no last item does nothing.
277    pub fn disabled(&mut self, disable: bool) -> &mut Self {
278        if let Some(last) = self.items.last_mut() {
279            last.disabled = disable;
280        }
281        self
282    }
283
284    /// Build and deconstruct.
285    pub fn items(self) -> Vec<MenuItem<'a>> {
286        self.items
287    }
288}
289
290/// Static menu structure.
291#[derive(Debug)]
292pub struct StaticMenu {
293    /// Array of menus + array of items.
294    ///
295    /// __MenuItems__
296    ///
297    /// The first '_' marks the navigation-char.
298    ///
299    /// __Separator__
300    ///
301    /// This uses `_` (underscore) as prefix and
302    /// a fixed string to identify the separator:
303    ///
304    /// * `_   ` - three blanks -> empty separator
305    /// * `____` - three underscores -> plain line
306    /// * `_______` - six underscore -> thick line
307    /// * `_===` - three equals -> double line
308    /// * `_---` - three hyphen -> dashed line
309    /// * `_...` - three dots -> dotted line
310    ///
311    pub menu: &'static [(&'static str, &'static [&'static str])],
312}
313
314impl MenuStructure<'static> for StaticMenu {
315    fn menus(&'static self, menu: &mut MenuBuilder<'static>) {
316        for (s, _) in self.menu.iter() {
317            menu.item_parsed(s);
318        }
319    }
320
321    fn submenu(&'static self, n: usize, submenu: &mut MenuBuilder<'static>) {
322        for s in self.menu[n].1 {
323            submenu.item_parsed(s);
324        }
325    }
326}
327
328mod _private {
329    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
330    pub struct NonExhaustive;
331}