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 pub use rat_event::*;
21 use rat_popup::event::PopupOutcome;
22
23 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25 pub enum MenuOutcome {
26 Continue,
28 Unchanged,
30 Changed,
32
33 Hide,
37
38 Selected(usize),
43
44 Activated(usize),
49
50 MenuSelected(usize, usize),
54
55 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#[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
136pub trait MenuStructure<'a>: Debug {
138 fn menus(&'a self, menu: &mut MenuBuilder<'a>);
140 fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
142}
143
144#[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 pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
157 self.items.push(item);
158 self
159 }
160
161 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 pub fn item_str(&mut self, text: &'a str) -> &mut Self {
182 self.items.push(MenuItem::new_str(text));
183 self
184 }
185
186 pub fn item_string(&mut self, text: String) -> &mut Self {
188 self.items.push(MenuItem::new_string(text));
189 self
190 }
191
192 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 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 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 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 pub fn items(self) -> Vec<MenuItem<'a>> {
238 self.items
239 }
240}
241
242#[derive(Debug)]
244pub struct StaticMenu {
245 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}