Skip to content

Commit bb4516e

Browse files
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>
1 parent 406f3d9 commit bb4516e

File tree

15 files changed

+230
-93
lines changed

15 files changed

+230
-93
lines changed

desktop/src/app.rs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -174,24 +174,6 @@ impl App {
174174
graphics_state.set_overlays_scene(scene);
175175
}
176176
}
177-
DesktopFrontendMessage::MinimizeWindow => {
178-
if let Some(window) = &self.window {
179-
window.minimize();
180-
}
181-
}
182-
DesktopFrontendMessage::MaximizeWindow => {
183-
if let Some(window) = &self.window {
184-
window.toggle_maximize();
185-
}
186-
}
187-
DesktopFrontendMessage::DragWindow => {
188-
if let Some(window) = &self.window {
189-
window.start_drag();
190-
}
191-
}
192-
DesktopFrontendMessage::CloseWindow => {
193-
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
194-
}
195177
DesktopFrontendMessage::PersistenceWriteDocument { id, document } => {
196178
self.persistent_data.write_document(id, document);
197179
}
@@ -270,6 +252,39 @@ impl App {
270252
window.update_menu(entries);
271253
}
272254
}
255+
DesktopFrontendMessage::WindowClose => {
256+
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
257+
}
258+
DesktopFrontendMessage::WindowMinimize => {
259+
if let Some(window) = &self.window {
260+
window.minimize();
261+
}
262+
}
263+
DesktopFrontendMessage::WindowMaximize => {
264+
if let Some(window) = &self.window {
265+
window.toggle_maximize();
266+
}
267+
}
268+
DesktopFrontendMessage::WindowDrag => {
269+
if let Some(window) = &self.window {
270+
window.start_drag();
271+
}
272+
}
273+
DesktopFrontendMessage::WindowHide => {
274+
if let Some(window) = &self.window {
275+
window.hide();
276+
}
277+
}
278+
DesktopFrontendMessage::WindowHideOthers => {
279+
if let Some(window) = &self.window {
280+
window.hide_others();
281+
}
282+
}
283+
DesktopFrontendMessage::WindowShowAll => {
284+
if let Some(window) = &self.window {
285+
window.show_all();
286+
}
287+
}
273288
}
274289
}
275290

desktop/src/window.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub(crate) trait NativeWindow {
1111
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
1212
fn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self;
1313
fn update_menu(&self, _entries: Vec<MenuItem>) {}
14+
fn hide(&self) {}
15+
fn hide_others(&self) {}
16+
fn show_all(&self) {}
1417
}
1518

1619
#[cfg(target_os = "linux")]
@@ -93,6 +96,18 @@ impl Window {
9396
let _ = self.winit_window.drag_window();
9497
}
9598

99+
pub(crate) fn hide(&self) {
100+
self.native_handle.hide();
101+
}
102+
103+
pub(crate) fn hide_others(&self) {
104+
self.native_handle.hide_others();
105+
}
106+
107+
pub(crate) fn show_all(&self) {
108+
self.native_handle.show_all();
109+
}
110+
96111
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
97112
self.winit_window.set_cursor(cursor);
98113
}

desktop/src/window/mac.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,16 @@ impl super::NativeWindow for NativeWindowImpl {
3434
fn update_menu(&self, entries: Vec<MenuItem>) {
3535
self.menu.update(entries);
3636
}
37+
38+
fn hide(&self) {
39+
app::hide();
40+
}
41+
42+
fn hide_others(&self) {
43+
app::hide_others();
44+
}
45+
46+
fn show_all(&self) {
47+
app::show_all();
48+
}
3749
}

desktop/src/window/mac/app.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ use objc2::{ClassType, define_class, msg_send};
22
use objc2_app_kit::{NSApplication, NSEvent, NSEventType, NSResponder};
33
use objc2_foundation::NSObject;
44

5-
pub(super) fn init() {
6-
unsafe {
7-
let _: &NSApplication = msg_send![GraphiteApplication::class(), sharedApplication];
8-
}
9-
}
10-
115
define_class!(
126
#[unsafe(super(NSApplication, NSResponder, NSObject))]
137
#[name = "GraphiteApplication"]
@@ -25,3 +19,23 @@ define_class!(
2519
}
2620
}
2721
);
22+
23+
fn instance() -> objc2::rc::Retained<NSApplication> {
24+
unsafe { msg_send![GraphiteApplication::class(), sharedApplication] }
25+
}
26+
27+
pub(super) fn init() {
28+
let _ = instance();
29+
}
30+
31+
pub(super) fn hide() {
32+
instance().hide(None);
33+
}
34+
35+
pub(super) fn hide_others() {
36+
instance().hideOtherApplications(None);
37+
}
38+
39+
pub(super) fn show_all() {
40+
instance().unhideAllApplications(None);
41+
}

desktop/src/window/mac/menu.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ impl Menu {
4343
let existing_entries = self.inner.items();
4444

4545
let mut new_entries_iter = new_entries.iter();
46-
let mut existing_entries_iter = existing_entries.iter().skip(1); // Skip first menu (app menu)
46+
let mut existing_entries_iter = existing_entries.iter();
4747

4848
let incremental_update_ok = std::iter::from_fn(move || match (existing_entries_iter.next(), new_entries_iter.next()) {
4949
(Some(MenuItemKind::Submenu(old)), Some(MenuItemKind::Submenu(new))) if old.text() == new.text() => {
50-
replace_children(old, 0, new.items());
50+
replace_children(old, new.items());
5151
Some(true)
5252
}
5353
(None, None) => None,
@@ -57,7 +57,7 @@ impl Menu {
5757

5858
if !incremental_update_ok {
5959
// Fallback to full replace
60-
replace_children(&self.inner, 1, new_entries); // Skip first menu (app menu)
60+
replace_children(&self.inner, new_entries);
6161
}
6262
}
6363
}
@@ -111,10 +111,10 @@ fn menu_id_to_u64(id: &MenuId) -> Option<u64> {
111111
u64::from_str_radix(&id.0, 16).ok()
112112
}
113113

114-
fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, skip: usize, new_items: Vec<MenuItemKind>) {
114+
fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, new_items: Vec<MenuItemKind>) {
115115
let menu: MenuContainer = menu.into();
116116
let items = menu.items();
117-
for item in items.iter().skip(skip) {
117+
for item in items.iter() {
118118
menu.remove(menu_item_kind_to_dyn(item)).unwrap();
119119
}
120120
let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();

desktop/wrapper/src/handle_desktop_wrapper_message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
117117
Platform::Mac => AppWindowPlatform::Mac,
118118
Platform::Linux => AppWindowPlatform::Linux,
119119
};
120-
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
120+
let message = AppWindowMessage::UpdatePlatform { platform };
121121
dispatcher.queue_editor_message(message);
122122
}
123123
DesktopWrapperMessage::UpdateMaximized { maximized } => {

desktop/wrapper/src/intercept_frontend_message.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,6 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
6767
FrontendMessage::TriggerVisitLink { url } => {
6868
dispatcher.respond(DesktopFrontendMessage::OpenUrl(url));
6969
}
70-
FrontendMessage::DragWindow => {
71-
dispatcher.respond(DesktopFrontendMessage::DragWindow);
72-
}
73-
FrontendMessage::CloseWindow => {
74-
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
75-
}
76-
FrontendMessage::TriggerMinimizeWindow => {
77-
dispatcher.respond(DesktopFrontendMessage::MinimizeWindow);
78-
}
79-
FrontendMessage::TriggerMaximizeWindow => {
80-
dispatcher.respond(DesktopFrontendMessage::MaximizeWindow);
81-
}
8270
FrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {
8371
dispatcher.respond(DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height });
8472
}
@@ -131,6 +119,27 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
131119

132120
return Some(FrontendMessage::UpdateMenuBarLayout { layout, layout_target });
133121
}
122+
FrontendMessage::WindowClose => {
123+
dispatcher.respond(DesktopFrontendMessage::WindowClose);
124+
}
125+
FrontendMessage::WindowMinimize => {
126+
dispatcher.respond(DesktopFrontendMessage::WindowMinimize);
127+
}
128+
FrontendMessage::WindowMaximize => {
129+
dispatcher.respond(DesktopFrontendMessage::WindowMaximize);
130+
}
131+
FrontendMessage::WindowDrag => {
132+
dispatcher.respond(DesktopFrontendMessage::WindowDrag);
133+
}
134+
FrontendMessage::WindowHide => {
135+
dispatcher.respond(DesktopFrontendMessage::WindowHide);
136+
}
137+
FrontendMessage::WindowHideOthers => {
138+
dispatcher.respond(DesktopFrontendMessage::WindowHideOthers);
139+
}
140+
FrontendMessage::WindowShowAll => {
141+
dispatcher.respond(DesktopFrontendMessage::WindowShowAll);
142+
}
134143
m => return Some(m),
135144
}
136145
None
@@ -151,11 +160,7 @@ fn convert_menu_bar_entry_to_menu_item(
151160
}: &MenuBarEntry,
152161
) -> Option<MenuItem> {
153162
let id = action.widget_id.0;
154-
let text = if label.is_empty() {
155-
return None;
156-
} else {
157-
label.clone()
158-
};
163+
let text = label.clone();
159164
let enabled = !*disabled;
160165

161166
if !children.0.is_empty() {

desktop/wrapper/src/messages.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ pub enum DesktopFrontendMessage {
3232
height: f64,
3333
},
3434
UpdateOverlays(vello::Scene),
35-
MinimizeWindow,
36-
MaximizeWindow,
37-
DragWindow,
38-
CloseWindow,
3935
PersistenceWriteDocument {
4036
id: DocumentId,
4137
document: Document,
@@ -58,6 +54,13 @@ pub enum DesktopFrontendMessage {
5854
UpdateMenu {
5955
entries: Vec<MenuItem>,
6056
},
57+
WindowClose,
58+
WindowMinimize,
59+
WindowMaximize,
60+
WindowDrag,
61+
WindowHide,
62+
WindowHideOthers,
63+
WindowShowAll,
6164
}
6265

6366
pub enum DesktopWrapperMessage {

editor/src/dispatcher.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ impl Dispatcher {
285285
pub fn collect_actions(&self) -> ActionList {
286286
// TODO: Reduce the number of heap allocations
287287
let mut list = Vec::new();
288+
list.extend(self.message_handlers.app_window_message_handler.actions());
288289
list.extend(self.message_handlers.dialog_message_handler.actions());
289290
list.extend(self.message_handlers.animation_message_handler.actions());
290291
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());

editor/src/messages/app_window/app_window_message.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use super::app_window_message_handler::AppWindowPlatform;
55
#[impl_message(Message, AppWindow)]
66
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
77
pub enum AppWindowMessage {
8-
AppWindowMinimize,
9-
AppWindowMaximize,
10-
AppWindowUpdatePlatform { platform: AppWindowPlatform },
11-
AppWindowDrag,
12-
AppWindowClose,
8+
UpdatePlatform { platform: AppWindowPlatform },
9+
Close,
10+
Minimize,
11+
Maximize,
12+
Drag,
13+
Hide,
14+
HideOthers,
15+
ShowAll,
1316
}

0 commit comments

Comments
 (0)