mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Execute editor natively
This commit is contained in:
parent
a52ee70e4c
commit
d3fe17bc45
21 changed files with 296 additions and 29 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<cef::Initialized>,
|
||||
pub(crate) window: Option<Arc<Window>>,
|
||||
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>,
|
||||
window_size_sender: Sender<WindowSize>,
|
||||
_viewport_frame_buffer: Option<wgpu::Texture>,
|
||||
graphics_state: Option<GraphicsState>,
|
||||
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<CustomEvent> for WinitApp {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ impl Context<Setup> {
|
|||
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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<H: CefEventHandler> {
|
||||
object: *mut RcImpl<_cef_client_t, Self>,
|
||||
render_handler: RenderHandler,
|
||||
event_handler: H,
|
||||
}
|
||||
impl ClientImpl {
|
||||
pub(crate) fn new(render_handler: RenderHandler) -> Self {
|
||||
impl<H: CefEventHandler> ClientImpl<H> {
|
||||
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<H: CefEventHandler> ImplClient for ClientImpl<H> {
|
||||
fn render_handler(&self) -> Option<RenderHandler> {
|
||||
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<H: CefEventHandler> Clone for ClientImpl<H> {
|
||||
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<H: CefEventHandler> Rc for ClientImpl<H> {
|
||||
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<H: CefEventHandler> WrapClient for ClientImpl<H> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
|
||||
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::{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<cef::RenderProcessHandler> {
|
||||
Some(cef::RenderProcessHandler::new(NonBrowserRenderProcessHandlerImpl::new()))
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
85
desktop/src/cef/internal/non_browser_v8_handler.rs
Normal file
85
desktop/src/cef/internal/non_browser_v8_handler.rs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -99,10 +99,16 @@ pub(crate) struct GraphicsState {
|
|||
surface: wgpu::Surface<'static>,
|
||||
context: WgpuContext,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
texture: Option<wgpu::Texture>,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
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 {
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
import Editor from "@graphite/components/Editor.svelte";
|
||||
|
||||
import { send_message_to_cef } from "/wasm/pkg/graphite_wasm";
|
||||
|
||||
let editor: GraphiteEditor | undefined = undefined;
|
||||
|
||||
onMount(async () => {
|
||||
|
|
@ -17,6 +19,9 @@
|
|||
// Destroy the WASM editor handle
|
||||
editor?.handle.free();
|
||||
});
|
||||
|
||||
console.log("Test from app.svelte javascript");
|
||||
sendMessageToCef("Test from app direct");
|
||||
</script>
|
||||
|
||||
{#if editor !== undefined}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
onMount(() => {
|
||||
// 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());
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@
|
|||
|
||||
function canvasPointerDown(e: PointerEvent) {
|
||||
const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable;
|
||||
|
||||
console.log("Canvas pointer down", e, onEditbox);
|
||||
if (!onEditbox) viewport?.setPointerCapture(e.pointerId);
|
||||
if (window.document.activeElement instanceof HTMLElement) {
|
||||
window.document.activeElement.blur();
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ impl EditorHandle {
|
|||
}
|
||||
|
||||
// Sends a message to the dispatcher in the Editor Backend
|
||||
#[cfg(not(feature = "native"))]
|
||||
fn dispatch<T: Into<Message>>(&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<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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -27,6 +27,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 +106,24 @@ extern "C" {
|
|||
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)]
|
||||
pub struct WasmLog;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue