Desktop: Add app menu for Mac (#3428)

* add mac app menu

* review fixup

* Remove "About Graphite" ellipsis, add "Show All", make it say "Quit Graphite"

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Timon
2025-11-28 23:42:14 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent 406f3d93f3
commit bb4516e377
15 changed files with 230 additions and 93 deletions
+20 -6
View File
@@ -2,12 +2,6 @@ use objc2::{ClassType, define_class, msg_send};
use objc2_app_kit::{NSApplication, NSEvent, NSEventType, NSResponder};
use objc2_foundation::NSObject;
pub(super) fn init() {
unsafe {
let _: &NSApplication = msg_send![GraphiteApplication::class(), sharedApplication];
}
}
define_class!(
#[unsafe(super(NSApplication, NSResponder, NSObject))]
#[name = "GraphiteApplication"]
@@ -25,3 +19,23 @@ define_class!(
}
}
);
fn instance() -> objc2::rc::Retained<NSApplication> {
unsafe { msg_send![GraphiteApplication::class(), sharedApplication] }
}
pub(super) fn init() {
let _ = instance();
}
pub(super) fn hide() {
instance().hide(None);
}
pub(super) fn hide_others() {
instance().hideOtherApplications(None);
}
pub(super) fn show_all() {
instance().unhideAllApplications(None);
}
+5 -5
View File
@@ -43,11 +43,11 @@ impl Menu {
let existing_entries = self.inner.items();
let mut new_entries_iter = new_entries.iter();
let mut existing_entries_iter = existing_entries.iter().skip(1); // Skip first menu (app menu)
let mut existing_entries_iter = existing_entries.iter();
let incremental_update_ok = std::iter::from_fn(move || match (existing_entries_iter.next(), new_entries_iter.next()) {
(Some(MenuItemKind::Submenu(old)), Some(MenuItemKind::Submenu(new))) if old.text() == new.text() => {
replace_children(old, 0, new.items());
replace_children(old, new.items());
Some(true)
}
(None, None) => None,
@@ -57,7 +57,7 @@ impl Menu {
if !incremental_update_ok {
// Fallback to full replace
replace_children(&self.inner, 1, new_entries); // Skip first menu (app menu)
replace_children(&self.inner, new_entries);
}
}
}
@@ -111,10 +111,10 @@ fn menu_id_to_u64(id: &MenuId) -> Option<u64> {
u64::from_str_radix(&id.0, 16).ok()
}
fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, skip: usize, new_items: Vec<MenuItemKind>) {
fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, new_items: Vec<MenuItemKind>) {
let menu: MenuContainer = menu.into();
let items = menu.items();
for item in items.iter().skip(skip) {
for item in items.iter() {
menu.remove(menu_item_kind_to_dyn(item)).unwrap();
}
let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();