mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
🐛 Compose child flip flags through parent placement
Keep the source and parent-frame flip composition when placing component content through a rotated parent. A fresh large matrix turn can otherwise set both child flip flags even though the user only rotated (F170). Preserve nil/false representation when the parent does not flip an axis. The portable regression fails two flag assertions before the fix. Eight geometry tests pass 61 assertions, the original library shrink passes, and the pure golden master matches all 1149 assertions without recording. Common and frontend lint and format pass. Benchmark ratios against Phase 1e: main 10/100/1000 copies 0.58/0.74/0.57, three levels 0.49, chain 0.49, library 0.52, many shapes 0.47, variant switch 0.61. No regression flags. AI-assisted-by: GPT-6
This commit is contained in:
parent
3e36275f96
commit
1e9d19b214
@ -20,6 +20,14 @@ After a geometric mutation, equivalent fields such as `:y`, `(:y :selrect)`, and
|
||||
- `gsh/transform-shape`: applies a full transform.
|
||||
- `cts/setup-shape`: initializes geometry for new shapes; variant test helpers such as `thv/add-variant-with-child` use it.
|
||||
|
||||
## Flip flags through parent placement
|
||||
|
||||
When composing a parent's orientation onto content, compose flip flags from
|
||||
the source shape and the origin/target parent frames. Matrix decomposition
|
||||
can set both flags during a large rotation, even when several smaller
|
||||
rotations authored neither flip (F170). Keep the original nil/false value
|
||||
when the parent frames do not change that axis's flip.
|
||||
|
||||
## Edits that break the invariant
|
||||
|
||||
- `(assoc shape :x ...)` or `(assoc shape :y ...)`: updates only one field and leaves `:selrect` / `:points` stale.
|
||||
@ -45,4 +53,4 @@ Using `(ths/update-shape file label :y 101)` leaves `:selrect.y` stale. Downstre
|
||||
|
||||
When a copy shape changes geometry through the proper pipeline (`set-shape-attr` via `process-operation :set`), `:touched` gains `:geometry-group` unless ignored. Tests can either drive the production update with `cls/generate-update-shapes`, or inject `(assoc shape :touched #{:geometry-group})` when only touched state matters.
|
||||
|
||||
If a test needs both a new position and touched state, move the shape first with geometry-preserving helpers, then inject or assert touched state.
|
||||
If a test needs both a new position and touched state, move the shape first with geometry-preserving helpers, then inject or assert touched state.
|
||||
|
||||
@ -276,10 +276,16 @@
|
||||
main-container
|
||||
(let [transform (gmt/transform-in (gsh/shape->center shape-main) relative)
|
||||
angle (- (or (:rotation target) 0) (or (:rotation origin) 0))
|
||||
turn #(-> %
|
||||
(assoc-in [:modifiers :rotation] angle)
|
||||
(gsh/apply-transform transform)
|
||||
(dissoc :modifiers))]
|
||||
flip (fn [shape key]
|
||||
(cond-> (get shape key)
|
||||
(not= (boolean (get origin key)) (boolean (get target key))) not))
|
||||
turn (fn [shape]
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :rotation] angle)
|
||||
(gsh/apply-transform transform)
|
||||
(dissoc :modifiers)
|
||||
(assoc :flip-x (flip shape :flip-x)
|
||||
:flip-y (flip shape :flip-y))))]
|
||||
(update main-container :objects
|
||||
(fn [objects]
|
||||
(reduce #(update %1 %2 turn) objects
|
||||
|
||||
@ -213,3 +213,18 @@
|
||||
after (get-in (thf/current-page synced) [:objects (:id child)])]
|
||||
(t/is (= (select-keys child [:flip-x :flip-y]) (select-keys after [:flip-x :flip-y])))
|
||||
(t/is (gsh/close-attrs? :points (:points child) (:points after) 0.001))))
|
||||
|
||||
(t/deftest update-main-keeps-child-flips-when-syncing-through-a-rotated-parent
|
||||
(let [updated (-> (setup/nested-component-with-copy)
|
||||
(r/step (n/rotate :copy-root 45)) r/settle
|
||||
(r/step (n/rotate :copy-root 90)) r/settle
|
||||
(r/step (n/update-main :copy-root)) r/settle)
|
||||
file (tm/file updated)
|
||||
main (tm/resolve-shape updated :main2-nested-head)
|
||||
child (tm/shape-by-id updated (first (:shapes main)))
|
||||
synced (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))]
|
||||
(doseq [shape [main child]]
|
||||
(let [after (ths/get-shape-by-id synced (:id shape))
|
||||
flips #(mapv (comp boolean %) [:flip-x :flip-y])]
|
||||
(t/is (= (flips shape) (flips after)))
|
||||
(t/is (gsh/close-attrs? :points (:points shape) (:points after) 0.001))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user