mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 06:18:13 +08:00
Desktop: Mac native menu bar (#3301)
* macos native menu bar * fix nix build * Add shortcut symbols to menu * fix fmt * fix vendoring * cleanup intercept frontend message * accept into editor message in queue function
This commit is contained in:
+11
-3
@@ -19,8 +19,8 @@ use crate::event::{AppEvent, AppEventScheduler};
|
||||
use crate::persist::PersistentData;
|
||||
use crate::render::GraphicsState;
|
||||
use crate::window::Window;
|
||||
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
||||
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
||||
use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||
|
||||
pub(crate) struct App {
|
||||
cef_context: Box<dyn cef::CefContext>,
|
||||
@@ -258,6 +258,11 @@ impl App {
|
||||
}
|
||||
});
|
||||
}
|
||||
DesktopFrontendMessage::UpdateMenu { entries } => {
|
||||
if let Some(window) = &self.window {
|
||||
window.update_menu(entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,12 +343,15 @@ impl App {
|
||||
tracing::info!("Exiting main event loop");
|
||||
event_loop.exit();
|
||||
}
|
||||
AppEvent::MenuEvent { id } => {
|
||||
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::MenuEvent { id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ApplicationHandler for App {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window = Window::new(event_loop);
|
||||
let window = Window::new(event_loop, self.app_event_scheduler.clone());
|
||||
self.window = Some(window);
|
||||
|
||||
let graphics_state = GraphicsState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone());
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
use crate::event::{AppEvent, AppEventScheduler};
|
||||
use crate::render::FrameBufferRef;
|
||||
use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message};
|
||||
use crate::wrapper::{WgpuContext, deserialize_editor_message};
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use graphite_desktop_wrapper::NodeGraphExecutionResult;
|
||||
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
||||
use crate::wrapper::NodeGraphExecutionResult;
|
||||
use crate::wrapper::messages::DesktopWrapperMessage;
|
||||
|
||||
pub(crate) enum AppEvent {
|
||||
UiUpdate(wgpu::Texture),
|
||||
@@ -9,6 +9,7 @@ pub(crate) enum AppEvent {
|
||||
DesktopWrapperMessage(DesktopWrapperMessage),
|
||||
NodeGraphExecutionResult(NodeGraphExecutionResult),
|
||||
CloseWindow,
|
||||
MenuEvent { id: u64 },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use graphite_desktop_wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures};
|
||||
use crate::wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures};
|
||||
|
||||
pub(super) async fn create_wgpu_context() -> WgpuContext {
|
||||
let wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::PUSH_CONSTANTS);
|
||||
|
||||
@@ -16,6 +16,8 @@ mod window;
|
||||
|
||||
mod gpu_context;
|
||||
|
||||
pub(crate) use graphite_desktop_wrapper as wrapper;
|
||||
|
||||
use app::App;
|
||||
use cef::CefHandler;
|
||||
use cli::Cli;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use graphite_desktop_wrapper::messages::{Document, DocumentId, Preferences};
|
||||
use crate::wrapper::messages::{Document, DocumentId, Preferences};
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct PersistentData {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::window::Window;
|
||||
|
||||
use graphite_desktop_wrapper::{Color, WgpuContext, WgpuExecutor};
|
||||
use crate::wrapper::{Color, WgpuContext, WgpuExecutor};
|
||||
|
||||
#[derive(derivative::Derivative)]
|
||||
#[derivative(Debug)]
|
||||
|
||||
+10
-3
@@ -3,10 +3,13 @@ use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::{Window as WinitWindow, WindowAttributes};
|
||||
|
||||
use crate::consts::APP_NAME;
|
||||
use crate::event::AppEventScheduler;
|
||||
use crate::wrapper::messages::MenuItem;
|
||||
|
||||
pub(crate) trait NativeWindow {
|
||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
|
||||
fn new(window: &dyn WinitWindow) -> Self;
|
||||
fn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self;
|
||||
fn update_menu(&self, _entries: Vec<MenuItem>) {}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -31,7 +34,7 @@ pub(crate) struct Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn new(event_loop: &dyn ActiveEventLoop) -> Self {
|
||||
pub(crate) fn new(event_loop: &dyn ActiveEventLoop, app_event_scheduler: AppEventScheduler) -> Self {
|
||||
let mut attributes = WindowAttributes::default()
|
||||
.with_title(APP_NAME)
|
||||
.with_min_surface_size(winit::dpi::LogicalSize::new(400, 300))
|
||||
@@ -42,7 +45,7 @@ impl Window {
|
||||
attributes = native::NativeWindowImpl::configure(attributes, event_loop);
|
||||
|
||||
let winit_window = event_loop.create_window(attributes).unwrap();
|
||||
let native_handle = native::NativeWindowImpl::new(winit_window.as_ref());
|
||||
let native_handle = native::NativeWindowImpl::new(winit_window.as_ref(), app_event_scheduler);
|
||||
Self {
|
||||
winit_window: winit_window.into(),
|
||||
native_handle,
|
||||
@@ -84,4 +87,8 @@ impl Window {
|
||||
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
|
||||
self.winit_window.set_cursor(cursor);
|
||||
}
|
||||
|
||||
pub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {
|
||||
self.native_handle.update_menu(entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@ use winit::platform::x11::WindowAttributesX11;
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
use crate::consts::{APP_ID, APP_NAME};
|
||||
|
||||
use super::NativeWindow;
|
||||
use crate::event::AppEventScheduler;
|
||||
|
||||
pub(super) struct NativeWindowImpl {}
|
||||
|
||||
impl NativeWindow for NativeWindowImpl {
|
||||
impl super::NativeWindow for NativeWindowImpl {
|
||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||
if event_loop.is_wayland() {
|
||||
let wayland_attributes = WindowAttributesWayland::default().with_name(APP_ID, "").with_prefer_csd(true);
|
||||
@@ -21,7 +20,7 @@ impl NativeWindow for NativeWindowImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn new(_window: &dyn Window) -> Self {
|
||||
fn new(_window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self {
|
||||
NativeWindowImpl {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
use super::NativeWindow;
|
||||
use crate::consts::APP_NAME;
|
||||
use crate::event::AppEventScheduler;
|
||||
use crate::wrapper::messages::MenuItem;
|
||||
|
||||
pub(super) struct NativeWindowImpl {}
|
||||
pub(super) struct NativeWindowImpl {
|
||||
menu: menu::Menu,
|
||||
}
|
||||
|
||||
impl NativeWindow for NativeWindowImpl {
|
||||
impl super::NativeWindow for NativeWindowImpl {
|
||||
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||
let mac_window = winit::platform::macos::WindowAttributesMacOS::default()
|
||||
.with_titlebar_transparent(true)
|
||||
@@ -14,7 +18,15 @@ impl NativeWindow for NativeWindowImpl {
|
||||
attributes.with_platform_attributes(Box::new(mac_window))
|
||||
}
|
||||
|
||||
fn new(_window: &dyn Window) -> Self {
|
||||
NativeWindowImpl {}
|
||||
fn new(_window: &dyn Window, app_event_scheduler: AppEventScheduler) -> Self {
|
||||
let menu = menu::Menu::new(app_event_scheduler, APP_NAME);
|
||||
|
||||
NativeWindowImpl { menu }
|
||||
}
|
||||
|
||||
fn update_menu(&self, entries: Vec<MenuItem>) {
|
||||
self.menu.update(entries);
|
||||
}
|
||||
}
|
||||
|
||||
mod menu;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
use muda::Menu as MudaMenu;
|
||||
use muda::accelerator::Accelerator;
|
||||
use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Submenu};
|
||||
|
||||
use crate::event::{AppEvent, AppEventScheduler};
|
||||
use crate::wrapper::messages::MenuItem as WrapperMenuItem;
|
||||
|
||||
pub(super) struct Menu {
|
||||
inner: MudaMenu,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub(super) fn new(event_scheduler: AppEventScheduler, app_name: &str) -> Self {
|
||||
let about = PredefinedMenuItem::about(None, Some(AboutMetadataBuilder::new().name(Some(app_name)).build()));
|
||||
let hide = PredefinedMenuItem::hide(None);
|
||||
let hide_others = PredefinedMenuItem::hide_others(None);
|
||||
let show_all = PredefinedMenuItem::show_all(None);
|
||||
let quit = PredefinedMenuItem::quit(None);
|
||||
let app_submenu = Submenu::with_items(
|
||||
"",
|
||||
true,
|
||||
&[&about, &PredefinedMenuItem::separator(), &hide, &hide_others, &show_all, &PredefinedMenuItem::separator(), &quit],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let menu = MudaMenu::new();
|
||||
menu.prepend(&app_submenu).unwrap();
|
||||
|
||||
menu.init_for_nsapp();
|
||||
|
||||
MenuEvent::set_event_handler(Some(move |event: MenuEvent| {
|
||||
if let Some(id) = menu_id_to_u64(event.id()) {
|
||||
event_scheduler.schedule(AppEvent::MenuEvent { id });
|
||||
}
|
||||
}));
|
||||
|
||||
Menu { inner: menu }
|
||||
}
|
||||
|
||||
pub(super) fn update(&self, entries: Vec<WrapperMenuItem>) {
|
||||
// remove all items except the first (app menu)
|
||||
self.inner.items().iter().skip(1).for_each(|item: &muda::MenuItemKind| {
|
||||
self.inner.remove(menu_item_kind_to_dyn(item)).unwrap();
|
||||
});
|
||||
|
||||
let items = menu_items_from_wrapper(entries);
|
||||
let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
|
||||
self.inner.append_items(items.as_ref()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn menu_items_from_wrapper(entries: Vec<WrapperMenuItem>) -> Vec<MenuItemKind> {
|
||||
let mut menu_items: Vec<MenuItemKind> = Vec::new();
|
||||
for entry in entries {
|
||||
match entry {
|
||||
WrapperMenuItem::Action { id, text, enabled, shortcut } => {
|
||||
let id = u64_to_menu_id(id);
|
||||
let accelerator = shortcut.map(|s| Accelerator::new(Some(s.modifiers), s.key));
|
||||
let item = MenuItem::with_id(id, text, enabled, accelerator);
|
||||
menu_items.push(MenuItemKind::MenuItem(item));
|
||||
}
|
||||
WrapperMenuItem::Checkbox { id, text, enabled, shortcut, checked } => {
|
||||
let id = u64_to_menu_id(id);
|
||||
let accelerator = shortcut.map(|s| Accelerator::new(Some(s.modifiers), s.key));
|
||||
let check = CheckMenuItem::with_id(id, text, enabled, checked, accelerator);
|
||||
menu_items.push(MenuItemKind::Check(check));
|
||||
}
|
||||
WrapperMenuItem::SubMenu { text: name, items, .. } => {
|
||||
let items = menu_items_from_wrapper(items);
|
||||
let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
|
||||
let submenu = Submenu::with_items(name, true, &items).unwrap();
|
||||
menu_items.push(MenuItemKind::Submenu(submenu));
|
||||
}
|
||||
WrapperMenuItem::Separator => {
|
||||
let separator = PredefinedMenuItem::separator();
|
||||
menu_items.push(MenuItemKind::Predefined(separator));
|
||||
}
|
||||
}
|
||||
}
|
||||
menu_items
|
||||
}
|
||||
|
||||
fn menu_item_kind_to_dyn(item: &MenuItemKind) -> &dyn IsMenuItem {
|
||||
match item {
|
||||
MenuItemKind::MenuItem(i) => i,
|
||||
MenuItemKind::Submenu(i) => i,
|
||||
MenuItemKind::Predefined(i) => i,
|
||||
MenuItemKind::Check(i) => i,
|
||||
MenuItemKind::Icon(i) => i,
|
||||
}
|
||||
}
|
||||
|
||||
fn u64_to_menu_id(id: u64) -> String {
|
||||
format!("{id:08x}")
|
||||
}
|
||||
|
||||
fn menu_id_to_u64(id: &MenuId) -> Option<u64> {
|
||||
u64::from_str_radix(&id.0, 16).ok()
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
use super::NativeWindow;
|
||||
use crate::event::AppEventScheduler;
|
||||
|
||||
pub(super) struct NativeWindowImpl {
|
||||
native_handle: native_handle::NativeWindowHandle,
|
||||
}
|
||||
|
||||
impl NativeWindow for NativeWindowImpl {
|
||||
impl super::NativeWindow for NativeWindowImpl {
|
||||
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||
if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) {
|
||||
let icon = winit::icon::Icon(std::sync::Arc::new(win_icon));
|
||||
@@ -17,7 +17,7 @@ impl NativeWindow for NativeWindowImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn new(window: &dyn Window) -> Self {
|
||||
fn new(window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self {
|
||||
let native_handle = native_handle::NativeWindowHandle::new(window);
|
||||
NativeWindowImpl { native_handle }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user