diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 9939108aa..ab961070d 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -85,6 +85,7 @@ [app.main.streams :as ms] [app.main.worker :as uw] [app.render-wasm :as wasm] + [app.render-wasm.api :as api] [app.util.code-gen.style-css :as css] [app.util.dom :as dom] [app.util.globals :as ug] @@ -348,8 +349,10 @@ ptk/WatchEvent (watch [_ state stream] (log/debug :hint "initialize-workspace" :file-id (dm/str file-id)) - (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) - rparams (rt/get-params state)] + (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) + rparams (rt/get-params state) + features (features/get-team-enabled-features state) + render-wasm? (contains? features "render-wasm/v1")] (->> (rx/merge (rx/of (ntf/hide) @@ -395,6 +398,13 @@ (rx/filter dch/commit?) (rx/map deref) (rx/mapcat (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}] + (when render-wasm? + (let [added (->> redo-changes + (filter #(= (:type %) :add-obj)) + (map :obj))] + (doseq [shape added] + (api/set-object [] shape)))) + (if (and save-undo? (seq undo-changes)) (let [entry {:undo-changes undo-changes :redo-changes redo-changes diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 3f41a55d0..559bd092e 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -292,10 +292,6 @@ (when @canvas-init? (wasm.api/resize-viewbox (:width vport) (:height vport)))) - (mf/with-effect [@canvas-init? base-objects] - (when (and @canvas-init? @initialized?) - (wasm.api/set-objects base-objects))) - (mf/with-effect [@canvas-init? preview-blend] (when (and @canvas-init? preview-blend) (wasm.api/request-render "with-effect"))) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index c5fcd50f8..7c084b5c1 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -16,10 +16,10 @@ [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cf] + [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.render :as render] [app.main.store :as st] - [app.main.ui.shapes.text.fontfaces :as fonts] [app.render-wasm.helpers :as h] [app.render-wasm.serializers :as sr] [app.util.debug :as dbg] @@ -155,15 +155,24 @@ (defn set-shape-children [shape-ids] - (h/call internal-module "_clear_shape_children") - (run! (fn [id] - (let [buffer (uuid/get-u32 id)] - (h/call internal-module "_add_shape_child" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3)))) - shape-ids)) + (let [ENTRY-SIZE 16 + ptr + (h/call internal-module "_alloc_bytes" (* ENTRY-SIZE (count shape-ids))) + + heap + (js/Uint8Array. + (.-buffer (gobj/get ^js internal-module "HEAPU8")) + ptr + (* ENTRY-SIZE (count shape-ids)))] + + (loop [entries (seq shape-ids) + offset 0] + (when-not (empty? entries) + (let [id (first entries)] + (.set heap (sr/uuid->u8 id) offset) + (recur (rest entries) (+ offset ENTRY-SIZE))))) + + (h/call internal-module "_set_children"))) (defn- get-string-length [string] (+ (count string) 1)) @@ -240,7 +249,6 @@ color (:fill-color fill) gradient (:fill-color-gradient fill) image (:fill-image fill)] - (cond (some? color) (let [rgba (rgba-from-hex color opacity)] @@ -712,34 +720,7 @@ (aget font-id 3) font-weight font-style font-size))) -(defn set-shape-text-content [content] - (h/call internal-module "_clear_shape_text") - (let [paragraph-set (first (dm/get-prop content :children)) - paragraphs (dm/get-prop paragraph-set :children) - total-paragraphs (count paragraphs)] - - (loop [index 0] - (when (< index total-paragraphs) - (let [paragraph (nth paragraphs index) - leaves (dm/get-prop paragraph :children) - total-leaves (count leaves)] - (h/call internal-module "_add_text_paragraph") - (loop [index-leaves 0] - (when (< index-leaves total-leaves) - (let [leaf (nth leaves index-leaves)] - (add-text-leaf leaf) - (recur (inc index-leaves)))))) - (recur (inc index)))))) - -(defn set-view-box - [zoom vbox] - (h/call internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) - (render nil)) - -(defn clear-drawing-cache [] - (h/call internal-module "_clear_drawing_cache")) - -(defn- store-all-fonts +(defn- store-fonts [fonts] (keep (fn [font] (let [font-id (dm/get-prop font :font-id) @@ -755,97 +736,126 @@ :weight weight}] (store-font-id font-data ttf-id))) fonts)) -(defn set-fonts - [objects] - (let [fonts (fonts/shapes->fonts (into [] (vals objects))) - pending (into [] (store-all-fonts fonts))] - (->> (rx/from pending) - (rx/mapcat identity) - (rx/reduce conj []) - (rx/subs! (fn [_] - (clear-drawing-cache) - (request-render "set-fonts")))))) +(defn set-shape-text-content [content] + (h/call internal-module "_clear_shape_text") + (let [paragraph-set (first (dm/get-prop content :children)) + paragraphs (dm/get-prop paragraph-set :children) + total-paragraphs (count paragraphs) + fonts (fonts/get-content-fonts content)] + + (loop [index 0] + (when (< index total-paragraphs) + (let [paragraph (nth paragraphs index) + leaves (dm/get-prop paragraph :children) + total-leaves (count leaves)] + (h/call internal-module "_add_text_paragraph") + (loop [index-leaves 0] + (when (< index-leaves total-leaves) + (let [leaf (nth leaves index-leaves)] + (add-text-leaf leaf) + (recur (inc index-leaves)))))) + (recur (inc index)))) + (store-fonts fonts))) + +(defn set-view-box + [zoom vbox] + (h/call internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) + (render nil)) + +(defn clear-drawing-cache [] + (h/call internal-module "_clear_drawing_cache")) + +(defn update-shape-tiles [] + (h/call internal-module "_update_shape_tiles")) + +(defn set-object + [objects shape] + (let [id (dm/get-prop shape :id) + parent-id (dm/get-prop shape :parent-id) + type (dm/get-prop shape :type) + masked (dm/get-prop shape :masked-group) + selrect (dm/get-prop shape :selrect) + constraint-h (dm/get-prop shape :constraints-h) + constraint-v (dm/get-prop shape :constraints-v) + clip-content (if (= type :frame) + (not (dm/get-prop shape :show-content)) + false) + rotation (dm/get-prop shape :rotation) + transform (dm/get-prop shape :transform) + fills (if (= type :group) + [] (dm/get-prop shape :fills)) + strokes (if (= type :group) + [] (dm/get-prop shape :strokes)) + children (dm/get-prop shape :shapes) + blend-mode (dm/get-prop shape :blend-mode) + opacity (dm/get-prop shape :opacity) + hidden (dm/get-prop shape :hidden) + content (dm/get-prop shape :content) + blur (dm/get-prop shape :blur) + corners (when (some? (dm/get-prop shape :r1)) + [(dm/get-prop shape :r1) + (dm/get-prop shape :r2) + (dm/get-prop shape :r3) + (dm/get-prop shape :r4)]) + svg-attrs (dm/get-prop shape :svg-attrs) + shadows (dm/get-prop shape :shadow)] + + (use-shape id) + (set-parent-id parent-id) + (set-shape-type type) + (set-shape-clip-content clip-content) + (set-shape-selrect selrect) + (set-constraints-h constraint-h) + (set-constraints-v constraint-v) + (set-shape-rotation rotation) + (set-shape-transform transform) + (set-shape-blend-mode blend-mode) + (set-shape-opacity opacity) + (set-shape-hidden hidden) + (set-shape-children children) + (when (and (= type :group) masked) + (set-masked masked)) + (when (some? blur) + (set-shape-blur blur)) + (when (and (some? content) + (or (= type :path) + (= type :bool))) + (set-shape-path-attrs svg-attrs) + (set-shape-path-content content)) + (when (and (some? content) (= type :svg-raw)) + (set-shape-svg-raw-content (get-static-markup shape))) + (when (some? corners) (set-shape-corners corners)) + (when (some? shadows) (set-shape-shadows shadows)) + (when (and (= type :text) (some? content)) + (set-shape-text-content content)) + + (when (or (ctl/any-layout? shape) + (ctl/any-layout-immediate-child? objects shape)) + (set-layout-child shape)) + + (when (ctl/flex-layout? shape) + (set-flex-layout shape)) + + (when (ctl/grid-layout? shape) + (set-grid-layout shape)) + + (into [] (concat + (if (and (= type :text) (some? content)) + (set-shape-text-content content) + []) + (set-shape-fills fills) + (set-shape-strokes strokes))))) (defn set-objects [objects] - (set-fonts objects) (let [shapes (into [] (vals objects)) total-shapes (count shapes) pending (loop [index 0 pending []] (if (< index total-shapes) - (let [shape (nth shapes index) - id (dm/get-prop shape :id) - parent-id (dm/get-prop shape :parent-id) - type (dm/get-prop shape :type) - masked (dm/get-prop shape :masked-group) - selrect (dm/get-prop shape :selrect) - constraint-h (dm/get-prop shape :constraints-h) - constraint-v (dm/get-prop shape :constraints-v) - clip-content (if (= type :frame) - (not (dm/get-prop shape :show-content)) - false) - rotation (dm/get-prop shape :rotation) - transform (dm/get-prop shape :transform) - fills (if (= type :group) - [] (dm/get-prop shape :fills)) - strokes (if (= type :group) - [] (dm/get-prop shape :strokes)) - children (dm/get-prop shape :shapes) - blend-mode (dm/get-prop shape :blend-mode) - opacity (dm/get-prop shape :opacity) - hidden (dm/get-prop shape :hidden) - content (dm/get-prop shape :content) - blur (dm/get-prop shape :blur) - corners (when (some? (dm/get-prop shape :r1)) - [(dm/get-prop shape :r1) - (dm/get-prop shape :r2) - (dm/get-prop shape :r3) - (dm/get-prop shape :r4)]) - svg-attrs (dm/get-prop shape :svg-attrs) - shadows (dm/get-prop shape :shadow)] - - (use-shape id) - (set-parent-id parent-id) - (set-shape-type type) - (set-shape-clip-content clip-content) - (set-shape-selrect selrect) - (set-constraints-h constraint-h) - (set-constraints-v constraint-v) - (set-shape-rotation rotation) - (set-shape-transform transform) - (set-shape-blend-mode blend-mode) - (set-shape-opacity opacity) - (set-shape-hidden hidden) - (set-shape-children children) - (when (and (= type :group) masked) - (set-masked masked)) - (when (some? blur) - (set-shape-blur blur)) - (when (and (some? content) - (or (= type :path) - (= type :bool))) - (set-shape-path-attrs svg-attrs) - (set-shape-path-content content)) - (when (and (some? content) (= type :svg-raw)) - (set-shape-svg-raw-content (get-static-markup shape))) - (when (some? corners) (set-shape-corners corners)) - (when (some? shadows) (set-shape-shadows shadows)) - (when (and (= type :text) (some? content)) - (set-shape-text-content content)) - - (when (or (ctl/any-layout? shape) - (ctl/any-layout-immediate-child? objects shape)) - (set-layout-child shape)) - - (when (ctl/flex-layout? shape) - (set-flex-layout shape)) - - (when (ctl/grid-layout? shape) - (set-grid-layout shape)) - - (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] - (recur (inc index) (into pending pending')))) + (let [shape (nth shapes index) + pending' (set-object objects shape)] + (recur (inc index) (into pending pending'))) pending))] (clear-drawing-cache) (request-render "set-objects") @@ -917,16 +927,16 @@ (if (empty? modifiers) (h/call internal-module "_clean_modifiers") - (let [ENTRY_SIZE 40 + (let [ENTRY-SIZE 40 ptr - (h/call internal-module "_alloc_bytes" (* ENTRY_SIZE (count modifiers))) + (h/call internal-module "_alloc_bytes" (* ENTRY-SIZE (count modifiers))) heap (js/Uint8Array. (.-buffer (gobj/get ^js internal-module "HEAPU8")) ptr - (* ENTRY_SIZE (count modifiers)))] + (* ENTRY-SIZE (count modifiers)))] (loop [entries (seq modifiers) offset 0] @@ -934,7 +944,7 @@ (let [{:keys [id transform]} (first entries)] (.set heap (sr/uuid->u8 id) offset) (.set heap (sr/matrix->u8 transform) (+ offset 16)) - (recur (rest entries) (+ offset ENTRY_SIZE))))) + (recur (rest entries) (+ offset ENTRY-SIZE))))) (h/call internal-module "_set_modifiers") diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index d8812487a..26e512537 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -120,13 +120,14 @@ (api/set-shape-clip-content false)) :rotation (api/set-shape-rotation v) :transform (api/set-shape-transform v) - :fills (api/set-shape-fills v) - :strokes (api/set-shape-strokes v) + :fills (into [] (api/set-shape-fills v)) + :strokes (into [] (api/set-shape-strokes v)) :blend-mode (api/set-shape-blend-mode v) :opacity (api/set-shape-opacity v) :hidden (api/set-shape-hidden v) :shapes (api/set-shape-children v) :blur (api/set-shape-blur v) + :shadow (api/set-shape-shadows v) :constraints-h (api/set-constraints-h v) :constraints-v (api/set-constraints-v v) @@ -139,12 +140,17 @@ (api/set-shape-path-content v) (= (:type self) :svg-raw) - (api/set-shape-svg-raw-content (api/get-static-markup self))) + (api/set-shape-svg-raw-content (api/get-static-markup self)) + + (= (:type self) :text) + (into [] (api/set-shape-text-content v))) nil) ;; when something synced with wasm ;; is modified, we need to request ;; a new render. - (api/clear-drawing-cache) + ;; TODO: set-wasm-attrs is called twice with every set + ;; (println "set-wasm-attrs" (:id self) k v) + (api/update-shape-tiles) (api/request-render "set-wasm-attrs"))) (defn- impl-assoc diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 600b1879b..ef5bbdff4 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -8,13 +8,15 @@ mod render; mod shapes; mod state; mod utils; +mod uuid; mod view; mod wasm; use crate::mem::SerializableResult; use crate::shapes::{BoolType, ConstraintH, ConstraintV, TransformEntry, Type}; - use crate::utils::uuid_from_u32_quartet; +use crate::uuid::Uuid; +use indexmap::IndexSet; use state::State; pub(crate) static mut STATE: Option> = None; @@ -201,6 +203,28 @@ pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) { }); } +#[no_mangle] +pub extern "C" fn set_children() { + let bytes = mem::bytes(); + let entries: IndexSet = bytes + .chunks(size_of::<::BytesType>()) + .map(|data| Uuid::from_bytes(data.try_into().unwrap())) + .collect(); + + let mut deleted = IndexSet::new(); + + with_current_shape!(state, |shape: &mut Shape| { + (_, deleted) = shape.compute_children_differences(&entries); + shape.children = entries.clone(); + }); + + with_state!(state, { + for id in deleted { + state.delete_shape(id); + } + }); +} + #[no_mangle] pub extern "C" fn clear_shape_children() { with_current_shape!(state, |shape: &mut Shape| { @@ -621,6 +645,13 @@ pub extern "C" fn clear_shape_shadows() { }); } +#[no_mangle] +pub extern "C" fn update_shape_tiles() { + with_state!(state, { + state.update_tile_for_current_shape(); + }); +} + #[no_mangle] pub extern "C" fn set_flex_layout_data( dir: u8, diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 35d017d49..a7b0a1e82 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,7 +1,7 @@ use skia_safe::{self as skia, Matrix, RRect, Rect}; +use crate::uuid::Uuid; use std::collections::HashMap; -use uuid::Uuid; use crate::view::Viewbox; use crate::{run_script, run_script_int}; @@ -474,6 +474,7 @@ impl RenderState { self.surfaces.cache_visit(tile); } } + self.pending_nodes = vec![]; self.current_tile = None; self.render_in_progress = true; self.apply_drawing_to_render_canvas(None); @@ -751,21 +752,33 @@ impl RenderState { self.update_render_context(next_tile); if !self.surfaces.has_cached_tile_surface(next_tile) { if let Some(ids) = self.tiles.get_shapes_at(next_tile) { - for id in ids { - let element = tree.get_mut(&id).ok_or( - "Error: Element with root_id {id} not found in the tree." - .to_string(), - )?; - if element.parent_id == Some(Uuid::nil()) { - self.pending_nodes.push(NodeRenderState { - id: *id, - visited_children: false, - clip_bounds: None, - visited_mask: false, - mask: false, - }); - } + // We only need first level shapes + let mut valid_ids: Vec = ids + .iter() + .filter_map(|id| { + tree.get(id) + .filter(|element| element.parent_id == Some(Uuid::nil())) + .map(|_| *id) + }) + .collect(); + + // These shapes for the tile should be ordered as they are in the parent node + if let Some(root) = tree.get(&Uuid::nil()) { + let root_ids = &root.children_ids(); + valid_ids.sort_by_key(|id| { + root_ids.iter().rev().position(|root_id| root_id == id) + }); } + + self.pending_nodes.extend(valid_ids.into_iter().map(|id| { + NodeRenderState { + id, + visited_children: false, + clip_bounds: None, + visited_mask: false, + mask: false, + } + })); } } } else { @@ -783,9 +796,13 @@ impl RenderState { Ok(()) } - pub fn update_tile_for(&mut self, shape: &Shape) { + pub fn get_tiles_for_rect(&mut self, shape: &Shape) -> (i32, i32, i32, i32) { let tile_size = tiles::get_tile_size(self.viewbox); - let (rsx, rsy, rex, rey) = tiles::get_tiles_for_rect(shape.extrect(), tile_size); + tiles::get_tiles_for_rect(shape.extrect(), tile_size) + } + + pub fn update_tile_for(&mut self, shape: &Shape) { + let (rsx, rsy, rex, rey) = self.get_tiles_for_rect(shape); // Update tiles where the shape was if let Some(tiles) = self.tiles.get_tiles_of(shape.id) { diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index a1d9f5fe7..651f85ae4 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -1,6 +1,6 @@ +use crate::uuid::Uuid; use skia_safe as skia; use std::collections::HashMap; -use uuid::Uuid; pub type Image = skia::Image; diff --git a/render-wasm/src/render/tiles.rs b/render-wasm/src/render/tiles.rs index 9931c423a..0b8785195 100644 --- a/render-wasm/src/render/tiles.rs +++ b/render-wasm/src/render/tiles.rs @@ -1,9 +1,8 @@ -use skia_safe as skia; -use std::collections::{HashMap, HashSet}; -use uuid::Uuid; - +use crate::uuid::Uuid; use crate::view::Viewbox; use indexmap::IndexSet; +use skia_safe as skia; +use std::collections::{HashMap, HashSet}; pub type Tile = (i32, i32); @@ -64,6 +63,12 @@ impl TileHashMap { return self.grid.get(&tile); } + pub fn remove_shape_at(&mut self, tile: Tile, id: Uuid) { + if let Some(shapes) = self.grid.get_mut(&tile) { + shapes.shift_remove(&id); + } + } + pub fn get_tiles_of(&mut self, shape_id: Uuid) -> Option<&HashSet> { self.index.get(&shape_id) } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index dc90958e5..354152c3a 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1,9 +1,8 @@ use skia_safe::{self as skia}; -use std::collections::HashMap; -use uuid::Uuid; - use crate::render::BlendMode; +use crate::uuid::Uuid; +use std::collections::HashMap; mod blurs; mod bools; @@ -41,6 +40,7 @@ pub use transform::*; use crate::math; use crate::math::{Bounds, Matrix, Point}; +use indexmap::IndexSet; const MIN_VISIBLE_SIZE: f32 = 2.0; const ANTIALIAS_THRESHOLD: f32 = 15.0; @@ -160,7 +160,7 @@ pub struct Shape { pub id: Uuid, pub parent_id: Option, pub shape_type: Type, - pub children: Vec, + pub children: IndexSet, pub selrect: math::Rect, pub transform: Matrix, pub rotation: f32, @@ -185,7 +185,7 @@ impl Shape { id, parent_id: None, shape_type: Type::Rect(Rect::default()), - children: Vec::::new(), + children: IndexSet::::new(), selrect: math::Rect::new_empty(), transform: Matrix::default(), rotation: 0., @@ -432,7 +432,16 @@ impl Shape { } pub fn add_child(&mut self, id: Uuid) { - self.children.push(id); + self.children.insert(id); + } + + pub fn compute_children_differences( + &mut self, + children: &IndexSet, + ) -> (IndexSet, IndexSet) { + let added = children.difference(&self.children).cloned().collect(); + let removed = self.children.difference(children).cloned().collect(); + (added, removed) } pub fn clear_children(&mut self) { @@ -655,12 +664,12 @@ impl Shape { vec![] } else if let Type::Group(group) = self.shape_type { if group.masked { - self.children[1..self.children.len()].to_vec() + self.children.iter().skip(1).cloned().collect() } else { - self.children.clone() + self.children.clone().into_iter().collect() } } else { - self.children.clone() + self.children.clone().into_iter().collect() } } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 1fb21eace..42ffa889d 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -1,7 +1,7 @@ use skia_safe::{self as skia, Rect}; use super::Color; -use uuid::Uuid; +use crate::uuid::Uuid; #[derive(Debug)] #[repr(C)] diff --git a/render-wasm/src/shapes/fonts.rs b/render-wasm/src/shapes/fonts.rs index 58104af06..a0e624bfc 100644 --- a/render-wasm/src/shapes/fonts.rs +++ b/render-wasm/src/shapes/fonts.rs @@ -1,6 +1,6 @@ use std::fmt; -use uuid::Uuid; +use crate::uuid::Uuid; #[derive(Debug, PartialEq, Clone, Copy)] pub enum FontStyle { diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs index a558767ab..f1c1af2d7 100644 --- a/render-wasm/src/shapes/layouts.rs +++ b/render-wasm/src/shapes/layouts.rs @@ -1,5 +1,5 @@ use crate::utils::uuid_from_u32_quartet; -use uuid::Uuid; +use crate::uuid::Uuid; #[derive(Debug, Clone, PartialEq)] #[allow(dead_code)] diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index a5e727851..c9ef10ee7 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -4,8 +4,6 @@ mod constraints; mod flex_layout; mod grid_layout; -use uuid::Uuid; - use common::GetBounds; use crate::math::{identitish, Bounds, Matrix, Point}; @@ -13,6 +11,7 @@ use crate::shapes::{ ConstraintH, ConstraintV, Frame, Group, Layout, Modifier, Shape, TransformEntry, Type, }; use crate::state::State; +use crate::uuid::Uuid; fn propagate_children( shape: &Shape, diff --git a/render-wasm/src/shapes/modifiers/common.rs b/render-wasm/src/shapes/modifiers/common.rs index 9324adb90..8b071ee54 100644 --- a/render-wasm/src/shapes/modifiers/common.rs +++ b/render-wasm/src/shapes/modifiers/common.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use uuid::Uuid; use crate::math::Bounds; use crate::shapes::Shape; +use crate::uuid::Uuid; pub trait GetBounds { fn find(&self, shape: &Shape) -> Bounds; diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index d0b47b429..e4edff1ff 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -4,8 +4,9 @@ use crate::shapes::{ AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem, Modifier, Shape, }; +use crate::uuid::Uuid; + use std::collections::{HashMap, VecDeque}; -use uuid::Uuid; use super::common::GetBounds; diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 3659ed7f2..2339c4d1d 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -1,8 +1,8 @@ #![allow(dead_code, unused_variables)] use crate::math::{Bounds, Matrix, Point, Vector, VectorExt}; use crate::shapes::{GridData, LayoutData, Modifier, Shape}; +use crate::uuid::Uuid; use std::collections::{HashMap, VecDeque}; -use uuid::Uuid; use super::common::GetBounds; diff --git a/render-wasm/src/shapes/transform.rs b/render-wasm/src/shapes/transform.rs index bbfd8f77d..bd495b0cc 100644 --- a/render-wasm/src/shapes/transform.rs +++ b/render-wasm/src/shapes/transform.rs @@ -1,8 +1,8 @@ use skia_safe as skia; -use uuid::Uuid; use crate::mem::SerializableResult; use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet}; +use crate::uuid::Uuid; use skia::Matrix; #[derive(PartialEq, Debug, Clone)] @@ -101,7 +101,7 @@ impl SerializableResult for TransformEntry { #[cfg(test)] mod tests { use super::*; - use uuid::uuid; + use crate::uuid::Uuid; #[test] fn test_serialization() { diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 6fdf6869c..347880513 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use skia_safe as skia; -use uuid::Uuid; use crate::render::RenderState; use crate::shapes::Shape; +use crate::uuid::Uuid; /// This struct holds the state of the Rust application between JS calls. /// @@ -59,6 +59,20 @@ impl<'a> State<'a> { self.current_shape = self.shapes.get_mut(&id); } + pub fn delete_shape(&mut self, id: Uuid) { + // We don't really do a self.shapes.remove so that redo/undo keep working + if let Some(shape) = self.shapes.get(&id) { + let (rsx, rsy, rex, rey) = self.render_state.get_tiles_for_rect(&shape); + for x in rsx..=rex { + for y in rsy..=rey { + let tile = (x, y); + self.render_state.surfaces.remove_cached_tile_surface(tile); + self.render_state.tiles.remove_shape_at(tile, id); + } + } + } + } + pub fn current_shape(&mut self) -> Option<&mut Shape> { self.current_shape.as_deref_mut() } @@ -80,6 +94,19 @@ impl<'a> State<'a> { } } + pub fn update_tile_for_current_shape(&mut self) { + match self.current_shape.as_mut() { + Some(shape) => { + // We don't need to update the tile for the root shape. + // We can also have deleted the selected shape + if !shape.id.is_nil() && self.shapes.contains_key(&shape.id) { + self.render_state.update_tile_for(&shape); + } + } + None => panic!("Invalid current shape"), + } + } + pub fn rebuild_tiles(&mut self) { self.render_state .rebuild_tiles(&mut self.shapes, &self.modifiers); diff --git a/render-wasm/src/utils.rs b/render-wasm/src/utils.rs index ae5ff986c..96062002f 100644 --- a/render-wasm/src/utils.rs +++ b/render-wasm/src/utils.rs @@ -1,4 +1,4 @@ -use uuid::Uuid; +use crate::uuid::Uuid; pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid { let hi: u64 = ((a as u64) << 32) | b as u64; diff --git a/render-wasm/src/uuid.rs b/render-wasm/src/uuid.rs new file mode 100644 index 000000000..2f78df686 --- /dev/null +++ b/render-wasm/src/uuid.rs @@ -0,0 +1,69 @@ +use crate::mem::SerializableResult; +use crate::utils::uuid_from_u32_quartet; +use crate::utils::uuid_to_u32_quartet; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use uuid::Uuid as ExternalUuid; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Uuid(ExternalUuid); + +impl Deref for Uuid { + type Target = ExternalUuid; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Uuid { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Uuid { + pub fn nil() -> Self { + Self(ExternalUuid::nil()) + } + + pub fn from_u64_pair(high: u64, low: u64) -> Self { + Self(ExternalUuid::from_u64_pair(high, low)) + } +} + +impl fmt::Display for Uuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl SerializableResult for Uuid { + type BytesType = [u8; 16]; + + fn from_bytes(bytes: Self::BytesType) -> Self { + Self(*uuid_from_u32_quartet( + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), + u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]), + u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]), + )) + } + + fn as_bytes(&self) -> Self::BytesType { + let mut result: Self::BytesType = [0; 16]; + let (a, b, c, d) = uuid_to_u32_quartet(&self); + result[0..4].clone_from_slice(&a.to_le_bytes()); + result[4..8].clone_from_slice(&b.to_le_bytes()); + result[8..12].clone_from_slice(&c.to_le_bytes()); + result[12..16].clone_from_slice(&d.to_le_bytes()); + + result + } + + // The generic trait doesn't know the size of the array. This is why the + // clone needs to be here even if it could be generic. + fn clone_to_slice(&self, slice: &mut [u8]) { + slice.clone_from_slice(&self.as_bytes()); + } +}