diff --git a/Cargo.lock b/Cargo.lock index 0116649cfa..7d905cfd23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1820,7 +1820,9 @@ dependencies = [ "cef", "dirs", "futures", + "graphite-editor", "include_dir", + "serde_json", "thiserror 2.0.12", "tracing", "tracing-subscriber", @@ -1887,6 +1889,7 @@ dependencies = [ "math-parser", "serde", "serde-wasm-bindgen", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 8594472325..ba1f3fa055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,6 @@ specta-macros = { opt-level = 1 } syn = { opt-level = 1 } [profile.release] -lto = "thin" debug = true [profile.profiling] diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 581f0ed604..83c1003856 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -9,17 +9,18 @@ edition = "2024" rust-version = "1.87" [features] -# default = ["gpu"] -# gpu = ["graphite-editor/gpu"] +default = ["gpu"] +gpu = ["graphite-editor/gpu"] [dependencies] -# Local dependencies -# graphite-editor = { path = "../editor", features = [ -# "gpu", -# "ron", -# "vello", -# "decouple-execution", -# ] } +# # Local dependencies +graphite-editor = { path = "../editor", features = [ + "gpu", + "ron", + "vello", + "decouple-execution", +] } + wgpu = { workspace = true } winit = { workspace = true, features = ["serde"] } thiserror = { workspace = true } @@ -29,3 +30,4 @@ include_dir = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } dirs = {workspace = true} +serde_json = { workspace = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index eced6556c1..9eebd77df0 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -2,6 +2,10 @@ use crate::CustomEvent; use crate::WindowSize; use crate::render::GraphicsState; use crate::render::WgpuContext; +use graphite_editor::application::Editor; +use graphite_editor::dispatcher::Dispatcher; +use graphite_editor::messages::prelude::Message; +use std::collections::VecDeque; use std::sync::Arc; use std::sync::mpsc::Sender; use std::time::Duration; @@ -21,11 +25,13 @@ pub(crate) struct WinitApp { pub(crate) cef_context: cef::Context, pub(crate) window: Option>, cef_schedule: Option, + // Cached frame buffer from CEF, used to check if mouse is on a transparent pixel _ui_frame_buffer: Option, window_size_sender: Sender, _viewport_frame_buffer: Option, graphics_state: Option, wgpu_context: WgpuContext, + pub(crate) editor: Editor, } impl WinitApp { @@ -39,6 +45,7 @@ impl WinitApp { graphics_state: None, window_size_sender, wgpu_context, + editor: Editor::new(), } } } @@ -97,6 +104,16 @@ impl ApplicationHandler for WinitApp { self.cef_schedule = Some(instant); } } + CustomEvent::MessageReceived { message } => { + let Ok(message) = serde_json::from_str::(&message) else { + tracing::error!("Message could not be deserialized: {:?}", message); + return; + }; + println!("Message received: {message:?}"); + let responses = self.editor.handle_message(message); + println!("responses: {:?}", responses); + // Send response to CEF + } } } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index d84e9ad672..64dc53d673 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -19,6 +19,8 @@ pub(crate) trait CefEventHandler: Clone { /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); + + fn send_message_to_editior(&self, message: String); } #[derive(Clone, Copy)] @@ -116,4 +118,7 @@ impl CefEventHandler for CefHandler { fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); } + fn send_message_to_editior(&self, message: String) { + let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message }); + } } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 04ebffb4ab..90eec13e5a 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -77,8 +77,8 @@ impl Context { return Err(InitError::InitializationFailed); } - let render_handler = RenderHandlerImpl::new(event_handler.clone()); - let mut client = Client::new(ClientImpl::new(RenderHandler::new(render_handler))); + let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone())); + let mut client = Client::new(ClientImpl::new(render_handler, event_handler.clone())); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs index 8473884fd3..37e140e4bf 100644 --- a/desktop/src/cef/internal.rs +++ b/desktop/src/cef/internal.rs @@ -2,6 +2,8 @@ mod app; mod browser_process_handler; mod client; mod non_browser_app; +mod non_browser_render_process_handler; +mod non_browser_v8_handler; mod render_handler; pub(crate) use app::AppImpl; diff --git a/desktop/src/cef/internal/app.rs b/desktop/src/cef/internal/app.rs index 5da815d162..33404334da 100644 --- a/desktop/src/cef/internal/app.rs +++ b/desktop/src/cef/internal/app.rs @@ -3,6 +3,7 @@ use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp}; use crate::cef::CefEventHandler; + use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; use super::browser_process_handler::BrowserProcessHandlerImpl; diff --git a/desktop/src/cef/internal/client.rs b/desktop/src/cef/internal/client.rs index 543f9c590d..91f28442b7 100644 --- a/desktop/src/cef/internal/client.rs +++ b/desktop/src/cef/internal/client.rs @@ -1,21 +1,25 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_client_t, cef_base_ref_counted_t}; -use cef::{ImplClient, RenderHandler, WrapClient}; +use cef::{ImplClient, ImplProcessMessage, RenderHandler, WrapClient}; -pub(crate) struct ClientImpl { +use crate::cef::CefEventHandler; + +pub(crate) struct ClientImpl { object: *mut RcImpl<_cef_client_t, Self>, render_handler: RenderHandler, + event_handler: H, } -impl ClientImpl { - pub(crate) fn new(render_handler: RenderHandler) -> Self { +impl ClientImpl { + pub(crate) fn new(render_handler: RenderHandler, event_handler: H) -> Self { Self { object: std::ptr::null_mut(), render_handler, + event_handler, } } } -impl ImplClient for ClientImpl { +impl ImplClient for ClientImpl { fn render_handler(&self) -> Option { Some(self.render_handler.clone()) } @@ -23,9 +27,33 @@ impl ImplClient for ClientImpl { fn get_raw(&self) -> *mut _cef_client_t { self.object.cast() } + + fn on_process_message_received( + &self, + browser: Option<&mut cef::Browser>, + frame: Option<&mut cef::Frame>, + source_process: cef::ProcessId, + message: Option<&mut cef::ProcessMessage>, + ) -> ::std::os::raw::c_int { + let Some(message) = message else { + tracing::event!(tracing::Level::ERROR, "No message in RenderProcessHandlerImpl::on_process_message_received"); + return 1; + }; + + let pointer: *mut cef::sys::_cef_string_utf16_t = message.name().into(); + let message = unsafe { + let str = (*pointer).str_; + let len = (*pointer).length; + let slice = std::slice::from_raw_parts(str, len as usize); + String::from_utf16(slice).unwrap() + }; + + let _ = self.event_handler.send_message_to_editior(message); + 0 + } } -impl Clone for ClientImpl { +impl Clone for ClientImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; @@ -34,10 +62,11 @@ impl Clone for ClientImpl { Self { object: self.object, render_handler: self.render_handler.clone(), + event_handler: self.event_handler.clone(), } } } -impl Rc for ClientImpl { +impl Rc for ClientImpl { fn as_base(&self) -> &cef_base_ref_counted_t { unsafe { let base = &*self.object; @@ -45,7 +74,7 @@ impl Rc for ClientImpl { } } } -impl WrapClient for ClientImpl { +impl WrapClient for ClientImpl { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) { self.object = object; } diff --git a/desktop/src/cef/internal/non_browser_app.rs b/desktop/src/cef/internal/non_browser_app.rs index 04007d729d..f460b6c0d2 100644 --- a/desktop/src/cef/internal/non_browser_app.rs +++ b/desktop/src/cef/internal/non_browser_app.rs @@ -2,6 +2,7 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::{App, ImplApp, SchemeRegistrar, WrapApp}; +use crate::cef::internal::non_browser_render_process_handler::NonBrowserRenderProcessHandlerImpl; use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; pub(crate) struct NonBrowserAppImpl { @@ -14,6 +15,10 @@ impl NonBrowserAppImpl { } impl ImplApp for NonBrowserAppImpl { + fn render_process_handler(&self) -> Option { + Some(cef::RenderProcessHandler::new(NonBrowserRenderProcessHandlerImpl::new())) + } + fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { GraphiteSchemeHandlerFactory::register_schemes(registrar); } diff --git a/desktop/src/cef/internal/non_browser_render_process_handler.rs b/desktop/src/cef/internal/non_browser_render_process_handler.rs new file mode 100644 index 0000000000..721f94fdd0 --- /dev/null +++ b/desktop/src/cef/internal/non_browser_render_process_handler.rs @@ -0,0 +1,61 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t}; +use cef::{CefString, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, WrapRenderProcessHandler, v8_value_create_function}; + +use crate::cef::internal::non_browser_v8_handler::NonBrowserV8HandlerImpl; + +pub(crate) struct NonBrowserRenderProcessHandlerImpl { + object: *mut RcImpl<_cef_render_process_handler_t, Self>, +} +impl NonBrowserRenderProcessHandlerImpl { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } + } +} + +impl ImplRenderProcessHandler for NonBrowserRenderProcessHandlerImpl { + fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) { + let Some(context) = context else { + tracing::event!(tracing::Level::ERROR, "No browser in RenderProcessHandlerImpl::on_context_created"); + return; + }; + let mut v8_handler = V8Handler::new(NonBrowserV8HandlerImpl::new()); + let Some(mut function) = v8_value_create_function(Some(&CefString::from("sendMessageToCef")), Some(&mut v8_handler)) else { + tracing::event!(tracing::Level::ERROR, "Failed to create V8 function"); + return; + }; + let Some(global) = context.global() else { + tracing::event!(tracing::Level::ERROR, "No global object in RenderProcessHandlerImpl::on_context_created"); + return; + }; + + global.set_value_bykey(Some(&CefString::from("sendMessageToCef")), Some(&mut function), V8Propertyattribute::default()); + } + + fn get_raw(&self) -> *mut _cef_render_process_handler_t { + self.object.cast() + } +} + +impl Clone for NonBrowserRenderProcessHandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { object: self.object } + } +} +impl Rc for NonBrowserRenderProcessHandlerImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapRenderProcessHandler for NonBrowserRenderProcessHandlerImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) { + self.object = object; + } +} diff --git a/desktop/src/cef/internal/non_browser_v8_handler.rs b/desktop/src/cef/internal/non_browser_v8_handler.rs new file mode 100644 index 0000000000..a06d437b1e --- /dev/null +++ b/desktop/src/cef/internal/non_browser_v8_handler.rs @@ -0,0 +1,77 @@ +use cef::{CefString, ImplFrame, ImplV8Context, ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, process_message_create, rc::Rc, sys::cef_process_id_t, v8_context_get_current_context}; + +pub struct NonBrowserV8HandlerImpl { + object: *mut cef::rc::RcImpl, +} + +impl NonBrowserV8HandlerImpl { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } + } +} + +impl ImplV8Handler for NonBrowserV8HandlerImpl { + fn execute( + &self, + name: Option<&cef::CefString>, + _object: Option<&mut V8Value>, + arguments: Option<&[Option]>, + _retval: Option<&mut Option>, + _exception: Option<&mut cef::CefString>, + ) -> ::std::os::raw::c_int { + if let Some(name) = name { + if name.to_string() == "sendMessageToCef" { + let string = arguments.unwrap().first().unwrap().as_ref().unwrap().string_value(); + + let pointer: *mut cef::sys::_cef_string_utf16_t = string.into(); + let message = unsafe { + let str = (*pointer).str_; + let len = (*pointer).length; + let slice = std::slice::from_raw_parts(str, len); + String::from_utf16(slice).unwrap() + }; + + let Some(mut process_message) = process_message_create(Some(&CefString::from(message.as_str()))) else { + tracing::event!(tracing::Level::ERROR, "Failed to create process message"); + return 0; + }; + + let Some(frame) = v8_context_get_current_context().and_then(|context| context.frame()) else { + tracing::event!(tracing::Level::ERROR, "No current V8 context in V8HandlerImpl::execute"); + return 0; + }; + frame.send_process_message(cef_process_id_t::PID_BROWSER.into(), Some(&mut process_message)); + } + } + 0 + } + + fn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t { + self.object.cast() + } +} + +impl Clone for NonBrowserV8HandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { object: self.object } + } +} + +impl Rc for NonBrowserV8HandlerImpl { + fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapV8Handler for NonBrowserV8HandlerImpl { + fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl) { + self.object = object; + } +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index c53848f8be..7e4c454316 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -20,6 +20,9 @@ mod dirs; pub(crate) enum CustomEvent { UiUpdate(wgpu::Texture), ScheduleBrowserWork(Instant), + MessageReceived { message: String }, + // // Called from the editor if the render node is evaluated and returns an UpdateViewport message + // ViewportUpdate { texture: wgpu::TextureView }, } fn main() { diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 5141544666..8b7a68a336 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -99,10 +99,16 @@ pub(crate) struct GraphicsState { surface: wgpu::Surface<'static>, context: WgpuContext, config: wgpu::SurfaceConfiguration, - texture: Option, - bind_group: Option, render_pipeline: wgpu::RenderPipeline, sampler: wgpu::Sampler, + + // Cached texture for UI rendering + ui_texture: Option, + ui_bind_group: Option, + // Cached texture for node graph output + // viewport_texture: Option, + // // Returned from CEF js event callback + pub viewport_top_left: (u32, u32), } impl GraphicsState { @@ -211,10 +217,11 @@ impl GraphicsState { surface, context, config, - texture: None, - bind_group: None, render_pipeline, sampler, + ui_texture: None, + ui_bind_group: None, + viewport_top_left: (0, 0), } } @@ -228,9 +235,9 @@ impl GraphicsState { pub(crate) fn bind_texture(&mut self, texture: &wgpu::Texture) { let bind_group = self.create_bindgroup(texture); - self.texture = Some(texture.clone()); + self.ui_texture = Some(texture.clone()); - self.bind_group = Some(bind_group); + self.ui_bind_group = Some(bind_group); } fn create_bindgroup(&self, texture: &wgpu::Texture) -> wgpu::BindGroup { @@ -275,7 +282,7 @@ impl GraphicsState { }); render_pass.set_pipeline(&self.render_pipeline); - if let Some(bind_group) = &self.bind_group { + if let Some(bind_group) = &self.ui_bind_group { render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle } else { diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f54ba79471..d5124a93fd 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -14,7 +14,6 @@ license = "Apache-2.0" default = ["wasm"] wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"] gpu = ["interpreted-executor/gpu", "wgpu-executor"] -tauri = ["ron", "decouple-execution"] decouple-execution = [] resvg = ["graphene-std/resvg"] vello = ["graphene-std/vello", "resvg"] diff --git a/editor/src/node_graph_executor/runtime_io.rs b/editor/src/node_graph_executor/runtime_io.rs index e4e6f1df40..bab5f191b9 100644 --- a/editor/src/node_graph_executor/runtime_io.rs +++ b/editor/src/node_graph_executor/runtime_io.rs @@ -15,10 +15,7 @@ extern "C" { #[derive(Debug)] pub struct NodeRuntimeIO { // Send to - #[cfg(any(not(feature = "tauri"), test))] sender: Sender, - #[cfg(all(feature = "tauri", not(test)))] - sender: Sender, receiver: Receiver, } @@ -31,25 +28,13 @@ impl Default for NodeRuntimeIO { impl NodeRuntimeIO { /// Creates a new NodeRuntimeIO instance pub fn new() -> Self { - #[cfg(any(not(feature = "tauri"), test))] - { - let (response_sender, response_receiver) = std::sync::mpsc::channel(); - let (request_sender, request_receiver) = std::sync::mpsc::channel(); - futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender))); + let (response_sender, response_receiver) = std::sync::mpsc::channel(); + let (request_sender, request_receiver) = std::sync::mpsc::channel(); + futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender))); - Self { - sender: request_sender, - receiver: response_receiver, - } - } - - #[cfg(all(feature = "tauri", not(test)))] - { - let (response_sender, response_receiver) = std::sync::mpsc::channel(); - Self { - sender: response_sender, - receiver: response_receiver, - } + Self { + sender: request_sender, + receiver: response_receiver, } } #[cfg(test)] @@ -59,44 +44,11 @@ impl NodeRuntimeIO { /// Sends a message to the NodeRuntime pub fn send(&self, message: GraphRuntimeRequest) -> Result<(), String> { - #[cfg(any(not(feature = "tauri"), test))] - { - self.sender.send(message).map_err(|e| e.to_string()) - } - - #[cfg(all(feature = "tauri", not(test)))] - { - let serialized = ron::to_string(&message).map_err(|e| e.to_string()).unwrap(); - wasm_bindgen_futures::spawn_local(async move { - let js_message = create_message_object(&serialized); - invoke("runtime_message", js_message).await; - }); - Ok(()) - } + self.sender.send(message).map_err(|e| e.to_string()) } /// Receives any pending updates from the NodeRuntime pub fn receive(&self) -> impl Iterator + use<'_> { - // TODO: This introduces extra latency - #[cfg(all(feature = "tauri", not(test)))] - { - let sender = self.sender.clone(); - // In the Tauri case, responses are handled separately via poll_node_runtime_updates - wasm_bindgen_futures::spawn_local(async move { - let messages = invoke_without_arg("poll_node_graph").await; - let vec: Vec<_> = ron::from_str(&messages.as_string().unwrap()).unwrap(); - for message in vec { - sender.send(message).unwrap(); - } - }); - } self.receiver.try_iter() } } - -#[cfg(all(feature = "tauri", not(test)))] -pub fn create_message_object(message: &str) -> JsValue { - let obj = js_sys::Object::new(); - js_sys::Reflect::set(&obj, &JsValue::from_str("message"), &JsValue::from_str(message)).unwrap(); - obj.into() -} diff --git a/frontend/package.json b/frontend/package.json index 07fc0460b0..40d36d4c88 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"", "---------- BUILDS ----------": "", "build-dev": "npm run wasm:build-dev && vite build", + "build-native": "npm run native:build-dev && vite build", "build-profiling": "npm run wasm:build-profiling && vite build", "build": "npm run wasm:build-production && vite build", "---------- UTILITIES ----------": "", @@ -19,6 +20,7 @@ "lint-fix": "eslint . --fix && tsc --noEmit", "---------- INTERNAL ----------": "", "setup": "node package-installer.js", + "native:build-dev": "wasm-pack build ./wasm --dev --target=web --features native", "wasm:build-dev": "wasm-pack build ./wasm --dev --target=web", "wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web", diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index bd36152f04..7f82625079 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -171,7 +171,6 @@ function canvasPointerDown(e: PointerEvent) { const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable; - if (!onEditbox) viewport?.setPointerCapture(e.pointerId); if (window.document.activeElement instanceof HTMLElement) { window.document.activeElement.blur(); diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index 13e45bd07e..1ba3ae2de3 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -13,7 +13,7 @@ license = "Apache-2.0" [features] default = ["gpu"] gpu = ["editor/gpu"] -tauri = ["editor/tauri"] +native = [] [lib] crate-type = ["cdylib", "rlib"] @@ -31,6 +31,7 @@ graphene-std = { workspace = true } graph-craft = { workspace = true } log = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } wasm-bindgen = { workspace = true } serde-wasm-bindgen = { workspace = true } js-sys = { workspace = true } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index c649ee17ac..842f564f4e 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -154,6 +154,7 @@ impl EditorHandle { } // Sends a message to the dispatcher in the Editor Backend + #[cfg(not(feature = "native"))] fn dispatch>(&self, message: T) { // Process no further messages after a crash to avoid spamming the console if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { @@ -169,6 +170,16 @@ impl EditorHandle { } } + #[cfg(feature = "native")] + fn dispatch>(&self, message: T) { + let message: Message = message.into(); + let Ok(serialized_message) = serde_json::to_string(&message) else { + log::error!("Failed to serialize message"); + return; + }; + crate::send_message_to_cef(serialized_message) + } + // Sends a FrontendMessage to JavaScript fn send_frontend_message_to_js(&self, mut message: FrontendMessage) { if let FrontendMessage::UpdateImageData { ref image_data } = message { @@ -202,11 +213,17 @@ impl EditorHandle { } } + #[cfg(feature = "native")] + #[wasm_bindgen(js_name = initAfterFrontendReady)] + pub fn init_after_frontend_ready(&self, platform: String) { + log::debug!("Init after frontend ready from rust"); + } // ======================================================================== // Add additional JS -> Rust wrapper functions below as needed for calling // the backend from the web frontend. // ======================================================================== + #[cfg(not(feature = "native"))] #[wasm_bindgen(js_name = initAfterFrontendReady)] pub fn init_after_frontend_ready(&self, platform: String) { // Send initialization messages @@ -889,7 +906,7 @@ fn editor(callback: impl FnOnce(&mut editor::application::Editor) -> } /// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments. -pub(crate) fn editor_and_handle(mut callback: impl FnMut(&mut Editor, &mut EditorHandle)) { +pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) { EDITOR_HANDLE.with(|editor_handle| { editor(|editor| { let mut guard = editor_handle.try_lock(); diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index 730fb4215c..2d67c9745e 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -7,7 +7,9 @@ extern crate log; pub mod editor_api; pub mod helpers; +use editor::application::Editor; use editor::messages::prelude::*; +use editor_api::EditorHandle; use std::panic; use std::sync::Mutex; use std::sync::atomic::{AtomicBool, Ordering}; @@ -27,6 +29,7 @@ thread_local! { #[wasm_bindgen(start)] pub fn init_graphite() { // Set up the panic hook + #[cfg(not(feature = "native"))] panic::set_hook(Box::new(panic_hook)); // Set up the logger with a default level of debug @@ -105,6 +108,28 @@ extern "C" { fn trace(msg: &str, format: &str); } +#[wasm_bindgen] +pub fn send_message_to_frontend(message: String) { + let Ok(message) = serde_json::from_str::(&message) else { return }; + + let callback = move |_: &mut Editor, handle: &mut EditorHandle| { + handle.send_frontend_message_to_js_rust_proxy(message); + }; + editor_api::editor_and_handle(callback); +} + +pub fn send_message_to_cef(message: String) { + let global = js_sys::global(); + + // Get the function by name + let func = js_sys::Reflect::get(&global, &JsValue::from_str("sendMessageToCef")).expect("Function not found"); + + let func = func.dyn_into::().expect("Not a function"); + + // Call it with argument + func.call1(&JsValue::NULL, &JsValue::from_str(&message)).expect("Function call failed"); +} + #[derive(Default)] pub struct WasmLog;