From fef08dfa188dab650bc740576c0b3e69191f8caf Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 May 2025 17:43:04 +0200 Subject: [PATCH] :sparkles: Set selrect for new render modifiers --- common/src/app/common/geom/shapes.cljc | 1 + .../app/main/data/workspace/modifiers.cljs | 24 ++++----- .../src/app/main/data/workspace/texts.cljs | 8 ++- .../app/main/data/workspace/transforms.cljs | 3 +- frontend/src/app/main/refs.cljs | 3 -- .../shapes/text/text_edition_outline.cljs | 4 +- .../ui/workspace/shapes/text/v2_editor.cljs | 53 +++++++++++++++---- .../main/ui/workspace/viewport/selection.cljs | 35 ++++++------ frontend/src/app/render_wasm/api.cljs | 27 +++++++++- render-wasm/src/main.rs | 38 +++++++++++-- render-wasm/src/math.rs | 50 ++++++++++++++--- render-wasm/src/shapes/modifiers.rs | 16 ++++-- render-wasm/src/wasm/text.rs | 10 ++-- 13 files changed, 200 insertions(+), 72 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 1710302c3..c63973fc6 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -195,6 +195,7 @@ ;; Rect (dm/export grc/rect->points) +(dm/export grc/center->rect) ;; (dm/export gsff/fit-frame-modifiers) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 0e869980e..87274e79c 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -504,6 +504,13 @@ (filter (fn [[_ {:keys [type]}]] (= type :change-property))))) +(defn set-temporary-selrect + [selrect] + (ptk/reify ::set-temporary-selrect + ptk/UpdateEvent + (update [_ state] + (assoc state :workspace-selrect selrect)))) + #_:clj-kondo/ignore (defn set-wasm-modifiers [modif-tree & {:keys [ignore-constraints ignore-snap-pixel] @@ -519,8 +526,8 @@ (assoc :prev-wasm-props (:wasm-props state)) (assoc :wasm-props property-changes)))) - ptk/EffectEvent - (effect [_ state _] + ptk/WatchEvent + (watch [_ state _] (wasm.api/clean-modifiers) (let [prev-wasm-props (:prev-wasm-props state) @@ -531,8 +538,9 @@ (let [structure-entries (parse-structure-modifiers modif-tree)] (wasm.api/set-structure-modifiers structure-entries) - (let [geometry-entries (parse-geometry-modifiers modif-tree)] - (wasm.api/propagate-apply geometry-entries))))))) + (let [geometry-entries (parse-geometry-modifiers modif-tree) + selrect (wasm.api/propagate-apply geometry-entries)] + (rx/of (set-temporary-selrect selrect)))))))) #_:clj-kondo/ignore (defn apply-wasm-modifiers @@ -567,14 +575,6 @@ (clear-local-transform) (dwsh/update-shapes ids update-shape)))))) - -(defn set-selrect-transform - [modifiers] - (ptk/reify ::set-selrect-transform - ptk/UpdateEvent - (update [_ state] - (assoc state :workspace-selrect-transform (ctm/modifiers->transform modifiers))))) - (def ^:private xf-rotation-shape (comp diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index d90a4472a..6e993228d 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -25,6 +25,7 @@ [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] [app.main.features :as features] [app.main.fonts :as fonts] @@ -945,14 +946,17 @@ new-shape)) {:undo-group (when new-shape? id)}) - (if finalize? + (if (and (not= :fixed (:grow-type shape)) finalize?) (dwm/apply-wasm-modifiers (resize-wasm-text-modifiers shape content) {:undo-group (when new-shape? id)}) (dwm/set-wasm-modifiers (resize-wasm-text-modifiers shape content) - {:undo-group (when new-shape? id)})))) + {:undo-group (when new-shape? id)})) + + (when finalize? + (dwt/finish-transform)))) (let [objects (dsh/lookup-page-objects state) shape (get objects id) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 7160f14e0..eb6804688 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -136,7 +136,7 @@ (update [_ state] (-> state (update :workspace-local dissoc :transform :duplicate-move-started?) - (dissoc :workspace-selrect-transform))))) + (dissoc :workspace-selrect))))) ;; -- Resize -------------------------------------------------------- @@ -296,7 +296,6 @@ (fn [modifiers] (let [modif-tree (dwm/create-modif-tree ids modifiers)] (rx/of - (dwm/set-selrect-transform modifiers) (dwm/set-wasm-modifiers modif-tree :ignore-constraints (contains? layout :scale-text)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 9a4859458..5a3dab07d 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -147,9 +147,6 @@ (def workspace-drawing (l/derived :workspace-drawing st/state)) -(def workspace-selrect-transform - (l/derived :workspace-selrect-transform st/state)) - (def workspace-tokens "All tokens related ephimeral state" (l/derived :workspace-tokens st/state)) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs index b76044b44..6974114ac 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs @@ -18,8 +18,8 @@ [{:keys [shape zoom modifiers]}] (if (features/active-feature? @st/state "render-wasm/v1") (let [transform (gsh/transform-str shape) - {:keys [id x y]} shape - {:keys [width height]} (wasm.api/text-dimensions id)] + {:keys [id x y grow-type]} shape + {:keys [width height]} (if (= :fixed grow-type) shape (wasm.api/text-dimensions id))] [:rect.main.viewport-selrect {:x x :y y diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index 20ce73b3a..50c0327a0 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -16,9 +16,13 @@ [app.config :as cf] [app.main.data.workspace :as dw] [app.main.data.workspace.texts :as dwt] + [app.main.features :as features] + [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.css-cursors :as cur] + [app.main.ui.hooks :as h] + [app.render-wasm.api :as wasm.api] [app.util.dom :as dom] [app.util.globals :as global] [app.util.keyboard :as kbd] @@ -34,6 +38,20 @@ result (.-textContent editor-root)] (when (not= result "") result)))) +(defn- get-fonts + [content] + (let [extract-fn (juxt :font-id :font-variant-id) + default (extract-fn txt/default-text-attrs)] + (->> (tree-seq map? :children content) + (into #{default} (keep extract-fn))))) + +(defn- load-fonts! + [fonts] + (->> fonts + (run! (fn [[font-id variant-id]] + (when (some? font-id) + (fonts/ensure-loaded! font-id variant-id)))))) + (defn- initialize-event-handlers "Internal editor events handler initializer/destructor" [shape-id content selection-ref editor-ref container-ref] @@ -137,7 +155,14 @@ ;; This reference is to the container container-ref (mf/use-ref nil) - selection-ref (mf/use-ref nil)] + selection-ref (mf/use-ref nil) + + fonts + (-> (mf/use-memo (mf/deps content) #(get-fonts content)) + (h/use-equal-memo))] + + (mf/with-effect [fonts] + (load-fonts! fonts)) ;; WARN: we explicitly do not pass content on effect dependency ;; array because we only need to initialize this once with initial @@ -235,16 +260,24 @@ (some? modifiers) (gsh/transform-shape modifiers)) - bounds (gst/shape->rect shape) + [x y width height] + (if (features/active-feature? @st/state "render-wasm/v1") + (let [{:keys [width height]} (wasm.api/text-dimensions shape-id) + {:keys [x y]} (:selrect shape)] + + [x y width height]) + + (let [bounds (gst/shape->rect shape) + x (mth/min (dm/get-prop bounds :x) + (dm/get-prop shape :x)) + y (mth/min (dm/get-prop bounds :y) + (dm/get-prop shape :y)) + width (mth/max (dm/get-prop bounds :width) + (dm/get-prop shape :width)) + height (mth/max (dm/get-prop bounds :height) + (dm/get-prop shape :height))] + [x y width height])) - x (mth/min (dm/get-prop bounds :x) - (dm/get-prop shape :x)) - y (mth/min (dm/get-prop bounds :y) - (dm/get-prop shape :y)) - width (mth/max (dm/get-prop bounds :width) - (dm/get-prop shape :width)) - height (mth/max (dm/get-prop bounds :height) - (dm/get-prop shape :height)) style (cond-> #js {:pointerEvents "all"} diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 7f6f47d32..d5e6f4428 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -24,6 +24,7 @@ [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.object :as obj] + [okulary.core :as l] [rumext.v2 :as mf])) (def rotation-handler-size 20) @@ -313,17 +314,23 @@ :style {:fill (if (dbg/enabled? :handlers) "yellow" "none") :stroke-width 0}}]])) +(def workspace-selrect-transform + (l/derived :workspace-selrect st/state)) + +(defn get-selrect + [selrect-transform shape] + (if (some? selrect-transform) + (let [{:keys [center width height transform]} selrect-transform] + [(gsh/center->rect center width height) + (gmt/transform-in center transform)]) + [(dm/get-prop shape :selrect) + (gsh/transform-matrix shape)])) + (mf/defc controls-selection* [{:keys [shape zoom color on-move-selected on-context-menu disabled]}] - (let [selrect (dm/get-prop shape :selrect) - transform-type (mf/deref refs/current-transform) - sr-transform (mf/deref refs/workspace-selrect-transform) - - transform - (dm/str - (cond->> (gsh/transform-matrix shape) - (some? sr-transform) - (gmt/multiply sr-transform)))] + (let [selrect-transform (mf/deref workspace-selrect-transform) + transform-type (mf/deref refs/current-transform) + [selrect transform] (get-selrect selrect-transform shape)] (when (and (some? selrect) (not (or (= transform-type :move) @@ -340,19 +347,15 @@ (mf/defc controls-handlers* {::mf/private true} [{:keys [shape zoom color on-resize on-rotate disabled]}] - (let [transform-type (mf/deref refs/current-transform) - sr-transform (mf/deref refs/workspace-selrect-transform) + (let [selrect-transform (mf/deref workspace-selrect-transform) + transform-type (mf/deref refs/current-transform) read-only? (mf/use-ctx ctx/workspace-read-only?) layout (mf/deref refs/workspace-layout) scale-text? (contains? layout :scale-text) - selrect (dm/get-prop shape :selrect) - - transform (cond->> (gsh/transform-matrix shape) - (some? sr-transform) - (gmt/multiply sr-transform)) + [selrect transform] (get-selrect selrect-transform shape) rotation (-> (gpt/point 1 0) (gpt/transform (:transform shape)) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index ef28d0a0f..f5fffbd3e 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -10,6 +10,8 @@ ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] [app.common.types.path :as path] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] @@ -808,8 +810,29 @@ (sr/heapu32-set-uuid id heapu32 current-offset) (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET))) (recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE)))))) - (h/call wasm/internal-module "_propagate_apply") - (request-render "set-modifiers")))) + + (let [offset (h/call wasm/internal-module "_propagate_apply") + heapf32 (mem/get-heap-f32) + width (aget heapf32 (mem/ptr8->ptr32 (+ offset 0))) + height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4))) + cx (aget heapf32 (mem/ptr8->ptr32 (+ offset 8))) + cy (aget heapf32 (mem/ptr8->ptr32 (+ offset 12))) + + a (aget heapf32 (mem/ptr8->ptr32 (+ offset 16))) + b (aget heapf32 (mem/ptr8->ptr32 (+ offset 20))) + c (aget heapf32 (mem/ptr8->ptr32 (+ offset 24))) + d (aget heapf32 (mem/ptr8->ptr32 (+ offset 28))) + e (aget heapf32 (mem/ptr8->ptr32 (+ offset 32))) + f (aget heapf32 (mem/ptr8->ptr32 (+ offset 36))) + transform (gmt/matrix a b c d e f)] + + (h/call wasm/internal-module "_free_bytes") + (request-render "set-modifiers") + + {:width width + :height height + :center (gpt/point cx cy) + :transform transform})))) (defn set-canvas-background [background] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 1c22f4eb9..9e6129f72 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -20,6 +20,7 @@ use crate::shapes::{BoolType, ConstraintH, ConstraintV, StructureEntry, Transfor use crate::utils::uuid_from_u32_quartet; use crate::uuid::Uuid; use indexmap::IndexSet; +use math::{Bounds, Matrix}; use state::State; pub(crate) static mut STATE: Option> = None; @@ -397,13 +398,13 @@ pub extern "C" fn propagate_modifiers() -> *mut u8 { .collect(); with_state!(state, { - let result = shapes::propagate_modifiers(state, entries); + let (result, _) = shapes::propagate_modifiers(state, &entries); mem::write_vec(result) }) } #[no_mangle] -pub extern "C" fn propagate_apply() { +pub extern "C" fn propagate_apply() -> *mut u8 { let bytes = mem::bytes(); let entries: Vec<_> = bytes @@ -412,15 +413,42 @@ pub extern "C" fn propagate_apply() { .collect(); with_state!(state, { - let result = shapes::propagate_modifiers(state, entries); + let (result, bounds) = shapes::propagate_modifiers(state, &entries); for entry in result { state.modifiers.insert(entry.id, entry.transform); } state.rebuild_modifier_tiles(); - }); - mem::free_bytes(); + mem::free_bytes(); + + let bbs: Vec<_> = entries.iter().flat_map(|e| bounds.get(&e.id)).collect(); + + let result_bound = if bbs.len() == 1 { + bbs[0] + } else { + &Bounds::join_bounds(&bbs) + }; + + let width = result_bound.width(); + let height = result_bound.height(); + let center = result_bound.center(); + let transform = result_bound.transform_matrix().unwrap_or(Matrix::default()); + + let mut bytes = Vec::::with_capacity(40); + bytes.resize(40, 0); + bytes[0..4].clone_from_slice(&width.to_le_bytes()); + bytes[4..8].clone_from_slice(&height.to_le_bytes()); + bytes[8..12].clone_from_slice(¢er.x.to_le_bytes()); + bytes[12..16].clone_from_slice(¢er.y.to_le_bytes()); + bytes[16..20].clone_from_slice(&transform[0].to_le_bytes()); + bytes[20..24].clone_from_slice(&transform[3].to_le_bytes()); + bytes[24..28].clone_from_slice(&transform[1].to_le_bytes()); + bytes[28..32].clone_from_slice(&transform[4].to_le_bytes()); + bytes[32..36].clone_from_slice(&transform[2].to_le_bytes()); + bytes[36..40].clone_from_slice(&transform[5].to_le_bytes()); + return mem::write_bytes(bytes); + }); } #[no_mangle] diff --git a/render-wasm/src/math.rs b/render-wasm/src/math.rs index f62e22250..4f0213a62 100644 --- a/render-wasm/src/math.rs +++ b/render-wasm/src/math.rs @@ -64,6 +64,29 @@ impl Bounds { Self { nw, ne, se, sw } } + pub fn join_bounds(bounds: &Vec<&Bounds>) -> Self { + let (min_x, min_y, max_x, max_y) = + bounds + .iter() + .fold((f32::MAX, f32::MAX, f32::MIN, f32::MIN), { + |(min_x, min_y, max_x, max_y), bound| { + ( + f32::min(bound.min_x(), min_x), + f32::min(bound.min_y(), min_y), + f32::max(bound.max_x(), max_x), + f32::max(bound.max_y(), max_y), + ) + } + }); + + Self::new( + Point::new(min_x, min_y), + Point::new(max_x, min_y), + Point::new(max_x, max_y), + Point::new(min_x, max_y), + ) + } + pub fn horizontal_vec(&self) -> Vector { Vector::new_points(&self.nw, &self.ne) } @@ -249,7 +272,10 @@ impl Bounds { pub fn center(&self) -> Point { // Calculates the centroid of the four points - Point::new(self.nw.x + self.se.x / 2.0, self.nw.y + self.se.y / 2.0) + Point::new( + self.nw.x + (self.se.x - self.nw.x) / 2.0, + self.nw.y + (self.se.y - self.nw.y) / 2.0, + ) } pub fn transform_matrix(&self) -> Option { @@ -297,11 +323,23 @@ impl Bounds { } pub fn to_rect(&self) -> Rect { - let minx = self.nw.x.min(self.ne.x).min(self.sw.x).min(self.se.x); - let miny = self.nw.y.min(self.ne.y).min(self.sw.y).min(self.se.y); - let maxx = self.nw.x.max(self.ne.x).max(self.sw.x).max(self.se.x); - let maxy = self.nw.y.max(self.ne.y).max(self.sw.y).max(self.se.y); - Rect::from_ltrb(minx, miny, maxx, maxy) + Rect::from_ltrb(self.min_x(), self.min_y(), self.max_x(), self.max_y()) + } + + pub fn min_x(&self) -> f32 { + self.nw.x.min(self.ne.x).min(self.sw.x).min(self.se.x) + } + + pub fn min_y(&self) -> f32 { + self.nw.y.min(self.ne.y).min(self.sw.y).min(self.se.y) + } + + pub fn max_x(&self) -> f32 { + self.nw.x.max(self.ne.x).max(self.sw.x).max(self.se.x) + } + + pub fn max_y(&self) -> f32 { + self.nw.y.max(self.ne.y).max(self.sw.y).max(self.se.y) } } diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 51128065d..d7cf4af1d 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -103,7 +103,10 @@ fn calculate_group_bounds( shape_bounds.from_points(result) } -pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec { +pub fn propagate_modifiers( + state: &State, + modifiers: &Vec, +) -> (Vec, HashMap) { let shapes = &state.shapes; let font_col = state.render_state.fonts.font_collection(); @@ -306,10 +309,13 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec layout_reflows = Vec::new(); } - modifiers - .iter() - .map(|(key, val)| TransformEntry::new(*key, *val)) - .collect() + ( + modifiers + .iter() + .map(|(key, val)| TransformEntry::new(*key, *val)) + .collect(), + bounds, + ) } #[cfg(test)] diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index 21c3befcd..7a4e6ea56 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -47,15 +47,11 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { height = shape.selrect.height(); if let Type::Text(content) = &shape.shape_type { + let paragraphs = content.get_skia_paragraphs(font_col); + height = auto_height(¶graphs).ceil(); match content.grow_type() { GrowType::AutoWidth => { - let paragraphs = content.get_skia_paragraphs(font_col); - width = auto_width(¶graphs); - height = auto_height(¶graphs); - } - GrowType::AutoHeight => { - let paragraphs = content.get_skia_paragraphs(font_col); - height = auto_height(¶graphs); + width = auto_width(¶graphs).ceil(); } _ => {} }