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:
Yu-Wei Wu 2022-05-07 21:19:42 +08:00
parent cadb53087b
commit b2f1a3ab4f
No known key found for this signature in database
GPG key ID: 3C5E0AA459C7E6E4
4 changed files with 266 additions and 0 deletions

View file

@ -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 }

View 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 _ });
}

View 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
}
}

View file

@ -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;