diff --git a/.serena/memories/frontend/composable-component-tests.md b/.serena/memories/frontend/composable-component-tests.md index ae1093b317..77422d5558 100644 --- a/.serena/memories/frontend/composable-component-tests.md +++ b/.serena/memories/frontend/composable-component-tests.md @@ -110,7 +110,7 @@ Two pure runners (no store, JVM + CLJS): (`:component-id-mismatch`), nor with `{:sync? false}`. Assembly ops (create/instantiate/nest/variant container): no sync, no undo group. `n/reset` (`reset-copy-instance` resets a lineage's latest copy) is a user op (sync + undo); the interpreter sends `dwl/reset-component`. `{:sync? false}` disables sync. `Rotate`/`ChangeHeight`/`MoveShape` - are `:frontend-only` and throw `::runner/frontend-only` (the placement step is frontend code). Swap and switch nodes use the production + are `:frontend-only` and throw `::runner/frontend-only` (their modifiers come from frontend events; the placement step itself is common, `app.common.logic.placement`). Swap and switch nodes use the production `clv/generate-component-swap-in-place` (+ `clv/find-switch-target` for switch), the same code the frontend events call. Not modelled: the `:modified-at` bump, and the swap/switch event follow-ups (token propagation, WASM text resize, layout update). Tests: diff --git a/.serena/memories/frontend/workspace-transform-subtleties.md b/.serena/memories/frontend/workspace-transform-subtleties.md index 958e121c9c..c7a7aa81dc 100644 --- a/.serena/memories/frontend/workspace-transform-subtleties.md +++ b/.serena/memories/frontend/workspace-transform-subtleties.md @@ -9,7 +9,7 @@ ## Component-copy touched suppression -- `calculate-ignore-tree` walks modified shapes and descendants to decide per copy-shape `ignore-geometry?`. +- `clp/calculate-ignore-tree` (common, `app.common.logic.placement`) walks modified shapes and descendants to decide per copy-shape `ignore-geometry?`; both renderers call it, with `clp/modifiers-transform` (SVG) or `clp/matrices-transform` (WASM). - `check-delta` compares a copy's relative position/rotation to its component root before and after transform. If relative movement is under about 1px and size/rotation are effectively unchanged, geometry touching is suppressed. - This logic is why pure translations of component copies can avoid marking every descendant as geometry-touched, while resizes/rotations still propagate touched state. diff --git a/common/src/app/common/logic/placement.cljc b/common/src/app/common/logic/placement.cljc new file mode 100644 index 0000000000..fd33f21dfd --- /dev/null +++ b/common/src/app/common/logic/placement.cljc @@ -0,0 +1,219 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.common.logic.placement + "The placement step of a transform: which shapes of a copy a transform + leaves untouched. Moving, resizing or rotating a copy as a whole is free + placement and touches none of its descendants; editing one shape of a copy + touches it (see `mem:common/component-sync-contract`). The result, the + ignore tree, goes to `cls/generate-update-shapes` (`:ignore-tree`), which + leaves the geometry of the shapes it marks untouched." + (:require + [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.rect :as grc] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.types.component :as ctk] + [app.common.types.container :as ctn])) + +(def transform-attrs + "The attributes a transform can change, so an update after it compares + only these." + #{:selrect + :points + :x + :y + :r1 + :r2 + :r3 + :r4 + :shadow + :blur + :background-blur + :strokes + :width + :height + :content + :transform + :transform-inverse + :rotation + :flip-x + :flip-y + :grow-type + :position-data + :layout-gap + :layout-padding + :layout-item-h-sizing + :layout-item-max-h + :layout-item-max-w + :layout-item-min-h + :layout-item-min-w + :layout-item-v-sizing + :layout-padding-type + :layout-item-margin + :layout-item-margin-type + :layout-grid-cells + :layout-grid-columns + :layout-grid-rows + :fills + :fill-image}) + +(defn- check-delta + "If the shape is a component instance, check whether the transformation is a + free change that must not mark the shape as touched. + + For the instance ROOT, position is free placement, but its rotation and flips + are inherited content: transforming the copy as a whole overrides them, so a + change there marks the root as touched. + + For DESCENDANTS, position, size, rotation and flips are compared RELATIVE TO + THE ROOT: a transformation of the whole instance preserves them all and does + not mark the descendants as touched; editing an individual shape does." + [shape root transformed-shape transformed-root] + (let [is-root? (and (some? root) + (= (dm/get-prop shape :id) (dm/get-prop root :id))) + + center (fn [shape] + (grc/rect->center (:selrect shape))) + + rel-pos (fn [shape root] + (when root + ;; vector from the root center to the shape center, + ;; expressed in the root's local (untransformed) axes + (-> (gpt/subtract (center shape) (center root)) + (gpt/transform (:transform-inverse root (gmt/matrix)))))) + + orientation (fn [shape root] + ;; the shape's rotation/flip orientation taken from its + ;; transform matrix, so it is reliable regardless of how the + ;; transform was applied (the WASM apply-transform path does + ;; not refresh the :rotation attribute). For the ROOT the + ;; absolute orientation; for a DESCENDANT the orientation + ;; relative to the root — invariant when the whole instance + ;; is rotated or flipped as a unit. + (let [t (:transform shape (gmt/matrix))] + (if is-root? + t + (gmt/multiply (:transform-inverse root (gmt/matrix)) t)))) + + pos-before (rel-pos shape root) + pos-after (rel-pos transformed-shape transformed-root) + + distance + (if (and pos-before pos-after) + (gpt/distance-vector pos-before pos-after) + (gpt/point 0 0)) + + orientation-unchanged? + (gmt/close? (orientation shape root) + (orientation transformed-shape transformed-root)) + + selrect (:selrect shape) + transformed-selrect (:selrect transformed-shape)] + + ;; There are cases in that the coordinates change slightly (e.g. when rounding + ;; to pixel, or when recalculating text positions in different zoom levels). + ;; To take this into account, we ignore movements smaller than 1 pixel. + ;; + ;; When the change is a resize, also has a transformation that may have the + ;; shape position unchanged. But in this case we do not want to ignore it. + (and (and (< (:x distance) 1) (< (:y distance) 1)) + (mth/close? (:width selrect) (:width transformed-selrect)) + (mth/close? (:height selrect) (:height transformed-selrect)) + orientation-unchanged?))) + +(defn cached-transform + "`transform` (shape -> transformed shape), computed once per shape value." + [transform] + (let [cache (volatile! {})] + (fn [shape] + (let [id (dm/get-prop shape :id) + [cached result] (get @cache id)] + (if (and (some? cached) (identical? shape cached)) + result + (let [result (transform shape)] + (vswap! cache assoc id [shape result]) + result)))))) + +(defn calculate-ignore-tree + "A map of each shape of a copy under the transformed shapes `ids` to + whether the transform leaves it untouched: true when its geometry + relative to its copy root (or, for the root, its orientation) is the + same after `transform` (shape -> transformed shape). Each shape is + compared with the root of the copy of the transformed shape latest in + `ids` among itself and its ancestors." + [objects ids transform] + (let [order (into {} (map-indexed (fn [i id] [id i])) ids) + + component-shape + (fn [shape parent parent-component] + (cond + (cfh/root? shape) nil + (ctk/instance-root? shape) shape + (and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent))) shape + :else parent-component)) + + walk + (fn walk [ignore-tree shape component latest] + (let [index (get order (dm/get-prop shape :id)) + latest (if (and (some? index) (or (nil? latest) (> index (nth latest 0)))) + (if (some? component) + [index component (transform component)] + latest) + latest) + ignore-tree + (if (and (some? latest) (ctk/in-component-copy? shape)) + (let [[_ root transformed-root] latest] + (assoc ignore-tree + (dm/get-prop shape :id) + (check-delta shape root (transform shape) transformed-root))) + ignore-tree)] + (reduce (fn [ignore-tree child] + (walk ignore-tree child (component-shape child shape component) latest)) + ignore-tree + (map (d/getf objects) (:shapes shape))))) + + under-transform (volatile! {}) + + under-transform? + (fn under-transform? [id] + (let [parent-id (dm/get-in objects [id :parent-id])] + (cond + (or (nil? parent-id) (= parent-id id)) false + (contains? order parent-id) true + :else + (let [cached (get @under-transform parent-id ::none)] + (if (= cached ::none) + (let [result (under-transform? parent-id)] + (vswap! under-transform assoc parent-id result) + result) + cached)))))] + (->> ids + (remove under-transform?) + (keep #(get objects %)) + (reduce (fn [ignore-tree shape] + (let [parent (get objects (:parent-id shape))] + (walk ignore-tree + shape + (component-shape shape parent (ctn/get-component-shape objects parent {:allow-main? true})) + nil))) + nil)))) + +(defn modifiers-transform + "A cached transform (see `cached-transform`) that applies to each shape its + modifiers in `modif-tree`." + [modif-tree] + (cached-transform #(gsh/transform-shape % (dm/get-in modif-tree [(dm/get-prop % :id) :modifiers])))) + +(defn matrices-transform + "A cached transform (see `cached-transform`) that applies to each shape its + matrix in `transforms`, a map of ids to matrices." + [transforms] + (cached-transform #(gsh/apply-transform % (get transforms (dm/get-prop % :id))))) diff --git a/common/src/app/common/test_helpers/composable/comp/nodes.cljc b/common/src/app/common/test_helpers/composable/comp/nodes.cljc index 88052c21c1..948922c80d 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -274,10 +274,11 @@ ;; --------------------------------------------------------------------------- ;; Geometry operations ;; -;; The frontend interpreter dispatches the real sidebar events, whose -;; transform step decides which copy shapes the change touches -;; (`check-delta`), frontend code. Their `apply-to` makes the same transform -;; without that step, so the pure runner rejects them. +;; The frontend interpreter dispatches the real sidebar events: they build +;; the modifiers (frontend code: constraints, pixel snap) and the placement +;; step (`clp/calculate-ignore-tree`) decides which copy shapes the change +;; touches. Their `apply-to` makes the same transform without either, so the +;; pure runner rejects them. (defrecord Rotate [target angle] IComponentOperation diff --git a/common/test/common_tests/logic/composable_sync_test.cljc b/common/test/common_tests/logic/composable_sync_test.cljc index 38c61e76b0..8dc5706cd1 100644 --- a/common/test/common_tests/logic/composable_sync_test.cljc +++ b/common/test/common_tests/logic/composable_sync_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.composable-sync-test "Runs the shared composable component cases through the pure runner, on the JVM and in JS. Only the frontend interpreter runs the geometry cases (N, Z, - AI and the rotation case), which need the frontend placement step, case X, + AI and the rotation case), whose modifiers come from frontend events, case X, which needs a pending frontend sync, case AJ, which needs the frontend reflow, and cases AT..AX, whose variant actions are frontend events." (:require diff --git a/common/test/common_tests/logic/placement_test.cljc b/common/test/common_tests/logic/placement_test.cljc new file mode 100644 index 0000000000..1e770d92a8 --- /dev/null +++ b/common/test/common_tests/logic/placement_test.cljc @@ -0,0 +1,77 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns common-tests.logic.placement-test + (:require + [app.common.files.helpers :as cfh] + [app.common.geom.matrix :as gmt] + [app.common.geom.modifiers :as gm] + [app.common.geom.point :as gpt] + [app.common.logic.placement :as clp] + [app.common.test-helpers.compositions :as tho] + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.ids-map :as thi] + [app.common.types.component :as ctk] + [app.common.types.modifiers :as ctm] + [clojure.test :as t])) + +(t/use-fixtures :each thi/test-fixture) + +(defn- setup + [] + (let [file (-> (thf/sample-file :file1) + (tho/add-frame :board) + (tho/add-nested-component-with-copy :component1 :main1-root :main1-child + :component2 :main2-root :nested-head + :copy2-root + :copy2-root-params {:parent-label :board}))] + (:objects (thf/current-page file)))) + +(defn- moved-ignore-tree + "The ignore tree of moving the shapes `moved-ids` by (10, 20), each with + the same matrix, as the WASM renderer gives it." + [objects moved-ids] + (let [transforms (zipmap moved-ids (repeat (gmt/translate-matrix (gpt/point 10 20))))] + (clp/calculate-ignore-tree objects moved-ids (clp/matrices-transform transforms)))) + +(defn- copy-ids + [objects] + (->> (cfh/get-children-ids-with-self objects (thi/id :copy2-root)) + (filter #(ctk/in-component-copy? (get objects %))))) + +(t/deftest moving-a-board-keeps-its-copies-untouched + (let [objects (setup) + tree (moved-ignore-tree objects (cfh/get-children-ids-with-self objects (thi/id :board)))] + (t/is (seq (copy-ids objects))) + (t/is (every? #(true? (get tree %)) (copy-ids objects))))) + +(t/deftest moving-a-copy-keeps-its-children-untouched + (let [objects (setup) + tree (moved-ignore-tree objects (cfh/get-children-ids-with-self objects (thi/id :copy2-root)))] + (t/is (every? #(true? (get tree %)) (copy-ids objects))))) + +(t/deftest moving-a-shape-inside-a-copy-touches-it + (let [objects (setup) + rect-id (last (copy-ids objects)) + tree (moved-ignore-tree objects [rect-id])] + (t/is (false? (get tree rect-id))))) + +(t/deftest rotating-a-copy-with-modifiers-keeps-its-children-untouched + (let [file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root + :main-root-params {:width 100 :height 100} + :main-child-params {:x 10 :y 10 :width 20 :height 20})) + objects (:objects (thf/current-page file)) + root (get objects (thi/id :copy-root)) + child-id (first (:shapes root)) + modif-tree (gm/set-objects-modifiers + {(:id root) {:modifiers (ctm/rotation-modifiers root (gpt/point 50 50) 30)}} + objects) + tree (clp/calculate-ignore-tree objects (keys modif-tree) (clp/modifiers-transform modif-tree))] + (t/testing "the root's rotation is an override" + (t/is (false? (get tree (:id root))))) + (t/testing "its child keeps its geometry relative to it" + (t/is (true? (get tree child-id)))))) diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index 77fe4026ec..65a7ced43f 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -76,6 +76,7 @@ [common-tests.logic.library-asset-sync-test] [common-tests.logic.move-shapes-test] [common-tests.logic.multiple-nesting-levels-test] + [common-tests.logic.placement-test] [common-tests.logic.swap-and-reset-test] [common-tests.logic.swap-as-override-test] [common-tests.logic.swap-group-test] @@ -200,6 +201,7 @@ 'common-tests.logic.library-asset-sync-test 'common-tests.logic.move-shapes-test 'common-tests.logic.multiple-nesting-levels-test + 'common-tests.logic.placement-test 'common-tests.logic.swap-and-reset-test 'common-tests.logic.swap-as-override-test 'common-tests.logic.swap-group-test diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 0e4bb0a5ce..8c4aba9eca 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -16,10 +16,9 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.logging :as log] + [app.common.logic.placement :as clp] [app.common.math :as mth] [app.common.render-wasm.api.select :as wselect] - [app.common.types.component :as ctk] - [app.common.types.container :as ctn] [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] [app.common.types.shape.attrs :refer [editable-attrs]] @@ -63,46 +62,6 @@ (when (wasm-gesture/try-end-interactive-transform!) (wasm.api/set-modifiers-end))) -(def ^:private transform-attrs - #{:selrect - :points - :x - :y - :r1 - :r2 - :r3 - :r4 - :shadow - :blur - :background-blur - :strokes - :width - :height - :content - :transform - :transform-inverse - :rotation - :flip-x - :flip-y - :grow-type - :position-data - :layout-gap - :layout-padding - :layout-item-h-sizing - :layout-item-max-h - :layout-item-max-w - :layout-item-min-h - :layout-item-min-w - :layout-item-v-sizing - :layout-padding-type - :layout-item-margin - :layout-item-margin-type - :layout-grid-cells - :layout-grid-columns - :layout-grid-rows - :fills - :fill-image}) - ;; -- temporary modifiers ------------------------------------------- ;; During an interactive transformation of shapes (e.g. when resizing or rotating @@ -119,193 +78,6 @@ ;; apply-modifiers event is done, that consolidates all modifiers into the base ;; geometric attributes of the shapes. -(defn- check-delta - "If the shape is a component instance, check whether the transformation is a - free change that must not mark the shape as touched. - - For the instance ROOT, position is free placement, but its rotation and flips - are inherited content: transforming the copy as a whole overrides them, so a - change there marks the root as touched. - - For DESCENDANTS, position, size, rotation and flips are compared RELATIVE TO - THE ROOT: a transformation of the whole instance preserves them all and does - not mark the descendants as touched; editing an individual shape does." - [shape root transformed-shape transformed-root] - (let [is-root? (and (some? root) - (= (dm/get-prop shape :id) (dm/get-prop root :id))) - - center (fn [shape] - (grc/rect->center (:selrect shape))) - - rel-pos (fn [shape root] - (when root - ;; vector from the root center to the shape center, - ;; expressed in the root's local (untransformed) axes - (-> (gpt/subtract (center shape) (center root)) - (gpt/transform (:transform-inverse root (gmt/matrix)))))) - - orientation (fn [shape root] - ;; the shape's rotation/flip orientation taken from its - ;; transform matrix, so it is reliable regardless of how the - ;; transform was applied (the WASM apply-transform path does - ;; not refresh the :rotation attribute). For the ROOT the - ;; absolute orientation; for a DESCENDANT the orientation - ;; relative to the root — invariant when the whole instance - ;; is rotated or flipped as a unit. - (let [t (:transform shape (gmt/matrix))] - (if is-root? - t - (gmt/multiply (:transform-inverse root (gmt/matrix)) t)))) - - pos-before (rel-pos shape root) - pos-after (rel-pos transformed-shape transformed-root) - - distance - (if (and pos-before pos-after) - (gpt/distance-vector pos-before pos-after) - (gpt/point 0 0)) - - orientation-unchanged? - (gmt/close? (orientation shape root) - (orientation transformed-shape transformed-root)) - - selrect (:selrect shape) - transformed-selrect (:selrect transformed-shape)] - - ;; There are cases in that the coordinates change slightly (e.g. when rounding - ;; to pixel, or when recalculating text positions in different zoom levels). - ;; To take this into account, we ignore movements smaller than 1 pixel. - ;; - ;; When the change is a resize, also has a transformation that may have the - ;; shape position unchanged. But in this case we do not want to ignore it. - (and (and (< (:x distance) 1) (< (:y distance) 1)) - (mth/close? (:width selrect) (:width transformed-selrect)) - (mth/close? (:height selrect) (:height transformed-selrect)) - orientation-unchanged?))) - -(defn calculate-ignore-tree - "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" - [modif-tree objects] - - (letfn [(get-ignore-tree - ([ignore-tree shape] - (let [shape-id (dm/get-prop shape :id) - transformed-shape (gsh/transform-shape shape (dm/get-in modif-tree [shape-id :modifiers])) - - root - (if (:component-root shape) - shape - (ctn/get-component-shape objects shape {:allow-main? true})) - - transformed-root - (if (:component-root shape) - transformed-shape - (gsh/transform-shape root (dm/get-in modif-tree [(:id root) :modifiers])))] - - (get-ignore-tree ignore-tree shape transformed-shape root transformed-root))) - - ([ignore-tree shape root transformed-root] - (let [shape-id (dm/get-prop shape :id) - transformed-shape (gsh/transform-shape shape (dm/get-in modif-tree [shape-id :modifiers]))] - (get-ignore-tree ignore-tree shape transformed-shape root transformed-root))) - - ([ignore-tree shape transformed-shape root transformed-root] - (let [shape-id (dm/get-prop shape :id) - - ignore-tree - (cond-> ignore-tree - (and (some? root) (ctk/in-component-copy? shape)) - (assoc - shape-id - (check-delta shape root transformed-shape transformed-root))) - - set-child - (fn [ignore-tree child] - (get-ignore-tree ignore-tree child root transformed-root))] - - (->> (:shapes shape) - (map (d/getf objects)) - (reduce set-child ignore-tree)))))] - - ;; we check twice because we want only to search parents of components but once the - ;; tree is traversed we only want to process the objects in components - (->> (keys modif-tree) - (map #(get objects %)) - (reduce get-ignore-tree nil)))) - -(defn- cached-transform - [transforms] - (let [cache (js/Map.)] - (fn [shape] - (let [id (dm/get-prop shape :id) - cached (.get cache id)] - (if (and (some? cached) (identical? shape (aget cached 0))) - (aget cached 1) - (let [result (gsh/apply-transform shape (get transforms id))] - (.set cache id #js [shape result]) - result)))))) - -(defn calculate-ignore-tree-wasm - "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" - ([transforms objects] - (calculate-ignore-tree-wasm transforms objects (cached-transform transforms))) - ([transforms objects transform] - (let [order (into {} (map-indexed (fn [i id] [id i])) (keys transforms)) - - component-shape - (fn [shape parent parent-component] - (cond - (cfh/root? shape) nil - (ctk/instance-root? shape) shape - (and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent))) shape - :else parent-component)) - - walk - (fn walk [ignore-tree shape component latest] - (let [index (get order (dm/get-prop shape :id)) - latest (if (and (some? index) (or (nil? latest) (> index (nth latest 0)))) - (if (some? component) - [index component (transform component)] - latest) - latest) - ignore-tree - (if (and (some? latest) (ctk/in-component-copy? shape)) - (let [[_ root transformed-root] latest] - (assoc ignore-tree - (dm/get-prop shape :id) - (check-delta shape root (transform shape) transformed-root))) - ignore-tree)] - (reduce (fn [ignore-tree child] - (walk ignore-tree child (component-shape child shape component) latest)) - ignore-tree - (map (d/getf objects) (:shapes shape))))) - - under-transform (volatile! {}) - - under-transform? - (fn under-transform? [id] - (let [parent-id (dm/get-in objects [id :parent-id])] - (cond - (or (nil? parent-id) (= parent-id id)) false - (contains? order parent-id) true - :else - (let [cached (get @under-transform parent-id ::none)] - (if (= cached ::none) - (let [result (under-transform? parent-id)] - (vswap! under-transform assoc parent-id result) - result) - cached)))))] - (->> (keys transforms) - (remove under-transform?) - (keep #(get objects %)) - (reduce (fn [ignore-tree shape] - (let [parent (get objects (:parent-id shape))] - (walk ignore-tree - shape - (component-shape shape parent (ctn/get-component-shape objects parent {:allow-main? true})) - nil))) - nil))))) - (defn assoc-position-data [shape position-data old-shape] (let [deltav (gpt/to-vec (gpt/point (ctm/safe-size-rect old-shape)) @@ -910,10 +682,10 @@ (into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel? snap-ignore-axis))) transform-shape - (cached-transform transforms) + (clp/matrices-transform transforms) ignore-tree - (calculate-ignore-tree-wasm transforms objects transform-shape) + (clp/calculate-ignore-tree objects (keys transforms) transform-shape) options (-> params @@ -922,7 +694,7 @@ (assoc :translation? translation?) ;; Attributes that can change in the transform. This ;; way we don't have to check all the attributes - (assoc :attrs transform-attrs)) + (assoc :attrs clp/transform-attrs)) modif-tree (propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state)) @@ -1064,7 +836,8 @@ ids) ignore-tree - (calculate-ignore-tree object-modifiers objects) + (clp/calculate-ignore-tree objects (keys object-modifiers) + (clp/modifiers-transform object-modifiers)) options (-> options @@ -1072,7 +845,7 @@ (assoc :ignore-tree ignore-tree) ;; Attributes that can change in the transform. This ;; way we don't have to check all the attributes - (assoc :attrs transform-attrs)) + (assoc :attrs clp/transform-attrs)) update-shape (fn [shape] diff --git a/frontend/test/frontend_tests/logic/ignore_tree_test.cljs b/frontend/test/frontend_tests/logic/ignore_tree_test.cljs deleted file mode 100644 index f625d0009c..0000000000 --- a/frontend/test/frontend_tests/logic/ignore_tree_test.cljs +++ /dev/null @@ -1,56 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS SUBSIDIARY SL - -(ns frontend-tests.logic.ignore-tree-test - (:require - [app.common.files.helpers :as cfh] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.test-helpers.compositions :as ctho] - [app.common.test-helpers.files :as cthf] - [app.common.test-helpers.ids-map :as cthi] - [app.common.types.component :as ctk] - [app.main.data.workspace.modifiers :as dwm] - [cljs.test :as t :include-macros true])) - -(t/use-fixtures :each {:before cthi/reset-idmap!}) - -(defn- setup - [] - (let [file (-> (cthf/sample-file :file1) - (ctho/add-frame :board) - (ctho/add-nested-component-with-copy :component1 :main1-root :main1-child - :component2 :main2-root :nested-head - :copy2-root - :copy2-root-params {:parent-label :board}))] - (:objects (cthf/current-page file)))) - -(defn- ignore-tree - [objects moved-ids] - (let [move (gmt/translate-matrix (gpt/point 10 20))] - (dwm/calculate-ignore-tree-wasm (zipmap moved-ids (repeat move)) objects))) - -(defn- copy-ids - [objects] - (->> (cfh/get-children-ids-with-self objects (cthi/id :copy2-root)) - (filter #(ctk/in-component-copy? (get objects %))))) - -(t/deftest moving-a-board-keeps-its-copies-untouched - (let [objects (setup) - tree (ignore-tree objects (cfh/get-children-ids-with-self objects (cthi/id :board)))] - (t/is (seq (copy-ids objects))) - (t/is (every? #(true? (get tree %)) (copy-ids objects))))) - -(t/deftest moving-a-copy-keeps-its-children-untouched - (let [objects (setup) - tree (ignore-tree objects (cfh/get-children-ids-with-self objects (cthi/id :copy2-root)))] - (t/is (every? #(true? (get tree %)) (copy-ids objects))))) - -(t/deftest moving-a-shape-inside-a-copy-touches-it - (let [objects (setup) - rect-id (last (copy-ids objects)) - tree (ignore-tree objects [rect-id])] - (t/is (false? (get tree rect-id))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 6bd8d8eee1..8c87826de0 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -33,8 +33,8 @@ [frontend-tests.data.workspace-comments-test] [frontend-tests.data.workspace-context-menu-test] [frontend-tests.data.workspace-edit-watchers-test] - [frontend-tests.data.workspace-interactions-test] [frontend-tests.data.workspace-font-variants-test] + [frontend-tests.data.workspace-interactions-test] [frontend-tests.data.workspace-libraries-test] [frontend-tests.data.workspace-library-links-test] [frontend-tests.data.workspace-library-updates-test] @@ -65,7 +65,6 @@ [frontend-tests.logic.copying-and-duplicating-test] [frontend-tests.logic.frame-guides-test] [frontend-tests.logic.groups-test] - [frontend-tests.logic.ignore-tree-test] [frontend-tests.logic.nudge-selected-shapes-test] [frontend-tests.logic.pasting-in-containers-test] [frontend-tests.logic.path-actions-test] @@ -244,7 +243,6 @@ 'frontend-tests.logic.copying-and-duplicating-test 'frontend-tests.logic.frame-guides-test 'frontend-tests.logic.groups-test - 'frontend-tests.logic.ignore-tree-test 'frontend-tests.logic.nudge-selected-shapes-test 'frontend-tests.logic.path-actions-test 'frontend-tests.logic.path-clipboard-test