mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-24 04:11:30 +11:00
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:
parent
fafab8544c
commit
0000e56974
5
.changes/menu-item-trait-name.md
Normal file
5
.changes/menu-item-trait-name.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"muda": "minor"
|
||||||
|
---
|
||||||
|
|
||||||
|
Changed `MenuItemExt` trait name to `IsMenuItem`
|
|
@ -27,12 +27,12 @@ fn main() {
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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| {
|
event_loop_builder.with_msg_hook(move |msg| {
|
||||||
use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG};
|
use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG};
|
||||||
unsafe {
|
unsafe {
|
||||||
let msg = msg as *const MSG;
|
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
|
translated == 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,12 +25,12 @@ fn main() {
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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| {
|
event_loop_builder.with_msg_hook(move |msg| {
|
||||||
use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG};
|
use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG};
|
||||||
unsafe {
|
unsafe {
|
||||||
let msg = msg as *const MSG;
|
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
|
translated == 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,26 +37,54 @@ use std::{borrow::Borrow, hash::Hash, str::FromStr};
|
||||||
pub struct Accelerator {
|
pub struct Accelerator {
|
||||||
pub(crate) mods: Modifiers,
|
pub(crate) mods: Modifiers,
|
||||||
pub(crate) key: Code,
|
pub(crate) key: Code,
|
||||||
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accelerator {
|
impl Accelerator {
|
||||||
/// Creates a new accelerator to define keyboard shortcuts throughout your application.
|
/// 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 {
|
pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
|
||||||
Self {
|
let mut mods = mods.unwrap_or_else(Modifiers::empty);
|
||||||
mods: mods.unwrap_or_else(Modifiers::empty),
|
if mods.contains(Modifiers::META) {
|
||||||
key,
|
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`.
|
/// Returns `true` if this [`Code`] and [`Modifiers`] matches this `Accelerator`.
|
||||||
pub fn matches(&self, modifiers: impl Borrow<Modifiers>, key: impl Borrow<Code>) -> bool {
|
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.
|
// Should be a const but const bit_or doesn't work here.
|
||||||
let base_mods = Modifiers::SHIFT
|
let base_mods = Modifiers::SHIFT | Modifiers::CONTROL | Modifiers::ALT | Modifiers::SUPER;
|
||||||
| Modifiers::CONTROL
|
|
||||||
| Modifiers::ALT
|
|
||||||
| Modifiers::META
|
|
||||||
| Modifiers::SUPER;
|
|
||||||
let modifiers = modifiers.borrow();
|
let modifiers = modifiers.borrow();
|
||||||
let key = key.borrow();
|
let key = key.borrow();
|
||||||
self.mods == *modifiers & base_mods && self.key == *key
|
self.mods == *modifiers & base_mods && self.key == *key
|
||||||
|
@ -73,51 +101,36 @@ 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 mods = Modifiers::empty();
|
||||||
let mut key = Code::Unidentified;
|
let mut key = None;
|
||||||
|
|
||||||
let mut split = accelerator_string.split('+');
|
match tokens.len() {
|
||||||
let len = split.clone().count();
|
// single key accelerator
|
||||||
let parse_key = |token: &str| -> crate::Result<Code> {
|
1 => {
|
||||||
if let Ok(code) = Code::from_str(token) {
|
key = Some(parse_key(tokens[0])?);
|
||||||
match code {
|
|
||||||
Code::Unidentified => Err(crate::Error::AcceleratorParseError(format!(
|
|
||||||
"Couldn't identify \"{}\" as a valid `Code`",
|
|
||||||
token
|
|
||||||
))),
|
|
||||||
_ => Ok(code),
|
|
||||||
}
|
}
|
||||||
} else {
|
// modifiers and key comobo accelerator
|
||||||
Err(crate::Error::AcceleratorParseError(format!(
|
_ => {
|
||||||
"Couldn't identify \"{}\" as a valid `Code`",
|
for raw in tokens {
|
||||||
token
|
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() {
|
if token.is_empty() {
|
||||||
return Err(crate::Error::AcceleratorParseError(
|
return Err(crate::Error::EmptyAcceleratorToken(accelerator.to_string()));
|
||||||
"Unexpected empty token while parsing accelerator".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if key != Code::Unidentified {
|
if key.is_some() {
|
||||||
// at this point we already parsed the modifiers and found a main key but
|
// At this point we have parsed the modifiers and a main key, so by reaching
|
||||||
// the function received more then one main key or it is not in the right order
|
// this code, the function either received more than one main key or
|
||||||
|
// the accelerator is not in the right order
|
||||||
// examples:
|
// examples:
|
||||||
// 1. "Ctrl+Shift+C+A" => only one main key should be allowd.
|
// 1. "Ctrl+Shift+C+A" => only one main key should be allowd.
|
||||||
// 2. "Ctrl+C+Shift" => wrong order
|
// 2. "Ctrl+C+Shift" => wrong order
|
||||||
return Err(crate::Error::AcceleratorParseError(format!(
|
return Err(crate::Error::UnexpectedAcceleratorFormat(
|
||||||
"Unexpected accelerator string format: \"{}\"",
|
accelerator.to_string(),
|
||||||
accelerator_string
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match token.to_uppercase().as_str() {
|
match token.to_uppercase().as_str() {
|
||||||
|
@ -135,89 +148,248 @@ fn parse_accelerator(accelerator_string: &str) -> crate::Result<Accelerator> {
|
||||||
}
|
}
|
||||||
"COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => {
|
"COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mods.set(Modifiers::META, true);
|
mods.set(Modifiers::SUPER, true);
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
mods.set(Modifiers::CONTROL, true);
|
mods.set(Modifiers::CONTROL, true);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
key = parse_key(token.as_str())?;
|
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]
|
#[test]
|
||||||
fn test_parse_accelerator() {
|
fn test_parse_accelerator() {
|
||||||
assert_eq!(
|
macro_rules! assert_parse_accelerator {
|
||||||
parse_accelerator("CTRL+KeyX").unwrap(),
|
($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 {
|
Accelerator {
|
||||||
mods: Modifiers::CONTROL,
|
mods: Modifiers::CONTROL,
|
||||||
key: Code::KeyX,
|
key: Code::KeyX,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse_accelerator("SHIFT+KeyC").unwrap(),
|
assert_parse_accelerator!(
|
||||||
|
"SHIFT+KeyC",
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::SHIFT,
|
mods: Modifiers::SHIFT,
|
||||||
key: Code::KeyC,
|
key: Code::KeyC,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse_accelerator("CTRL+KeyZ").unwrap(),
|
assert_parse_accelerator!(
|
||||||
|
"SHIFT+KeyC",
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::CONTROL,
|
mods: Modifiers::SHIFT,
|
||||||
key: Code::KeyZ,
|
key: Code::KeyC,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse_accelerator("super+ctrl+SHIFT+alt+ArrowUp").unwrap(),
|
assert_parse_accelerator!(
|
||||||
|
"super+ctrl+SHIFT+alt+ArrowUp",
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::META | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT,
|
mods: Modifiers::SUPER | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT,
|
||||||
key: Code::ArrowUp,
|
key: Code::ArrowUp,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_parse_accelerator!(
|
||||||
parse_accelerator("Digit5").unwrap(),
|
"Digit5",
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::empty(),
|
mods: Modifiers::empty(),
|
||||||
key: Code::Digit5,
|
key: Code::Digit5,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_parse_accelerator!(
|
||||||
parse_accelerator("KeyG").unwrap(),
|
"KeyG",
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::empty(),
|
mods: Modifiers::empty(),
|
||||||
key: Code::KeyG,
|
key: Code::KeyG,
|
||||||
|
id: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let acc = parse_accelerator("+G");
|
assert_parse_accelerator!(
|
||||||
assert!(acc.is_err());
|
"SHiFT+F12",
|
||||||
|
|
||||||
let acc = parse_accelerator("SHGSH+G");
|
|
||||||
assert!(acc.is_err());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse_accelerator("SHiFT+F12").unwrap(),
|
|
||||||
Accelerator {
|
Accelerator {
|
||||||
mods: Modifiers::SHIFT,
|
mods: Modifiers::SHIFT,
|
||||||
key: Code::F12,
|
key: Code::F12,
|
||||||
}
|
id: 0,
|
||||||
);
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let acc = parse_accelerator("CTRL+");
|
assert_parse_accelerator!(
|
||||||
assert!(acc.is_err());
|
"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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,12 @@ pub enum Error {
|
||||||
AlreadyInitialized,
|
AlreadyInitialized,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
AcceleratorParseError(String),
|
AcceleratorParseError(String),
|
||||||
#[error("Cannot map {0} to gdk key")]
|
#[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")]
|
||||||
AcceleratorKeyNotSupported(keyboard_types::Code),
|
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.
|
/// Convenient type alias of Result type for muda.
|
||||||
|
|
121
src/icon.rs
121
src/icon.rs
|
@ -164,3 +164,124 @@ impl Icon {
|
||||||
Ok(Icon { inner: win_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,
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// 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`]
|
/// A check menu item inside a [`Menu`] or [`Submenu`]
|
||||||
/// and usually contains a text and a check mark or a similar toggle
|
/// 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
|
/// [`Menu`]: crate::Menu
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
#[derive(Clone)]
|
#[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 {
|
fn type_(&self) -> MenuItemType {
|
||||||
MenuItemType::Check
|
MenuItemType::Check
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -30,60 +33,62 @@ impl CheckMenuItem {
|
||||||
/// Create a new check menu item.
|
/// Create a new check menu item.
|
||||||
///
|
///
|
||||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
/// - `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>>(
|
pub fn new<S: AsRef<str>>(
|
||||||
text: S,
|
text: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
acccelerator: Option<Accelerator>,
|
acccelerator: Option<Accelerator>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(crate::platform_impl::CheckMenuItem::new(
|
Self(Rc::new(RefCell::new(
|
||||||
|
crate::platform_impl::MenuChild::new_check(
|
||||||
text.as_ref(),
|
text.as_ref(),
|
||||||
enabled,
|
enabled,
|
||||||
checked,
|
checked,
|
||||||
acccelerator,
|
acccelerator,
|
||||||
))
|
),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unique identifier associated with this submenu.
|
/// Returns a unique identifier associated with this submenu.
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.0.id()
|
self.0.borrow().id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this check menu item.
|
/// Get the text for this check menu item.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0.text()
|
self.0.borrow().text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this check menu item. `text` could optionally contain
|
/// Get the text for this check menu item. `text` could optionally contain
|
||||||
/// an `&` before a character to assign this character as the mnemonic
|
/// 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) {
|
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.
|
/// Get whether this check menu item is enabled or not.
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.0.is_enabled()
|
self.0.borrow().is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable this check menu item.
|
/// Enable or disable this check menu item.
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
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.
|
/// Get whether this check menu item is checked or not.
|
||||||
pub fn is_checked(&self) -> bool {
|
pub fn is_checked(&self) -> bool {
|
||||||
self.0.is_checked()
|
self.0.borrow().is_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check or Uncheck this check menu item.
|
/// Check or Uncheck this check menu item.
|
||||||
pub fn set_checked(&self, checked: bool) {
|
pub fn set_checked(&self, checked: bool) {
|
||||||
self.0.set_checked(checked)
|
self.0.borrow_mut().set_checked(checked)
|
||||||
}
|
|
||||||
|
|
||||||
/// Set this check menu item accelerator.
|
|
||||||
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
|
|
||||||
self.0.set_accelerator(acccelerator)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,13 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// 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`]
|
/// An icon menu item inside a [`Menu`] or [`Submenu`]
|
||||||
/// and usually contains an icon and a text.
|
/// and usually contains an icon and a text.
|
||||||
|
@ -10,12 +16,13 @@ use crate::{accelerator::Accelerator, icon::Icon, MenuItemExt, MenuItemType};
|
||||||
/// [`Menu`]: crate::Menu
|
/// [`Menu`]: crate::Menu
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
#[derive(Clone)]
|
#[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 {
|
fn type_(&self) -> MenuItemType {
|
||||||
MenuItemType::Icon
|
MenuItemType::Icon
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -26,58 +33,65 @@ unsafe impl MenuItemExt for IconMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// - `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>>(
|
pub fn new<S: AsRef<str>>(
|
||||||
text: S,
|
text: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
acccelerator: Option<Accelerator>,
|
acccelerator: Option<Accelerator>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(crate::platform_impl::IconMenuItem::new(
|
Self(Rc::new(RefCell::new(
|
||||||
text.as_ref(),
|
crate::platform_impl::MenuChild::new_icon(text.as_ref(), enabled, icon, acccelerator),
|
||||||
enabled,
|
)))
|
||||||
icon,
|
|
||||||
acccelerator,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unique identifier associated with this submenu.
|
/// Returns a unique identifier associated with this submenu.
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.0.id()
|
self.0.borrow().id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this check menu item.
|
/// Get the text for this check menu item.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0.text()
|
self.0.borrow().text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this check menu item. `text` could optionally contain
|
/// Get the text for this check menu item. `text` could optionally contain
|
||||||
/// an `&` before a character to assign this character as the mnemonic
|
/// 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) {
|
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.
|
/// Get whether this check menu item is enabled or not.
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.0.is_enabled()
|
self.0.borrow().is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable this check menu item.
|
/// Enable or disable this check menu item.
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
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.
|
/// Change this menu item icon or remove it.
|
||||||
pub fn set_icon(&self, icon: Option<Icon>) {
|
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.
|
/// Change this menu item icon to a native image or remove it.
|
||||||
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
|
///
|
||||||
self.0.set_accelerator(acccelerator)
|
/// ## 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
15
src/items/mod.rs
Normal 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::*;
|
|
@ -1,20 +1,19 @@
|
||||||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
use std::{cell::RefCell, rc::Rc};
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
use crate::{accelerator::Accelerator, MenuItemExt, MenuItemType};
|
use crate::{accelerator::Accelerator, IsMenuItem, MenuItemType};
|
||||||
|
|
||||||
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
|
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
|
||||||
///
|
///
|
||||||
/// [`Menu`]: crate::Menu
|
/// [`Menu`]: crate::Menu
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
#[derive(Clone)]
|
#[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 {
|
fn type_(&self) -> MenuItemType {
|
||||||
MenuItemType::Normal
|
MenuItemType::Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -28,44 +27,44 @@ impl MenuItem {
|
||||||
/// Create a new menu item.
|
/// Create a new menu item.
|
||||||
///
|
///
|
||||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
/// - `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 {
|
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(),
|
text.as_ref(),
|
||||||
enabled,
|
enabled,
|
||||||
acccelerator,
|
acccelerator,
|
||||||
))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unique identifier associated with this menu item.
|
/// Returns a unique identifier associated with this menu item.
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.0.id()
|
self.0.borrow().id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this menu item.
|
/// Get the text for this menu item.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0.text()
|
self.0.borrow().text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the text for this menu item. `text` could optionally contain
|
/// Set the text for this menu item. `text` could optionally contain
|
||||||
/// an `&` before a character to assign this character as the mnemonic
|
/// 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) {
|
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.
|
/// Get whether this menu item is enabled or not.
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.0.is_enabled()
|
self.0.borrow().is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable this menu item.
|
/// Enable or disable this menu item.
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
pub fn set_enabled(&self, enabled: bool) {
|
||||||
self.0.set_enabled(enabled)
|
self.0.borrow_mut().set_enabled(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set this menu item accelerator.
|
/// Set this menu item accelerator.
|
||||||
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
|
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) -> crate::Result<()> {
|
||||||
self.0.set_accelerator(acccelerator)
|
self.0.borrow_mut().set_accelerator(acccelerator)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,9 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// 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};
|
use keyboard_types::{Code, Modifiers};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -11,12 +13,13 @@ pub const CMD_OR_CTRL: Modifiers = Modifiers::META;
|
||||||
pub const CMD_OR_CTRL: Modifiers = Modifiers::CONTROL;
|
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.
|
/// 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 {
|
fn type_(&self) -> MenuItemType {
|
||||||
MenuItemType::Predefined
|
MenuItemType::Predefined
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -156,24 +159,26 @@ impl PredefinedMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new<S: AsRef<str>>(item: PredfinedMenuItemType, text: Option<S>) -> Self {
|
fn new<S: AsRef<str>>(item: PredfinedMenuItemType, text: Option<S>) -> Self {
|
||||||
Self(crate::platform_impl::PredefinedMenuItem::new(
|
Self(Rc::new(RefCell::new(
|
||||||
|
crate::platform_impl::MenuChild::new_predefined(
|
||||||
item,
|
item,
|
||||||
text.map(|t| t.as_ref().to_string()),
|
text.map(|t| t.as_ref().to_string()),
|
||||||
))
|
),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> u32 {
|
fn id(&self) -> u32 {
|
||||||
self.0.id()
|
self.0.borrow().id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this predefined menu item.
|
/// Get the text for this predefined menu item.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0.text()
|
self.0.borrow().text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the text for this predefined menu item.
|
/// Set the text for this predefined menu item.
|
||||||
pub fn set_text<S: AsRef<str>>(&self, text: S) {
|
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 {
|
impl AboutMetadata {
|
||||||
|
#[allow(unused)]
|
||||||
pub(crate) fn full_version(&self) -> Option<String> {
|
pub(crate) fn full_version(&self) -> Option<String> {
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{}{}",
|
"{}{}",
|
|
@ -2,18 +2,21 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// 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`].
|
/// A menu that can be added to a [`Menu`] or another [`Submenu`].
|
||||||
///
|
///
|
||||||
/// [`Menu`]: crate::Menu
|
/// [`Menu`]: crate::Menu
|
||||||
#[derive(Clone)]
|
#[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 {
|
fn type_(&self) -> MenuItemType {
|
||||||
MenuItemType::Submenu
|
MenuItemType::Submenu
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -27,90 +30,101 @@ impl Submenu {
|
||||||
/// Create a new submenu.
|
/// Create a new submenu.
|
||||||
///
|
///
|
||||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
/// - `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 {
|
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.
|
/// 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);
|
let menu = Self::new(text, enabled);
|
||||||
menu.append_items(items);
|
menu.append_items(items)?;
|
||||||
menu
|
Ok(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unique identifier associated with this submenu.
|
/// Returns a unique identifier associated with this submenu.
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.0.id()
|
self.0.borrow().id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a menu item to the end of this menu.
|
/// Add a menu item to the end of this menu.
|
||||||
pub fn append(&self, item: &dyn MenuItemExt) {
|
pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Append)
|
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.
|
/// 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 {
|
for item in items {
|
||||||
self.append(*item);
|
self.append(*item)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a menu item to the beginning of this submenu.
|
/// Add a menu item to the beginning of this submenu.
|
||||||
pub fn prepend(&self, item: &dyn MenuItemExt) {
|
pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Insert(0))
|
self.0.borrow_mut().add_menu_item(item, AddOp::Insert(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add menu items to the beginning of this submenu.
|
/// Add menu items to the beginning of this submenu.
|
||||||
/// It calls [`Menu::prepend`](crate::Menu::prepend) on the first element and
|
/// 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`.
|
/// passes the rest to [`Menu::insert_items`](crate::Menu::insert_items) with position of `1`.
|
||||||
pub fn prepend_items(&self, items: &[&dyn MenuItemExt]) {
|
pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
|
||||||
self.prepend(items[0]);
|
self.insert_items(items, 0)
|
||||||
self.insert_items(&items[1..], 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a menu item at the specified `postion` in the submenu.
|
/// Insert a menu item at the specified `postion` in the submenu.
|
||||||
pub fn insert(&self, item: &dyn MenuItemExt, position: usize) {
|
pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Insert(position))
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.add_menu_item(item, AddOp::Insert(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert menu items at the specified `postion` in the submenu.
|
/// 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() {
|
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.
|
/// Remove a menu item from this submenu.
|
||||||
pub fn remove(&self, item: &dyn MenuItemExt) -> crate::Result<()> {
|
pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.remove(item)
|
self.0.borrow_mut().remove(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of menu items that has been added to this submenu.
|
/// Returns a list of menu items that has been added to this submenu.
|
||||||
pub fn items(&self) -> Vec<Box<dyn MenuItemExt>> {
|
pub fn items(&self) -> Vec<Box<dyn IsMenuItem>> {
|
||||||
self.0.items()
|
self.0.borrow().items()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the text for this submenu.
|
/// Get the text for this submenu.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0.text()
|
self.0.borrow().text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the text for this submenu. `text` could optionally contain
|
/// Set the text for this submenu. `text` could optionally contain
|
||||||
/// an `&` before a character to assign this character as the mnemonic
|
/// 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) {
|
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.
|
/// Get whether this submenu is enabled or not.
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
self.0.is_enabled()
|
self.0.borrow().is_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable this submenu.
|
/// Enable or disable this submenu.
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
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.
|
/// Set this submenu as the Window menu for the application on macOS.
|
||||||
|
@ -119,7 +133,7 @@ impl Submenu {
|
||||||
/// certain other items to the menu.
|
/// certain other items to the menu.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn set_windows_menu_for_nsapp(&self) {
|
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.
|
/// 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".
|
/// which has a title matching the localized word "Help".
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn set_help_menu_for_nsapp(&self) {
|
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 {
|
impl ContextMenu for Submenu {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU {
|
fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU {
|
||||||
self.0.hpopupmenu()
|
self.0.borrow().hpopupmenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) {
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn gtk_context_menu(&self) -> gtk::Menu {
|
fn gtk_context_menu(&self) -> gtk::Menu {
|
||||||
self.0.gtk_context_menu()
|
self.0.borrow_mut().gtk_context_menu()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "macos")]
|
||||||
fn ns_menu(&self) -> *mut std::ffi::c_void {
|
fn ns_menu(&self) -> *mut std::ffi::c_void {
|
||||||
self.0.ns_menu()
|
self.0.borrow().ns_menu()
|
||||||
}
|
}
|
||||||
}
|
}
|
20
src/lib.rs
20
src/lib.rs
|
@ -63,7 +63,7 @@
|
||||||
//! &PredefinedMenuItem::separator(),
|
//! &PredefinedMenuItem::separator(),
|
||||||
//! &menu_item2,
|
//! &menu_item2,
|
||||||
//! ],
|
//! ],
|
||||||
//! ),
|
//! ).unwrap(),
|
||||||
//! ],
|
//! ],
|
||||||
//! );
|
//! );
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -129,14 +129,10 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
use once_cell::sync::{Lazy, OnceCell};
|
||||||
|
|
||||||
pub mod accelerator;
|
pub mod accelerator;
|
||||||
mod check_menu_item;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod icon_menu_item;
|
mod items;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod menu_item;
|
|
||||||
mod platform_impl;
|
mod platform_impl;
|
||||||
mod predefined;
|
|
||||||
mod submenu;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -144,14 +140,12 @@ mod util;
|
||||||
extern crate objc;
|
extern crate objc;
|
||||||
|
|
||||||
pub use self::error::*;
|
pub use self::error::*;
|
||||||
pub use check_menu_item::CheckMenuItem;
|
pub use items::*;
|
||||||
pub use icon_menu_item::IconMenuItem;
|
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
pub mod icon;
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MenuItemType {
|
pub enum MenuItemType {
|
||||||
Submenu,
|
Submenu,
|
||||||
|
@ -174,7 +168,7 @@ impl Default for MenuItemType {
|
||||||
/// This trait is ONLY meant to be implemented internally.
|
/// 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`
|
// 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.
|
// 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
|
/// Get the type of this menu entry
|
||||||
fn type_(&self) -> MenuItemType;
|
fn type_(&self) -> MenuItemType;
|
||||||
|
|
||||||
|
@ -246,7 +240,7 @@ pub trait ContextMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a menu event emitted when a menu item is activated
|
/// Describes a menu event emitted when a menu item is activated
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct MenuEvent {
|
pub struct MenuEvent {
|
||||||
/// Id of the menu item which triggered this event
|
/// Id of the menu item which triggered this event
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
|
|
112
src/menu.rs
112
src/menu.rs
|
@ -2,12 +2,14 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// 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
|
/// A root menu that can be added to a Window on Windows and Linux
|
||||||
/// and used as the app global menu on macOS.
|
/// and used as the app global menu on macOS.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Menu(crate::platform_impl::Menu);
|
pub struct Menu(Rc<RefCell<crate::platform_impl::Menu>>);
|
||||||
|
|
||||||
impl Default for Menu {
|
impl Default for Menu {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -18,14 +20,19 @@ impl Default for Menu {
|
||||||
impl Menu {
|
impl Menu {
|
||||||
/// Creates a new menu.
|
/// Creates a new menu.
|
||||||
pub fn new() -> Self {
|
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.
|
/// 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();
|
let menu = Self::new();
|
||||||
menu.append_items(items);
|
menu.append_items(items)?;
|
||||||
menu
|
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.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
pub fn append(&self, item: &dyn MenuItemExt) {
|
pub fn append(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Append)
|
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.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`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 {
|
for item in items {
|
||||||
self.append(*item);
|
self.append(*item)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a menu item to the beginning of this menu.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
pub fn prepend(&self, item: &dyn MenuItemExt) {
|
pub fn prepend(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Insert(0))
|
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.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
pub fn prepend_items(&self, items: &[&dyn MenuItemExt]) {
|
pub fn prepend_items(&self, items: &[&dyn IsMenuItem]) -> crate::Result<()> {
|
||||||
self.insert_items(items, 0);
|
self.insert_items(items, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a menu item at the specified `postion` in the menu.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`Submenu`]: crate::Submenu
|
||||||
pub fn insert(&self, item: &dyn MenuItemExt, position: usize) {
|
pub fn insert(&self, item: &dyn IsMenuItem, position: usize) -> crate::Result<()> {
|
||||||
self.0.add_menu_item(item, AddOp::Insert(position))
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.add_menu_item(item, AddOp::Insert(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert menu items at the specified `postion` in the menu.
|
/// 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
|
/// - **macOS:** Only [`Submenu`] can be added to the menu
|
||||||
///
|
///
|
||||||
/// [`Submenu`]: crate::Submenu
|
/// [`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() {
|
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.
|
/// Remove a menu item from this menu.
|
||||||
pub fn remove(&self, item: &dyn MenuItemExt) -> crate::Result<()> {
|
pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
|
||||||
self.0.remove(item)
|
self.0.borrow_mut().remove(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of menu items that has been added to this menu.
|
/// Returns a list of menu items that has been added to this menu.
|
||||||
pub fn items(&self) -> Vec<Box<dyn MenuItemExt>> {
|
pub fn items(&self) -> Vec<Box<dyn IsMenuItem>> {
|
||||||
self.0.items()
|
self.0.borrow().items()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds this menu to a [`gtk::ApplicationWindow`]
|
/// Adds this menu to a [`gtk::ApplicationWindow`]
|
||||||
|
@ -128,7 +141,7 @@ impl Menu {
|
||||||
W: gtk::prelude::IsA<gtk::Container>,
|
W: gtk::prelude::IsA<gtk::Container>,
|
||||||
W: gtk::prelude::IsA<gtk::Window>,
|
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.
|
/// Adds this menu to a win32 window.
|
||||||
|
@ -157,7 +170,7 @@ impl Menu {
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn init_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
|
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
|
/// 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
|
/// in the event loop to enable accelerators
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn haccel(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HACCEL {
|
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`]
|
/// Removes this menu from a [`gtk::ApplicationWindow`]
|
||||||
|
@ -175,13 +188,13 @@ impl Menu {
|
||||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||||
W: gtk::prelude::IsA<gtk::Window>,
|
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
|
/// Removes this menu from a win32 window
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn remove_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
|
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`]
|
/// Hides this menu from a [`gtk::ApplicationWindow`]
|
||||||
|
@ -190,13 +203,13 @@ impl Menu {
|
||||||
where
|
where
|
||||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
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
|
/// Hides this menu from a win32 window
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
|
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`]
|
/// Shows this menu on a [`gtk::ApplicationWindow`]
|
||||||
|
@ -205,66 +218,83 @@ impl Menu {
|
||||||
where
|
where
|
||||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
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
|
/// Shows this menu on a win32 window
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
|
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.
|
/// Adds this menu to an NSApp.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn init_for_nsapp(&self) {
|
pub fn init_for_nsapp(&self) {
|
||||||
self.0.init_for_nsapp()
|
self.0.borrow_mut().init_for_nsapp()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes this menu from an NSApp.
|
/// Removes this menu from an NSApp.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn remove_for_nsapp(&self) {
|
pub fn remove_for_nsapp(&self) {
|
||||||
self.0.remove_for_nsapp()
|
self.0.borrow_mut().remove_for_nsapp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextMenu for Menu {
|
impl ContextMenu for Menu {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU {
|
fn hpopupmenu(&self) -> windows_sys::Win32::UI::WindowsAndMessaging::HMENU {
|
||||||
self.0.hpopupmenu()
|
self.0.borrow().hpopupmenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn detach_menu_subclass_from_hwnd(&self, hwnd: isize) {
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn gtk_context_menu(&self) -> gtk::Menu {
|
fn gtk_context_menu(&self) -> gtk::Menu {
|
||||||
self.0.gtk_context_menu()
|
self.0.borrow_mut().gtk_context_menu()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) {
|
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")]
|
#[cfg(target_os = "macos")]
|
||||||
fn ns_menu(&self) -> *mut std::ffi::c_void {
|
fn ns_menu(&self) -> *mut std::ffi::c_void {
|
||||||
self.0.ns_menu()
|
self.0.borrow().ns_menu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use gtk::{prelude::*, AccelGroup};
|
|
||||||
use keyboard_types::{Code, Modifiers};
|
use keyboard_types::{Code, Modifiers};
|
||||||
|
|
||||||
use crate::accelerator::Accelerator;
|
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) {
|
if let Some(gdk_key) = key_to_raw_key(k) {
|
||||||
*gdk_key
|
*gdk_key
|
||||||
} else {
|
} 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))
|
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 {
|
fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType {
|
||||||
let mut result = gdk::ModifierType::empty();
|
let mut result = gdk::ModifierType::empty();
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,8 +11,8 @@ impl Accelerator {
|
||||||
/// Return the string value of this hotkey, without modifiers.
|
/// Return the string value of this hotkey, without modifiers.
|
||||||
///
|
///
|
||||||
/// Returns the empty string if no key equivalent is known.
|
/// Returns the empty string if no key equivalent is known.
|
||||||
pub fn key_equivalent(self) -> String {
|
pub fn key_equivalent(self) -> crate::Result<String> {
|
||||||
match self.key {
|
Ok(match self.key {
|
||||||
Code::KeyA => "a".into(),
|
Code::KeyA => "a".into(),
|
||||||
Code::KeyB => "b".into(),
|
Code::KeyB => "b".into(),
|
||||||
Code::KeyC => "c".into(),
|
Code::KeyC => "c".into(),
|
||||||
|
@ -103,12 +103,8 @@ impl Accelerator {
|
||||||
Code::F22 => "\u{F719}".into(),
|
Code::F22 => "\u{F719}".into(),
|
||||||
Code::F23 => "\u{F71A}".into(),
|
Code::F23 => "\u{F71A}".into(),
|
||||||
Code::F24 => "\u{F71B}".into(),
|
Code::F24 => "\u{F71B}".into(),
|
||||||
_ => {
|
key => return Err(crate::Error::UnrecognizedAcceleratorCode(key.to_string())),
|
||||||
#[cfg(debug_assertions)]
|
})
|
||||||
eprintln!("no key equivalent for {:?}", self);
|
|
||||||
"".into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the modifiers of this hotkey, as an NSEventModifierFlags bitflag.
|
/// Return the modifiers of this hotkey, as an NSEventModifierFlags bitflag.
|
||||||
|
|
|
@ -43,18 +43,15 @@ impl PlatformIcon {
|
||||||
let (width, height) = self.get_size();
|
let (width, height) = self.get_size();
|
||||||
let icon = self.to_png();
|
let icon = self.to_png();
|
||||||
|
|
||||||
let (icon_height, icon_width) = match fixed_height {
|
let (icon_width, icon_height) = match fixed_height {
|
||||||
Some(fixed_height) => {
|
Some(fixed_height) => {
|
||||||
let icon_height: f64 = fixed_height;
|
let icon_height: f64 = fixed_height;
|
||||||
let icon_width: f64 = (width as f64) / (height as f64 / icon_height);
|
let icon_width: f64 = (width as f64) / (height as f64 / icon_height);
|
||||||
|
|
||||||
(icon_height, icon_width)
|
(icon_width, icon_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => (width as f64, height as f64),
|
||||||
let (icon_height, icon_width) = self.get_size();
|
|
||||||
(icon_height as f64, icon_width as f64)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let nsdata = NSData::dataWithBytes_length_(
|
let nsdata = NSData::dataWithBytes_length_(
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,4 +12,58 @@ mod platform;
|
||||||
#[path = "macos/mod.rs"]
|
#[path = "macos/mod.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{items::*, IsMenuItem, MenuItemType};
|
||||||
|
|
||||||
pub(crate) use self::platform::*;
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::accelerator::Accelerator;
|
||||||
|
|
||||||
impl Accelerator {
|
impl Accelerator {
|
||||||
// Convert a hotkey to an 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 mut virt_key = FVIRTKEY;
|
||||||
let key_mods: Modifiers = self.mods;
|
let key_mods: Modifiers = self.mods;
|
||||||
if key_mods.contains(Modifiers::CONTROL) {
|
if key_mods.contains(Modifiers::CONTROL) {
|
||||||
|
@ -27,7 +27,7 @@ impl Accelerator {
|
||||||
virt_key |= FSHIFT;
|
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;
|
let mod_code = vk_code >> 8;
|
||||||
if mod_code & 0x1 != 0 {
|
if mod_code & 0x1 != 0 {
|
||||||
virt_key |= FSHIFT;
|
virt_key |= FSHIFT;
|
||||||
|
@ -40,17 +40,17 @@ impl Accelerator {
|
||||||
}
|
}
|
||||||
let raw_key = vk_code & 0x00ff;
|
let raw_key = vk_code & 0x00ff;
|
||||||
|
|
||||||
ACCEL {
|
Ok(ACCEL {
|
||||||
fVirt: virt_key,
|
fVirt: virt_key,
|
||||||
key: raw_key,
|
key: raw_key,
|
||||||
cmd: menu_id,
|
cmd: menu_id,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to build accelerators table from Key
|
// used to build accelerators table from Key
|
||||||
fn key_to_vk(key: &Code) -> VIRTUAL_KEY {
|
fn key_to_vk(key: &Code) -> crate::Result<VIRTUAL_KEY> {
|
||||||
match key {
|
Ok(match key {
|
||||||
Code::KeyA => unsafe { VkKeyScanW('a' as u16) as u16 },
|
Code::KeyA => unsafe { VkKeyScanW('a' as u16) as u16 },
|
||||||
Code::KeyB => unsafe { VkKeyScanW('b' as u16) as u16 },
|
Code::KeyB => unsafe { VkKeyScanW('b' as u16) as u16 },
|
||||||
Code::KeyC => unsafe { VkKeyScanW('c' 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::MediaPlayPause => VK_MEDIA_PLAY_PAUSE,
|
||||||
Code::LaunchMail => VK_LAUNCH_MAIL,
|
Code::LaunchMail => VK_LAUNCH_MAIL,
|
||||||
Code::Convert => VK_CONVERT,
|
Code::Convert => VK_CONVERT,
|
||||||
key => panic!("Unsupported key: {}", key),
|
key => return Err(crate::Error::UnrecognizedAcceleratorCode(key.to_string())),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Accelerator {
|
impl fmt::Display for Accelerator {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue