diff --git a/crates/gpui/examples/set_menus.rs b/crates/gpui/examples/set_menus.rs index f53fff7c7f..8a97a8d8a2 100644 --- a/crates/gpui/examples/set_menus.rs +++ b/crates/gpui/examples/set_menus.rs @@ -1,5 +1,6 @@ use gpui::{ - App, Application, Context, Menu, MenuItem, Window, WindowOptions, actions, div, prelude::*, rgb, + App, Application, Context, Menu, MenuItem, SystemMenuType, Window, WindowOptions, actions, div, + prelude::*, rgb, }; struct SetMenus; @@ -27,7 +28,11 @@ fn main() { // Add menu items cx.set_menus(vec![Menu { name: "set_menus".into(), - items: vec![MenuItem::action("Quit", Quit)], + items: vec![ + MenuItem::os_submenu("Services", SystemMenuType::Services), + MenuItem::separator(), + MenuItem::action("Quit", Quit), + ], }]); cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| SetMenus {})) .unwrap(); diff --git a/crates/gpui/src/platform/app_menu.rs b/crates/gpui/src/platform/app_menu.rs index 2815cbdd7f..4069fee726 100644 --- a/crates/gpui/src/platform/app_menu.rs +++ b/crates/gpui/src/platform/app_menu.rs @@ -20,6 +20,34 @@ impl Menu { } } +/// OS menus are menus that are recognized by the operating system +/// This allows the operating system to provide specialized items for +/// these menus +pub 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 menu pub enum MenuItem { /// A separator between items @@ -28,6 +56,9 @@ pub enum MenuItem { /// 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 @@ -53,6 +84,14 @@ impl MenuItem { Self::Submenu(menu) } + /// Creates a new submenu that is populated by the OS + pub fn os_submenu(name: impl Into, 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, action: impl Action) -> Self { Self::Action { @@ -89,10 +128,23 @@ impl MenuItem { action, os_action, }, + MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()), } } } +/// 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 { @@ -111,6 +163,9 @@ pub enum OwnedMenuItem { /// 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 @@ -139,6 +194,7 @@ impl Clone for OwnedMenuItem { action: action.boxed_clone(), os_action: *os_action, }, + OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()), } } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index c71eb448c4..c573131799 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -7,9 +7,9 @@ use super::{ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher, - MacDisplay, MacWindow, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay, - PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, - WindowAppearance, WindowParams, hash, + MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform, + PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, + SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash, }; use anyhow::{Context as _, anyhow}; use block::ConcreteBlock; @@ -413,9 +413,20 @@ impl MacPlatform { } item.setSubmenu_(submenu); item.setTitle_(ns_string(&name)); - if name == "Services" { - let app: id = msg_send![APP_CLASS, sharedApplication]; - app.setServicesMenu_(item); + item + } + MenuItem::SystemMenu(OsMenu { name, menu_type }) => { + let item = NSMenuItem::new(nil).autorelease(); + let submenu = NSMenu::new(nil).autorelease(); + submenu.setDelegate_(delegate); + item.setSubmenu_(submenu); + item.setTitle_(ns_string(&name)); + + match menu_type { + SystemMenuType::Services => { + let app: id = msg_send![APP_CLASS, sharedApplication]; + app.setServicesMenu_(item); + } } item diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index a5d5f154c9..98f0eeb6cc 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -121,8 +121,16 @@ impl ApplicationMenu { menu.action(name, action) } OwnedMenuItem::Submenu(_) => menu, + OwnedMenuItem::SystemMenu(_) => { + // A system menu doesn't make sense in this context, so ignore it + menu + } }) } + OwnedMenuItem::SystemMenu(_) => { + // A system menu doesn't make sense in this context, so ignore it + menu + } }) }) } diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 15d5659f03..53eec42ba0 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -35,10 +35,7 @@ pub fn app_menus() -> Vec { ], }), MenuItem::separator(), - MenuItem::submenu(Menu { - name: "Services".into(), - items: vec![], - }), + MenuItem::os_submenu("Services", gpui::SystemMenuType::Services), MenuItem::separator(), MenuItem::action("Extensions", zed_actions::Extensions::default()), MenuItem::action("Install CLI", install_cli::Install),