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 pub focus: Option<Style>,
120
121 pub popup_style: Option<Style>,
123 pub block: Option<Block<'static>>,
125 pub popup: PopupStyle,
127 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
151pub trait MenuStructure<'a>: Debug {
153 fn menus(&'a self, menu: &mut MenuBuilder<'a>);
155 fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
157}
158
159#[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 pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
172 self.items.push(item);
173 self
174 }
175
176 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 pub fn item_str(&mut self, text: &'a str) -> &mut Self {
197 self.items.push(MenuItem::new_str(text));
198 self
199 }
200
201 pub fn item_string(&mut self, text: String) -> &mut Self {
203 self.items.push(MenuItem::new_string(text));
204 self
205 }
206
207 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 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 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 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 pub fn items(self) -> Vec<MenuItem<'a>> {
253 self.items
254 }
255}
256
257#[derive(Debug)]
259pub struct StaticMenu {
260 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}