mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-24 04:11:30 +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"
|
parking_lot = "0.12"
|
||||||
gtk = "0.15"
|
gtk = "0.15"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
cocoa = "0.24"
|
||||||
|
objc = "0.2"
|
||||||
|
#core-graphics = "0.22"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
winit = "0.26"
|
winit = "0.26"
|
||||||
tao = { path = "../tao", default-features = false }
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
#[path = "linux.rs"]
|
#[path = "linux.rs"]
|
||||||
mod platform_impl;
|
mod platform_impl;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[path = "macos/mod.rs"]
|
||||||
|
mod platform_impl;
|
||||||
|
|
Loading…
Reference in a new issue