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