mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
Add macos port
Some configurations haven't implemented yet or just seem not possible. Co-authored-by: Jason Tsai <jason@pews.dev>
This commit is contained in:
parent
cadb53087b
commit
b2f1a3ab4f
|
@ -28,6 +28,11 @@ features = [
|
|||
parking_lot = "0.12"
|
||||
gtk = "0.15"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.24"
|
||||
objc = "0.2"
|
||||
#core-graphics = "0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
winit = "0.26"
|
||||
tao = { path = "../tao", default-features = false }
|
||||
|
|
194
src/platform_impl/macos/menu_item.rs
Normal file
194
src/platform_impl/macos/menu_item.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use cocoa::{
|
||||
appkit::{NSButton, NSEventModifierFlags, NSMenuItem},
|
||||
base::{id, nil, NO, YES},
|
||||
foundation::NSString,
|
||||
};
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::sync::Once;
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
/// Identifier of a custom menu item.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular menu, this event contains a `MenuId` which
|
||||
/// identifies its origin.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MenuId(pub u64);
|
||||
|
||||
impl From<MenuId> for u64 {
|
||||
fn from(s: MenuId) -> u64 {
|
||||
s.0
|
||||
}
|
||||
}
|
||||
|
||||
impl MenuId {
|
||||
/// Return an empty `MenuId`.
|
||||
pub const EMPTY: MenuId = MenuId(0);
|
||||
|
||||
/// Create new `MenuId` from a String.
|
||||
pub fn new(unique_string: &str) -> MenuId {
|
||||
MenuId(hash_string_to_u64(unique_string))
|
||||
}
|
||||
|
||||
/// Whenever this menu is empty.
|
||||
pub fn is_empty(self) -> bool {
|
||||
Self::EMPTY == self
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_string_to_u64(title: &str) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
title.to_uppercase().hash(&mut s);
|
||||
s.finish() as u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextMenuItem {
|
||||
pub(crate) id: MenuId,
|
||||
pub(crate) ns_menu_item: id,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
unsafe {
|
||||
(&mut *ns_menu_item).set_ivar(MENU_IDENTITY, id.0);
|
||||
let () = msg_send![&*ns_menu_item, setTarget:&*ns_menu_item];
|
||||
|
||||
if !enabled {
|
||||
let () = msg_send![ns_menu_item, setEnabled: NO];
|
||||
}
|
||||
}
|
||||
|
||||
Self { id, ns_menu_item }
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_menu_item(
|
||||
title: &str,
|
||||
selector: Sel,
|
||||
//key_equivalent: Option<key::KeyEquivalent>,
|
||||
//menu_type: MenuType,
|
||||
) -> (MenuId, *mut Object) {
|
||||
let alloc = make_menu_item_alloc();
|
||||
let menu_id = MenuId::new(title);
|
||||
|
||||
unsafe {
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
let menu_item = make_menu_item_from_alloc(alloc, title, selector); //, key_equivalent, menu_type);
|
||||
|
||||
(menu_id, menu_item)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_menu_item_alloc() -> *mut Object {
|
||||
unsafe { msg_send![make_menu_item_class(), alloc] }
|
||||
}
|
||||
|
||||
static MENU_IDENTITY: &str = "MenuItemIdentity";
|
||||
|
||||
fn make_menu_item_class() -> *const Class {
|
||||
static mut APP_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = class!(NSMenuItem);
|
||||
let mut decl = ClassDecl::new("MenuItem", superclass).unwrap();
|
||||
decl.add_ivar::<u64>(MENU_IDENTITY);
|
||||
|
||||
decl.add_method(
|
||||
sel!(dealloc),
|
||||
dealloc_custom_menuitem as extern "C" fn(&Object, _),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(fireMenubarAction:),
|
||||
fire_menu_bar_click as extern "C" fn(&Object, _, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(fireStatusbarAction:),
|
||||
fire_status_bar_click as extern "C" fn(&Object, _, id),
|
||||
);
|
||||
|
||||
APP_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe { APP_CLASS }
|
||||
}
|
||||
|
||||
fn make_menu_item_from_alloc(
|
||||
alloc: *mut Object,
|
||||
title: *mut Object,
|
||||
selector: Sel,
|
||||
//key_equivalent: Option<key::KeyEquivalent>,
|
||||
//menu_type: MenuType,
|
||||
) -> *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("");
|
||||
|
||||
// allocate our item to our class
|
||||
let item: id = msg_send![alloc, initWithTitle: title action: selector keyEquivalent: key];
|
||||
|
||||
// item.setKeyEquivalentModifierMask_(masks);
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn fire_menu_bar_click(this: &Object, _: Sel, _item: id) {
|
||||
send_event(this);
|
||||
}
|
||||
|
||||
extern "C" fn fire_status_bar_click(this: &Object, _: Sel, _item: id) {
|
||||
send_event(this);
|
||||
}
|
||||
|
||||
extern "C" fn dealloc_custom_menuitem(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSMenuItem)), dealloc];
|
||||
}
|
||||
}
|
||||
|
||||
fn send_event(this: &Object) {
|
||||
let id: u64 = unsafe { *this.get_ivar(MENU_IDENTITY) };
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id: id as _ });
|
||||
}
|
64
src/platform_impl/macos/mod.rs
Normal file
64
src/platform_impl/macos/mod.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use cocoa::{
|
||||
appkit::{NSApp, NSApplication, NSMenu, NSMenuItem},
|
||||
base::{id, nil, NO},
|
||||
foundation::{NSAutoreleasePool, NSString},
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
mod menu_item;
|
||||
pub use menu_item::TextMenuItem;
|
||||
use menu_item::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Menu(id);
|
||||
|
||||
impl Menu {
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let ns_menu = NSMenu::alloc(nil).autorelease();
|
||||
let () = msg_send![ns_menu, setAutoenablesItems: NO];
|
||||
Self(ns_menu)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||
let mut sub_menu = Submenu(Menu::new());
|
||||
sub_menu.set_label(label);
|
||||
sub_menu.set_enabled(enabled);
|
||||
sub_menu
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Submenu(Menu);
|
||||
|
||||
impl Submenu {
|
||||
pub fn label(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||
unsafe {
|
||||
let menu_title = NSString::alloc(nil).init_str(label.as_ref());
|
||||
let () = msg_send![self.0 .0, setTitle: menu_title];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, _enabled: bool) {}
|
||||
|
||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||
self.0.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:));
|
||||
unsafe {
|
||||
self.0 .0.addItem_(item.ns_menu_item);
|
||||
}
|
||||
item
|
||||
}
|
||||
}
|
|
@ -6,3 +6,6 @@ mod platform_impl;
|
|||
#[cfg(target_os = "linux")]
|
||||
#[path = "linux.rs"]
|
||||
mod platform_impl;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "macos/mod.rs"]
|
||||
mod platform_impl;
|
||||
|
|
Loading…
Reference in a new issue