rat_menu/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
#![doc = include_str!("../readme.md")]
use crate::_private::NonExhaustive;
use crate::menuitem::{MenuItem, Separator};
use rat_popup::PopupStyle;
use ratatui::prelude::Style;
use std::fmt::Debug;
use std::ops::Range;
pub mod menubar;
pub mod menuitem;
pub mod menuline;
pub mod popup_menu;
mod util;
pub mod event {
//!
//! Event-handler traits and Keybindings.
//!
pub use rat_event::*;
use rat_popup::event::PopupOutcome;
/// Outcome for menuline.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MenuOutcome {
/// The given event was not handled at all.
Continue,
/// The event was handled, no repaint necessary.
Unchanged,
/// The event was handled, repaint necessary.
Changed,
/// Popup should be hidden.
///
/// Used by PopupMenu.
Hide,
/// A menuitem was select.
///
/// Used by MenuLine and PopupMenu.
/// Used by Menubar for results from the main menu.
Selected(usize),
/// A menuitem was activated.
///
/// Used by MenuLine and PopupMenu.
/// Used by Menubar for results from the main menu.
Activated(usize),
/// A popup-menuitem was selected.
///
/// Used by Menubar for results from a popup-menu. Is (main-idx, popup-idx).
MenuSelected(usize, usize),
/// A popup-menuitem was activated.
///
/// Used by Menubar for results from a popup-menu. Is (main-idx, popup-idx);
MenuActivated(usize, usize),
}
impl ConsumedEvent for MenuOutcome {
fn is_consumed(&self) -> bool {
*self != MenuOutcome::Continue
}
}
impl From<MenuOutcome> for Outcome {
fn from(value: MenuOutcome) -> Self {
match value {
MenuOutcome::Continue => Outcome::Continue,
MenuOutcome::Unchanged => Outcome::Unchanged,
MenuOutcome::Changed => Outcome::Changed,
MenuOutcome::Selected(_) => Outcome::Changed,
MenuOutcome::Activated(_) => Outcome::Changed,
MenuOutcome::MenuSelected(_, _) => Outcome::Changed,
MenuOutcome::MenuActivated(_, _) => Outcome::Changed,
MenuOutcome::Hide => Outcome::Changed,
}
}
}
impl From<PopupOutcome> for MenuOutcome {
fn from(value: PopupOutcome) -> Self {
match value {
PopupOutcome::Continue => MenuOutcome::Continue,
PopupOutcome::Unchanged => MenuOutcome::Unchanged,
PopupOutcome::Changed => MenuOutcome::Changed,
PopupOutcome::Hide => MenuOutcome::Hide,
}
}
}
impl From<Outcome> for MenuOutcome {
fn from(value: Outcome) -> Self {
match value {
Outcome::Continue => MenuOutcome::Continue,
Outcome::Unchanged => MenuOutcome::Unchanged,
Outcome::Changed => MenuOutcome::Changed,
}
}
}
}
/// Combined styles.
#[derive(Debug, Clone)]
pub struct MenuStyle {
pub style: Style,
pub title: Option<Style>,
pub highlight: Option<Style>,
pub disabled: Option<Style>,
pub right: Option<Style>,
pub select: Option<Style>,
pub focus: Option<Style>,
pub popup: PopupStyle,
pub non_exhaustive: NonExhaustive,
}
impl Default for MenuStyle {
fn default() -> Self {
Self {
style: Default::default(),
title: None,
highlight: None,
disabled: None,
right: None,
select: None,
focus: None,
popup: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
/// Trait for the structural data of the MenuBar.
pub trait MenuStructure<'a>: Debug {
/// Main menu.
fn menus(&'a self, menu: &mut MenuBuilder<'a>);
/// Submenus.
fn submenu(&'a self, n: usize, submenu: &mut MenuBuilder<'a>);
}
/// Builder to fill a menu with items.
#[derive(Debug, Default, Clone)]
pub struct MenuBuilder<'a> {
pub(crate) items: Vec<MenuItem<'a>>,
}
impl<'a> MenuBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
/// Add a menu-item.
pub fn item(&mut self, item: MenuItem<'a>) -> &mut Self {
self.items.push(item);
self
}
/// Parse the text.
///
/// __See__
///
/// [MenuItem::new_parsed]
pub fn item_parsed(&mut self, text: &'a str) -> &mut Self {
let item = MenuItem::new_parsed(text);
if let Some(separator) = item.separator {
if let Some(last) = self.items.last_mut() {
last.separator = Some(separator);
} else {
self.items.push(item);
}
} else {
self.items.push(item);
}
self
}
/// New item.
pub fn item_str(&mut self, text: &'a str) -> &mut Self {
self.items.push(MenuItem::new_str(text));
self
}
/// New item with owned text.
pub fn item_string(&mut self, text: String) -> &mut Self {
self.items.push(MenuItem::new_string(text));
self
}
/// New item with navigation.
pub fn item_nav_str(
&mut self,
text: &'a str,
highlight: Range<usize>,
navchar: char,
) -> &mut Self {
self.items
.push(MenuItem::new_nav_str(text, highlight, navchar));
self
}
/// New item with navigation.
pub fn item_nav_string(
&mut self,
text: String,
highlight: Range<usize>,
navchar: char,
) -> &mut Self {
self.items
.push(MenuItem::new_nav_string(text, highlight, navchar));
self
}
/// Sets the separator for the last item added.
/// If there is none adds this as an empty menu-item.
pub fn separator(&mut self, separator: Separator) -> &mut Self {
if let Some(last) = self.items.last_mut() {
last.separator = Some(separator);
} else {
self.items.push(MenuItem::new().separator(separator));
}
self
}
/// Sets the last item to disabled.
/// If there is no last item does nothing.
pub fn disabled(&mut self, disable: bool) -> &mut Self {
if let Some(last) = self.items.last_mut() {
last.disabled = disable;
}
self
}
/// Build and deconstruct.
pub fn items(self) -> Vec<MenuItem<'a>> {
self.items
}
}
/// Static menu structure.
#[derive(Debug)]
pub struct StaticMenu {
/// Array of menus + array of items.
///
/// __MenuItems__
///
/// The first '_' marks the navigation-char.
///
/// __Separator__
///
/// This uses `_` (underscore) as prefix and
/// a fixed string to identify the separator:
///
/// * `_ ` - three blanks -> empty separator
/// * `____` - three underscores -> plain line
/// * `_______` - six underscore -> thick line
/// * `_===` - three equals -> double line
/// * `_---` - three hyphen -> dashed line
/// * `_...` - three dots -> dotted line
///
pub menu: &'static [(&'static str, &'static [&'static str])],
}
impl MenuStructure<'static> for StaticMenu {
fn menus(&'static self, menu: &mut MenuBuilder<'static>) {
for (s, _) in self.menu.iter() {
menu.item_parsed(s);
}
}
fn submenu(&'static self, n: usize, submenu: &mut MenuBuilder<'static>) {
for s in self.menu[n].1 {
submenu.item_parsed(s);
}
}
}
mod _private {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonExhaustive;
}