diff --git a/.changes/event_handler.md b/.changes/event_handler.md new file mode 100644 index 00000000..35d02407 --- /dev/null +++ b/.changes/event_handler.md @@ -0,0 +1,5 @@ +--- +"muda": "patch" +--- + +Add `MenuEvent::set_event_handler` to set a handler for new menu events. \ No newline at end of file diff --git a/.changes/rm-menu-event-receiver.md b/.changes/rm-menu-event-receiver.md new file mode 100644 index 00000000..a1fa63cc --- /dev/null +++ b/.changes/rm-menu-event-receiver.md @@ -0,0 +1,5 @@ +--- +"muda": "minor" +--- + +**Breaking change** Remove `menu_event_receiver` function, use `MenuEvent::receiver` instead. \ No newline at end of file diff --git a/README.md b/README.md index 6f012231..d3a3db44 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ menu.show_context_menu_for_nsview(nsview, x, y); ``` ## Processing menu events -You can use [`menu_event_receiver`](https://docs.rs/muda/latest/muda/fn.menu_event_receiver.html) to get a reference to the [`MenuEventReceiver`](https://docs.rs/muda/latest/muda/type.MenuEventReceiver.html) +You can use `MenuEvent::receiver` to get a reference to the `MenuEventReceiver` which you can use to listen to events when a menu item is activated ```rs -if let Ok(event) = menu_event_receiver().try_recv() { +if let Ok(event) = MenuEvent::receiver().try_recv() { match event.id { _ if event.id == save_item.id() => { println!("Save menu item activated"); diff --git a/examples/tao.rs b/examples/tao.rs index 99613a88..906917fc 100644 --- a/examples/tao.rs +++ b/examples/tao.rs @@ -5,7 +5,7 @@ #![allow(unused)] use muda::{ accelerator::{Accelerator, Code, Modifiers}, - menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuItem, + AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu, }; #[cfg(target_os = "macos")] @@ -144,7 +144,7 @@ fn main() { window_m.set_windows_menu_for_nsapp(); } - let menu_channel = menu_event_receiver(); + let menu_channel = MenuEvent::receiver(); let mut x = 0_f64; let mut y = 0_f64; diff --git a/examples/winit.rs b/examples/winit.rs index 9f909b8a..699ee6ab 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -5,7 +5,7 @@ #![allow(unused)] use muda::{ accelerator::{Accelerator, Code, Modifiers}, - menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuItem, + AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu, }; #[cfg(target_os = "macos")] @@ -136,7 +136,7 @@ fn main() { window_m.set_windows_menu_for_nsapp(); } - let menu_channel = menu_event_receiver(); + let menu_channel = MenuEvent::receiver(); let mut x = 0_f64; let mut y = 0_f64; diff --git a/src/lib.rs b/src/lib.rs index 12a1c807..26793733 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,13 +79,13 @@ //! ``` //! # Processing menu events //! -//! You can use [`menu_event_receiver`] to get a reference to the [`MenuEventReceiver`] +//! You can use [`MenuEvent::receiver`] to get a reference to the [`MenuEventReceiver`] //! which you can use to listen to events when a menu item is activated //! ```no_run -//! # use muda::menu_event_receiver; +//! # use muda::MenuEvent; //! # //! # let save_item: muda::MenuItem = unsafe { std::mem::zeroed() }; -//! if let Ok(event) = menu_event_receiver().try_recv() { +//! if let Ok(event) = MenuEvent::receiver().try_recv() { //! match event.id { //! id if id == save_item.id() => { //! println!("Save menu item activated"); @@ -103,7 +103,7 @@ //! See [`Menu::init_for_hwnd`] for more details use crossbeam_channel::{unbounded, Receiver, Sender}; -use once_cell::sync::Lazy; +use once_cell::sync::{Lazy, OnceCell}; pub mod accelerator; mod check_menu_item; @@ -192,7 +192,7 @@ pub trait ContextMenu { fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64); /// Attach the menu subclass handler to the given hwnd - /// so you can recieve events from that window using [menu_event_receiver] + /// so you can recieve events from that window using [MenuEvent::receiver] /// /// This can be used along with [`ContextMenu::hpopupmenu`] when implementing a tray icon menu. #[cfg(target_os = "windows")] @@ -231,11 +231,41 @@ pub struct MenuEvent { /// A reciever that could be used to listen to menu events. pub type MenuEventReceiver = Receiver; +type MenuEventHandler = Box; static MENU_CHANNEL: Lazy<(Sender, MenuEventReceiver)> = Lazy::new(unbounded); +static MENU_EVENT_HANDLER: OnceCell> = OnceCell::new(); -/// Gets a reference to the event channel's [MenuEventReceiver] -/// which can be used to listen for menu events. -pub fn menu_event_receiver<'a>() -> &'a MenuEventReceiver { - &MENU_CHANNEL.1 +impl MenuEvent { + /// Gets a reference to the event channel's [`MenuEventReceiver`] + /// which can be used to listen for menu events. + /// + /// ## Note + /// + /// This will not receive any events if [`MenuEvent::set_event_handler`] has been called with a `Some` value. + pub fn receiver<'a>() -> &'a MenuEventReceiver { + &MENU_CHANNEL.1 + } + + /// Set a handler to be called for new events. Useful for implementing custom event sender. + /// + /// ## Note + /// + /// Calling this function with a `Some` value, + /// will not send new events to the channel associated with [`MenuEvent::receiver`] + pub fn set_event_handler(f: Option) { + if let Some(f) = f { + let _ = MENU_EVENT_HANDLER.set(Some(Box::new(f))); + } else { + let _ = MENU_EVENT_HANDLER.set(None); + } + } + + pub(crate) fn send(event: MenuEvent) { + if let Some(handler) = MENU_EVENT_HANDLER.get_or_init(|| None) { + handler(event); + } else { + let _ = MENU_CHANNEL.0.send(event); + } + } } diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index d8c0459b..f5d0f3b0 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -12,7 +12,7 @@ use crate::{ icon::Icon, predefined::PredfinedMenuItemType, util::{AddOp, Counter}, - MenuItemType, + MenuEvent, MenuItemType, }; use accelerator::{from_gtk_mnemonic, parse_accelerator, register_accelerator, to_gtk_mnemonic}; use gtk::{prelude::*, Orientation}; @@ -826,7 +826,7 @@ impl MenuItem { let id = self_.id; item.connect_activate(move |_| { - let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id }); }); if add_to_cache { @@ -1069,7 +1069,7 @@ impl CheckMenuItem { is_syncing_checked_state_c.store(false, Ordering::Release); - let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id }); } }); @@ -1188,7 +1188,7 @@ impl IconMenuItem { let id = self_.id; item.connect_activate(move |_| { - let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id }); }); if add_to_cache { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 8bebb222..7db9cdd1 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -26,7 +26,7 @@ use crate::{ icon::Icon, predefined::PredfinedMenuItemType, util::{AddOp, Counter}, - MenuItemExt, MenuItemType, + MenuEvent, MenuItemExt, MenuItemType, }; static COUNTER: Counter = Counter::new(); @@ -952,7 +952,7 @@ extern "C" fn fire_menu_item_click(this: &Object, _: Sel, _item: id) { (*item).set_checked(!(*item).is_checked()); } - let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id }); } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index b1f48fd7..2323c3a3 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,7 +13,7 @@ use crate::{ icon::Icon, predefined::PredfinedMenuItemType, util::{AddOp, Counter}, - MenuItemType, + MenuEvent, MenuItemType, }; use std::{cell::RefCell, fmt::Debug, rc::Rc}; use util::{decode_wide, encode_wide, Accel}; @@ -1144,7 +1144,7 @@ website: {} {} } if dispatch { - let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id }); } } }