mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
implement native -> browser communication
This commit is contained in:
parent
4f21693772
commit
d2b3bc2a87
8 changed files with 35 additions and 120 deletions
|
|
@ -2,10 +2,11 @@ use crate::CustomEvent;
|
|||
use crate::WindowSize;
|
||||
use crate::render::GraphicsState;
|
||||
use crate::render::WgpuContext;
|
||||
use ::cef::CefString;
|
||||
use ::cef::ImplBrowser;
|
||||
use ::cef::ImplFrame;
|
||||
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;
|
||||
|
|
@ -109,10 +110,21 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||
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
|
||||
let Some(frame) = self.cef_context.browser.as_ref().unwrap().main_frame() else {
|
||||
tracing::error!("Could not get frame after editor processed messages");
|
||||
return;
|
||||
};
|
||||
for frontend_message in responses {
|
||||
let Ok(serialized_message) = serde_json::to_string(&frontend_message) else {
|
||||
tracing::error!("Failed to serialize frontend message in CustomEvent::MessageReceived");
|
||||
continue;
|
||||
};
|
||||
let message = format!("window.handle.sendMessageToFrontendFromCEF(\'{serialized_message}\')");
|
||||
let code = CefString::from(message.as_str());
|
||||
frame.execute_java_script(Some(&code), None, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub(crate) trait CefEventHandler: Clone {
|
|||
/// [`_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);
|
||||
fn send_message_to_editor(&self, message: String);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
@ -118,7 +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) {
|
||||
fn send_message_to_editor(&self, message: String) {
|
||||
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod non_browser_app;
|
|||
mod non_browser_render_process_handler;
|
||||
mod non_browser_v8_handler;
|
||||
mod render_handler;
|
||||
mod utility;
|
||||
|
||||
pub(crate) use app::AppImpl;
|
||||
pub(crate) use client::ClientImpl;
|
||||
|
|
|
|||
|
|
@ -30,25 +30,19 @@ impl<H: CefEventHandler> ImplClient for ClientImpl<H> {
|
|||
|
||||
fn on_process_message_received(
|
||||
&self,
|
||||
browser: Option<&mut cef::Browser>,
|
||||
frame: Option<&mut cef::Frame>,
|
||||
source_process: cef::ProcessId,
|
||||
_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");
|
||||
tracing::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);
|
||||
let string_message = super::utility::pointer_to_string(pointer);
|
||||
let _ = self.event_handler.send_message_to_editor(string_message);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,7 @@ impl ImplV8Handler for NonBrowserV8HandlerImpl {
|
|||
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 message = super::utility::pointer_to_string(pointer);
|
||||
|
||||
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");
|
||||
|
|
|
|||
8
desktop/src/cef/internal/utility.rs
Normal file
8
desktop/src/cef/internal/utility.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
pub fn pointer_to_string(pointer: *mut cef::sys::_cef_string_utf16_t) -> String {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
editor = createEditor();
|
||||
|
||||
window.handle = editor.handle;
|
||||
// Auto save every 15 seconds
|
||||
autoSaveAllDocumentsId = setInterval(() => {
|
||||
editor?.handle.autoSaveAllDocuments();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,8 @@
|
|||
use crate::Message;
|
||||
use editor::messages::prelude::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use js_sys::{Object, Reflect};
|
||||
use serde::ser::Serialize;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};
|
||||
|
||||
// TODO: Remove
|
||||
static IMAGE_DATA_HASH: AtomicU64 = AtomicU64::new(0);
|
||||
fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = DefaultHasher::new();
|
||||
t.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
|
|
@ -40,17 +25,6 @@ impl EditorHandle {
|
|||
pub fn send_message_to_frontend_from_cef(&self, message: String) {
|
||||
let Ok(mut message) = serde_json::from_str::<FrontendMessage>(&message) else { return };
|
||||
|
||||
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
||||
let new_hash = calculate_hash(image_data);
|
||||
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
|
||||
|
||||
if new_hash != prev_hash {
|
||||
render_image_data_to_canvases(image_data.as_slice());
|
||||
IMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message {
|
||||
message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() };
|
||||
}
|
||||
|
|
@ -96,73 +70,3 @@ pub fn send_message_to_cef<T: Into<Message>>(message: T) {
|
|||
// Call it with argument
|
||||
func.call1(&JsValue::NULL, &JsValue::from_str(&serialized_message)).expect("Function call failed");
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
fn render_image_data_to_canvases(image_data: &[(u64, Image<Color>)]) {
|
||||
let window = match window() {
|
||||
Some(window) => window,
|
||||
None => {
|
||||
error!("Cannot render canvas: window object not found");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let document = window.document().expect("window should have a document");
|
||||
let window_obj = Object::from(window);
|
||||
let image_canvases_key = JsValue::from_str("imageCanvases");
|
||||
|
||||
let canvases_obj = match Reflect::get(&window_obj, &image_canvases_key) {
|
||||
Ok(obj) if !obj.is_undefined() && !obj.is_null() => obj,
|
||||
_ => {
|
||||
let new_obj = Object::new();
|
||||
if Reflect::set(&window_obj, &image_canvases_key, &new_obj).is_err() {
|
||||
error!("Failed to create and set imageCanvases object on window");
|
||||
return;
|
||||
}
|
||||
new_obj.into()
|
||||
}
|
||||
};
|
||||
let canvases_obj = Object::from(canvases_obj);
|
||||
|
||||
for (placeholder_id, image) in image_data.iter() {
|
||||
let canvas_name = placeholder_id.to_string();
|
||||
let js_key = JsValue::from_str(&canvas_name);
|
||||
|
||||
if Reflect::has(&canvases_obj, &js_key).unwrap_or(false) || image.width == 0 || image.height == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.expect("Failed to create canvas element")
|
||||
.dyn_into::<HtmlCanvasElement>()
|
||||
.expect("Failed to cast element to HtmlCanvasElement");
|
||||
|
||||
canvas.set_width(image.width);
|
||||
canvas.set_height(image.height);
|
||||
|
||||
let context: CanvasRenderingContext2d = canvas
|
||||
.get_context("2d")
|
||||
.expect("Failed to get 2d context")
|
||||
.expect("2d context was not found")
|
||||
.dyn_into::<CanvasRenderingContext2d>()
|
||||
.expect("Failed to cast context to CanvasRenderingContext2d");
|
||||
let u8_data: Vec<u8> = image.data.iter().flat_map(|color| color.to_rgba8_srgb()).collect();
|
||||
let clamped_u8_data = wasm_bindgen::Clamped(&u8_data[..]);
|
||||
match ImageData::new_with_u8_clamped_array_and_sh(clamped_u8_data, image.width, image.height) {
|
||||
Ok(image_data_obj) => {
|
||||
if context.put_image_data(&image_data_obj, 0., 0.).is_err() {
|
||||
error!("Failed to put image data on canvas for id: {placeholder_id}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to create ImageData for id: {placeholder_id}: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let js_value = JsValue::from(canvas);
|
||||
|
||||
if Reflect::set(&canvases_obj, &js_key, &js_value).is_err() {
|
||||
error!("Failed to set canvas '{canvas_name}' on imageCanvases object");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue