refactor: move Rc<RefCell<MenuChild>> out of platform_impl (#71)

* refactor: move `Rc<RefCell<MenuChild>>` out of platform_impl

* fix doc test
This commit is contained in:
Amr Bashir 2023-07-18 03:44:52 +03:00 committed by GitHub
parent fafab8544c
commit 0000e56974
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 2167 additions and 2288 deletions

View file

@ -0,0 +1,5 @@
---
"muda": "minor"
---
Changed `MenuItemExt` trait name to `IsMenuItem`

View file

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

View file

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

View file

@ -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<Modifiers>, 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<Modifiers>, key: impl Borrow<Code>) -> 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<Accelerator> {
fn parse_accelerator(accelerator: &str) -> crate::Result<Accelerator> {
let tokens = accelerator.split('+').collect::<Vec<&str>>();
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<Code> {
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<Code> {
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()
);
}

View file

@ -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.

View file

@ -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 iChats available image.
StatusAvailable,
/// Small clear indicator.
StatusNone,
/// Small yellow indicator, similar to iChats idle image.
StatusPartiallyAvailable,
/// Small red indicator, similar to iChats 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,
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
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<S: AsRef<str>>(
text: S,
enabled: bool,
checked: bool,
acccelerator: Option<Accelerator>,
) -> 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<S: AsRef<str>>(&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<Accelerator>) -> 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<Accelerator>) {
self.0.set_accelerator(acccelerator)
self.0.borrow_mut().set_checked(checked)
}
}

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
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<S: AsRef<str>>(
text: S,
enabled: bool,
icon: Option<Icon>,
acccelerator: Option<Accelerator>,
) -> 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<S: AsRef<str>>(&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<Accelerator>) -> crate::Result<()> {
self.0.borrow_mut().set_accelerator(acccelerator)
}
/// Change this menu item icon or remove it.
pub fn set_icon(&self, icon: Option<Icon>) {
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<Accelerator>) {
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<NativeIcon>) {
#[cfg(target_os = "macos")]
self.0.borrow_mut().set_native_icon(_icon)
}
}

15
src/items/mod.rs Normal file
View file

@ -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::*;

View file

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

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
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<S: AsRef<str>>(item: PredfinedMenuItemType, text: Option<S>) -> 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<S: AsRef<str>>(&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<String> {
Some(format!(
"{}{}",

View file

@ -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<RefCell<crate::platform_impl::MenuChild>>);
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<S: AsRef<str>>(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<S: AsRef<str>>(text: S, enabled: bool, items: &[&dyn MenuItemExt]) -> Self {
pub fn with_items<S: AsRef<str>>(
text: S,
enabled: bool,
items: &[&dyn IsMenuItem],
) -> crate::Result<Self> {
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<Box<dyn MenuItemExt>> {
self.0.items()
pub fn items(&self) -> Vec<Box<dyn IsMenuItem>> {
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<S: AsRef<str>>(&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: &gtk::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()
}
}

View file

@ -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,

View file

@ -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<RefCell<crate::platform_impl::Menu>>);
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<Self> {
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<Box<dyn MenuItemExt>> {
self.0.items()
pub fn items(&self) -> Vec<Box<dyn IsMenuItem>> {
self.0.borrow().items()
}
/// Adds this menu to a [`gtk::ApplicationWindow`]
@ -128,7 +141,7 @@ impl Menu {
W: gtk::prelude::IsA<gtk::Container>,
W: gtk::prelude::IsA<gtk::Window>,
{
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<gtk::ApplicationWindow>,
W: gtk::prelude::IsA<gtk::Window>,
{
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<gtk::ApplicationWindow>,
{
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<gtk::ApplicationWindow>,
{
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<W>(&self, w: &W) -> bool
where
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
{
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: &gtk::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()
}
}

View file

@ -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<M: IsA<gtk::Widget>>(
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<M: IsA<gtk::Widget>>(
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();

File diff suppressed because it is too large Load diff

View file

@ -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<String> {
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.

View file

@ -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_(

File diff suppressed because it is too large Load diff

View file

@ -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<RefCell<MenuChild>> {
match self.type_() {
MenuItemType::Submenu => self
.as_any()
.downcast_ref::<crate::Submenu>()
.unwrap()
.0
.clone(),
MenuItemType::Normal => self
.as_any()
.downcast_ref::<crate::MenuItem>()
.unwrap()
.0
.clone(),
MenuItemType::Predefined => self
.as_any()
.downcast_ref::<crate::PredefinedMenuItem>()
.unwrap()
.0
.clone(),
MenuItemType::Check => self
.as_any()
.downcast_ref::<crate::CheckMenuItem>()
.unwrap()
.0
.clone(),
MenuItemType::Icon => self
.as_any()
.downcast_ref::<crate::IconMenuItem>()
.unwrap()
.0
.clone(),
}
}
}
/// Internal utilities
impl MenuChild {
fn boxed(&self, c: Rc<RefCell<MenuChild>>) -> Box<dyn IsMenuItem> {
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)),
}
}
}

View file

@ -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<ACCEL> {
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<VIRTUAL_KEY> {
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 {

File diff suppressed because it is too large Load diff