From 0000e569746e7cb630a1453a401bf8f6b0568e9d Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 18 Jul 2023 03:44:52 +0300 Subject: [PATCH] refactor: move `Rc>` out of platform_impl (#71) * refactor: move `Rc>` out of platform_impl * fix doc test --- .changes/menu-item-trait-name.md | 5 + examples/tao.rs | 4 +- examples/winit.rs | 4 +- src/accelerator.rs | 386 +++++-- src/error.rs | 8 +- src/icon.rs | 121 ++ src/{check_menu_item.rs => items/check.rs} | 51 +- src/{icon_menu_item.rs => items/icon.rs} | 56 +- src/items/mod.rs | 15 + src/{menu_item.rs => items/normal.rs} | 33 +- src/{ => items}/predefined.rs | 26 +- src/{ => items}/submenu.rs | 98 +- src/lib.rs | 20 +- src/menu.rs | 112 +- src/platform_impl/gtk/accelerator.rs | 26 +- src/platform_impl/gtk/mod.rs | 1165 +++++++++----------- src/platform_impl/macos/accelerator.rs | 12 +- src/platform_impl/macos/icon.rs | 9 +- src/platform_impl/macos/mod.rs | 1080 +++++++++--------- src/platform_impl/mod.rs | 54 + src/platform_impl/windows/accelerator.rs | 16 +- src/platform_impl/windows/mod.rs | 1154 ++++++++----------- 22 files changed, 2167 insertions(+), 2288 deletions(-) create mode 100644 .changes/menu-item-trait-name.md rename src/{check_menu_item.rs => items/check.rs} (69%) rename src/{icon_menu_item.rs => items/icon.rs} (58%) create mode 100644 src/items/mod.rs rename src/{menu_item.rs => items/normal.rs} (70%) rename src/{ => items}/predefined.rs (95%) rename src/{ => items}/submenu.rs (62%) diff --git a/.changes/menu-item-trait-name.md b/.changes/menu-item-trait-name.md new file mode 100644 index 0000000..4728e87 --- /dev/null +++ b/.changes/menu-item-trait-name.md @@ -0,0 +1,5 @@ +--- +"muda": "minor" +--- + +Changed `MenuItemExt` trait name to `IsMenuItem` diff --git a/examples/tao.rs b/examples/tao.rs index 45f8625..f3fce31 100644 --- a/examples/tao.rs +++ b/examples/tao.rs @@ -27,12 +27,12 @@ fn main() { #[cfg(target_os = "windows")] { - let menu_bar_c = menu_bar.clone(); + let menu_bar = menu_bar.clone(); event_loop_builder.with_msg_hook(move |msg| { use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG}; unsafe { let msg = msg as *const MSG; - let translated = TranslateAcceleratorW((*msg).hwnd, menu_bar_c.haccel(), msg); + let translated = TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel(), msg); translated == 1 } }); diff --git a/examples/winit.rs b/examples/winit.rs index 5018887..06494b1 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -25,12 +25,12 @@ fn main() { #[cfg(target_os = "windows")] { - let menu_bar_c = menu_bar.clone(); + let menu_bar = menu_bar.clone(); event_loop_builder.with_msg_hook(move |msg| { use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG}; unsafe { let msg = msg as *const MSG; - let translated = TranslateAcceleratorW((*msg).hwnd, menu_bar_c.haccel(), msg); + let translated = TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel(), msg); translated == 1 } }); diff --git a/src/accelerator.rs b/src/accelerator.rs index 0afc6cb..737214e 100644 --- a/src/accelerator.rs +++ b/src/accelerator.rs @@ -37,26 +37,54 @@ use std::{borrow::Borrow, hash::Hash, str::FromStr}; pub struct Accelerator { pub(crate) mods: Modifiers, pub(crate) key: Code, + id: u32, } impl Accelerator { /// Creates a new accelerator to define keyboard shortcuts throughout your application. - /// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::META`]/[`Modifiers::SUPER`] + /// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::SUPER`] pub fn new(mods: Option, key: Code) -> Self { - Self { - mods: mods.unwrap_or_else(Modifiers::empty), - key, + let mut mods = mods.unwrap_or_else(Modifiers::empty); + if mods.contains(Modifiers::META) { + mods.remove(Modifiers::META); + mods.insert(Modifiers::SUPER); } + let mut accelerator = Self { mods, key, id: 0 }; + accelerator.generate_hash(); + accelerator + } + + fn generate_hash(&mut self) { + let mut str = String::new(); + if self.mods.contains(Modifiers::SHIFT) { + str.push_str("shift+") + } + if self.mods.contains(Modifiers::CONTROL) { + str.push_str("control+") + } + if self.mods.contains(Modifiers::ALT) { + str.push_str("alt+") + } + if self.mods.contains(Modifiers::SUPER) { + str.push_str("super+") + } + str.push_str(&self.key.to_string()); + + let mut s = std::collections::hash_map::DefaultHasher::new(); + str.hash(&mut s); + self.id = std::hash::Hasher::finish(&s) as u32; + } + + /// Returns the id associated with this accelerator + /// which is a hash of a string representing modifiers and key within this accelerator + pub fn id(&self) -> u32 { + self.id } /// Returns `true` if this [`Code`] and [`Modifiers`] matches this `Accelerator`. pub fn matches(&self, modifiers: impl Borrow, key: impl Borrow) -> bool { // Should be a const but const bit_or doesn't work here. - let base_mods = Modifiers::SHIFT - | Modifiers::CONTROL - | Modifiers::ALT - | Modifiers::META - | Modifiers::SUPER; + let base_mods = Modifiers::SHIFT | Modifiers::CONTROL | Modifiers::ALT | Modifiers::SUPER; let modifiers = modifiers.borrow(); let key = key.borrow(); self.mods == *modifiers & base_mods && self.key == *key @@ -73,151 +101,295 @@ impl FromStr for Accelerator { } } -fn parse_accelerator(accelerator_string: &str) -> crate::Result { +fn parse_accelerator(accelerator: &str) -> crate::Result { + let tokens = accelerator.split('+').collect::>(); + let mut mods = Modifiers::empty(); - let mut key = Code::Unidentified; + let mut key = None; - let mut split = accelerator_string.split('+'); - let len = split.clone().count(); - let parse_key = |token: &str| -> crate::Result { - if let Ok(code) = Code::from_str(token) { - match code { - Code::Unidentified => Err(crate::Error::AcceleratorParseError(format!( - "Couldn't identify \"{}\" as a valid `Code`", - token - ))), - _ => Ok(code), - } - } else { - Err(crate::Error::AcceleratorParseError(format!( - "Couldn't identify \"{}\" as a valid `Code`", - token - ))) + match tokens.len() { + // single key accelerator + 1 => { + key = Some(parse_key(tokens[0])?); } - }; + // modifiers and key comobo accelerator + _ => { + for raw in tokens { + let token = raw.trim(); - if len == 1 { - let token = split.next().unwrap(); - key = parse_key(token)?; - } else { - for raw in accelerator_string.split('+') { - let token = raw.trim().to_string(); - if token.is_empty() { - return Err(crate::Error::AcceleratorParseError( - "Unexpected empty token while parsing accelerator".into(), - )); - } + if token.is_empty() { + return Err(crate::Error::EmptyAcceleratorToken(accelerator.to_string())); + } - if key != Code::Unidentified { - // at this point we already parsed the modifiers and found a main key but - // the function received more then one main key or it is not in the right order - // examples: - // 1. "Ctrl+Shift+C+A" => only one main key should be allowd. - // 2. "Ctrl+C+Shift" => wrong order - return Err(crate::Error::AcceleratorParseError(format!( - "Unexpected accelerator string format: \"{}\"", - accelerator_string - ))); - } + if key.is_some() { + // At this point we have parsed the modifiers and a main key, so by reaching + // this code, the function either received more than one main key or + // the accelerator is not in the right order + // examples: + // 1. "Ctrl+Shift+C+A" => only one main key should be allowd. + // 2. "Ctrl+C+Shift" => wrong order + return Err(crate::Error::UnexpectedAcceleratorFormat( + accelerator.to_string(), + )); + } - match token.to_uppercase().as_str() { - "OPTION" | "ALT" => { - mods.set(Modifiers::ALT, true); - } - "CONTROL" | "CTRL" => { - mods.set(Modifiers::CONTROL, true); - } - "COMMAND" | "CMD" | "SUPER" => { - mods.set(Modifiers::META, true); - } - "SHIFT" => { - mods.set(Modifiers::SHIFT, true); - } - "COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => { - #[cfg(target_os = "macos")] - mods.set(Modifiers::META, true); - #[cfg(not(target_os = "macos"))] - mods.set(Modifiers::CONTROL, true); - } - _ => { - key = parse_key(token.as_str())?; + match token.to_uppercase().as_str() { + "OPTION" | "ALT" => { + mods.set(Modifiers::ALT, true); + } + "CONTROL" | "CTRL" => { + mods.set(Modifiers::CONTROL, true); + } + "COMMAND" | "CMD" | "SUPER" => { + mods.set(Modifiers::META, true); + } + "SHIFT" => { + mods.set(Modifiers::SHIFT, true); + } + "COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => { + #[cfg(target_os = "macos")] + mods.set(Modifiers::SUPER, true); + #[cfg(not(target_os = "macos"))] + mods.set(Modifiers::CONTROL, true); + } + _ => { + key = Some(parse_key(token)?); + } } } } } - Ok(Accelerator { key, mods }) + Ok(Accelerator::new(Some(mods), key.unwrap())) +} + +fn parse_key(key: &str) -> crate::Result { + use Code::*; + match key.to_uppercase().as_str() { + "BACKQUOTE" | "`" => Ok(Backquote), + "BACKSLASH" | "\\" => Ok(Backslash), + "BRACKETLEFT" | "[" => Ok(BracketLeft), + "BRACKETRIGHT" | "]" => Ok(BracketRight), + "COMMA" | "," => Ok(Comma), + "DIGIT0" | "0" => Ok(Digit0), + "DIGIT1" | "1" => Ok(Digit1), + "DIGIT2" | "2" => Ok(Digit2), + "DIGIT3" | "3" => Ok(Digit3), + "DIGIT4" | "4" => Ok(Digit4), + "DIGIT5" | "5" => Ok(Digit5), + "DIGIT6" | "6" => Ok(Digit6), + "DIGIT7" | "7" => Ok(Digit7), + "DIGIT8" | "8" => Ok(Digit8), + "DIGIT9" | "9" => Ok(Digit9), + "EQUAL" | "=" => Ok(Equal), + "KEYA" | "A" => Ok(KeyA), + "KEYB" | "B" => Ok(KeyB), + "KEYC" | "C" => Ok(KeyC), + "KEYD" | "D" => Ok(KeyD), + "KEYE" | "E" => Ok(KeyE), + "KEYF" | "F" => Ok(KeyF), + "KEYG" | "G" => Ok(KeyG), + "KEYH" | "H" => Ok(KeyH), + "KEYI" | "I" => Ok(KeyI), + "KEYJ" | "J" => Ok(KeyJ), + "KEYK" | "K" => Ok(KeyK), + "KEYL" | "L" => Ok(KeyL), + "KEYM" | "M" => Ok(KeyM), + "KEYN" | "N" => Ok(KeyN), + "KEYO" | "O" => Ok(KeyO), + "KEYP" | "P" => Ok(KeyP), + "KEYQ" | "Q" => Ok(KeyQ), + "KEYR" | "R" => Ok(KeyR), + "KEYS" | "S" => Ok(KeyS), + "KEYT" | "T" => Ok(KeyT), + "KEYU" | "U" => Ok(KeyU), + "KEYV" | "V" => Ok(KeyV), + "KEYW" | "W" => Ok(KeyW), + "KEYX" | "X" => Ok(KeyX), + "KEYY" | "Y" => Ok(KeyY), + "KEYZ" | "Z" => Ok(KeyZ), + "MINUS" | "-" => Ok(Minus), + "PERIOD" | "." => Ok(Period), + "QUOTE" | "'" => Ok(Quote), + "SEMICOLON" | ";" => Ok(Semicolon), + "SLASH" | "/" => Ok(Slash), + "BACKSPACE" => Ok(Backspace), + "CAPSLOCK" => Ok(CapsLock), + "ENTER" => Ok(Enter), + "SPACE" => Ok(Space), + "TAB" => Ok(Tab), + "DELETE" => Ok(Delete), + "END" => Ok(End), + "HOME" => Ok(Home), + "INSERT" => Ok(Insert), + "PAGEDOWN" => Ok(PageDown), + "PAGEUP" => Ok(PageUp), + "PRINTSCREEN" => Ok(PrintScreen), + "SCROLLLOCK" => Ok(ScrollLock), + "ARROWDOWN" | "DOWN" => Ok(ArrowDown), + "ARROWLEFT" | "LEFT" => Ok(ArrowLeft), + "ARROWRIGHT" | "RIGHT" => Ok(ArrowRight), + "ARROWUP" | "UP" => Ok(ArrowUp), + "NUMLOCK" => Ok(NumLock), + "NUMPAD0" | "NUM0" => Ok(Numpad0), + "NUMPAD1" | "NUM1" => Ok(Numpad1), + "NUMPAD2" | "NUM2" => Ok(Numpad2), + "NUMPAD3" | "NUM3" => Ok(Numpad3), + "NUMPAD4" | "NUM4" => Ok(Numpad4), + "NUMPAD5" | "NUM5" => Ok(Numpad5), + "NUMPAD6" | "NUM6" => Ok(Numpad6), + "NUMPAD7" | "NUM7" => Ok(Numpad7), + "NUMPAD8" | "NUM8" => Ok(Numpad8), + "NUMPAD9" | "NUM9" => Ok(Numpad9), + "NUMPADADD" | "NUMADD" | "NUMPADPLUS" | "NUMPLUS" => Ok(NumpadAdd), + "NUMPADDECIMAL" | "NUMDECIMAL" => Ok(NumpadDecimal), + "NUMPADDIVIDE" | "NUMDIVIDE" => Ok(NumpadDivide), + "NUMPADENTER" | "NUMENTER" => Ok(NumpadEnter), + "NUMPADEQUAL" | "NUMEQUAL" => Ok(NumpadEqual), + "NUMPADMULTIPLY" | "NUMMULTIPLY" => Ok(NumpadMultiply), + "NUMPADSUBTRACT" | "NUMSUBTRACT" => Ok(NumpadSubtract), + "ESCAPE" | "ESC" => Ok(Escape), + "F1" => Ok(F1), + "F2" => Ok(F2), + "F3" => Ok(F3), + "F4" => Ok(F4), + "F5" => Ok(F5), + "F6" => Ok(F6), + "F7" => Ok(F7), + "F8" => Ok(F8), + "F9" => Ok(F9), + "F10" => Ok(F10), + "F11" => Ok(F11), + "F12" => Ok(F12), + "AUDIOVOLUMEDOWN" | "VOLUMEDOWN" => Ok(AudioVolumeDown), + "AUDIOVOLUMEUP" | "VOLUMEUP" => Ok(AudioVolumeUp), + "AUDIOVOLUMEMUTE" | "VOLUMEMUTE" => Ok(AudioVolumeMute), + "F13" => Ok(F13), + "F14" => Ok(F14), + "F15" => Ok(F15), + "F16" => Ok(F16), + "F17" => Ok(F17), + "F18" => Ok(F18), + "F19" => Ok(F19), + "F20" => Ok(F20), + "F21" => Ok(F21), + "F22" => Ok(F22), + "F23" => Ok(F23), + "F24" => Ok(F24), + + _ => Err(crate::Error::UnrecognizedAcceleratorCode(key.to_string())), + } } #[test] fn test_parse_accelerator() { - assert_eq!( - parse_accelerator("CTRL+KeyX").unwrap(), + macro_rules! assert_parse_accelerator { + ($key:literal, $lrh:expr) => { + let r = parse_accelerator($key).unwrap(); + let l = $lrh; + assert_eq!(r.mods, l.mods); + assert_eq!(r.key, l.key); + }; + } + + assert_parse_accelerator!( + "KeyX", + Accelerator { + mods: Modifiers::empty(), + key: Code::KeyX, + id: 0, + } + ); + + assert_parse_accelerator!( + "CTRL+KeyX", Accelerator { mods: Modifiers::CONTROL, key: Code::KeyX, + id: 0, } ); - assert_eq!( - parse_accelerator("SHIFT+KeyC").unwrap(), + + assert_parse_accelerator!( + "SHIFT+KeyC", Accelerator { mods: Modifiers::SHIFT, key: Code::KeyC, + id: 0, } ); - assert_eq!( - parse_accelerator("CTRL+KeyZ").unwrap(), + + assert_parse_accelerator!( + "SHIFT+KeyC", Accelerator { - mods: Modifiers::CONTROL, - key: Code::KeyZ, + mods: Modifiers::SHIFT, + key: Code::KeyC, + id: 0, } ); - assert_eq!( - parse_accelerator("super+ctrl+SHIFT+alt+ArrowUp").unwrap(), + + assert_parse_accelerator!( + "super+ctrl+SHIFT+alt+ArrowUp", Accelerator { - mods: Modifiers::META | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT, + mods: Modifiers::SUPER | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT, key: Code::ArrowUp, + id: 0, } ); - assert_eq!( - parse_accelerator("Digit5").unwrap(), + assert_parse_accelerator!( + "Digit5", Accelerator { mods: Modifiers::empty(), key: Code::Digit5, + id: 0, } ); - assert_eq!( - parse_accelerator("KeyG").unwrap(), + assert_parse_accelerator!( + "KeyG", Accelerator { mods: Modifiers::empty(), key: Code::KeyG, + id: 0, } ); - let acc = parse_accelerator("+G"); - assert!(acc.is_err()); - - let acc = parse_accelerator("SHGSH+G"); - assert!(acc.is_err()); - - assert_eq!( - parse_accelerator("SHiFT+F12").unwrap(), + assert_parse_accelerator!( + "SHiFT+F12", Accelerator { mods: Modifiers::SHIFT, key: Code::F12, - } - ); - assert_eq!( - parse_accelerator("CmdOrCtrl+Space").unwrap(), - Accelerator { - #[cfg(target_os = "macos")] - mods: Modifiers::META, - #[cfg(not(target_os = "macos"))] - mods: Modifiers::CONTROL, - key: Code::Space, + id: 0, } ); - let acc = parse_accelerator("CTRL+"); - assert!(acc.is_err()); + assert_parse_accelerator!( + "CmdOrCtrl+Space", + Accelerator { + #[cfg(target_os = "macos")] + mods: Modifiers::SUPER, + #[cfg(not(target_os = "macos"))] + mods: Modifiers::CONTROL, + key: Code::Space, + id: 0, + } + ); +} + +#[test] +fn test_equality() { + let h1 = parse_accelerator("Shift+KeyR").unwrap(); + let h2 = parse_accelerator("Shift+KeyR").unwrap(); + let h3 = Accelerator::new(Some(Modifiers::SHIFT), Code::KeyR); + let h4 = parse_accelerator("Alt+KeyR").unwrap(); + let h5 = parse_accelerator("Alt+KeyR").unwrap(); + let h6 = parse_accelerator("KeyR").unwrap(); + + assert!(h1 == h2 && h2 == h3 && h3 != h4 && h4 == h5 && h5 != h6); + assert!( + h1.id() == h2.id() + && h2.id() == h3.id() + && h3.id() != h4.id() + && h4.id() == h5.id() + && h5.id() != h6.id() + ); } diff --git a/src/error.rs b/src/error.rs index 9e67f35..c5e2fd7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,8 +24,12 @@ pub enum Error { AlreadyInitialized, #[error("{0}")] AcceleratorParseError(String), - #[error("Cannot map {0} to gdk key")] - AcceleratorKeyNotSupported(keyboard_types::Code), + #[error("Couldn't recognize \"{0}\" as a valid Accelerator Code, if you feel like it should be, please report this to https://github.com/tauri-apps/muda")] + UnrecognizedAcceleratorCode(String), + #[error("Unexpected empty token while parsing accelerator: \"{0}\"")] + EmptyAcceleratorToken(String), + #[error("Unexpected accelerator string format: \"{0}\", a accelerator should have the modifiers first and only contain one main key")] + UnexpectedAcceleratorFormat(String), } /// Convenient type alias of Result type for muda. diff --git a/src/icon.rs b/src/icon.rs index 824cecb..ec62bb3 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -164,3 +164,124 @@ impl Icon { Ok(Icon { inner: win_icon }) } } + +/// A native Icon to be used for the menu item +/// +/// ## Platform-specific: +/// +/// - **Windows / Linux**: Unsupported. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NativeIcon { + /// An add item template image. + Add, + /// Advanced preferences toolbar icon for the preferences window. + Advanced, + /// A Bluetooth template image. + Bluetooth, + /// Bookmarks image suitable for a template. + Bookmarks, + /// A caution image. + Caution, + /// A color panel toolbar icon. + ColorPanel, + /// A column view mode template image. + ColumnView, + /// A computer icon. + Computer, + /// An enter full-screen mode template image. + EnterFullScreen, + /// Permissions for all users. + Everyone, + /// An exit full-screen mode template image. + ExitFullScreen, + /// A cover flow view mode template image. + FlowView, + /// A folder image. + Folder, + /// A burnable folder icon. + FolderBurnable, + /// A smart folder icon. + FolderSmart, + /// A link template image. + FollowLinkFreestanding, + /// A font panel toolbar icon. + FontPanel, + /// A `go back` template image. + GoLeft, + /// A `go forward` template image. + GoRight, + /// Home image suitable for a template. + Home, + /// An iChat Theater template image. + IChatTheater, + /// An icon view mode template image. + IconView, + /// An information toolbar icon. + Info, + /// A template image used to denote invalid data. + InvalidDataFreestanding, + /// A generic left-facing triangle template image. + LeftFacingTriangle, + /// A list view mode template image. + ListView, + /// A locked padlock template image. + LockLocked, + /// An unlocked padlock template image. + LockUnlocked, + /// A horizontal dash, for use in menus. + MenuMixedState, + /// A check mark template image, for use in menus. + MenuOnState, + /// A MobileMe icon. + MobileMe, + /// A drag image for multiple items. + MultipleDocuments, + /// A network icon. + Network, + /// A path button template image. + Path, + /// General preferences toolbar icon for the preferences window. + PreferencesGeneral, + /// A Quick Look template image. + QuickLook, + /// A refresh template image. + RefreshFreestanding, + /// A refresh template image. + Refresh, + /// A remove item template image. + Remove, + /// A reveal contents template image. + RevealFreestanding, + /// A generic right-facing triangle template image. + RightFacingTriangle, + /// A share view template image. + Share, + /// A slideshow template image. + Slideshow, + /// A badge for a `smart` item. + SmartBadge, + /// Small green indicator, similar to iChat’s available image. + StatusAvailable, + /// Small clear indicator. + StatusNone, + /// Small yellow indicator, similar to iChat’s idle image. + StatusPartiallyAvailable, + /// Small red indicator, similar to iChat’s unavailable image. + StatusUnavailable, + /// A stop progress template image. + StopProgressFreestanding, + /// A stop progress button template image. + StopProgress, + /// An image of the empty trash can. + TrashEmpty, + /// An image of the full trash can. + TrashFull, + /// Permissions for a single user. + User, + /// User account toolbar icon for the preferences window. + UserAccounts, + /// Permissions for a group of users. + UserGroup, + /// Permissions for guests. + UserGuest, +} diff --git a/src/check_menu_item.rs b/src/items/check.rs similarity index 69% rename from src/check_menu_item.rs rename to src/items/check.rs index 5c27c76..086d601 100644 --- a/src/check_menu_item.rs +++ b/src/items/check.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{accelerator::Accelerator, MenuItemExt, MenuItemType}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{accelerator::Accelerator, IsMenuItem, MenuItemType}; /// A check menu item inside a [`Menu`] or [`Submenu`] /// and usually contains a text and a check mark or a similar toggle @@ -11,12 +13,13 @@ use crate::{accelerator::Accelerator, MenuItemExt, MenuItemType}; /// [`Menu`]: crate::Menu /// [`Submenu`]: crate::Submenu #[derive(Clone)] -pub struct CheckMenuItem(pub(crate) crate::platform_impl::CheckMenuItem); +pub struct CheckMenuItem(pub(crate) Rc>); -unsafe impl MenuItemExt for CheckMenuItem { +unsafe impl IsMenuItem for CheckMenuItem { fn type_(&self) -> MenuItemType { MenuItemType::Check } + fn as_any(&self) -> &(dyn std::any::Any + 'static) { self } @@ -30,60 +33,62 @@ impl CheckMenuItem { /// Create a new 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 `&&` + /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>( text: S, enabled: bool, checked: bool, acccelerator: Option, ) -> Self { - Self(crate::platform_impl::CheckMenuItem::new( - text.as_ref(), - enabled, - checked, - acccelerator, - )) + Self(Rc::new(RefCell::new( + crate::platform_impl::MenuChild::new_check( + text.as_ref(), + enabled, + checked, + acccelerator, + ), + ))) } /// Returns a unique identifier associated with this submenu. pub fn id(&self) -> u32 { - self.0.id() + self.0.borrow().id() } /// Get the text for this check menu item. pub fn text(&self) -> String { - self.0.text() + self.0.borrow().text() } /// Get 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 `&&` + /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.set_text(text.as_ref()) + self.0.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.is_enabled() + self.0.borrow().is_enabled() } /// Enable or disable this check menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.set_enabled(enabled) + self.0.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) } /// Get whether this check menu item is checked or not. pub fn is_checked(&self) -> bool { - self.0.is_checked() + self.0.borrow().is_checked() } /// Check or Uncheck this check menu item. pub fn set_checked(&self, checked: bool) { - self.0.set_checked(checked) - } - - /// Set this check menu item accelerator. - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.set_accelerator(acccelerator) + self.0.borrow_mut().set_checked(checked) } } diff --git a/src/icon_menu_item.rs b/src/items/icon.rs similarity index 58% rename from src/icon_menu_item.rs rename to src/items/icon.rs index 827bc4d..ec5c450 100644 --- a/src/icon_menu_item.rs +++ b/src/items/icon.rs @@ -2,7 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{accelerator::Accelerator, icon::Icon, MenuItemExt, MenuItemType}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{ + accelerator::Accelerator, + icon::{Icon, NativeIcon}, + IsMenuItem, MenuItemType, +}; /// An icon menu item inside a [`Menu`] or [`Submenu`] /// and usually contains an icon and a text. @@ -10,12 +16,13 @@ use crate::{accelerator::Accelerator, icon::Icon, MenuItemExt, MenuItemType}; /// [`Menu`]: crate::Menu /// [`Submenu`]: crate::Submenu #[derive(Clone)] -pub struct IconMenuItem(pub(crate) crate::platform_impl::IconMenuItem); +pub struct IconMenuItem(pub(crate) Rc>); -unsafe impl MenuItemExt for IconMenuItem { +unsafe impl IsMenuItem for IconMenuItem { fn type_(&self) -> MenuItemType { MenuItemType::Icon } + fn as_any(&self) -> &(dyn std::any::Any + 'static) { self } @@ -26,58 +33,65 @@ unsafe impl MenuItemExt for IconMenuItem { } impl IconMenuItem { - /// Create a new check menu item. + /// Create a new icon 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 `&&` + /// for this icon menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>( text: S, enabled: bool, icon: Option, acccelerator: Option, ) -> Self { - Self(crate::platform_impl::IconMenuItem::new( - text.as_ref(), - enabled, - icon, - acccelerator, - )) + Self(Rc::new(RefCell::new( + crate::platform_impl::MenuChild::new_icon(text.as_ref(), enabled, icon, acccelerator), + ))) } /// Returns a unique identifier associated with this submenu. pub fn id(&self) -> u32 { - self.0.id() + self.0.borrow().id() } /// Get the text for this check menu item. pub fn text(&self) -> String { - self.0.text() + self.0.borrow().text() } /// Get 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 `&&` + /// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.set_text(text.as_ref()) + self.0.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.is_enabled() + self.0.borrow().is_enabled() } /// Enable or disable this check menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.set_enabled(enabled) + self.0.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) } /// Change this menu item icon or remove it. pub fn set_icon(&self, icon: Option) { - self.0.set_icon(icon) + self.0.borrow_mut().set_icon(icon) } - /// Set this icon menu item accelerator. - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.set_accelerator(acccelerator) + /// Change this menu item icon to a native image or remove it. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: Unsupported. + pub fn set_native_icon(&mut self, _icon: Option) { + #[cfg(target_os = "macos")] + self.0.borrow_mut().set_native_icon(_icon) } } diff --git a/src/items/mod.rs b/src/items/mod.rs new file mode 100644 index 0000000..4a76aac --- /dev/null +++ b/src/items/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2022-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +mod check; +mod icon; +mod normal; +mod predefined; +mod submenu; + +pub use check::*; +pub use icon::*; +pub use normal::*; +pub use predefined::*; +pub use submenu::*; diff --git a/src/menu_item.rs b/src/items/normal.rs similarity index 70% rename from src/menu_item.rs rename to src/items/normal.rs index 776c00b..6546761 100644 --- a/src/menu_item.rs +++ b/src/items/normal.rs @@ -1,20 +1,19 @@ -// Copyright 2022-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT +use std::{cell::RefCell, rc::Rc}; -use crate::{accelerator::Accelerator, MenuItemExt, MenuItemType}; +use crate::{accelerator::Accelerator, IsMenuItem, MenuItemType}; /// 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) crate::platform_impl::MenuItem); +pub struct MenuItem(pub(crate) Rc>); -unsafe impl MenuItemExt for MenuItem { +unsafe impl IsMenuItem for MenuItem { fn type_(&self) -> MenuItemType { MenuItemType::Normal } + fn as_any(&self) -> &(dyn std::any::Any + 'static) { self } @@ -28,44 +27,44 @@ impl MenuItem { /// Create a new 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 `&&` + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>(text: S, enabled: bool, acccelerator: Option) -> Self { - Self(crate::platform_impl::MenuItem::new( + Self(Rc::new(RefCell::new(crate::platform_impl::MenuChild::new( text.as_ref(), enabled, acccelerator, - )) + )))) } /// Returns a unique identifier associated with this menu item. pub fn id(&self) -> u32 { - self.0.id() + self.0.borrow().id() } /// Get the text for this menu item. pub fn text(&self) -> String { - self.0.text() + self.0.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 `&&` + /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.set_text(text.as_ref()) + self.0.borrow_mut().set_text(text.as_ref()) } /// Get whether this menu item is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.is_enabled() + self.0.borrow().is_enabled() } /// Enable or disable this menu item. pub fn set_enabled(&self, enabled: bool) { - self.0.set_enabled(enabled) + self.0.borrow_mut().set_enabled(enabled) } /// Set this menu item accelerator. - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.set_accelerator(acccelerator) + pub fn set_accelerator(&self, acccelerator: Option) -> crate::Result<()> { + self.0.borrow_mut().set_accelerator(acccelerator) } } diff --git a/src/predefined.rs b/src/items/predefined.rs similarity index 95% rename from src/predefined.rs rename to src/items/predefined.rs index c7c7a92..7c2fa29 100644 --- a/src/predefined.rs +++ b/src/items/predefined.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{accelerator::Accelerator, icon::Icon, MenuItemExt, MenuItemType}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{accelerator::Accelerator, icon::Icon, IsMenuItem, MenuItemType}; use keyboard_types::{Code, Modifiers}; #[cfg(target_os = "macos")] @@ -11,12 +13,13 @@ pub const CMD_OR_CTRL: Modifiers = Modifiers::META; pub const CMD_OR_CTRL: Modifiers = Modifiers::CONTROL; /// A predefined (native) menu item which has a predfined behavior by the OS or by this crate. -pub struct PredefinedMenuItem(pub(crate) crate::platform_impl::PredefinedMenuItem); +pub struct PredefinedMenuItem(pub(crate) Rc>); -unsafe impl MenuItemExt for PredefinedMenuItem { +unsafe impl IsMenuItem for PredefinedMenuItem { fn type_(&self) -> MenuItemType { MenuItemType::Predefined } + fn as_any(&self) -> &(dyn std::any::Any + 'static) { self } @@ -156,24 +159,26 @@ impl PredefinedMenuItem { } fn new>(item: PredfinedMenuItemType, text: Option) -> Self { - Self(crate::platform_impl::PredefinedMenuItem::new( - item, - text.map(|t| t.as_ref().to_string()), - )) + Self(Rc::new(RefCell::new( + crate::platform_impl::MenuChild::new_predefined( + item, + text.map(|t| t.as_ref().to_string()), + ), + ))) } fn id(&self) -> u32 { - self.0.id() + self.0.borrow().id() } /// Get the text for this predefined menu item. pub fn text(&self) -> String { - self.0.text() + self.0.borrow().text() } /// Set the text for this predefined menu item. pub fn set_text>(&self, text: S) { - self.0.set_text(text.as_ref()) + self.0.borrow_mut().set_text(text.as_ref()) } } @@ -237,6 +242,7 @@ pub struct AboutMetadata { } impl AboutMetadata { + #[allow(unused)] pub(crate) fn full_version(&self) -> Option { Some(format!( "{}{}", diff --git a/src/submenu.rs b/src/items/submenu.rs similarity index 62% rename from src/submenu.rs rename to src/items/submenu.rs index c817b78..a129f7e 100644 --- a/src/submenu.rs +++ b/src/items/submenu.rs @@ -2,18 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{util::AddOp, ContextMenu, MenuItemExt, MenuItemType}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuItemType}; /// A menu that can be added to a [`Menu`] or another [`Submenu`]. /// /// [`Menu`]: crate::Menu #[derive(Clone)] -pub struct Submenu(pub(crate) crate::platform_impl::Submenu); +pub struct Submenu(pub(crate) Rc>); -unsafe impl MenuItemExt for Submenu { +unsafe impl IsMenuItem for Submenu { fn type_(&self) -> MenuItemType { MenuItemType::Submenu } + fn as_any(&self) -> &(dyn std::any::Any + 'static) { self } @@ -27,90 +30,101 @@ impl Submenu { /// Create a new 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 `&&` + /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. pub fn new>(text: S, enabled: bool) -> Self { - Self(crate::platform_impl::Submenu::new(text.as_ref(), enabled)) + Self(Rc::new(RefCell::new( + crate::platform_impl::MenuChild::new_submenu(text.as_ref(), enabled), + ))) } /// Creates a new submenu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally. - pub fn with_items>(text: S, enabled: bool, items: &[&dyn MenuItemExt]) -> Self { + pub fn with_items>( + text: S, + enabled: bool, + items: &[&dyn IsMenuItem], + ) -> crate::Result { let menu = Self::new(text, enabled); - menu.append_items(items); - menu + menu.append_items(items)?; + Ok(menu) } /// Returns a unique identifier associated with this submenu. pub fn id(&self) -> u32 { - self.0.id() + self.0.borrow().id() } /// Add a menu item to the end of this menu. - pub fn append(&self, item: &dyn MenuItemExt) { - self.0.add_menu_item(item, AddOp::Append) + pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.borrow_mut().add_menu_item(item, AddOp::Append) } /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop. - pub fn append_items(&self, items: &[&dyn MenuItemExt]) { + pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { for item in items { - self.append(*item); + self.append(*item)? } + + Ok(()) } /// Add a menu item to the beginning of this submenu. - pub fn prepend(&self, item: &dyn MenuItemExt) { - self.0.add_menu_item(item, AddOp::Insert(0)) + pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.borrow_mut().add_menu_item(item, AddOp::Insert(0)) } /// Add menu items to the beginning of this submenu. /// It calls [`Menu::prepend`](crate::Menu::prepend) on the first element and /// passes the rest to [`Menu::insert_items`](crate::Menu::insert_items) with position of `1`. - pub fn prepend_items(&self, items: &[&dyn MenuItemExt]) { - self.prepend(items[0]); - self.insert_items(&items[1..], 1); + pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + self.insert_items(items, 0) } /// Insert a menu item at the specified `postion` in the submenu. - pub fn insert(&self, item: &dyn MenuItemExt, position: usize) { - self.0.add_menu_item(item, AddOp::Insert(position)) + pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { + self.0 + .borrow_mut() + .add_menu_item(item, AddOp::Insert(position)) } /// Insert menu items at the specified `postion` in the submenu. - pub fn insert_items(&self, items: &[&dyn MenuItemExt], position: usize) { + pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> { for (i, item) in items.iter().enumerate() { - self.insert(*item, position + i) + self.insert(*item, position + i)? } + + Ok(()) } /// Remove a menu item from this submenu. - pub fn remove(&self, item: &dyn MenuItemExt) -> crate::Result<()> { - self.0.remove(item) + pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.borrow_mut().remove(item) } /// Returns a list of menu items that has been added to this submenu. - pub fn items(&self) -> Vec> { - self.0.items() + pub fn items(&self) -> Vec> { + self.0.borrow().items() } /// Get the text for this submenu. pub fn text(&self) -> String { - self.0.text() + self.0.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 `&&` + /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`. pub fn set_text>(&self, text: S) { - self.0.set_text(text.as_ref()) + self.0.borrow_mut().set_text(text.as_ref()) } /// Get whether this submenu is enabled or not. pub fn is_enabled(&self) -> bool { - self.0.is_enabled() + self.0.borrow().is_enabled() } /// Enable or disable this submenu. pub fn set_enabled(&self, enabled: bool) { - self.0.set_enabled(enabled) + self.0.borrow_mut().set_enabled(enabled) } /// Set this submenu as the Window menu for the application on macOS. @@ -119,7 +133,7 @@ impl Submenu { /// certain other items to the menu. #[cfg(target_os = "macos")] pub fn set_windows_menu_for_nsapp(&self) { - self.0.set_windows_menu_for_nsapp() + self.0.borrow_mut().set_windows_menu_for_nsapp() } /// Set this submenu as the Help menu for the application on macOS. @@ -130,48 +144,50 @@ impl Submenu { /// which has a title matching the localized word "Help". #[cfg(target_os = "macos")] pub fn set_help_menu_for_nsapp(&self) { - self.0.set_help_menu_for_nsapp() + self.0.borrow_mut().set_help_menu_for_nsapp() } } impl ContextMenu for Submenu { #[cfg(target_os = "windows")] fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU { - self.0.hpopupmenu() + self.0.borrow().hpopupmenu() } #[cfg(target_os = "windows")] fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) { - self.0.show_context_menu_for_hwnd(hwnd, x, y) + self.0.borrow_mut().show_context_menu_for_hwnd(hwnd, x, y) } #[cfg(target_os = "windows")] fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { - self.0.attach_menu_subclass_for_hwnd(hwnd) + self.0.borrow_mut().attach_menu_subclass_for_hwnd(hwnd) } #[cfg(target_os = "windows")] fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) { - self.0.detach_menu_subclass_from_hwnd(hwnd) + self.0.borrow_mut().detach_menu_subclass_from_hwnd(hwnd) } #[cfg(target_os = "linux")] fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64) { - self.0.show_context_menu_for_gtk_window(w, x, y) + self.0 + .borrow_mut() + .show_context_menu_for_gtk_window(w, x, y) } #[cfg(target_os = "linux")] fn gtk_context_menu(&self) -> gtk::Menu { - self.0.gtk_context_menu() + self.0.borrow_mut().gtk_context_menu() } #[cfg(target_os = "macos")] fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) { - self.0.show_context_menu_for_nsview(view, x, y) + self.0.borrow_mut().show_context_menu_for_nsview(view, x, y) } #[cfg(target_os = "macos")] fn ns_menu(&self) -> *mut std::ffi::c_void { - self.0.ns_menu() + self.0.borrow().ns_menu() } } diff --git a/src/lib.rs b/src/lib.rs index d92d16b..3b5fdfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ //! &PredefinedMenuItem::separator(), //! &menu_item2, //! ], -//! ), +//! ).unwrap(), //! ], //! ); //! ``` @@ -129,14 +129,10 @@ use crossbeam_channel::{unbounded, Receiver, Sender}; use once_cell::sync::{Lazy, OnceCell}; pub mod accelerator; -mod check_menu_item; mod error; -mod icon_menu_item; +mod items; mod menu; -mod menu_item; mod platform_impl; -mod predefined; -mod submenu; mod util; #[cfg(target_os = "macos")] @@ -144,14 +140,12 @@ mod util; extern crate objc; pub use self::error::*; -pub use check_menu_item::CheckMenuItem; -pub use icon_menu_item::IconMenuItem; +pub use items::*; pub use menu::Menu; pub mod icon; -pub use menu_item::MenuItem; -pub use predefined::{AboutMetadata, PredefinedMenuItem}; -pub use submenu::Submenu; +/// An enumeration of all available menu types, useful to match against +/// the items return from [`Menu::items`] or [`Submenu::items`] #[derive(Clone, Debug, PartialEq, Eq)] pub enum MenuItemType { Submenu, @@ -174,7 +168,7 @@ impl Default for MenuItemType { /// This trait is ONLY meant to be implemented internally. // TODO(amrbashir): first person to replace this trait with an enum while keeping `Menu.append_items` // taking mix of types (`MenuItem`, `CheckMenuItem`, `Submenu`...etc) in the same call, gets a cookie. -pub unsafe trait MenuItemExt { +pub unsafe trait IsMenuItem { /// Get the type of this menu entry fn type_(&self) -> MenuItemType; @@ -246,7 +240,7 @@ pub trait ContextMenu { } /// Describes a menu event emitted when a menu item is activated -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct MenuEvent { /// Id of the menu item which triggered this event pub id: u32, diff --git a/src/menu.rs b/src/menu.rs index 1373fe5..2af8f57 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{util::AddOp, ContextMenu, MenuItemExt}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{util::AddOp, ContextMenu, IsMenuItem}; /// 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(crate::platform_impl::Menu); +pub struct Menu(Rc>); impl Default for Menu { fn default() -> Self { @@ -18,14 +20,19 @@ impl Default for Menu { impl Menu { /// Creates a new menu. pub fn new() -> Self { - Self(crate::platform_impl::Menu::new()) + Self(Rc::new(RefCell::new(crate::platform_impl::Menu::new()))) } /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally. - pub fn with_items(items: &[&dyn MenuItemExt]) -> Self { + pub fn with_items(items: &[&dyn IsMenuItem]) -> crate::Result { let menu = Self::new(); - menu.append_items(items); - menu + menu.append_items(items)?; + Ok(menu) + } + + /// Returns a unique identifier associated with this menu. + pub fn id(&self) -> u32 { + self.0.borrow().id() } /// Add a menu item to the end of this menu. @@ -35,8 +42,8 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn append(&self, item: &dyn MenuItemExt) { - self.0.add_menu_item(item, AddOp::Append) + pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.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. @@ -46,10 +53,12 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn append_items(&self, items: &[&dyn MenuItemExt]) { + pub fn append_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { for item in items { - self.append(*item); + self.append(*item)? } + + Ok(()) } /// Add a menu item to the beginning of this menu. @@ -59,8 +68,8 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn prepend(&self, item: &dyn MenuItemExt) { - self.0.add_menu_item(item, AddOp::Insert(0)) + pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.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. @@ -70,8 +79,8 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn prepend_items(&self, items: &[&dyn MenuItemExt]) { - self.insert_items(items, 0); + pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> { + self.insert_items(items, 0) } /// Insert a menu item at the specified `postion` in the menu. @@ -81,8 +90,10 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn insert(&self, item: &dyn MenuItemExt, position: usize) { - self.0.add_menu_item(item, AddOp::Insert(position)) + pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> { + self.0 + .borrow_mut() + .add_menu_item(item, AddOp::Insert(position)) } /// Insert menu items at the specified `postion` in the menu. @@ -92,20 +103,22 @@ impl Menu { /// - **macOS:** Only [`Submenu`] can be added to the menu /// /// [`Submenu`]: crate::Submenu - pub fn insert_items(&self, items: &[&dyn MenuItemExt], position: usize) { + pub fn insert_items(&self, items: &[&dyn IsMenuItem], position: usize) -> crate::Result<()> { for (i, item) in items.iter().enumerate() { - self.insert(*item, position + i) + self.insert(*item, position + i)? } + + Ok(()) } /// Remove a menu item from this menu. - pub fn remove(&self, item: &dyn MenuItemExt) -> crate::Result<()> { - self.0.remove(item) + pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> { + self.0.borrow_mut().remove(item) } /// Returns a list of menu items that has been added to this menu. - pub fn items(&self) -> Vec> { - self.0.items() + pub fn items(&self) -> Vec> { + self.0.borrow().items() } /// Adds this menu to a [`gtk::ApplicationWindow`] @@ -128,7 +141,7 @@ impl Menu { W: gtk::prelude::IsA, W: gtk::prelude::IsA, { - self.0.init_for_gtk_window(w) + self.0.borrow_mut().init_for_gtk_window(w) } /// Adds this menu to a win32 window. @@ -157,7 +170,7 @@ impl Menu { /// ``` #[cfg(target_os = "windows")] pub fn init_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.init_for_hwnd(hwnd) + self.0.borrow_mut().init_for_hwnd(hwnd) } /// Returns The [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) associated with this menu @@ -165,7 +178,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.haccel() + self.0.borrow_mut().haccel() } /// Removes this menu from a [`gtk::ApplicationWindow`] @@ -175,13 +188,13 @@ impl Menu { W: gtk::prelude::IsA, W: gtk::prelude::IsA, { - self.0.remove_for_gtk_window(w) + self.0.borrow_mut().remove_for_gtk_window(w) } /// Removes this menu from a win32 window #[cfg(target_os = "windows")] pub fn remove_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.remove_for_hwnd(hwnd) + self.0.borrow_mut().remove_for_hwnd(hwnd) } /// Hides this menu from a [`gtk::ApplicationWindow`] @@ -190,13 +203,13 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.hide_for_gtk_window(w) + self.0.borrow_mut().hide_for_gtk_window(w) } /// Hides this menu from a win32 window #[cfg(target_os = "windows")] pub fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.hide_for_hwnd(hwnd) + self.0.borrow().hide_for_hwnd(hwnd) } /// Shows this menu on a [`gtk::ApplicationWindow`] @@ -205,66 +218,83 @@ impl Menu { where W: gtk::prelude::IsA, { - self.0.show_for_gtk_window(w) + self.0.borrow_mut().show_for_gtk_window(w) } /// Shows this menu on a win32 window #[cfg(target_os = "windows")] pub fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - self.0.show_for_hwnd(hwnd) + self.0.borrow().show_for_hwnd(hwnd) + } + + /// Returns whether this menu visible on a [`gtk::ApplicationWindow`] + #[cfg(target_os = "linux")] + pub fn is_visible_on_gtk_window(&self, w: &W) -> bool + where + W: gtk::prelude::IsA, + { + self.0.borrow().is_visible_on_gtk_window(w) + } + + /// 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) } /// Adds this menu to an NSApp. #[cfg(target_os = "macos")] pub fn init_for_nsapp(&self) { - self.0.init_for_nsapp() + self.0.borrow_mut().init_for_nsapp() } /// Removes this menu from an NSApp. #[cfg(target_os = "macos")] pub fn remove_for_nsapp(&self) { - self.0.remove_for_nsapp() + self.0.borrow_mut().remove_for_nsapp() } } impl ContextMenu for Menu { #[cfg(target_os = "windows")] fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU { - self.0.hpopupmenu() + self.0.borrow().hpopupmenu() } #[cfg(target_os = "windows")] fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) { - self.0.show_context_menu_for_hwnd(hwnd, x, y) + self.0.borrow().show_context_menu_for_hwnd(hwnd, x, y) } #[cfg(target_os = "windows")] fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { - self.0.attach_menu_subclass_for_hwnd(hwnd) + self.0.borrow().attach_menu_subclass_for_hwnd(hwnd) } #[cfg(target_os = "windows")] fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) { - self.0.detach_menu_subclass_from_hwnd(hwnd) + self.0.borrow().detach_menu_subclass_from_hwnd(hwnd) } #[cfg(target_os = "linux")] fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64) { - self.0.show_context_menu_for_gtk_window(w, x, y) + self.0 + .borrow_mut() + .show_context_menu_for_gtk_window(w, x, y) } #[cfg(target_os = "linux")] fn gtk_context_menu(&self) -> gtk::Menu { - self.0.gtk_context_menu() + self.0.borrow_mut().gtk_context_menu() } #[cfg(target_os = "macos")] fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) { - self.0.show_context_menu_for_nsview(view, x, y) + self.0.borrow_mut().show_context_menu_for_nsview(view, x, y) } #[cfg(target_os = "macos")] fn ns_menu(&self) -> *mut std::ffi::c_void { - self.0.ns_menu() + self.0.borrow().ns_menu() } } diff --git a/src/platform_impl/gtk/accelerator.rs b/src/platform_impl/gtk/accelerator.rs index db4c294..2082189 100644 --- a/src/platform_impl/gtk/accelerator.rs +++ b/src/platform_impl/gtk/accelerator.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use gtk::{prelude::*, AccelGroup}; use keyboard_types::{Code, Modifiers}; use crate::accelerator::Accelerator; @@ -78,7 +77,7 @@ pub fn parse_accelerator(accelerator: &Accelerator) -> crate::Result<(gdk::Modif if let Some(gdk_key) = key_to_raw_key(k) { *gdk_key } else { - return Err(crate::Error::AcceleratorKeyNotSupported(*k)); + return Err(crate::Error::UnrecognizedAcceleratorCode(k.to_string())); } } }; @@ -86,29 +85,6 @@ pub fn parse_accelerator(accelerator: &Accelerator) -> crate::Result<(gdk::Modif Ok((modifiers_to_gdk_modifier_type(accelerator.mods), key)) } -pub fn register_accelerator>( - item: &M, - accel_group: &AccelGroup, - accelerator: &Accelerator, -) -> Option<(gdk::ModifierType, u32)> { - if let Ok((mods, key)) = parse_accelerator(accelerator) { - item.add_accelerator("activate", accel_group, key, mods, gtk::AccelFlags::VISIBLE); - Some((mods, key)) - } else { - None - } -} - -pub fn remove_accelerator>( - item: &M, - accel_group: &AccelGroup, - accelerator: &Accelerator, -) { - if let Ok((mods, key)) = parse_accelerator(accelerator) { - item.remove_accelerator(accel_group, key, mods); - } -} - fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType { let mut result = gdk::ModifierType::empty(); diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index 01af751..e9112ad 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -10,13 +10,11 @@ pub(crate) use icon::PlatformIcon; use crate::{ accelerator::Accelerator, icon::Icon, - predefined::PredfinedMenuItemType, + items::*, util::{AddOp, Counter}, MenuEvent, MenuItemType, }; -use accelerator::{ - from_gtk_mnemonic, parse_accelerator, register_accelerator, remove_accelerator, to_gtk_mnemonic, -}; +use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic}; use gtk::{prelude::*, Orientation}; use std::{ cell::RefCell, @@ -29,7 +27,7 @@ static COUNTER: Counter = Counter::new(); macro_rules! return_if_predefined_item_not_supported { ($item:tt) => { - let child = $item.get_child(); + let child = $item.child(); let child_ = child.borrow(); match (&child_.type_, &child_.predefined_item_type) { ( @@ -48,184 +46,42 @@ macro_rules! return_if_predefined_item_not_supported { | crate::MenuItemType::Icon, _, ) => {} - _ => return, + _ => return Ok(()), } drop(child_); }; } -/// A generic child in a menu -/// -/// Be careful when cloning this item and treat it as read-only -#[derive(Debug, Default)] -#[allow(dead_code)] -struct MenuChild { - // shared fields between submenus and menu items - type_: MenuItemType, - text: String, - enabled: bool, +pub struct Menu { id: u32, - - gtk_menu_items: HashMap>, - - // menu item fields - accelerator: Option, - - // predefined menu item fields - predefined_item_type: PredfinedMenuItemType, - - // check menu item fields - checked: bool, - is_syncing_checked_state: Rc, - - // icon menu item fields - icon: Option, - - // submenu fields - children: Option>>>, - gtk_menus: HashMap>, - gtk_menu: (u32, Option), // dedicated menu for tray or context menus - accel_group: Option, -} - -impl MenuChild { - fn id(&self) -> u32 { - self.id - } - - fn text(&self) -> String { - match self - .gtk_menu_items - .values() - .collect::>() - .first() - .map(|v| v.first()) - .map(|e| e.map(|i| i.label().map(from_gtk_mnemonic))) - { - Some(Some(Some(text))) => text, - _ => self.text.clone(), - } - } - - fn set_text(&mut self, text: &str) { - self.text = text.to_string(); - let text = to_gtk_mnemonic(text); - for items in self.gtk_menu_items.values() { - for i in items { - i.set_label(&text); - } - } - } - - fn is_enabled(&self) -> bool { - match self - .gtk_menu_items - .values() - .collect::>() - .first() - .map(|v| v.first()) - .map(|e| e.map(|i| i.is_sensitive())) - { - Some(Some(enabled)) => enabled, - _ => self.enabled, - } - } - - fn set_enabled(&mut self, enabled: bool) { - self.enabled = enabled; - for items in self.gtk_menu_items.values() { - for i in items { - i.set_sensitive(enabled); - } - } - } - - fn is_checked(&self) -> bool { - match self - .gtk_menu_items - .values() - .collect::>() - .first() - .map(|v| v.first()) - .map(|e| e.map(|i| i.downcast_ref::().unwrap().is_active())) - { - Some(Some(checked)) => checked, - _ => self.checked, - } - } - - fn set_checked(&mut self, checked: bool) { - self.checked = checked; - self.is_syncing_checked_state.store(true, Ordering::Release); - for items in self.gtk_menu_items.values() { - for i in items { - i.downcast_ref::() - .unwrap() - .set_active(checked); - } - } - self.is_syncing_checked_state - .store(false, Ordering::Release); - } - - fn set_icon(&mut self, icon: Option) { - self.icon = icon.clone(); - - let pixbuf = icon.map(|i| i.inner.to_pixbuf_scale(16, 16)); - for items in self.gtk_menu_items.values() { - for i in items { - let box_container = i.child().unwrap().downcast::().unwrap(); - box_container.children()[0] - .downcast_ref::() - .unwrap() - .set_pixbuf(pixbuf.as_ref()) - } - } - } - - fn set_accelerator(&mut self, accelerator: Option) { - for items in self.gtk_menu_items.values() { - for i in items { - if let Some(accel) = self.accelerator { - remove_accelerator(i, self.accel_group.as_ref().unwrap(), &accel); - } - if let Some(accel) = accelerator.as_ref() { - register_accelerator(i, self.accel_group.as_ref().unwrap(), accel); - } - } - } - self.accelerator = accelerator; - } -} - -struct InnerMenu { children: Vec>>, gtk_menubars: HashMap, gtk::Box)>, accel_group: Option, gtk_menu: (u32, Option), // dedicated menu for tray or context menus } -#[derive(Clone)] -pub struct Menu(Rc>); - impl Menu { pub fn new() -> Self { - Self(Rc::new(RefCell::new(InnerMenu { + Self { + id: COUNTER.next(), children: Vec::new(), gtk_menubars: HashMap::new(), accel_group: None, gtk_menu: (COUNTER.next(), None), - }))) + } } - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { + pub fn id(&self) -> u32 { + self.id + } + + pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let mut self_ = self.0.borrow_mut(); - - for (menu_id, (menu_bar, _)) in &self_.gtk_menubars { + for (menu_id, (menu_bar, _)) in &self.gtk_menubars { if let Some(menu_bar) = menu_bar { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op { AddOp::Append => menu_bar.append(>k_item), AddOp::Insert(position) => menu_bar.insert(>k_item, position as i32), @@ -235,9 +91,10 @@ impl Menu { } { - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op { AddOp::Append => menu.append(>k_item), AddOp::Insert(position) => menu.insert(>k_item, position as i32), @@ -247,79 +104,87 @@ impl Menu { } match op { - AddOp::Append => self_.children.push(item.get_child()), - AddOp::Insert(position) => self_.children.insert(position, item.get_child()), + AddOp::Append => self.children.push(item.child()), + AddOp::Insert(position) => self.children.insert(position, item.child()), } + + Ok(()) } - fn add_menu_item_with_id(&self, item: &dyn crate::MenuItemExt, id: u32) { + fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let self_ = self.0.borrow(); - - for (menu_id, (menu_bar, _)) in self_.gtk_menubars.iter().filter(|m| *m.0 == id) { + for (menu_id, (menu_bar, _)) in self.gtk_menubars.iter().filter(|m| *m.0 == id) { if let Some(menu_bar) = menu_bar { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; menu_bar.append(>k_item); gtk_item.show(); } } + + Ok(()) } - fn add_menu_item_to_context_menu(&self, item: &dyn crate::MenuItemExt) { + fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let self_ = self.0.borrow(); - - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; menu.append(>k_item); gtk_item.show(); } + + Ok(()) } - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { + pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { self.remove_inner(item, true, None) } + pub fn remove_inner( - &self, - item: &dyn crate::MenuItemExt, + &mut self, + item: &dyn crate::IsMenuItem, remove_from_cache: bool, id: Option, ) -> crate::Result<()> { let child = { - let mut self_ = self.0.borrow_mut(); - let index = self_ + let index = self .children .iter() .position(|e| e.borrow().id == item.id()) .ok_or(crate::Error::NotAChildOfThisMenu)?; if remove_from_cache { - self_.children.remove(index) + self.children.remove(index) } else { - self_.children.get(index).cloned().unwrap() + self.children.get(index).cloned().unwrap() } }; if item.type_() == crate::MenuItemType::Submenu { let submenu = item.as_any().downcast_ref::().unwrap(); - let gtk_menus = submenu.0 .0.borrow().gtk_menus.clone(); + let gtk_menus = submenu.0.borrow().gtk_menus.clone(); for (menu_id, _) in gtk_menus { for item in submenu.items() { submenu .0 + .borrow_mut() .remove_inner(item.as_ref(), false, Some(menu_id))?; } } } - let self_ = self.0.borrow(); - for (menu_id, (menu_bar, _)) in &self_.gtk_menubars { + for (menu_id, (menu_bar, _)) in &self.gtk_menubars { if id.map(|i| i == *menu_id).unwrap_or(true) { if let Some(menu_bar) = menu_bar { - if let Some(items) = child.borrow_mut().gtk_menu_items.remove(menu_id) { + if let Some(items) = child + .borrow_mut() + .gtk_menu_items + .borrow_mut() + .remove(menu_id) + { for item in items { menu_bar.remove(&item); } @@ -329,9 +194,14 @@ impl Menu { } if remove_from_cache { - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - if let Some(items) = child.borrow_mut().gtk_menu_items.remove(menu_id) { + if let Some(items) = child + .borrow_mut() + .gtk_menu_items + .borrow_mut() + .remove(menu_id) + { for item in items { menu.remove(&item); } @@ -341,48 +211,34 @@ impl Menu { Ok(()) } - pub fn items(&self) -> Vec> { - self.0 - .borrow() - .children + pub fn items(&self) -> Vec> { + self.children .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) + .map(|c| c.borrow().boxed(c.clone())) .collect() } - pub fn init_for_gtk_window(&self, window: &W) -> crate::Result + pub fn init_for_gtk_window(&mut self, window: &W) -> crate::Result where W: IsA, W: IsA, W: IsA, { - let mut self_ = self.0.borrow_mut(); let id = window.as_ptr() as u32; - if self_.accel_group.is_none() { - self_.accel_group = Some(gtk::AccelGroup::new()); + if self.accel_group.is_none() { + self.accel_group = Some(gtk::AccelGroup::new()); } // This is the first time this method has been called on this window // so we need to create the menubar and its parent box - if self_.gtk_menubars.get(&id).is_none() { + if self.gtk_menubars.get(&id).is_none() { let menu_bar = gtk::MenuBar::new(); let vbox = gtk::Box::new(Orientation::Vertical, 0); window.add(&vbox); vbox.show(); - self_.gtk_menubars.insert(id, (Some(menu_bar), vbox)); - } else if let Some((menu_bar, _)) = self_.gtk_menubars.get_mut(&id) { + self.gtk_menubars.insert(id, (Some(menu_bar), vbox)); + } else if let Some((menu_bar, _)) = self.gtk_menubars.get_mut(&id) { // This is NOT the first time this method has been called on a window. // So it already contains a [`gtk::Box`] but it doesn't have a [`gtk::MenuBar`] // because it was probably removed using [`Menu::remove_for_gtk_window`] @@ -395,15 +251,13 @@ impl Menu { } // Construct the entries of the menubar - let (menu_bar, vbox) = self_.gtk_menubars.get(&id).cloned().unwrap(); + let (menu_bar, vbox) = self.gtk_menubars.get(&id).cloned().unwrap(); let menu_bar = menu_bar.as_ref().unwrap(); - window.add_accel_group(self_.accel_group.as_ref().unwrap()); - - drop(self_); + window.add_accel_group(self.accel_group.as_ref().unwrap()); for item in self.items() { - self.add_menu_item_with_id(item.as_ref(), id); + self.add_menu_item_with_id(item.as_ref(), id)?; } // Show the menubar on the window @@ -413,46 +267,40 @@ impl Menu { Ok(vbox) } - pub fn remove_for_gtk_window(&self, window: &W) -> crate::Result<()> + pub fn remove_for_gtk_window(&mut self, window: &W) -> crate::Result<()> where W: IsA, W: IsA, { let id = window.as_ptr() as u32; - let menu_bar = { - let mut self_ = self.0.borrow_mut(); - self_ - .gtk_menubars - .remove(&id) - .ok_or(crate::Error::NotInitialized)? - }; + let menu_bar = self + .gtk_menubars + .remove(&id) + .ok_or(crate::Error::NotInitialized)?; if let (Some(menu_bar), vbox) = menu_bar { for item in self.items() { let _ = self.remove_inner(item.as_ref(), false, Some(id)); } - let mut self_ = self.0.borrow_mut(); // Remove the [`gtk::Menubar`] from the widget tree unsafe { menu_bar.destroy() }; // Detach the accelerators from the window - window.remove_accel_group(self_.accel_group.as_ref().unwrap()); + window.remove_accel_group(self.accel_group.as_ref().unwrap()); // Remove the removed [`gtk::Menubar`] from our cache - self_.gtk_menubars.insert(id, (None, vbox)); + self.gtk_menubars.insert(id, (None, vbox)); Ok(()) } else { - self.0.borrow_mut().gtk_menubars.insert(id, menu_bar); + self.gtk_menubars.insert(id, menu_bar); Err(crate::Error::NotInitialized) } } - pub fn hide_for_gtk_window(&self, window: &W) -> crate::Result<()> + pub fn hide_for_gtk_window(&mut self, window: &W) -> crate::Result<()> where W: IsA, { - if let Some((Some(menu_bar), _)) = - self.0.borrow().gtk_menubars.get(&(window.as_ptr() as u32)) - { + if let Some((Some(menu_bar), _)) = self.gtk_menubars.get(&(window.as_ptr() as u32)) { menu_bar.hide(); Ok(()) } else { @@ -464,9 +312,7 @@ impl Menu { where W: IsA, { - if let Some((Some(menu_bar), _)) = - self.0.borrow().gtk_menubars.get(&(window.as_ptr() as u32)) - { + if let Some((Some(menu_bar), _)) = self.gtk_menubars.get(&(window.as_ptr() as u32)) { menu_bar.show_all(); Ok(()) } else { @@ -474,12 +320,22 @@ impl Menu { } } + pub fn is_visible_on_gtk_window(&self, window: &W) -> bool + where + W: IsA, + { + self.gtk_menubars + .get(&(window.as_ptr() as u32)) + .map(|m| m.0.as_ref().map(|m| m.get_visible()).unwrap_or(false)) + .unwrap_or(false) + } + pub fn show_context_menu_for_gtk_window(&self, window: &impl IsA, x: f64, y: f64) { if let Some(window) = window.window() { let gtk_menu = gtk::Menu::new(); for item in self.items() { - let gtk_item = item.make_gtk_menu_item(0, None, false); + let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap(); gtk_menu.append(>k_item); } gtk_menu.show_all(); @@ -494,55 +350,280 @@ impl Menu { } } - pub fn gtk_context_menu(&self) -> gtk::Menu { + pub fn gtk_context_menu(&mut self) -> gtk::Menu { let mut add_items = false; { - let mut self_ = self.0.borrow_mut(); - if self_.gtk_menu.1.is_none() { - self_.gtk_menu.1 = Some(gtk::Menu::new()); + if self.gtk_menu.1.is_none() { + self.gtk_menu.1 = Some(gtk::Menu::new()); add_items = true; } } if add_items { for item in self.items() { - self.add_menu_item_to_context_menu(item.as_ref()); + self.add_menu_item_to_context_menu(item.as_ref()).unwrap(); } } - self.0.borrow().gtk_menu.1.as_ref().unwrap().clone() + self.gtk_menu.1.as_ref().unwrap().clone() } } -#[derive(Clone)] -pub struct Submenu(Rc>); +/// A generic child in a menu +#[derive(Debug, Default)] +pub struct MenuChild { + // shared fields between submenus and menu items + pub type_: MenuItemType, + text: String, + enabled: bool, + id: u32, -impl Submenu { - pub fn new(text: &str, enabled: bool) -> Self { - let child = Rc::new(RefCell::new(MenuChild { + gtk_menu_items: Rc>>>, + + // menu item fields + accelerator: Option, + gtk_accelerator: Option<(gdk::ModifierType, u32)>, + + // predefined menu item fields + predefined_item_type: PredfinedMenuItemType, + + // check menu item fields + checked: Rc, + is_syncing_checked_state: Rc, + + // icon menu item fields + icon: Option, + + // submenu fields + pub children: Option>>>, + gtk_menus: HashMap>, + gtk_menu: (u32, Option), // dedicated menu for tray or context menus + accel_group: Option, +} + +/// Constructors +impl MenuChild { + pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + Self { + text: text.to_string(), + enabled, + accelerator, + id: COUNTER.next(), + type_: MenuItemType::Normal, + gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), + ..Default::default() + } + } + + pub fn new_submenu(text: &str, enabled: bool) -> Self { + Self { text: text.to_string(), enabled, id: COUNTER.next(), children: Some(Vec::new()), type_: MenuItemType::Submenu, gtk_menu: (COUNTER.next(), None), - gtk_menu_items: HashMap::new(), + gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menus: HashMap::new(), ..Default::default() - })); - - Self(child) + } } - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { + pub(crate) fn new_predefined(item_type: PredfinedMenuItemType, text: Option) -> Self { + Self { + text: text.unwrap_or_else(|| item_type.text().to_string()), + enabled: true, + accelerator: item_type.accelerator(), + id: COUNTER.next(), + type_: MenuItemType::Predefined, + predefined_item_type: item_type, + gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), + ..Default::default() + } + } + + pub fn new_check( + text: &str, + enabled: bool, + checked: bool, + accelerator: Option, + ) -> Self { + Self { + text: text.to_string(), + enabled, + checked: Rc::new(AtomicBool::new(checked)), + accelerator, + id: COUNTER.next(), + type_: MenuItemType::Check, + gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), + is_syncing_checked_state: Rc::new(AtomicBool::new(false)), + ..Default::default() + } + } + + pub fn new_icon( + text: &str, + enabled: bool, + icon: Option, + accelerator: Option, + ) -> Self { + Self { + text: text.to_string(), + enabled, + icon, + accelerator, + id: COUNTER.next(), + type_: MenuItemType::Icon, + gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), + is_syncing_checked_state: Rc::new(AtomicBool::new(false)), + ..Default::default() + } + } +} + +/// Shared methods +impl MenuChild { + pub fn id(&self) -> u32 { + self.id + } + + pub fn text(&self) -> String { + match self + .gtk_menu_items + .borrow() + .values() + .collect::>() + .first() + .map(|v| v.first()) + .map(|e| e.map(|i| i.label().map(from_gtk_mnemonic))) + { + Some(Some(Some(text))) => text, + _ => self.text.clone(), + } + } + + pub fn set_text(&mut self, text: &str) { + self.text = text.to_string(); + let text = to_gtk_mnemonic(text); + for items in self.gtk_menu_items.borrow().values() { + for i in items { + i.set_label(&text); + } + } + } + + pub fn is_enabled(&self) -> bool { + match self + .gtk_menu_items + .borrow() + .values() + .collect::>() + .first() + .map(|v| v.first()) + .map(|e| e.map(|i| i.is_sensitive())) + { + Some(Some(enabled)) => enabled, + _ => self.enabled, + } + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + for items in self.gtk_menu_items.borrow().values() { + for i in items { + i.set_sensitive(enabled); + } + } + } + + pub fn set_accelerator(&mut self, accelerator: Option) -> crate::Result<()> { + let prev_accel = self.gtk_accelerator.as_ref(); + let new_accel = accelerator.as_ref().map(parse_accelerator).transpose()?; + + for items in self.gtk_menu_items.borrow().values() { + for i in items { + if let Some((mods, key)) = prev_accel { + i.remove_accelerator(self.accel_group.as_ref().unwrap(), *key, *mods); + } + if let Some((mods, key)) = new_accel { + i.add_accelerator( + "activate", + self.accel_group.as_ref().unwrap(), + key, + mods, + gtk::AccelFlags::VISIBLE, + ) + } + } + } + + self.gtk_accelerator = new_accel; + self.accelerator = accelerator; + + Ok(()) + } +} + +/// CheckMenuItem methods +impl MenuChild { + pub fn is_checked(&self) -> bool { + match self + .gtk_menu_items + .borrow() + .values() + .collect::>() + .first() + .map(|v| v.first()) + .map(|e| e.map(|i| i.downcast_ref::().unwrap().is_active())) + { + Some(Some(checked)) => checked, + _ => self.checked.load(Ordering::Relaxed), + } + } + + pub fn set_checked(&mut self, checked: bool) { + self.checked.store(checked, Ordering::Release); + self.is_syncing_checked_state.store(true, Ordering::Release); + for items in self.gtk_menu_items.borrow().values() { + for i in items { + i.downcast_ref::() + .unwrap() + .set_active(checked); + } + } + self.is_syncing_checked_state + .store(false, Ordering::Release); + } +} + +/// IconMenuItem methods +impl MenuChild { + pub fn set_icon(&mut self, icon: Option) { + self.icon = icon.clone(); + + let pixbuf = icon.map(|i| i.inner.to_pixbuf_scale(16, 16)); + for items in self.gtk_menu_items.borrow().values() { + for i in items { + let box_container = i.child().unwrap().downcast::().unwrap(); + box_container.children()[0] + .downcast_ref::() + .unwrap() + .set_pixbuf(pixbuf.as_ref()) + } + } + } +} + +/// Submenu methods +impl MenuChild { + pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let mut self_ = self.0.borrow_mut(); - - for menus in self_.gtk_menus.values() { + for menus in self.gtk_menus.values() { for (menu_id, menu) in menus { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op { AddOp::Append => menu.append(>k_item), AddOp::Insert(position) => menu.insert(>k_item, position as i32), @@ -552,9 +633,10 @@ impl Submenu { } { - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op { AddOp::Append => menu.append(>k_item), AddOp::Insert(position) => menu.insert(>k_item, position as i32), @@ -564,55 +646,57 @@ impl Submenu { } match op { - AddOp::Append => self_.children.as_mut().unwrap().push(item.get_child()), - AddOp::Insert(position) => self_ + AddOp::Append => self.children.as_mut().unwrap().push(item.child()), + AddOp::Insert(position) => self .children .as_mut() .unwrap() - .insert(position, item.get_child()), + .insert(position, item.child()), } + + Ok(()) } - fn add_menu_item_with_id(&self, item: &dyn crate::MenuItemExt, id: u32) { + fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let self_ = self.0.borrow(); - - for menus in self_.gtk_menus.values() { + for menus in self.gtk_menus.values() { for (menu_id, menu) in menus.iter().filter(|m| m.0 == id) { - let gtk_item = item.make_gtk_menu_item(*menu_id, self_.accel_group.as_ref(), true); + let gtk_item = + item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; menu.append(>k_item); gtk_item.show(); } } + + Ok(()) } - fn add_menu_item_to_context_menu(&self, item: &dyn crate::MenuItemExt) { + fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { return_if_predefined_item_not_supported!(item); - let self_ = self.0.borrow(); - - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - let gtk_item = item.make_gtk_menu_item(*menu_id, None, true); + let gtk_item = item.make_gtk_menu_item(*menu_id, None, true)?; menu.append(>k_item); gtk_item.show(); } + + Ok(()) } - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { + pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { self.remove_inner(item, true, None) } fn remove_inner( - &self, - item: &dyn crate::MenuItemExt, + &mut self, + item: &dyn crate::IsMenuItem, remove_from_cache: bool, id: Option, ) -> crate::Result<()> { let child = { - let mut self_ = self.0.borrow_mut(); - let index = self_ + let index = self .children .as_ref() .unwrap() @@ -620,36 +704,35 @@ impl Submenu { .position(|e| e.borrow().id == item.id()) .ok_or(crate::Error::NotAChildOfThisMenu)?; if remove_from_cache { - self_.children.as_mut().unwrap().remove(index) + self.children.as_mut().unwrap().remove(index) } else { - self_ - .children - .as_ref() - .unwrap() - .get(index) - .cloned() - .unwrap() + self.children.as_ref().unwrap().get(index).cloned().unwrap() } }; if item.type_() == crate::MenuItemType::Submenu { let submenu = item.as_any().downcast_ref::().unwrap(); - let gtk_menus = submenu.0 .0.borrow().gtk_menus.clone(); + let gtk_menus = submenu.0.borrow().gtk_menus.clone(); for (menu_id, _) in gtk_menus { for item in submenu.items() { submenu .0 + .borrow_mut() .remove_inner(item.as_ref(), false, Some(menu_id))?; } } } - let self_ = self.0.borrow(); - for menus in self_.gtk_menus.values() { + for menus in self.gtk_menus.values() { for (menu_id, menu) in menus { if id.map(|i| i == *menu_id).unwrap_or(true) { - if let Some(items) = child.borrow_mut().gtk_menu_items.remove(menu_id) { + if let Some(items) = child + .borrow_mut() + .gtk_menu_items + .borrow_mut() + .remove(menu_id) + { for item in items { menu.remove(&item); } @@ -659,9 +742,14 @@ impl Submenu { } if remove_from_cache { - let (menu_id, menu) = &self_.gtk_menu; + let (menu_id, menu) = &self.gtk_menu; if let Some(menu) = menu { - if let Some(items) = child.borrow_mut().gtk_menu_items.remove(menu_id) { + if let Some(items) = child + .borrow_mut() + .gtk_menu_items + .borrow_mut() + .remove(menu_id) + { for item in items { menu.remove(&item); } @@ -672,25 +760,12 @@ impl Submenu { Ok(()) } - pub fn items(&self) -> Vec> { - self.0 - .borrow() - .children + pub fn items(&self) -> Vec> { + self.children .as_ref() .unwrap() .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) + .map(|c| c.borrow().boxed(c.clone())) .collect() } @@ -699,7 +774,7 @@ impl Submenu { let gtk_menu = gtk::Menu::new(); for item in self.items() { - let gtk_item = item.make_gtk_menu_item(0, None, false); + let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap(); gtk_menu.append(>k_item); } gtk_menu.show_all(); @@ -714,204 +789,140 @@ impl Submenu { } } - pub fn gtk_context_menu(&self) -> gtk::Menu { + pub fn gtk_context_menu(&mut self) -> gtk::Menu { let mut add_items = false; { - let mut self_ = self.0.borrow_mut(); - if self_.gtk_menu.1.is_none() { - self_.gtk_menu.1 = Some(gtk::Menu::new()); + if self.gtk_menu.1.is_none() { + self.gtk_menu.1 = Some(gtk::Menu::new()); add_items = true; } } if add_items { for item in self.items() { - self.add_menu_item_to_context_menu(item.as_ref()); + self.add_menu_item_to_context_menu(item.as_ref()).unwrap(); } } - self.0.borrow().gtk_menu.1.as_ref().unwrap().clone() + self.gtk_menu.1.as_ref().unwrap().clone() } +} - fn make_gtk_menu_item( - &self, +macro_rules! register_accel { + ($self:ident, $item:ident, $accel_group:ident) => { + $self.gtk_accelerator = $self + .accelerator + .as_ref() + .map(parse_accelerator) + .transpose()?; + + if let Some((mods, key)) = &$self.gtk_accelerator { + if let Some(accel_group) = $accel_group { + $item.add_accelerator( + "activate", + accel_group, + *key, + *mods, + gtk::AccelFlags::VISIBLE, + ) + } + } + }; +} + +/// Gtk menu item creation methods +impl MenuChild { + fn create_gtk_item_for_submenu( + &mut self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { - let mut self_ = self.0.borrow_mut(); + ) -> crate::Result { let submenu = gtk::Menu::new(); let item = gtk::MenuItem::builder() - .label(&to_gtk_mnemonic(&self_.text)) + .label(&to_gtk_mnemonic(&self.text)) .use_underline(true) .submenu(&submenu) - .sensitive(self_.enabled) + .sensitive(self.enabled) .build(); item.show(); item.set_submenu(Some(&submenu)); - self_.accel_group = accel_group.cloned(); + self.accel_group = accel_group.cloned(); let mut id = 0; if add_to_cache { id = COUNTER.next(); - self_ - .gtk_menu_items + self.gtk_menu_items + .borrow_mut() .entry(menu_id) .or_insert_with(Vec::new) .push(item.clone()); - self_ - .gtk_menus + self.gtk_menus .entry(menu_id) .or_insert_with(Vec::new) .push((id, submenu.clone())); } - drop(self_); - for item in self.items() { if add_to_cache { - self.add_menu_item_with_id(item.as_ref(), id); + self.add_menu_item_with_id(item.as_ref(), id)?; } else { - let gtk_item = item.make_gtk_menu_item(0, None, false); + let gtk_item = item.make_gtk_menu_item(0, None, false)?; submenu.append(>k_item); } } - item + Ok(item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } -} - -#[derive(Clone)] -pub struct MenuItem(Rc>); - -impl MenuItem { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { - let child = Rc::new(RefCell::new(MenuChild { - text: text.to_string(), - enabled, - accelerator, - id: COUNTER.next(), - type_: MenuItemType::Normal, - gtk_menu_items: HashMap::new(), - ..Default::default() - })); - - Self(child) - } - - fn make_gtk_menu_item( - &self, + fn create_gtk_item_for_menu_item( + &mut self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { - let mut self_ = self.0.borrow_mut(); + ) -> crate::Result { let item = gtk::MenuItem::builder() - .label(&to_gtk_mnemonic(&self_.text)) + .label(&to_gtk_mnemonic(&self.text)) .use_underline(true) - .sensitive(self_.enabled) + .sensitive(self.enabled) .build(); - self_.accel_group = accel_group.cloned(); + self.accel_group = accel_group.cloned(); - if let Some(accelerator) = &self_.accelerator { - if let Some(accel_group) = accel_group { - register_accelerator(&item, accel_group, accelerator); - } - } + register_accel!(self, item, accel_group); - let id = self_.id; + let id = self.id; item.connect_activate(move |_| { MenuEvent::send(crate::MenuEvent { id }); }); if add_to_cache { - self_ - .gtk_menu_items + self.gtk_menu_items + .borrow_mut() .entry(menu_id) .or_insert_with(Vec::new) .push(item.clone()); } - item + Ok(item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) - } -} - -#[derive(Clone)] -pub struct PredefinedMenuItem(Rc>); - -impl PredefinedMenuItem { - pub(crate) fn new(item: PredfinedMenuItemType, text: Option) -> Self { - let child = Rc::new(RefCell::new(MenuChild { - text: text.unwrap_or_else(|| item.text().to_string()), - enabled: true, - accelerator: item.accelerator(), - id: COUNTER.next(), - type_: MenuItemType::Predefined, - predefined_item_type: item, - gtk_menu_items: HashMap::new(), - ..Default::default() - })); - - Self(child) - } - - fn make_gtk_menu_item( - &self, + fn create_gtk_item_for_predefined_menu_item( + &mut self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { - let self_ = self.0.borrow(); - let text = self_.text.clone(); - let accelerator = self_.accelerator; - let predefined_item_type = self_.predefined_item_type.clone(); - drop(self_); + ) -> crate::Result { + let text = self.text.clone(); + self.gtk_accelerator = self + .accelerator + .as_ref() + .map(parse_accelerator) + .transpose()?; + let predefined_item_type = self.predefined_item_type.clone(); let make_item = || { gtk::MenuItem::builder() @@ -921,9 +932,15 @@ impl PredefinedMenuItem { .build() }; let register_accel = |item: >k::MenuItem| { - if let Some(accelerator) = accelerator { + if let Some((mods, key)) = &self.gtk_accelerator { if let Some(accel_group) = accel_group { - register_accelerator(item, accel_group, &accelerator); + item.add_accelerator( + "activate", + accel_group, + *key, + *mods, + gtk::AccelFlags::VISIBLE, + ) } } }; @@ -1003,98 +1020,54 @@ impl PredefinedMenuItem { }; if add_to_cache { - let mut self_ = self.0.borrow_mut(); - self_ - .gtk_menu_items + self.gtk_menu_items + .borrow_mut() .entry(menu_id) .or_insert_with(Vec::new) .push(item.clone()); } - item + Ok(item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } -} - -#[derive(Clone)] -pub struct CheckMenuItem(Rc>); - -impl CheckMenuItem { - pub fn new(text: &str, enabled: bool, checked: bool, accelerator: Option) -> Self { - let child = Rc::new(RefCell::new(MenuChild { - text: text.to_string(), - enabled, - checked, - accelerator, - id: COUNTER.next(), - type_: MenuItemType::Check, - gtk_menu_items: HashMap::new(), - is_syncing_checked_state: Rc::new(AtomicBool::new(false)), - ..Default::default() - })); - - Self(child) - } - - fn make_gtk_menu_item( - &self, + fn create_gtk_item_for_check_menu_item( + &mut self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { - let mut self_ = self.0.borrow_mut(); + ) -> crate::Result { let item = gtk::CheckMenuItem::builder() - .label(&to_gtk_mnemonic(&self_.text)) + .label(&to_gtk_mnemonic(&self.text)) .use_underline(true) - .sensitive(self_.enabled) - .active(self_.checked) + .sensitive(self.enabled) + .active(self.checked.load(Ordering::Relaxed)) .build(); - self_.accel_group = accel_group.cloned(); + self.accel_group = accel_group.cloned(); - if let Some(accelerator) = &self_.accelerator { - if let Some(accel_group) = accel_group { - register_accelerator(&item, accel_group, accelerator); - } - } + register_accel!(self, item, accel_group); - let id = self_.id; - let self_c = self.0.clone(); - let is_syncing_checked_state = self_.is_syncing_checked_state.clone(); + let id = self.id; + let is_syncing_checked_state = self.is_syncing_checked_state.clone(); + let checked = self.checked.clone(); + let store = self.gtk_menu_items.clone(); item.connect_toggled(move |i| { let should_dispatch = is_syncing_checked_state .compare_exchange(false, true, Ordering::Release, Ordering::Relaxed) .is_ok(); if should_dispatch { - let checked = i.is_active(); - let (is_syncing_checked_state_c, store) = { - let mut self_ = self_c.borrow_mut(); - self_.checked = checked; - ( - Rc::clone(&self_.is_syncing_checked_state), - self_.gtk_menu_items.clone(), - ) - }; + let c = i.is_active(); + checked.store(c, Ordering::Release); - for items in store.values() { + for items in store.borrow().values() { for i in items { i.downcast_ref::() .unwrap() - .set_active(checked); + .set_active(c); } } - is_syncing_checked_state_c.store(false, Ordering::Release); + is_syncing_checked_state.store(false, Ordering::Release); MenuEvent::send(crate::MenuEvent { id }); } @@ -1103,91 +1076,32 @@ impl CheckMenuItem { let item = item.upcast::(); if add_to_cache { - self_ - .gtk_menu_items + self.gtk_menu_items + .borrow_mut() .entry(menu_id) .or_insert_with(Vec::new) .push(item.clone()); } - item + Ok(item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn is_checked(&self) -> bool { - self.0.borrow().is_checked() - } - - pub fn set_checked(&self, checked: bool) { - self.0.borrow_mut().set_checked(checked) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) - } -} - -#[derive(Clone)] -pub struct IconMenuItem(Rc>); - -impl IconMenuItem { - pub fn new( - text: &str, - enabled: bool, - icon: Option, - accelerator: Option, - ) -> Self { - let child = Rc::new(RefCell::new(MenuChild { - text: text.to_string(), - enabled, - icon, - accelerator, - id: COUNTER.next(), - type_: MenuItemType::Icon, - gtk_menu_items: HashMap::new(), - is_syncing_checked_state: Rc::new(AtomicBool::new(false)), - ..Default::default() - })); - - Self(child) - } - - fn make_gtk_menu_item( - &self, + fn create_gtk_item_for_icon_menu_item( + &mut self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { - let mut self_ = self.0.borrow_mut(); - - let image = self_ + ) -> crate::Result { + let image = self .icon .as_ref() .map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf_scale(16, 16)))) .unwrap_or_else(gtk::Image::default); - self_.accel_group = accel_group.cloned(); + self.accel_group = accel_group.cloned(); let label = gtk::AccelLabel::builder() - .label(&to_gtk_mnemonic(&self_.text)) + .label(&to_gtk_mnemonic(&self.text)) .use_underline(true) .xalign(0.0) .build(); @@ -1208,140 +1122,71 @@ impl IconMenuItem { let item = gtk::MenuItem::builder() .child(&box_container) - .sensitive(self_.enabled) + .sensitive(self.enabled) .build(); - if let Some(accelerator) = &self_.accelerator { - if let Some(accel_group) = accel_group { - if let Some((mods, key)) = register_accelerator(&item, accel_group, accelerator) { - label.set_accel(key, mods); - } - } - } + register_accel!(self, item, accel_group); - let id = self_.id; + let id = self.id; item.connect_activate(move |_| { MenuEvent::send(crate::MenuEvent { id }); }); if add_to_cache { - self_ - .gtk_menu_items + self.gtk_menu_items + .borrow_mut() .entry(menu_id) .or_insert_with(Vec::new) .push(item.clone()); } - item - } - - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_icon(&self, icon: Option) { - self.0.borrow_mut().set_icon(icon) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) + Ok(item) } } -impl dyn crate::MenuItemExt + '_ { - fn get_child(&self) -> Rc> { - match self.type_() { - MenuItemType::Submenu => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .0 - .clone(), - MenuItemType::Normal => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .0 - .clone(), - - MenuItemType::Predefined => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .0 - .clone(), - MenuItemType::Check => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .0 - .clone(), - MenuItemType::Icon => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .0 - .clone(), - } - } - +impl dyn crate::IsMenuItem + '_ { fn make_gtk_menu_item( &self, menu_id: u32, accel_group: Option<>k::AccelGroup>, add_to_cache: bool, - ) -> gtk::MenuItem { + ) -> crate::Result { match self.type_() { MenuItemType::Submenu => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .make_gtk_menu_item(menu_id, accel_group, add_to_cache), + .borrow_mut() + .create_gtk_item_for_submenu(menu_id, accel_group, add_to_cache), MenuItemType::Normal => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .make_gtk_menu_item(menu_id, accel_group, add_to_cache), + .borrow_mut() + .create_gtk_item_for_menu_item(menu_id, accel_group, add_to_cache), MenuItemType::Predefined => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .make_gtk_menu_item(menu_id, accel_group, add_to_cache), + .borrow_mut() + .create_gtk_item_for_predefined_menu_item(menu_id, accel_group, add_to_cache), MenuItemType::Check => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .make_gtk_menu_item(menu_id, accel_group, add_to_cache), + .borrow_mut() + .create_gtk_item_for_check_menu_item(menu_id, accel_group, add_to_cache), MenuItemType::Icon => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .make_gtk_menu_item(menu_id, accel_group, add_to_cache), + .borrow_mut() + .create_gtk_item_for_icon_menu_item(menu_id, accel_group, add_to_cache), } } } @@ -1353,7 +1198,7 @@ impl PredfinedMenuItemType { PredfinedMenuItemType::Cut => "ctrl+X", PredfinedMenuItemType::Paste => "ctrl+v", PredfinedMenuItemType::SelectAll => "ctrl+a", - _ => "", + _ => unreachable!(), } } } diff --git a/src/platform_impl/macos/accelerator.rs b/src/platform_impl/macos/accelerator.rs index 8b28e33..42317c4 100644 --- a/src/platform_impl/macos/accelerator.rs +++ b/src/platform_impl/macos/accelerator.rs @@ -11,8 +11,8 @@ impl Accelerator { /// Return the string value of this hotkey, without modifiers. /// /// Returns the empty string if no key equivalent is known. - pub fn key_equivalent(self) -> String { - match self.key { + pub fn key_equivalent(self) -> crate::Result { + Ok(match self.key { Code::KeyA => "a".into(), Code::KeyB => "b".into(), Code::KeyC => "c".into(), @@ -103,12 +103,8 @@ impl Accelerator { Code::F22 => "\u{F719}".into(), Code::F23 => "\u{F71A}".into(), Code::F24 => "\u{F71B}".into(), - _ => { - #[cfg(debug_assertions)] - eprintln!("no key equivalent for {:?}", self); - "".into() - } - } + key => return Err(crate::Error::UnrecognizedAcceleratorCode(key.to_string())), + }) } /// Return the modifiers of this hotkey, as an NSEventModifierFlags bitflag. diff --git a/src/platform_impl/macos/icon.rs b/src/platform_impl/macos/icon.rs index 8b70b8f..deb14bd 100644 --- a/src/platform_impl/macos/icon.rs +++ b/src/platform_impl/macos/icon.rs @@ -43,18 +43,15 @@ impl PlatformIcon { let (width, height) = self.get_size(); let icon = self.to_png(); - let (icon_height, icon_width) = match fixed_height { + let (icon_width, icon_height) = match fixed_height { Some(fixed_height) => { let icon_height: f64 = fixed_height; let icon_width: f64 = (width as f64) / (height as f64 / icon_height); - (icon_height, icon_width) + (icon_width, icon_height) } - None => { - let (icon_height, icon_width) = self.get_size(); - (icon_height as f64, icon_width as f64) - } + None => (width as f64, height as f64), }; let nsdata = NSData::dataWithBytes_length_( diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index fff574c..4e95aa5 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -11,9 +11,11 @@ pub(crate) use icon::PlatformIcon; use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Once}; use cocoa::{ - appkit::{CGFloat, NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}, + appkit::{self, CGFloat, NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}, base::{id, nil, selector, NO, YES}, - foundation::{NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSString}, + foundation::{ + NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, + }, }; use objc::{ declare::ClassDecl, @@ -23,10 +25,10 @@ use objc::{ use self::util::{app_name_string, strip_mnemonic}; use crate::{ accelerator::Accelerator, - icon::Icon, - predefined::PredfinedMenuItemType, + icon::{Icon, NativeIcon}, + items::*, util::{AddOp, Counter}, - MenuEvent, MenuItemExt, MenuItemType, + IsMenuItem, MenuEvent, MenuItemType, }; static COUNTER: Counter = Counter::new(); @@ -45,14 +47,133 @@ extern "C" { #[allow(non_upper_case_globals)] const NSAboutPanelOptionCopyright: &str = "Copyright"; +#[derive(Clone, Debug)] +pub struct Menu { + id: u32, + ns_menu: id, + children: Rc>>>>, +} + +impl Menu { + pub fn new() -> Self { + Self { + id: COUNTER.next(), + ns_menu: unsafe { + let ns_menu = NSMenu::alloc(nil).autorelease(); + ns_menu.setAutoenablesItems(NO); + ns_menu + }, + children: Rc::new(RefCell::new(Vec::new())), + } + } + + pub fn id(&self) -> u32 { + 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 child = item.child(); + + unsafe { + match op { + AddOp::Append => { + self.ns_menu.addItem_(ns_menu_item); + self.children.borrow_mut().push(child); + } + AddOp::Insert(position) => { + let () = msg_send![self.ns_menu, insertItem: ns_menu_item atIndex: position as NSInteger]; + self.children.borrow_mut().insert(position, child); + } + } + } + + Ok(()) + } + + pub fn remove(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { + // get a list of instances of the specified NSMenuItem in this menu + if let Some(ns_menu_items) = match item.type_() { + MenuItemType::Submenu => { + let submenu = item.as_any().downcast_ref::().unwrap(); + submenu.0.borrow_mut() + } + MenuItemType::Normal => { + let menuitem = item.as_any().downcast_ref::().unwrap(); + menuitem.0.borrow_mut() + } + MenuItemType::Predefined => { + let menuitem = item.as_any().downcast_ref::().unwrap(); + menuitem.0.borrow_mut() + } + MenuItemType::Check => { + let menuitem = item.as_any().downcast_ref::().unwrap(); + menuitem.0.borrow_mut() + } + MenuItemType::Icon => { + let menuitem = item.as_any().downcast_ref::().unwrap(); + menuitem.0.borrow_mut() + } + } + .ns_menu_items + .remove(&self.id) + { + // remove each NSMenuItem from the NSMenu + unsafe { + for item in ns_menu_items { + let () = msg_send![self.ns_menu, removeItem: item]; + } + } + } + + // remove the item from our internal list of children + let mut children = self.children.borrow_mut(); + let index = children + .iter() + .position(|e| e.borrow().id() == item.id()) + .ok_or(crate::Error::NotAChildOfThisMenu)?; + children.remove(index); + + Ok(()) + } + + pub fn items(&self) -> Vec> { + self.children + .borrow() + .iter() + .map(|c| c.borrow().boxed(c.clone())) + .collect() + } + + pub fn init_for_nsapp(&self) { + unsafe { NSApp().setMainMenu_(self.ns_menu) } + } + + pub fn remove_for_nsapp(&self) { + unsafe { NSApp().setMainMenu_(nil) } + } + + pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) { + unsafe { + let window: id = msg_send![view, window]; + let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; + let view_point = NSPoint::new(x / scale_factor, y / scale_factor); + let view_rect: NSRect = msg_send![view, frame]; + let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y); + msg_send![self.ns_menu, popUpMenuPositioningItem: nil atLocation: location inView: view] + } + } + + pub fn ns_menu(&self) -> *mut std::ffi::c_void { + self.ns_menu as _ + } +} + /// A generic child in a menu -/// -/// Be careful when cloning this item and treat it as read-only #[derive(Debug)] -#[allow(dead_code)] -struct MenuChild { +pub struct MenuChild { // shared fields between submenus and menu items - type_: MenuItemType, + pub type_: MenuItemType, id: u32, text: String, enabled: bool, @@ -70,9 +191,10 @@ struct MenuChild { // icon menu item fields icon: Option, + native_icon: Option, // submenu fields - children: Option>>>, + pub children: Option>>>, ns_menus: HashMap>, ns_menu: (u32, id), } @@ -89,6 +211,7 @@ impl Default for MenuChild { predefined_item_type: Default::default(), checked: Default::default(), icon: Default::default(), + native_icon: Default::default(), children: Default::default(), ns_menus: Default::default(), ns_menu: (0, 0 as _), @@ -96,6 +219,101 @@ impl Default for MenuChild { } } +/// Constructors +impl MenuChild { + pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + Self { + type_: MenuItemType::Normal, + text: strip_mnemonic(text), + enabled, + id: COUNTER.next(), + accelerator, + ..Default::default() + } + } + + pub fn new_submenu(text: &str, enabled: bool) -> Self { + Self { + type_: MenuItemType::Submenu, + text: strip_mnemonic(text), + enabled, + children: Some(Vec::new()), + ns_menu: (COUNTER.next(), unsafe { NSMenu::alloc(nil).autorelease() }), + ..Default::default() + } + } + + pub(crate) fn new_predefined(item_type: PredfinedMenuItemType, text: Option) -> Self { + let text = strip_mnemonic(text.unwrap_or_else(|| { + match item_type { + PredfinedMenuItemType::About(_) => { + format!("About {}", unsafe { app_name_string() }.unwrap_or_default()) + .trim() + .to_string() + } + PredfinedMenuItemType::Hide => { + format!("Hide {}", unsafe { app_name_string() }.unwrap_or_default()) + .trim() + .to_string() + } + PredfinedMenuItemType::Quit => { + format!("Quit {}", unsafe { app_name_string() }.unwrap_or_default()) + .trim() + .to_string() + } + _ => item_type.text().to_string(), + } + })); + let accelerator = item_type.accelerator(); + + Self { + type_: MenuItemType::Predefined, + text, + enabled: true, + id: COUNTER.next(), + accelerator, + predefined_item_type: item_type, + // ns_menu_item, + ..Default::default() + } + } + + pub fn new_check( + text: &str, + enabled: bool, + checked: bool, + accelerator: Option, + ) -> Self { + Self { + type_: MenuItemType::Check, + text: text.to_string(), + enabled, + id: COUNTER.next(), + accelerator, + checked, + ..Default::default() + } + } + + pub fn new_icon( + text: &str, + enabled: bool, + icon: Option, + accelerator: Option, + ) -> Self { + Self { + type_: MenuItemType::Icon, + text: text.to_string(), + enabled, + id: COUNTER.next(), + icon, + accelerator, + ..Default::default() + } + } +} + +/// Shared methods impl MenuChild { pub fn id(&self) -> u32 { self.id @@ -136,6 +354,42 @@ impl MenuChild { } } + pub fn set_accelerator(&mut self, accelerator: Option) -> crate::Result<()> { + let key_equivalent = (accelerator) + .as_ref() + .map(|accel| accel.key_equivalent()) + .transpose()?; + + if let Some(key_equivalent) = key_equivalent { + let key_equivalent = unsafe { + NSString::alloc(nil) + .init_str(key_equivalent.as_str()) + .autorelease() + }; + + let modifier_mask = (accelerator) + .as_ref() + .map(|accel| accel.key_modifier_mask()) + .unwrap_or_else(NSEventModifierFlags::empty); + + for ns_items in self.ns_menu_items.values() { + for &ns_item in ns_items { + unsafe { + let _: () = msg_send![ns_item, setKeyEquivalent: key_equivalent]; + ns_item.setKeyEquivalentModifierMask_(modifier_mask); + } + } + } + } + + self.accelerator = accelerator; + + Ok(()) + } +} + +/// CheckMenuItem methods +impl MenuChild { pub fn is_checked(&self) -> bool { self.checked } @@ -150,9 +404,13 @@ impl MenuChild { } } } +} - fn set_icon(&mut self, icon: Option) { +/// IconMenuItem methods +impl MenuChild { + pub fn set_icon(&mut self, icon: Option) { self.icon = icon.clone(); + self.native_icon = None; for ns_items in self.ns_menu_items.values() { for &ns_item in ns_items { menuitem_set_icon(ns_item, icon.as_ref()); @@ -160,307 +418,85 @@ impl MenuChild { } } - fn set_accelerator(&mut self, accelerator: Option) { - let key_equivalent = (accelerator) - .as_ref() - .map(|accel| accel.key_equivalent()) - .unwrap_or_default(); - let key_equivalent = unsafe { - NSString::alloc(nil) - .init_str(key_equivalent.as_str()) - .autorelease() - }; - - let modifier_mask = (accelerator) - .as_ref() - .map(|accel| accel.key_modifier_mask()) - .unwrap_or_else(NSEventModifierFlags::empty); - + pub fn set_native_icon(&mut self, icon: Option) { + self.native_icon = icon; + self.icon = None; for ns_items in self.ns_menu_items.values() { for &ns_item in ns_items { - unsafe { - let _: () = msg_send![ns_item, setKeyEquivalent: key_equivalent]; - ns_item.setKeyEquivalentModifierMask_(modifier_mask); - } + menuitem_set_native_icon(ns_item, icon); } } - - self.accelerator = accelerator; } } -#[derive(Clone, Debug)] -pub struct Menu { - id: u32, - ns_menu: id, - children: Rc>>>>, -} - -impl Menu { - pub fn new() -> Self { - Self { - id: COUNTER.next(), - ns_menu: unsafe { - let ns_menu = NSMenu::alloc(nil).autorelease(); - ns_menu.setAutoenablesItems(NO); - ns_menu - }, - children: Rc::new(RefCell::new(Vec::new())), - } - } - - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { - let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.id); - let child: Rc> = item.get_child(); +/// Submenu methods +impl MenuChild { + pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { + let child = item.child(); unsafe { match op { AddOp::Append => { - self.ns_menu.addItem_(ns_menu_item); - self.children.borrow_mut().push(child); - } - AddOp::Insert(position) => { - let () = msg_send![self.ns_menu, insertItem: ns_menu_item atIndex: position as NSInteger]; - self.children.borrow_mut().insert(position, child); - } - } - } - } - - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { - // get a list of instances of the specified NSMenuItem in this menu - if let Some(ns_menu_items) = match item.type_() { - MenuItemType::Submenu => { - let submenu = item.as_any().downcast_ref::().unwrap(); - submenu.0 .0.borrow_mut() - } - MenuItemType::Normal => { - let menuitem = item.as_any().downcast_ref::().unwrap(); - menuitem.0 .0.borrow_mut() - } - MenuItemType::Predefined => { - let menuitem = item - .as_any() - .downcast_ref::() - .unwrap(); - menuitem.0 .0.borrow_mut() - } - MenuItemType::Check => { - let menuitem = item - .as_any() - .downcast_ref::() - .unwrap(); - menuitem.0 .0.borrow_mut() - } - MenuItemType::Icon => { - let menuitem = item.as_any().downcast_ref::().unwrap(); - menuitem.0 .0.borrow_mut() - } - } - .ns_menu_items - .remove(&self.id) - { - // remove each NSMenuItem from the NSMenu - unsafe { - for item in ns_menu_items { - let () = msg_send![self.ns_menu, removeItem: item]; - } - } - } - - // remove the item from our internal list of children - let mut children = self.children.borrow_mut(); - let index = children - .iter() - .position(|e| e.borrow().id() == item.id()) - .ok_or(crate::Error::NotAChildOfThisMenu)?; - children.remove(index); - - Ok(()) - } - - pub fn items(&self) -> Vec> { - self.children - .borrow() - .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) - .collect() - } - - pub fn init_for_nsapp(&self) { - unsafe { NSApp().setMainMenu_(self.ns_menu) } - } - - pub fn remove_for_nsapp(&self) { - unsafe { NSApp().setMainMenu_(nil) } - } - - pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) { - unsafe { - let window: id = msg_send![view, window]; - let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; - let view_point = NSPoint::new(x / scale_factor, y / scale_factor); - let view_rect: NSRect = msg_send![view, frame]; - let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y); - msg_send![self.ns_menu, popUpMenuPositioningItem: nil atLocation: location inView: view] - } - } - - pub fn ns_menu(&self) -> *mut std::ffi::c_void { - self.ns_menu as _ - } -} - -#[derive(Clone)] -pub(crate) struct Submenu(Rc>); - -impl Submenu { - pub fn new(text: &str, enabled: bool) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Submenu, - text: strip_mnemonic(text), - enabled, - children: Some(Vec::new()), - ns_menu: (COUNTER.next(), unsafe { NSMenu::alloc(nil).autorelease() }), - ..Default::default() - }))) - } - - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id { - let mut self_ = self.0.borrow_mut(); - let ns_menu_item: *mut Object; - let ns_submenu: *mut Object; - - unsafe { - ns_menu_item = NSMenuItem::alloc(nil).autorelease(); - ns_submenu = NSMenu::alloc(nil).autorelease(); - - let title = NSString::alloc(nil).init_str(&self_.text).autorelease(); - let () = msg_send![ns_submenu, setTitle: title]; - let () = msg_send![ns_menu_item, setTitle: title]; - let () = msg_send![ns_menu_item, setSubmenu: ns_submenu]; - - if !self_.enabled { - let () = msg_send![ns_menu_item, setEnabled: NO]; - } - } - - for item in self_.children.as_ref().unwrap() { - let item_type = &item.borrow().type_.clone(); - let ns_item = match item_type { - MenuItemType::Submenu => Submenu(item.clone()).make_ns_item_for_menu(menu_id), - MenuItemType::Normal => MenuItem(item.clone()).make_ns_item_for_menu(menu_id), - MenuItemType::Predefined => { - PredefinedMenuItem(item.clone()).make_ns_item_for_menu(menu_id) - } - MenuItemType::Check => CheckMenuItem(item.clone()).make_ns_item_for_menu(menu_id), - MenuItemType::Icon => IconMenuItem(item.clone()).make_ns_item_for_menu(menu_id), - }; - unsafe { ns_submenu.addItem_(ns_item) }; - } - - self_ - .ns_menus - .entry(menu_id) - .or_insert_with(Vec::new) - .push(ns_submenu); - - self_ - .ns_menu_items - .entry(menu_id) - .or_insert_with(Vec::new) - .push(ns_menu_item); - - ns_menu_item - } - - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { - let mut self_ = self.0.borrow_mut(); - - let item_child: Rc> = item.get_child(); - - unsafe { - match op { - AddOp::Append => { - for menus in self_.ns_menus.values() { + 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); - self_.ns_menu.1.addItem_(ns_menu_item); + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.ns_menu.0)?; + self.ns_menu.1.addItem_(ns_menu_item); - self_.children.as_mut().unwrap().push(item_child); + self.children.as_mut().unwrap().push(child); } AddOp::Insert(position) => { - for menus in self_.ns_menus.values() { + 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 () = msg_send![ self_.ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger]; + let ns_menu_item: *mut Object = item.make_ns_item_for_menu(self.ns_menu.0)?; + let () = msg_send![ self.ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger]; - self_ - .children - .as_mut() - .unwrap() - .insert(position, item_child); + self.children.as_mut().unwrap().insert(position, child); } } } + + Ok(()) } - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { - let mut self_ = self.0.borrow_mut(); - - let child: Rc> = item.get_child(); + pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { + let child = item.child(); // get a list of instances of the specified NSMenuItem in this menu - if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self_.id) { + if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self.id) { // remove each NSMenuItem from the NSMenu unsafe { for item in ns_menu_items { - for menus in self_.ns_menus.values() { + for menus in self.ns_menus.values() { for &ns_menu in menus { let () = msg_send![ns_menu, removeItem: item]; } } - let () = msg_send![self_.ns_menu.1, removeItem: item]; + let () = msg_send![self.ns_menu.1, removeItem: item]; } } } - if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self_.ns_menu.0) { + if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self.ns_menu.0) { unsafe { for item in ns_menu_items { - let () = msg_send![self_.ns_menu.1, removeItem: item]; + let () = msg_send![self.ns_menu.1, removeItem: item]; } } } // remove the item from our internal list of children - let children = self_.children.as_mut().unwrap(); + let children = self.children.as_mut().unwrap(); let index = children .iter() .position(|e| e.borrow().id == item.id()) @@ -470,44 +506,15 @@ impl Submenu { Ok(()) } - pub fn items(&self) -> Vec> { - self.0 - .borrow() - .children + pub fn items(&self) -> Vec> { + self.children .as_ref() .unwrap() .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) + .map(|c| c.borrow().boxed(c.clone())) .collect() } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) { unsafe { let window: id = msg_send![view, window]; @@ -515,160 +522,114 @@ impl Submenu { let view_point = NSPoint::new(x / scale_factor, y / scale_factor); let view_rect: NSRect = msg_send![view, frame]; let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y); - msg_send![self.0.borrow().ns_menu.1, popUpMenuPositioningItem: nil atLocation: location inView: view] + msg_send![self.ns_menu.1, popUpMenuPositioningItem: nil atLocation: location inView: view] } } pub fn set_windows_menu_for_nsapp(&self) { - unsafe { NSApp().setWindowsMenu_(self.0.borrow().ns_menu.1) } + unsafe { NSApp().setWindowsMenu_(self.ns_menu.1) } } pub fn set_help_menu_for_nsapp(&self) { - unsafe { msg_send![NSApp(), setHelpMenu: self.0.borrow().ns_menu.1] } + unsafe { msg_send![NSApp(), setHelpMenu: self.ns_menu.1] } } pub fn ns_menu(&self) -> *mut std::ffi::c_void { - self.0.borrow().ns_menu.1 as _ + self.ns_menu.1 as _ } } -#[derive(Clone, Debug)] -pub(crate) struct MenuItem(Rc>); - -impl MenuItem { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Normal, - text: strip_mnemonic(text), - enabled, - id: COUNTER.next(), - accelerator, - ..Default::default() - }))) - } - - pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id { - let mut child = self.0.borrow_mut(); - - let ns_menu_item = create_ns_menu_item( - &child.text, - Some(sel!(fireMenuItemAction:)), - &child.accelerator, - ); +/// NSMenuItem item creation methods +impl MenuChild { + pub fn create_ns_item_for_submenu(&mut self, menu_id: u32) -> crate::Result { + let ns_menu_item: *mut Object; + let ns_submenu: *mut Object; unsafe { - let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:child.id()]; + ns_menu_item = NSMenuItem::alloc(nil).autorelease(); + ns_submenu = NSMenu::alloc(nil).autorelease(); - // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item - let ptr = Box::into_raw(Box::new(&*child)); - (*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize); + let title = NSString::alloc(nil).init_str(&self.text).autorelease(); + let () = msg_send![ns_submenu, setTitle: title]; + let () = msg_send![ns_menu_item, setTitle: title]; + let () = msg_send![ns_menu_item, setSubmenu: ns_submenu]; - if !child.enabled { + if !self.enabled { let () = msg_send![ns_menu_item, setEnabled: NO]; } } - child - .ns_menu_items + for item in self.children.as_ref().unwrap() { + let ns_item = item.borrow_mut().make_ns_item_for_menu(menu_id)?; + unsafe { ns_submenu.addItem_(ns_item) }; + } + + self.ns_menus + .entry(menu_id) + .or_insert_with(Vec::new) + .push(ns_submenu); + + self.ns_menu_items .entry(menu_id) .or_insert_with(Vec::new) .push(ns_menu_item); - ns_menu_item + Ok(ns_menu_item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } + pub fn create_ns_item_for_menu_item(&mut self, menu_id: u32) -> crate::Result { + let ns_menu_item = create_ns_menu_item( + &self.text, + Some(sel!(fireMenuItemAction:)), + &self.accelerator, + )?; - pub fn text(&self) -> String { - self.0.borrow().text() - } + unsafe { + let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; + let _: () = msg_send![ns_menu_item, setTag:self.id()]; - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } + // Store a raw pointer to the `MenuChild` as an instance variable on the native menu item + let ptr = Box::into_raw(Box::new(&*self)); + (*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize); - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct PredefinedMenuItem(Rc>); - -impl PredefinedMenuItem { - pub fn new(item_type: PredfinedMenuItemType, text: Option) -> Self { - let text = strip_mnemonic(text.unwrap_or_else(|| { - match item_type { - PredfinedMenuItemType::About(_) => { - format!("About {}", unsafe { app_name_string() }.unwrap_or_default()) - .trim() - .to_string() - } - PredfinedMenuItemType::Hide => { - format!("Hide {}", unsafe { app_name_string() }.unwrap_or_default()) - .trim() - .to_string() - } - PredfinedMenuItemType::Quit => { - format!("Quit {}", unsafe { app_name_string() }.unwrap_or_default()) - .trim() - .to_string() - } - _ => item_type.text().to_string(), + if !self.enabled { + let () = msg_send![ns_menu_item, setEnabled: NO]; } - })); - let accelerator = item_type.accelerator(); + } - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Predefined, - text, - enabled: true, - id: COUNTER.next(), - accelerator, - predefined_item_type: item_type, - // ns_menu_item, - ..Default::default() - }))) + self.ns_menu_items + .entry(menu_id) + .or_insert_with(Vec::new) + .push(ns_menu_item); + + Ok(ns_menu_item) } - pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id { - let mut child = self.0.borrow_mut(); - - let item_type = &child.predefined_item_type; + pub fn create_ns_item_for_predefined_menu_item(&mut self, menu_id: u32) -> crate::Result { + let item_type = &self.predefined_item_type; let ns_menu_item = match item_type { PredfinedMenuItemType::Separator => unsafe { NSMenuItem::separatorItem(nil).autorelease() }, - _ => create_ns_menu_item(&child.text, item_type.selector(), &child.accelerator), + _ => create_ns_menu_item(&self.text, item_type.selector(), &self.accelerator)?, }; - if let PredfinedMenuItemType::About(_) = child.predefined_item_type { + if let PredfinedMenuItemType::About(_) = self.predefined_item_type { unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:child.id()]; + 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(&*child)); + let ptr = Box::into_raw(Box::new(&*self)); (*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize); } } unsafe { - if !child.enabled { + if !self.enabled { let () = msg_send![ns_menu_item, setEnabled: NO]; } - if let PredfinedMenuItemType::Services = child.predefined_item_type { + if let PredfinedMenuItemType::Services = self.predefined_item_type { // we have to assign an empty menu as the app's services menu, and macOS will populate it let services_menu = NSMenu::new(nil).autorelease(); let () = msg_send![NSApp(), setServicesMenu: services_menu]; @@ -676,191 +637,83 @@ impl PredefinedMenuItem { } } - child - .ns_menu_items + self.ns_menu_items .entry(menu_id) .or_insert_with(Vec::new) .push(ns_menu_item); - ns_menu_item + Ok(ns_menu_item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct CheckMenuItem(Rc>); - -impl CheckMenuItem { - pub fn new(text: &str, enabled: bool, checked: bool, accelerator: Option) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Check, - text: text.to_string(), - enabled, - id: COUNTER.next(), - accelerator, - checked, - ..Default::default() - }))) - } - - pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id { - let mut child = self.0.borrow_mut(); - + pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: u32) -> crate::Result { let ns_menu_item = create_ns_menu_item( - &child.text, + &self.text, Some(sel!(fireMenuItemAction:)), - &child.accelerator, - ); + &self.accelerator, + )?; unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:child.id()]; + 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(&*child)); + let ptr = Box::into_raw(Box::new(&*self)); (*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize); - if !child.enabled { + if !self.enabled { let () = msg_send![ns_menu_item, setEnabled: NO]; } - if child.checked { + if self.checked { let () = msg_send![ns_menu_item, setState: 1_isize]; } } - child - .ns_menu_items + self.ns_menu_items .entry(menu_id) .or_insert_with(Vec::new) .push(ns_menu_item); - ns_menu_item + Ok(ns_menu_item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn is_checked(&self) -> bool { - self.0.borrow().is_checked() - } - - pub fn set_checked(&self, checked: bool) { - self.0.borrow_mut().set_checked(checked) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct IconMenuItem(Rc>); - -impl IconMenuItem { - pub fn new( - text: &str, - enabled: bool, - icon: Option, - accelerator: Option, - ) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Icon, - text: text.to_string(), - enabled, - id: COUNTER.next(), - icon, - accelerator, - ..Default::default() - }))) - } - - pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id { - let mut child = self.0.borrow_mut(); - + pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: u32) -> crate::Result { let ns_menu_item = create_ns_menu_item( - &child.text, + &self.text, Some(sel!(fireMenuItemAction:)), - &child.accelerator, - ); + &self.accelerator, + )?; unsafe { let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; - let _: () = msg_send![ns_menu_item, setTag:child.id()]; + 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(&*child)); + let ptr = Box::into_raw(Box::new(&*self)); (*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize); - if !child.enabled { + if !self.enabled { let () = msg_send![ns_menu_item, setEnabled: NO]; } - menuitem_set_icon(ns_menu_item, child.icon.as_ref()); + menuitem_set_icon(ns_menu_item, self.icon.as_ref()); } - child - .ns_menu_items + self.ns_menu_items .entry(menu_id) .or_insert_with(Vec::new) .push(ns_menu_item); - ns_menu_item + Ok(ns_menu_item) } - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_icon(&self, icon: Option) { - self.0.borrow_mut().set_icon(icon) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) + fn make_ns_item_for_menu(&mut self, menu_id: u32) -> crate::Result<*mut Object> { + match self.type_ { + MenuItemType::Submenu => self.create_ns_item_for_submenu(menu_id), + MenuItemType::Normal => self.create_ns_item_for_menu_item(menu_id), + MenuItemType::Predefined => self.create_ns_item_for_predefined_menu_item(menu_id), + MenuItemType::Check => self.create_ns_item_for_check_menu_item(menu_id), + MenuItemType::Icon => self.create_ns_item_for_icon_menu_item(menu_id), + } } } @@ -890,79 +743,44 @@ impl PredfinedMenuItemType { } } -impl dyn MenuItemExt + '_ { - fn get_child(&self) -> Rc> { +impl dyn IsMenuItem + '_ { + fn make_ns_item_for_menu(&self, menu_id: u32) -> crate::Result<*mut Object> { match self.type_() { MenuItemType::Submenu => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .0 - .clone(), + .borrow_mut() + .create_ns_item_for_submenu(menu_id), MenuItemType::Normal => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .0 - .clone(), + .borrow_mut() + .create_ns_item_for_menu_item(menu_id), MenuItemType::Predefined => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .0 - .clone(), + .borrow_mut() + .create_ns_item_for_predefined_menu_item(menu_id), MenuItemType::Check => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .0 - .clone(), + .borrow_mut() + .create_ns_item_for_check_menu_item(menu_id), MenuItemType::Icon => self .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .0 - .0 - .clone(), - } - } - - fn make_ns_item_for_menu(&self, menu_id: u32) -> *mut Object { - match self.type_() { - MenuItemType::Submenu => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .make_ns_item_for_menu(menu_id), - MenuItemType::Normal => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .make_ns_item_for_menu(menu_id), - MenuItemType::Predefined => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .make_ns_item_for_menu(menu_id), - MenuItemType::Check => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .make_ns_item_for_menu(menu_id), - MenuItemType::Icon => self - .as_any() - .downcast_ref::() - .unwrap() - .0 - .make_ns_item_for_menu(menu_id), + .borrow_mut() + .create_ns_item_for_icon_menu_item(menu_id), } } } @@ -1082,7 +900,7 @@ fn create_ns_menu_item( title: &str, selector: Option, accelerator: &Option, -) -> id { +) -> crate::Result { unsafe { let title = NSString::alloc(nil).init_str(title).autorelease(); @@ -1090,6 +908,7 @@ fn create_ns_menu_item( let key_equivalent = (*accelerator) .map(|accel| accel.key_equivalent()) + .transpose()? .unwrap_or_default(); let key_equivalent = NSString::alloc(nil) .init_str(key_equivalent.as_str()) @@ -1104,7 +923,7 @@ fn create_ns_menu_item( ns_menu_item.initWithTitle_action_keyEquivalent_(title, selector, key_equivalent); ns_menu_item.setKeyEquivalentModifierMask_(modifier_mask); - ns_menu_item.autorelease() + Ok(ns_menu_item.autorelease()) } } @@ -1120,3 +939,86 @@ fn menuitem_set_icon(menuitem: id, icon: Option<&Icon>) { } } } + +fn menuitem_set_native_icon(menuitem: id, icon: Option) { + if let Some(icon) = icon { + unsafe { + let named_img: id = icon.named_img(); + let nsimage: id = msg_send![class!(NSImage), imageNamed: named_img]; + let size = NSSize::new(18.0, 18.0); + let _: () = msg_send![nsimage, setSize: size]; + let _: () = msg_send![menuitem, setImage: nsimage]; + } + } else { + unsafe { + let _: () = msg_send![menuitem, setImage: nil]; + } + } +} + +impl NativeIcon { + unsafe fn named_img(self) -> id { + match self { + NativeIcon::Add => appkit::NSImageNameAddTemplate, + NativeIcon::StatusAvailable => appkit::NSImageNameStatusAvailable, + NativeIcon::StatusUnavailable => appkit::NSImageNameStatusUnavailable, + NativeIcon::StatusPartiallyAvailable => appkit::NSImageNameStatusPartiallyAvailable, + NativeIcon::Advanced => appkit::NSImageNameAdvanced, + NativeIcon::Bluetooth => appkit::NSImageNameBluetoothTemplate, + NativeIcon::Bookmarks => appkit::NSImageNameBookmarksTemplate, + NativeIcon::Caution => appkit::NSImageNameCaution, + NativeIcon::ColorPanel => appkit::NSImageNameColorPanel, + NativeIcon::ColumnView => appkit::NSImageNameColumnViewTemplate, + NativeIcon::Computer => appkit::NSImageNameComputer, + NativeIcon::EnterFullScreen => appkit::NSImageNameEnterFullScreenTemplate, + NativeIcon::Everyone => appkit::NSImageNameEveryone, + NativeIcon::ExitFullScreen => appkit::NSImageNameExitFullScreenTemplate, + NativeIcon::FlowView => appkit::NSImageNameFlowViewTemplate, + NativeIcon::Folder => appkit::NSImageNameFolder, + NativeIcon::FolderBurnable => appkit::NSImageNameFolderBurnable, + NativeIcon::FolderSmart => appkit::NSImageNameFolderSmart, + NativeIcon::FollowLinkFreestanding => appkit::NSImageNameFollowLinkFreestandingTemplate, + NativeIcon::FontPanel => appkit::NSImageNameFontPanel, + NativeIcon::GoLeft => appkit::NSImageNameGoLeftTemplate, + NativeIcon::GoRight => appkit::NSImageNameGoRightTemplate, + NativeIcon::Home => appkit::NSImageNameHomeTemplate, + NativeIcon::IChatTheater => appkit::NSImageNameIChatTheaterTemplate, + NativeIcon::IconView => appkit::NSImageNameIconViewTemplate, + NativeIcon::Info => appkit::NSImageNameInfo, + NativeIcon::InvalidDataFreestanding => { + appkit::NSImageNameInvalidDataFreestandingTemplate + } + NativeIcon::LeftFacingTriangle => appkit::NSImageNameLeftFacingTriangleTemplate, + NativeIcon::ListView => appkit::NSImageNameListViewTemplate, + NativeIcon::LockLocked => appkit::NSImageNameLockLockedTemplate, + NativeIcon::LockUnlocked => appkit::NSImageNameLockUnlockedTemplate, + NativeIcon::MenuMixedState => appkit::NSImageNameMenuMixedStateTemplate, + NativeIcon::MenuOnState => appkit::NSImageNameMenuOnStateTemplate, + NativeIcon::MobileMe => appkit::NSImageNameMobileMe, + NativeIcon::MultipleDocuments => appkit::NSImageNameMultipleDocuments, + NativeIcon::Network => appkit::NSImageNameNetwork, + NativeIcon::Path => appkit::NSImageNamePathTemplate, + NativeIcon::PreferencesGeneral => appkit::NSImageNamePreferencesGeneral, + NativeIcon::QuickLook => appkit::NSImageNameQuickLookTemplate, + NativeIcon::RefreshFreestanding => appkit::NSImageNameRefreshFreestandingTemplate, + NativeIcon::Refresh => appkit::NSImageNameRefreshTemplate, + NativeIcon::Remove => appkit::NSImageNameRemoveTemplate, + NativeIcon::RevealFreestanding => appkit::NSImageNameRevealFreestandingTemplate, + NativeIcon::RightFacingTriangle => appkit::NSImageNameRightFacingTriangleTemplate, + NativeIcon::Share => appkit::NSImageNameShareTemplate, + NativeIcon::Slideshow => appkit::NSImageNameSlideshowTemplate, + NativeIcon::SmartBadge => appkit::NSImageNameSmartBadgeTemplate, + NativeIcon::StatusNone => appkit::NSImageNameStatusNone, + NativeIcon::StopProgressFreestanding => { + appkit::NSImageNameStopProgressFreestandingTemplate + } + NativeIcon::StopProgress => appkit::NSImageNameStopProgressTemplate, + NativeIcon::TrashEmpty => appkit::NSImageNameTrashEmpty, + NativeIcon::TrashFull => appkit::NSImageNameTrashFull, + NativeIcon::User => appkit::NSImageNameUser, + NativeIcon::UserAccounts => appkit::NSImageNameUserAccounts, + NativeIcon::UserGroup => appkit::NSImageNameUserGroup, + NativeIcon::UserGuest => appkit::NSImageNameUserGuest, + } + } +} diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index 3dabaee..de54604 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -12,4 +12,58 @@ mod platform; #[path = "macos/mod.rs"] mod platform; +use std::{cell::RefCell, rc::Rc}; + +use crate::{items::*, IsMenuItem, MenuItemType}; + pub(crate) use self::platform::*; + +impl dyn IsMenuItem + '_ { + fn child(&self) -> Rc> { + match self.type_() { + MenuItemType::Submenu => self + .as_any() + .downcast_ref::() + .unwrap() + .0 + .clone(), + MenuItemType::Normal => self + .as_any() + .downcast_ref::() + .unwrap() + .0 + .clone(), + MenuItemType::Predefined => self + .as_any() + .downcast_ref::() + .unwrap() + .0 + .clone(), + MenuItemType::Check => self + .as_any() + .downcast_ref::() + .unwrap() + .0 + .clone(), + MenuItemType::Icon => self + .as_any() + .downcast_ref::() + .unwrap() + .0 + .clone(), + } + } +} + +/// Internal utilities +impl MenuChild { + fn boxed(&self, c: Rc>) -> Box { + match self.type_ { + MenuItemType::Submenu => Box::new(Submenu(c)), + MenuItemType::Normal => Box::new(MenuItem(c)), + MenuItemType::Predefined => Box::new(PredefinedMenuItem(c)), + MenuItemType::Check => Box::new(CheckMenuItem(c)), + MenuItemType::Icon => Box::new(IconMenuItem(c)), + } + } +} diff --git a/src/platform_impl/windows/accelerator.rs b/src/platform_impl/windows/accelerator.rs index adc3510..8bb0a4b 100644 --- a/src/platform_impl/windows/accelerator.rs +++ b/src/platform_impl/windows/accelerator.rs @@ -14,7 +14,7 @@ use crate::accelerator::Accelerator; impl Accelerator { // Convert a hotkey to an accelerator. - pub fn to_accel(&self, menu_id: u16) -> ACCEL { + pub fn to_accel(&self, menu_id: u16) -> crate::Result { let mut virt_key = FVIRTKEY; let key_mods: Modifiers = self.mods; if key_mods.contains(Modifiers::CONTROL) { @@ -27,7 +27,7 @@ impl Accelerator { virt_key |= FSHIFT; } - let vk_code = key_to_vk(&self.key); + let vk_code = key_to_vk(&self.key)?; let mod_code = vk_code >> 8; if mod_code & 0x1 != 0 { virt_key |= FSHIFT; @@ -40,17 +40,17 @@ impl Accelerator { } let raw_key = vk_code & 0x00ff; - ACCEL { + Ok(ACCEL { fVirt: virt_key, key: raw_key, cmd: menu_id, - } + }) } } // used to build accelerators table from Key -fn key_to_vk(key: &Code) -> VIRTUAL_KEY { - match key { +fn key_to_vk(key: &Code) -> crate::Result { + Ok(match key { Code::KeyA => unsafe { VkKeyScanW('a' as u16) as u16 }, Code::KeyB => unsafe { VkKeyScanW('b' as u16) as u16 }, Code::KeyC => unsafe { VkKeyScanW('c' as u16) as u16 }, @@ -162,8 +162,8 @@ fn key_to_vk(key: &Code) -> VIRTUAL_KEY { Code::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, Code::LaunchMail => VK_LAUNCH_MAIL, Code::Convert => VK_CONVERT, - key => panic!("Unsupported key: {}", key), - } + key => return Err(crate::Error::UnrecognizedAcceleratorCode(key.to_string())), + }) } impl fmt::Display for Accelerator { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 6ac0cca..3bc7bd4 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -11,9 +11,10 @@ pub(crate) use self::icon::WinIcon as PlatformIcon; use crate::{ accelerator::Accelerator, icon::Icon, - predefined::PredfinedMenuItemType, + items::{AboutMetadata, PredfinedMenuItemType}, util::{AddOp, Counter}, - AboutMetadata, MenuEvent, MenuItemType, + CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType, PredefinedMenuItem, + Submenu, }; use std::{ cell::{RefCell, RefMut}, @@ -30,233 +31,39 @@ use windows_sys::Win32::{ Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass}, WindowsAndMessaging::{ AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu, - DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetMenuItemInfoW, InsertMenuW, - PostQuitMessage, RemoveMenu, SendMessageW, SetMenu, SetMenuItemInfoW, ShowWindow, - TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW, MFS_CHECKED, MFS_DISABLED, MF_BYCOMMAND, - MF_BYPOSITION, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, - MF_STRING, MF_UNCHECKED, MIIM_BITMAP, MIIM_STATE, MIIM_STRING, SW_HIDE, SW_MAXIMIZE, - SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE, WM_COMMAND, WM_DESTROY, + DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetMenu, GetMenuItemInfoW, + InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu, SetMenuItemInfoW, + ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW, MFS_CHECKED, MFS_DISABLED, + MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, + MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP, MIIM_STATE, MIIM_STRING, SW_HIDE, + SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE, WM_COMMAND, WM_DESTROY, }, }, }; -const COUNTER_START: u32 = 1000; -static COUNTER: Counter = Counter::new_with_start(COUNTER_START); +static COUNTER: Counter = Counter::new_with_start(1000); type AccelWrapper = (HACCEL, HashMap); -/// A generic child in a menu -/// -/// Be careful when cloning this item and treat it as read-only -#[derive(Debug, Default)] -struct MenuChild { - // shared fields between submenus and menu items - type_: MenuItemType, - text: String, - enabled: bool, - parents_hemnu: Vec, - root_menu_haccel_stores: Option>>>, - - // menu item fields - id: u32, - accelerator: Option, - - // predefined menu item fields - predefined_item_type: PredfinedMenuItemType, - - // check menu item fields - checked: bool, - - // icon menu item fields - icon: Option, - - // submenu fields - hmenu: HMENU, - hpopupmenu: HMENU, - children: Option>>>, -} - -impl MenuChild { - fn id(&self) -> u32 { - match self.type_ { - MenuItemType::Submenu => self.hmenu as u32, - _ => self.id, - } - } - fn text(&self) -> String { - self.parents_hemnu - .first() - .map(|hmenu| { - let mut label = Vec::::new(); - - let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - info.cbSize = std::mem::size_of::() as _; - info.fMask = MIIM_STRING; - info.dwTypeData = label.as_mut_ptr(); - - unsafe { GetMenuItemInfoW(*hmenu, self.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) }; - - let text = decode_wide(info.dwTypeData); - text.split('\t').next().unwrap().to_string() - }) - .unwrap_or_else(|| self.text.clone()) - } - - fn set_text(&mut self, text: &str) { - self.text = if let Some(accelerator) = self.accelerator { - format!("{text}\t{}", accelerator) - } else { - text.to_string() - }; - for parent in &self.parents_hemnu { - let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - info.cbSize = std::mem::size_of::() as _; - info.fMask = MIIM_STRING; - info.dwTypeData = encode_wide(&self.text).as_mut_ptr(); - - unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; - } - } - - fn is_enabled(&self) -> bool { - self.parents_hemnu - .first() - .map(|hmenu| { - let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - info.cbSize = std::mem::size_of::() as _; - info.fMask = MIIM_STATE; - - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; - - (info.fState & MFS_DISABLED) == 0 - }) - .unwrap_or(self.enabled) - } - - fn set_enabled(&mut self, enabled: bool) { - self.enabled = enabled; - for parent in &self.parents_hemnu { - unsafe { - EnableMenuItem( - *parent, - self.id(), - if enabled { MF_ENABLED } else { MF_DISABLED }, - ) - }; - } - } - - fn is_checked(&self) -> bool { - self.parents_hemnu - .first() - .map(|hmenu| { - let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - info.cbSize = std::mem::size_of::() as _; - info.fMask = MIIM_STATE; - - unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; - - (info.fState & MFS_CHECKED) != 0 - }) - .unwrap_or(self.enabled) - } - - fn set_checked(&mut self, checked: bool) { - use windows_sys::Win32::UI::WindowsAndMessaging; - - self.checked = checked; - for parent in &self.parents_hemnu { - unsafe { - WindowsAndMessaging::CheckMenuItem( - *parent, - self.id(), - if checked { MF_CHECKED } else { MF_UNCHECKED }, - ) - }; - } - } - - fn set_icon(&mut self, icon: Option) { - self.icon = icon.clone(); - - 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) }; - } - } - - fn set_accelerator(&mut self, accelerator: Option) { - self.accelerator = accelerator; - self.set_text(&self.text.clone()); - - let haccel_stores = self.root_menu_haccel_stores.as_mut().unwrap(); - for store in haccel_stores { - let mut store = store.borrow_mut(); - if let Some(accelerator) = self.accelerator { - AccelAction::add(&mut store, self.id, &accelerator) - } else { - AccelAction::remove(&mut store, self.id) - } - } - } -} - -#[derive(Clone)] -pub(crate) struct Menu { - hmenu: HMENU, - hpopupmenu: HMENU, - hwnds: Rc>>, - haccel_store: Rc>, - children: Rc>>>>, -} - -impl Menu { - pub fn new() -> Self { - Self { - hmenu: unsafe { CreateMenu() }, - hpopupmenu: unsafe { CreatePopupMenu() }, - haccel_store: Rc::new(RefCell::new((0, HashMap::new()))), - children: Rc::new(RefCell::new(Vec::new())), - hwnds: Rc::new(RefCell::new(Vec::new())), - } - } - - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { +macro_rules! inner_menu_child_and_flags { + ($item:ident) => {{ let mut flags = 0; - let child = match item.type_() { + let child = match $item.type_() { MenuItemType::Submenu => { - let submenu = item.as_any().downcast_ref::().unwrap(); - let child = &submenu.0 .0; - flags |= MF_POPUP; - - child + &$item.as_any().downcast_ref::().unwrap().0 } MenuItemType::Normal => { - let item = item.as_any().downcast_ref::().unwrap(); - let child = &item.0 .0; - flags |= MF_STRING; - - child + &$item.as_any().downcast_ref::().unwrap().0 } + MenuItemType::Predefined => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - let child = &item.0 .0; - + let item = $item.as_any().downcast_ref::().unwrap(); + let child = &item.0; let child_ = child.borrow(); - match child_.predefined_item_type { - PredfinedMenuItemType::None => return, + PredfinedMenuItemType::None => return Ok(()), PredfinedMenuItemType::Separator => { flags |= MF_SEPARATOR; } @@ -264,33 +71,55 @@ impl Menu { flags |= MF_STRING; } } - child } MenuItemType::Check => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - let child = &item.0 .0; - + let item = $item.as_any().downcast_ref::().unwrap(); + let child = &item.0; flags |= MF_STRING; if child.borrow().checked { flags |= MF_CHECKED; } - child } MenuItemType::Icon => { - let item = item.as_any().downcast_ref::().unwrap(); - let child = &item.0 .0; - flags |= MF_STRING; - - child + &$item.as_any().downcast_ref::().unwrap().0 } + }; + + (child.clone(), flags) + }}; +} + +#[derive(Debug)] +pub(crate) struct Menu { + id: u32, + hmenu: HMENU, + hpopupmenu: HMENU, + hwnds: Vec, + haccel_store: Rc>, + children: Vec>>, +} + +impl Menu { + pub fn new() -> Self { + Self { + id: COUNTER.next(), + hmenu: unsafe { CreateMenu() }, + hpopupmenu: unsafe { CreatePopupMenu() }, + haccel_store: Rc::new(RefCell::new((0, HashMap::new()))), + children: Vec::new(), + hwnds: Vec::new(), } - .clone(); + } + + pub fn id(&self) -> u32 { + self.id + } + + pub fn add_menu_item(&mut self, item: &dyn IsMenuItem, op: AddOp) -> crate::Result<()> { + let (child, mut flags) = inner_menu_child_and_flags!(item); { child @@ -315,8 +144,11 @@ impl Menu { text.push('\t'); text.push_str(&accel_str); - let mut haccel_store = self.haccel_store.borrow_mut(); - AccelAction::add(&mut haccel_store, child_.id(), accelerator); + AccelAction::add( + &mut self.haccel_store.borrow_mut(), + child_.id(), + accelerator, + )?; } let id = child_.id() as usize; @@ -373,52 +205,26 @@ impl Menu { } { - let mut children = self.children.borrow_mut(); match op { - AddOp::Append => children.push(child), - AddOp::Insert(position) => children.insert(position, child), + AddOp::Append => self.children.push(child), + AddOp::Insert(position) => self.children.insert(position, child), } } + + Ok(()) } - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { + pub fn remove(&mut self, item: &dyn IsMenuItem) -> crate::Result<()> { unsafe { RemoveMenu(self.hmenu, item.id(), MF_BYCOMMAND); RemoveMenu(self.hpopupmenu, item.id(), MF_BYCOMMAND); - for hwnd in self.hwnds.borrow().iter() { + for hwnd in &self.hwnds { DrawMenuBar(*hwnd); } } - let child = match item.type_() { - MenuItemType::Submenu => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - MenuItemType::Normal => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - MenuItemType::Predefined => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - &item.0 .0 - } - MenuItemType::Check => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - &item.0 .0 - } - MenuItemType::Icon => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - }; + let child = item.child(); { let mut child = child.borrow_mut(); @@ -436,51 +242,25 @@ impl Menu { child.parents_hemnu.remove(index); } - let mut children = self.children.borrow_mut(); - let index = children + let index = self + .children .iter() .position(|e| e.borrow().id() == item.id()) .ok_or(crate::Error::NotAChildOfThisMenu)?; - children.remove(index); + self.children.remove(index); Ok(()) } - pub fn items(&self) -> Vec> { + pub fn items(&self) -> Vec> { self.children - .borrow() .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) + .map(|c| c.borrow().boxed(c.clone())) .collect() } fn find_by_id(&self, id: u32) -> Option>> { - let children = self.children.borrow(); - for i in children.iter() { - let item = i.borrow(); - if item.id == id { - return Some(i.clone()); - } - - if item.type_ == MenuItemType::Submenu { - let submenu = Submenu(i.clone()); - if let Some(child) = submenu.find_by_id(id) { - return Some(child); - } - } - } - None + find_by_id(id, &self.children) } pub fn haccel(&self) -> HACCEL { @@ -491,19 +271,19 @@ impl Menu { self.hpopupmenu } - pub fn init_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - if self.hwnds.borrow().iter().any(|h| *h == hwnd) { + pub fn init_for_hwnd(&mut self, hwnd: isize) -> crate::Result<()> { + if self.hwnds.iter().any(|h| *h == hwnd) { return Err(crate::Error::AlreadyInitialized); } - self.hwnds.borrow_mut().push(hwnd); + self.hwnds.push(hwnd); unsafe { SetMenu(hwnd, self.hmenu); SetWindowSubclass( hwnd, Some(menu_subclass_proc), MENU_SUBCLASS_ID, - Box::into_raw(Box::new(self.clone())) as _, + Box::into_raw(Box::new(self)) as _, ); DrawMenuBar(hwnd); }; @@ -511,34 +291,34 @@ impl Menu { Ok(()) } + pub fn remove_for_hwnd(&mut self, hwnd: isize) -> crate::Result<()> { + let index = self + .hwnds + .iter() + .position(|h| *h == hwnd) + .ok_or(crate::Error::NotInitialized)?; + self.hwnds.remove(index); + unsafe { + SendMessageW(hwnd, WM_CLEAR_MENU_DATA, 0, 0); + RemoveWindowSubclass(hwnd, Some(menu_subclass_proc), MENU_SUBCLASS_ID); + SetMenu(hwnd, 0); + DrawMenuBar(hwnd); + } + + Ok(()) + } + pub fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { unsafe { SetWindowSubclass( hwnd, Some(menu_subclass_proc), MENU_SUBCLASS_ID, - Box::into_raw(Box::new(self.clone())) as _, + Box::into_raw(Box::new(self)) as _, ); } } - pub fn remove_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - let mut hwnds = self.hwnds.borrow_mut(); - let index = hwnds - .iter() - .position(|h| *h == hwnd) - .ok_or(crate::Error::NotInitialized)?; - hwnds.remove(index); - unsafe { - SendMessageW(hwnd, WM_CLEAR_MENU_DATA, 0, 0); - RemoveWindowSubclass(hwnd, Some(menu_subclass_proc), MENU_SUBCLASS_ID); - SetMenu(hwnd, 0); - DrawMenuBar(hwnd); - } - - Ok(()) - } - pub fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) { unsafe { SendMessageW(hwnd, WM_CLEAR_MENU_DATA, 0, 0); @@ -547,12 +327,12 @@ impl Menu { } pub fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - if !self.hwnds.borrow_mut().iter().any(|h| *h == hwnd) { + if !self.hwnds.iter().any(|h| *h == hwnd) { return Err(crate::Error::NotInitialized); } unsafe { - SetMenu(hwnd, 0); + SetMenu(hwnd, HMENU::default()); DrawMenuBar(hwnd); } @@ -560,7 +340,7 @@ impl Menu { } pub fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> { - if !self.hwnds.borrow_mut().iter().any(|h| *h == hwnd) { + if !self.hwnds.iter().any(|h| *h == hwnd) { return Err(crate::Error::NotInitialized); } @@ -572,17 +352,65 @@ impl Menu { Ok(()) } + pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool { + self.hwnds + .iter() + .find(|h| **h == hwnd) + .map(|hwnd| unsafe { GetMenu(*hwnd) } != HMENU::default()) + .unwrap_or(false) + } + pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) { show_context_menu(hwnd, self.hpopupmenu, x, y) } } -#[derive(Clone)] -pub(crate) struct Submenu(Rc>); +/// A generic child in a menu +#[derive(Debug, Default)] +pub(crate) struct MenuChild { + // shared fields between submenus and menu items + pub type_: MenuItemType, + text: String, + enabled: bool, + parents_hemnu: Vec, + root_menu_haccel_stores: Option>>>, -impl Submenu { - pub fn new(text: &str, enabled: bool) -> Self { - Self(Rc::new(RefCell::new(MenuChild { + // menu item fields + id: u32, + accelerator: Option, + + // predefined menu item fields + predefined_item_type: PredfinedMenuItemType, + + // check menu item fields + checked: bool, + + // icon menu item fields + icon: Option, + + // submenu fields + hmenu: HMENU, + hpopupmenu: HMENU, + pub children: Option>>>, +} + +/// Constructors +impl MenuChild { + pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { + Self { + type_: MenuItemType::Normal, + text: text.to_string(), + enabled, + parents_hemnu: Vec::new(), + id: COUNTER.next(), + accelerator, + root_menu_haccel_stores: Some(Vec::new()), + ..Default::default() + } + } + + pub fn new_submenu(text: &str, enabled: bool) -> Self { + Self { type_: MenuItemType::Submenu, text: text.to_string(), enabled, @@ -592,82 +420,211 @@ impl Submenu { hpopupmenu: unsafe { CreatePopupMenu() }, root_menu_haccel_stores: Some(Vec::new()), ..Default::default() - }))) + } } + pub fn new_predefined(item_type: PredfinedMenuItemType, text: Option) -> Self { + Self { + type_: MenuItemType::Predefined, + text: text.unwrap_or_else(|| item_type.text().to_string()), + enabled: true, + parents_hemnu: Vec::new(), + id: COUNTER.next(), + accelerator: item_type.accelerator(), + predefined_item_type: item_type, + root_menu_haccel_stores: Some(Vec::new()), + ..Default::default() + } + } + + pub fn new_check( + text: &str, + enabled: bool, + checked: bool, + accelerator: Option, + ) -> Self { + Self { + type_: MenuItemType::Check, + text: text.to_string(), + enabled, + parents_hemnu: Vec::new(), + id: COUNTER.next(), + accelerator, + checked, + root_menu_haccel_stores: Some(Vec::new()), + ..Default::default() + } + } + + pub fn new_icon( + text: &str, + enabled: bool, + icon: Option, + accelerator: Option, + ) -> Self { + Self { + type_: MenuItemType::Icon, + text: text.to_string(), + enabled, + parents_hemnu: Vec::new(), + id: COUNTER.next(), + accelerator, + icon, + root_menu_haccel_stores: Some(Vec::new()), + ..Default::default() + } + } +} + +/// Shared methods +impl MenuChild { pub fn id(&self) -> u32 { - self.0.borrow().id() + match self.type_ { + MenuItemType::Submenu => self.hmenu as u32, + _ => self.id, + } } - pub fn hpopupmenu(&self) -> HMENU { - self.0.borrow().hpopupmenu + pub fn text(&self) -> String { + self.parents_hemnu + .first() + .map(|hmenu| { + let mut label = Vec::::new(); + + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STRING; + info.dwTypeData = label.as_mut_ptr(); + + unsafe { GetMenuItemInfoW(*hmenu, self.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) }; + + let text = decode_wide(info.dwTypeData); + text.split('\t').next().unwrap().to_string() + }) + .unwrap_or_else(|| self.text.clone()) } - pub fn add_menu_item(&self, item: &dyn crate::MenuItemExt, op: AddOp) { - let mut flags = 0; - let child = match item.type_() { - MenuItemType::Submenu => { - let submenu = item.as_any().downcast_ref::().unwrap(); - let child = &submenu.0 .0; + pub fn set_text(&mut self, text: &str) { + self.text = if let Some(accelerator) = self.accelerator { + format!("{text}\t{}", accelerator) + } else { + text.to_string() + }; + for parent in &self.parents_hemnu { + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STRING; + info.dwTypeData = encode_wide(&self.text).as_mut_ptr(); - flags |= MF_POPUP; + unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; + } + } - child - } - MenuItemType::Normal => { - let item = item.as_any().downcast_ref::().unwrap(); - let child = &item.0 .0; + pub fn is_enabled(&self) -> bool { + self.parents_hemnu + .first() + .map(|hmenu| { + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STATE; - flags |= MF_STRING; + unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; - child - } + (info.fState & MFS_DISABLED) == 0 + }) + .unwrap_or(self.enabled) + } - MenuItemType::Predefined => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - let child = &item.0 .0; + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + for parent in &self.parents_hemnu { + unsafe { + EnableMenuItem( + *parent, + self.id(), + if enabled { MF_ENABLED } else { MF_DISABLED }, + ) + }; + } + } - let child_ = child.borrow(); + pub fn set_accelerator(&mut self, accelerator: Option) -> crate::Result<()> { + self.accelerator = accelerator; + self.set_text(&self.text.clone()); - match child_.predefined_item_type { - PredfinedMenuItemType::None => return, - PredfinedMenuItemType::Separator => { - flags |= MF_SEPARATOR; - } - _ => { - flags |= MF_STRING; - } - } - - child - } - MenuItemType::Check => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - let child = &item.0 .0; - - flags |= MF_STRING; - if child.borrow().checked { - flags |= MF_CHECKED; - } - - child - } - MenuItemType::Icon => { - let item = item.as_any().downcast_ref::().unwrap(); - let child = &item.0 .0; - - flags |= MF_STRING; - - child + let haccel_stores = self.root_menu_haccel_stores.as_mut().unwrap(); + for store in haccel_stores { + let mut store = store.borrow_mut(); + if let Some(accelerator) = self.accelerator { + AccelAction::add(&mut store, self.id, &accelerator)? + } else { + AccelAction::remove(&mut store, self.id) } } - .clone(); + + Ok(()) + } +} + +/// CheckMenuItem methods +impl MenuChild { + pub fn is_checked(&self) -> bool { + self.parents_hemnu + .first() + .map(|hmenu| { + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STATE; + + unsafe { GetMenuItemInfoW(*hmenu, self.id(), false.into(), &mut info) }; + + (info.fState & MFS_CHECKED) != 0 + }) + .unwrap_or(self.enabled) + } + + pub fn set_checked(&mut self, checked: bool) { + use windows_sys::Win32::UI::WindowsAndMessaging; + + self.checked = checked; + for parent in &self.parents_hemnu { + unsafe { + WindowsAndMessaging::CheckMenuItem( + *parent, + self.id(), + if checked { MF_CHECKED } else { MF_UNCHECKED }, + ) + }; + } + } +} + +/// IconMenuItem methods +impl MenuChild { + pub fn set_icon(&mut self, icon: Option) { + self.icon = icon.clone(); + + 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) }; + } + } +} + +/// Submenu methods +impl MenuChild { + pub fn hpopupmenu(&self) -> HMENU { + self.hpopupmenu + } + + pub fn add_menu_item(&mut self, item: &dyn IsMenuItem, op: AddOp) -> crate::Result<()> { + let (child, mut flags) = inner_menu_child_and_flags!(item); { child @@ -675,18 +632,10 @@ impl Submenu { .root_menu_haccel_stores .as_mut() .unwrap() - .extend_from_slice( - self.0 - .borrow_mut() - .root_menu_haccel_stores - .as_ref() - .unwrap(), - ); + .extend_from_slice(self.root_menu_haccel_stores.as_ref().unwrap()); } { - let mut self_ = self.0.borrow_mut(); - let child_ = child.borrow(); if !child_.enabled { flags |= MF_GRAYED; @@ -700,9 +649,9 @@ impl Submenu { text.push('\t'); text.push_str(&accel_str); - for root_menu in self_.root_menu_haccel_stores.as_mut().unwrap() { + 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_.id(), accelerator)?; } } @@ -711,19 +660,19 @@ impl Submenu { unsafe { match op { AddOp::Append => { - AppendMenuW(self_.hmenu, flags, id, text.as_ptr()); - AppendMenuW(self_.hpopupmenu, flags, id, text.as_ptr()); + AppendMenuW(self.hmenu, flags, id, text.as_ptr()); + AppendMenuW(self.hpopupmenu, flags, id, text.as_ptr()); } AddOp::Insert(position) => { InsertMenuW( - self_.hmenu, + self.hmenu, position as _, flags | MF_BYPOSITION, id, text.as_ptr(), ); InsertMenuW( - self_.hpopupmenu, + self.hpopupmenu, position as _, flags | MF_BYPOSITION, id, @@ -735,7 +684,6 @@ impl Submenu { } { - let self_ = self.0.borrow(); let child_ = child.borrow(); if child_.type_ == MenuItemType::Icon { @@ -747,82 +695,54 @@ impl Submenu { 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_.id, false.into(), &info); + SetMenuItemInfoW(self.hpopupmenu, child_.id, false.into(), &info); }; } } { - let self_ = self.0.borrow(); let mut child_ = child.borrow_mut(); - child_.parents_hemnu.push(self_.hmenu); - child_.parents_hemnu.push(self_.hpopupmenu); + child_.parents_hemnu.push(self.hmenu); + child_.parents_hemnu.push(self.hpopupmenu); } { - let mut self_ = self.0.borrow_mut(); - let children = self_.children.as_mut().unwrap(); + let children = self.children.as_mut().unwrap(); match op { AddOp::Append => children.push(child), AddOp::Insert(position) => children.insert(position, child), } } + + Ok(()) } - pub fn remove(&self, item: &dyn crate::MenuItemExt) -> crate::Result<()> { + pub fn remove(&mut self, item: &dyn IsMenuItem) -> crate::Result<()> { unsafe { - RemoveMenu(self.0.borrow().hmenu, item.id(), MF_BYCOMMAND); - RemoveMenu(self.0.borrow().hpopupmenu, item.id(), MF_BYCOMMAND); + RemoveMenu(self.hmenu, item.id(), MF_BYCOMMAND); + RemoveMenu(self.hpopupmenu, item.id(), MF_BYCOMMAND); } - let child = match item.type_() { - MenuItemType::Submenu => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - MenuItemType::Normal => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - MenuItemType::Predefined => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - &item.0 .0 - } - MenuItemType::Check => { - let item = item - .as_any() - .downcast_ref::() - .unwrap(); - &item.0 .0 - } - MenuItemType::Icon => { - let item = item.as_any().downcast_ref::().unwrap(); - &item.0 .0 - } - }; + let child = item.child(); { let mut child = child.borrow_mut(); let index = child .parents_hemnu .iter() - .position(|h| *h == self.0.borrow().hmenu) + .position(|h| *h == self.hmenu) .ok_or(crate::Error::NotAChildOfThisMenu)?; child.parents_hemnu.remove(index); let index = child .parents_hemnu .iter() - .position(|h| *h == self.0.borrow().hpopupmenu) + .position(|h| *h == self.hpopupmenu) .ok_or(crate::Error::NotAChildOfThisMenu)?; child.parents_hemnu.remove(index); } - let mut self_ = self.0.borrow_mut(); - let children = self_.children.as_mut().unwrap(); + let children = self.children.as_mut().unwrap(); let index = children .iter() .position(|e| e.borrow().id() == item.id()) @@ -832,64 +752,17 @@ impl Submenu { Ok(()) } - pub fn items(&self) -> Vec> { - self.0 - .borrow() - .children + pub fn items(&self) -> Vec> { + self.children .as_ref() .unwrap() .iter() - .map(|c| -> Box { - let child = c.borrow(); - match child.type_ { - MenuItemType::Submenu => Box::new(crate::Submenu(Submenu(c.clone()))), - MenuItemType::Normal => Box::new(crate::MenuItem(MenuItem(c.clone()))), - MenuItemType::Predefined => { - Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone()))) - } - MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))), - MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))), - } - }) + .map(|c| c.borrow().boxed(c.clone())) .collect() } - fn find_by_id(&self, id: u32) -> Option>> { - let self_ = self.0.borrow(); - let children = self_.children.as_ref().unwrap(); - for i in children.iter() { - let item = i.borrow(); - if item.id == id { - return Some(i.clone()); - } - - if item.type_ == MenuItemType::Submenu { - let submenu = Submenu(i.clone()); - if let Some(child) = submenu.find_by_id(id) { - return Some(child); - } - } - } - None - } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) { - show_context_menu(hwnd, self.0.borrow().hpopupmenu, x, y) + show_context_menu(hwnd, self.hpopupmenu, x, y) } pub fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) { @@ -898,7 +771,7 @@ impl Submenu { hwnd, Some(menu_subclass_proc), SUBMENU_SUBCLASS_ID, - Box::into_raw(Box::new(self.clone())) as _, + Box::into_raw(Box::new(self)) as _, ); } } @@ -911,180 +784,93 @@ impl Submenu { } } -#[derive(Clone, Debug)] -pub(crate) struct MenuItem(Rc>); - -impl MenuItem { - pub fn new(text: &str, enabled: bool, accelerator: Option) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Normal, - text: text.to_string(), - enabled, - parents_hemnu: Vec::new(), - id: COUNTER.next(), - accelerator, - root_menu_haccel_stores: Some(Vec::new()), - ..Default::default() - }))) - } - - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) +/// Internal Utilitles +impl MenuChild { + fn find_by_id(&self, id: u32) -> Option>> { + let children = self.children.as_ref().unwrap(); + find_by_id(id, children) } } -#[derive(Clone, Debug)] -pub(crate) struct PredefinedMenuItem(Rc>); +fn find_by_id(id: u32, children: &Vec>>) -> Option>> { + for i in children { + let item = i.borrow(); + if item.id() == id { + return Some(i.clone()); + } -impl PredefinedMenuItem { - pub fn new(item_type: PredfinedMenuItemType, text: Option) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Predefined, - text: text.unwrap_or_else(|| item_type.text().to_string()), - enabled: true, - parents_hemnu: Vec::new(), - id: COUNTER.next(), - accelerator: item_type.accelerator(), - predefined_item_type: item_type, - root_menu_haccel_stores: Some(Vec::new()), - ..Default::default() - }))) + if item.type_ == MenuItemType::Submenu { + if let Some(child) = item.find_by_id(id) { + return Some(child); + } + } } + None +} - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) +fn show_context_menu(hwnd: HWND, hmenu: HMENU, x: f64, y: f64) { + unsafe { + let mut point = POINT { + x: x as _, + y: y as _, + }; + ClientToScreen(hwnd, &mut point); + TrackPopupMenu( + hmenu, + TPM_LEFTALIGN, + point.x, + point.y, + 0, + hwnd, + std::ptr::null(), + ); } } -#[derive(Clone, Debug)] -pub(crate) struct CheckMenuItem(Rc>); +struct AccelAction; -impl CheckMenuItem { - pub fn new(text: &str, enabled: bool, checked: bool, accelerator: Option) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Check, - text: text.to_string(), - enabled, - parents_hemnu: Vec::new(), - id: COUNTER.next(), - accelerator, - checked, - root_menu_haccel_stores: Some(Vec::new()), - ..Default::default() - }))) +impl AccelAction { + fn add( + haccel_store: &mut RefMut, + id: u32, + accelerator: &Accelerator, + ) -> crate::Result<()> { + let accel = accelerator.to_accel(id as _)?; + haccel_store.1.insert(id, Accel(accel)); + + Self::update_store(haccel_store); + + Ok(()) } - pub fn id(&self) -> u32 { - self.0.borrow().id() + fn remove(haccel_store: &mut RefMut, id: u32) { + haccel_store.1.remove(&id); + + Self::update_store(haccel_store) } - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn is_checked(&self) -> bool { - self.0.borrow().is_checked() - } - - pub fn set_checked(&self, checked: bool) { - self.0.borrow_mut().set_checked(checked) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) + fn update_store(haccel_store: &mut RefMut) { + unsafe { + DestroyAcceleratorTable(haccel_store.0); + haccel_store.0 = CreateAcceleratorTableW( + haccel_store + .1 + .values() + .map(|i| i.0) + .collect::>() + .as_ptr(), + haccel_store.1.len() as _, + ); + } } } -#[derive(Clone, Debug)] -pub(crate) struct IconMenuItem(Rc>); - -impl IconMenuItem { - pub fn new( - text: &str, - enabled: bool, - icon: Option, - accelerator: Option, - ) -> Self { - Self(Rc::new(RefCell::new(MenuChild { - type_: MenuItemType::Icon, - text: text.to_string(), - enabled, - parents_hemnu: Vec::new(), - id: COUNTER.next(), - accelerator, - icon, - root_menu_haccel_stores: Some(Vec::new()), - ..Default::default() - }))) - } - - pub fn id(&self) -> u32 { - self.0.borrow().id() - } - - pub fn text(&self) -> String { - self.0.borrow().text() - } - - pub fn set_text(&self, text: &str) { - self.0.borrow_mut().set_text(text) - } - - pub fn is_enabled(&self) -> bool { - self.0.borrow().is_enabled() - } - - pub fn set_enabled(&self, enabled: bool) { - self.0.borrow_mut().set_enabled(enabled) - } - - pub fn set_icon(&self, icon: Option) { - self.0.borrow_mut().set_icon(icon) - } - - pub fn set_accelerator(&self, acccelerator: Option) { - self.0.borrow_mut().set_accelerator(acccelerator) - } +fn create_icon_item_info(hbitmap: HBITMAP) -> MENUITEMINFOW { + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_BITMAP; + info.hbmpItem = hbitmap; + info } const MENU_SUBCLASS_ID: usize = 200; @@ -1104,17 +890,17 @@ unsafe extern "system" fn menu_subclass_proc( if uidsubclass == MENU_SUBCLASS_ID { drop(Box::from_raw(dwrefdata as *mut Menu)); } else { - drop(Box::from_raw(dwrefdata as *mut Submenu)); + drop(Box::from_raw(dwrefdata as *mut MenuChild)); } } if msg == WM_COMMAND { let id = util::LOWORD(wparam as _) as u32; let item = if uidsubclass == MENU_SUBCLASS_ID { - let menu = dwrefdata as *mut Menu; + let menu = dwrefdata as *mut Box; (*menu).find_by_id(id) } else { - let menu = dwrefdata as *mut Submenu; + let menu = dwrefdata as *mut Box; (*menu).find_by_id(id) }; @@ -1158,7 +944,7 @@ unsafe extern "system" fn menu_subclass_proc( PredfinedMenuItemType::Quit => { PostQuitMessage(0); } - PredfinedMenuItemType::About(Some(metadata)) => { + PredfinedMenuItemType::About(Some(ref metadata)) => { show_about_dialog(hwnd, metadata) } @@ -1169,7 +955,7 @@ unsafe extern "system" fn menu_subclass_proc( } if dispatch { - MenuEvent::send(crate::MenuEvent { id }); + MenuEvent::send(MenuEvent { id }); } } } @@ -1181,56 +967,6 @@ unsafe extern "system" fn menu_subclass_proc( } } -struct AccelAction; - -impl AccelAction { - fn add(haccel_store: &mut RefMut, id: u32, accelerator: &Accelerator) { - let accel = accelerator.to_accel(id as _); - haccel_store.1.insert(id, Accel(accel)); - - Self::update_store(haccel_store) - } - fn remove(haccel_store: &mut RefMut, id: u32) { - haccel_store.1.remove(&id); - - Self::update_store(haccel_store) - } - - fn update_store(haccel_store: &mut RefMut) { - unsafe { - DestroyAcceleratorTable(haccel_store.0); - haccel_store.0 = CreateAcceleratorTableW( - haccel_store - .1 - .values() - .map(|i| i.0) - .collect::>() - .as_ptr(), - haccel_store.1.len() as _, - ); - } - } -} - -fn show_context_menu(hwnd: HWND, hmenu: HMENU, x: f64, y: f64) { - unsafe { - let mut point = POINT { - x: x as _, - y: y as _, - }; - ClientToScreen(hwnd, &mut point); - TrackPopupMenu( - hmenu, - TPM_LEFTALIGN, - point.x, - point.y, - 0, - hwnd, - std::ptr::null(), - ); - } -} - enum EditCommand { Copy, Cut, @@ -1268,14 +1004,6 @@ fn execute_edit_command(command: EditCommand) { } } -fn create_icon_item_info(hbitmap: HBITMAP) -> MENUITEMINFOW { - let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - info.cbSize = std::mem::size_of::() as _; - info.fMask = MIIM_BITMAP; - info.hbmpItem = hbitmap; - info -} - fn show_about_dialog(hwnd: HWND, metadata: &AboutMetadata) { use std::fmt::Write;