426 lines · 11.2 KB · Rust
RawDownload
use crate::{Action, App, Platform, SharedString}; /// A menu of the application, either a main menu or a submenupub struct Menu {    /// The name of the menu    pub name: SharedString,     /// The items in the menu    pub items: Vec<MenuItem>,     /// Whether this menu is disabled    pub disabled: bool,} impl Menu {    /// Create a new Menu with the given name    pub fn new(name: impl Into<SharedString>) -> Self {        Self {            name: name.into(),            items: vec![],            disabled: false,        }    }     /// Set items to be in this menu    pub fn items(mut self, items: impl IntoIterator<Item = MenuItem>) -> Self {        self.items = items.into_iter().collect();        self    }     /// Set whether this menu is disabled    pub fn disabled(mut self, disabled: bool) -> Self {        self.disabled = disabled;        self    }     /// Create an OwnedMenu from this Menu    pub fn owned(self) -> OwnedMenu {        OwnedMenu {            name: self.name.to_string().into(),            items: self.items.into_iter().map(|item| item.owned()).collect(),            disabled: self.disabled,        }    }} /// OS menus are menus that are recognized by the operating system/// This allows the operating system to provide specialized items for/// these menuspub struct OsMenu {    /// The name of the menu    pub name: SharedString,     /// The type of menu    pub menu_type: SystemMenuType,} impl OsMenu {    /// Create an OwnedOsMenu from this OsMenu    pub fn owned(self) -> OwnedOsMenu {        OwnedOsMenu {            name: self.name.to_string().into(),            menu_type: self.menu_type,        }    }} /// The type of system menu#[derive(Copy, Clone, Eq, PartialEq)]pub enum SystemMenuType {    /// The 'Services' menu in the Application menu on macOS    Services,} /// The different kinds of items that can be in a menupub enum MenuItem {    /// A separator between items    Separator,     /// A submenu    Submenu(Menu),     /// A menu, managed by the system (for example, the Services menu on macOS)    SystemMenu(OsMenu),     /// An action that can be performed    Action {        /// The name of this menu item        name: SharedString,         /// The action to perform when this menu item is selected        action: Box<dyn Action>,         /// The OS Action that corresponds to this action, if any        /// See [`OsAction`] for more information        os_action: Option<OsAction>,         /// Whether this action is checked        checked: bool,         /// Whether this action is disabled        disabled: bool,    },} impl MenuItem {    /// Creates a new menu item that is a separator    pub fn separator() -> Self {        Self::Separator    }     /// Creates a new menu item that is a submenu    pub fn submenu(menu: Menu) -> Self {        Self::Submenu(menu)    }     /// Creates a new submenu that is populated by the OS    pub fn os_submenu(name: impl Into<SharedString>, menu_type: SystemMenuType) -> Self {        Self::SystemMenu(OsMenu {            name: name.into(),            menu_type,        })    }     /// Creates a new menu item that invokes an action    pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {        Self::Action {            name: name.into(),            action: Box::new(action),            os_action: None,            checked: false,            disabled: false,        }    }     /// Creates a new menu item that invokes an action and has an OS action    pub fn os_action(        name: impl Into<SharedString>,        action: impl Action,        os_action: OsAction,    ) -> Self {        Self::Action {            name: name.into(),            action: Box::new(action),            os_action: Some(os_action),            checked: false,            disabled: false,        }    }     /// Create an OwnedMenuItem from this MenuItem    pub fn owned(self) -> OwnedMenuItem {        match self {            MenuItem::Separator => OwnedMenuItem::Separator,            MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),            MenuItem::Action {                name,                action,                os_action,                checked,                disabled,            } => OwnedMenuItem::Action {                name: name.into(),                action,                os_action,                checked,                disabled,            },            MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),        }    }     /// Set whether this menu item is checked    ///    /// Only for [`MenuItem::Action`], otherwise, will be ignored    pub fn checked(mut self, checked: bool) -> Self {        match &mut self {            MenuItem::Action { checked: old, .. } => {                *old = checked;            }            _ => {}        }        self    }     /// Returns whether this menu item is checked    ///    /// Only for [`MenuItem::Action`], otherwise, returns false    #[inline]    pub fn is_checked(&self) -> bool {        match self {            MenuItem::Action { checked, .. } => *checked,            _ => false,        }    }     /// Set whether this menu item is disabled    pub fn disabled(mut self, disabled: bool) -> Self {        match &mut self {            MenuItem::Action { disabled: old, .. } => {                *old = disabled;            }            MenuItem::Submenu(submenu) => {                submenu.disabled = disabled;            }            _ => {}        }        self    }     /// Returns whether this menu item is disabled    ///    /// Only for [`MenuItem::Action`] and [`MenuItem::Submenu`], otherwise, returns false    #[inline]    pub fn is_disabled(&self) -> bool {        match self {            MenuItem::Action { disabled, .. } => *disabled,            MenuItem::Submenu(submenu) => submenu.disabled,            _ => false,        }    }} /// OS menus are menus that are recognized by the operating system/// This allows the operating system to provide specialized items for/// these menus#[derive(Clone)]pub struct OwnedOsMenu {    /// The name of the menu    pub name: SharedString,     /// The type of menu    pub menu_type: SystemMenuType,} /// A menu of the application, either a main menu or a submenu#[derive(Clone)]pub struct OwnedMenu {    /// The name of the menu    pub name: SharedString,     /// The items in the menu    pub items: Vec<OwnedMenuItem>,     /// Whether this menu is disabled    pub disabled: bool,} /// The different kinds of items that can be in a menupub enum OwnedMenuItem {    /// A separator between items    Separator,     /// A submenu    Submenu(OwnedMenu),     /// A menu, managed by the system (for example, the Services menu on macOS)    SystemMenu(OwnedOsMenu),     /// An action that can be performed    Action {        /// The name of this menu item        name: String,         /// The action to perform when this menu item is selected        action: Box<dyn Action>,         /// The OS Action that corresponds to this action, if any        /// See [`OsAction`] for more information        os_action: Option<OsAction>,         /// Whether this action is checked        checked: bool,         /// Whether this action is disabled        disabled: bool,    },} impl Clone for OwnedMenuItem {    fn clone(&self) -> Self {        match self {            OwnedMenuItem::Separator => OwnedMenuItem::Separator,            OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),            OwnedMenuItem::Action {                name,                action,                os_action,                checked,                disabled,            } => OwnedMenuItem::Action {                name: name.clone(),                action: action.boxed_clone(),                os_action: *os_action,                checked: *checked,                disabled: *disabled,            },            OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),        }    }} // TODO: As part of the global selections refactor, these should// be moved to GPUI-provided actions that make this association// without leaking the platform details to GPUI users /// OS actions are actions that are recognized by the operating system/// This allows the operating system to provide specialized behavior for/// these actions#[derive(Copy, Clone, Eq, PartialEq)]pub enum OsAction {    /// The 'cut' action    Cut,     /// The 'copy' action    Copy,     /// The 'paste' action    Paste,     /// The 'select all' action    SelectAll,     /// The 'undo' action    Undo,     /// The 'redo' action    Redo,} pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &App) {    platform.on_will_open_app_menu(Box::new({        let cx = cx.to_async();        move || {            if let Some(app) = cx.app.upgrade() {                app.borrow_mut().update(|cx| cx.clear_pending_keystrokes());            }        }    }));     platform.on_validate_app_menu_command(Box::new({        let cx = cx.to_async();        move |action| {            cx.app                .upgrade()                .map(|app| app.borrow_mut().update(|cx| cx.is_action_available(action)))                .unwrap_or(false)        }    }));     platform.on_app_menu_action(Box::new({        let cx = cx.to_async();        move |action| {            if let Some(app) = cx.app.upgrade() {                app.borrow_mut().update(|cx| cx.dispatch_action(action));            }        }    }));} #[cfg(test)]mod tests {    use crate::Menu;     #[test]    fn test_menu() {        let menu = Menu::new("App")            .items(vec![                crate::MenuItem::action("Action 1", gpui::NoAction),                crate::MenuItem::separator(),            ])            .disabled(true);         assert_eq!(menu.name.as_ref(), "App");        assert_eq!(menu.items.len(), 2);        assert!(menu.disabled);    }     #[test]    fn test_menu_item_builder() {        use super::MenuItem;         let item = MenuItem::action("Test Action", gpui::NoAction);        assert_eq!(            match &item {                MenuItem::Action { name, .. } => name.as_ref(),                _ => unreachable!(),            },            "Test Action"        );        assert!(matches!(            item,            MenuItem::Action {                checked: false,                disabled: false,                ..            }        ));         assert!(            MenuItem::action("Test Action", gpui::NoAction)                .checked(true)                .is_checked()        );        assert!(            MenuItem::action("Test Action", gpui::NoAction)                .disabled(true)                .is_disabled()        );         let submenu = MenuItem::submenu(super::Menu {            name: "Submenu".into(),            items: vec![],            disabled: true,        });        assert_eq!(            match &submenu {                MenuItem::Submenu(menu) => menu.name.as_ref(),                _ => unreachable!(),            },            "Submenu"        );        assert!(!submenu.is_checked());        assert!(submenu.is_disabled());    }}