diff --git a/examples/tao.rs b/examples/tao.rs index 5458a01..8153eed 100644 --- a/examples/tao.rs +++ b/examples/tao.rs @@ -72,7 +72,8 @@ fn main() { menu_bar.append_items(&[&file_m, &edit_m, &window_m]); - let custom_i_1 = MenuItem::new( + let custom_i_1 = MenuItem::with_id( + "custom-i-1", "C&ustom 1", true, Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)), @@ -80,16 +81,20 @@ fn main() { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); let icon = load_icon(std::path::Path::new(path)); - let image_item = IconMenuItem::new( + let image_item = IconMenuItem::with_id( + "image-custom-1", "Image custom 1", true, Some(icon), Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)), ); - let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None); - let check_custom_i_2 = CheckMenuItem::new("Check Custom 2", false, true, None); - let check_custom_i_3 = CheckMenuItem::new( + let check_custom_i_1 = + CheckMenuItem::with_id("check-custom-1", "Check Custom 1", true, true, None); + let check_custom_i_2 = + CheckMenuItem::with_id("check-custom-2", "Check Custom 2", false, true, None); + let check_custom_i_3 = CheckMenuItem::with_id( + "check-custom-3", "Check Custom 3", true, true, @@ -201,7 +206,10 @@ fn main() { if event.id == custom_i_1.id() { custom_i_1 .set_accelerator(Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyF))); - file_m.insert(&MenuItem::new("New Menu Item", true, None), 2); + file_m.insert( + &MenuItem::with_id("new-menu-id", "New Menu Item", true, None), + 2, + ); } println!("{event:?}"); } @@ -218,7 +226,7 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option

muda::icon::Icon { +fn load_icon(path: &std::path::Path) -> muda::Icon { let (icon_rgba, icon_width, icon_height) = { let image = image::open(path) .expect("Failed to open icon path") @@ -227,5 +235,5 @@ fn load_icon(path: &std::path::Path) -> muda::icon::Icon { let rgba = image.into_raw(); (rgba, width, height) }; - muda::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") + muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") } diff --git a/examples/winit.rs b/examples/winit.rs index 664b069..b950066 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -204,7 +204,7 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option

muda::icon::Icon { +fn load_icon(path: &std::path::Path) -> muda::Icon { let (icon_rgba, icon_width, icon_height) = { let image = image::open(path) .expect("Failed to open icon path") @@ -213,5 +213,5 @@ fn load_icon(path: &std::path::Path) -> muda::icon::Icon { let rgba = image.into_raw(); (rgba, width, height) }; - muda::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") + muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") } diff --git a/examples/wry.rs b/examples/wry.rs index 93e4e36..c795df4 100644 --- a/examples/wry.rs +++ b/examples/wry.rs @@ -266,7 +266,7 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option muda::icon::Icon { +fn load_icon(path: &std::path::Path) -> muda::Icon { let (icon_rgba, icon_width, icon_height) = { let image = image::open(path) .expect("Failed to open icon path") @@ -275,5 +275,5 @@ fn load_icon(path: &std::path::Path) -> muda::icon::Icon { let rgba = image.into_raw(); (rgba, width, height) }; - muda::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") + muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") } diff --git a/src/builders/check.rs b/src/builders/check.rs index 38d0c37..4597082 100644 --- a/src/builders/check.rs +++ b/src/builders/check.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{accelerator::Accelerator, CheckMenuItem}; +use crate::{accelerator::Accelerator, CheckMenuItem, MenuId}; /// A builder type for [`CheckMenuItem`] #[derive(Clone, Debug, Default)] @@ -11,6 +11,7 @@ pub struct CheckMenuItemBuilder { enabled: bool, checked: bool, acccelerator: Option, + id: Option, } impl CheckMenuItemBuilder { @@ -18,6 +19,12 @@ impl CheckMenuItemBuilder { Default::default() } + /// Set the id this check menu item. + pub fn id(mut self, id: MenuId) -> Self { + self.id.replace(id); + self + } + /// Set the text for this check menu item. /// /// See [`CheckMenuItem::set_text`] for more info. @@ -52,6 +59,10 @@ impl CheckMenuItemBuilder { /// Build this check menu item. pub fn build(self) -> CheckMenuItem { - CheckMenuItem::new(self.text, self.enabled, self.checked, self.acccelerator) + if let Some(id) = self.id { + CheckMenuItem::with_id(id, self.text, self.enabled, self.checked, self.acccelerator) + } else { + CheckMenuItem::new(self.text, self.enabled, self.checked, self.acccelerator) + } } } diff --git a/src/builders/icon.rs b/src/builders/icon.rs index e8a1dc6..d413da1 100644 --- a/src/builders/icon.rs +++ b/src/builders/icon.rs @@ -5,7 +5,7 @@ use crate::{ accelerator::Accelerator, icon::{Icon, NativeIcon}, - IconMenuItem, + IconMenuItem, MenuId, }; /// A builder type for [`IconMenuItem`] @@ -13,6 +13,7 @@ use crate::{ pub struct IconMenuItemBuilder { text: String, enabled: bool, + id: Option, acccelerator: Option, icon: Option, native_icon: Option, @@ -23,6 +24,12 @@ impl IconMenuItemBuilder { Default::default() } + /// Set the id this icon menu item. + pub fn id(mut self, id: MenuId) -> Self { + self.id.replace(id); + self + } + /// Set the text for this icon menu item. /// /// See [`IconMenuItem::set_text`] for more info. @@ -65,7 +72,19 @@ impl IconMenuItemBuilder { /// Build this icon menu item. pub fn build(self) -> IconMenuItem { - if self.icon.is_some() { + if let Some(id) = self.id { + if self.icon.is_some() { + IconMenuItem::with_id(id, self.text, self.enabled, self.icon, self.acccelerator) + } else { + IconMenuItem::with_id_and_native_icon( + id, + self.text, + self.enabled, + self.native_icon, + self.acccelerator, + ) + } + } else if self.icon.is_some() { IconMenuItem::new(self.text, self.enabled, self.icon, self.acccelerator) } else { IconMenuItem::with_native_icon( diff --git a/src/builders/normal.rs b/src/builders/normal.rs index 4fc459e..3fde37a 100644 --- a/src/builders/normal.rs +++ b/src/builders/normal.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{accelerator::Accelerator, MenuItem}; +use crate::{accelerator::Accelerator, MenuId, MenuItem}; /// A builder type for [`MenuItem`] #[derive(Clone, Debug, Default)] pub struct MenuItemBuilder { text: String, enabled: bool, + id: Option, acccelerator: Option, } @@ -17,6 +18,12 @@ impl MenuItemBuilder { Default::default() } + /// Set the id this menu item. + pub fn id(mut self, id: MenuId) -> Self { + self.id.replace(id); + self + } + /// Set the text for this menu item. /// /// See [`MenuItem::set_text`] for more info. @@ -45,6 +52,10 @@ impl MenuItemBuilder { /// Build this menu item. pub fn build(self) -> MenuItem { - MenuItem::new(self.text, self.enabled, self.acccelerator) + if let Some(id) = self.id { + MenuItem::with_id(id, self.text, self.enabled, self.acccelerator) + } else { + MenuItem::new(self.text, self.enabled, self.acccelerator) + } } } diff --git a/src/builders/submenu.rs b/src/builders/submenu.rs index a3f7214..15a01ea 100644 --- a/src/builders/submenu.rs +++ b/src/builders/submenu.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{IsMenuItem, Submenu}; +use crate::{IsMenuItem, MenuId, Submenu}; /// A builder type for [`Submenu`] #[derive(Clone, Default)] pub struct SubmenuBuilder<'a> { text: String, enabled: bool, + id: Option, items: Vec<&'a dyn IsMenuItem>, } @@ -26,6 +27,12 @@ impl<'a> SubmenuBuilder<'a> { Default::default() } + /// Set the id this submenu. + pub fn id(mut self, id: MenuId) -> Self { + self.id.replace(id); + self + } + /// Set the text for this submenu. /// /// See [`Submenu::set_text`] for more info. @@ -54,6 +61,10 @@ impl<'a> SubmenuBuilder<'a> { /// Build this menu item. pub fn build(self) -> crate::Result { - Submenu::with_items(self.text, self.enabled, &self.items) + if let Some(id) = self.id { + Submenu::with_id_and_items(id, self.text, self.enabled, &self.items) + } else { + Submenu::with_items(self.text, self.enabled, &self.items) + } } } diff --git a/src/items/check.rs b/src/items/check.rs index 3bc4d6a..6cd92ea 100644 --- a/src/items/check.rs +++ b/src/items/check.rs @@ -1,10 +1,10 @@ // Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: Apache-2.inner // SPDX-License-Identifier: MIT use std::{cell::RefCell, rc::Rc}; -use crate::{accelerator::Accelerator, IsMenuItem, MenuItemKind}; +use crate::{accelerator::Accelerator, IsMenuItem, MenuId, MenuItemKind}; /// A check menu item inside a [`Menu`] or [`Submenu`] /// and usually contains a text and a check mark or a similar toggle @@ -13,12 +13,19 @@ use crate::{accelerator::Accelerator, IsMenuItem, MenuItemKind}; /// [`Menu`]: crate::Menu /// [`Submenu`]: crate::Submenu #[derive(Clone)] -pub struct CheckMenuItem(pub(crate) Rc>); +pub struct CheckMenuItem { + pub(crate) id: Rc, + pub(crate) inner: Rc>, +} unsafe impl IsMenuItem for CheckMenuItem { fn kind(&self) -> MenuItemKind { MenuItemKind::Check(self.clone()) } + + fn id(&self) -> &MenuId { + self.id() + } } impl CheckMenuItem { @@ -32,55 +39,82 @@ impl CheckMenuItem { checked: bool, acccelerator: Option, ) -> Self { - Self(Rc::new(RefCell::new( - crate::platform_impl::MenuChild::new_check( + let item = crate::platform_impl::MenuChild::new_check( + text.as_ref(), + enabled, + checked, + acccelerator, + None, + ); + Self { + id: Rc::new(item.id().clone()), + inner: Rc::new(RefCell::new(item)), + } + } + + /// Create a new check menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>( + id: I, + text: S, + enabled: bool, + checked: bool, + acccelerator: Option, + ) -> Self { + let id = id.into(); + Self { + id: Rc::new(id.clone()), + inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new_check( text.as_ref(), enabled, checked, acccelerator, - ), - ))) + Some(id), + ))), + } } /// Returns a unique identifier associated with this submenu. - pub fn id(&self) -> u32 { - self.0.borrow().id() + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this check menu item. pub fn text(&self) -> String { - self.0.borrow().text() + self.inner.borrow().text() } /// Set the text for this check menu item. `text` could optionally contain /// an `&` before a character to assign this character as the mnemonic /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.borrow_mut().set_text(text.as_ref()) + self.inner.borrow_mut().set_text(text.as_ref()) } /// Get whether this check menu item is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() + self.inner.borrow().is_enabled() } /// Enable or disable this check menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) + self.inner.borrow_mut().set_enabled(enabled) } /// Set this check menu item accelerator. pub fn set_accelerator(&self, acccelerator: Option) -> crate::Result<()> { - self.0.borrow_mut().set_accelerator(acccelerator) + self.inner.borrow_mut().set_accelerator(acccelerator) } /// Get whether this check menu item is checked or not. pub fn is_checked(&self) -> bool { - self.0.borrow().is_checked() + self.inner.borrow().is_checked() } /// Check or Uncheck this check menu item. pub fn set_checked(&self, checked: bool) { - self.0.borrow_mut().set_checked(checked) + self.inner.borrow_mut().set_checked(checked) } } diff --git a/src/items/icon.rs b/src/items/icon.rs index 0d3a446..222b658 100644 --- a/src/items/icon.rs +++ b/src/items/icon.rs @@ -1,5 +1,5 @@ // Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: Apache-2.inner // SPDX-License-Identifier: MIT use std::{cell::RefCell, rc::Rc}; @@ -7,7 +7,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ accelerator::Accelerator, icon::{Icon, NativeIcon}, - IsMenuItem, MenuItemKind, + IsMenuItem, MenuId, MenuItemKind, }; /// An icon menu item inside a [`Menu`] or [`Submenu`] @@ -16,12 +16,19 @@ use crate::{ /// [`Menu`]: crate::Menu /// [`Submenu`]: crate::Submenu #[derive(Clone)] -pub struct IconMenuItem(pub(crate) Rc>); +pub struct IconMenuItem { + pub(crate) id: Rc, + pub(crate) inner: Rc>, +} unsafe impl IsMenuItem for IconMenuItem { fn kind(&self) -> MenuItemKind { MenuItemKind::Icon(self.clone()) } + + fn id(&self) -> &MenuId { + self.id() + } } impl IconMenuItem { @@ -35,9 +42,41 @@ impl IconMenuItem { icon: Option, acccelerator: Option, ) -> Self { - Self(Rc::new(RefCell::new( - crate::platform_impl::MenuChild::new_icon(text.as_ref(), enabled, icon, acccelerator), - ))) + let item = crate::platform_impl::MenuChild::new_icon( + text.as_ref(), + enabled, + icon, + acccelerator, + None, + ); + Self { + id: Rc::new(item.id().clone()), + inner: Rc::new(RefCell::new(item)), + } + } + + /// Create a new icon menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this icon menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>( + id: I, + text: S, + enabled: bool, + icon: Option, + acccelerator: Option, + ) -> Self { + let id = id.into(); + Self { + id: Rc::new(id.clone()), + inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new_icon( + text.as_ref(), + enabled, + icon, + acccelerator, + Some(id), + ))), + } } /// Create a new icon menu item but with a native icon. @@ -53,51 +92,83 @@ impl IconMenuItem { native_icon: Option, acccelerator: Option, ) -> Self { - Self(Rc::new(RefCell::new( - crate::platform_impl::MenuChild::new_native_icon( - text.as_ref(), - enabled, - native_icon, - acccelerator, - ), - ))) + let item = crate::platform_impl::MenuChild::new_native_icon( + text.as_ref(), + enabled, + native_icon, + acccelerator, + None, + ); + Self { + id: Rc::new(item.id().clone()), + inner: Rc::new(RefCell::new(item)), + } + } + + /// Create a new icon menu item but with the specified id and a native icon. + /// + /// See [`IconMenuItem::new`] for more info. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn with_id_and_native_icon, S: AsRef>( + id: I, + text: S, + enabled: bool, + native_icon: Option, + acccelerator: Option, + ) -> Self { + let id = id.into(); + Self { + id: Rc::new(id.clone()), + inner: Rc::new(RefCell::new( + crate::platform_impl::MenuChild::new_native_icon( + text.as_ref(), + enabled, + native_icon, + acccelerator, + Some(id), + ), + )), + } } /// Returns a unique identifier associated with this submenu. - pub fn id(&self) -> u32 { - self.0.borrow().id() + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this check menu item. pub fn text(&self) -> String { - self.0.borrow().text() + self.inner.borrow().text() } /// Set the text for this check menu item. `text` could optionally contain /// an `&` before a character to assign this character as the mnemonic /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.borrow_mut().set_text(text.as_ref()) + self.inner.borrow_mut().set_text(text.as_ref()) } /// Get whether this check menu item is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() + self.inner.borrow().is_enabled() } /// Enable or disable this check menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) + self.inner.borrow_mut().set_enabled(enabled) } /// Set this icon menu item accelerator. pub fn set_accelerator(&self, acccelerator: Option) -> crate::Result<()> { - self.0.borrow_mut().set_accelerator(acccelerator) + self.inner.borrow_mut().set_accelerator(acccelerator) } /// Change this menu item icon or remove it. pub fn set_icon(&self, icon: Option) { - self.0.borrow_mut().set_icon(icon) + self.inner.borrow_mut().set_icon(icon) } /// Change this menu item icon to a native image or remove it. @@ -107,6 +178,6 @@ impl IconMenuItem { /// - **Windows / Linux**: Unsupported. pub fn set_native_icon(&mut self, _icon: Option) { #[cfg(target_os = "macos")] - self.0.borrow_mut().set_native_icon(_icon) + self.inner.borrow_mut().set_native_icon(_icon) } } diff --git a/src/items/mod.rs b/src/items/mod.rs index 4a76aac..43b9a3b 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -13,3 +13,23 @@ pub use icon::*; pub use normal::*; pub use predefined::*; pub use submenu::*; + +#[cfg(test)] +mod test { + use crate::{CheckMenuItem, IconMenuItem, MenuId, MenuItem, Submenu}; + + #[test] + fn it_returns_same_id() { + let id = MenuId("1".into()); + assert_eq!(id, MenuItem::with_id(id.clone(), "", true, None).id()); + assert_eq!(id, Submenu::with_id(id.clone(), "", true).id()); + assert_eq!( + id, + CheckMenuItem::with_id(id.clone(), "", true, true, None).id() + ); + assert_eq!( + id, + IconMenuItem::with_id(id.clone(), "", true, None, None).id() + ); + } +} diff --git a/src/items/normal.rs b/src/items/normal.rs index d49f6cc..52a79e8 100644 --- a/src/items/normal.rs +++ b/src/items/normal.rs @@ -1,18 +1,25 @@ use std::{cell::RefCell, rc::Rc}; -use crate::{accelerator::Accelerator, IsMenuItem, MenuItemKind}; +use crate::{accelerator::Accelerator, IsMenuItem, MenuId, MenuItemKind}; /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text. /// /// [`Menu`]: crate::Menu /// [`Submenu`]: crate::Submenu #[derive(Clone)] -pub struct MenuItem(pub(crate) Rc>); +pub struct MenuItem { + pub(crate) id: Rc, + pub(crate) inner: Rc>, +} unsafe impl IsMenuItem for MenuItem { fn kind(&self) -> MenuItemKind { MenuItemKind::MenuItem(self.clone()) } + + fn id(&self) -> &MenuId { + self.id() + } } impl MenuItem { @@ -21,42 +28,64 @@ impl MenuItem { /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>(text: S, enabled: bool, acccelerator: Option) -> Self { - Self(Rc::new(RefCell::new(crate::platform_impl::MenuChild::new( - text.as_ref(), - enabled, - acccelerator, - )))) + let item = crate::platform_impl::MenuChild::new(text.as_ref(), enabled, acccelerator, None); + Self { + id: Rc::new(item.id().clone()), + inner: Rc::new(RefCell::new(item)), + } + } + + /// Create a new menu item with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>( + id: I, + text: S, + enabled: bool, + acccelerator: Option, + ) -> Self { + let id = id.into(); + Self { + id: Rc::new(id.clone()), + inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new( + text.as_ref(), + enabled, + acccelerator, + Some(id), + ))), + } } /// Returns a unique identifier associated with this menu item. - pub fn id(&self) -> u32 { - self.0.borrow().id() + pub fn id(&self) -> &MenuId { + &self.id } /// Set the text for this menu item. pub fn text(&self) -> String { - self.0.borrow().text() + self.inner.borrow().text() } /// Set the text for this menu item. `text` could optionally contain /// an `&` before a character to assign this character as the mnemonic /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.borrow_mut().set_text(text.as_ref()) + self.inner.borrow_mut().set_text(text.as_ref()) } /// Get whether this menu item is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() + self.inner.borrow().is_enabled() } /// Enable or disable this menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) + self.inner.borrow_mut().set_enabled(enabled) } /// Set this menu item accelerator. pub fn set_accelerator(&self, acccelerator: Option) -> crate::Result<()> { - self.0.borrow_mut().set_accelerator(acccelerator) + self.inner.borrow_mut().set_accelerator(acccelerator) } } diff --git a/src/items/predefined.rs b/src/items/predefined.rs index 0f16061..af01c0c 100644 --- a/src/items/predefined.rs +++ b/src/items/predefined.rs @@ -1,23 +1,30 @@ // Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: Apache-2.inner // SPDX-License-Identifier: MIT use std::{cell::RefCell, rc::Rc}; use crate::{ accelerator::{Accelerator, CMD_OR_CTRL}, - AboutMetadata, IsMenuItem, MenuItemKind, + AboutMetadata, IsMenuItem, MenuId, MenuItemKind, }; use keyboard_types::{Code, Modifiers}; /// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. #[derive(Clone)] -pub struct PredefinedMenuItem(pub(crate) Rc>); +pub struct PredefinedMenuItem { + pub(crate) id: Rc, + pub(crate) inner: Rc>, +} unsafe impl IsMenuItem for PredefinedMenuItem { fn kind(&self) -> MenuItemKind { MenuItemKind::Predefined(self.clone()) } + + fn id(&self) -> &MenuId { + self.id() + } } impl PredefinedMenuItem { @@ -150,26 +157,29 @@ impl PredefinedMenuItem { } fn new>(item: PredefinedMenuItemType, text: Option) -> Self { - Self(Rc::new(RefCell::new( - crate::platform_impl::MenuChild::new_predefined( - item, - text.map(|t| t.as_ref().to_string()), - ), - ))) + let item = crate::platform_impl::MenuChild::new_predefined( + item, + text.map(|t| t.as_ref().to_string()), + ); + Self { + id: Rc::new(item.id().clone()), + inner: Rc::new(RefCell::new(item)), + } } - pub(crate) fn id(&self) -> u32 { - self.0.borrow().id() + /// Returns a unique identifier associated with this predefined menu item. + pub fn id(&self) -> &MenuId { + &self.id } /// Get the text for this predefined menu item. pub fn text(&self) -> String { - self.0.borrow().text() + self.inner.borrow().text() } /// Set the text for this predefined menu item. pub fn set_text>(&self, text: S) { - self.0.borrow_mut().set_text(text.as_ref()) + self.inner.borrow_mut().set_text(text.as_ref()) } } @@ -185,21 +195,21 @@ fn test_about_metadata() { assert_eq!( AboutMetadata { - version: Some("Version: 1.0".into()), + version: Some("Version: 1.inner".into()), ..Default::default() } .full_version(), - Some("Version: 1.0".into()) + Some("Version: 1.inner".into()) ); assert_eq!( AboutMetadata { - version: Some("Version: 1.0".into()), + version: Some("Version: 1.inner".into()), short_version: Some("Universal".into()), ..Default::default() } .full_version(), - Some("Version: 1.0 (Universal)".into()) + Some("Version: 1.inner (Universal)".into()) ); } diff --git a/src/items/submenu.rs b/src/items/submenu.rs index ba60c5e..3f288fd 100644 --- a/src/items/submenu.rs +++ b/src/items/submenu.rs @@ -1,21 +1,28 @@ // Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: Apache-2.inner // SPDX-License-Identifier: MIT use std::{cell::RefCell, rc::Rc}; -use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuItemKind, Position}; +use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuId, MenuItemKind, Position}; /// A menu that can be added to a [`Menu`] or another [`Submenu`]. /// /// [`Menu`]: crate::Menu #[derive(Clone)] -pub struct Submenu(pub(crate) Rc>); +pub struct Submenu { + pub(crate) id: Rc, + pub(crate) inner: Rc>, +} unsafe impl IsMenuItem for Submenu { fn kind(&self) -> MenuItemKind { MenuItemKind::Submenu(self.clone()) } + + fn id(&self) -> &MenuId { + self.id() + } } impl Submenu { @@ -24,9 +31,28 @@ impl Submenu { /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>(text: S, enabled: bool) -> Self { - Self(Rc::new(RefCell::new( - crate::platform_impl::MenuChild::new_submenu(text.as_ref(), enabled), - ))) + let submenu = crate::platform_impl::MenuChild::new_submenu(text.as_ref(), enabled, None); + Self { + id: Rc::new(submenu.id().clone()), + inner: Rc::new(RefCell::new(submenu)), + } + } + + /// Create a new submenu with the specified id. + /// + /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic + /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. + pub fn with_id, S: AsRef>(id: I, text: S, enabled: bool) -> Self { + let id = id.into(); + + Self { + id: Rc::new(id.clone()), + inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new_submenu( + text.as_ref(), + enabled, + Some(id), + ))), + } } /// Creates a new submenu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. @@ -40,14 +66,26 @@ impl Submenu { Ok(menu) } + /// Creates a new submenu with the specified id and given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. + pub fn with_id_and_items, S: AsRef>( + id: I, + text: S, + enabled: bool, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::with_id(id, text, enabled); + menu.append_items(items)?; + Ok(menu) + } + /// Returns a unique identifier associated with this submenu. - pub fn id(&self) -> u32 { - self.0.borrow().id() + pub fn id(&self) -> &MenuId { + &self.id } /// Add a menu item to the end of this menu. pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().add_menu_item(item, AddOp::Append) + self.inner.borrow_mut().add_menu_item(item, AddOp::Append) } /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop. @@ -61,7 +99,9 @@ impl Submenu { /// Add a menu item to the beginning of this submenu. pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().add_menu_item(item, AddOp::Insert(0)) + self.inner + .borrow_mut() + .add_menu_item(item, AddOp::Insert(0)) } /// Add menu items to the beginning of this submenu. @@ -73,7 +113,7 @@ impl Submenu { /// Insert a menu item at the specified `postion` in the submenu. pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { - self.0 + self.inner .borrow_mut() .add_menu_item(item, AddOp::Insert(position)) } @@ -89,34 +129,34 @@ impl Submenu { /// Remove a menu item from this submenu. pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().remove(item) + self.inner.borrow_mut().remove(item) } /// Returns a list of menu items that has been added to this submenu. pub fn items(&self) -> Vec { - self.0.borrow().items() + self.inner.borrow().items() } /// Get the text for this submenu. pub fn text(&self) -> String { - self.0.borrow().text() + self.inner.borrow().text() } /// Set the text for this submenu. `text` could optionally contain /// an `&` before a character to assign this character as the mnemonic /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.borrow_mut().set_text(text.as_ref()) + self.inner.borrow_mut().set_text(text.as_ref()) } /// Get whether this submenu is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() + self.inner.borrow().is_enabled() } /// Enable or disable this submenu. pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) + self.inner.borrow_mut().set_enabled(enabled) } // TODO: in a minor release, rename the following two functions to be `set_as_*` @@ -127,7 +167,7 @@ impl Submenu { /// certain other items to the menu. #[cfg(target_os = "macos")] pub fn set_as_windows_menu_for_nsapp(&self) { - self.0.borrow_mut().set_as_windows_menu_for_nsapp() + self.inner.borrow_mut().set_as_windows_menu_for_nsapp() } /// Set this submenu as the Help menu for the application on macOS. @@ -138,31 +178,31 @@ impl Submenu { /// which has a title matching the localized word "Help". #[cfg(target_os = "macos")] pub fn set_as_help_menu_for_nsapp(&self) { - self.0.borrow_mut().set_as_help_menu_for_nsapp() + self.inner.borrow_mut().set_as_help_menu_for_nsapp() } } impl ContextMenu for Submenu { #[cfg(target_os = "windows")] fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU { - self.0.borrow().hpopupmenu() + self.inner.borrow().hpopupmenu() } #[cfg(target_os = "windows")] fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) { - self.0 + self.inner .borrow_mut() .show_context_menu_for_hwnd(hwnd, position) } #[cfg(target_os = "windows")] fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { - self.0.borrow_mut().attach_menu_subclass_for_hwnd(hwnd) + self.inner.borrow_mut().attach_menu_subclass_for_hwnd(hwnd) } #[cfg(target_os = "windows")] fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) { - self.0.borrow_mut().detach_menu_subclass_from_hwnd(hwnd) + self.inner.borrow_mut().detach_menu_subclass_from_hwnd(hwnd) } #[cfg(target_os = "linux")] @@ -171,25 +211,25 @@ impl ContextMenu for Submenu { w: >k::ApplicationWindow, position: Option, ) { - self.0 + self.inner .borrow_mut() .show_context_menu_for_gtk_window(w, position) } #[cfg(target_os = "linux")] fn gtk_context_menu(&self) -> gtk::Menu { - self.0.borrow_mut().gtk_context_menu() + self.inner.borrow_mut().gtk_context_menu() } #[cfg(target_os = "macos")] fn show_context_menu_for_nsview(&self, view: cocoa::base::id, position: Option) { - self.0 + self.inner .borrow_mut() .show_context_menu_for_nsview(view, position) } #[cfg(target_os = "macos")] fn ns_menu(&self) -> *mut std::ffi::c_void { - self.0.borrow().ns_menu() + self.inner.borrow().ns_menu() } } diff --git a/src/lib.rs b/src/lib.rs index 1e76d1f..c6f25f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,14 +126,17 @@ //! } //! ``` +use std::{convert::Infallible, str::FromStr}; + use crossbeam_channel::{unbounded, Receiver, Sender}; use once_cell::sync::{Lazy, OnceCell}; mod about_metadata; pub mod accelerator; -pub mod builders; +mod builders; mod dpi; mod error; +mod icon; mod items; mod menu; mod platform_impl; @@ -144,14 +147,63 @@ mod util; extern crate objc; pub use about_metadata::AboutMetadata; +pub use builders::*; pub use dpi::*; pub use error::*; +pub use icon::{BadIcon, Icon, NativeIcon}; pub use items::*; pub use menu::Menu; -pub mod icon; + +/// An unique id that is associated with a menu item. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] +pub struct MenuId(String); + +impl AsRef for MenuId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl From for MenuId { + fn from(value: String) -> Self { + Self(value) + } +} + +impl From<&str> for MenuId { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} + +impl PartialEq<&str> for MenuId { + fn eq(&self, other: &&str) -> bool { + other == &self.0 + } +} + +impl PartialEq for MenuId { + fn eq(&self, other: &String) -> bool { + other == &self.0 + } +} + +impl PartialEq<&MenuId> for MenuId { + fn eq(&self, other: &&MenuId) -> bool { + other.0 == self.0 + } +} + +impl FromStr for MenuId { + type Err = Infallible; + + fn from_str(s: &str) -> std::result::Result { + Ok(Self(s.to_string())) + } +} /// An enumeration of all available menu types, useful to match against -/// the items return from [`Menu::items`] or [`Submenu::items`] +/// the items returned from [`Menu::items`] or [`Submenu::items`] #[derive(Clone)] pub enum MenuItemKind { MenuItem(MenuItem), @@ -162,17 +214,6 @@ pub enum MenuItemKind { } impl MenuItemKind { - /// Returns the id associated with this menu entry - fn id(&self) -> u32 { - match self { - MenuItemKind::MenuItem(i) => i.id(), - MenuItemKind::Submenu(i) => i.id(), - MenuItemKind::Predefined(i) => i.id(), - MenuItemKind::Check(i) => i.id(), - MenuItemKind::Icon(i) => i.id(), - } - } - /// Casts this item to a [`MenuItem`], and returns `None` if it wasn't. pub fn as_menuitem(&self) -> Option<&MenuItem> { match self { @@ -262,9 +303,7 @@ impl MenuItemKind { pub unsafe trait IsMenuItem { fn kind(&self) -> MenuItemKind; - fn id(&self) -> u32 { - self.kind().id() - } + fn id(&self) -> &MenuId; } #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] @@ -332,10 +371,10 @@ pub trait ContextMenu { } /// Describes a menu event emitted when a menu item is activated -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct MenuEvent { /// Id of the menu item which triggered this event - pub id: u32, + pub id: MenuId, } /// A reciever that could be used to listen to menu events. @@ -347,8 +386,8 @@ static MENU_EVENT_HANDLER: OnceCell> = OnceCell::new(); impl MenuEvent { /// Returns the id of the menu item which triggered this event - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } /// Gets a reference to the event channel's [`MenuEventReceiver`] diff --git a/src/menu.rs b/src/menu.rs index 65d99c6..0b20dea 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,15 +1,18 @@ // Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: Apache-2.inner // SPDX-License-Identifier: MIT use std::{cell::RefCell, rc::Rc}; -use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuItemKind, Position}; +use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuId, MenuItemKind, Position}; /// A root menu that can be added to a Window on Windows and Linux /// and used as the app global menu on macOS. #[derive(Clone)] -pub struct Menu(Rc>); +pub struct Menu { + id: MenuId, + inner: Rc>, +} impl Default for Menu { fn default() -> Self { @@ -20,7 +23,20 @@ impl Default for Menu { impl Menu { /// Creates a new menu. pub fn new() -> Self { - Self(Rc::new(RefCell::new(crate::platform_impl::Menu::new()))) + let menu = crate::platform_impl::Menu::new(None); + Self { + id: menu.id().clone(), + inner: Rc::new(RefCell::new(menu)), + } + } + + /// Creates a new menu with the specified id. + pub fn with_id>(id: I) -> Self { + let id = id.into(); + Self { + id: id.clone(), + inner: Rc::new(RefCell::new(crate::platform_impl::Menu::new(Some(id)))), + } } /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. @@ -30,9 +46,19 @@ impl Menu { Ok(menu) } + /// Creates a new menu with the specified id and given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. + pub fn with_id_and_items>( + id: I, + items: &[&dyn IsMenuItem], + ) -> crate::Result { + let menu = Self::with_id(id); + menu.append_items(items)?; + Ok(menu) + } + /// Returns a unique identifier associated with this menu. - pub fn id(&self) -> u32 { - self.0.borrow().id() + pub fn id(&self) -> &MenuId { + &self.id } /// Add a menu item to the end of this menu. @@ -43,7 +69,7 @@ impl Menu { /// /// [`Submenu`]: crate::Submenu pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().add_menu_item(item, AddOp::Append) + self.inner.borrow_mut().add_menu_item(item, AddOp::Append) } /// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally. @@ -69,7 +95,9 @@ impl Menu { /// /// [`Submenu`]: crate::Submenu pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().add_menu_item(item, AddOp::Insert(0)) + self.inner + .borrow_mut() + .add_menu_item(item, AddOp::Insert(0)) } /// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally. @@ -91,7 +119,7 @@ impl Menu { /// /// [`Submenu`]: crate::Submenu pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { - self.0 + self.inner .borrow_mut() .add_menu_item(item, AddOp::Insert(position)) } @@ -113,12 +141,12 @@ impl Menu { /// Remove a menu item from this menu. pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { - self.0.borrow_mut().remove(item) + self.inner.borrow_mut().remove(item) } /// Returns a list of menu items that has been added to this menu. pub fn items(&self) -> Vec { - self.0.borrow().items() + self.inner.borrow().items() } /// Adds this menu to a [`gtk::ApplicationWindow`] @@ -149,7 +177,9 @@ impl Menu { C: gtk::prelude::IsA, C: gtk::prelude::IsA, { - self.0.borrow_mut().init_for_gtk_window(window, container) + self.inner + .borrow_mut() + .init_for_gtk_window(window, container) } /// Adds this menu to a win32 window. @@ -178,7 +208,7 @@ impl Menu { /// ``` #[cfg(target_os = "windows")] pub fn init_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.borrow_mut().init_for_hwnd(hwnd) + self.inner.borrow_mut().init_for_hwnd(hwnd) } /// Returns The [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) associated with this menu @@ -186,7 +216,7 @@ impl Menu { /// in the event loop to enable accelerators #[cfg(target_os = "windows")] pub fn haccel(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HACCEL { - self.0.borrow_mut().haccel() + self.inner.borrow_mut().haccel() } /// Removes this menu from a [`gtk::ApplicationWindow`] @@ -196,13 +226,13 @@ impl Menu { W: gtk::prelude::IsA, W: gtk::prelude::IsA, { - self.0.borrow_mut().remove_for_gtk_window(window) + self.inner.borrow_mut().remove_for_gtk_window(window) } /// Removes this menu from a win32 window #[cfg(target_os = "windows")] pub fn remove_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.borrow_mut().remove_for_hwnd(hwnd) + self.inner.borrow_mut().remove_for_hwnd(hwnd) } /// Hides this menu from a [`gtk::ApplicationWindow`] @@ -211,13 +241,13 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.borrow_mut().hide_for_gtk_window(window) + self.inner.borrow_mut().hide_for_gtk_window(window) } /// Hides this menu from a win32 window #[cfg(target_os = "windows")] pub fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.borrow().hide_for_hwnd(hwnd) + self.inner.borrow().hide_for_hwnd(hwnd) } /// Shows this menu on a [`gtk::ApplicationWindow`] @@ -226,13 +256,13 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.borrow_mut().show_for_gtk_window(window) + self.inner.borrow_mut().show_for_gtk_window(window) } /// Shows this menu on a win32 window #[cfg(target_os = "windows")] pub fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.borrow().show_for_hwnd(hwnd) + self.inner.borrow().show_for_hwnd(hwnd) } /// Returns whether this menu visible on a [`gtk::ApplicationWindow`] @@ -241,7 +271,7 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.borrow().is_visible_on_gtk_window(window) + self.inner.borrow().is_visible_on_gtk_window(window) } #[cfg(target_os = "linux")] @@ -251,47 +281,49 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.borrow().gtk_menubar_for_gtk_window(window) + self.inner.borrow().gtk_menubar_for_gtk_window(window) } /// Returns whether this menu visible on a on a win32 window #[cfg(target_os = "windows")] pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool { - self.0.borrow().is_visible_on_hwnd(hwnd) + self.inner.borrow().is_visible_on_hwnd(hwnd) } /// Adds this menu to an NSApp. #[cfg(target_os = "macos")] pub fn init_for_nsapp(&self) { - self.0.borrow_mut().init_for_nsapp() + self.inner.borrow_mut().init_for_nsapp() } /// Removes this menu from an NSApp. #[cfg(target_os = "macos")] pub fn remove_for_nsapp(&self) { - self.0.borrow_mut().remove_for_nsapp() + self.inner.borrow_mut().remove_for_nsapp() } } impl ContextMenu for Menu { #[cfg(target_os = "windows")] fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU { - self.0.borrow().hpopupmenu() + self.inner.borrow().hpopupmenu() } #[cfg(target_os = "windows")] fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option) { - self.0.borrow().show_context_menu_for_hwnd(hwnd, position) + self.inner + .borrow() + .show_context_menu_for_hwnd(hwnd, position) } #[cfg(target_os = "windows")] fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { - self.0.borrow().attach_menu_subclass_for_hwnd(hwnd) + self.inner.borrow().attach_menu_subclass_for_hwnd(hwnd) } #[cfg(target_os = "windows")] fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) { - self.0.borrow().detach_menu_subclass_from_hwnd(hwnd) + self.inner.borrow().detach_menu_subclass_from_hwnd(hwnd) } #[cfg(target_os = "linux")] @@ -300,25 +332,25 @@ impl ContextMenu for Menu { window: >k::ApplicationWindow, position: Option, ) { - self.0 + self.inner .borrow_mut() .show_context_menu_for_gtk_window(window, position) } #[cfg(target_os = "linux")] fn gtk_context_menu(&self) -> gtk::Menu { - self.0.borrow_mut().gtk_context_menu() + self.inner.borrow_mut().gtk_context_menu() } #[cfg(target_os = "macos")] fn show_context_menu_for_nsview(&self, view: cocoa::base::id, position: Option) { - self.0 + self.inner .borrow_mut() .show_context_menu_for_nsview(view, position) } #[cfg(target_os = "macos")] fn ns_menu(&self) -> *mut std::ffi::c_void { - self.0.borrow().ns_menu() + self.inner.borrow().ns_menu() } } diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index 00b0ce6..c339290 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -12,7 +12,7 @@ use crate::{ icon::{Icon, NativeIcon}, items::*, util::{AddOp, Counter}, - IsMenuItem, MenuEvent, MenuItemKind, MenuItemType, Position, + IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, Position, }; use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic}; use gtk::{prelude::*, Orientation}; @@ -53,7 +53,7 @@ macro_rules! return_if_predefined_item_not_supported { } pub struct Menu { - id: u32, + id: MenuId, children: Vec>>, gtk_menubars: HashMap, accel_group: Option, @@ -61,9 +61,9 @@ pub struct Menu { } impl Menu { - pub fn new() -> Self { + pub fn new(id: Option) -> Self { Self { - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), children: Vec::new(), gtk_menubars: HashMap::new(), accel_group: None, @@ -71,8 +71,8 @@ impl Menu { } } - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { @@ -157,11 +157,12 @@ impl Menu { }; if let MenuItemKind::Submenu(i) = item.kind() { - let gtk_menus = i.0.borrow().gtk_menus.clone(); + let gtk_menus = i.inner.borrow().gtk_menus.clone(); for (menu_id, _) in gtk_menus { for item in i.items() { - i.0.borrow_mut() + i.inner + .borrow_mut() .remove_inner(item.as_ref(), false, Some(menu_id))?; } } @@ -355,7 +356,7 @@ pub struct MenuChild { item_type: MenuItemType, text: String, enabled: bool, - id: u32, + id: MenuId, gtk_menu_items: Rc>>>, @@ -382,23 +383,28 @@ pub struct MenuChild { /// Constructors impl MenuChild { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + pub fn new( + text: &str, + enabled: bool, + accelerator: Option, + id: Option, + ) -> Self { Self { text: text.to_string(), enabled, accelerator, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), item_type: MenuItemType::MenuItem, gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), ..Default::default() } } - pub fn new_submenu(text: &str, enabled: bool) -> Self { + pub fn new_submenu(text: &str, enabled: bool, id: Option) -> Self { Self { text: text.to_string(), enabled, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), children: Some(Vec::new()), item_type: MenuItemType::Submenu, gtk_menu: (COUNTER.next(), None), @@ -413,7 +419,7 @@ impl MenuChild { text: text.unwrap_or_else(|| item_type.text().to_string()), enabled: true, accelerator: item_type.accelerator(), - id: COUNTER.next(), + id: MenuId(COUNTER.next().to_string()), item_type: MenuItemType::Predefined, predefined_item_type: item_type, gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), @@ -426,13 +432,14 @@ impl MenuChild { enabled: bool, checked: bool, accelerator: Option, + id: Option, ) -> Self { Self { text: text.to_string(), enabled, checked: Rc::new(AtomicBool::new(checked)), accelerator, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), item_type: MenuItemType::Check, gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), is_syncing_checked_state: Rc::new(AtomicBool::new(false)), @@ -445,13 +452,14 @@ impl MenuChild { enabled: bool, icon: Option, accelerator: Option, + id: Option, ) -> Self { Self { text: text.to_string(), enabled, icon, accelerator, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), item_type: MenuItemType::Icon, gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), is_syncing_checked_state: Rc::new(AtomicBool::new(false)), @@ -464,12 +472,13 @@ impl MenuChild { enabled: bool, _native_icon: Option, accelerator: Option, + id: Option, ) -> Self { Self { text: text.to_string(), enabled, accelerator, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), item_type: MenuItemType::Icon, gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), is_syncing_checked_state: Rc::new(AtomicBool::new(false)), @@ -484,8 +493,8 @@ impl MenuChild { self.item_type } - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } pub fn text(&self) -> String { @@ -715,11 +724,12 @@ impl MenuChild { }; if let MenuItemKind::Submenu(i) = item.kind() { - let gtk_menus = i.0.borrow().gtk_menus.clone(); + let gtk_menus = i.inner.borrow().gtk_menus.clone(); for (menu_id, _) in gtk_menus { for item in i.items() { - i.0.borrow_mut() + i.inner + .borrow_mut() .remove_inner(item.as_ref(), false, Some(menu_id))?; } } @@ -883,9 +893,9 @@ impl MenuChild { register_accel!(self, item, accel_group); - let id = self.id; + let id = self.id.clone(); item.connect_activate(move |_| { - MenuEvent::send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id: id.clone() }); }); if add_to_cache { @@ -1035,7 +1045,7 @@ impl MenuChild { register_accel!(self, item, accel_group); - let id = self.id; + let id = self.id.clone(); let is_syncing_checked_state = self.is_syncing_checked_state.clone(); let checked = self.checked.clone(); let store = self.gtk_menu_items.clone(); @@ -1058,7 +1068,7 @@ impl MenuChild { is_syncing_checked_state.store(false, Ordering::Release); - MenuEvent::send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id: id.clone() }); } }); @@ -1116,9 +1126,9 @@ impl MenuChild { register_accel!(self, item, accel_group); - let id = self.id; + let id = self.id.clone(); item.connect_activate(move |_| { - MenuEvent::send(crate::MenuEvent { id }); + MenuEvent::send(crate::MenuEvent { id: id.clone() }); }); if add_to_cache { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index c0df7c6..160f333 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -28,7 +28,7 @@ use crate::{ icon::{Icon, NativeIcon}, items::*, util::{AddOp, Counter}, - IsMenuItem, LogicalPosition, MenuEvent, MenuItemKind, MenuItemType, Position, + IsMenuItem, LogicalPosition, MenuEvent, MenuId, MenuItemKind, MenuItemType, Position, }; static COUNTER: Counter = Counter::new(); @@ -49,7 +49,7 @@ const NSAboutPanelOptionCopyright: &str = "Copyright"; #[derive(Debug)] pub struct Menu { - id: u32, + id: MenuId, ns_menu: id, children: Rc>>>>, } @@ -63,9 +63,9 @@ impl Drop for Menu { } impl Menu { - pub fn new() -> Self { + pub fn new(id: Option) -> Self { Self { - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), ns_menu: unsafe { let ns_menu = NSMenu::new(nil); ns_menu.setAutoenablesItems(NO); @@ -76,12 +76,12 @@ impl Menu { } } - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { - let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.id)?; + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; let child = item.child(); unsafe { @@ -103,11 +103,11 @@ impl Menu { pub fn remove(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { // get a list of instances of the specified `NSMenuItem` in this menu let child = match item.kind() { - MenuItemKind::Submenu(i) => i.0.clone(), - MenuItemKind::MenuItem(i) => i.0.clone(), - MenuItemKind::Predefined(i) => i.0.clone(), - MenuItemKind::Check(i) => i.0.clone(), - MenuItemKind::Icon(i) => i.0.clone(), + MenuItemKind::Submenu(i) => i.inner.clone(), + MenuItemKind::MenuItem(i) => i.inner.clone(), + MenuItemKind::Predefined(i) => i.inner.clone(), + MenuItemKind::Check(i) => i.inner.clone(), + MenuItemKind::Icon(i) => i.inner.clone(), }; let mut child_ = child.borrow_mut(); if let Some(ns_menu_items) = child_.ns_menu_items.remove(&self.id) { @@ -160,11 +160,11 @@ impl Menu { pub struct MenuChild { // shared fields between submenus and menu items item_type: MenuItemType, - id: u32, + id: MenuId, text: String, enabled: bool, - ns_menu_items: HashMap>, + ns_menu_items: HashMap>, // menu item fields accelerator: Option, @@ -181,12 +181,12 @@ pub struct MenuChild { // submenu fields pub children: Option>>>, - ns_menus: HashMap>, + ns_menus: HashMap>, ns_menu: NsMenuRef, } #[derive(Debug)] -struct NsMenuRef(u32, id); +struct NsMenuRef(MenuId, id); impl Drop for NsMenuRef { fn drop(&mut self) { @@ -211,31 +211,37 @@ impl Default for MenuChild { native_icon: Default::default(), children: Default::default(), ns_menus: Default::default(), - ns_menu: NsMenuRef(0, 0 as _), + ns_menu: NsMenuRef(MenuId(COUNTER.next().to_string()), 0 as _), } } } /// Constructors impl MenuChild { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + pub fn new( + text: &str, + enabled: bool, + accelerator: Option, + id: Option, + ) -> Self { Self { item_type: MenuItemType::MenuItem, text: strip_mnemonic(text), enabled, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), accelerator, ..Default::default() } } - pub fn new_submenu(text: &str, enabled: bool) -> Self { + pub fn new_submenu(text: &str, enabled: bool, id: Option) -> Self { Self { item_type: MenuItemType::Submenu, text: strip_mnemonic(text), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), enabled, children: Some(Vec::new()), - ns_menu: NsMenuRef(COUNTER.next(), unsafe { + ns_menu: NsMenuRef(MenuId(COUNTER.next().to_string()), unsafe { let menu = NSMenu::new(nil); let _: () = msg_send![menu, retain]; menu @@ -271,7 +277,7 @@ impl MenuChild { item_type: MenuItemType::Predefined, text, enabled: true, - id: COUNTER.next(), + id: MenuId(COUNTER.next().to_string()), accelerator, predefined_item_type: item_type, // ns_menu_item, @@ -284,12 +290,13 @@ impl MenuChild { enabled: bool, checked: bool, accelerator: Option, + id: Option, ) -> Self { Self { item_type: MenuItemType::Check, text: text.to_string(), enabled, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), accelerator, checked, ..Default::default() @@ -301,12 +308,13 @@ impl MenuChild { enabled: bool, icon: Option, accelerator: Option, + id: Option, ) -> Self { Self { item_type: MenuItemType::Icon, text: text.to_string(), enabled, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), icon, accelerator, ..Default::default() @@ -318,12 +326,13 @@ impl MenuChild { enabled: bool, native_icon: Option, accelerator: Option, + id: Option, ) -> Self { Self { item_type: MenuItemType::Icon, text: text.to_string(), enabled, - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), native_icon, accelerator, ..Default::default() @@ -337,8 +346,8 @@ impl MenuChild { self.item_type } - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } pub fn text(&self) -> String { @@ -461,12 +470,12 @@ impl MenuChild { AddOp::Append => { for menus in self.ns_menus.values() { for ns_menu in menus { - let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.id)?; + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; ns_menu.addItem_(ns_menu_item); } } - let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.ns_menu.0)?; + 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(child); @@ -474,12 +483,12 @@ impl MenuChild { AddOp::Insert(position) => { for menus in self.ns_menus.values() { for &ns_menu in menus { - let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.id)?; + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; 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 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().unwrap().insert(position, child); @@ -556,7 +565,7 @@ impl MenuChild { /// NSMenuItem item creation methods impl MenuChild { - pub fn create_ns_item_for_submenu(&mut self, menu_id: u32) -> crate::Result { + pub fn create_ns_item_for_submenu(&mut self, menu_id: &MenuId) -> crate::Result { let ns_menu_item: *mut Object; let ns_submenu: *mut Object; @@ -580,19 +589,19 @@ impl MenuChild { } self.ns_menus - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_submenu); self.ns_menu_items - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_menu_item); Ok(ns_menu_item) } - pub fn create_ns_item_for_menu_item(&mut self, menu_id: u32) -> crate::Result { + pub fn create_ns_item_for_menu_item(&mut self, menu_id: &MenuId) -> crate::Result { let ns_menu_item = create_ns_menu_item( &self.text, Some(sel!(fireMenuItemAction:)), @@ -601,7 +610,6 @@ impl MenuChild { unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:self.id()]; // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item let ptr = Box::into_raw(Box::new(&*self)); @@ -613,14 +621,17 @@ impl MenuChild { } self.ns_menu_items - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_menu_item); Ok(ns_menu_item) } - pub fn create_ns_item_for_predefined_menu_item(&mut self, menu_id: u32) -> crate::Result { + pub fn create_ns_item_for_predefined_menu_item( + &mut self, + menu_id: &MenuId, + ) -> crate::Result { let item_type = &self.predefined_item_type; let ns_menu_item = match item_type { PredefinedMenuItemType::Separator => unsafe { @@ -632,7 +643,6 @@ impl MenuChild { if let PredefinedMenuItemType::About(_) = self.predefined_item_type { unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:self.id()]; // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item let ptr = Box::into_raw(Box::new(&*self)); @@ -653,14 +663,14 @@ impl MenuChild { } self.ns_menu_items - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_menu_item); Ok(ns_menu_item) } - pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: u32) -> crate::Result { + pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: &MenuId) -> crate::Result { let ns_menu_item = create_ns_menu_item( &self.text, Some(sel!(fireMenuItemAction:)), @@ -669,7 +679,6 @@ impl MenuChild { unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:self.id()]; // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item let ptr = Box::into_raw(Box::new(&*self)); @@ -684,14 +693,14 @@ impl MenuChild { } self.ns_menu_items - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_menu_item); Ok(ns_menu_item) } - pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: u32) -> crate::Result { + pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: &MenuId) -> crate::Result { let ns_menu_item = create_ns_menu_item( &self.text, Some(sel!(fireMenuItemAction:)), @@ -700,7 +709,6 @@ impl MenuChild { unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:self.id()]; // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item let ptr = Box::into_raw(Box::new(&*self)); @@ -718,14 +726,14 @@ impl MenuChild { } self.ns_menu_items - .entry(menu_id) + .entry(menu_id.clone()) .or_insert_with(Vec::new) .push(ns_menu_item); Ok(ns_menu_item) } - fn make_ns_item_for_menu(&mut self, menu_id: u32) -> crate::Result<*mut Object> { + fn make_ns_item_for_menu(&mut self, menu_id: &MenuId) -> crate::Result<*mut Object> { match self.item_type { MenuItemType::Submenu => self.create_ns_item_for_submenu(menu_id), MenuItemType::MenuItem => self.create_ns_item_for_menu_item(menu_id), @@ -763,16 +771,22 @@ impl PredefinedMenuItemType { } impl dyn IsMenuItem + '_ { - fn make_ns_item_for_menu(&self, menu_id: u32) -> crate::Result<*mut Object> { + fn make_ns_item_for_menu(&self, menu_id: &MenuId) -> crate::Result<*mut Object> { match self.kind() { - MenuItemKind::Submenu(i) => i.0.borrow_mut().create_ns_item_for_submenu(menu_id), - MenuItemKind::MenuItem(i) => i.0.borrow_mut().create_ns_item_for_menu_item(menu_id), - MenuItemKind::Predefined(i) => { - i.0.borrow_mut() - .create_ns_item_for_predefined_menu_item(menu_id) - } - MenuItemKind::Check(i) => i.0.borrow_mut().create_ns_item_for_check_menu_item(menu_id), - MenuItemKind::Icon(i) => i.0.borrow_mut().create_ns_item_for_icon_menu_item(menu_id), + MenuItemKind::Submenu(i) => i.inner.borrow_mut().create_ns_item_for_submenu(menu_id), + MenuItemKind::MenuItem(i) => i.inner.borrow_mut().create_ns_item_for_menu_item(menu_id), + MenuItemKind::Predefined(i) => i + .inner + .borrow_mut() + .create_ns_item_for_predefined_menu_item(menu_id), + MenuItemKind::Check(i) => i + .inner + .borrow_mut() + .create_ns_item_for_check_menu_item(menu_id), + MenuItemKind::Icon(i) => i + .inner + .borrow_mut() + .create_ns_item_for_icon_menu_item(menu_id), } } } @@ -816,8 +830,6 @@ extern "C" fn dealloc_custom_menuitem(this: &Object, _: Sel) { extern "C" fn fire_menu_item_click(this: &Object, _: Sel, _item: id) { unsafe { - let id: u32 = msg_send![this, tag]; - // Create a reference to the `MenuChild` from the raw pointer // stored as an instance variable on the native menu item let ptr: usize = *this.get_ivar(BLOCK_PTR); @@ -884,6 +896,7 @@ extern "C" fn fire_menu_item_click(this: &Object, _: Sel, _item: id) { (*item).set_checked(!(*item).is_checked()); } + let id = (*item).id().clone(); MenuEvent::send(crate::MenuEvent { id }); } } diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index 0c57a49..c6abb72 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -24,13 +24,12 @@ pub(crate) use self::platform::*; impl dyn IsMenuItem + '_ { fn child(&self) -> Rc> { match self.kind() { - MenuItemKind::MenuItem(i) => i.0, - MenuItemKind::Submenu(i) => i.0, - MenuItemKind::Predefined(i) => i.0, - MenuItemKind::Check(i) => i.0, - MenuItemKind::Icon(i) => i.0, + MenuItemKind::MenuItem(i) => i.inner, + MenuItemKind::Submenu(i) => i.inner, + MenuItemKind::Predefined(i) => i.inner, + MenuItemKind::Check(i) => i.inner, + MenuItemKind::Icon(i) => i.inner, } - .clone() } } @@ -38,11 +37,41 @@ impl dyn IsMenuItem + '_ { impl MenuChild { fn kind(&self, c: Rc>) -> MenuItemKind { match self.item_type() { - MenuItemType::Submenu => MenuItemKind::Submenu(Submenu(c)), - MenuItemType::MenuItem => MenuItemKind::MenuItem(MenuItem(c)), - MenuItemType::Predefined => MenuItemKind::Predefined(PredefinedMenuItem(c)), - MenuItemType::Check => MenuItemKind::Check(CheckMenuItem(c)), - MenuItemType::Icon => MenuItemKind::Icon(IconMenuItem(c)), + MenuItemType::Submenu => { + let id = c.borrow().id().clone(); + MenuItemKind::Submenu(Submenu { + id: Rc::new(id), + inner: c, + }) + } + MenuItemType::MenuItem => { + let id = c.borrow().id().clone(); + MenuItemKind::MenuItem(MenuItem { + id: Rc::new(id), + inner: c, + }) + } + MenuItemType::Predefined => { + let id = c.borrow().id().clone(); + MenuItemKind::Predefined(PredefinedMenuItem { + id: Rc::new(id), + inner: c, + }) + } + MenuItemType::Check => { + let id = c.borrow().id().clone(); + MenuItemKind::Check(CheckMenuItem { + id: Rc::new(id), + inner: c, + }) + } + MenuItemType::Icon => { + let id = c.borrow().id().clone(); + MenuItemKind::Icon(IconMenuItem { + id: Rc::new(id), + inner: c, + }) + } } } } @@ -61,21 +90,21 @@ impl MenuItemKind { pub(crate) fn child(&self) -> Ref { match self { - MenuItemKind::MenuItem(i) => i.0.borrow(), - MenuItemKind::Submenu(i) => i.0.borrow(), - MenuItemKind::Predefined(i) => i.0.borrow(), - MenuItemKind::Check(i) => i.0.borrow(), - MenuItemKind::Icon(i) => i.0.borrow(), + MenuItemKind::MenuItem(i) => i.inner.borrow(), + MenuItemKind::Submenu(i) => i.inner.borrow(), + MenuItemKind::Predefined(i) => i.inner.borrow(), + MenuItemKind::Check(i) => i.inner.borrow(), + MenuItemKind::Icon(i) => i.inner.borrow(), } } pub(crate) fn child_mut(&self) -> RefMut { match self { - MenuItemKind::MenuItem(i) => i.0.borrow_mut(), - MenuItemKind::Submenu(i) => i.0.borrow_mut(), - MenuItemKind::Predefined(i) => i.0.borrow_mut(), - MenuItemKind::Check(i) => i.0.borrow_mut(), - MenuItemKind::Icon(i) => i.0.borrow_mut(), + MenuItemKind::MenuItem(i) => i.inner.borrow_mut(), + MenuItemKind::Submenu(i) => i.inner.borrow_mut(), + MenuItemKind::Predefined(i) => i.inner.borrow_mut(), + MenuItemKind::Check(i) => i.inner.borrow_mut(), + MenuItemKind::Icon(i) => i.inner.borrow_mut(), } } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 211ed73..108150b 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,7 +13,7 @@ use crate::{ icon::{Icon, NativeIcon}, items::PredefinedMenuItemType, util::{AddOp, Counter}, - AboutMetadata, IsMenuItem, MenuEvent, MenuItemKind, MenuItemType, Position, + AboutMetadata, IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, Position, }; use std::{ cell::{RefCell, RefMut}, @@ -51,15 +51,15 @@ macro_rules! inner_menu_child_and_flags { let child = match $item.kind() { MenuItemKind::Submenu(i) => { flags |= MF_POPUP; - i.0.clone() + i.inner.clone() } MenuItemKind::MenuItem(i) => { flags |= MF_STRING; - i.0.clone() + i.inner.clone() } MenuItemKind::Predefined(i) => { - let child = i.0.clone(); + let child = i.inner.clone(); let child_ = child.borrow(); match child_.predefined_item_type { PredefinedMenuItemType::None => return Ok(()), @@ -74,7 +74,7 @@ macro_rules! inner_menu_child_and_flags { child } MenuItemKind::Check(i) => { - let child = i.0.clone(); + let child = i.inner.clone(); flags |= MF_STRING; if child.borrow().checked { flags |= MF_CHECKED; @@ -83,7 +83,7 @@ macro_rules! inner_menu_child_and_flags { } MenuItemKind::Icon(i) => { flags |= MF_STRING; - i.0.clone() + i.inner.clone() } }; @@ -93,7 +93,7 @@ macro_rules! inner_menu_child_and_flags { #[derive(Debug)] pub(crate) struct Menu { - id: u32, + id: MenuId, hmenu: HMENU, hpopupmenu: HMENU, hwnds: Vec, @@ -102,9 +102,9 @@ pub(crate) struct Menu { } impl Menu { - pub fn new() -> Self { + pub fn new(id: Option) -> Self { Self { - id: COUNTER.next(), + id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), hmenu: unsafe { CreateMenu() }, hpopupmenu: unsafe { CreatePopupMenu() }, haccel_store: Rc::new(RefCell::new((0, HashMap::new()))), @@ -113,8 +113,8 @@ impl Menu { } } - pub fn id(&self) -> u32 { - self.id + pub fn id(&self) -> &MenuId { + &self.id } pub fn add_menu_item(&mut self, item: &dyn IsMenuItem, op: AddOp) -> crate::Result<()> { @@ -145,12 +145,12 @@ impl Menu { AccelAction::add( &mut self.haccel_store.borrow_mut(), - child_.id(), + child_.internal_id(), accelerator, )?; } - let id = child_.id() as usize; + let id = child_.internal_id() as usize; let text = encode_wide(text); unsafe { @@ -191,8 +191,8 @@ impl Menu { let info = create_icon_item_info(hbitmap); unsafe { - SetMenuItemInfoW(self.hmenu, child_.id, false.into(), &info); - SetMenuItemInfoW(self.hpopupmenu, child_.id, false.into(), &info); + SetMenuItemInfoW(self.hmenu, child_.internal_id, false.into(), &info); + SetMenuItemInfoW(self.hpopupmenu, child_.internal_id, false.into(), &info); }; } } @@ -214,9 +214,10 @@ impl Menu { } pub fn remove(&mut self, item: &dyn IsMenuItem) -> crate::Result<()> { + let id = item.child().borrow().internal_id(); unsafe { - RemoveMenu(self.hmenu, item.id(), MF_BYCOMMAND); - RemoveMenu(self.hpopupmenu, item.id(), MF_BYCOMMAND); + RemoveMenu(self.hmenu, id, MF_BYCOMMAND); + RemoveMenu(self.hpopupmenu, id, MF_BYCOMMAND); for hwnd in &self.hwnds { DrawMenuBar(*hwnd); @@ -244,7 +245,7 @@ impl Menu { let index = self .children .iter() - .position(|e| e.borrow().id() == item.id()) + .position(|e| e.borrow().internal_id() == id) .ok_or(crate::Error::NotAChildOfThisMenu)?; self.children.remove(index); @@ -373,7 +374,8 @@ pub(crate) struct MenuChild { root_menu_haccel_stores: Option>>>, // menu item fields - id: u32, + internal_id: u32, + id: MenuId, accelerator: Option, // predefined menu item fields @@ -393,20 +395,28 @@ pub(crate) struct MenuChild { /// Constructors impl MenuChild { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + pub fn new( + text: &str, + enabled: bool, + accelerator: Option, + id: Option, + ) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::MenuItem, text: text.to_string(), enabled, parents_hemnu: Vec::new(), - id: COUNTER.next(), + internal_id, + id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), accelerator, root_menu_haccel_stores: Some(Vec::new()), ..Default::default() } } - pub fn new_submenu(text: &str, enabled: bool) -> Self { + pub fn new_submenu(text: &str, enabled: bool, id: Option) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::Submenu, text: text.to_string(), @@ -414,6 +424,8 @@ impl MenuChild { parents_hemnu: Vec::new(), children: Some(Vec::new()), hmenu: unsafe { CreateMenu() }, + internal_id, + id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), hpopupmenu: unsafe { CreatePopupMenu() }, root_menu_haccel_stores: Some(Vec::new()), ..Default::default() @@ -421,12 +433,14 @@ impl MenuChild { } pub fn new_predefined(item_type: PredefinedMenuItemType, text: Option) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::Predefined, text: text.unwrap_or_else(|| item_type.text().to_string()), enabled: true, parents_hemnu: Vec::new(), - id: COUNTER.next(), + internal_id, + id: MenuId(internal_id.to_string()), accelerator: item_type.accelerator(), predefined_item_type: item_type, root_menu_haccel_stores: Some(Vec::new()), @@ -439,13 +453,16 @@ impl MenuChild { enabled: bool, checked: bool, accelerator: Option, + id: Option, ) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::Check, text: text.to_string(), enabled, parents_hemnu: Vec::new(), - id: COUNTER.next(), + internal_id, + id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), accelerator, checked, root_menu_haccel_stores: Some(Vec::new()), @@ -458,13 +475,16 @@ impl MenuChild { enabled: bool, icon: Option, accelerator: Option, + id: Option, ) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::Icon, text: text.to_string(), enabled, parents_hemnu: Vec::new(), - id: COUNTER.next(), + internal_id, + id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), accelerator, icon, root_menu_haccel_stores: Some(Vec::new()), @@ -477,13 +497,16 @@ impl MenuChild { enabled: bool, _native_icon: Option, accelerator: Option, + id: Option, ) -> Self { + let internal_id = COUNTER.next(); Self { item_type: MenuItemType::Icon, text: text.to_string(), enabled, parents_hemnu: Vec::new(), - id: COUNTER.next(), + internal_id, + id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), accelerator, root_menu_haccel_stores: Some(Vec::new()), ..Default::default() @@ -497,10 +520,14 @@ impl MenuChild { self.item_type } - pub fn id(&self) -> u32 { + pub fn id(&self) -> &MenuId { + &self.id + } + + pub fn internal_id(&self) -> u32 { match self.item_type() { MenuItemType::Submenu => self.hmenu as u32, - _ => self.id, + _ => self.internal_id, } } @@ -515,12 +542,12 @@ impl MenuChild { info.fMask = MIIM_STRING; info.dwTypeData = label.as_mut_ptr(); - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; + unsafe { GetMenuItemInfoW(*hmenu, self.internal_id(), false.into(), &mut info) }; info.cch += 1; info.dwTypeData = Vec::with_capacity(info.cch as usize).as_mut_ptr(); - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; + unsafe { GetMenuItemInfoW(*hmenu, self.internal_id(), false.into(), &mut info) }; let text = decode_wide(info.dwTypeData); text.split('\t').next().unwrap().to_string() @@ -540,7 +567,7 @@ impl MenuChild { info.fMask = MIIM_STRING; info.dwTypeData = encode_wide(&self.text).as_mut_ptr(); - unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; + unsafe { SetMenuItemInfoW(*parent, self.internal_id(), false.into(), &info) }; } } @@ -552,7 +579,7 @@ impl MenuChild { info.cbSize = std::mem::size_of::() as _; info.fMask = MIIM_STATE; - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; + unsafe { GetMenuItemInfoW(*hmenu, self.internal_id(), false.into(), &mut info) }; (info.fState & MFS_DISABLED) == 0 }) @@ -565,7 +592,7 @@ impl MenuChild { unsafe { EnableMenuItem( *parent, - self.id(), + self.internal_id(), if enabled { MF_ENABLED } else { MF_DISABLED }, ) }; @@ -580,9 +607,9 @@ impl MenuChild { for store in haccel_stores { let mut store = store.borrow_mut(); if let Some(accelerator) = self.accelerator { - AccelAction::add(&mut store, self.id, &accelerator)? + AccelAction::add(&mut store, self.internal_id, &accelerator)? } else { - AccelAction::remove(&mut store, self.id) + AccelAction::remove(&mut store, self.internal_id) } } @@ -600,7 +627,7 @@ impl MenuChild { info.cbSize = std::mem::size_of::() as _; info.fMask = MIIM_STATE; - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; + unsafe { GetMenuItemInfoW(*hmenu, self.internal_id(), false.into(), &mut info) }; (info.fState & MFS_CHECKED) != 0 }) @@ -615,7 +642,7 @@ impl MenuChild { unsafe { WindowsAndMessaging::CheckMenuItem( *parent, - self.id(), + self.internal_id(), if checked { MF_CHECKED } else { MF_UNCHECKED }, ) }; @@ -631,7 +658,7 @@ impl MenuChild { let hbitmap = icon.map(|i| unsafe { i.inner.to_hbitmap() }).unwrap_or(0); let info = create_icon_item_info(hbitmap); for parent in &self.parents_hemnu { - unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; + unsafe { SetMenuItemInfoW(*parent, self.internal_id(), false.into(), &info) }; } } } @@ -670,11 +697,11 @@ impl MenuChild { for root_menu in self.root_menu_haccel_stores.as_mut().unwrap() { let mut haccel = root_menu.borrow_mut(); - AccelAction::add(&mut haccel, child_.id(), accelerator)?; + AccelAction::add(&mut haccel, child_.internal_id(), accelerator)?; } } - let id = child_.id() as usize; + let id = child_.internal_id() as usize; let text = encode_wide(text); unsafe { match op { @@ -714,8 +741,8 @@ impl MenuChild { let info = create_icon_item_info(hbitmap); unsafe { - SetMenuItemInfoW(self.hmenu, child_.id, false.into(), &info); - SetMenuItemInfoW(self.hpopupmenu, child_.id, false.into(), &info); + SetMenuItemInfoW(self.hmenu, child_.internal_id, false.into(), &info); + SetMenuItemInfoW(self.hpopupmenu, child_.internal_id, false.into(), &info); }; } } @@ -738,9 +765,10 @@ impl MenuChild { } pub fn remove(&mut self, item: &dyn IsMenuItem) -> crate::Result<()> { + let id = item.child().borrow().internal_id(); unsafe { - RemoveMenu(self.hmenu, item.id(), MF_BYCOMMAND); - RemoveMenu(self.hpopupmenu, item.id(), MF_BYCOMMAND); + RemoveMenu(self.hmenu, id, MF_BYCOMMAND); + RemoveMenu(self.hpopupmenu, id, MF_BYCOMMAND); } let child = item.child(); @@ -764,7 +792,7 @@ impl MenuChild { let children = self.children.as_mut().unwrap(); let index = children .iter() - .position(|e| e.borrow().id() == item.id()) + .position(|e| e.borrow().internal_id() == id) .ok_or(crate::Error::NotAChildOfThisMenu)?; children.remove(index); @@ -813,7 +841,7 @@ impl MenuChild { fn find_by_id(id: u32, children: &Vec>>) -> Option>> { for i in children { let item = i.borrow(); - if item.id() == id { + if item.internal_id() == id { return Some(i.clone()); } @@ -918,13 +946,15 @@ unsafe extern "system" fn menu_subclass_proc( if let Some(item) = item { ret = 0; - let mut dispatch = true; + let (mut dispatch, mut menu_id) = (true, None); { let mut item = item.borrow_mut(); if item.item_type() == MenuItemType::Predefined { dispatch = false; + } else { + menu_id.replace(item.id.clone()); } match item.item_type() { @@ -966,7 +996,9 @@ unsafe extern "system" fn menu_subclass_proc( } if dispatch { - MenuEvent::send(MenuEvent { id }); + MenuEvent::send(MenuEvent { + id: menu_id.unwrap(), + }); } } }