Execute editor natively

This commit is contained in:
Adam
2025-07-25 11:01:21 -07:00
parent 4fec24893e
commit 0c60ebaabc
21 changed files with 296 additions and 29 deletions
Generated
+3
View File
@@ -1837,7 +1837,9 @@ dependencies = [
"cef", "cef",
"dirs", "dirs",
"futures", "futures",
"graphite-editor",
"include_dir", "include_dir",
"serde_json",
"thiserror 2.0.12", "thiserror 2.0.12",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -1904,6 +1906,7 @@ dependencies = [
"math-parser", "math-parser",
"serde", "serde",
"serde-wasm-bindgen", "serde-wasm-bindgen",
"serde_json",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
+11 -9
View File
@@ -9,17 +9,18 @@ edition = "2024"
rust-version = "1.87" rust-version = "1.87"
[features] [features]
# default = ["gpu"] default = ["gpu"]
# gpu = ["graphite-editor/gpu"] gpu = ["graphite-editor/gpu"]
[dependencies] [dependencies]
# Local dependencies # # Local dependencies
# graphite-editor = { path = "../editor", features = [ graphite-editor = { path = "../editor", features = [
# "gpu", "gpu",
# "ron", "ron",
# "vello", "vello",
# "decouple-execution", "decouple-execution",
# ] } ] }
wgpu = { workspace = true } wgpu = { workspace = true }
winit = { workspace = true, features = ["serde"] } winit = { workspace = true, features = ["serde"] }
thiserror = { workspace = true } thiserror = { workspace = true }
@@ -29,3 +30,4 @@ include_dir = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
dirs = {workspace = true} dirs = {workspace = true}
serde_json = { workspace = true }
+17
View File
@@ -2,6 +2,10 @@ use crate::CustomEvent;
use crate::WindowSize; use crate::WindowSize;
use crate::render::GraphicsState; use crate::render::GraphicsState;
use crate::render::WgpuContext; 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::Arc;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::time::Duration; use std::time::Duration;
@@ -21,11 +25,13 @@ pub(crate) struct WinitApp {
pub(crate) cef_context: cef::Context<cef::Initialized>, pub(crate) cef_context: cef::Context<cef::Initialized>,
pub(crate) window: Option<Arc<Window>>, pub(crate) window: Option<Arc<Window>>,
cef_schedule: Option<Instant>, cef_schedule: Option<Instant>,
// Cached frame buffer from CEF, used to check if mouse is on a transparent pixel
_ui_frame_buffer: Option<wgpu::Texture>, _ui_frame_buffer: Option<wgpu::Texture>,
window_size_sender: Sender<WindowSize>, window_size_sender: Sender<WindowSize>,
_viewport_frame_buffer: Option<wgpu::Texture>, _viewport_frame_buffer: Option<wgpu::Texture>,
graphics_state: Option<GraphicsState>, graphics_state: Option<GraphicsState>,
wgpu_context: WgpuContext, wgpu_context: WgpuContext,
pub(crate) editor: Editor,
} }
impl WinitApp { impl WinitApp {
@@ -39,6 +45,7 @@ impl WinitApp {
graphics_state: None, graphics_state: None,
window_size_sender, window_size_sender,
wgpu_context, wgpu_context,
editor: Editor::new(),
} }
} }
} }
@@ -97,6 +104,16 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.cef_schedule = Some(instant); self.cef_schedule = Some(instant);
} }
} }
CustomEvent::MessageReceived { message } => {
let Ok(message) = serde_json::from_str::<Message>(&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
}
} }
} }
+5
View File
@@ -19,6 +19,8 @@ pub(crate) trait CefEventHandler: Clone {
/// Scheudule the main event loop to run the cef event loop after the timeout /// 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. /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
fn send_message_to_editior(&self, message: String);
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -116,4 +118,7 @@ impl CefEventHandler for CefHandler {
fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); 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 });
}
} }
+2 -2
View File
@@ -77,8 +77,8 @@ impl Context<Setup> {
return Err(InitError::InitializationFailed); return Err(InitError::InitializationFailed);
} }
let render_handler = RenderHandlerImpl::new(event_handler.clone()); let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
let mut client = Client::new(ClientImpl::new(RenderHandler::new(render_handler))); let mut client = Client::new(ClientImpl::new(render_handler, event_handler.clone()));
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
+2
View File
@@ -2,6 +2,8 @@ mod app;
mod browser_process_handler; mod browser_process_handler;
mod client; mod client;
mod non_browser_app; mod non_browser_app;
mod non_browser_render_process_handler;
mod non_browser_v8_handler;
mod render_handler; mod render_handler;
pub(crate) use app::AppImpl; pub(crate) use app::AppImpl;
+1
View File
@@ -3,6 +3,7 @@ use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp}; use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp};
use crate::cef::CefEventHandler; use crate::cef::CefEventHandler;
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
use super::browser_process_handler::BrowserProcessHandlerImpl; use super::browser_process_handler::BrowserProcessHandlerImpl;
+37 -8
View File
@@ -1,21 +1,25 @@
use cef::rc::{Rc, RcImpl}; use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_client_t, cef_base_ref_counted_t}; 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<H: CefEventHandler> {
object: *mut RcImpl<_cef_client_t, Self>, object: *mut RcImpl<_cef_client_t, Self>,
render_handler: RenderHandler, render_handler: RenderHandler,
event_handler: H,
} }
impl ClientImpl { impl<H: CefEventHandler> ClientImpl<H> {
pub(crate) fn new(render_handler: RenderHandler) -> Self { pub(crate) fn new(render_handler: RenderHandler, event_handler: H) -> Self {
Self { Self {
object: std::ptr::null_mut(), object: std::ptr::null_mut(),
render_handler, render_handler,
event_handler,
} }
} }
} }
impl ImplClient for ClientImpl { impl<H: CefEventHandler> ImplClient for ClientImpl<H> {
fn render_handler(&self) -> Option<RenderHandler> { fn render_handler(&self) -> Option<RenderHandler> {
Some(self.render_handler.clone()) Some(self.render_handler.clone())
} }
@@ -23,9 +27,33 @@ impl ImplClient for ClientImpl {
fn get_raw(&self) -> *mut _cef_client_t { fn get_raw(&self) -> *mut _cef_client_t {
self.object.cast() 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<H: CefEventHandler> Clone for ClientImpl<H> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
unsafe { unsafe {
let rc_impl = &mut *self.object; let rc_impl = &mut *self.object;
@@ -34,10 +62,11 @@ impl Clone for ClientImpl {
Self { Self {
object: self.object, object: self.object,
render_handler: self.render_handler.clone(), render_handler: self.render_handler.clone(),
event_handler: self.event_handler.clone(),
} }
} }
} }
impl Rc for ClientImpl { impl<H: CefEventHandler> Rc for ClientImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t { fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe { unsafe {
let base = &*self.object; let base = &*self.object;
@@ -45,7 +74,7 @@ impl Rc for ClientImpl {
} }
} }
} }
impl WrapClient for ClientImpl { impl<H: CefEventHandler> WrapClient for ClientImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
self.object = object; self.object = object;
} }
@@ -2,6 +2,7 @@ use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{App, ImplApp, SchemeRegistrar, WrapApp}; use cef::{App, ImplApp, SchemeRegistrar, WrapApp};
use crate::cef::internal::non_browser_render_process_handler::NonBrowserRenderProcessHandlerImpl;
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
pub(crate) struct NonBrowserAppImpl { pub(crate) struct NonBrowserAppImpl {
@@ -14,6 +15,10 @@ impl NonBrowserAppImpl {
} }
impl ImplApp for NonBrowserAppImpl { impl ImplApp for NonBrowserAppImpl {
fn render_process_handler(&self) -> Option<cef::RenderProcessHandler> {
Some(cef::RenderProcessHandler::new(NonBrowserRenderProcessHandlerImpl::new()))
}
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
GraphiteSchemeHandlerFactory::register_schemes(registrar); GraphiteSchemeHandlerFactory::register_schemes(registrar);
} }
@@ -0,0 +1,64 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_browser_process_handler_t, _cef_render_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t, cef_v8_handler_t, cef_v8_propertyattribute_t};
use cef::{
CefString, ImplBrowserProcessHandler, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, SchemeHandlerFactory, V8Handler, V8Propertyattribute, V8Value, WrapBrowserProcessHandler,
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;
}
}
@@ -0,0 +1,85 @@
use cef::{
CefString, Frame, ImplFrame, ImplV8Context, ImplV8Handler, ImplV8Value, ProcessId, ProcessMessage, V8Value, WrapV8Handler, process_message_create,
rc::Rc,
string_userfree_utf16_free,
sys::{cef_process_id_t, cef_string_userfree_utf16_free},
v8_context_get_current_context,
};
use tracing::event;
use winit::event_loop::EventLoopProxy;
pub struct NonBrowserV8HandlerImpl {
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
}
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<V8Value>]>,
_retval: Option<&mut Option<V8Value>>,
_exception: Option<&mut cef::CefString>,
) -> ::std::os::raw::c_int {
if let Some(name) = name {
if name.to_string() == "sendMessageToCef".to_string() {
let string = arguments.unwrap().get(0).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 as usize);
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<cef::sys::_cef_v8_handler_t, Self>) {
self.object = object;
}
}
+3
View File
@@ -20,6 +20,9 @@ mod dirs;
pub(crate) enum CustomEvent { pub(crate) enum CustomEvent {
UiUpdate(wgpu::Texture), UiUpdate(wgpu::Texture),
ScheduleBrowserWork(Instant), 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() { fn main() {
+14 -7
View File
@@ -99,10 +99,16 @@ pub(crate) struct GraphicsState {
surface: wgpu::Surface<'static>, surface: wgpu::Surface<'static>,
context: WgpuContext, context: WgpuContext,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
sampler: wgpu::Sampler, sampler: wgpu::Sampler,
// Cached texture for UI rendering
ui_texture: Option<wgpu::Texture>,
ui_bind_group: Option<wgpu::BindGroup>,
// Cached texture for node graph output
// viewport_texture: Option<wgpu::Texture>,
// // Returned from CEF js event callback
pub viewport_top_left: (u32, u32),
} }
impl GraphicsState { impl GraphicsState {
@@ -211,10 +217,11 @@ impl GraphicsState {
surface, surface,
context, context,
config, config,
texture: None,
bind_group: None,
render_pipeline, render_pipeline,
sampler, 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) { pub(crate) fn bind_texture(&mut self, texture: &wgpu::Texture) {
let bind_group = self.create_bindgroup(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 { fn create_bindgroup(&self, texture: &wgpu::Texture) -> wgpu::BindGroup {
@@ -275,7 +282,7 @@ impl GraphicsState {
}); });
render_pass.set_pipeline(&self.render_pipeline); 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.set_bind_group(0, bind_group, &[]);
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
} else { } else {
-1
View File
@@ -14,7 +14,6 @@ license = "Apache-2.0"
default = ["wasm"] default = ["wasm"]
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"] wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
gpu = ["interpreted-executor/gpu", "wgpu-executor"] gpu = ["interpreted-executor/gpu", "wgpu-executor"]
tauri = ["ron", "decouple-execution"]
decouple-execution = [] decouple-execution = []
resvg = ["graphene-std/resvg"] resvg = ["graphene-std/resvg"]
vello = ["graphene-std/vello", "resvg"] vello = ["graphene-std/vello", "resvg"]
+2
View File
@@ -12,6 +12,7 @@
"production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"", "production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"",
"---------- BUILDS ----------": "", "---------- BUILDS ----------": "",
"build-dev": "npm run wasm:build-dev && vite build", "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-profiling": "npm run wasm:build-profiling && vite build",
"build": "npm run wasm:build-production && vite build", "build": "npm run wasm:build-production && vite build",
"---------- UTILITIES ----------": "", "---------- UTILITIES ----------": "",
@@ -19,6 +20,7 @@
"lint-fix": "eslint . --fix && tsc --noEmit", "lint-fix": "eslint . --fix && tsc --noEmit",
"---------- INTERNAL ----------": "", "---------- INTERNAL ----------": "",
"setup": "node package-installer.js", "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-dev": "wasm-pack build ./wasm --dev --target=web",
"wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web",
"wasm:build-production": "wasm-pack build ./wasm --release --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web",
+5
View File
@@ -5,6 +5,8 @@
import Editor from "@graphite/components/Editor.svelte"; import Editor from "@graphite/components/Editor.svelte";
import { send_message_to_cef } from "/wasm/pkg/graphite_wasm";
let editor: GraphiteEditor | undefined = undefined; let editor: GraphiteEditor | undefined = undefined;
onMount(async () => { onMount(async () => {
@@ -17,6 +19,9 @@
// Destroy the WASM editor handle // Destroy the WASM editor handle
editor?.handle.free(); editor?.handle.free();
}); });
console.log("Test from app.svelte javascript");
sendMessageToCef("Test from app direct");
</script> </script>
{#if editor !== undefined} {#if editor !== undefined}
+1
View File
@@ -48,6 +48,7 @@
onMount(() => { onMount(() => {
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready // Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
console.log("init after frontend ready from js");
editor.handle.initAfterFrontendReady(operatingSystem()); editor.handle.initAfterFrontendReady(operatingSystem());
}); });
@@ -171,7 +171,7 @@
function canvasPointerDown(e: PointerEvent) { function canvasPointerDown(e: PointerEvent) {
const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable; const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable;
console.log("Canvas pointer down", e, onEditbox);
if (!onEditbox) viewport?.setPointerCapture(e.pointerId); if (!onEditbox) viewport?.setPointerCapture(e.pointerId);
if (window.document.activeElement instanceof HTMLElement) { if (window.document.activeElement instanceof HTMLElement) {
window.document.activeElement.blur(); window.document.activeElement.blur();
+2 -1
View File
@@ -13,7 +13,7 @@ license = "Apache-2.0"
[features] [features]
default = ["gpu"] default = ["gpu"]
gpu = ["editor/gpu"] gpu = ["editor/gpu"]
tauri = ["editor/tauri"] native = []
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
@@ -31,6 +31,7 @@ graphene-std = { workspace = true }
graph-craft = { workspace = true } graph-craft = { workspace = true }
log = { workspace = true } log = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true }
wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true }
serde-wasm-bindgen = { workspace = true } serde-wasm-bindgen = { workspace = true }
js-sys = { workspace = true } js-sys = { workspace = true }
+17
View File
@@ -154,6 +154,7 @@ impl EditorHandle {
} }
// Sends a message to the dispatcher in the Editor Backend // Sends a message to the dispatcher in the Editor Backend
#[cfg(not(feature = "native"))]
fn dispatch<T: Into<Message>>(&self, message: T) { fn dispatch<T: Into<Message>>(&self, message: T) {
// Process no further messages after a crash to avoid spamming the console // Process no further messages after a crash to avoid spamming the console
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
@@ -169,6 +170,16 @@ impl EditorHandle {
} }
} }
#[cfg(feature = "native")]
fn dispatch<T: Into<Message>>(&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 // Sends a FrontendMessage to JavaScript
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) { fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
if let FrontendMessage::UpdateImageData { ref image_data } = message { 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 // Add additional JS -> Rust wrapper functions below as needed for calling
// the backend from the web frontend. // the backend from the web frontend.
// ======================================================================== // ========================================================================
#[cfg(not(feature = "native"))]
#[wasm_bindgen(js_name = initAfterFrontendReady)] #[wasm_bindgen(js_name = initAfterFrontendReady)]
pub fn init_after_frontend_ready(&self, platform: String) { pub fn init_after_frontend_ready(&self, platform: String) {
// Send initialization messages // Send initialization messages
+19
View File
@@ -27,6 +27,7 @@ thread_local! {
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn init_graphite() { pub fn init_graphite() {
// Set up the panic hook // Set up the panic hook
#[cfg(not(feature = "native"))]
panic::set_hook(Box::new(panic_hook)); panic::set_hook(Box::new(panic_hook));
// Set up the logger with a default level of debug // Set up the logger with a default level of debug
@@ -105,6 +106,24 @@ extern "C" {
fn trace(msg: &str, format: &str); fn trace(msg: &str, format: &str);
} }
#[wasm_bindgen]
extern "C" {
fn sendMessageToCefFromWasm(message: String);
}
#[wasm_bindgen]
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::<js_sys::Function>().expect("Not a function");
// Call it with argument
func.call1(&JsValue::NULL, &JsValue::from_str(&message)).expect("Function call failed");
}
#[derive(Default)] #[derive(Default)]
pub struct WasmLog; pub struct WasmLog;