diff --git a/src/platform_impl/macos/menu_item.rs b/src/platform_impl/macos/menu_item.rs index 5e5bbdd..3aee1d1 100644 --- a/src/platform_impl/macos/menu_item.rs +++ b/src/platform_impl/macos/menu_item.rs @@ -82,6 +82,93 @@ impl MenuItem { } } +#[derive(Debug, Clone)] +pub struct CheckMenuItem { + pub(crate) id: u64, + pub(crate) ns_menu_item: id, + label: Rc, +} + +impl CheckMenuItem { + pub fn new>( + label: S, + enabled: bool, + checked: 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); + let () = msg_send![&*ns_menu_item, setTarget:&*ns_menu_item]; + + if !enabled { + let () = msg_send![ns_menu_item, setEnabled: NO]; + } + + if checked { + let () = msg_send![ns_menu_item, setState: 1_isize]; + } + } + Self { + id, + ns_menu_item, + label: Rc::from(label.as_ref()), + } + } + + pub fn label(&self) -> String { + self.label.to_string() + } + + pub fn set_label>(&mut self, label: S) { + unsafe { + 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 { + unsafe { + let enabled: BOOL = msg_send![self.ns_menu_item, isEnabled]; + enabled == YES + } + } + + pub fn set_enabled(&mut self, enabled: bool) { + unsafe { + let status = match enabled { + true => YES, + false => NO, + }; + let () = msg_send![self.ns_menu_item, setEnabled: status]; + } + } + + pub fn checked(&self) -> bool { + unsafe { + let checked: isize = msg_send![self.ns_menu_item, state]; + checked == 1_isize + } + } + + pub fn set_checked(&mut self, checked: bool) { + unsafe { + let state = match checked { + true => 1_isize, + false => 0_isize, + }; + let () = msg_send![self.ns_menu_item, setState: state]; + } + } + + pub fn id(&self) -> u64 { + self.id + } +} + 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(); diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index bedb43a..264feec 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,17 +1,18 @@ mod accelerator; mod menu_item; +use crate::platform_impl::platform_impl::menu_item::make_menu_item; use crate::NativeMenuItem; use cocoa::{ appkit::{NSApp, NSApplication, NSMenu, NSMenuItem}, - base::{id, nil, NO}, + base::{id, nil, selector, NO}, foundation::{NSAutoreleasePool, NSString}, }; -use objc::{msg_send, sel, sel_impl}; - -pub use menu_item::MenuItem; +use objc::{class, msg_send, sel, sel_impl}; use self::accelerator::remove_mnemonic; +pub use menu_item::CheckMenuItem; +pub use menu_item::MenuItem; #[derive(Debug, Clone)] pub struct Menu(id); @@ -98,8 +99,84 @@ impl Submenu { item } - pub fn add_native_item(&mut self, _item: NativeMenuItem) { - // TODO - return; + pub fn add_native_item(&mut self, item: NativeMenuItem) { + let (_, native_menu_item) = match item { + NativeMenuItem::Separator => unsafe { (0, NSMenuItem::separatorItem(nil)) }, + NativeMenuItem::About(app_name, _) => { + let title = format!("About {}", app_name); + make_menu_item( + title.as_str(), + selector("orderFrontStandardAboutPanel:"), + None, + ) + } + NativeMenuItem::CloseWindow => { + make_menu_item("Close Window", selector("performClose:"), Some("Command+W")) + } + NativeMenuItem::Quit => { + make_menu_item("Quit", selector("terminate:"), Some("Command+Q")) + } + NativeMenuItem::Hide => make_menu_item("Hide", selector("hide:"), Some("Command+H")), + NativeMenuItem::HideOthers => make_menu_item( + "Hide Others", + selector("hideOtherApplications:"), + Some("Alt+H"), + ), + NativeMenuItem::ShowAll => { + make_menu_item("Show All", selector("unhideAllApplications:"), None) + } + NativeMenuItem::EnterFullScreen => make_menu_item( + "Toggle Full Screen", + selector("toggleFullScreen:"), + Some("Ctrl+F"), + ), + NativeMenuItem::Minimize => make_menu_item( + "Minimize", + selector("performMiniaturize:"), + Some("Command+M"), + ), + NativeMenuItem::Zoom => make_menu_item("Zoom", selector("performZoom:"), None), + NativeMenuItem::Copy => make_menu_item("Copy", selector("copy:"), Some("Command+C")), + NativeMenuItem::Cut => make_menu_item("Cut", selector("cut:"), Some("Command+X")), + NativeMenuItem::Paste => make_menu_item("Paste", selector("paste:"), Some("Command+V")), + NativeMenuItem::Undo => make_menu_item("Undo", selector("undo:"), Some("Command+Z")), + NativeMenuItem::Redo => { + make_menu_item("Redo", selector("redo:"), Some("Command+Shift+Z")) + } + NativeMenuItem::SelectAll => { + make_menu_item("Select All", selector("selectAll:"), Some("Command+A")) + } + NativeMenuItem::Services => unsafe { + let (_, item) = make_menu_item("Services", sel!(fireMenubarAction:), None); + let app_class = class!(NSApplication); + let app: id = msg_send![app_class, sharedApplication]; + let services: id = msg_send![app, servicesMenu]; + let _: () = msg_send![&*item, setSubmenu: services]; + (0, item) + }, + }; + unsafe { + self.menu.0.addItem_(native_menu_item); + } + } + + pub fn add_check_item>( + &mut self, + label: S, + enabled: bool, + checked: bool, + accelerator: Option<&str>, + ) -> CheckMenuItem { + let item = CheckMenuItem::new( + label, + enabled, + checked, + sel!(fireMenubarAction:), + accelerator, + ); + unsafe { + self.menu.0.addItem_(item.ns_menu_item); + } + item } }