feat(macos): add accelerator (#12)

* feat(macos): add accelerator

* chore(macos): todo add_native_item

to make compile works

* Update src/platform_impl/macos/accelerator.rs

Should just remove an ampersand here

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* fix: save original label for later use

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
This commit is contained in:
Jason Tsai 2022-06-12 16:25:15 +08:00 committed by GitHub
parent 166e699c24
commit 01e7a2a848
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 47 deletions

View file

@ -0,0 +1,110 @@
use cocoa::appkit::NSEventModifierFlags;
/// 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)
}
};
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),
}
}

View file

@ -1,6 +1,7 @@
use crate::counter::Counter;
use crate::platform_impl::platform_impl::accelerator::{parse_accelerator, remove_mnemonic};
use cocoa::{
appkit::NSButton,
appkit::{NSButton, NSEventModifierFlags, NSMenuItem},
base::{id, nil, BOOL, NO, YES},
foundation::NSString,
};
@ -11,12 +12,8 @@ use objc::{
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use std::slice;
use std::rc::Rc;
use std::sync::Once;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
static COUNTER: Counter = Counter::new();
@ -24,11 +21,17 @@ static COUNTER: Counter = Counter::new();
pub struct TextMenuItem {
pub(crate) id: u64,
pub(crate) ns_menu_item: id,
label: Rc<str>,
}
impl TextMenuItem {
pub fn new(label: impl AsRef<str>, enabled: bool, selector: Sel) -> Self {
let (id, ns_menu_item) = make_menu_item(label.as_ref(), selector);
pub fn new(
label: impl AsRef<str>,
enabled: bool,
selector: Sel,
accelerator: Option<&str>,
) -> Self {
let (id, ns_menu_item) = make_menu_item(&remove_mnemonic(&label), selector, accelerator);
unsafe {
(&mut *ns_menu_item).set_ivar(MENU_IDENTITY, id);
@ -38,25 +41,23 @@ impl TextMenuItem {
let () = msg_send![ns_menu_item, setEnabled: NO];
}
}
Self { id, ns_menu_item }
Self {
id,
ns_menu_item,
label: Rc::from(label.as_ref()),
}
}
pub fn label(&self) -> String {
unsafe {
let title: id = msg_send![self.ns_menu_item, title];
let data = title.UTF8String() as *const u8;
let len = title.len();
String::from_utf8_lossy(slice::from_raw_parts(data, len)).to_string()
}
self.label.to_string()
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
unsafe {
let title = NSString::alloc(nil).init_str(label.as_ref());
let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label));
self.ns_menu_item.setTitle_(title);
}
self.label = Rc::from(label.as_ref());
}
pub fn enabled(&self) -> bool {
@ -81,18 +82,13 @@ impl TextMenuItem {
}
}
pub fn make_menu_item(
title: &str,
selector: Sel,
//key_equivalent: Option<key::KeyEquivalent>,
//menu_type: MenuType,
) -> (MenuId, *mut Object) {
pub fn make_menu_item(title: &str, selector: Sel, accelerator: Option<&str>) -> (u64, *mut Object) {
let alloc = make_menu_item_alloc();
let menu_id = COUNTER.next();
unsafe {
let title = NSString::alloc(nil).init_str(title);
let menu_item = make_menu_item_from_alloc(alloc, title, selector); //, key_equivalent, menu_type);
let menu_item = make_menu_item_from_alloc(alloc, title, selector, accelerator);
(menu_id, menu_item)
}
@ -138,26 +134,26 @@ fn make_menu_item_from_alloc(
alloc: *mut Object,
title: *mut Object,
selector: Sel,
//key_equivalent: Option<key::KeyEquivalent>,
//menu_type: MenuType,
accelerator: Option<&str>,
) -> *mut Object {
unsafe {
// let (key, masks) = match key_equivalent {
// Some(ke) => (
// NSString::alloc(nil).init_str(ke.key),
// ke.masks.unwrap_or_else(NSEventModifierFlags::empty),
// ),
// None => (
// NSString::alloc(nil).init_str(""),
// NSEventModifierFlags::empty(),
// ),
// };
let key = NSString::alloc(nil).init_str("");
let (key_equivalent, masks) = match accelerator {
Some(accelerator) => {
let (key, mods) = parse_accelerator(accelerator);
let key = NSString::alloc(nil).init_str(&key);
(key, mods)
}
None => (
NSString::alloc(nil).init_str(""),
NSEventModifierFlags::empty(),
),
};
// allocate our item to our class
let item: id = msg_send![alloc, initWithTitle: title action: selector keyEquivalent: key];
let item: id =
msg_send![alloc, initWithTitle: title action: selector keyEquivalent: key_equivalent];
item.setKeyEquivalentModifierMask_(masks);
// item.setKeyEquivalentModifierMask_(masks);
item
}
}

View file

@ -1,3 +1,7 @@
mod accelerator;
mod menu_item;
use crate::NativeMenuItem;
use cocoa::{
appkit::{NSApp, NSApplication, NSMenu, NSMenuItem},
base::{id, nil, NO},
@ -5,9 +9,9 @@ use cocoa::{
};
use objc::{msg_send, sel, sel_impl};
mod menu_item;
pub use menu_item::TextMenuItem;
use menu_item::*;
use self::accelerator::remove_mnemonic;
#[derive(Debug, Clone)]
pub struct Menu(id);
@ -23,7 +27,7 @@ impl Menu {
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
let menu = Menu::new();
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:));
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None);
unsafe {
menu_item.ns_menu_item.setSubmenu_(menu.0);
@ -61,9 +65,10 @@ impl Submenu {
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
self.menu_item.set_label(label.as_ref().to_string());
let label = remove_mnemonic(label);
self.menu_item.set_label(&label);
unsafe {
let menu_title = NSString::alloc(nil).init_str(label.as_ref());
let menu_title = NSString::alloc(nil).init_str(&label);
let () = msg_send![self.menu.0, setTitle: menu_title];
}
}
@ -80,11 +85,21 @@ impl Submenu {
self.menu.add_submenu(label, enabled)
}
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
let item = TextMenuItem::new(label, enabled, sel!(fireMenubarAction:));
pub fn add_text_item(
&mut self,
label: impl AsRef<str>,
enabled: bool,
accelerator: Option<&str>,
) -> TextMenuItem {
let item = TextMenuItem::new(label, enabled, sel!(fireMenubarAction:), accelerator);
unsafe {
self.menu.0.addItem_(item.ns_menu_item);
}
item
}
pub fn add_native_item(&mut self, _item: NativeMenuItem) {
// TODO
return;
}
}