diff --git a/CHANGELOG.md b/CHANGELOG.md index a14bc047..8e62cb68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows. - On macOS, emit `RedrawRequested` events immediately while the window is being resized. - Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`. +- On macOS, initialize the Menu Bar with minimal defaults. # 0.24.0 (2020-12-09) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 59cf0f2a..4f20e216 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -25,6 +25,7 @@ use crate::{ platform_impl::platform::{ event::{EventProxy, EventWrapper}, event_loop::{post_dummy_event, PanicInfo}, + menu, observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker}, util::{IdRef, Never}, window::get_window_id, @@ -274,6 +275,9 @@ impl AppState { pub fn launched() { HANDLER.set_ready(); HANDLER.waker().start(); + // The menubar initialization should be before the `NewEvents` event, to allow overriding + // of the default menu in the event + menu::initialize(); HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( StartCause::Init, diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs new file mode 100644 index 00000000..a0d61edd --- /dev/null +++ b/src/platform_impl/macos/menu.rs @@ -0,0 +1,117 @@ +use cocoa::appkit::{ + NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSEventModifierFlags, NSMenu, + NSMenuItem, +}; +use cocoa::base::{nil, selector}; +use cocoa::foundation::{NSAutoreleasePool, NSProcessInfo, NSString}; +use objc::{ + rc::autoreleasepool, + runtime::{Object, Sel}, +}; + +struct KeyEquivalent<'a> { + key: &'a str, + masks: Option, +} + +pub fn initialize() { + autoreleasepool(|| unsafe { + let menubar = NSMenu::new(nil).autorelease(); + let app_menu_item = NSMenuItem::new(nil).autorelease(); + menubar.addItem_(app_menu_item); + let app = NSApp(); + app.setMainMenu_(menubar); + + let app_menu = NSMenu::new(nil); + let process_name = NSProcessInfo::processInfo(nil).processName(); + + // About menu item + let about_item_prefix = NSString::alloc(nil).init_str("About "); + let about_item_title = about_item_prefix.stringByAppendingString_(process_name); + let about_item = menu_item( + about_item_title, + selector("orderFrontStandardAboutPanel:"), + None, + ); + + // Seperator menu item + let sep_first = NSMenuItem::separatorItem(nil); + + // Hide application menu item + let hide_item_prefix = NSString::alloc(nil).init_str("Hide "); + let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name); + let hide_item = menu_item( + hide_item_title, + selector("hide:"), + Some(KeyEquivalent { + key: "h", + masks: None, + }), + ); + + // Hide other applications menu item + let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others"); + let hide_others_item = menu_item( + hide_others_item_title, + selector("hideOtherApplications:"), + Some(KeyEquivalent { + key: "h", + masks: Some( + NSEventModifierFlags::NSAlternateKeyMask + | NSEventModifierFlags::NSCommandKeyMask, + ), + }), + ); + + // Show applications menu item + let show_all_item_title = NSString::alloc(nil).init_str("Show All"); + let show_all_item = menu_item( + show_all_item_title, + selector("unhideAllApplications:"), + None, + ); + + // Seperator menu item + let sep = NSMenuItem::separatorItem(nil); + + // Quit application menu item + let quit_item_prefix = NSString::alloc(nil).init_str("Quit "); + let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name); + let quit_item = menu_item( + quit_item_title, + selector("terminate:"), + Some(KeyEquivalent { + key: "q", + masks: None, + }), + ); + + app_menu.addItem_(about_item); + app_menu.addItem_(sep_first); + app_menu.addItem_(hide_item); + app_menu.addItem_(hide_others_item); + app_menu.addItem_(show_all_item); + app_menu.addItem_(sep); + app_menu.addItem_(quit_item); + app_menu_item.setSubmenu_(app_menu); + }); +} + +fn menu_item( + title: *mut Object, + selector: Sel, + key_equivalent: Option>, +) -> *mut Object { + unsafe { + let (key, masks) = match key_equivalent { + Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks), + None => (NSString::alloc(nil).init_str(""), None), + }; + let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key); + if let Some(masks) = masks { + item.setKeyEquivalentModifierMask_(masks) + } + + item + } +} diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 72b8e0a3..09b61a5d 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -7,6 +7,7 @@ mod app_state; mod event; mod event_loop; mod ffi; +mod menu; mod monitor; mod observer; mod util;