mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
✨ Keep the children of a transformed copy shape derived
Decision (f) of the component contract, with a fix it needed and a check of decision (g) (Phase 3c). - Return empty modifiers for a scale constraint axis. The constraint of each axis of a child gives the modifiers that the parent's resize adds to the child; :scale has no method of its own and took the default, which returned an empty vector. Adding a vector to modifiers reads its :last-order as nil: JavaScript adds nil as 0, the JVM throws, so a child scaled on one axis and constrained on the other (scale and top, say) could not be resized on the JVM. The default now returns empty modifiers. geom-shapes-constraints-test checks the default is empty and adds to modifiers. - Keep the children of a transformed copy shape derived. Decision (f) of the component contract: transforming a copy shape touches that shape only; its children change as a consequence and stay untouched, and sync gives them their expected geometry, their main's placed against their parent and resized through their constraints. Touch. The placement step decided per shape whether a transform touched it by comparing it with its copy root, so the children of a resized root that stretched (scale, left and right) were touched (F10), a derived width or height always touched (F22), and a resize that scales the content touched the strokes, radii and the like of every child (F39). The editor now tells the placement step which shapes the user transformed (the keys of the modifiers given, or those the stored modifiers were set for); any other shape of the transform changes only as a consequence of theirs (a child through its constraints, a board that hugs them) and touches nothing. Sync. Sync placed every main shape by the offset between the roots only, so the children of a resized copy root that a resize leaves untouched (center) went back to their main's place at the next sync of the main (F10). Now, before it walks the children of a copy shape that keeps its own geometry, sync resizes, turns and moves the main shape's subtree to match it, its children following through their constraints and layouts, and walks those: each child gets that geometry unless it has its own. comp-expected-geometry-test: a copy root resized as the editor does places a child constrained left, right, center, scale and left-right, leaves it untouched and keeps it there through a sync; a resize that scales the content scales the child's stroke and touches only the root. The frontend golden master records, in cases AI and the touched child under a main rotation, the children the resize or rotation of a root no longer touches. - Check a copy child resize leaves its hug board untouched. Decision (g) of the component contract: the size of a board that hugs its content is the layout's, never an override. The layout reflow writes it as derived, and a transform touches only the shapes the user transformed (decision (f)), so a resize of a copy child that grows the board it sits in touches the child only. workspace-layout-owned-sizes-test pins it. Benchmark (lower quantile, ms; baseline / now / ratio); the machine was loaded, and the code before this change, run in between, gave the same spread (0.44 to 0.59): deepest-edit-through-three-levels 35.2 / 16.7 / 0.47 edit-through-a-chain-of-dependents 68.9 / 33.4 / 0.48 library-update-into-copies 83.1 / 40.3 / 0.49 main-edit-among-many-shapes 4.8 / 2.4 / 0.49 main-edit-into-10-copies 1.8 / 1.0 / 0.54 main-edit-into-100-copies 15.4 / 8.8 / 0.57 main-edit-into-1000-copies 153.1 / 92.7 / 0.61 variant-switch-into-copies 32.1 / 19.6 / 0.61 AI-assisted-by: claude-opus-5-5
This commit is contained in:
parent
b57674b33c
commit
e0bbc67605
@ -235,7 +235,7 @@
|
||||
(ctm/move-modifiers (displacement center-before center-after before-side-vector after-side-vector))))
|
||||
|
||||
(defmethod constraint-modifier :default [_ _ _ _ _ _]
|
||||
[])
|
||||
(ctm/empty))
|
||||
|
||||
(def const->type+axis
|
||||
{:left :start
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[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.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
@ -309,6 +310,51 @@
|
||||
(-> (set-touched shape-inst container nil)
|
||||
(change-remote-synced shape-inst container nil)))))
|
||||
|
||||
(defn- in-main-frame
|
||||
"The copy shape `shape-inst` expressed in the frame of the main, as
|
||||
`reposition-shape` would place it from there: turned back by the roots'
|
||||
relative rotation and flips when the copy root has them, and moved by the
|
||||
roots' offset."
|
||||
[shape-inst root-inst root-main]
|
||||
(let [turn (when (ctk/touched-group? root-inst :geometry-group)
|
||||
(orient-like root-inst root-main (grc/rect->center (:selrect root-inst))))
|
||||
shape (cond-> shape-inst (some? turn) turn)
|
||||
delta (gpt/subtract (gpt/point (-> root-main :selrect :x) (-> root-main :selrect :y))
|
||||
(gpt/point (-> root-inst :selrect :x) (-> root-inst :selrect :y)))]
|
||||
(gsh/move shape delta)))
|
||||
|
||||
(defn- close-geometry?
|
||||
[shape other]
|
||||
(and (gsh/close-attrs? :selrect (:selrect shape) (:selrect other))
|
||||
(gsh/close-attrs? :points (:points shape) (:points other))))
|
||||
|
||||
(defn- placed-like
|
||||
"The main `objects` with the subtree of `shape-main` placed like `placed`,
|
||||
a copy shape expressed in the main's frame: resized to its size, its
|
||||
children following through their constraints and layouts, then turned
|
||||
and moved with it as one piece."
|
||||
[objects shape-main placed]
|
||||
(let [width (-> placed :selrect :width)
|
||||
height (-> placed :selrect :height)
|
||||
objects (if (and (mth/close? width (-> shape-main :selrect :width))
|
||||
(mth/close? height (-> shape-main :selrect :height)))
|
||||
objects
|
||||
(let [modif-tree (gm/set-objects-modifiers
|
||||
{(:id shape-main) {:modifiers (ctm/change-size shape-main width height)}}
|
||||
objects)]
|
||||
(reduce-kv (fn [objects id {:keys [modifiers]}]
|
||||
(d/update-when objects id gsh/transform-shape modifiers))
|
||||
objects
|
||||
modif-tree)))
|
||||
ids (cfh/get-children-ids-with-self objects (:id shape-main))
|
||||
resized (get objects (:id shape-main))
|
||||
turn (orient-like resized placed (grc/rect->center (:selrect resized)))
|
||||
objects (cond-> objects
|
||||
(some? turn) (as-> objects (reduce #(update %1 %2 turn) objects ids)))
|
||||
delta (gpt/subtract (grc/rect->center (:selrect placed))
|
||||
(grc/rect->center (:selrect (get objects (:id shape-main)))))]
|
||||
(reduce #(update %1 %2 gsh/move delta) objects ids)))
|
||||
|
||||
(defn- sync-copy-recursive
|
||||
"Sync the copy shape `shape-inst` from `shape-main`, the main shape it
|
||||
pairs with, then pair their children (`compare-children`) and walk each
|
||||
@ -317,7 +363,9 @@
|
||||
overrides and clears them. `ctx` holds what stays the same along the
|
||||
walk: the copy's `:container`, the `:main-container` of the main shapes,
|
||||
the `:component` and its `:library`, the roots `:root-inst` and
|
||||
`:root-main`, and `:reset?`."
|
||||
`:root-main`, and `:reset?`. The main shapes of the children of a copy
|
||||
shape that keeps its own geometry are placed like it first, so they get
|
||||
their expected geometry."
|
||||
[changes {:keys [container main-container library component reset?] :as ctx} shape-inst shape-main]
|
||||
(shape-log :debug (:id shape-inst) container
|
||||
:msg "Sync shape direct recursive"
|
||||
@ -326,8 +374,16 @@
|
||||
(if (nil? shape-main)
|
||||
;; This should not occur, but protect against it in any case
|
||||
changes
|
||||
(let [children-inst (vec (ctn/get-direct-children container shape-inst))
|
||||
children-main (vec (ctn/get-direct-children main-container shape-main))]
|
||||
(let [;; a copy shape that keeps its own geometry passes it on to its
|
||||
;; children, which follow it as they follow their main's
|
||||
placed (when (and (not reset?) (ctk/touched-group? shape-inst :geometry-group))
|
||||
(in-main-frame shape-inst (:root-inst ctx) (:root-main ctx)))
|
||||
main-container (cond-> main-container
|
||||
(and (some? placed) (not (close-geometry? placed shape-main)))
|
||||
(update :objects placed-like shape-main placed))
|
||||
ctx (assoc ctx :main-container main-container)
|
||||
children-inst (vec (ctn/get-direct-children container shape-inst))
|
||||
children-main (vec (ctn/get-direct-children main-container shape-main))]
|
||||
(cond-> (-> changes
|
||||
(sync-copy-shape ctx shape-inst shape-main)
|
||||
(compare-children
|
||||
|
||||
@ -148,63 +148,69 @@
|
||||
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)
|
||||
`ids` among itself and its ancestors. With `user-ids`, the shapes the
|
||||
user transformed, every other shape changes only as a consequence of
|
||||
theirs (a child through its constraints, a board that hugs them), a
|
||||
derived change that leaves it untouched."
|
||||
([objects ids transform]
|
||||
(calculate-ignore-tree objects ids transform nil))
|
||||
([objects ids transform user-ids]
|
||||
(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))
|
||||
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)))))
|
||||
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
|
||||
id (dm/get-prop shape :id)]
|
||||
(assoc ignore-tree id
|
||||
(or (and (some? user-ids) (not (contains? user-ids 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 (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))))
|
||||
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
|
||||
|
||||
@ -89,9 +89,11 @@
|
||||
[changes ids update-fn objects {:keys [attrs changed-sub-attr changed-item-index ignore-tree ignore-touched with-objects? translation? skip-grid-reassignment?]}]
|
||||
(let [changes (reduce
|
||||
(fn [changes id]
|
||||
;; a shape the transform leaves untouched changes only
|
||||
;; as a consequence of it: no attribute touches
|
||||
(let [opts {:attrs attrs
|
||||
:ignore-geometry? (get ignore-tree id)
|
||||
:ignore-touched ignore-touched
|
||||
:ignore-touched (or (true? (get ignore-tree id)) ignore-touched)
|
||||
:with-objects? with-objects?}]
|
||||
(pcb/update-shapes changes [id] update-fn (d/without-nils opts))))
|
||||
(cond-> changes
|
||||
|
||||
@ -7,21 +7,19 @@
|
||||
(ns common-tests.geom-shapes-constraints-test
|
||||
(:require
|
||||
[app.common.geom.shapes.constraints :as gsc]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[clojure.test :as t]))
|
||||
|
||||
;; ---- constraint-modifier :default ----
|
||||
|
||||
(t/deftest constraint-modifier-default-returns-empty-vector
|
||||
(t/testing ":default method accepts 6 args and returns an empty vector"
|
||||
;; Before the fix the :default method only accepted 5 positional args
|
||||
;; (plus the dispatch value), so calling it with 6 args would throw an
|
||||
;; arity error. After the fix it takes [_ _ _ _ _ _] and returns [].
|
||||
(t/deftest constraint-modifier-default-returns-empty-modifiers
|
||||
(t/testing ":default method accepts 6 args and returns empty modifiers"
|
||||
;; :scale and any other type with no method of its own add nothing; the
|
||||
;; caller adds the result to the child's modifiers (`ctm/add-modifiers`)
|
||||
(let [result (gsc/constraint-modifier :unknown-constraint-type
|
||||
:x nil nil nil nil)]
|
||||
(t/is (vector? result))
|
||||
(t/is (empty? result))))
|
||||
(t/is (ctm/empty? result))))
|
||||
|
||||
(t/testing ":default method returns [] for :scale-like unknown type on :y axis"
|
||||
(let [result (gsc/constraint-modifier :some-other-unknown
|
||||
:y nil nil nil nil)]
|
||||
(t/is (= [] result)))))
|
||||
(t/testing ":default method can be added to modifiers"
|
||||
(let [result (gsc/constraint-modifier :scale :y nil nil nil nil)]
|
||||
(t/is (ctm/empty? (ctm/add-modifiers (ctm/empty) result))))))
|
||||
|
||||
114
common/test/common_tests/logic/comp_expected_geometry_test.cljc
Normal file
114
common/test/common_tests/logic/comp_expected_geometry_test.cljc
Normal file
@ -0,0 +1,114 @@
|
||||
;; 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.comp-expected-geometry-test
|
||||
"A copy whose root (or another shape) keeps a geometry of its own, and the
|
||||
children of that shape: a transform of the shape leaves them untouched,
|
||||
and sync gives them their main's geometry placed against it, resized
|
||||
through their constraints (decision (f) of the component contract)."
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.modifiers :as gm]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.placement :as clp]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.math :as mth]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(defn- component-with-child
|
||||
"A 100x100 main board with a 20x20 rect at (70, 10) of it, constrained
|
||||
`constraints-h`, and a copy of it at (300, 0)."
|
||||
[constraints-h]
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame :main-root {:x 0 :y 0 :width 100 :height 100})
|
||||
(tho/add-rect :main-child {:parent-label :main-root :x 70 :y 10 :width 20 :height 20
|
||||
:constraints-h constraints-h :constraints-v :top})
|
||||
(thc/make-component :component1 :main-root)
|
||||
(thc/instantiate-component :component1 :copy-root :children-labels [:copy-child])))
|
||||
|
||||
(defn- resize
|
||||
"Resize the shape `label` to `width` x `height`, as the editor does: the
|
||||
modifiers propagate to its children through their constraints, and the
|
||||
placement step decides what the transform touches. With `scale-content`,
|
||||
strokes, radii and the like scale by it too."
|
||||
[file label width height & {:keys [scale-content]}]
|
||||
(let [page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
shape (ths/get-shape file label)
|
||||
modifiers (cond-> (ctm/change-size shape width height)
|
||||
(some? scale-content) (ctm/scale-content scale-content))
|
||||
modif-tree (gm/set-objects-modifiers {(:id shape) {:modifiers modifiers}}
|
||||
objects)
|
||||
ids (keys modif-tree)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
ids
|
||||
(clp/modifiers-transform modif-tree)
|
||||
objects
|
||||
{:ignore-tree (clp/calculate-ignore-tree objects ids
|
||||
(clp/modifiers-transform modif-tree)
|
||||
#{(:id shape)})
|
||||
:attrs clp/transform-attrs})]
|
||||
(thf/apply-changes file changes)))
|
||||
|
||||
(defn- edit-and-sync
|
||||
"Change the fill of the main child and sync the copy."
|
||||
[file]
|
||||
(let [page (thf/current-page file)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(thi/id :main-child)}
|
||||
#(assoc % :fills (ths/sample-fills-color :fill-color "#ff0000"))
|
||||
(:objects page)
|
||||
{})
|
||||
file (thf/apply-changes file changes)]
|
||||
(thf/apply-changes file (cll/generate-sync-file-changes (pcb/empty-changes) nil :components (:id file)
|
||||
(thi/id :component1) (:id file)
|
||||
{(:id file) file} (:id file)))))
|
||||
|
||||
(defn- child-box
|
||||
"The position of the copy child relative to its root, and its size."
|
||||
[file]
|
||||
(let [root (ths/get-shape file :copy-root)
|
||||
child (ths/get-shape file :copy-child)]
|
||||
(mapv #(mth/round (double %))
|
||||
[(- (-> child :selrect :x) (-> root :selrect :x))
|
||||
(- (-> child :selrect :y) (-> root :selrect :y))
|
||||
(-> child :selrect :width)
|
||||
(-> child :selrect :height)])))
|
||||
|
||||
(t/deftest a-resized-copy-root-keeps-its-children-placed-through-their-constraints
|
||||
(doseq [[constraints-h box] [[:left [70 10 20 20]]
|
||||
[:right [170 10 20 20]]
|
||||
[:center [120 10 20 20]]
|
||||
[:scale [140 10 40 20]]
|
||||
[:leftright [70 10 120 20]]]]
|
||||
(t/testing (name constraints-h)
|
||||
(let [resized (resize (component-with-child constraints-h) :copy-root 200 100)
|
||||
synced (edit-and-sync resized)]
|
||||
(t/is (= box (child-box resized)) "the resize places the child")
|
||||
(t/is (contains? (:touched (ths/get-shape resized :copy-root)) :geometry-group))
|
||||
(t/is (nil? (:touched (ths/get-shape resized :copy-child))) "the child is not touched")
|
||||
(t/is (= box (child-box synced)) "the sync keeps it there")
|
||||
(t/is (= "#ff0000" (-> (ths/get-shape synced :copy-child) :fills first :fill-color)))))))
|
||||
|
||||
(t/deftest a-copy-root-resized-with-its-content-touches-only-the-root
|
||||
(let [file (-> (component-with-child :scale)
|
||||
(ths/update-shape :main-child :strokes [{:stroke-color "#000000" :stroke-opacity 1
|
||||
:stroke-width 2 :stroke-alignment :center
|
||||
:stroke-style :solid}])
|
||||
(thc/instantiate-component :component1 :copy2-root :children-labels [:copy2-child]))
|
||||
resized (resize file :copy2-root 200 200 :scale-content 2)
|
||||
child (ths/get-shape resized :copy2-child)]
|
||||
(t/is (mth/close? 4 (-> child :strokes first :stroke-width)))
|
||||
(t/is (nil? (:touched child)))))
|
||||
|
||||
@ -140,7 +140,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(dissoc :workspace-modifiers :wasm-props :prev-wasm-props)
|
||||
(dissoc :workspace-modifiers :workspace-modifiers-ids :wasm-props :prev-wasm-props)
|
||||
(dissoc :app.main.data.workspace.transforms/current-move-selected)))))
|
||||
|
||||
(defn create-modif-tree
|
||||
@ -356,7 +356,9 @@
|
||||
(ptk/reify ::update-modifiers
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree)))))
|
||||
(-> state
|
||||
(update :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree)
|
||||
(update :workspace-modifiers-ids (fnil into #{}) (keys modif-tree)))))))
|
||||
|
||||
|
||||
(defn set-modifiers
|
||||
@ -375,7 +377,9 @@
|
||||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
|
||||
(assoc state :workspace-modifiers modifiers))))))
|
||||
(assoc state
|
||||
:workspace-modifiers modifiers
|
||||
:workspace-modifiers-ids (set (keys modif-tree))))))))
|
||||
|
||||
(defn- without-nil-ids
|
||||
"Drop nil-keyed entries from a modif-tree. A nil shape id (possible in
|
||||
@ -682,7 +686,7 @@
|
||||
(clp/matrices-transform transforms)
|
||||
|
||||
ignore-tree
|
||||
(clp/calculate-ignore-tree objects (keys transforms) transform-shape)
|
||||
(clp/calculate-ignore-tree objects (keys transforms) transform-shape (set (keys modif-tree)))
|
||||
|
||||
;; the guides move in a commit of their own, in the undo
|
||||
;; group of the shapes, so the copies' sync joins them both
|
||||
@ -793,7 +797,9 @@
|
||||
(-> (build-modif-tree ids objects get-modifier)
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree))))))
|
||||
(assoc state
|
||||
:workspace-modifiers modif-tree
|
||||
:workspace-modifiers-ids (set ids)))))))
|
||||
|
||||
;; This function is similar to set-rotation-modifiers but:
|
||||
;; - It consideres the center for everyshape instead of the center of the total selrect
|
||||
@ -821,7 +827,9 @@
|
||||
(-> (build-modif-tree ids objects get-modifier)
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree)))))
|
||||
(assoc state
|
||||
:workspace-modifiers modif-tree
|
||||
:workspace-modifiers-ids (set ids))))))
|
||||
|
||||
(defn apply-modifiers*
|
||||
"A lower-level version of apply-modifiers, that expects receive ready
|
||||
@ -840,7 +848,8 @@
|
||||
|
||||
ignore-tree
|
||||
(clp/calculate-ignore-tree objects (keys object-modifiers)
|
||||
(clp/modifiers-transform object-modifiers))
|
||||
(clp/modifiers-transform object-modifiers)
|
||||
(:user-ids options))
|
||||
|
||||
;; the guides move in a commit of their own, in the undo group of
|
||||
;; the shapes, so the copies' sync joins them both
|
||||
@ -894,6 +903,13 @@
|
||||
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers page-id)
|
||||
(get state :workspace-modifiers))
|
||||
|
||||
;; the shapes the user transformed: those of the given
|
||||
;; modifiers, or those the stored ones were set for
|
||||
user-ids
|
||||
(if (some? modifiers)
|
||||
(set (keys modifiers))
|
||||
(get state :workspace-modifiers-ids))
|
||||
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
|
||||
@ -901,7 +917,7 @@
|
||||
(if undo-transation?
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
(rx/empty))
|
||||
(rx/of (apply-modifiers* objects object-modifiers text-modifiers options)
|
||||
(rx/of (apply-modifiers* objects object-modifiers text-modifiers (assoc options :user-ids user-ids))
|
||||
(fn [state]
|
||||
(let [ids (into [] xf:without-uuid-zero (keys object-modifiers))]
|
||||
(update state :workspace-text-modifier #(apply dissoc % ids)))))
|
||||
|
||||
@ -563,7 +563,7 @@
|
||||
(rx/of (dwm/apply-wasm-modifiers modif-tree (assoc options :ignore-snap-pixel true)))
|
||||
|
||||
(let [modif-tree (gm/set-objects-modifiers modif-tree objects)]
|
||||
(rx/of (dwm/apply-modifiers* objects modif-tree nil options)))))))))
|
||||
(rx/of (dwm/apply-modifiers* objects modif-tree nil (assoc options :user-ids (set ids)))))))))))
|
||||
|
||||
(defn change-orientation
|
||||
"Change orientation of shapes, from the sidebar options form.
|
||||
@ -590,7 +590,9 @@
|
||||
(-> (dwm/build-modif-tree ids objects get-modifier)
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree))))
|
||||
(assoc state
|
||||
:workspace-modifiers modif-tree
|
||||
:workspace-modifiers-ids (set ids)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
{:transcript "one-of -> \n {:target :copy-sub1}\n one-of -> ", :states ["fff37cf722a40e61" "6fd3a8bad999968b"]}
|
||||
{:transcript "one-of -> \n {:target :copy-sub3}\n {:target :copy-root, :value 500}", :states ["efada86db353920f" "701edc575ad9c0c6" "91db7f5306659911"]}
|
||||
{:transcript "one-of -> \n {:target :copy-sub3}\n one-of -> ", :states ["efada86db353920f" "3364b0fbfb76067a"]}
|
||||
{:transcript "one-of -> \n {:target :copy-root, :value 500}", :states ["df9a66ef7c8ee469" "c4f0c92f92575cda"]}
|
||||
{:transcript "one-of -> \n {:target :copy-root, :value 500}", :states ["000943ecb6c266ee" "209fe67d76a27a9b"]}
|
||||
{:transcript "one-of -> \n one-of -> ", :states ["94318a628db305b6"]}]
|
||||
:case-aj-reflow-after-nested-sync-is-its-own-undo-step
|
||||
[{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :property :fills, :value \"#0000ff\"}\n {:times 1}", :states ["43ca8b2de80a51fc" "32b45a5ac7536b08" "7104bfaccd42cd3b"]}]
|
||||
@ -194,7 +194,7 @@
|
||||
{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :property :fills, :value \"#00ff00\"}\n {:target #fn, :new-head 8}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["170f05fe60d794fb" "6f95a0e52dbc58f3" "1682e155232c79ff" "7e41824902c17537"]}
|
||||
{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n {:component \"main\", :copy sync-main-copyhead-0}\n one-of -> \n {:target #fn, :new-head 8}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["e09ce2b9958878d1" "59617d71e336c70c" "4f37d9c4a6c72163"]}]
|
||||
:case-touched-copy-child-survives-main-rotation
|
||||
[{:transcript " {:target :copy-root, :angle 30}\n {:target :copy-instance, :value 60}\n \n {:target :main-root, :angle 40}", :states ["6b63805de28b6fa4" "cfb4d7484a4abd78" "f4cf8a16c5457089" "fa0aab3b284e3448"]}]
|
||||
[{:transcript " {:target :copy-root, :angle 30}\n {:target :copy-instance, :value 60}\n \n {:target :main-root, :angle 40}", :states ["4b1199b00726b9ad" "cfb4d7484a4abd78" "f4cf8a16c5457089" "fa0aab3b284e3448"]}]
|
||||
:case-u-nested-copy-reaches-copies-of-the-receiving-main
|
||||
[{:transcript " {:component \"outer\", :id sync-outer-component}\n {:component \"inner\", :id sync-inner-component}\n {:component \"outer\", :copy sync-outer-copyhead-0}\n {:component \"inner\", :copy sync-inner-copyhead-0}\n {:copy #fn, :into #fn, :cell nil}\n {:target #fn, :property :fills, :value \"#00ff00\"}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["3e741919b165a9b1" "3b36d2a788757ccf" "13cc7bde13f6db61" "d2df2363c6fd87ee"]}
|
||||
{:transcript " {:component \"outer\", :id sync-outer-component}\n {:component \"inner\", :id sync-inner-component}\n {:component \"outer\", :copy sync-outer-copyhead-0}\n {:component \"inner\", :copy sync-inner-copyhead-0}\n {:copy #fn, :into #fn, :cell nil}\n one-of -> \n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["3e741919b165a9b1" "20ac8b615e23f0ea" "3b80647668a43256"]}]
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
;; 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.data.workspace-layout-owned-sizes-test
|
||||
"The size of a board that hugs its content follows the layout: a resize
|
||||
of a child in a copy touches the child, not the board."
|
||||
(:require
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.composable-tests.interpreter :as ftm]))
|
||||
|
||||
(t/use-fixtures :each ftm/fixture)
|
||||
|
||||
(defn- hug-component-with-copy
|
||||
"A component whose root has a flex layout (row) and hugs its 50x50
|
||||
child, and a copy of it."
|
||||
[]
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy
|
||||
:component1 :main-root :main-child :copy-root
|
||||
:main-root-params {:layout :flex :layout-flex-dir :row
|
||||
:layout-item-h-sizing :auto
|
||||
:layout-item-v-sizing :auto
|
||||
:width 50 :height 50}
|
||||
:main-child-params {:width 50 :height 50}
|
||||
:copy-root-params {:children-labels [:copy-child]}))]
|
||||
(tm/make-situation file)))
|
||||
|
||||
(t/deftest ^:async a-resize-of-a-copy-child-leaves-its-hugging-board-untouched
|
||||
(ftm/install! (hug-component-with-copy))
|
||||
(await (ftm/await-step [(dwt/update-dimensions [(thi/id :copy-child)] :width 80)]))
|
||||
(let [file (ftm/current-file)
|
||||
child (ths/get-shape file :copy-child)
|
||||
root (ths/get-shape file :copy-root)]
|
||||
(t/is (= 80 (:width child)))
|
||||
(t/is (= 80 (:width root)))
|
||||
(t/is (contains? (:touched child) :geometry-group))
|
||||
(t/is (not (contains? (:touched root) :geometry-group)))))
|
||||
@ -35,6 +35,7 @@
|
||||
[frontend-tests.data.workspace-font-variants-test]
|
||||
[frontend-tests.data.workspace-frame-guides-undo-test]
|
||||
[frontend-tests.data.workspace-interactions-test]
|
||||
[frontend-tests.data.workspace-layout-owned-sizes-test]
|
||||
[frontend-tests.data.workspace-libraries-test]
|
||||
[frontend-tests.data.workspace-library-links-test]
|
||||
[frontend-tests.data.workspace-library-updates-test]
|
||||
@ -205,6 +206,7 @@
|
||||
'frontend-tests.data.workspace-context-menu-test
|
||||
'frontend-tests.data.workspace-edit-watchers-test
|
||||
'frontend-tests.data.workspace-interactions-test
|
||||
'frontend-tests.data.workspace-layout-owned-sizes-test
|
||||
'frontend-tests.data.workspace-font-variants-test
|
||||
'frontend-tests.data.workspace-frame-guides-undo-test
|
||||
'frontend-tests.data.workspace-text-modifier-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user