From a99915c8c3ef5aa71a62bf495693f599e088a7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kav=C3=ADk?= Date: Tue, 8 Oct 2024 14:40:01 +0200 Subject: [PATCH] excalidraw_canvas.rs --- frontend/src/diagram_panel.rs | 20 +++ .../src/diagram_panel/excalidraw_canvas.rs | 141 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 frontend/src/diagram_panel/excalidraw_canvas.rs diff --git a/frontend/src/diagram_panel.rs b/frontend/src/diagram_panel.rs index 72d77ed..56bcb0c 100644 --- a/frontend/src/diagram_panel.rs +++ b/frontend/src/diagram_panel.rs @@ -1,13 +1,20 @@ use zoon::*; +mod excalidraw_canvas; +use excalidraw_canvas::ExcalidrawCanvas; +pub use excalidraw_canvas::ExcalidrawController; + #[derive(Clone)] pub struct DiagramPanel { + canvas_controller: Mutable>>> } impl DiagramPanel { pub fn new( + canvas_controller: Mutable>>>, ) -> impl Element { Self { + canvas_controller } .root() } @@ -20,5 +27,18 @@ impl DiagramPanel { .s(Height::fill()) .s(Gap::new().y(20)) .item("Diagram panel") + .item(self.canvas()) + } + + fn canvas(&self) -> impl Element { + let canvas_controller = self.canvas_controller.clone(); + ExcalidrawCanvas::new() + .s(Align::new().top()) + .s(Width::fill()) + .s(Height::fill()) + .task_with_controller(move |controller| { + canvas_controller.set(controller.clone()); + println!("hello from task_with_controller") + }) } } diff --git a/frontend/src/diagram_panel/excalidraw_canvas.rs b/frontend/src/diagram_panel/excalidraw_canvas.rs new file mode 100644 index 0000000..09d1b96 --- /dev/null +++ b/frontend/src/diagram_panel/excalidraw_canvas.rs @@ -0,0 +1,141 @@ +use crate::platform; +pub use js_bridge::ExcalidrawController; +use std::rc::Rc; +use zoon::*; + +pub struct ExcalidrawCanvas { + raw_el: RawHtmlEl, + controller: Mutable>>, + #[allow(dead_code)] + width: ReadOnlyMutable, + #[allow(dead_code)] + height: ReadOnlyMutable, + task_with_controller: Mutable>, +} + +impl Element for ExcalidrawCanvas {} + +impl RawElWrapper for ExcalidrawCanvas { + type RawEl = RawHtmlEl; + fn raw_el_mut(&mut self) -> &mut Self::RawEl { + &mut self.raw_el + } +} + +impl Styleable<'_> for ExcalidrawCanvas {} +impl KeyboardEventAware for ExcalidrawCanvas {} +impl MouseEventAware for ExcalidrawCanvas {} +impl PointerEventAware for ExcalidrawCanvas {} +impl TouchEventAware for ExcalidrawCanvas {} +impl AddNearbyElement<'_> for ExcalidrawCanvas {} +impl HasIds for ExcalidrawCanvas {} + +impl ExcalidrawCanvas { + pub fn new() -> Self { + let controller: Mutable>> = + Mutable::new(None); + let width = Mutable::new(0); + let height = Mutable::new(0); + let resize_task = Task::start_droppable( + map_ref! { + let width = width.signal(), + let height = height.signal() => (*width, *height) + } + .dedupe() + .throttle(|| Timer::sleep(50)) + .for_each( + clone!((controller) move |(width, height)| clone!((controller) async move { + if let Some(controller) = controller.lock_ref().as_ref() { + controller.resize(width, height).await + } + })), + ), + ); + let task_with_controller = Mutable::new(None); + Self { + controller: controller.clone(), + width: width.read_only(), + height: height.read_only(), + task_with_controller: task_with_controller.clone(), + raw_el: El::new() + .s(Clip::both()) + .on_viewport_size_change(clone!((width, height) move |new_width, new_height| { + width.set_neq(new_width); + height.set_neq(new_height); + })) + .update_raw_el(|raw_el| { + // @TODO rewrite to a native Zoon API + raw_el.event_handler_with_options( + EventOptions::new().preventable(), + clone!((controller) move |event: events_extra::WheelEvent| { + event.prevent_default(); + if let Some(controller) = controller.lock_ref().as_ref() { + controller.zoom_or_pan( + event.delta_y(), + event.shift_key(), + event.offset_x() as u32, + ); + } + }), + ) + }) + .after_insert(clone!((controller, timeline_getter) move |element| { + Task::start(async move { + let pixi_controller = SendWrapper::new(js_bridge::ExcalidrawController::new( + 1., + width.get(), + 0, + row_height, + row_gap, + &timeline_getter + )); + pixi_controller.init(&element).await; + controller.set(Some(pixi_controller)); + }); + })) + .after_remove(move |_| { + drop(timeline_getter); + drop(resize_task); + drop(task_with_controller); + if let Some(controller) = controller.take() { + controller.destroy(); + } + }) + .into_raw_el(), + } + } + + pub fn task_with_controller + 'static>( + self, + f: impl FnOnce(Mutable>>) -> FUT, + ) -> Self { + self.task_with_controller + .set(Some(Task::start_droppable(f(self.controller.clone())))); + self + } +} + +mod js_bridge { + use zoon::*; + + // Note: Add all corresponding methods to `frontend/typescript/excalidraw_canvas/excalidraw_canvas.ts` + #[wasm_bindgen(module = "/typescript/bundles/excalidraw_canvas.js")] + extern "C" { + #[derive(Clone)] + pub type ExcalidrawController; + + #[wasm_bindgen(method)] + pub async fn init(this: &ExcalidrawController, parent_element: &JsValue); + + #[wasm_bindgen(method)] + pub async fn resize(this: &ExcalidrawController, width: u32, height: u32); + + #[wasm_bindgen(method)] + pub fn destroy(this: &ExcalidrawController); + + // -- FastWave-specific -- + + #[wasm_bindgen(method)] + pub fn get_timeline_zoom(this: &ExcalidrawController) -> f64; + } +}