mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
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:
@@ -174,24 +174,6 @@ impl App {
|
||||
graphics_state.set_overlays_scene(scene);
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::MinimizeWindow => {
|
||||
if let Some(window) = &self.window {
|
||||
window.minimize();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::MaximizeWindow => {
|
||||
if let Some(window) = &self.window {
|
||||
window.toggle_maximize();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::DragWindow => {
|
||||
if let Some(window) = &self.window {
|
||||
window.start_drag();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::CloseWindow => {
|
||||
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
|
||||
}
|
||||
DesktopFrontendMessage::PersistenceWriteDocument { id, document } => {
|
||||
self.persistent_data.write_document(id, document);
|
||||
}
|
||||
@@ -270,6 +252,39 @@ impl App {
|
||||
window.update_menu(entries);
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowClose => {
|
||||
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
|
||||
}
|
||||
DesktopFrontendMessage::WindowMinimize => {
|
||||
if let Some(window) = &self.window {
|
||||
window.minimize();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowMaximize => {
|
||||
if let Some(window) = &self.window {
|
||||
window.toggle_maximize();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowDrag => {
|
||||
if let Some(window) = &self.window {
|
||||
window.start_drag();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowHide => {
|
||||
if let Some(window) = &self.window {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowHideOthers => {
|
||||
if let Some(window) = &self.window {
|
||||
window.hide_others();
|
||||
}
|
||||
}
|
||||
DesktopFrontendMessage::WindowShowAll => {
|
||||
if let Some(window) = &self.window {
|
||||
window.show_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ pub(crate) trait NativeWindow {
|
||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
|
||||
fn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self;
|
||||
fn update_menu(&self, _entries: Vec<MenuItem>) {}
|
||||
fn hide(&self) {}
|
||||
fn hide_others(&self) {}
|
||||
fn show_all(&self) {}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -93,6 +96,18 @@ impl Window {
|
||||
let _ = self.winit_window.drag_window();
|
||||
}
|
||||
|
||||
pub(crate) fn hide(&self) {
|
||||
self.native_handle.hide();
|
||||
}
|
||||
|
||||
pub(crate) fn hide_others(&self) {
|
||||
self.native_handle.hide_others();
|
||||
}
|
||||
|
||||
pub(crate) fn show_all(&self) {
|
||||
self.native_handle.show_all();
|
||||
}
|
||||
|
||||
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
|
||||
self.winit_window.set_cursor(cursor);
|
||||
}
|
||||
|
||||
@@ -34,4 +34,16 @@ impl super::NativeWindow for NativeWindowImpl {
|
||||
fn update_menu(&self, entries: Vec<MenuItem>) {
|
||||
self.menu.update(entries);
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
app::hide();
|
||||
}
|
||||
|
||||
fn hide_others(&self) {
|
||||
app::hide_others();
|
||||
}
|
||||
|
||||
fn show_all(&self) {
|
||||
app::show_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
@@ -117,7 +117,7 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
||||
Platform::Mac => AppWindowPlatform::Mac,
|
||||
Platform::Linux => AppWindowPlatform::Linux,
|
||||
};
|
||||
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
||||
let message = AppWindowMessage::UpdatePlatform { platform };
|
||||
dispatcher.queue_editor_message(message);
|
||||
}
|
||||
DesktopWrapperMessage::UpdateMaximized { maximized } => {
|
||||
|
||||
@@ -67,18 +67,6 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
||||
FrontendMessage::TriggerVisitLink { url } => {
|
||||
dispatcher.respond(DesktopFrontendMessage::OpenUrl(url));
|
||||
}
|
||||
FrontendMessage::DragWindow => {
|
||||
dispatcher.respond(DesktopFrontendMessage::DragWindow);
|
||||
}
|
||||
FrontendMessage::CloseWindow => {
|
||||
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
|
||||
}
|
||||
FrontendMessage::TriggerMinimizeWindow => {
|
||||
dispatcher.respond(DesktopFrontendMessage::MinimizeWindow);
|
||||
}
|
||||
FrontendMessage::TriggerMaximizeWindow => {
|
||||
dispatcher.respond(DesktopFrontendMessage::MaximizeWindow);
|
||||
}
|
||||
FrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {
|
||||
dispatcher.respond(DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height });
|
||||
}
|
||||
@@ -131,6 +119,27 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
||||
|
||||
return Some(FrontendMessage::UpdateMenuBarLayout { layout, layout_target });
|
||||
}
|
||||
FrontendMessage::WindowClose => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowClose);
|
||||
}
|
||||
FrontendMessage::WindowMinimize => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowMinimize);
|
||||
}
|
||||
FrontendMessage::WindowMaximize => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowMaximize);
|
||||
}
|
||||
FrontendMessage::WindowDrag => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowDrag);
|
||||
}
|
||||
FrontendMessage::WindowHide => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowHide);
|
||||
}
|
||||
FrontendMessage::WindowHideOthers => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowHideOthers);
|
||||
}
|
||||
FrontendMessage::WindowShowAll => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WindowShowAll);
|
||||
}
|
||||
m => return Some(m),
|
||||
}
|
||||
None
|
||||
@@ -151,11 +160,7 @@ fn convert_menu_bar_entry_to_menu_item(
|
||||
}: &MenuBarEntry,
|
||||
) -> Option<MenuItem> {
|
||||
let id = action.widget_id.0;
|
||||
let text = if label.is_empty() {
|
||||
return None;
|
||||
} else {
|
||||
label.clone()
|
||||
};
|
||||
let text = label.clone();
|
||||
let enabled = !*disabled;
|
||||
|
||||
if !children.0.is_empty() {
|
||||
|
||||
@@ -32,10 +32,6 @@ pub enum DesktopFrontendMessage {
|
||||
height: f64,
|
||||
},
|
||||
UpdateOverlays(vello::Scene),
|
||||
MinimizeWindow,
|
||||
MaximizeWindow,
|
||||
DragWindow,
|
||||
CloseWindow,
|
||||
PersistenceWriteDocument {
|
||||
id: DocumentId,
|
||||
document: Document,
|
||||
@@ -58,6 +54,13 @@ pub enum DesktopFrontendMessage {
|
||||
UpdateMenu {
|
||||
entries: Vec<MenuItem>,
|
||||
},
|
||||
WindowClose,
|
||||
WindowMinimize,
|
||||
WindowMaximize,
|
||||
WindowDrag,
|
||||
WindowHide,
|
||||
WindowHideOthers,
|
||||
WindowShowAll,
|
||||
}
|
||||
|
||||
pub enum DesktopWrapperMessage {
|
||||
|
||||
Reference in New Issue
Block a user