From 37153b826f8b381ed814e1032f085f9a2551b21d Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 24 Nov 2022 18:07:11 +0200 Subject: [PATCH 1/2] feat: expose macOS NSMenu --- src/lib.rs | 3 +++ src/menu.rs | 5 +++++ src/platform_impl/macos/mod.rs | 17 +++++++++++++++++ src/submenu.rs | 5 +++++ 4 files changed, 30 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0a13730..cc9b108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,9 @@ pub trait ContextMenu { /// (the click which triggered the menu to be shown). #[cfg(target_os = "macos")] fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64); + + #[cfg(target_os = "macos")] + fn ns_menu(&self) -> *mut std::ffi::c_void; } /// Describes a menu event emitted when a menu item is activated diff --git a/src/menu.rs b/src/menu.rs index c6d2b11..d13f666 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -46,6 +46,11 @@ impl ContextMenu for Menu { fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) { self.0.show_context_menu_for_nsview(view, x, y) } + + #[cfg(target_os = "macos")] + fn ns_menu(&self) -> *mut std::ffi::c_void { + self.0.ns_menu() + } } impl Menu { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 463de4a..21fbd22 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -242,6 +242,10 @@ impl Menu { msg_send![self.ns_menu, popUpMenuPositioningItem: nil atLocation: location inView: view] } } + + pub fn ns_menu(&self) -> *mut std::ffi::c_void { + self.ns_menu as _ + } } #[derive(Clone)] @@ -446,6 +450,19 @@ impl Submenu { unsafe { msg_send![NSApp(), setHelpMenu: ns_menus[0]] } } } + + pub fn ns_menu(&self) -> *mut std::ffi::c_void { + *self + .0 + .borrow() + .ns_menus + .values() + .collect::>() + .get(0) + .unwrap() + .get(0) + .unwrap() as _ + } } #[derive(Clone, Debug)] diff --git a/src/submenu.rs b/src/submenu.rs index 9fa0696..de97c26 100644 --- a/src/submenu.rs +++ b/src/submenu.rs @@ -54,6 +54,11 @@ impl ContextMenu for Submenu { fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) { self.0.show_context_menu_for_nsview(view, x, y) } + + #[cfg(target_os = "macos")] + fn ns_menu(&self) -> *mut std::ffi::c_void { + self.0.ns_menu() + } } impl Submenu { From 35912d699ddb70bca1cff2acbb6b39f098badb32 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sat, 26 Nov 2022 15:13:30 +0200 Subject: [PATCH 2/2] add dedicated ns_menu for context menus and such --- src/platform_impl/macos/mod.rs | 88 +++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 21fbd22..4f9a94b 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -27,7 +27,7 @@ static BLOCK_PTR: &str = "mudaMenuItemBlockPtr"; /// A generic child in a menu /// /// Be careful when cloning this item and treat it as read-only -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] #[allow(dead_code)] struct MenuChild { // shared fields between submenus and menu items @@ -50,6 +50,25 @@ struct MenuChild { // submenu fields children: Option>>>, ns_menus: HashMap>, + ns_menu: (u32, id), +} + +impl Default for MenuChild { + fn default() -> Self { + Self { + type_: Default::default(), + id: Default::default(), + text: Default::default(), + enabled: Default::default(), + ns_menu_items: Default::default(), + accelerator: Default::default(), + predefined_item_type: Default::default(), + checked: Default::default(), + children: Default::default(), + ns_menus: Default::default(), + ns_menu: (0, 0 as _), + } + } } impl MenuChild { @@ -258,6 +277,7 @@ impl Submenu { text: strip_mnemonic(text), enabled, children: Some(Vec::new()), + ns_menu: (COUNTER.next(), unsafe { NSMenu::alloc(nil).autorelease() }), ..Default::default() }))) } @@ -339,6 +359,10 @@ impl Submenu { ns_menu.addItem_(ns_menu_item); } } + + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self_.ns_menu.0); + self_.ns_menu.1.addItem_(ns_menu_item); + self_.children.as_mut().unwrap().push(item_child); } AddOp::Insert(position) => { @@ -348,6 +372,10 @@ impl Submenu { let () = msg_send![ns_menu, insertItem: ns_menu_item atIndex: position as NSInteger]; } } + + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self_.ns_menu.0); + let () = msg_send![ self_.ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger]; + self_ .children .as_mut() @@ -359,29 +387,39 @@ impl Submenu { } pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { - let mut child = self.0.borrow_mut(); + let mut self_ = self.0.borrow_mut(); - let item_child: Rc> = item.get_child(); + let child: Rc> = item.get_child(); // get a list of instances of the specified NSMenuItem in this menu - if let Some(ns_menu_items) = item_child.borrow_mut().ns_menu_items.remove(&child.id) { + if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self_.id) { // remove each NSMenuItem from the NSMenu unsafe { for item in ns_menu_items { - for menus in child.ns_menus.values() { + for menus in self_.ns_menus.values() { for &ns_menu in menus { let () = msg_send![ns_menu, removeItem: item]; } } + + let () = msg_send![self_.ns_menu.1, removeItem: item]; + } + } + } + + if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self_.ns_menu.0) { + unsafe { + for item in ns_menu_items { + let () = msg_send![self_.ns_menu.1, removeItem: item]; } } } // remove the item from our internal list of children - let children = child.children.as_mut().unwrap(); + let children = self_.children.as_mut().unwrap(); let index = children .iter() - .position(|e| e == &item_child) + .position(|e| e == &child) .ok_or(crate::Error::NotAChildOfThisMenu)?; children.remove(index); @@ -426,42 +464,26 @@ impl Submenu { } pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) { - // TODO: this needs to work even if it hasn't already been added to a menu - if let Some(ns_menus) = self.0.borrow().ns_menus.get(&1) { - unsafe { - let window: id = msg_send![view, window]; - let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; - let view_point = NSPoint::new(x / scale_factor, y / scale_factor); - let view_rect: NSRect = msg_send![view, frame]; - let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y); - msg_send![ns_menus[0], popUpMenuPositioningItem: nil atLocation: location inView: view] - } + unsafe { + let window: id = msg_send![view, window]; + let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; + let view_point = NSPoint::new(x / scale_factor, y / scale_factor); + let view_rect: NSRect = msg_send![view, frame]; + let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y); + msg_send![self.0.borrow().ns_menu.1, popUpMenuPositioningItem: nil atLocation: location inView: view] } } pub fn set_windows_menu_for_nsapp(&self) { - if let Some(ns_menus) = self.0.borrow().ns_menus.get(&1) { - unsafe { NSApp().setWindowsMenu_(ns_menus[0]) } - } + unsafe { NSApp().setWindowsMenu_(self.0.borrow().ns_menu.1) } } pub fn set_help_menu_for_nsapp(&self) { - if let Some(ns_menus) = self.0.borrow().ns_menus.get(&1) { - unsafe { msg_send![NSApp(), setHelpMenu: ns_menus[0]] } - } + unsafe { msg_send![NSApp(), setHelpMenu: self.0.borrow().ns_menu.1] } } pub fn ns_menu(&self) -> *mut std::ffi::c_void { - *self - .0 - .borrow() - .ns_menus - .values() - .collect::>() - .get(0) - .unwrap() - .get(0) - .unwrap() as _ + self.0.borrow().ns_menu.1 as _ } }