use crate::event::MenuItemId;
#[derive(Debug, Clone, Default)]
pub struct Menu {
items: Vec<MenuItem>,
}
impl Menu {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn items(&self) -> &[MenuItem] {
&self.items
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn find_item(&self, id: &MenuItemId) -> Option<&MenuItem> {
self.items.iter().find(|item| {
if let MenuItem::Item { id: item_id, .. } = item {
item_id == id
} else if let MenuItem::CheckItem { id: item_id, .. } = item {
item_id == id
} else {
false
}
})
}
}
#[derive(Debug, Clone)]
pub enum MenuItem {
Item {
id: MenuItemId,
label: String,
enabled: bool,
},
CheckItem {
id: MenuItemId,
label: String,
checked: bool,
enabled: bool,
},
Separator,
SubMenu {
label: String,
items: Vec<MenuItem>,
},
}
impl MenuItem {
pub fn new(id: impl Into<MenuItemId>, label: impl Into<String>) -> Self {
MenuItem::Item {
id: id.into(),
label: label.into(),
enabled: true,
}
}
pub fn check(
id: impl Into<MenuItemId>,
label: impl Into<String>,
checked: bool,
) -> Self {
MenuItem::CheckItem {
id: id.into(),
label: label.into(),
checked,
enabled: true,
}
}
pub fn separator() -> Self {
MenuItem::Separator
}
pub fn id(&self) -> Option<&MenuItemId> {
match self {
MenuItem::Item { id, .. } => Some(id),
MenuItem::CheckItem { id, .. } => Some(id),
_ => None,
}
}
pub fn label(&self) -> Option<&str> {
match self {
MenuItem::Item { label, .. } => Some(label),
MenuItem::CheckItem { label, .. } => Some(label),
MenuItem::SubMenu { label, .. } => Some(label),
MenuItem::Separator => None,
}
}
pub fn is_enabled(&self) -> bool {
match self {
MenuItem::Item { enabled, .. } => *enabled,
MenuItem::CheckItem { enabled, .. } => *enabled,
MenuItem::SubMenu { .. } => true,
MenuItem::Separator => false,
}
}
}
#[derive(Debug, Default)]
pub struct MenuBuilder {
items: Vec<MenuItem>,
}
impl MenuBuilder {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn item(mut self, id: impl Into<MenuItemId>, label: impl Into<String>) -> Self {
self.items.push(MenuItem::Item {
id: id.into(),
label: label.into(),
enabled: true,
});
self
}
pub fn item_disabled(
mut self,
id: impl Into<MenuItemId>,
label: impl Into<String>,
) -> Self {
self.items.push(MenuItem::Item {
id: id.into(),
label: label.into(),
enabled: false,
});
self
}
pub fn check_item(
mut self,
id: impl Into<MenuItemId>,
label: impl Into<String>,
checked: bool,
) -> Self {
self.items.push(MenuItem::CheckItem {
id: id.into(),
label: label.into(),
checked,
enabled: true,
});
self
}
pub fn separator(mut self) -> Self {
self.items.push(MenuItem::Separator);
self
}
pub fn submenu(mut self, label: impl Into<String>, builder: MenuBuilder) -> Self {
self.items.push(MenuItem::SubMenu {
label: label.into(),
items: builder.items,
});
self
}
pub fn build(self) -> Menu {
Menu { items: self.items }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_menu() {
let menu = Menu::new();
assert!(menu.is_empty());
assert_eq!(menu.len(), 0);
}
#[test]
fn test_menu_builder() {
let menu = MenuBuilder::new()
.item("open", "打开")
.separator()
.check_item("auto_start", "开机启动", false)
.item("exit", "退出")
.build();
assert_eq!(menu.len(), 4);
assert!(!menu.is_empty());
}
#[test]
fn test_submenu() {
let _submenu = MenuBuilder::new()
.item("sub1", "子项1")
.item("sub2", "子项2")
.build();
let menu = MenuBuilder::new()
.item("main", "主菜单")
.submenu("更多", MenuBuilder::new().item("sub1", "子项1"))
.build();
assert_eq!(menu.len(), 2);
if let MenuItem::SubMenu { label, items } = &menu.items()[1] {
assert_eq!(label, "更多");
assert_eq!(items.len(), 1);
} else {
panic!("Expected SubMenu");
}
}
#[test]
fn test_find_item() {
let menu = MenuBuilder::new()
.item("open", "打开")
.item("exit", "退出")
.build();
let open = menu.find_item(&MenuItemId::new("open"));
assert!(open.is_some());
assert_eq!(open.unwrap().label(), Some("打开"));
let not_found = menu.find_item(&MenuItemId::new("unknown"));
assert!(not_found.is_none());
}
#[test]
fn test_menu_item_properties() {
let item = MenuItem::new("test", "Test Label");
assert_eq!(item.id().map(|id| id.as_str()), Some("test"));
assert_eq!(item.label(), Some("Test Label"));
assert!(item.is_enabled());
let sep = MenuItem::separator();
assert!(sep.id().is_none());
assert!(sep.label().is_none());
assert!(!sep.is_enabled());
}
#[test]
fn test_disabled_item() {
let menu = MenuBuilder::new()
.item_disabled("disabled", "禁用项")
.build();
if let MenuItem::Item { enabled, .. } = &menu.items()[0] {
assert!(!enabled);
} else {
panic!("Expected Item");
}
}
}