From cf9dcfafd000336db4e9f239ed5581539d9168f6 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Tue, 5 Nov 2024 10:21:07 +0800 Subject: [PATCH] feat: return context menu dismiss result (#236) --- .changes/add-context-menu-dismiss-result.md | 5 ++++ src/items/submenu.rs | 10 +++++-- src/lib.rs | 22 ++++++++++++-- src/menu.rs | 10 +++++-- src/platform_impl/gtk/mod.rs | 32 +++++++++++++++++++-- src/platform_impl/macos/mod.rs | 12 +++++--- src/platform_impl/windows/mod.rs | 20 +++++++++++-- 7 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 .changes/add-context-menu-dismiss-result.md diff --git a/.changes/add-context-menu-dismiss-result.md b/.changes/add-context-menu-dismiss-result.md new file mode 100644 index 00000000..426e58f4 --- /dev/null +++ b/.changes/add-context-menu-dismiss-result.md @@ -0,0 +1,5 @@ +--- +"muda": minor +--- + +Return `bool` in `ContextMenu::show_context_menu_for_hwnd`, `ContextMenu::show_context_menu_for_nsview` and `ContextMenu::show_context_menu_for_gtk_window` to indicate why the context menu was closed. \ No newline at end of file diff --git a/src/items/submenu.rs b/src/items/submenu.rs index cae67400..3c21c382 100644 --- a/src/items/submenu.rs +++ b/src/items/submenu.rs @@ -217,7 +217,7 @@ impl ContextMenu for Submenu { } #[cfg(target_os = "windows")] - unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) { + unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) -> bool { self.inner .borrow_mut() .show_context_menu_for_hwnd(hwnd, position) @@ -234,7 +234,11 @@ impl ContextMenu for Submenu { } #[cfg(target_os = "linux")] - fn show_context_menu_for_gtk_window(&self, w: >k::Window, position: Option) { + fn show_context_menu_for_gtk_window( + &self, + w: >k::Window, + position: Option, + ) -> bool { self.inner .borrow_mut() .show_context_menu_for_gtk_window(w, position) @@ -250,7 +254,7 @@ impl ContextMenu for Submenu { &self, view: *const std::ffi::c_void, position: Option, - ) { + ) -> bool { self.inner .borrow_mut() .show_context_menu_for_nsview(view, position) diff --git a/src/lib.rs b/src/lib.rs index 6357384b..d0a91771 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -311,11 +311,17 @@ pub trait ContextMenu { /// /// - `position` is relative to the window top-left corner, if `None`, the cursor position is used. /// + /// Returns `true` if menu tracking ended because an item was selected, and `false` if menu tracking was cancelled for any reason. + /// /// # Safety /// /// The `hwnd` must be a valid window HWND. #[cfg(target_os = "windows")] - unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option); + unsafe fn show_context_menu_for_hwnd( + &self, + hwnd: isize, + position: Option, + ) -> bool; /// Attach the menu subclass handler to the given hwnd /// so you can recieve events from that window using [MenuEvent::receiver] @@ -341,8 +347,16 @@ pub trait ContextMenu { /// Shows this menu as a context menu inside a [`gtk::Window`] /// /// - `position` is relative to the window top-left corner, if `None`, the cursor position is used. + /// + /// Returns `true` if menu tracking ended because an item was selected or clicked outside the menu to dismiss it. + /// + /// Returns `false` if menu tracking was cancelled for any reason. #[cfg(target_os = "linux")] - fn show_context_menu_for_gtk_window(&self, w: >k::Window, position: Option); + fn show_context_menu_for_gtk_window( + &self, + w: >k::Window, + position: Option, + ) -> bool; /// Get the underlying gtk menu reserved for context menus. /// @@ -354,6 +368,8 @@ pub trait ContextMenu { /// /// - `position` is relative to the window top-left corner, if `None`, the cursor position is used. /// + /// Returns `true` if menu tracking ended because an item was selected, and `false` if menu tracking was cancelled for any reason. + /// /// # Safety /// /// The view must be a pointer to a valid `NSView`. @@ -362,7 +378,7 @@ pub trait ContextMenu { &self, view: *const std::ffi::c_void, position: Option, - ); + ) -> bool; /// Get the underlying NSMenu reserved for context menus. /// diff --git a/src/menu.rs b/src/menu.rs index 18d9aaa7..8d5c79fe 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -375,7 +375,7 @@ impl ContextMenu for Menu { } #[cfg(target_os = "windows")] - unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) { + unsafe fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) -> bool { self.inner .borrow_mut() .show_context_menu_for_hwnd(hwnd, position) @@ -392,7 +392,11 @@ impl ContextMenu for Menu { } #[cfg(target_os = "linux")] - fn show_context_menu_for_gtk_window(&self, window: >k::Window, position: Option) { + fn show_context_menu_for_gtk_window( + &self, + window: >k::Window, + position: Option, + ) -> bool { self.inner .borrow_mut() .show_context_menu_for_gtk_window(window, position) @@ -408,7 +412,7 @@ impl ContextMenu for Menu { &self, view: *const std::ffi::c_void, position: Option, - ) { + ) -> bool { self.inner .borrow_mut() .show_context_menu_for_nsview(view, position) diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index e1b53c60..7a9875ab 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -368,7 +368,7 @@ impl Menu { &mut self, widget: &impl IsA, position: Option, - ) { + ) -> bool { show_context_menu(self.gtk_context_menu(), widget, position) } @@ -952,7 +952,7 @@ impl MenuChild { &mut self, widget: &impl IsA, position: Option, - ) { + ) -> bool { show_context_menu(self.gtk_context_menu(), widget, position) } @@ -1364,7 +1364,7 @@ fn show_context_menu( gtk_menu: gtk::Menu, widget: &impl IsA, position: Option, -) { +) -> bool { let (pos, window) = if let Some(pos) = position { let window = widget.window(); ( @@ -1411,6 +1411,11 @@ fn show_context_menu( } } + let (tx, rx) = crossbeam_channel::unbounded(); + let tx_clone = tx.clone(); + let id = gtk_menu.connect_cancel(move |_| tx_clone.send(false).unwrap_or(())); + let id2 = gtk_menu.connect_selection_done(move |_| tx.send(true).unwrap_or(())); + gtk_menu.popup_at_rect( &window, &gdk::Rectangle::new(pos.0, pos.1, 0, 0), @@ -1418,7 +1423,28 @@ fn show_context_menu( gdk::Gravity::NorthWest, Some(&event), ); + + loop { + gtk::main_iteration(); + + match rx.try_recv() { + Ok(result) => { + gtk_menu.disconnect(id); + gtk_menu.disconnect(id2); + return result; + } + Err(err) => { + if err.is_disconnected() { + gtk_menu.disconnect(id); + gtk_menu.disconnect(id2); + return false; + } + } + } + } } + + false } impl PredefinedMenuItemType { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index f9694281..5c41a735 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -175,7 +175,7 @@ impl Menu { &self, view: *const c_void, position: Option, - ) { + ) -> bool { // SAFETY: Upheld by caller show_context_menu(&self.ns_menu.1, view, position) } @@ -660,7 +660,7 @@ impl MenuChild { &self, view: *const c_void, position: Option, - ) { + ) -> bool { show_context_menu(&self.ns_menu.as_ref().unwrap().1, view, position) } @@ -1100,7 +1100,11 @@ fn menuitem_set_native_icon(menuitem: &NSMenuItem, icon: Option) { } } -unsafe fn show_context_menu(ns_menu: &NSMenu, view: *const c_void, position: Option) { +unsafe fn show_context_menu( + ns_menu: &NSMenu, + view: *const c_void, + position: Option, +) -> bool { // SAFETY: Caller verifies that the view is valid. let view: &NSView = unsafe { &*view.cast() }; @@ -1120,7 +1124,7 @@ unsafe fn show_context_menu(ns_menu: &NSMenu, view: *const c_void, position: Opt (location, None) }; - let _ = unsafe { ns_menu.popUpMenuPositioningItem_atLocation_inView(None, location, in_view) }; + unsafe { ns_menu.popUpMenuPositioningItem_atLocation_inView(None, location, in_view) } } impl NativeIcon { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 9f415b7a..320f2118 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -420,11 +420,19 @@ impl Menu { .unwrap_or(false) } - pub unsafe fn show_context_menu_for_hwnd(&mut self, hwnd: isize, position: Option) { + pub unsafe fn show_context_menu_for_hwnd( + &mut self, + hwnd: isize, + position: Option, + ) -> bool { let rc = show_context_menu(hwnd as _, self.hpopupmenu, position); if let Some(item) = rc.and_then(|rc| self.find_by_id(rc)) { - menu_selected(hwnd as _, &mut item.borrow_mut()); + unsafe { + menu_selected(hwnd as _, &mut item.borrow_mut()); + } + return true; } + false } pub unsafe fn set_theme_for_hwnd(&self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> { @@ -943,13 +951,19 @@ impl MenuChild { .collect() } - pub unsafe fn show_context_menu_for_hwnd(&mut self, hwnd: isize, position: Option) { + pub unsafe fn show_context_menu_for_hwnd( + &mut self, + hwnd: isize, + position: Option, + ) -> bool { let rc = show_context_menu(hwnd as _, self.hpopupmenu, position); if let Some(item) = rc.and_then(|rc| self.find_by_id(rc)) { unsafe { menu_selected(hwnd as _, &mut item.borrow_mut()); } + return true; } + false } pub unsafe fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {