refactor: add MenuId type and related functions (#91)

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Amr Bashir 2023-08-04 22:17:58 +03:00 committed by GitHub
parent dc8e9d41fd
commit c777f6606a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 744 additions and 325 deletions

View file

@ -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<P
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
}
fn load_icon(path: &std::path::Path) -> 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")
}

View file

@ -204,7 +204,7 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<P
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
}
fn load_icon(path: &std::path::Path) -> 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")
}

View file

@ -266,7 +266,7 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<m
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
}
fn load_icon(path: &std::path::Path) -> 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")
}

View file

@ -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<Accelerator>,
id: Option<MenuId>,
}
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)
}
}
}

View file

@ -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<MenuId>,
acccelerator: Option<Accelerator>,
icon: Option<Icon>,
native_icon: Option<NativeIcon>,
@ -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(

View file

@ -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<MenuId>,
acccelerator: Option<Accelerator>,
}
@ -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)
}
}
}

View file

@ -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<MenuId>,
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> {
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)
}
}
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
pub struct CheckMenuItem {
pub(crate) id: Rc<MenuId>,
pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
}
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<Accelerator>,
) -> 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<I: Into<MenuId>, S: AsRef<str>>(
id: I,
text: S,
enabled: bool,
checked: bool,
acccelerator: Option<Accelerator>,
) -> 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<S: AsRef<str>>(&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<Accelerator>) -> 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)
}
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
pub struct IconMenuItem {
pub(crate) id: Rc<MenuId>,
pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
}
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<Icon>,
acccelerator: Option<Accelerator>,
) -> 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<I: Into<MenuId>, S: AsRef<str>>(
id: I,
text: S,
enabled: bool,
icon: Option<Icon>,
acccelerator: Option<Accelerator>,
) -> 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<NativeIcon>,
acccelerator: Option<Accelerator>,
) -> 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<I: Into<MenuId>, S: AsRef<str>>(
id: I,
text: S,
enabled: bool,
native_icon: Option<NativeIcon>,
acccelerator: Option<Accelerator>,
) -> 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<S: AsRef<str>>(&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<Accelerator>) -> 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<Icon>) {
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<NativeIcon>) {
#[cfg(target_os = "macos")]
self.0.borrow_mut().set_native_icon(_icon)
self.inner.borrow_mut().set_native_icon(_icon)
}
}

View file

@ -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()
);
}
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
pub struct MenuItem {
pub(crate) id: Rc<MenuId>,
pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
}
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<S: AsRef<str>>(text: S, enabled: bool, acccelerator: Option<Accelerator>) -> 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<I: Into<MenuId>, S: AsRef<str>>(
id: I,
text: S,
enabled: bool,
acccelerator: Option<Accelerator>,
) -> 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<S: AsRef<str>>(&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<Accelerator>) -> crate::Result<()> {
self.0.borrow_mut().set_accelerator(acccelerator)
self.inner.borrow_mut().set_accelerator(acccelerator)
}
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
pub struct PredefinedMenuItem {
pub(crate) id: Rc<MenuId>,
pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
}
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<S: AsRef<str>>(item: PredefinedMenuItemType, text: Option<S>) -> 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<S: AsRef<str>>(&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())
);
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
pub struct Submenu {
pub(crate) id: Rc<MenuId>,
pub(crate) inner: Rc<RefCell<crate::platform_impl::MenuChild>>,
}
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<S: AsRef<str>>(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<I: Into<MenuId>, S: AsRef<str>>(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<I: Into<MenuId>, S: AsRef<str>>(
id: I,
text: S,
enabled: bool,
items: &[&dyn IsMenuItem],
) -> crate::Result<Self> {
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<MenuItemKind> {
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<S: AsRef<str>>(&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<Position>) {
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: &gtk::ApplicationWindow,
position: Option<Position>,
) {
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<Position>) {
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()
}
}

View file

@ -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<str> for MenuId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl From<String> 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<String> 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<Self, Self::Err> {
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<Option<MenuEventHandler>> = 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`]

View file

@ -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<RefCell<crate::platform_impl::Menu>>);
pub struct Menu {
id: MenuId,
inner: Rc<RefCell<crate::platform_impl::Menu>>,
}
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<I: Into<MenuId>>(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<I: Into<MenuId>>(
id: I,
items: &[&dyn IsMenuItem],
) -> crate::Result<Self> {
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<MenuItemKind> {
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<gtk::Container>,
C: gtk::prelude::IsA<gtk::Box>,
{
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<gtk::ApplicationWindow>,
W: gtk::prelude::IsA<gtk::Window>,
{
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<gtk::ApplicationWindow>,
{
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<gtk::ApplicationWindow>,
{
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<gtk::ApplicationWindow>,
{
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<gtk::ApplicationWindow>,
{
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<Position>) {
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: &gtk::ApplicationWindow,
position: Option<Position>,
) {
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<Position>) {
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()
}
}

View file

@ -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<Rc<RefCell<MenuChild>>>,
gtk_menubars: HashMap<u32, gtk::MenuBar>,
accel_group: Option<gtk::AccelGroup>,
@ -61,9 +61,9 @@ pub struct Menu {
}
impl Menu {
pub fn new() -> Self {
pub fn new(id: Option<MenuId>) -> 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<RefCell<HashMap<u32, Vec<gtk::MenuItem>>>>,
@ -382,23 +383,28 @@ pub struct MenuChild {
/// Constructors
impl MenuChild {
pub fn new(text: &str, enabled: bool, accelerator: Option<Accelerator>) -> Self {
pub fn new(
text: &str,
enabled: bool,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<MenuId>) -> 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<Accelerator>,
id: Option<MenuId>,
) -> 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<Icon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<NativeIcon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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 {

View file

@ -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<RefCell<Vec<Rc<RefCell<MenuChild>>>>>,
}
@ -63,9 +63,9 @@ impl Drop for Menu {
}
impl Menu {
pub fn new() -> Self {
pub fn new(id: Option<MenuId>) -> 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<u32, Vec<id>>,
ns_menu_items: HashMap<MenuId, Vec<id>>,
// menu item fields
accelerator: Option<Accelerator>,
@ -181,12 +181,12 @@ pub struct MenuChild {
// submenu fields
pub children: Option<Vec<Rc<RefCell<MenuChild>>>>,
ns_menus: HashMap<u32, Vec<id>>,
ns_menus: HashMap<MenuId, Vec<id>>,
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<Accelerator>) -> Self {
pub fn new(
text: &str,
enabled: bool,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<MenuId>) -> 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<Accelerator>,
id: Option<MenuId>,
) -> 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<Icon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<NativeIcon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<id> {
pub fn create_ns_item_for_submenu(&mut self, menu_id: &MenuId) -> crate::Result<id> {
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<id> {
pub fn create_ns_item_for_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> {
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<id> {
pub fn create_ns_item_for_predefined_menu_item(
&mut self,
menu_id: &MenuId,
) -> crate::Result<id> {
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<id> {
pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> {
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<id> {
pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> {
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 });
}
}

View file

@ -24,13 +24,12 @@ pub(crate) use self::platform::*;
impl dyn IsMenuItem + '_ {
fn child(&self) -> Rc<RefCell<MenuChild>> {
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<RefCell<MenuChild>>) -> 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<MenuChild> {
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<MenuChild> {
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(),
}
}
}

View file

@ -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<HWND>,
@ -102,9 +102,9 @@ pub(crate) struct Menu {
}
impl Menu {
pub fn new() -> Self {
pub fn new(id: Option<MenuId>) -> 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<Vec<Rc<RefCell<AccelWrapper>>>>,
// menu item fields
id: u32,
internal_id: u32,
id: MenuId,
accelerator: Option<Accelerator>,
// predefined menu item fields
@ -393,20 +395,28 @@ pub(crate) struct MenuChild {
/// Constructors
impl MenuChild {
pub fn new(text: &str, enabled: bool, accelerator: Option<Accelerator>) -> Self {
pub fn new(
text: &str,
enabled: bool,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<MenuId>) -> 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<String>) -> 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<Accelerator>,
id: Option<MenuId>,
) -> 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<Icon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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<NativeIcon>,
accelerator: Option<Accelerator>,
id: Option<MenuId>,
) -> 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::<MENUITEMINFOW>() 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::<MENUITEMINFOW>() 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<Rc<RefCell<MenuChild>>>) -> Option<Rc<RefCell<MenuChild>>> {
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(),
});
}
}
}