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 pub use rat_event::*;
22 use rat_popup::event::PopupOutcome;
23
24 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26 pub enum MenuOutcome {
27 Continue,
29 Unchanged,
31 Changed,
33
34 Hide,
38
39 Selected(usize),
44
45 Activated(usize),
50
51 MenuSelected(usize, usize),
55
56 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#[derive(Debug, Clone)]
107pub struct MenuStyle {
108 pub style: Style,
110 pub title: Option<Style>,
112 pub highlight: Option<Style>,
114 pub disabled: Option<Style>,
116 pub right: Option<Style>,
118 #[deprecated(since = "1.1.0", note = "merged with focus style")]
120 pub select: Option<Style>,
121 pub focus: Option<Style>,
123
124 pub popup_style: Option<Style>,
126 pub block: Option<Block<'static>>,
128 pub popup: PopupStyle,
130 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
156pub trait MenuStructure<'a>: Debug {
158 fn menus(&'a self, menu: &mut MenuBuilder<'a>);
160 fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
162}
163
164#[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 pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
177 self.items.push(item);
178 self
179 }
180
181 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 pub fn item_str(&mut self, text: &'a str) -> &mut Self {
202 self.items.push(MenuItem::new_str(text));
203 self
204 }
205
206 pub fn item_string(&mut self, text: String) -> &mut Self {
208 self.items.push(MenuItem::new_string(text));
209 self
210 }
211
212 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 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 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 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 pub fn items(self) -> Vec<MenuItem<'a>> {
258 self.items
259 }
260}
261
262#[derive(Debug)]
264pub struct StaticMenu {
265 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}