Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ web-sys = { version = "=0.3.77", features = [
"ImageBitmapRenderingContext",
] }
winit = { git = "https://github.com/rust-windowing/winit.git" }
keyboard-types = "0.8"
url = "2.5"
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
Expand Down Expand Up @@ -225,3 +226,7 @@ debug = true
[profile.profiling]
inherits = "release"
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" }
1 change: 1 addition & 0 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ windows = { version = "0.58.0", features = [

# macOS-specific 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 }
objc = { version = "0.2", optional = true }
core-foundation = { version = "0.10", optional = true }
Expand Down
14 changes: 11 additions & 3 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -258,6 +258,11 @@ impl App {
}
});
}
DesktopFrontendMessage::UpdateMenu { entries } => {
if let Some(window) = &self.window {
window.update_menu(entries);
}
}
}
}

Expand Down Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/cef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions desktop/src/event.rs
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -9,6 +9,7 @@ pub(crate) enum AppEvent {
DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult),
CloseWindow,
MenuEvent { id: u64 },
}

#[derive(Clone)]
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/gpu_context.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
2 changes: 2 additions & 0 deletions desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/persist.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/render/graphics_state.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
13 changes: 10 additions & 3 deletions desktop/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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))
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
}
7 changes: 3 additions & 4 deletions desktop/src/window/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {}
}
}
22 changes: 17 additions & 5 deletions desktop/src/window/mac.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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;
99 changes: 99 additions & 0 deletions desktop/src/window/mac/menu.rs
Original file line number Diff line number Diff line change
@@ -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()
}
Loading
Loading