//! Build menus for your application. use crate::keyboard::Hotkey; /// Menu representation. /// /// This can be used by `shell` implementations to create a menu. #[derive(Debug, Clone)] pub struct Menu { entries: Vec>, } impl PartialEq for Menu { fn eq(&self, other: &Self) -> bool { self.entries == other.entries } } impl Menu { /// Creates an empty [`Menu`]. pub fn new() -> Self { Self::with_entries(Vec::new()) } /// Creates a new [`Menu`] with the given entries. pub fn with_entries(entries: Vec>) -> Self { Self { entries } } /// Returns a [`MenuEntry`] iterator. pub fn iter(&self) -> impl Iterator> { self.entries.iter() } /// Adds an [`Entry`] to the [`Menu`]. pub fn push(mut self, entry: Entry) -> Self { self.entries.push(entry); self } /// Maps the `Message` of the [`Menu`] using the provided function. /// /// This is useful to compose menus and split them into different /// abstraction levels. pub fn map(self, f: impl Fn(Message) -> B + Copy) -> Menu { // TODO: Use a boxed trait to avoid reallocation of entries Menu { entries: self .entries .into_iter() .map(|entry| entry.map(f)) .collect(), } } } /// Represents one of the possible entries used to build a [`Menu`]. #[derive(Debug, Clone)] pub enum Entry { /// Item for a [`Menu`] Item { /// The title of the item title: String, /// The [`Hotkey`] to activate the item, if any hotkey: Option, /// The message generated when the item is activated on_activation: Message, }, /// Dropdown for a [`Menu`] Dropdown { /// Title of the dropdown title: String, /// The submenu of the dropdown submenu: Menu, }, /// Separator for a [`Menu`] Separator, } impl Entry { /// Creates an [`Entry::Item`]. pub fn item>( title: S, hotkey: impl Into>, on_activation: Message, ) -> Self { let title = title.into(); let hotkey = hotkey.into(); Self::Item { title, hotkey, on_activation, } } /// Creates an [`Entry::Dropdown`]. pub fn dropdown>(title: S, submenu: Menu) -> Self { let title = title.into(); Self::Dropdown { title, submenu } } fn map(self, f: impl Fn(Message) -> B + Copy) -> Entry { match self { Self::Item { title, hotkey, on_activation, } => Entry::Item { title, hotkey, on_activation: f(on_activation), }, Self::Dropdown { title, submenu } => Entry::Dropdown { title, submenu: submenu.map(f), }, Self::Separator => Entry::Separator, } } } impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { match (self, other) { ( Entry::Item { title, hotkey, .. }, Entry::Item { title: other_title, hotkey: other_hotkey, .. }, ) => title == other_title && hotkey == other_hotkey, ( Entry::Dropdown { title, submenu }, Entry::Dropdown { title: other_title, submenu: other_submenu, }, ) => title == other_title && submenu == other_submenu, (Entry::Separator, Entry::Separator) => true, _ => false, } } }