mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 12:01:31 +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::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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue