mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
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:
parent
166e699c24
commit
01e7a2a848
110
src/platform_impl/macos/accelerator.rs
Normal file
110
src/platform_impl/macos/accelerator.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::counter::Counter;
|
use crate::counter::Counter;
|
||||||
|
use crate::platform_impl::platform_impl::accelerator::{parse_accelerator, remove_mnemonic};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::NSButton,
|
appkit::{NSButton, NSEventModifierFlags, NSMenuItem},
|
||||||
base::{id, nil, BOOL, NO, YES},
|
base::{id, nil, BOOL, NO, YES},
|
||||||
foundation::NSString,
|
foundation::NSString,
|
||||||
};
|
};
|
||||||
|
@ -11,12 +12,8 @@ use objc::{
|
||||||
runtime::{Class, Object, Sel},
|
runtime::{Class, Object, Sel},
|
||||||
sel, sel_impl,
|
sel, sel_impl,
|
||||||
};
|
};
|
||||||
use std::slice;
|
use std::rc::Rc;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
use std::{
|
|
||||||
collections::hash_map::DefaultHasher,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
static COUNTER: Counter = Counter::new();
|
static COUNTER: Counter = Counter::new();
|
||||||
|
|
||||||
|
@ -24,11 +21,17 @@ static COUNTER: Counter = Counter::new();
|
||||||
pub struct TextMenuItem {
|
pub struct TextMenuItem {
|
||||||
pub(crate) id: u64,
|
pub(crate) id: u64,
|
||||||
pub(crate) ns_menu_item: id,
|
pub(crate) ns_menu_item: id,
|
||||||
|
label: Rc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextMenuItem {
|
impl TextMenuItem {
|
||||||
pub fn new(label: impl AsRef<str>, enabled: bool, selector: Sel) -> Self {
|
pub fn new(
|
||||||
let (id, ns_menu_item) = make_menu_item(label.as_ref(), selector);
|
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 {
|
unsafe {
|
||||||
(&mut *ns_menu_item).set_ivar(MENU_IDENTITY, id);
|
(&mut *ns_menu_item).set_ivar(MENU_IDENTITY, id);
|
||||||
|
@ -38,25 +41,23 @@ impl TextMenuItem {
|
||||||
let () = msg_send![ns_menu_item, setEnabled: NO];
|
let () = msg_send![ns_menu_item, setEnabled: NO];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self {
|
||||||
Self { id, ns_menu_item }
|
id,
|
||||||
|
ns_menu_item,
|
||||||
|
label: Rc::from(label.as_ref()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self) -> String {
|
pub fn label(&self) -> String {
|
||||||
unsafe {
|
self.label.to_string()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||||
unsafe {
|
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.ns_menu_item.setTitle_(title);
|
||||||
}
|
}
|
||||||
|
self.label = Rc::from(label.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled(&self) -> bool {
|
pub fn enabled(&self) -> bool {
|
||||||
|
@ -81,18 +82,13 @@ impl TextMenuItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_menu_item(
|
pub fn make_menu_item(title: &str, selector: Sel, accelerator: Option<&str>) -> (u64, *mut Object) {
|
||||||
title: &str,
|
|
||||||
selector: Sel,
|
|
||||||
//key_equivalent: Option<key::KeyEquivalent>,
|
|
||||||
//menu_type: MenuType,
|
|
||||||
) -> (MenuId, *mut Object) {
|
|
||||||
let alloc = make_menu_item_alloc();
|
let alloc = make_menu_item_alloc();
|
||||||
let menu_id = COUNTER.next();
|
let menu_id = COUNTER.next();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let title = NSString::alloc(nil).init_str(title);
|
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)
|
(menu_id, menu_item)
|
||||||
}
|
}
|
||||||
|
@ -138,26 +134,26 @@ fn make_menu_item_from_alloc(
|
||||||
alloc: *mut Object,
|
alloc: *mut Object,
|
||||||
title: *mut Object,
|
title: *mut Object,
|
||||||
selector: Sel,
|
selector: Sel,
|
||||||
//key_equivalent: Option<key::KeyEquivalent>,
|
accelerator: Option<&str>,
|
||||||
//menu_type: MenuType,
|
|
||||||
) -> *mut Object {
|
) -> *mut Object {
|
||||||
unsafe {
|
unsafe {
|
||||||
// let (key, masks) = match key_equivalent {
|
let (key_equivalent, masks) = match accelerator {
|
||||||
// Some(ke) => (
|
Some(accelerator) => {
|
||||||
// NSString::alloc(nil).init_str(ke.key),
|
let (key, mods) = parse_accelerator(accelerator);
|
||||||
// ke.masks.unwrap_or_else(NSEventModifierFlags::empty),
|
let key = NSString::alloc(nil).init_str(&key);
|
||||||
// ),
|
(key, mods)
|
||||||
// None => (
|
}
|
||||||
// NSString::alloc(nil).init_str(""),
|
None => (
|
||||||
// NSEventModifierFlags::empty(),
|
NSString::alloc(nil).init_str(""),
|
||||||
// ),
|
NSEventModifierFlags::empty(),
|
||||||
// };
|
),
|
||||||
let key = NSString::alloc(nil).init_str("");
|
};
|
||||||
|
|
||||||
// allocate our item to our class
|
// 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
|
item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
mod accelerator;
|
||||||
|
mod menu_item;
|
||||||
|
|
||||||
|
use crate::NativeMenuItem;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{NSApp, NSApplication, NSMenu, NSMenuItem},
|
appkit::{NSApp, NSApplication, NSMenu, NSMenuItem},
|
||||||
base::{id, nil, NO},
|
base::{id, nil, NO},
|
||||||
|
@ -5,9 +9,9 @@ use cocoa::{
|
||||||
};
|
};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
mod menu_item;
|
|
||||||
pub use menu_item::TextMenuItem;
|
pub use menu_item::TextMenuItem;
|
||||||
use menu_item::*;
|
|
||||||
|
use self::accelerator::remove_mnemonic;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Menu(id);
|
pub struct Menu(id);
|
||||||
|
@ -23,7 +27,7 @@ impl Menu {
|
||||||
|
|
||||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:));
|
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
menu_item.ns_menu_item.setSubmenu_(menu.0);
|
menu_item.ns_menu_item.setSubmenu_(menu.0);
|
||||||
|
@ -61,9 +65,10 @@ impl Submenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
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 {
|
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];
|
let () = msg_send![self.menu.0, setTitle: menu_title];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,11 +85,21 @@ impl Submenu {
|
||||||
self.menu.add_submenu(label, enabled)
|
self.menu.add_submenu(label, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
pub fn add_text_item(
|
||||||
let item = TextMenuItem::new(label, enabled, sel!(fireMenubarAction:));
|
&mut self,
|
||||||
|
label: impl AsRef<str>,
|
||||||
|
enabled: bool,
|
||||||
|
accelerator: Option<&str>,
|
||||||
|
) -> TextMenuItem {
|
||||||
|
let item = TextMenuItem::new(label, enabled, sel!(fireMenubarAction:), accelerator);
|
||||||
unsafe {
|
unsafe {
|
||||||
self.menu.0.addItem_(item.ns_menu_item);
|
self.menu.0.addItem_(item.ns_menu_item);
|
||||||
}
|
}
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_native_item(&mut self, _item: NativeMenuItem) {
|
||||||
|
// TODO
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue