use crate::core::command::CommandId;
use crate::core::event::KeyCode;
#[derive(Clone, Debug)]
pub enum MenuItem {
Regular {
text: String,
command: CommandId,
key_code: KeyCode,
help_ctx: u16,
enabled: bool,
shortcut: Option<String>,
},
SubMenu {
text: String,
key_code: KeyCode,
help_ctx: u16,
menu: Menu,
},
Separator,
}
impl MenuItem {
pub fn new(text: &str, command: CommandId, key_code: KeyCode, help_ctx: u16) -> Self {
Self::Regular {
text: text.to_string(),
command,
key_code,
help_ctx,
enabled: true,
shortcut: None,
}
}
pub fn with_shortcut(text: &str, command: CommandId, key_code: KeyCode, shortcut: &str, help_ctx: u16) -> Self {
Self::Regular {
text: text.to_string(),
command,
key_code,
help_ctx,
enabled: true,
shortcut: Some(shortcut.to_string()),
}
}
pub fn new_disabled(text: &str, command: CommandId, key_code: KeyCode, help_ctx: u16) -> Self {
Self::Regular {
text: text.to_string(),
command,
key_code,
help_ctx,
enabled: false,
shortcut: None,
}
}
pub fn submenu(text: &str, key_code: KeyCode, menu: Menu, help_ctx: u16) -> Self {
Self::SubMenu {
text: text.to_string(),
key_code,
help_ctx,
menu,
}
}
pub fn separator() -> Self {
Self::Separator
}
pub fn is_selectable(&self) -> bool {
match self {
Self::Regular { enabled, .. } => *enabled,
Self::SubMenu { .. } => true,
Self::Separator => false,
}
}
pub fn get_accelerator(&self) -> Option<char> {
let text = match self {
Self::Regular { text, .. } | Self::SubMenu { text, .. } => text,
Self::Separator => return None,
};
let mut chars = text.chars();
while let Some(ch) = chars.next() {
if ch == '~' {
if let Some(accel) = chars.next() {
return Some(accel.to_ascii_lowercase());
}
}
}
None
}
pub fn text(&self) -> &str {
match self {
Self::Regular { text, .. } | Self::SubMenu { text, .. } => text,
Self::Separator => "",
}
}
pub fn command(&self) -> Option<CommandId> {
match self {
Self::Regular { command, .. } => Some(*command),
_ => None,
}
}
pub fn shortcut(&self) -> Option<&str> {
match self {
Self::Regular { shortcut, .. } => shortcut.as_deref(),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Menu {
pub items: Vec<MenuItem>,
pub default_index: Option<usize>,
}
impl Menu {
pub fn new() -> Self {
Self {
items: Vec::new(),
default_index: None,
}
}
pub fn from_items(items: Vec<MenuItem>) -> Self {
Self {
items,
default_index: None,
}
}
pub fn with_default(items: Vec<MenuItem>, default_index: usize) -> Self {
Self {
items,
default_index: Some(default_index),
}
}
pub fn add(&mut self, item: MenuItem) {
self.items.push(item);
}
pub fn set_default(&mut self, index: usize) {
if index < self.items.len() {
self.default_index = Some(index);
}
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl Default for Menu {
fn default() -> Self {
Self::new()
}
}
pub struct MenuBuilder {
items: Vec<MenuItem>,
help_ctx: u16,
}
impl MenuBuilder {
pub fn new() -> Self {
Self {
items: Vec::new(),
help_ctx: 0,
}
}
pub fn help_context(mut self, help_ctx: u16) -> Self {
self.help_ctx = help_ctx;
self
}
pub fn item(mut self, text: &str, command: CommandId, key_code: KeyCode) -> Self {
self.items.push(MenuItem::new(text, command, key_code, self.help_ctx));
self
}
pub fn item_with_shortcut(mut self, text: &str, command: CommandId, key_code: KeyCode, shortcut: &str) -> Self {
self.items.push(MenuItem::with_shortcut(text, command, key_code, shortcut, self.help_ctx));
self
}
pub fn item_disabled(mut self, text: &str, command: CommandId, key_code: KeyCode) -> Self {
self.items.push(MenuItem::new_disabled(text, command, key_code, self.help_ctx));
self
}
pub fn submenu(mut self, text: &str, key_code: KeyCode, menu: Menu) -> Self {
self.items.push(MenuItem::submenu(text, key_code, menu, self.help_ctx));
self
}
pub fn separator(mut self) -> Self {
self.items.push(MenuItem::separator());
self
}
pub fn build(self) -> Menu {
Menu::from_items(self.items)
}
}
impl Default for MenuBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_menu_builder() {
let menu = MenuBuilder::new()
.item("~O~pen", 100, 0x3D00)
.item("~S~ave", 101, 0x3C00)
.separator()
.item("E~x~it", 102, 0x2D00)
.build();
assert_eq!(menu.len(), 4);
assert!(matches!(menu.items[0], MenuItem::Regular { .. }));
assert!(matches!(menu.items[2], MenuItem::Separator));
}
#[test]
fn test_accelerator() {
let item = MenuItem::new("~O~pen", 100, 0x3D00, 0);
assert_eq!(item.get_accelerator(), Some('o'));
}
}