mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
feat: add accelerator module (#17)
* Add accelerator module * Add Linux port * Add macOS port * Add Windows port * Remove unused types * Fix doc tests * Add more variants
This commit is contained in:
parent
28ffd206fa
commit
e33c5f0daf
|
@ -13,6 +13,7 @@ categories = ["gui"]
|
|||
[dependencies]
|
||||
crossbeam-channel = "0.5"
|
||||
once_cell = "1.10"
|
||||
keyboard-types = "0.6"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.34"
|
||||
|
@ -26,6 +27,7 @@ features = [
|
|||
]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gdk = "0.15"
|
||||
gtk = "0.15"
|
||||
libxdo = "0.6.0"
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use muda::{menu_event_receiver, Menu, NativeMenuItem};
|
||||
use keyboard_types::Code;
|
||||
use muda::{
|
||||
accelerator::{Accelerator, Mods},
|
||||
menu_event_receiver, Menu, NativeMenuItem,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -19,7 +23,11 @@ fn main() {
|
|||
|
||||
let mut file_menu = menu_bar.add_submenu("&File", true);
|
||||
let mut open_item = file_menu.add_item("&Open", true, None);
|
||||
let mut save_item = file_menu.add_item("&Save", true, Some("CommandOrCtrl+S"));
|
||||
let mut save_item = file_menu.add_item(
|
||||
"&Save",
|
||||
true,
|
||||
Some(Accelerator::new(Mods::Ctrl, Code::KeyS)),
|
||||
);
|
||||
file_menu.add_native_item(NativeMenuItem::Minimize);
|
||||
file_menu.add_native_item(NativeMenuItem::CloseWindow);
|
||||
file_menu.add_native_item(NativeMenuItem::Quit);
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use muda::{menu_event_receiver, Menu, NativeMenuItem};
|
||||
use keyboard_types::Code;
|
||||
use muda::{
|
||||
accelerator::{Accelerator, Mods},
|
||||
menu_event_receiver, Menu, NativeMenuItem,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::EventLoopBuilderExtMacOS;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -37,7 +41,11 @@ fn main() {
|
|||
|
||||
let mut file_menu = menu_bar.add_submenu("&File", true);
|
||||
let mut open_item = file_menu.add_item("&Open", true, None);
|
||||
let mut save_item = file_menu.add_item("&Save", true, Some("CommandOrCtrl+S"));
|
||||
let mut save_item = file_menu.add_item(
|
||||
"&Save",
|
||||
true,
|
||||
Some(Accelerator::new(Mods::Ctrl, Code::KeyS)),
|
||||
);
|
||||
file_menu.add_native_item(NativeMenuItem::Minimize);
|
||||
file_menu.add_native_item(NativeMenuItem::CloseWindow);
|
||||
file_menu.add_native_item(NativeMenuItem::Quit);
|
||||
|
|
288
src/accelerator.rs
Normal file
288
src/accelerator.rs
Normal file
|
@ -0,0 +1,288 @@
|
|||
//! Accelerators describe keyboard shortcuts defined by the application.
|
||||
//!
|
||||
//! [`Accelerator`s](crate::accelerator::Accelerator) are used to define a keyboard shortcut consisting
|
||||
//! of an optional combination of modifier keys (provided by [`SysMods`](crate::accelerator::SysMods),
|
||||
//! [`RawMods`](crate::accelerator::RawMods) or [`Modifiers`](crate::accelerator::Modifiers)) and
|
||||
//! one key ([`Code`](crate::accelerator::Code)).
|
||||
//!
|
||||
//! # Examples
|
||||
//! They can be created directly
|
||||
//! ```
|
||||
//! # use muda::accelerator::{Accelerator, Mods, Modifiers, Code};
|
||||
//! #
|
||||
//! let accelerator = Accelerator::new(Mods::Shift, Code::KeyQ);
|
||||
//! let accelerator_with_raw_mods = Accelerator::new(Mods::Shift, Code::KeyQ);
|
||||
//! let accelerator_without_mods = Accelerator::new(None, Code::KeyQ);
|
||||
//! # assert_eq!(accelerator, accelerator_with_raw_mods);
|
||||
//! ```
|
||||
//! or from `&str`, note that all modifiers
|
||||
//! have to be listed before the non-modifier key, `shift+alt+KeyQ` is legal,
|
||||
//! whereas `shift+q+alt` is not.
|
||||
//! ```
|
||||
//! # use muda::accelerator::{Accelerator, Mods};
|
||||
//! #
|
||||
//! let accelerator: Accelerator = "shift+alt+KeyQ".parse().unwrap();
|
||||
//! #
|
||||
//! # // This assert exists to ensure a test breaks once the
|
||||
//! # // statement above about ordering is no longer valid.
|
||||
//! # assert!("shift+KeyQ+alt".parse::<Accelerator>().is_err());
|
||||
//! ```
|
||||
//!
|
||||
|
||||
pub use keyboard_types::{Code, Modifiers};
|
||||
use std::{borrow::Borrow, hash::Hash, str::FromStr};
|
||||
|
||||
/// Base `Accelerator` functions.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct Accelerator {
|
||||
pub(crate) mods: Modifiers,
|
||||
pub(crate) key: Code,
|
||||
}
|
||||
|
||||
impl Accelerator {
|
||||
/// Creates a new accelerator to define keyboard shortcuts throughout your application.
|
||||
pub fn new(mods: impl Into<Option<Modifiers>>, key: Code) -> Self {
|
||||
Self {
|
||||
mods: mods.into().unwrap_or_else(Modifiers::empty),
|
||||
key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`Code`] and [`Modifiers`] matches this `Accelerator`.
|
||||
///
|
||||
/// [`Code`]: Code
|
||||
/// [`Modifiers`]: crate::accelerator::Modifiers
|
||||
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::SUPER;
|
||||
let modifiers = modifiers.borrow();
|
||||
let key = key.borrow();
|
||||
self.mods == *modifiers & base_mods && self.key == *key
|
||||
}
|
||||
}
|
||||
|
||||
// Accelerator::from_str is available to be backward
|
||||
// compatible with tauri and it also open the option
|
||||
// to generate accelerator from string
|
||||
impl FromStr for Accelerator {
|
||||
type Err = AcceleratorParseError;
|
||||
fn from_str(accelerator_string: &str) -> Result<Self, Self::Err> {
|
||||
parse_accelerator(accelerator_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the active modifier keys.
|
||||
///
|
||||
/// This is intended to be clearer than [`Modifiers`], when describing accelerators.
|
||||
///
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
pub enum Mods {
|
||||
None,
|
||||
Alt,
|
||||
Ctrl,
|
||||
Command,
|
||||
CommandOrCtrl,
|
||||
Meta,
|
||||
Shift,
|
||||
AltCtrl,
|
||||
AltMeta,
|
||||
AltShift,
|
||||
CtrlShift,
|
||||
CtrlMeta,
|
||||
MetaShift,
|
||||
AltCtrlMeta,
|
||||
AltCtrlShift,
|
||||
AltMetaShift,
|
||||
CtrlMetaShift,
|
||||
AltCtrlMetaShift,
|
||||
}
|
||||
|
||||
impl From<Mods> for Option<Modifiers> {
|
||||
fn from(src: Mods) -> Option<Modifiers> {
|
||||
Some(src.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mods> for Modifiers {
|
||||
fn from(src: Mods) -> Modifiers {
|
||||
let (alt, ctrl, meta, shift) = match src {
|
||||
Mods::None => (false, false, false, false),
|
||||
Mods::Alt => (true, false, false, false),
|
||||
Mods::Ctrl => (false, true, false, false),
|
||||
Mods::Command => (false, false, true, false),
|
||||
#[cfg(target_os = "macos")]
|
||||
Mods::CommandOrCtrl => (false, false, true, false),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Mods::CommandOrCtrl => (false, true, false, false),
|
||||
Mods::Meta => (false, false, true, false),
|
||||
Mods::Shift => (false, false, false, true),
|
||||
Mods::AltCtrl => (true, true, false, false),
|
||||
Mods::AltMeta => (true, false, true, false),
|
||||
Mods::AltShift => (true, false, false, true),
|
||||
Mods::CtrlMeta => (false, true, true, false),
|
||||
Mods::CtrlShift => (false, true, false, true),
|
||||
Mods::MetaShift => (false, false, true, true),
|
||||
Mods::AltCtrlMeta => (true, true, true, false),
|
||||
Mods::AltMetaShift => (true, false, true, true),
|
||||
Mods::AltCtrlShift => (true, true, false, true),
|
||||
Mods::CtrlMetaShift => (false, true, true, true),
|
||||
Mods::AltCtrlMetaShift => (true, true, true, true),
|
||||
};
|
||||
let mut mods = Modifiers::empty();
|
||||
mods.set(Modifiers::ALT, alt);
|
||||
mods.set(Modifiers::CONTROL, ctrl);
|
||||
mods.set(Modifiers::SUPER, meta);
|
||||
mods.set(Modifiers::SHIFT, shift);
|
||||
mods
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AcceleratorParseError(String);
|
||||
|
||||
impl std::fmt::Display for AcceleratorParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[AcceleratorParseError]: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_accelerator(accelerator_string: &str) -> Result<Accelerator, AcceleratorParseError> {
|
||||
let mut mods = Modifiers::empty();
|
||||
let mut key = Code::Unidentified;
|
||||
|
||||
for raw in accelerator_string.split('+') {
|
||||
let token = raw.trim().to_string();
|
||||
if token.is_empty() {
|
||||
return Err(AcceleratorParseError(
|
||||
"Unexpected empty token while parsing accelerator".into(),
|
||||
));
|
||||
}
|
||||
|
||||
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(AcceleratorParseError(format!(
|
||||
"Unexpected accelerator string format: \"{}\"",
|
||||
accelerator_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::SUPER, 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);
|
||||
}
|
||||
_ => {
|
||||
if let Ok(code) = Code::from_str(token.as_str()) {
|
||||
match code {
|
||||
Code::Unidentified => {
|
||||
return Err(AcceleratorParseError(format!(
|
||||
"Couldn't identify \"{}\" as a valid `Code`",
|
||||
token
|
||||
)))
|
||||
}
|
||||
_ => key = code,
|
||||
}
|
||||
} else {
|
||||
return Err(AcceleratorParseError(format!(
|
||||
"Couldn't identify \"{}\" as a valid `Code`",
|
||||
token
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Accelerator { key, mods })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_accelerator() {
|
||||
assert_eq!(
|
||||
parse_accelerator("CTRL+KeyX").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::CONTROL,
|
||||
key: Code::KeyX,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("SHIFT+KeyC").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::SHIFT,
|
||||
key: Code::KeyC,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("CTRL+KeyZ").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::CONTROL,
|
||||
key: Code::KeyZ,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("super+ctrl+SHIFT+alt+ArrowUp").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::SUPER | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT,
|
||||
key: Code::ArrowUp,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("Digit5").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::empty(),
|
||||
key: Code::Digit5,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("KeyG").unwrap(),
|
||||
Accelerator {
|
||||
mods: Modifiers::empty(),
|
||||
key: Code::KeyG,
|
||||
}
|
||||
);
|
||||
|
||||
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(),
|
||||
Accelerator {
|
||||
mods: Modifiers::SHIFT,
|
||||
key: Code::F12,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_accelerator("CmdOrCtrl+Space").unwrap(),
|
||||
Accelerator {
|
||||
#[cfg(target_os = "macos")]
|
||||
mods: Modifiers::SUPER,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mods: Modifiers::CONTROL,
|
||||
key: Code::Space,
|
||||
}
|
||||
);
|
||||
|
||||
let acc = parse_accelerator("CTRL+");
|
||||
assert!(acc.is_err());
|
||||
}
|
36
src/lib.rs
36
src/lib.rs
|
@ -3,14 +3,14 @@
|
|||
//!
|
||||
//! Before you can add submenus and menu items, you first need a root or a base menu.
|
||||
//! ```no_run
|
||||
//! let mut menu = Menu::new();
|
||||
//! let mut menu = muda::Menu::new();
|
||||
//! ```
|
||||
//!
|
||||
//! # Adding submens to the root menu
|
||||
//!
|
||||
//! Once you have a root menu you can start adding [`Submenu`]s by using [`Menu::add_submenu`].
|
||||
//! ```no_run
|
||||
//! let mut menu = Menu::new();
|
||||
//! let mut menu = muda::Menu::new();
|
||||
//! let file_menu = menu.add_submenu("File", true);
|
||||
//! let edit_menu = menu.add_submenu("Edit", true);
|
||||
//! ```
|
||||
|
@ -19,22 +19,22 @@
|
|||
//!
|
||||
//! Once you have a [`Submenu`] you can star creating more [`Submenu`]s or [`MenuItem`]s.
|
||||
//! ```no_run
|
||||
//! let mut menu = Menu::new();
|
||||
//! let mut menu = muda::Menu::new();
|
||||
//!
|
||||
//! let file_menu = menu.add_submenu("File", true);
|
||||
//! let open_item = file_menu.add_text_item("Open", true);
|
||||
//! let save_item = file_menu.add_text_item("Save", true);
|
||||
//! let mut file_menu = menu.add_submenu("File", true);
|
||||
//! let open_item = file_menu.add_item("Open", true, None);
|
||||
//! let save_item = file_menu.add_item("Save", true, None);
|
||||
//!
|
||||
//! let edit_menu = menu.add_submenu("Edit", true);
|
||||
//! let copy_item = file_menu.add_text_item("Copy", true);
|
||||
//! let cut_item = file_menu.add_text_item("Cut", true);
|
||||
//! let mut edit_menu = menu.add_submenu("Edit", true);
|
||||
//! let copy_item = file_menu.add_item("Copy", true, None);
|
||||
//! let cut_item = file_menu.add_item("Cut", true, None);
|
||||
//! ```
|
||||
//!
|
||||
//! # Add your root menu to a Window (Windows and Linux Only)
|
||||
//!
|
||||
//! You can use [`Menu`] to display a top menu in a Window on Windows and Linux.
|
||||
//! ```no_run
|
||||
//! let mut menu = Menu::new();
|
||||
//! ```ignore
|
||||
//! let mut menu = muda::Menu::new();
|
||||
//! // --snip--
|
||||
//! #[cfg(target_os = "windows")]
|
||||
//! menu.init_for_hwnd(window.hwnd() as isize);
|
||||
|
@ -48,8 +48,8 @@
|
|||
//!
|
||||
//! You can use [`menu_event_receiver`] to get a reference to the [`MenuEventReceiver`]
|
||||
//! which you can use to listen to events when a menu item is activated
|
||||
//! ```no_run
|
||||
//! if let Ok(event) = menu_event_receiver().try_recv() {
|
||||
//! ```ignore
|
||||
//! if let Ok(event) = muda::menu_event_receiver().try_recv() {
|
||||
//! match event.id {
|
||||
//! _ if event.id == save_item.id() => {
|
||||
//! println!("Save menu item activated");
|
||||
|
@ -59,9 +59,11 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
use accelerator::Accelerator;
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub mod accelerator;
|
||||
mod counter;
|
||||
mod platform_impl;
|
||||
|
||||
|
@ -85,8 +87,8 @@ pub struct MenuEvent {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let mut menu = Menu::new();
|
||||
/// ```no_run
|
||||
/// let mut menu = muda::Menu::new();
|
||||
/// let file_menu = menu.add_submenu("File", true);
|
||||
/// let edit_menu = menu.add_submenu("Edit", true);
|
||||
/// ```
|
||||
|
@ -278,7 +280,7 @@ impl Submenu {
|
|||
&mut self,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> MenuItem {
|
||||
MenuItem(self.0.add_item(label, enabled, accelerator))
|
||||
}
|
||||
|
@ -294,7 +296,7 @@ impl Submenu {
|
|||
label: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> CheckMenuItem {
|
||||
CheckMenuItem(self.0.add_check_item(label, enabled, checked, accelerator))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
use gtk::{prelude::*, AccelGroup};
|
||||
use keyboard_types::{Code, Modifiers};
|
||||
|
||||
use crate::accelerator::Accelerator;
|
||||
|
||||
pub fn to_gtk_menemenoic<S: AsRef<str>>(string: S) -> String {
|
||||
string
|
||||
.as_ref()
|
||||
|
@ -6,39 +11,168 @@ pub fn to_gtk_menemenoic<S: AsRef<str>>(string: S) -> String {
|
|||
.replace("[~~]", "&&")
|
||||
}
|
||||
|
||||
pub fn to_gtk_accelerator<S: AsRef<str>>(accelerator: S) -> String {
|
||||
let accelerator = accelerator.as_ref();
|
||||
let mut s = accelerator.split("+");
|
||||
let count = s.clone().count();
|
||||
let (mod1, mod2, key) = {
|
||||
if count == 2 {
|
||||
(s.next().unwrap(), None, s.next().unwrap())
|
||||
} else if count == 3 {
|
||||
(
|
||||
s.next().unwrap(),
|
||||
Some(s.next().unwrap()),
|
||||
s.next().unwrap(),
|
||||
)
|
||||
} else {
|
||||
panic!("Unsupported accelerator format: {}", accelerator)
|
||||
pub fn register_accelerator<M: IsA<gtk::Widget>>(
|
||||
item: &M,
|
||||
accel_group: &AccelGroup,
|
||||
menu_key: &Accelerator,
|
||||
) {
|
||||
let accel_key = match &menu_key.key {
|
||||
Code::KeyA => 'A' as u32,
|
||||
Code::KeyB => 'B' as u32,
|
||||
Code::KeyC => 'C' as u32,
|
||||
Code::KeyD => 'D' as u32,
|
||||
Code::KeyE => 'E' as u32,
|
||||
Code::KeyF => 'F' as u32,
|
||||
Code::KeyG => 'G' as u32,
|
||||
Code::KeyH => 'H' as u32,
|
||||
Code::KeyI => 'I' as u32,
|
||||
Code::KeyJ => 'J' as u32,
|
||||
Code::KeyK => 'K' as u32,
|
||||
Code::KeyL => 'L' as u32,
|
||||
Code::KeyM => 'M' as u32,
|
||||
Code::KeyN => 'N' as u32,
|
||||
Code::KeyO => 'O' as u32,
|
||||
Code::KeyP => 'P' as u32,
|
||||
Code::KeyQ => 'Q' as u32,
|
||||
Code::KeyR => 'R' as u32,
|
||||
Code::KeyS => 'S' as u32,
|
||||
Code::KeyT => 'T' as u32,
|
||||
Code::KeyU => 'U' as u32,
|
||||
Code::KeyV => 'V' as u32,
|
||||
Code::KeyW => 'W' as u32,
|
||||
Code::KeyX => 'X' as u32,
|
||||
Code::KeyY => 'Y' as u32,
|
||||
Code::KeyZ => 'Z' as u32,
|
||||
Code::Digit0 => '0' as u32,
|
||||
Code::Digit1 => '1' as u32,
|
||||
Code::Digit2 => '2' as u32,
|
||||
Code::Digit3 => '3' as u32,
|
||||
Code::Digit4 => '4' as u32,
|
||||
Code::Digit5 => '5' as u32,
|
||||
Code::Digit6 => '6' as u32,
|
||||
Code::Digit7 => '7' as u32,
|
||||
Code::Digit8 => '8' as u32,
|
||||
Code::Digit9 => '9' as u32,
|
||||
Code::Comma => ',' as u32,
|
||||
Code::Minus => '-' as u32,
|
||||
Code::Period => '.' as u32,
|
||||
Code::Space => ' ' as u32,
|
||||
Code::Equal => '=' as u32,
|
||||
Code::Semicolon => ';' as u32,
|
||||
Code::Slash => '/' as u32,
|
||||
Code::Backslash => '\\' as u32,
|
||||
Code::Quote => '\'' as u32,
|
||||
Code::Backquote => '`' as u32,
|
||||
Code::BracketLeft => '[' as u32,
|
||||
Code::BracketRight => ']' as u32,
|
||||
k => {
|
||||
if let Some(gdk_key) = key_to_raw_key(k) {
|
||||
*gdk_key
|
||||
} else {
|
||||
dbg!("Cannot map key {:?}", k);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut gtk_accelerator = parse_mod(mod1).to_string();
|
||||
if let Some(mod2) = mod2 {
|
||||
gtk_accelerator.push_str(parse_mod(mod2));
|
||||
}
|
||||
gtk_accelerator.push_str(key);
|
||||
|
||||
gtk_accelerator
|
||||
item.add_accelerator(
|
||||
"activate",
|
||||
accel_group,
|
||||
accel_key,
|
||||
modifiers_to_gdk_modifier_type(menu_key.mods),
|
||||
gtk::AccelFlags::VISIBLE,
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_mod(modifier: &str) -> &str {
|
||||
match modifier.to_uppercase().as_str() {
|
||||
"SHIFT" => "<Shift>",
|
||||
"CONTROL" | "CTRL" | "COMMAND" | "COMMANDORCONTROL" | "COMMANDORCTRL" => "<Ctrl>",
|
||||
"ALT" => "<Alt>",
|
||||
"SUPER" | "META" | "WIN" => "<Meta>",
|
||||
_ => panic!("Unsupported modifier: {}", modifier),
|
||||
}
|
||||
fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType {
|
||||
let mut result = gdk::ModifierType::empty();
|
||||
|
||||
result.set(
|
||||
gdk::ModifierType::MOD1_MASK,
|
||||
modifiers.contains(Modifiers::ALT),
|
||||
);
|
||||
result.set(
|
||||
gdk::ModifierType::CONTROL_MASK,
|
||||
modifiers.contains(Modifiers::CONTROL),
|
||||
);
|
||||
result.set(
|
||||
gdk::ModifierType::SHIFT_MASK,
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
result.set(
|
||||
gdk::ModifierType::META_MASK,
|
||||
modifiers.contains(Modifiers::SUPER),
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn key_to_raw_key(src: &Code) -> Option<gdk::keys::Key> {
|
||||
use gdk::keys::constants::*;
|
||||
Some(match src {
|
||||
Code::Escape => Escape,
|
||||
Code::Backspace => BackSpace,
|
||||
|
||||
Code::Tab => Tab,
|
||||
Code::Enter => Return,
|
||||
|
||||
Code::ControlLeft => Control_L,
|
||||
Code::AltLeft => Alt_L,
|
||||
Code::ShiftLeft => Shift_L,
|
||||
Code::MetaLeft => Super_L,
|
||||
|
||||
Code::ControlRight => Control_R,
|
||||
Code::AltRight => Alt_R,
|
||||
Code::ShiftRight => Shift_R,
|
||||
Code::MetaRight => Super_R,
|
||||
|
||||
Code::CapsLock => Caps_Lock,
|
||||
Code::F1 => F1,
|
||||
Code::F2 => F2,
|
||||
Code::F3 => F3,
|
||||
Code::F4 => F4,
|
||||
Code::F5 => F5,
|
||||
Code::F6 => F6,
|
||||
Code::F7 => F7,
|
||||
Code::F8 => F8,
|
||||
Code::F9 => F9,
|
||||
Code::F10 => F10,
|
||||
Code::F11 => F11,
|
||||
Code::F12 => F12,
|
||||
Code::F13 => F13,
|
||||
Code::F14 => F14,
|
||||
Code::F15 => F15,
|
||||
Code::F16 => F16,
|
||||
Code::F17 => F17,
|
||||
Code::F18 => F18,
|
||||
Code::F19 => F19,
|
||||
Code::F20 => F20,
|
||||
Code::F21 => F21,
|
||||
Code::F22 => F22,
|
||||
Code::F23 => F23,
|
||||
Code::F24 => F24,
|
||||
|
||||
Code::PrintScreen => Print,
|
||||
Code::ScrollLock => Scroll_Lock,
|
||||
// Pause/Break not audio.
|
||||
Code::Pause => Pause,
|
||||
|
||||
Code::Insert => Insert,
|
||||
Code::Delete => Delete,
|
||||
Code::Home => Home,
|
||||
Code::End => End,
|
||||
Code::PageUp => Page_Up,
|
||||
Code::PageDown => Page_Down,
|
||||
|
||||
Code::NumLock => Num_Lock,
|
||||
|
||||
Code::ArrowUp => Up,
|
||||
Code::ArrowDown => Down,
|
||||
Code::ArrowLeft => Left,
|
||||
Code::ArrowRight => Right,
|
||||
|
||||
Code::ContextMenu => Menu,
|
||||
Code::WakeUp => WakeUp,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
mod accelerator;
|
||||
|
||||
use crate::{counter::Counter, NativeMenuItem};
|
||||
use accelerator::{to_gtk_accelerator, to_gtk_menemenoic};
|
||||
use crate::{accelerator::Accelerator, counter::Counter, NativeMenuItem};
|
||||
use accelerator::{register_accelerator, to_gtk_menemenoic};
|
||||
use gtk::{prelude::*, Orientation};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
|
@ -14,7 +14,7 @@ struct MenuEntry {
|
|||
enabled: bool,
|
||||
checked: bool,
|
||||
id: u64,
|
||||
accelerator: Option<String>,
|
||||
accelerator: Option<Accelerator>,
|
||||
r#type: MenuEntryType,
|
||||
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ impl Submenu {
|
|||
&mut self,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> MenuItem {
|
||||
let label = label.as_ref().to_string();
|
||||
let id = COUNTER.next();
|
||||
|
@ -257,7 +257,7 @@ impl Submenu {
|
|||
enabled,
|
||||
r#type: MenuEntryType::MenuItem(Vec::new()),
|
||||
id,
|
||||
accelerator: accelerator.map(|s| s.to_string()),
|
||||
accelerator: accelerator.clone(),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
|
@ -265,13 +265,7 @@ impl Submenu {
|
|||
|
||||
if let MenuEntryType::Submenu(native_menus) = &mut inner.r#type {
|
||||
for (_, menu) in native_menus {
|
||||
let item = create_gtk_menu_item(
|
||||
&label,
|
||||
enabled,
|
||||
&accelerator.map(|s| s.to_string()),
|
||||
id,
|
||||
&*self.1,
|
||||
);
|
||||
let item = create_gtk_menu_item(&label, enabled, &accelerator, id, &*self.1);
|
||||
menu.append(&item);
|
||||
if let MenuEntryType::MenuItem(native_items) = &mut entry.borrow_mut().r#type {
|
||||
native_items.push(item);
|
||||
|
@ -304,7 +298,7 @@ impl Submenu {
|
|||
label: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> CheckMenuItem {
|
||||
let label = label.as_ref().to_string();
|
||||
let id = COUNTER.next();
|
||||
|
@ -315,7 +309,7 @@ impl Submenu {
|
|||
checked,
|
||||
r#type: MenuEntryType::CheckMenuItem(Vec::new()),
|
||||
id,
|
||||
accelerator: accelerator.map(|s| s.to_string()),
|
||||
accelerator: accelerator.clone(),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
|
@ -327,7 +321,7 @@ impl Submenu {
|
|||
&label,
|
||||
enabled,
|
||||
checked,
|
||||
&accelerator.map(|s| s.to_string()),
|
||||
&accelerator,
|
||||
id,
|
||||
&*self.1,
|
||||
);
|
||||
|
@ -511,21 +505,14 @@ fn create_gtk_submenu(label: &str, enabled: bool) -> (gtk::MenuItem, gtk::Menu)
|
|||
fn create_gtk_menu_item(
|
||||
label: &str,
|
||||
enabled: bool,
|
||||
accelerator: &Option<String>,
|
||||
accelerator: &Option<Accelerator>,
|
||||
id: u64,
|
||||
accel_group: >k::AccelGroup,
|
||||
) -> gtk::MenuItem {
|
||||
let item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(label));
|
||||
item.set_sensitive(enabled);
|
||||
if let Some(accelerator) = accelerator {
|
||||
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
|
||||
item.add_accelerator(
|
||||
"activate",
|
||||
accel_group,
|
||||
key,
|
||||
modifiers,
|
||||
gtk::AccelFlags::VISIBLE,
|
||||
);
|
||||
register_accelerator(&item, accel_group, accelerator);
|
||||
}
|
||||
item.connect_activate(move |_| {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
|
@ -538,7 +525,7 @@ fn create_gtk_check_menu_item(
|
|||
label: &str,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: &Option<String>,
|
||||
accelerator: &Option<Accelerator>,
|
||||
id: u64,
|
||||
accel_group: >k::AccelGroup,
|
||||
) -> gtk::CheckMenuItem {
|
||||
|
@ -546,14 +533,7 @@ fn create_gtk_check_menu_item(
|
|||
item.set_sensitive(enabled);
|
||||
item.set_active(checked);
|
||||
if let Some(accelerator) = accelerator {
|
||||
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
|
||||
item.add_accelerator(
|
||||
"activate",
|
||||
accel_group,
|
||||
key,
|
||||
modifiers,
|
||||
gtk::AccelFlags::VISIBLE,
|
||||
);
|
||||
register_accelerator(&item, accel_group, accelerator);
|
||||
}
|
||||
item.connect_activate(move |_| {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
|
|
|
@ -1,108 +1,133 @@
|
|||
use cocoa::appkit::NSEventModifierFlags;
|
||||
use keyboard_types::{Code, Modifiers};
|
||||
|
||||
use crate::accelerator::Accelerator;
|
||||
|
||||
/// Mnemonic is deprecated since macOS 10
|
||||
pub fn remove_mnemonic(string: impl AsRef<str>) -> String {
|
||||
string.as_ref().replace("&", "")
|
||||
}
|
||||
|
||||
/// Returns a tuple of (Key, Modifier)
|
||||
pub fn parse_accelerator(accelerator: impl AsRef<str>) -> (String, NSEventModifierFlags) {
|
||||
let accelerator = accelerator.as_ref();
|
||||
let mut s = accelerator.split("+");
|
||||
let count = s.clone().count();
|
||||
let (mod1, mod2, key) = {
|
||||
if count == 2 {
|
||||
(s.next().unwrap(), None, s.next().unwrap())
|
||||
} else if count == 3 {
|
||||
(
|
||||
s.next().unwrap(),
|
||||
Some(s.next().unwrap()),
|
||||
s.next().unwrap(),
|
||||
)
|
||||
} else {
|
||||
panic!("Unsupported accelerator format: {}", accelerator)
|
||||
impl Accelerator {
|
||||
/// Return the string value of this hotkey, for use with Cocoa `NSResponder`
|
||||
/// objects.
|
||||
///
|
||||
/// Returns the empty string if no key equivalent is known.
|
||||
pub fn key_equivalent(&self) -> String {
|
||||
match self.key {
|
||||
Code::KeyA => "a".into(),
|
||||
Code::KeyB => "b".into(),
|
||||
Code::KeyC => "c".into(),
|
||||
Code::KeyD => "d".into(),
|
||||
Code::KeyE => "e".into(),
|
||||
Code::KeyF => "f".into(),
|
||||
Code::KeyG => "g".into(),
|
||||
Code::KeyH => "h".into(),
|
||||
Code::KeyI => "i".into(),
|
||||
Code::KeyJ => "j".into(),
|
||||
Code::KeyK => "k".into(),
|
||||
Code::KeyL => "l".into(),
|
||||
Code::KeyM => "m".into(),
|
||||
Code::KeyN => "n".into(),
|
||||
Code::KeyO => "o".into(),
|
||||
Code::KeyP => "p".into(),
|
||||
Code::KeyQ => "q".into(),
|
||||
Code::KeyR => "r".into(),
|
||||
Code::KeyS => "s".into(),
|
||||
Code::KeyT => "t".into(),
|
||||
Code::KeyU => "u".into(),
|
||||
Code::KeyV => "v".into(),
|
||||
Code::KeyW => "w".into(),
|
||||
Code::KeyX => "x".into(),
|
||||
Code::KeyY => "y".into(),
|
||||
Code::KeyZ => "z".into(),
|
||||
Code::Digit0 => "0".into(),
|
||||
Code::Digit1 => "1".into(),
|
||||
Code::Digit2 => "2".into(),
|
||||
Code::Digit3 => "3".into(),
|
||||
Code::Digit4 => "4".into(),
|
||||
Code::Digit5 => "5".into(),
|
||||
Code::Digit6 => "6".into(),
|
||||
Code::Digit7 => "7".into(),
|
||||
Code::Digit8 => "8".into(),
|
||||
Code::Digit9 => "9".into(),
|
||||
Code::Comma => ",".into(),
|
||||
Code::Minus => "-".into(),
|
||||
Code::Period => ".".into(),
|
||||
Code::Space => "\u{0020}".into(),
|
||||
Code::Equal => "=".into(),
|
||||
Code::Semicolon => ";".into(),
|
||||
Code::Slash => "/".into(),
|
||||
Code::Backslash => "\\".into(),
|
||||
Code::Quote => "\'".into(),
|
||||
Code::Backquote => "`".into(),
|
||||
Code::BracketLeft => "[".into(),
|
||||
Code::BracketRight => "]".into(),
|
||||
Code::Tab => "⇥".into(),
|
||||
Code::Escape => "\u{001b}".into(),
|
||||
// from NSText.h
|
||||
Code::Enter => "\u{0003}".into(),
|
||||
Code::Backspace => "\u{0008}".into(),
|
||||
Code::Delete => "\u{007f}".into(),
|
||||
// from NSEvent.h
|
||||
Code::Insert => "\u{F727}".into(),
|
||||
Code::Home => "\u{F729}".into(),
|
||||
Code::End => "\u{F72B}".into(),
|
||||
Code::PageUp => "\u{F72C}".into(),
|
||||
Code::PageDown => "\u{F72D}".into(),
|
||||
Code::PrintScreen => "\u{F72E}".into(),
|
||||
Code::ScrollLock => "\u{F72F}".into(),
|
||||
Code::ArrowUp => "\u{F700}".into(),
|
||||
Code::ArrowDown => "\u{F701}".into(),
|
||||
Code::ArrowLeft => "\u{F702}".into(),
|
||||
Code::ArrowRight => "\u{F703}".into(),
|
||||
Code::F1 => "\u{F704}".into(),
|
||||
Code::F2 => "\u{F705}".into(),
|
||||
Code::F3 => "\u{F706}".into(),
|
||||
Code::F4 => "\u{F707}".into(),
|
||||
Code::F5 => "\u{F708}".into(),
|
||||
Code::F6 => "\u{F709}".into(),
|
||||
Code::F7 => "\u{F70A}".into(),
|
||||
Code::F8 => "\u{F70B}".into(),
|
||||
Code::F9 => "\u{F70C}".into(),
|
||||
Code::F10 => "\u{F70D}".into(),
|
||||
Code::F11 => "\u{F70E}".into(),
|
||||
Code::F12 => "\u{F70F}".into(),
|
||||
Code::F13 => "\u{F710}".into(),
|
||||
Code::F14 => "\u{F711}".into(),
|
||||
Code::F15 => "\u{F712}".into(),
|
||||
Code::F16 => "\u{F713}".into(),
|
||||
Code::F17 => "\u{F714}".into(),
|
||||
Code::F18 => "\u{F715}".into(),
|
||||
Code::F19 => "\u{F716}".into(),
|
||||
Code::F20 => "\u{F717}".into(),
|
||||
Code::F21 => "\u{F718}".into(),
|
||||
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()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut mods = NSEventModifierFlags::empty();
|
||||
let mod1_flag = parse_mod(mod1);
|
||||
mods |= mod1_flag;
|
||||
if let Some(mod2) = mod2 {
|
||||
let mod2_flag = parse_mod(mod2);
|
||||
mods |= mod2_flag;
|
||||
}
|
||||
|
||||
let key_equivalent = parse_key(key);
|
||||
|
||||
(key_equivalent, mods)
|
||||
}
|
||||
|
||||
fn parse_mod(modifier: &str) -> NSEventModifierFlags {
|
||||
match modifier.to_uppercase().as_str() {
|
||||
"SHIFT" => NSEventModifierFlags::NSShiftKeyMask,
|
||||
"CONTROL" | "CTRL" => NSEventModifierFlags::NSControlKeyMask,
|
||||
"OPTION" | "ALT" => NSEventModifierFlags::NSAlternateKeyMask,
|
||||
"COMMAND" | "CMD" | "SUPER" | "COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL"
|
||||
| "CMDORCONTROL" => NSEventModifierFlags::NSCommandKeyMask,
|
||||
_ => panic!("Unsupported modifier: {}", modifier),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_key(key: &str) -> String {
|
||||
match key.to_uppercase().as_str() {
|
||||
"SPACE" => "\u{0020}".into(),
|
||||
"BACKSPACE" => "\u{0008}".into(),
|
||||
"TAB" => "⇥".into(),
|
||||
"ENTER" | "RETURN" => "\u{0003}".into(),
|
||||
"ESC" | "ESCAPE" => "\u{001b}".into(),
|
||||
"PAGEUP" => "\u{F72C}".into(),
|
||||
"PAGEDOWN" => "\u{F72D}".into(),
|
||||
"END" => "\u{F72B}".into(),
|
||||
"HOME" => "\u{F729}".into(),
|
||||
"LEFTARROW" => "\u{F702}".into(),
|
||||
"UPARROW" => "\u{F700}".into(),
|
||||
"RIGHTARROW" => "\u{F703}".into(),
|
||||
"DOWNARROW" => "\u{F701}".into(),
|
||||
"DELETE" => "\u{007f}".into(),
|
||||
"0" => "0".into(),
|
||||
"1" => "1".into(),
|
||||
"2" => "2".into(),
|
||||
"3" => "3".into(),
|
||||
"4" => "4".into(),
|
||||
"5" => "5".into(),
|
||||
"6" => "6".into(),
|
||||
"7" => "7".into(),
|
||||
"8" => "8".into(),
|
||||
"9" => "9".into(),
|
||||
"A" => "a".into(),
|
||||
"B" => "b".into(),
|
||||
"C" => "c".into(),
|
||||
"D" => "d".into(),
|
||||
"E" => "e".into(),
|
||||
"F" => "f".into(),
|
||||
"G" => "g".into(),
|
||||
"H" => "h".into(),
|
||||
"I" => "i".into(),
|
||||
"J" => "j".into(),
|
||||
"K" => "k".into(),
|
||||
"L" => "l".into(),
|
||||
"M" => "m".into(),
|
||||
"N" => "n".into(),
|
||||
"O" => "o".into(),
|
||||
"P" => "p".into(),
|
||||
"Q" => "q".into(),
|
||||
"R" => "r".into(),
|
||||
"S" => "s".into(),
|
||||
"T" => "t".into(),
|
||||
"U" => "u".into(),
|
||||
"V" => "v".into(),
|
||||
"W" => "w".into(),
|
||||
"X" => "x".into(),
|
||||
"Y" => "y".into(),
|
||||
"Z" => "z".into(),
|
||||
"," => ",".into(),
|
||||
"." => ".".into(),
|
||||
"/" => "/".into(),
|
||||
_ => panic!("Unsupported modifier: {}", key),
|
||||
pub fn key_modifier_mask(&self) -> NSEventModifierFlags {
|
||||
let mods: Modifiers = self.mods;
|
||||
let mut flags = NSEventModifierFlags::empty();
|
||||
if mods.contains(Modifiers::SHIFT) {
|
||||
flags.insert(NSEventModifierFlags::NSShiftKeyMask);
|
||||
}
|
||||
if mods.contains(Modifiers::SUPER) {
|
||||
flags.insert(NSEventModifierFlags::NSCommandKeyMask);
|
||||
}
|
||||
if mods.contains(Modifiers::ALT) {
|
||||
flags.insert(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
}
|
||||
if mods.contains(Modifiers::CONTROL) {
|
||||
flags.insert(NSEventModifierFlags::NSControlKeyMask);
|
||||
}
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::accelerator::Accelerator;
|
||||
use crate::counter::Counter;
|
||||
use crate::platform_impl::platform_impl::accelerator::{parse_accelerator, remove_mnemonic};
|
||||
use crate::platform_impl::platform_impl::accelerator::remove_mnemonic;
|
||||
use cocoa::{
|
||||
appkit::{NSButton, NSEventModifierFlags, NSMenuItem},
|
||||
base::{id, nil, BOOL, NO, YES},
|
||||
|
@ -29,7 +30,7 @@ impl MenuItem {
|
|||
label: S,
|
||||
enabled: bool,
|
||||
selector: Sel,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
let (id, ns_menu_item) = make_menu_item(&remove_mnemonic(&label), selector, accelerator);
|
||||
|
||||
|
@ -95,7 +96,7 @@ impl CheckMenuItem {
|
|||
enabled: bool,
|
||||
checked: bool,
|
||||
selector: Sel,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
let (id, ns_menu_item) = make_menu_item(&remove_mnemonic(&label), selector, accelerator);
|
||||
|
||||
|
@ -169,7 +170,11 @@ impl CheckMenuItem {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn make_menu_item(title: &str, selector: Sel, accelerator: Option<&str>) -> (u64, *mut Object) {
|
||||
pub fn make_menu_item(
|
||||
title: &str,
|
||||
selector: Sel,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> (u64, *mut Object) {
|
||||
let alloc = make_menu_item_alloc();
|
||||
let menu_id = COUNTER.next();
|
||||
|
||||
|
@ -221,12 +226,13 @@ fn make_menu_item_from_alloc(
|
|||
alloc: *mut Object,
|
||||
title: *mut Object,
|
||||
selector: Sel,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> *mut Object {
|
||||
unsafe {
|
||||
let (key_equivalent, masks) = match accelerator {
|
||||
Some(accelerator) => {
|
||||
let (key, mods) = parse_accelerator(accelerator);
|
||||
let key = accelerator.key_equivalent();
|
||||
let mods = accelerator.key_modifier_mask();
|
||||
let key = NSString::alloc(nil).init_str(&key);
|
||||
(key, mods)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
mod accelerator;
|
||||
mod menu_item;
|
||||
|
||||
use crate::platform_impl::platform_impl::menu_item::make_menu_item;
|
||||
use crate::accelerator::{RawMods, SysMods};
|
||||
use crate::NativeMenuItem;
|
||||
use crate::{accelerator::Accelerator, platform_impl::platform_impl::menu_item::make_menu_item};
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplication, NSMenu, NSMenuItem},
|
||||
base::{id, nil, selector, NO},
|
||||
foundation::{NSAutoreleasePool, NSString},
|
||||
};
|
||||
use keyboard_types::Code;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use self::accelerator::remove_mnemonic;
|
||||
|
@ -90,7 +92,7 @@ impl Submenu {
|
|||
&mut self,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> MenuItem {
|
||||
let item = MenuItem::new(label, enabled, sel!(fireMenubarAction:), accelerator);
|
||||
unsafe {
|
||||
|
@ -110,17 +112,25 @@ impl Submenu {
|
|||
None,
|
||||
)
|
||||
}
|
||||
NativeMenuItem::CloseWindow => {
|
||||
make_menu_item("Close Window", selector("performClose:"), Some("Command+W"))
|
||||
}
|
||||
NativeMenuItem::Quit => {
|
||||
make_menu_item("Quit", selector("terminate:"), Some("Command+Q"))
|
||||
}
|
||||
NativeMenuItem::Hide => make_menu_item("Hide", selector("hide:"), Some("Command+H")),
|
||||
NativeMenuItem::CloseWindow => make_menu_item(
|
||||
"Close Window",
|
||||
selector("performClose:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyW)),
|
||||
),
|
||||
NativeMenuItem::Quit => make_menu_item(
|
||||
"Quit",
|
||||
selector("terminate:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyQ)),
|
||||
),
|
||||
NativeMenuItem::Hide => make_menu_item(
|
||||
"Hide",
|
||||
selector("hide:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyH)),
|
||||
),
|
||||
NativeMenuItem::HideOthers => make_menu_item(
|
||||
"Hide Others",
|
||||
selector("hideOtherApplications:"),
|
||||
Some("Alt+H"),
|
||||
Some(Accelerator::new(RawMods::Alt, Code::KeyH)),
|
||||
),
|
||||
NativeMenuItem::ShowAll => {
|
||||
make_menu_item("Show All", selector("unhideAllApplications:"), None)
|
||||
|
@ -128,24 +138,44 @@ impl Submenu {
|
|||
NativeMenuItem::ToggleFullScreen => make_menu_item(
|
||||
"Toggle Full Screen",
|
||||
selector("toggleFullScreen:"),
|
||||
Some("Ctrl+F"),
|
||||
Some(Accelerator::new(RawMods::Ctrl, Code::KeyF)),
|
||||
),
|
||||
NativeMenuItem::Minimize => make_menu_item(
|
||||
"Minimize",
|
||||
selector("performMiniaturize:"),
|
||||
Some("Command+M"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyM)),
|
||||
),
|
||||
NativeMenuItem::Zoom => make_menu_item("Zoom", selector("performZoom:"), None),
|
||||
NativeMenuItem::Copy => make_menu_item("Copy", selector("copy:"), Some("Command+C")),
|
||||
NativeMenuItem::Cut => make_menu_item("Cut", selector("cut:"), Some("Command+X")),
|
||||
NativeMenuItem::Paste => make_menu_item("Paste", selector("paste:"), Some("Command+V")),
|
||||
NativeMenuItem::Undo => make_menu_item("Undo", selector("undo:"), Some("Command+Z")),
|
||||
NativeMenuItem::Redo => {
|
||||
make_menu_item("Redo", selector("redo:"), Some("Command+Shift+Z"))
|
||||
}
|
||||
NativeMenuItem::SelectAll => {
|
||||
make_menu_item("Select All", selector("selectAll:"), Some("Command+A"))
|
||||
}
|
||||
NativeMenuItem::Copy => make_menu_item(
|
||||
"Copy",
|
||||
selector("copy:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyC)),
|
||||
),
|
||||
NativeMenuItem::Cut => make_menu_item(
|
||||
"Cut",
|
||||
selector("cut:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyX)),
|
||||
),
|
||||
NativeMenuItem::Paste => make_menu_item(
|
||||
"Paste",
|
||||
selector("paste:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyV)),
|
||||
),
|
||||
NativeMenuItem::Undo => make_menu_item(
|
||||
"Undo",
|
||||
selector("undo:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyZ)),
|
||||
),
|
||||
NativeMenuItem::Redo => make_menu_item(
|
||||
"Redo",
|
||||
selector("redo:"),
|
||||
Some(Accelerator::new(SysMods::CmdShift, Code::KeyZ)),
|
||||
),
|
||||
NativeMenuItem::SelectAll => make_menu_item(
|
||||
"Select All",
|
||||
selector("selectAll:"),
|
||||
Some(Accelerator::new(SysMods::Cmd, Code::KeyA)),
|
||||
),
|
||||
NativeMenuItem::Services => unsafe {
|
||||
let (_, item) = make_menu_item("Services", sel!(fireMenubarAction:), None);
|
||||
let app_class = class!(NSApplication);
|
||||
|
@ -165,7 +195,7 @@ impl Submenu {
|
|||
label: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> CheckMenuItem {
|
||||
let item = CheckMenuItem::new(
|
||||
label,
|
||||
|
|
|
@ -1,115 +1,243 @@
|
|||
use windows_sys::Win32::UI::WindowsAndMessaging::{FALT, FCONTROL, FSHIFT, FVIRTKEY};
|
||||
use std::fmt;
|
||||
|
||||
/// Returns a tuple of (Key, Modifier, a string representation to be used in menu items)
|
||||
pub fn parse_accelerator<S: AsRef<str>>(accelerator: S) -> (u16, u32, String) {
|
||||
let accelerator = accelerator.as_ref();
|
||||
let mut s = accelerator.split("+");
|
||||
let count = s.clone().count();
|
||||
let (mod1, mod2, key) = {
|
||||
if count == 2 {
|
||||
(s.next().unwrap(), None, s.next().unwrap())
|
||||
} else if count == 3 {
|
||||
(
|
||||
s.next().unwrap(),
|
||||
Some(s.next().unwrap()),
|
||||
s.next().unwrap(),
|
||||
)
|
||||
} else {
|
||||
panic!("Unsupported accelerator format: {}", accelerator)
|
||||
use keyboard_types::{Code, Modifiers};
|
||||
use windows_sys::Win32::UI::{
|
||||
Input::KeyboardAndMouse::*,
|
||||
WindowsAndMessaging::{ACCEL, FALT, FCONTROL, FSHIFT, FVIRTKEY},
|
||||
};
|
||||
|
||||
use crate::accelerator::Accelerator;
|
||||
|
||||
impl Accelerator {
|
||||
// Convert a hotkey to an accelerator.
|
||||
pub fn to_accel(&self, menu_id: u16) -> ACCEL {
|
||||
let mut virt_key = FVIRTKEY;
|
||||
let key_mods: Modifiers = self.mods;
|
||||
if key_mods.contains(Modifiers::CONTROL) {
|
||||
virt_key |= FCONTROL;
|
||||
}
|
||||
if key_mods.contains(Modifiers::ALT) {
|
||||
virt_key |= FALT;
|
||||
}
|
||||
if key_mods.contains(Modifiers::SHIFT) {
|
||||
virt_key |= FSHIFT;
|
||||
}
|
||||
};
|
||||
|
||||
let mut accel_str = String::new();
|
||||
let mut mods_vk = FVIRTKEY;
|
||||
let vk_code = key_to_vk(&self.key);
|
||||
let mod_code = vk_code >> 8;
|
||||
if mod_code & 0x1 != 0 {
|
||||
virt_key |= FSHIFT;
|
||||
}
|
||||
if mod_code & 0x02 != 0 {
|
||||
virt_key |= FCONTROL;
|
||||
}
|
||||
if mod_code & 0x04 != 0 {
|
||||
virt_key |= FALT;
|
||||
}
|
||||
let raw_key = vk_code & 0x00ff;
|
||||
|
||||
let (mod1_vk, mod1_str) = parse_mod(mod1);
|
||||
accel_str.push_str(mod1_str);
|
||||
accel_str.push_str("+");
|
||||
mods_vk |= mod1_vk;
|
||||
if let Some(mod2) = mod2 {
|
||||
let (mod2_vk, mod2_str) = parse_mod(mod2);
|
||||
accel_str.push_str(mod2_str);
|
||||
accel_str.push_str("+");
|
||||
mods_vk |= mod2_vk;
|
||||
}
|
||||
let (key_vk, key_str) = parse_key(key);
|
||||
accel_str.push_str(key_str);
|
||||
|
||||
(key_vk, mods_vk, accel_str)
|
||||
}
|
||||
|
||||
fn parse_mod(modifier: &str) -> (u32, &str) {
|
||||
match modifier.to_uppercase().as_str() {
|
||||
"SHIFT" => (FSHIFT, "Shift"),
|
||||
"CONTROL" | "CTRL" | "COMMAND" | "COMMANDORCONTROL" | "COMMANDORCTRL" => (FCONTROL, "Ctrl"),
|
||||
"ALT" => (FALT, "Alt"),
|
||||
_ => panic!("Unsupported modifier: {}", modifier),
|
||||
ACCEL {
|
||||
fVirt: virt_key as u8,
|
||||
key: raw_key as u16,
|
||||
cmd: menu_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_key(key: &str) -> (u16, &str) {
|
||||
match key.to_uppercase().as_str() {
|
||||
"SPACE" => (0x20, "Space"),
|
||||
"BACKSPACE" => (0x08, "Backspace"),
|
||||
"TAB" => (0x09, "Tab"),
|
||||
"ENTER" | "RETURN" => (0x0D, "Enter"),
|
||||
"CAPSLOCK" => (0x14, "Caps Lock"),
|
||||
"ESC" | "ESCAPE" => (0x1B, "Esc"),
|
||||
"PAGEUP" => (0x21, "Page Up"),
|
||||
"PAGEDOWN" => (0x22, "Page Down"),
|
||||
"END" => (0x23, "End"),
|
||||
"HOME" => (0x24, "Home"),
|
||||
"LEFTARROW" => (0x25, "Left Arrow"),
|
||||
"UPARROW" => (0x26, "Up Arrow"),
|
||||
"RIGHTARROW" => (0x27, "Right Arrow"),
|
||||
"DOWNARROW" => (0x28, "Down Arrow"),
|
||||
"DELETE" => (0x2E, "Del"),
|
||||
"0" => (0x30, "0"),
|
||||
"1" => (0x31, "1"),
|
||||
"2" => (0x32, "2"),
|
||||
"3" => (0x33, "3"),
|
||||
"4" => (0x34, "4"),
|
||||
"5" => (0x35, "5"),
|
||||
"6" => (0x36, "6"),
|
||||
"7" => (0x37, "7"),
|
||||
"8" => (0x38, "8"),
|
||||
"9" => (0x39, "9"),
|
||||
"A" => (0x41, "A"),
|
||||
"B" => (0x42, "B"),
|
||||
"C" => (0x43, "C"),
|
||||
"D" => (0x44, "D"),
|
||||
"E" => (0x45, "E"),
|
||||
"F" => (0x46, "F"),
|
||||
"G" => (0x47, "G"),
|
||||
"H" => (0x48, "H"),
|
||||
"I" => (0x49, "I"),
|
||||
"J" => (0x4A, "J"),
|
||||
"K" => (0x4B, "K"),
|
||||
"L" => (0x4C, "L"),
|
||||
"M" => (0x4D, "M"),
|
||||
"N" => (0x4E, "N"),
|
||||
"O" => (0x4F, "O"),
|
||||
"P" => (0x50, "P"),
|
||||
"Q" => (0x51, "Q"),
|
||||
"R" => (0x52, "R"),
|
||||
"S" => (0x53, "S"),
|
||||
"T" => (0x54, "T"),
|
||||
"U" => (0x55, "U"),
|
||||
"V" => (0x56, "V"),
|
||||
"W" => (0x57, "W"),
|
||||
"X" => (0x58, "X"),
|
||||
"Y" => (0x59, "Y"),
|
||||
"Z" => (0x5A, "Z"),
|
||||
"NUM0" | "NUMPAD0" => (0x60, "Num 0"),
|
||||
"NUM1" | "NUMPAD1" => (0x61, "Num 1"),
|
||||
"NUM2" | "NUMPAD2" => (0x62, "Num 2"),
|
||||
"NUM3" | "NUMPAD3" => (0x63, "Num 3"),
|
||||
"NUM4" | "NUMPAD4" => (0x64, "Num 4"),
|
||||
"NUM5" | "NUMPAD5" => (0x65, "Num 5"),
|
||||
"NUM6" | "NUMPAD6" => (0x66, "Num 6"),
|
||||
"NUM7" | "NUMPAD7" => (0x67, "Num 7"),
|
||||
"NUM8" | "NUMPAD8" => (0x68, "Num 8"),
|
||||
"NUM9" | "NUMPAD9" => (0x69, "Num 9"),
|
||||
_ => panic!("Unsupported modifier: {}", key),
|
||||
// used to build accelerators table from Key
|
||||
fn key_to_vk(key: &Code) -> VIRTUAL_KEY {
|
||||
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 },
|
||||
Code::KeyD => unsafe { VkKeyScanW('d' as u16) as u16 },
|
||||
Code::KeyE => unsafe { VkKeyScanW('e' as u16) as u16 },
|
||||
Code::KeyF => unsafe { VkKeyScanW('f' as u16) as u16 },
|
||||
Code::KeyG => unsafe { VkKeyScanW('g' as u16) as u16 },
|
||||
Code::KeyH => unsafe { VkKeyScanW('h' as u16) as u16 },
|
||||
Code::KeyI => unsafe { VkKeyScanW('i' as u16) as u16 },
|
||||
Code::KeyJ => unsafe { VkKeyScanW('j' as u16) as u16 },
|
||||
Code::KeyK => unsafe { VkKeyScanW('k' as u16) as u16 },
|
||||
Code::KeyL => unsafe { VkKeyScanW('l' as u16) as u16 },
|
||||
Code::KeyM => unsafe { VkKeyScanW('m' as u16) as u16 },
|
||||
Code::KeyN => unsafe { VkKeyScanW('n' as u16) as u16 },
|
||||
Code::KeyO => unsafe { VkKeyScanW('o' as u16) as u16 },
|
||||
Code::KeyP => unsafe { VkKeyScanW('p' as u16) as u16 },
|
||||
Code::KeyQ => unsafe { VkKeyScanW('q' as u16) as u16 },
|
||||
Code::KeyR => unsafe { VkKeyScanW('r' as u16) as u16 },
|
||||
Code::KeyS => unsafe { VkKeyScanW('s' as u16) as u16 },
|
||||
Code::KeyT => unsafe { VkKeyScanW('t' as u16) as u16 },
|
||||
Code::KeyU => unsafe { VkKeyScanW('u' as u16) as u16 },
|
||||
Code::KeyV => unsafe { VkKeyScanW('v' as u16) as u16 },
|
||||
Code::KeyW => unsafe { VkKeyScanW('w' as u16) as u16 },
|
||||
Code::KeyX => unsafe { VkKeyScanW('x' as u16) as u16 },
|
||||
Code::KeyY => unsafe { VkKeyScanW('y' as u16) as u16 },
|
||||
Code::KeyZ => unsafe { VkKeyScanW('z' as u16) as u16 },
|
||||
Code::Digit0 => unsafe { VkKeyScanW('0' as u16) as u16 },
|
||||
Code::Digit1 => unsafe { VkKeyScanW('1' as u16) as u16 },
|
||||
Code::Digit2 => unsafe { VkKeyScanW('2' as u16) as u16 },
|
||||
Code::Digit3 => unsafe { VkKeyScanW('3' as u16) as u16 },
|
||||
Code::Digit4 => unsafe { VkKeyScanW('4' as u16) as u16 },
|
||||
Code::Digit5 => unsafe { VkKeyScanW('5' as u16) as u16 },
|
||||
Code::Digit6 => unsafe { VkKeyScanW('6' as u16) as u16 },
|
||||
Code::Digit7 => unsafe { VkKeyScanW('7' as u16) as u16 },
|
||||
Code::Digit8 => unsafe { VkKeyScanW('8' as u16) as u16 },
|
||||
Code::Digit9 => unsafe { VkKeyScanW('9' as u16) as u16 },
|
||||
Code::Comma => VK_OEM_COMMA,
|
||||
Code::Minus => VK_OEM_MINUS,
|
||||
Code::Period => VK_OEM_PERIOD,
|
||||
Code::Equal => unsafe { VkKeyScanW('=' as u16) as u16 },
|
||||
Code::Semicolon => unsafe { VkKeyScanW(';' as u16) as u16 },
|
||||
Code::Slash => unsafe { VkKeyScanW('/' as u16) as u16 },
|
||||
Code::Backslash => unsafe { VkKeyScanW('\\' as u16) as u16 },
|
||||
Code::Quote => unsafe { VkKeyScanW('\'' as u16) as u16 },
|
||||
Code::Backquote => unsafe { VkKeyScanW('`' as u16) as u16 },
|
||||
Code::BracketLeft => unsafe { VkKeyScanW('[' as u16) as u16 },
|
||||
Code::BracketRight => unsafe { VkKeyScanW(']' as u16) as u16 },
|
||||
Code::Backspace => VK_BACK,
|
||||
Code::Tab => VK_TAB,
|
||||
Code::Space => VK_SPACE,
|
||||
Code::Enter => VK_RETURN,
|
||||
Code::Pause => VK_PAUSE,
|
||||
Code::CapsLock => VK_CAPITAL,
|
||||
Code::KanaMode => VK_KANA,
|
||||
Code::Escape => VK_ESCAPE,
|
||||
Code::NonConvert => VK_NONCONVERT,
|
||||
Code::PageUp => VK_PRIOR,
|
||||
Code::PageDown => VK_NEXT,
|
||||
Code::End => VK_END,
|
||||
Code::Home => VK_HOME,
|
||||
Code::ArrowLeft => VK_LEFT,
|
||||
Code::ArrowUp => VK_UP,
|
||||
Code::ArrowRight => VK_RIGHT,
|
||||
Code::ArrowDown => VK_DOWN,
|
||||
Code::PrintScreen => VK_SNAPSHOT,
|
||||
Code::Insert => VK_INSERT,
|
||||
Code::Delete => VK_DELETE,
|
||||
Code::Help => VK_HELP,
|
||||
Code::ContextMenu => VK_APPS,
|
||||
Code::F1 => VK_F1,
|
||||
Code::F2 => VK_F2,
|
||||
Code::F3 => VK_F3,
|
||||
Code::F4 => VK_F4,
|
||||
Code::F5 => VK_F5,
|
||||
Code::F6 => VK_F6,
|
||||
Code::F7 => VK_F7,
|
||||
Code::F8 => VK_F8,
|
||||
Code::F9 => VK_F9,
|
||||
Code::F10 => VK_F10,
|
||||
Code::F11 => VK_F11,
|
||||
Code::F12 => VK_F12,
|
||||
Code::F13 => VK_F13,
|
||||
Code::F14 => VK_F14,
|
||||
Code::F15 => VK_F15,
|
||||
Code::F16 => VK_F16,
|
||||
Code::F17 => VK_F17,
|
||||
Code::F18 => VK_F18,
|
||||
Code::F19 => VK_F19,
|
||||
Code::F20 => VK_F20,
|
||||
Code::F21 => VK_F21,
|
||||
Code::F22 => VK_F22,
|
||||
Code::F23 => VK_F23,
|
||||
Code::F24 => VK_F24,
|
||||
Code::NumLock => VK_NUMLOCK,
|
||||
Code::ScrollLock => VK_SCROLL,
|
||||
Code::BrowserBack => VK_BROWSER_BACK,
|
||||
Code::BrowserForward => VK_BROWSER_FORWARD,
|
||||
Code::BrowserRefresh => VK_BROWSER_REFRESH,
|
||||
Code::BrowserStop => VK_BROWSER_STOP,
|
||||
Code::BrowserSearch => VK_BROWSER_SEARCH,
|
||||
Code::BrowserFavorites => VK_BROWSER_FAVORITES,
|
||||
Code::BrowserHome => VK_BROWSER_HOME,
|
||||
Code::AudioVolumeMute => VK_VOLUME_MUTE,
|
||||
Code::AudioVolumeDown => VK_VOLUME_DOWN,
|
||||
Code::AudioVolumeUp => VK_VOLUME_UP,
|
||||
Code::MediaTrackNext => VK_MEDIA_NEXT_TRACK,
|
||||
Code::MediaTrackPrevious => VK_MEDIA_PREV_TRACK,
|
||||
Code::MediaStop => VK_MEDIA_STOP,
|
||||
Code::MediaPlayPause => VK_MEDIA_PLAY_PAUSE,
|
||||
Code::LaunchMail => VK_LAUNCH_MAIL,
|
||||
Code::Convert => VK_CONVERT,
|
||||
key => panic!("Unsupported modifier: {}", key),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Accelerator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let key_mods: Modifiers = self.mods;
|
||||
if key_mods.contains(Modifiers::CONTROL) {
|
||||
write!(f, "Ctrl+")?;
|
||||
}
|
||||
if key_mods.contains(Modifiers::SHIFT) {
|
||||
write!(f, "Shift+")?;
|
||||
}
|
||||
if key_mods.contains(Modifiers::ALT) {
|
||||
write!(f, "Alt+")?;
|
||||
}
|
||||
if key_mods.contains(Modifiers::SUPER) {
|
||||
write!(f, "Windows+")?;
|
||||
}
|
||||
match &self.key {
|
||||
Code::KeyA => write!(f, "A"),
|
||||
Code::KeyB => write!(f, "B"),
|
||||
Code::KeyC => write!(f, "C"),
|
||||
Code::KeyD => write!(f, "D"),
|
||||
Code::KeyE => write!(f, "E"),
|
||||
Code::KeyF => write!(f, "F"),
|
||||
Code::KeyG => write!(f, "G"),
|
||||
Code::KeyH => write!(f, "H"),
|
||||
Code::KeyI => write!(f, "I"),
|
||||
Code::KeyJ => write!(f, "J"),
|
||||
Code::KeyK => write!(f, "K"),
|
||||
Code::KeyL => write!(f, "L"),
|
||||
Code::KeyM => write!(f, "M"),
|
||||
Code::KeyN => write!(f, "N"),
|
||||
Code::KeyO => write!(f, "O"),
|
||||
Code::KeyP => write!(f, "P"),
|
||||
Code::KeyQ => write!(f, "Q"),
|
||||
Code::KeyR => write!(f, "R"),
|
||||
Code::KeyS => write!(f, "S"),
|
||||
Code::KeyT => write!(f, "T"),
|
||||
Code::KeyU => write!(f, "U"),
|
||||
Code::KeyV => write!(f, "V"),
|
||||
Code::KeyW => write!(f, "W"),
|
||||
Code::KeyX => write!(f, "X"),
|
||||
Code::KeyY => write!(f, "Y"),
|
||||
Code::KeyZ => write!(f, "Z"),
|
||||
Code::Digit0 => write!(f, "0"),
|
||||
Code::Digit1 => write!(f, "1"),
|
||||
Code::Digit2 => write!(f, "2"),
|
||||
Code::Digit3 => write!(f, "3"),
|
||||
Code::Digit4 => write!(f, "4"),
|
||||
Code::Digit5 => write!(f, "5"),
|
||||
Code::Digit6 => write!(f, "6"),
|
||||
Code::Digit7 => write!(f, "7"),
|
||||
Code::Digit8 => write!(f, "8"),
|
||||
Code::Digit9 => write!(f, "9"),
|
||||
Code::Comma => write!(f, ","),
|
||||
Code::Minus => write!(f, "-"),
|
||||
Code::Period => write!(f, "."),
|
||||
Code::Space => write!(f, "Space"),
|
||||
Code::Equal => write!(f, "="),
|
||||
Code::Semicolon => write!(f, ";"),
|
||||
Code::Slash => write!(f, "/"),
|
||||
Code::Backslash => write!(f, "\\"),
|
||||
Code::Quote => write!(f, "\'"),
|
||||
Code::Backquote => write!(f, "`"),
|
||||
Code::BracketLeft => write!(f, "["),
|
||||
Code::BracketRight => write!(f, "]"),
|
||||
Code::Tab => write!(f, "Tab"),
|
||||
Code::Escape => write!(f, "Esc"),
|
||||
Code::Delete => write!(f, "Del"),
|
||||
Code::Insert => write!(f, "Ins"),
|
||||
Code::PageUp => write!(f, "PgUp"),
|
||||
Code::PageDown => write!(f, "PgDn"),
|
||||
// These names match LibreOffice.
|
||||
Code::ArrowLeft => write!(f, "Left"),
|
||||
Code::ArrowRight => write!(f, "Right"),
|
||||
Code::ArrowUp => write!(f, "Up"),
|
||||
Code::ArrowDown => write!(f, "Down"),
|
||||
_ => write!(f, "{:?}", self.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
mod accelerator;
|
||||
mod util;
|
||||
|
||||
use crate::{counter::Counter, NativeMenuItem};
|
||||
use crate::{accelerator::Accelerator, counter::Counter, NativeMenuItem};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use util::{decode_wide, encode_wide, LOWORD};
|
||||
|
@ -23,8 +23,6 @@ use windows_sys::Win32::{
|
|||
},
|
||||
};
|
||||
|
||||
use self::accelerator::parse_accelerator;
|
||||
|
||||
const COUNTER_START: u64 = 1000;
|
||||
static COUNTER: Counter = Counter::new_with_start(COUNTER_START);
|
||||
|
||||
|
@ -195,7 +193,7 @@ impl Submenu {
|
|||
&mut self,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> MenuItem {
|
||||
let id = COUNTER.next();
|
||||
let mut flags = MF_STRING;
|
||||
|
@ -205,12 +203,8 @@ impl Submenu {
|
|||
|
||||
let mut label = label.as_ref().to_string();
|
||||
if let Some(accelerator) = accelerator {
|
||||
let (key, mods, accel_str) = parse_accelerator(accelerator);
|
||||
let accel = ACCEL {
|
||||
key,
|
||||
fVirt: mods as _,
|
||||
cmd: id as _,
|
||||
};
|
||||
let accel_str = accelerator.to_string();
|
||||
let accel = accelerator.to_accel(id as u16);
|
||||
|
||||
label.push_str("\t");
|
||||
label.push_str(&accel_str);
|
||||
|
@ -268,7 +262,7 @@ impl Submenu {
|
|||
label: S,
|
||||
enabled: bool,
|
||||
checked: bool,
|
||||
accelerator: Option<&str>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> CheckMenuItem {
|
||||
let mut item = CheckMenuItem(self.add_item(label, enabled, accelerator));
|
||||
item.set_checked(checked);
|
||||
|
|
Loading…
Reference in a new issue