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-29 00:42:14 +01:00
committed by GitHub
parent 406f3d93f3
commit bb4516e377
15 changed files with 230 additions and 93 deletions

View File

@@ -285,6 +285,7 @@ impl Dispatcher {
pub fn collect_actions(&self) -> ActionList {
// TODO: Reduce the number of heap allocations
let mut list = Vec::new();
list.extend(self.message_handlers.app_window_message_handler.actions());
list.extend(self.message_handlers.dialog_message_handler.actions());
list.extend(self.message_handlers.animation_message_handler.actions());
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());

View File

@@ -5,9 +5,12 @@ use super::app_window_message_handler::AppWindowPlatform;
#[impl_message(Message, AppWindow)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum AppWindowMessage {
AppWindowMinimize,
AppWindowMaximize,
AppWindowUpdatePlatform { platform: AppWindowPlatform },
AppWindowDrag,
AppWindowClose,
UpdatePlatform { platform: AppWindowPlatform },
Close,
Minimize,
Maximize,
Drag,
Hide,
HideOthers,
ShowAll,
}

View File

@@ -11,28 +11,41 @@ pub struct AppWindowMessageHandler {
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
match message {
AppWindowMessage::AppWindowMaximize => {
responses.add(FrontendMessage::TriggerMaximizeWindow);
}
AppWindowMessage::AppWindowMinimize => {
responses.add(FrontendMessage::TriggerMinimizeWindow);
}
AppWindowMessage::AppWindowUpdatePlatform { platform } => {
AppWindowMessage::UpdatePlatform { platform } => {
self.platform = platform;
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
}
AppWindowMessage::AppWindowDrag => {
responses.add(FrontendMessage::DragWindow);
AppWindowMessage::Close => {
responses.add(FrontendMessage::WindowClose);
}
AppWindowMessage::AppWindowClose => {
responses.add(FrontendMessage::CloseWindow);
AppWindowMessage::Minimize => {
responses.add(FrontendMessage::WindowMinimize);
}
AppWindowMessage::Maximize => {
responses.add(FrontendMessage::WindowMaximize);
}
AppWindowMessage::Drag => {
responses.add(FrontendMessage::WindowDrag);
}
AppWindowMessage::Hide => {
responses.add(FrontendMessage::WindowHide);
}
AppWindowMessage::HideOthers => {
responses.add(FrontendMessage::WindowHideOthers);
}
AppWindowMessage::ShowAll => {
responses.add(FrontendMessage::WindowShowAll);
}
}
}
fn actions(&self) -> ActionList {
actions!(AppWindowMessageDiscriminant;)
}
advertise_actions!(AppWindowMessageDiscriminant;
Close,
Minimize,
Maximize,
Drag,
Hide,
HideOthers,
);
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)]

View File

@@ -121,8 +121,6 @@ pub enum FrontendMessage {
TriggerVisitLink {
url: String,
},
TriggerMinimizeWindow,
TriggerMaximizeWindow,
// Update prefix: give the frontend a new value or state for it to use
UpdateActiveDocument {
@@ -348,8 +346,6 @@ pub enum FrontendMessage {
UpdateMaximized {
maximized: bool,
},
DragWindow,
CloseWindow,
UpdateViewportHolePunch {
active: bool,
},
@@ -365,4 +361,11 @@ pub enum FrontendMessage {
#[derivative(Debug = "ignore", PartialEq = "ignore")]
context: OverlayContext,
},
WindowClose,
WindowMinimize,
WindowMaximize,
WindowDrag,
WindowHide,
WindowHideOthers,
WindowShowAll,
}

View File

@@ -468,7 +468,7 @@ pub fn input_mappings() -> Mapping {
// Sort `pointer_shake`
sort(&mut pointer_shake);
Mapping {
let mut mapping = Mapping {
key_up,
key_down,
key_up_no_repeat,
@@ -477,7 +477,16 @@ pub fn input_mappings() -> Mapping {
wheel_scroll,
pointer_move,
pointer_shake,
};
if cfg!(target_os = "macos") {
let remove: [&[&[MappingEntry; 0]; 0]; 0] = [];
let add = [entry!(KeyDown(KeyQ); modifiers=[Accel], action_dispatch=AppWindowMessage::Close)];
apply_mapping_patch(&mut mapping, remove, add);
}
mapping
}
/// Default mappings except that scrolling without modifier keys held down is bound to zooming instead of vertical panning

View File

@@ -50,12 +50,65 @@ impl LayoutHolder for MenuBarMessageHandler {
let reset_node_definitions_on_open = self.reset_node_definitions_on_open;
let make_path_editable_is_allowed = self.make_path_editable_is_allowed;
let about = MenuBarEntry {
#[cfg(not(target_os = "macos"))]
label: "About Graphite…".into(),
#[cfg(target_os = "macos")]
label: "About Graphite".into(),
icon: Some("GraphiteLogo".into()),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
..MenuBarEntry::default()
};
let preferences = MenuBarEntry {
label: "Preferences…".into(),
icon: Some("Settings".into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()),
..MenuBarEntry::default()
};
let menu_bar_entries = vec![
#[cfg(not(target_os = "macos"))]
MenuBarEntry {
icon: Some("GraphiteLogo".into()),
action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()),
..Default::default()
},
#[cfg(target_os = "macos")]
MenuBarEntry::new_root(
"".into(),
false,
MenuBarEntryChildren(vec![
vec![about],
vec![preferences],
vec![
MenuBarEntry {
label: "Hide Graphite".into(),
shortcut: action_keys!(AppWindowMessageDiscriminant::Hide),
action: MenuBarEntry::create_action(|_| AppWindowMessage::Hide.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Hide Others".into(),
shortcut: action_keys!(AppWindowMessageDiscriminant::HideOthers),
action: MenuBarEntry::create_action(|_| AppWindowMessage::HideOthers.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Show All".into(),
shortcut: action_keys!(AppWindowMessageDiscriminant::ShowAll),
action: MenuBarEntry::create_action(|_| AppWindowMessage::ShowAll.into()),
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Quit Graphite".into(),
shortcut: action_keys!(AppWindowMessageDiscriminant::Close),
action: MenuBarEntry::create_action(|_| AppWindowMessage::Close.into()),
..MenuBarEntry::default()
}],
]),
),
MenuBarEntry::new_root(
"File".into(),
false,
@@ -137,13 +190,8 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Preferences…".into(),
icon: Some("Settings".into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()),
..MenuBarEntry::default()
}],
#[cfg(not(target_os = "macos"))]
vec![preferences],
]),
),
MenuBarEntry::new_root(
@@ -633,12 +681,8 @@ impl LayoutHolder for MenuBarMessageHandler {
"Help".into(),
false,
MenuBarEntryChildren(vec![
vec![MenuBarEntry {
label: "About Graphite…".into(),
icon: Some("GraphiteLogo".into()),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
..MenuBarEntry::default()
}],
#[cfg(not(target_os = "macos"))]
vec![about],
vec![
MenuBarEntry {
label: "Donate to Graphite".into(),