mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 10:48:12 +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:
Generated
+30
@@ -1078,6 +1078,15 @@ dependencies = [
|
|||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -2272,6 +2281,7 @@ dependencies = [
|
|||||||
"graphite-desktop-wrapper",
|
"graphite-desktop-wrapper",
|
||||||
"libc",
|
"libc",
|
||||||
"metal",
|
"metal",
|
||||||
|
"muda",
|
||||||
"objc",
|
"objc",
|
||||||
"open",
|
"open",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
@@ -2335,8 +2345,10 @@ dependencies = [
|
|||||||
"graphene-std",
|
"graphene-std",
|
||||||
"graphite-editor",
|
"graphite-editor",
|
||||||
"image",
|
"image",
|
||||||
|
"keyboard-types",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tracing",
|
"tracing",
|
||||||
"vello",
|
"vello",
|
||||||
@@ -3398,6 +3410,24 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "muda"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "git+https://github.com/tauri-apps/muda.git?rev=3f460b8fbaed59cda6d95ceea6904f000f093f15#3f460b8fbaed59cda6d95ceea6904f000f093f15"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"dpi",
|
||||||
|
"keyboard-types",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation",
|
||||||
|
"once_cell",
|
||||||
|
"png",
|
||||||
|
"thiserror 2.0.16",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "25.0.1"
|
version = "25.0.1"
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ web-sys = { version = "=0.3.77", features = [
|
|||||||
"ImageBitmapRenderingContext",
|
"ImageBitmapRenderingContext",
|
||||||
] }
|
] }
|
||||||
winit = { git = "https://github.com/rust-windowing/winit.git" }
|
winit = { git = "https://github.com/rust-windowing/winit.git" }
|
||||||
|
keyboard-types = "0.8"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
|
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
|
||||||
vello = { git = "https://github.com/linebender/vello.git", rev = "87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" } # TODO switch back to stable when a release is made
|
vello = { git = "https://github.com/linebender/vello.git", rev = "87cc5bee6d3a34d15017dbbb58634ddc7f33ff9b" } # TODO switch back to stable when a release is made
|
||||||
@@ -225,3 +226,7 @@ debug = true
|
|||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# Force cargo to use only one version of the dpi crate (vendoring breaks without this)
|
||||||
|
dpi = { git = "https://github.com/rust-windowing/winit.git" }
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ windows = { version = "0.58.0", features = [
|
|||||||
|
|
||||||
# macOS-specific dependencies
|
# macOS-specific dependencies
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false }
|
||||||
metal = { version = "0.31.0", optional = true }
|
metal = { version = "0.31.0", optional = true }
|
||||||
objc = { version = "0.2", optional = true }
|
objc = { version = "0.2", optional = true }
|
||||||
core-foundation = { version = "0.10", optional = true }
|
core-foundation = { version = "0.10", optional = true }
|
||||||
|
|||||||
+11
-3
@@ -19,8 +19,8 @@ use crate::event::{AppEvent, AppEventScheduler};
|
|||||||
use crate::persist::PersistentData;
|
use crate::persist::PersistentData;
|
||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
||||||
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||||
|
|
||||||
pub(crate) struct App {
|
pub(crate) struct App {
|
||||||
cef_context: Box<dyn cef::CefContext>,
|
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");
|
tracing::info!("Exiting main event loop");
|
||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
}
|
}
|
||||||
|
AppEvent::MenuEvent { id } => {
|
||||||
|
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::MenuEvent { id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ApplicationHandler for App {
|
impl ApplicationHandler for App {
|
||||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
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);
|
self.window = Some(window);
|
||||||
|
|
||||||
let graphics_state = GraphicsState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone());
|
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::event::{AppEvent, AppEventScheduler};
|
||||||
use crate::render::FrameBufferRef;
|
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::fs::File;
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use graphite_desktop_wrapper::NodeGraphExecutionResult;
|
use crate::wrapper::NodeGraphExecutionResult;
|
||||||
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
use crate::wrapper::messages::DesktopWrapperMessage;
|
||||||
|
|
||||||
pub(crate) enum AppEvent {
|
pub(crate) enum AppEvent {
|
||||||
UiUpdate(wgpu::Texture),
|
UiUpdate(wgpu::Texture),
|
||||||
@@ -9,6 +9,7 @@ pub(crate) enum AppEvent {
|
|||||||
DesktopWrapperMessage(DesktopWrapperMessage),
|
DesktopWrapperMessage(DesktopWrapperMessage),
|
||||||
NodeGraphExecutionResult(NodeGraphExecutionResult),
|
NodeGraphExecutionResult(NodeGraphExecutionResult),
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
|
MenuEvent { id: u64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[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 {
|
pub(super) async fn create_wgpu_context() -> WgpuContext {
|
||||||
let wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::PUSH_CONSTANTS);
|
let wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::PUSH_CONSTANTS);
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ mod window;
|
|||||||
|
|
||||||
mod gpu_context;
|
mod gpu_context;
|
||||||
|
|
||||||
|
pub(crate) use graphite_desktop_wrapper as wrapper;
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use cef::CefHandler;
|
use cef::CefHandler;
|
||||||
use cli::Cli;
|
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)]
|
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub(crate) struct PersistentData {
|
pub(crate) struct PersistentData {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
|
||||||
use graphite_desktop_wrapper::{Color, WgpuContext, WgpuExecutor};
|
use crate::wrapper::{Color, WgpuContext, WgpuExecutor};
|
||||||
|
|
||||||
#[derive(derivative::Derivative)]
|
#[derive(derivative::Derivative)]
|
||||||
#[derivative(Debug)]
|
#[derivative(Debug)]
|
||||||
|
|||||||
+10
-3
@@ -3,10 +3,13 @@ use winit::event_loop::ActiveEventLoop;
|
|||||||
use winit::window::{Window as WinitWindow, WindowAttributes};
|
use winit::window::{Window as WinitWindow, WindowAttributes};
|
||||||
|
|
||||||
use crate::consts::APP_NAME;
|
use crate::consts::APP_NAME;
|
||||||
|
use crate::event::AppEventScheduler;
|
||||||
|
use crate::wrapper::messages::MenuItem;
|
||||||
|
|
||||||
pub(crate) trait NativeWindow {
|
pub(crate) trait NativeWindow {
|
||||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -31,7 +34,7 @@ pub(crate) struct Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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()
|
let mut attributes = WindowAttributes::default()
|
||||||
.with_title(APP_NAME)
|
.with_title(APP_NAME)
|
||||||
.with_min_surface_size(winit::dpi::LogicalSize::new(400, 300))
|
.with_min_surface_size(winit::dpi::LogicalSize::new(400, 300))
|
||||||
@@ -42,7 +45,7 @@ impl Window {
|
|||||||
attributes = native::NativeWindowImpl::configure(attributes, event_loop);
|
attributes = native::NativeWindowImpl::configure(attributes, event_loop);
|
||||||
|
|
||||||
let winit_window = event_loop.create_window(attributes).unwrap();
|
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 {
|
Self {
|
||||||
winit_window: winit_window.into(),
|
winit_window: winit_window.into(),
|
||||||
native_handle,
|
native_handle,
|
||||||
@@ -84,4 +87,8 @@ impl Window {
|
|||||||
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
|
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
|
||||||
self.winit_window.set_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 winit::window::{Window, WindowAttributes};
|
||||||
|
|
||||||
use crate::consts::{APP_ID, APP_NAME};
|
use crate::consts::{APP_ID, APP_NAME};
|
||||||
|
use crate::event::AppEventScheduler;
|
||||||
use super::NativeWindow;
|
|
||||||
|
|
||||||
pub(super) struct NativeWindowImpl {}
|
pub(super) struct NativeWindowImpl {}
|
||||||
|
|
||||||
impl NativeWindow for NativeWindowImpl {
|
impl super::NativeWindow for NativeWindowImpl {
|
||||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||||
if event_loop.is_wayland() {
|
if event_loop.is_wayland() {
|
||||||
let wayland_attributes = WindowAttributesWayland::default().with_name(APP_ID, "").with_prefer_csd(true);
|
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 {}
|
NativeWindowImpl {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
use winit::event_loop::ActiveEventLoop;
|
use winit::event_loop::ActiveEventLoop;
|
||||||
use winit::window::{Window, WindowAttributes};
|
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 {
|
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||||
let mac_window = winit::platform::macos::WindowAttributesMacOS::default()
|
let mac_window = winit::platform::macos::WindowAttributesMacOS::default()
|
||||||
.with_titlebar_transparent(true)
|
.with_titlebar_transparent(true)
|
||||||
@@ -14,7 +18,15 @@ impl NativeWindow for NativeWindowImpl {
|
|||||||
attributes.with_platform_attributes(Box::new(mac_window))
|
attributes.with_platform_attributes(Box::new(mac_window))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(_window: &dyn Window) -> Self {
|
fn new(_window: &dyn Window, app_event_scheduler: AppEventScheduler) -> Self {
|
||||||
NativeWindowImpl {}
|
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::event_loop::ActiveEventLoop;
|
||||||
use winit::window::{Window, WindowAttributes};
|
use winit::window::{Window, WindowAttributes};
|
||||||
|
|
||||||
use super::NativeWindow;
|
use crate::event::AppEventScheduler;
|
||||||
|
|
||||||
pub(super) struct NativeWindowImpl {
|
pub(super) struct NativeWindowImpl {
|
||||||
native_handle: native_handle::NativeWindowHandle,
|
native_handle: native_handle::NativeWindowHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeWindow for NativeWindowImpl {
|
impl super::NativeWindow for NativeWindowImpl {
|
||||||
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||||
if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) {
|
if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) {
|
||||||
let icon = winit::icon::Icon(std::sync::Arc::new(win_icon));
|
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);
|
let native_handle = native_handle::NativeWindowHandle::new(window);
|
||||||
NativeWindowImpl { native_handle }
|
NativeWindowImpl { native_handle }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,5 @@ ron = { workspace = true}
|
|||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
keyboard-types = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
|
use graphite_editor::messages::layout::LayoutMessage;
|
||||||
use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, FrontendMessage, PortfolioMessage, PreferencesMessage};
|
use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, FrontendMessage, PortfolioMessage, PreferencesMessage};
|
||||||
|
use graphite_editor::messages::tool::tool_messages::tool_prelude::{LayoutTarget, WidgetId};
|
||||||
|
|
||||||
use crate::messages::Platform;
|
use crate::messages::Platform;
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
document_path: Some(path),
|
document_path: Some(path),
|
||||||
document_serialized_content: content,
|
document_serialized_content: content,
|
||||||
};
|
};
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::ImportFile { path, content } => {
|
DesktopWrapperMessage::ImportFile { path, content } => {
|
||||||
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
|
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
|
||||||
@@ -80,7 +82,7 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
mouse: None,
|
mouse: None,
|
||||||
parent_and_insert_index: None,
|
parent_and_insert_index: None,
|
||||||
};
|
};
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::ImportImage { path, content } => {
|
DesktopWrapperMessage::ImportImage { path, content } => {
|
||||||
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
|
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
|
||||||
@@ -106,7 +108,7 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
mouse: None,
|
mouse: None,
|
||||||
parent_and_insert_index: None,
|
parent_and_insert_index: None,
|
||||||
};
|
};
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),
|
DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),
|
||||||
DesktopWrapperMessage::UpdatePlatform(platform) => {
|
DesktopWrapperMessage::UpdatePlatform(platform) => {
|
||||||
@@ -116,11 +118,11 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
Platform::Linux => AppWindowPlatform::Linux,
|
Platform::Linux => AppWindowPlatform::Linux,
|
||||||
};
|
};
|
||||||
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::UpdateMaximized { maximized } => {
|
DesktopWrapperMessage::UpdateMaximized { maximized } => {
|
||||||
let message = FrontendMessage::UpdateMaximized { maximized };
|
let message = FrontendMessage::UpdateMaximized { maximized };
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::LoadDocument {
|
DesktopWrapperMessage::LoadDocument {
|
||||||
id,
|
id,
|
||||||
@@ -138,15 +140,23 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
to_front,
|
to_front,
|
||||||
select_after_open,
|
select_after_open,
|
||||||
};
|
};
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::SelectDocument { id } => {
|
DesktopWrapperMessage::SelectDocument { id } => {
|
||||||
let message = PortfolioMessage::SelectDocument { document_id: id };
|
let message = PortfolioMessage::SelectDocument { document_id: id };
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
DesktopWrapperMessage::LoadPreferences { preferences } => {
|
DesktopWrapperMessage::LoadPreferences { preferences } => {
|
||||||
let message = PreferencesMessage::Load { preferences };
|
let message = PreferencesMessage::Load { preferences };
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message);
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::MenuEvent { id } => {
|
||||||
|
let message = LayoutMessage::WidgetValueUpdate {
|
||||||
|
layout_target: LayoutTarget::MenuBar,
|
||||||
|
widget_id: WidgetId(id),
|
||||||
|
value: serde_json::Value::Bool(true),
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LayoutKey, LayoutKeysGroup};
|
||||||
|
use graphite_editor::messages::input_mapper::utility_types::misc::ActionKeys;
|
||||||
|
use graphite_editor::messages::layout::utility_types::widgets::menu_widgets::MenuBarEntry;
|
||||||
use graphite_editor::messages::prelude::FrontendMessage;
|
use graphite_editor::messages::prelude::FrontendMessage;
|
||||||
|
|
||||||
use super::DesktopWrapperMessageDispatcher;
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
use super::messages::{DesktopFrontendMessage, Document, FileFilter, OpenFileDialogContext, SaveFileDialogContext};
|
use super::messages::{DesktopFrontendMessage, Document, FileFilter, KeyCode, MenuItem, Modifiers, OpenFileDialogContext, SaveFileDialogContext, Shortcut};
|
||||||
|
|
||||||
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
|
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
|
||||||
match message {
|
match message {
|
||||||
@@ -119,7 +122,210 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
FrontendMessage::TriggerLoadPreferences => {
|
FrontendMessage::TriggerLoadPreferences => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadPreferences);
|
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadPreferences);
|
||||||
}
|
}
|
||||||
|
FrontendMessage::UpdateMenuBarLayout { layout_target, layout } => {
|
||||||
|
let entries = convert_menu_bar_entries_to_menu_items(&layout);
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::UpdateMenu { entries });
|
||||||
|
|
||||||
|
return Some(FrontendMessage::UpdateMenuBarLayout { layout, layout_target });
|
||||||
|
}
|
||||||
m => return Some(m),
|
m => return Some(m),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_menu_bar_entries_to_menu_items(layout: &Vec<MenuBarEntry>) -> Vec<MenuItem> {
|
||||||
|
layout.iter().filter_map(|entry| convert_menu_bar_entry_to_menu_item(entry)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_menu_bar_entry_to_menu_item(
|
||||||
|
MenuBarEntry {
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
shortcut,
|
||||||
|
action,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
}: &MenuBarEntry,
|
||||||
|
) -> Option<MenuItem> {
|
||||||
|
let id = action.widget_id.0;
|
||||||
|
let text = if label.is_empty() {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
label.clone()
|
||||||
|
};
|
||||||
|
let enabled = !*disabled;
|
||||||
|
|
||||||
|
if !children.0.is_empty() {
|
||||||
|
let items = convert_menu_bar_entry_children_to_menu_items(&children.0);
|
||||||
|
return Some(MenuItem::SubMenu { id, text, enabled, items });
|
||||||
|
}
|
||||||
|
|
||||||
|
let shortcut = match shortcut {
|
||||||
|
Some(ActionKeys::Keys(LayoutKeysGroup(keys))) => {
|
||||||
|
if let Some(shortcut) = convert_layout_keys_to_shortcut(&keys) {
|
||||||
|
Some(shortcut)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Find a better way to determine if this is a checkbox
|
||||||
|
match icon.as_deref() {
|
||||||
|
Some("CheckboxChecked") => {
|
||||||
|
return Some(MenuItem::Checkbox {
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
enabled,
|
||||||
|
shortcut,
|
||||||
|
checked: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some("CheckboxUnchecked") => {
|
||||||
|
return Some(MenuItem::Checkbox {
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
enabled,
|
||||||
|
shortcut,
|
||||||
|
checked: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(MenuItem::Action { id, text, shortcut, enabled })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_menu_bar_entry_children_to_menu_items(children: &Vec<Vec<MenuBarEntry>>) -> Vec<MenuItem> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for (i, section) in children.iter().enumerate() {
|
||||||
|
for entry in section.iter() {
|
||||||
|
if let Some(item) = convert_menu_bar_entry_to_menu_item(entry) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i != children.len() - 1 {
|
||||||
|
items.push(MenuItem::Separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_layout_keys_to_shortcut(layout_keys: &Vec<LayoutKey>) -> Option<Shortcut> {
|
||||||
|
let mut key: Option<KeyCode> = None;
|
||||||
|
let mut modifiers = Modifiers::default();
|
||||||
|
for layout_key in layout_keys {
|
||||||
|
match layout_key.key {
|
||||||
|
Key::Shift => modifiers |= Modifiers::SHIFT,
|
||||||
|
Key::Control => modifiers |= Modifiers::CONTROL,
|
||||||
|
Key::Alt => modifiers |= Modifiers::ALT,
|
||||||
|
Key::Meta => modifiers |= Modifiers::META,
|
||||||
|
Key::Command => modifiers |= Modifiers::ALT,
|
||||||
|
Key::Accel => modifiers |= Modifiers::META,
|
||||||
|
Key::Digit0 => key = Some(KeyCode::Digit0),
|
||||||
|
Key::Digit1 => key = Some(KeyCode::Digit1),
|
||||||
|
Key::Digit2 => key = Some(KeyCode::Digit2),
|
||||||
|
Key::Digit3 => key = Some(KeyCode::Digit3),
|
||||||
|
Key::Digit4 => key = Some(KeyCode::Digit4),
|
||||||
|
Key::Digit5 => key = Some(KeyCode::Digit5),
|
||||||
|
Key::Digit6 => key = Some(KeyCode::Digit6),
|
||||||
|
Key::Digit7 => key = Some(KeyCode::Digit7),
|
||||||
|
Key::Digit8 => key = Some(KeyCode::Digit8),
|
||||||
|
Key::Digit9 => key = Some(KeyCode::Digit9),
|
||||||
|
Key::KeyA => key = Some(KeyCode::KeyA),
|
||||||
|
Key::KeyB => key = Some(KeyCode::KeyB),
|
||||||
|
Key::KeyC => key = Some(KeyCode::KeyC),
|
||||||
|
Key::KeyD => key = Some(KeyCode::KeyD),
|
||||||
|
Key::KeyE => key = Some(KeyCode::KeyE),
|
||||||
|
Key::KeyF => key = Some(KeyCode::KeyF),
|
||||||
|
Key::KeyG => key = Some(KeyCode::KeyG),
|
||||||
|
Key::KeyH => key = Some(KeyCode::KeyH),
|
||||||
|
Key::KeyI => key = Some(KeyCode::KeyI),
|
||||||
|
Key::KeyJ => key = Some(KeyCode::KeyJ),
|
||||||
|
Key::KeyK => key = Some(KeyCode::KeyK),
|
||||||
|
Key::KeyL => key = Some(KeyCode::KeyL),
|
||||||
|
Key::KeyM => key = Some(KeyCode::KeyM),
|
||||||
|
Key::KeyN => key = Some(KeyCode::KeyN),
|
||||||
|
Key::KeyO => key = Some(KeyCode::KeyO),
|
||||||
|
Key::KeyP => key = Some(KeyCode::KeyP),
|
||||||
|
Key::KeyQ => key = Some(KeyCode::KeyQ),
|
||||||
|
Key::KeyR => key = Some(KeyCode::KeyR),
|
||||||
|
Key::KeyS => key = Some(KeyCode::KeyS),
|
||||||
|
Key::KeyT => key = Some(KeyCode::KeyT),
|
||||||
|
Key::KeyU => key = Some(KeyCode::KeyU),
|
||||||
|
Key::KeyV => key = Some(KeyCode::KeyV),
|
||||||
|
Key::KeyW => key = Some(KeyCode::KeyW),
|
||||||
|
Key::KeyX => key = Some(KeyCode::KeyX),
|
||||||
|
Key::KeyY => key = Some(KeyCode::KeyY),
|
||||||
|
Key::KeyZ => key = Some(KeyCode::KeyZ),
|
||||||
|
Key::Backquote => key = Some(KeyCode::Backquote),
|
||||||
|
Key::Backslash => key = Some(KeyCode::Backslash),
|
||||||
|
Key::BracketLeft => key = Some(KeyCode::BracketLeft),
|
||||||
|
Key::BracketRight => key = Some(KeyCode::BracketRight),
|
||||||
|
Key::Comma => key = Some(KeyCode::Comma),
|
||||||
|
Key::Equal => key = Some(KeyCode::Equal),
|
||||||
|
Key::Minus => key = Some(KeyCode::Minus),
|
||||||
|
Key::Period => key = Some(KeyCode::Period),
|
||||||
|
Key::Quote => key = Some(KeyCode::Quote),
|
||||||
|
Key::Semicolon => key = Some(KeyCode::Semicolon),
|
||||||
|
Key::Slash => key = Some(KeyCode::Slash),
|
||||||
|
Key::Backspace => key = Some(KeyCode::Backspace),
|
||||||
|
Key::CapsLock => key = Some(KeyCode::CapsLock),
|
||||||
|
Key::ContextMenu => key = Some(KeyCode::ContextMenu),
|
||||||
|
Key::Enter => key = Some(KeyCode::Enter),
|
||||||
|
Key::Space => key = Some(KeyCode::Space),
|
||||||
|
Key::Tab => key = Some(KeyCode::Tab),
|
||||||
|
Key::Delete => key = Some(KeyCode::Delete),
|
||||||
|
Key::End => key = Some(KeyCode::End),
|
||||||
|
Key::Help => key = Some(KeyCode::Help),
|
||||||
|
Key::Home => key = Some(KeyCode::Home),
|
||||||
|
Key::Insert => key = Some(KeyCode::Insert),
|
||||||
|
Key::PageDown => key = Some(KeyCode::PageDown),
|
||||||
|
Key::PageUp => key = Some(KeyCode::PageUp),
|
||||||
|
Key::ArrowDown => key = Some(KeyCode::ArrowDown),
|
||||||
|
Key::ArrowLeft => key = Some(KeyCode::ArrowLeft),
|
||||||
|
Key::ArrowRight => key = Some(KeyCode::ArrowRight),
|
||||||
|
Key::ArrowUp => key = Some(KeyCode::ArrowUp),
|
||||||
|
Key::NumLock => key = Some(KeyCode::NumLock),
|
||||||
|
Key::NumpadAdd => key = Some(KeyCode::NumpadAdd),
|
||||||
|
Key::NumpadHash => key = Some(KeyCode::NumpadHash),
|
||||||
|
Key::NumpadMultiply => key = Some(KeyCode::NumpadMultiply),
|
||||||
|
Key::NumpadParenLeft => key = Some(KeyCode::NumpadParenLeft),
|
||||||
|
Key::NumpadParenRight => key = Some(KeyCode::NumpadParenRight),
|
||||||
|
Key::Escape => key = Some(KeyCode::Escape),
|
||||||
|
Key::F1 => key = Some(KeyCode::F1),
|
||||||
|
Key::F2 => key = Some(KeyCode::F2),
|
||||||
|
Key::F3 => key = Some(KeyCode::F3),
|
||||||
|
Key::F4 => key = Some(KeyCode::F4),
|
||||||
|
Key::F5 => key = Some(KeyCode::F5),
|
||||||
|
Key::F6 => key = Some(KeyCode::F6),
|
||||||
|
Key::F7 => key = Some(KeyCode::F7),
|
||||||
|
Key::F8 => key = Some(KeyCode::F8),
|
||||||
|
Key::F9 => key = Some(KeyCode::F9),
|
||||||
|
Key::F10 => key = Some(KeyCode::F10),
|
||||||
|
Key::F11 => key = Some(KeyCode::F11),
|
||||||
|
Key::F12 => key = Some(KeyCode::F12),
|
||||||
|
Key::F13 => key = Some(KeyCode::F13),
|
||||||
|
Key::F14 => key = Some(KeyCode::F14),
|
||||||
|
Key::F15 => key = Some(KeyCode::F15),
|
||||||
|
Key::F16 => key = Some(KeyCode::F16),
|
||||||
|
Key::F17 => key = Some(KeyCode::F17),
|
||||||
|
Key::F18 => key = Some(KeyCode::F18),
|
||||||
|
Key::F19 => key = Some(KeyCode::F19),
|
||||||
|
Key::F20 => key = Some(KeyCode::F20),
|
||||||
|
Key::F21 => key = Some(KeyCode::F21),
|
||||||
|
Key::F22 => key = Some(KeyCode::F22),
|
||||||
|
Key::F23 => key = Some(KeyCode::F23),
|
||||||
|
Key::F24 => key = Some(KeyCode::F24),
|
||||||
|
Key::Fn => key = Some(KeyCode::Fn),
|
||||||
|
Key::FnLock => key = Some(KeyCode::FnLock),
|
||||||
|
Key::PrintScreen => key = Some(KeyCode::PrintScreen),
|
||||||
|
Key::ScrollLock => key = Some(KeyCode::ScrollLock),
|
||||||
|
Key::Pause => key = Some(KeyCode::Pause),
|
||||||
|
Key::Unidentified => key = Some(KeyCode::Unidentified),
|
||||||
|
_ => key = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(key) = key { Some(Shortcut { key, modifiers }) } else { None }
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ impl<'a> DesktopWrapperMessageDispatcher<'a> {
|
|||||||
self.desktop_wrapper_message_queue.push_back(message);
|
self.desktop_wrapper_message_queue.push_back(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn queue_editor_message(&mut self, message: EditorMessage) {
|
pub(super) fn queue_editor_message<T: Into<EditorMessage>>(&mut self, message: T) {
|
||||||
if let Some(message) = intercept_editor_message(self, message) {
|
if let Some(message) = intercept_editor_message(self, message.into()) {
|
||||||
self.editor_message_queue.push(message);
|
self.editor_message_queue.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
pub use graphite_editor::messages::prelude::DocumentId;
|
|
||||||
use graphite_editor::messages::prelude::FrontendMessage;
|
use graphite_editor::messages::prelude::FrontendMessage;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
|
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
|
||||||
|
|
||||||
|
pub use graphite_editor::messages::prelude::DocumentId;
|
||||||
pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Preferences;
|
pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Preferences;
|
||||||
|
|
||||||
pub enum DesktopFrontendMessage {
|
pub enum DesktopFrontendMessage {
|
||||||
ToWeb(Vec<FrontendMessage>),
|
ToWeb(Vec<FrontendMessage>),
|
||||||
OpenLaunchDocuments,
|
OpenLaunchDocuments,
|
||||||
@@ -56,6 +55,9 @@ pub enum DesktopFrontendMessage {
|
|||||||
preferences: Preferences,
|
preferences: Preferences,
|
||||||
},
|
},
|
||||||
PersistenceLoadPreferences,
|
PersistenceLoadPreferences,
|
||||||
|
UpdateMenu {
|
||||||
|
entries: Vec<MenuItem>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum DesktopWrapperMessage {
|
pub enum DesktopWrapperMessage {
|
||||||
@@ -106,6 +108,9 @@ pub enum DesktopWrapperMessage {
|
|||||||
LoadPreferences {
|
LoadPreferences {
|
||||||
preferences: Option<Preferences>,
|
preferences: Option<Preferences>,
|
||||||
},
|
},
|
||||||
|
MenuEvent {
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
|
||||||
@@ -136,3 +141,32 @@ pub enum Platform {
|
|||||||
Mac,
|
Mac,
|
||||||
Linux,
|
Linux,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum MenuItem {
|
||||||
|
Action {
|
||||||
|
id: u64,
|
||||||
|
text: String,
|
||||||
|
enabled: bool,
|
||||||
|
shortcut: Option<Shortcut>,
|
||||||
|
},
|
||||||
|
Checkbox {
|
||||||
|
id: u64,
|
||||||
|
text: String,
|
||||||
|
enabled: bool,
|
||||||
|
shortcut: Option<Shortcut>,
|
||||||
|
checked: bool,
|
||||||
|
},
|
||||||
|
SubMenu {
|
||||||
|
id: u64,
|
||||||
|
text: String,
|
||||||
|
enabled: bool,
|
||||||
|
items: Vec<MenuItem>,
|
||||||
|
},
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use keyboard_types::{Code as KeyCode, Modifiers};
|
||||||
|
pub struct Shortcut {
|
||||||
|
pub key: KeyCode,
|
||||||
|
pub modifiers: Modifiers,
|
||||||
|
}
|
||||||
|
|||||||
@@ -304,16 +304,13 @@ impl fmt::Display for Key {
|
|||||||
|
|
||||||
impl From<Key> for LayoutKey {
|
impl From<Key> for LayoutKey {
|
||||||
fn from(key: Key) -> Self {
|
fn from(key: Key) -> Self {
|
||||||
Self {
|
Self { key, label: key.to_string() }
|
||||||
key: format!("{key:?}"),
|
|
||||||
label: key.to_string(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
struct LayoutKey {
|
pub struct LayoutKey {
|
||||||
key: String,
|
pub key: Key,
|
||||||
label: String,
|
label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +356,7 @@ impl From<KeysGroup> for String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct LayoutKeysGroup(Vec<LayoutKey>);
|
pub struct LayoutKeysGroup(pub Vec<LayoutKey>);
|
||||||
|
|
||||||
impl From<KeysGroup> for LayoutKeysGroup {
|
impl From<KeysGroup> for LayoutKeysGroup {
|
||||||
fn from(keys_group: KeysGroup) -> Self {
|
fn from(keys_group: KeysGroup) -> Self {
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export class HintInfo {
|
|||||||
|
|
||||||
// Rust enum `Key`
|
// Rust enum `Key`
|
||||||
export type KeyRaw = string;
|
export type KeyRaw = string;
|
||||||
// Serde converts a Rust `Key` enum variant into this format (via a custom serializer) with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
// Serde converts a Rust `Key` enum variant into this format with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
||||||
export type Key = { key: KeyRaw; label: string };
|
export type Key = { key: KeyRaw; label: string };
|
||||||
export type LayoutKeysGroup = Key[];
|
export type LayoutKeysGroup = Key[];
|
||||||
export type ActionKeys = { keys: LayoutKeysGroup };
|
export type ActionKeys = { keys: LayoutKeysGroup };
|
||||||
|
|||||||
Reference in New Issue
Block a user