From 0a700864c9b5dbccc92652e31990b3e081d5836b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 6 Nov 2025 16:53:04 +0100 Subject: [PATCH 1/5] :bug: Fix problem with grid layout modifiers --- common/src/app/common/types/modifiers.cljc | 164 +++++++++--------- .../app/main/data/workspace/modifiers.cljs | 6 +- 2 files changed, 86 insertions(+), 84 deletions(-) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 6e1d8c7ef9..ccf59454cd 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -732,89 +732,89 @@ [shape scale-text-content value] (update shape :content scale-text-content value)) +(defn scale-text-content + [content value] + (->> content + (txt/transform-nodes txt/is-text-node? (partial transform-text-node value)) + (txt/transform-nodes txt/is-paragraph-node? (partial transform-paragraph-node value)))) + +(defn apply-scale-content + [shape value] + ;; Scale can only be positive + (let [value (mth/abs value)] + (cond-> shape + (cfh/text-shape? shape) + (update-text-content scale-text-content value) + + :always + (gsc/update-corners-scale value) + + (d/not-empty? (:strokes shape)) + (gss/update-strokes-width value) + + (d/not-empty? (:shadow shape)) + (gse/update-shadows-scale value) + + (some? (:blur shape)) + (gse/update-blur-scale value) + + (ctl/flex-layout? shape) + (ctl/update-flex-scale value) + + (ctl/grid-layout? shape) + (ctl/update-grid-scale value) + + :always + (ctl/update-flex-child value)))) + +(defn apply-modifier + [shape operation] + (let [type (dm/get-prop operation :type)] + (case type + :rotation + (let [rotation (dm/get-prop operation :value)] + (update shape :rotation #(mod (+ (or % 0) rotation) 360))) + + :add-children + (let [value (dm/get-prop operation :value) + index (dm/get-prop operation :index) + + shape + (if (some? index) + (update shape :shapes + (fn [shapes] + (if (vector? shapes) + (d/insert-at-index shapes index value) + (d/concat-vec shapes value)))) + (update shape :shapes d/concat-vec value))] + + ;; Remove duplication + (update shape :shapes #(into [] (apply d/ordered-set %)))) + + :remove-children + (let [value (dm/get-prop operation :value)] + (update shape :shapes remove-children value)) + + :scale-content + (let [value (dm/get-prop operation :value)] + (apply-scale-content shape value)) + + :change-property + (let [property (dm/get-prop operation :property) + value (dm/get-prop operation :value)] + (assoc shape property value)) + + ;; :default => no change to shape + shape))) + (defn apply-structure-modifiers "Apply structure changes to a shape" [shape modifiers] - (letfn [(scale-text-content - [content value] - (->> content - (txt/transform-nodes txt/is-text-node? (partial transform-text-node value)) - (txt/transform-nodes txt/is-paragraph-node? (partial transform-paragraph-node value)))) + (let [remove-children + (fn [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes)))] - (apply-scale-content - [shape value] - ;; Scale can only be positive - (let [value (mth/abs value)] - (cond-> shape - (cfh/text-shape? shape) - (update-text-content scale-text-content value) - - :always - (gsc/update-corners-scale value) - - (d/not-empty? (:strokes shape)) - (gss/update-strokes-width value) - - (d/not-empty? (:shadow shape)) - (gse/update-shadows-scale value) - - (some? (:blur shape)) - (gse/update-blur-scale value) - - (ctl/flex-layout? shape) - (ctl/update-flex-scale value) - - (ctl/grid-layout? shape) - (ctl/update-grid-scale value) - - :always - (ctl/update-flex-child value))))] - - (let [remove-children - (fn [shapes children-to-remove] - (let [remove? (set children-to-remove)] - (d/removev remove? shapes))) - - apply-modifier - (fn [shape operation] - (let [type (dm/get-prop operation :type)] - (case type - :rotation - (let [rotation (dm/get-prop operation :value)] - (update shape :rotation #(mod (+ (or % 0) rotation) 360))) - - :add-children - (let [value (dm/get-prop operation :value) - index (dm/get-prop operation :index) - - shape - (if (some? index) - (update shape :shapes - (fn [shapes] - (if (vector? shapes) - (d/insert-at-index shapes index value) - (d/concat-vec shapes value)))) - (update shape :shapes d/concat-vec value))] - - ;; Remove duplication - (update shape :shapes #(into [] (apply d/ordered-set %)))) - - :remove-children - (let [value (dm/get-prop operation :value)] - (update shape :shapes remove-children value)) - - :scale-content - (let [value (dm/get-prop operation :value)] - (apply-scale-content shape value)) - - :change-property - (let [property (dm/get-prop operation :property) - value (dm/get-prop operation :value)] - (assoc shape property value)) - - ;; :default => no change to shape - shape)))] - - (as-> shape $ - (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) - (reduce apply-modifier $ (dm/get-prop modifiers :structure-child)))))) + (as-> shape $ + (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-child))))) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 8383dc1115..c12cc4b32e 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -19,6 +19,7 @@ [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.modifiers :as ctm] + [app.common.types.modifiers :as ctm] [app.common.types.path :as path] [app.common.types.shape-tree :as ctst] [app.common.types.shape.attrs :refer [editable-attrs]] @@ -212,13 +213,14 @@ ;; Create a new objects only with the temporary modifications objects-changed (->> wasm-props + (group-by first) (reduce (fn [objects [id properties]] (let [shape (->> properties (reduce - (fn [shape {:keys [property value]}] - (assoc shape property value)) + (fn [shape [_ operation]] + (ctm/apply-modifier shape operation)) (get objects id)))] (assoc objects id shape))) objects))] From 76f6f71e02979c8d48a4d5fa3e04cb54d527818b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 6 Nov 2025 17:33:33 +0100 Subject: [PATCH 2/5] :bug: Fix z-ordering for flex elements --- render-wasm/src/render.rs | 4 ++++ render-wasm/src/shapes.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 80fc974fef..22b58b098c 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1472,6 +1472,10 @@ impl RenderState { // Z-index ordering on Layouts if element.has_layout() { + if element.is_flex() && !element.is_flex_reverse() { + children_ids.reverse(); + } + children_ids.sort_by(|id1, id2| { let z1 = tree.get(id1).map_or_else(|| 0, |s| s.z_index()); let z2 = tree.get(id2).map_or_else(|| 0, |s| s.z_index()); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index e727bba0c5..d0792c9060 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -328,6 +328,28 @@ impl Shape { ) } + pub fn is_flex(&self) -> bool { + matches!( + self.shape_type, + Type::Frame(Frame { + layout: Some(layouts::Layout::FlexLayout(_, _)), + .. + }) + ) + } + + pub fn is_flex_reverse(&self) -> bool { + matches!( + self.shape_type, + Type::Frame(Frame { + layout: Some(layouts::Layout::FlexLayout(_, FlexData { + direction: layouts::FlexDirection::RowReverse | layouts::FlexDirection::ColumnReverse, .. + })), + .. + }) + ) + } + pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) { self.invalidate_bounds(); self.invalidate_extrect(); From c1638817b2f04c87d14e60641239163884fc01ec Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 6 Nov 2025 17:42:07 +0100 Subject: [PATCH 3/5] :bug: Fix problem with frame titles not moving --- frontend/src/app/main/ui/workspace/viewport_wasm.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index ed7167b9f8..63799e91ac 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -66,9 +66,9 @@ (defn apply-modifiers-to-selected [selected objects modifiers] (->> modifiers - (filter #(contains? selected (:id %))) + (filter #(contains? selected (first %))) (reduce - (fn [objects {:keys [id transform]}] + (fn [objects [id transform]] (update objects id gsh/apply-transform transform)) objects))) From 2d63730bfa4a3372207486c2401b86966faa3090 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 11 Nov 2025 12:04:23 +0100 Subject: [PATCH 4/5] :sparkles: Improved performance in modifiers --- common/src/app/common/types/modifiers.cljc | 18 ++-- .../app/main/data/workspace/modifiers.cljs | 1 - render-wasm/src/math.rs | 7 ++ render-wasm/src/shapes.rs | 18 +++- render-wasm/src/shapes/modifiers.rs | 91 ++++++++++++------- .../src/shapes/modifiers/constraints.rs | 4 +- .../src/shapes/modifiers/flex_layout.rs | 2 +- .../src/shapes/modifiers/grid_layout.rs | 2 +- render-wasm/src/shapes/transform.rs | 27 +++++- 9 files changed, 113 insertions(+), 57 deletions(-) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index ccf59454cd..2f79e6483f 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -767,6 +767,11 @@ :always (ctl/update-flex-child value)))) +(defn remove-children-set + [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes))) + (defn apply-modifier [shape operation] (let [type (dm/get-prop operation :type)] @@ -793,7 +798,7 @@ :remove-children (let [value (dm/get-prop operation :value)] - (update shape :shapes remove-children value)) + (update shape :shapes remove-children-set value)) :scale-content (let [value (dm/get-prop operation :value)] @@ -810,11 +815,6 @@ (defn apply-structure-modifiers "Apply structure changes to a shape" [shape modifiers] - (let [remove-children - (fn [shapes children-to-remove] - (let [remove? (set children-to-remove)] - (d/removev remove? shapes)))] - - (as-> shape $ - (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) - (reduce apply-modifier $ (dm/get-prop modifiers :structure-child))))) + (as-> shape $ + (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-child)))) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index c12cc4b32e..a7db199f14 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -19,7 +19,6 @@ [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.modifiers :as ctm] - [app.common.types.modifiers :as ctm] [app.common.types.path :as path] [app.common.types.shape-tree :as ctst] [app.common.types.shape.attrs :refer [editable-attrs]] diff --git a/render-wasm/src/math.rs b/render-wasm/src/math.rs index 2aa121735c..9392cb00e8 100644 --- a/render-wasm/src/math.rs +++ b/render-wasm/src/math.rs @@ -51,6 +51,13 @@ pub fn identitish(m: &Matrix) -> bool { && is_close_to(m.skew_y(), 0.0) } +pub fn is_move_only_matrix(m: &Matrix) -> bool { + is_close_to(m.scale_x(), 1.0) + && is_close_to(m.scale_y(), 1.0) + && is_close_to(m.skew_x(), 0.0) + && is_close_to(m.skew_y(), 0.0) +} + #[derive(Debug, Copy, Clone, PartialEq)] pub struct Bounds { pub nw: Point, diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index d0792c9060..bdf1eae215 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1,5 +1,7 @@ use skia_safe::{self as skia}; +use indexmap::IndexSet; + use crate::uuid::Uuid; use std::borrow::Cow; use std::cell::{OnceCell, RefCell}; @@ -342,9 +344,14 @@ impl Shape { matches!( self.shape_type, Type::Frame(Frame { - layout: Some(layouts::Layout::FlexLayout(_, FlexData { - direction: layouts::FlexDirection::RowReverse | layouts::FlexDirection::ColumnReverse, .. - })), + layout: Some(layouts::Layout::FlexLayout( + _, + FlexData { + direction: layouts::FlexDirection::RowReverse + | layouts::FlexDirection::ColumnReverse, + .. + } + )), .. }) ) @@ -1279,13 +1286,14 @@ impl Shape { } pub fn apply_structure(&mut self, structure: &Vec) { - let mut result: Vec = Vec::from_iter(self.children.iter().copied()); + let mut result = IndexSet::::from_iter(self.children.iter().copied()); let mut to_remove = HashSet::<&Uuid>::new(); for st in structure { match st.entry_type { StructureEntryType::AddChild => { - result.insert(st.index as usize, st.id); + let index = usize::min(result.len() - 1, st.index as usize); + result.shift_insert(index, st.id); } StructureEntryType::RemoveChild => { to_remove.insert(&st.id); diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index c5de2ae704..c7ca3e5484 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -6,11 +6,12 @@ mod flex_layout; pub mod common; pub mod grid_layout; -use crate::math::{self as math, bools, identitish, Bounds, Matrix, Point}; +use crate::math::{self as math, bools, identitish, is_close_to, Bounds, Matrix, Point}; use common::GetBounds; use crate::shapes::{ - ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry, Type, + ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry, + TransformEntrySource, Type, }; use crate::state::{ShapesPoolRef, State}; use crate::uuid::Uuid; @@ -75,7 +76,7 @@ fn propagate_children( child.ignore_constraints, ); - result.push_back(Modifier::transform(*child_id, transform)); + result.push_back(Modifier::transform_propagate(*child_id, transform)); } result @@ -182,33 +183,43 @@ fn propagate_transform( let mut transform = entry.transform; - // NOTA: No puedo utilizar un clone porque entonces estarĂ­amos - // perdiendo la referencia al contenido del layout... - if let Type::Text(text_content) = &mut shape.shape_type.clone() { - if text_content.needs_update_layout() { - text_content.update_layout(shape.selrect); - } - match text_content.grow_type() { - GrowType::AutoHeight => { - let height = text_content.size.height; - let resize_transform = math::resize_matrix( - &shape_bounds_after, - &shape_bounds_after, - shape_bounds_after.width(), - height, - ); - shape_bounds_after = shape_bounds_after.transform(&resize_transform); - transform.post_concat(&resize_transform); + // Only check the text layout when the width/height changes + if !is_close_to(shape_bounds_before.width(), shape_bounds_after.width()) + || !is_close_to(shape_bounds_before.height(), shape_bounds_after.height()) + { + if let Type::Text(text_content) = &mut shape.shape_type.clone() { + match text_content.grow_type() { + GrowType::AutoHeight => { + if text_content.needs_update_layout() { + text_content.update_layout(shape.selrect); + } + let height = text_content.size.height; + let resize_transform = math::resize_matrix( + &shape_bounds_after, + &shape_bounds_after, + shape_bounds_after.width(), + height, + ); + shape_bounds_after = shape_bounds_after.transform(&resize_transform); + transform.post_concat(&resize_transform); + } + GrowType::AutoWidth => { + if text_content.needs_update_layout() { + text_content.update_layout(shape.selrect); + } + let width = text_content.width(); + let height = text_content.size.height; + let resize_transform = math::resize_matrix( + &shape_bounds_after, + &shape_bounds_after, + width, + height, + ); + shape_bounds_after = shape_bounds_after.transform(&resize_transform); + transform.post_concat(&resize_transform); + } + GrowType::Fixed => {} } - GrowType::AutoWidth => { - let width = text_content.width(); - let height = text_content.size.height; - let resize_transform = - math::resize_matrix(&shape_bounds_after, &shape_bounds_after, width, height); - shape_bounds_after = shape_bounds_after.transform(&resize_transform); - transform.post_concat(&resize_transform); - } - GrowType::Fixed => {} } } @@ -234,12 +245,19 @@ fn propagate_transform( shape_modif.post_concat(&transform); modifiers.insert(shape.id, shape_modif); - if shape.has_layout() { + let is_resize = !math::is_move_only_matrix(&transform); + let is_propagate = entry.source == TransformEntrySource::Propagate; + + // If this is a layout and we're only moving don't need to reflow + if shape.has_layout() && is_resize { entries.push_back(Modifier::reflow(shape.id)); } if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) { - if parent.has_layout() || parent.is_group_like() { + // When the parent is either a group or a layout we only mark for reflow + // if the current transformation is not a move propagation. + // If it's a move propagation we don't need to reflow, the parent is already changed. + if (parent.has_layout() || parent.is_group_like()) && (is_resize || !is_propagate) { entries.push_back(Modifier::reflow(parent.id)); } } @@ -360,7 +378,14 @@ pub fn propagate_modifiers( ) -> Vec { let mut entries: VecDeque<_> = modifiers .iter() - .map(|entry| Modifier::Transform(entry.clone())) + .map(|entry| { + // If we receibe a identity matrix we force a reflow + if math::identitish(&entry.transform) { + Modifier::Reflow(entry.id) + } else { + Modifier::Transform(entry.clone()) + } + }) .collect(); let mut modifiers = HashMap::::new(); @@ -407,7 +432,7 @@ pub fn propagate_modifiers( modifiers .iter() - .map(|(key, val)| TransformEntry::new(*key, *val)) + .map(|(key, val)| TransformEntry::from_input(*key, *val)) .collect() } diff --git a/render-wasm/src/shapes/modifiers/constraints.rs b/render-wasm/src/shapes/modifiers/constraints.rs index 1a838c0a9a..190fd32734 100644 --- a/render-wasm/src/shapes/modifiers/constraints.rs +++ b/render-wasm/src/shapes/modifiers/constraints.rs @@ -1,4 +1,4 @@ -use crate::math::{Bounds, Matrix}; +use crate::math::{is_move_only_matrix, Bounds, Matrix}; use crate::shapes::{ConstraintH, ConstraintV}; pub fn calculate_resize( @@ -110,7 +110,7 @@ pub fn propagate_shape_constraints( // can propagate as is if (ignore_constrainst || constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale) - || transform.is_translate() + || is_move_only_matrix(&transform) { return transform; } diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index b3202c43f3..d3ed26b1db 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -623,7 +623,7 @@ pub fn reflow_flex_layout( transform.post_concat(&Matrix::translate(delta_v)); } - result.push_back(Modifier::transform(child.id, transform)); + result.push_back(Modifier::transform_propagate(child.id, transform)); shape_anchor = next_anchor( layout_data, diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 2c3d19df1d..083d89d0f6 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -791,7 +791,7 @@ pub fn reflow_grid_layout( transform.post_concat(&Matrix::translate(delta_v)); } - result.push_back(Modifier::transform(child.id, transform)); + result.push_back(Modifier::transform_propagate(child.id, transform)); } if shape.is_layout_horizontal_auto() || shape.is_layout_vertical_auto() { diff --git a/render-wasm/src/shapes/transform.rs b/render-wasm/src/shapes/transform.rs index 7abf938b34..f3eba62d67 100644 --- a/render-wasm/src/shapes/transform.rs +++ b/render-wasm/src/shapes/transform.rs @@ -12,8 +12,8 @@ pub enum Modifier { } impl Modifier { - pub fn transform(id: Uuid, transform: Matrix) -> Self { - Modifier::Transform(TransformEntry::new(id, transform)) + pub fn transform_propagate(id: Uuid, transform: Matrix) -> Self { + Modifier::Transform(TransformEntry::from_propagate(id, transform)) } pub fn parent(id: Uuid, transform: Matrix) -> Self { Modifier::Transform(TransformEntry::parent(id, transform)) @@ -23,19 +23,35 @@ impl Modifier { } } +#[derive(PartialEq, Debug, Clone)] +pub enum TransformEntrySource { + Input, + Propagate, +} + #[derive(PartialEq, Debug, Clone)] #[repr(C)] pub struct TransformEntry { pub id: Uuid, pub transform: Matrix, + pub source: TransformEntrySource, pub propagate: bool, } impl TransformEntry { - pub fn new(id: Uuid, transform: Matrix) -> Self { + pub fn from_input(id: Uuid, transform: Matrix) -> Self { TransformEntry { id, transform, + source: TransformEntrySource::Input, + propagate: true, + } + } + pub fn from_propagate(id: Uuid, transform: Matrix) -> Self { + TransformEntry { + id, + transform, + source: TransformEntrySource::Propagate, propagate: true, } } @@ -43,6 +59,7 @@ impl TransformEntry { TransformEntry { id, transform, + source: TransformEntrySource::Propagate, propagate: false, } } @@ -70,7 +87,7 @@ impl SerializableResult for TransformEntry { 0.0, 1.0, ); - TransformEntry::new(id, transform) + TransformEntry::from_input(id, transform) } fn as_bytes(&self) -> Self::BytesType { @@ -176,7 +193,7 @@ mod tests { #[test] fn test_serialization() { - let entry = TransformEntry::new( + let entry = TransformEntry::from_input( Uuid::new_v4(), Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0), ); From 639952abc84071318fb73e99349829f3c3b2645a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 13 Nov 2025 12:31:02 +0100 Subject: [PATCH 5/5] :bug: Fix problems with text positioning in layout --- frontend/src/app/main/data/helpers.cljs | 10 +++++ frontend/src/app/main/refs.cljs | 3 ++ .../shapes/text/text_edition_outline.cljs | 7 ++- .../ui/workspace/shapes/text/v2_editor.cljs | 44 ++++++++++++++----- .../main/ui/workspace/viewport/selection.cljs | 22 +++------- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/main/data/helpers.cljs b/frontend/src/app/main/data/helpers.cljs index 05237fcd1b..97b8911385 100644 --- a/frontend/src/app/main/data/helpers.cljs +++ b/frontend/src/app/main/data/helpers.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.types.path :as path])) @@ -207,3 +208,12 @@ :projects (filter #(= team-id (:team-id (val %)))) (into {})))) + +(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)])) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index f699ce20f1..2561557367 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -154,6 +154,9 @@ "All tokens related ephimeral state" (l/derived :workspace-tokens st/state)) +(def workspace-selrect + (l/derived :workspace-selrect st/state)) + ;; WARNING: Don't use directly from components, this is a proxy to ;; improve performance of selected-shapes and (def ^:private selected-shapes-data 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 6214bf64b4..b7367a4431 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 @@ -7,19 +7,18 @@ (ns app.main.ui.workspace.shapes.text.text-edition-outline (:require [app.common.geom.shapes :as gsh] + [app.main.data.helpers :as dsh] [app.main.data.workspace.texts :as dwt] [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] - [app.render-wasm.api :as wasm.api] [rumext.v2 :as mf])) (mf/defc text-edition-outline [{:keys [shape zoom modifiers]}] (if (features/active-feature? @st/state "render-wasm/v1") - (let [transform (gsh/transform-str shape) - {:keys [id x y grow-type]} shape - {:keys [width height]} (if (= :fixed grow-type) shape (wasm.api/get-text-dimensions id))] + (let [selrect-transform (mf/deref refs/workspace-selrect) + [{:keys [x y width height]} transform] (dsh/get-selrect selrect-transform shape)] [: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 3bcc00fc0a..e611409192 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 @@ -10,12 +10,14 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.text :as gst] [app.common.math :as mth] [app.common.types.color :as color] [app.common.types.text :as txt] [app.config :as cf] + [app.main.data.helpers :as dsh] [app.main.data.workspace :as dw] [app.main.data.workspace.texts :as dwt] [app.main.features :as features] @@ -226,8 +228,8 @@ (stl/css :text-editor-container)) :ref container-ref :data-testid "text-editor-container" - :style {:width (:width shape) - :height (:height shape)} + :style {:width "var(--editor-container-width)" + :height "var(--editor-container-height)"} ;; We hide the editor when is blurred because otherwise the ;; selection won't let us see the underlying text. Use opacity ;; because display or visibility won't allow to recover focus @@ -303,12 +305,22 @@ (some? modifiers) (gsh/transform-shape modifiers)) - [x y width height] - (if (features/active-feature? @st/state "render-wasm/v1") - (let [{:keys [width height]} (wasm.api/get-text-dimensions shape-id) - {:keys [x y]} (:selrect shape)] + render-wasm? (mf/use-memo #(features/active-feature? @st/state "render-wasm/v1")) - [x y width height]) + [{:keys [x y width height]} transform] + (if render-wasm? + (let [{:keys [width height]} (wasm.api/get-text-dimensions shape-id) + selrect-transform (mf/deref refs/workspace-selrect) + [selrect transform] (dsh/get-selrect selrect-transform shape) + + valign (-> shape :content :vertical-align) + + y (:y selrect) + y (case valign + "bottom" (- y (- height (:height selrect))) + "center" (- y (/ (- height (:height selrect)) 2)) + y)] + [(assoc selrect :y y :width width :height height) transform]) (let [bounds (gst/shape->rect shape) x (mth/min (dm/get-prop bounds :x) @@ -319,12 +331,24 @@ (dm/get-prop shape :width)) height (mth/max (dm/get-prop bounds :height) (dm/get-prop shape :height))] - [x y width height])) + [(grc/make-rect x y width height) (gsh/transform-matrix shape)])) style (cond-> #js {:pointerEvents "all"} + render-wasm? + (obj/merge! + #js {"--editor-container-width" (dm/str width "px") + "--editor-container-height" (dm/str height "px")}) - (not (cf/check-browser? :safari)) + (not render-wasm?) + (obj/merge! + #js {"--editor-container-width" (dm/str (:width shape) "px") + "--editor-container-height" (dm/str (:height shape) "px")}) + + ;; Transform is necessary when there is a text overflow and the vertical + ;; aligment is center or bottom. + (and (not render-wasm?) + (not (cf/check-browser? :safari))) (obj/merge! #js {:transform (dm/fmt "translate(%px, %px)" (- (dm/get-prop shape :x) x) (- (dm/get-prop shape :y) y))}) @@ -345,7 +369,7 @@ (dm/fmt "scale(%)" maybe-zoom))}))] [:g.text-editor {:clip-path (dm/fmt "url(#%)" clip-id) - :transform (dm/str (gsh/transform-matrix shape))} + :transform (dm/str transform)} [:defs [:clipPath {:id clip-id} [:rect {:x x :y y :width width :height height}]]] diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 071233f44b..6f34d8d13f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -15,6 +15,7 @@ [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.shape :as cts] + [app.main.data.helpers :as dsh] [app.main.data.workspace :as dw] [app.main.data.workspace.shapes :as dwsh] [app.main.refs :as refs] @@ -25,7 +26,6 @@ [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) @@ -327,23 +327,11 @@ :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-transform (mf/deref workspace-selrect-transform) + (let [selrect-transform (mf/deref refs/workspace-selrect) transform-type (mf/deref refs/current-transform) - [selrect transform] (get-selrect selrect-transform shape)] + [selrect transform] (dsh/get-selrect selrect-transform shape)] (when (and (some? selrect) (not (or (= transform-type :move) @@ -360,7 +348,7 @@ (mf/defc controls-handlers* {::mf/private true} [{:keys [shape zoom color on-resize on-rotate disabled]}] - (let [selrect-transform (mf/deref workspace-selrect-transform) + (let [selrect-transform (mf/deref refs/workspace-selrect) transform-type (mf/deref refs/current-transform) read-only? (mf/use-ctx ctx/workspace-read-only?) @@ -368,7 +356,7 @@ layout (mf/deref refs/workspace-layout) scale-text? (contains? layout :scale-text) - [selrect transform] (get-selrect selrect-transform shape) + [selrect transform] (dsh/get-selrect selrect-transform shape) rotation (-> (gpt/point 1 0) (gpt/transform (:transform shape))