diff --git a/CHANGES.md b/CHANGES.md
index 5238ce23a..8c3d7ac4f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,8 +4,11 @@
### :boom: Breaking changes & Deprecations
### :sparkles: New features
+
+- Adds layout flex functionality for boards
- Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386)
- Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475)
+
### :bug: Bugs fixed
- Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218)
diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc
index e4448acb7..b8145090f 100644
--- a/common/src/app/common/geom/matrix.cljc
+++ b/common/src/app/common/geom/matrix.cljc
@@ -252,3 +252,21 @@
(update :d mth/precision 4)
(update :e mth/precision 4)
(update :f mth/precision 4)))
+
+(defn transform-point-center
+ "Transform a point around the shape center"
+ [point center matrix]
+ (if (and (some? point) (some? matrix) (some? center))
+ (gpt/transform
+ point
+ (multiply (translate-matrix center)
+ matrix
+ (translate-matrix (gpt/negate center))))
+ point))
+
+(defn move?
+ [{:keys [a b c d _ _]}]
+ (and (mth/almost-zero? (- a 1))
+ (mth/almost-zero? b)
+ (mth/almost-zero? c)
+ (mth/almost-zero? (- d 1))))
diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc
index 9ce97f8af..5b17160d9 100644
--- a/common/src/app/common/geom/point.cljc
+++ b/common/src/app/common/geom/point.cljc
@@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.geom.point
- (:refer-clojure :exclude [divide min max])
+ (:refer-clojure :exclude [divide min max abs])
(:require
#?(:cljs [cljs.pprint :as pp]
:clj [clojure.pprint :as pp])
@@ -328,6 +328,13 @@
(update :x #(if (mth/almost-zero? %) 0.001 %))
(update :y #(if (mth/almost-zero? %) 0.001 %))))
+
+(defn abs
+ [point]
+ (-> point
+ (update :x mth/abs)
+ (update :y mth/abs)))
+
;; --- Debug
(defmethod pp/simple-dispatch Point [obj] (pr obj))
diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc
index d0d05d47f..54c07a570 100644
--- a/common/src/app/common/geom/shapes.cljc
+++ b/common/src/app/common/geom/shapes.cljc
@@ -14,7 +14,6 @@
[app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.corners :as gsc]
[app.common.geom.shapes.intersect :as gin]
- [app.common.geom.shapes.layout :as gcl]
[app.common.geom.shapes.modifiers :as gsm]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr]
@@ -28,7 +27,7 @@
rotation of each shape. Mainly used for multiple selection."
[shapes]
(->> shapes
- (map (comp gpr/points->selrect :points gtr/transform-shape))
+ (map (comp gpr/points->selrect :points))
(gpr/join-selrects)))
(defn translate-to-frame
@@ -160,37 +159,31 @@
(dm/export gpr/join-rects)
(dm/export gpr/join-selrects)
(dm/export gpr/contains-selrect?)
+(dm/export gpr/contains-point?)
+(dm/export gpr/close-selrect?)
(dm/export gtr/move)
(dm/export gtr/absolute-move)
(dm/export gtr/transform-matrix)
(dm/export gtr/transform-str)
(dm/export gtr/inverse-transform-matrix)
-(dm/export gtr/transform-point-center)
(dm/export gtr/transform-rect)
(dm/export gtr/calculate-adjust-matrix)
(dm/export gtr/update-group-selrect)
(dm/export gtr/update-mask-selrect)
-(dm/export gtr/resize-modifiers)
-(dm/export gtr/change-orientation-modifiers)
-(dm/export gtr/rotation-modifiers)
-(dm/export gtr/merge-modifiers)
+(dm/export gtr/update-bool-selrect)
(dm/export gtr/transform-shape)
(dm/export gtr/transform-selrect)
(dm/export gtr/transform-selrect-matrix)
(dm/export gtr/transform-bounds)
-(dm/export gtr/modifiers->transform)
-(dm/export gtr/empty-modifiers?)
(dm/export gtr/move-position-data)
-(dm/export gtr/apply-transform)
+(dm/export gtr/apply-objects-modifiers)
+(dm/export gtr/parent-coords-rect)
+(dm/export gtr/parent-coords-points)
;; Constratins
(dm/export gct/calc-child-modifiers)
-;; Layout
-(dm/export gcl/calc-layout-data)
-(dm/export gcl/calc-layout-modifiers)
-
;; PATHS
(dm/export gsp/content->selrect)
(dm/export gsp/transform-content)
@@ -203,7 +196,7 @@
(dm/export gin/rect-contains-shape?)
;; Bool
-(dm/export gsb/update-bool-selrect)
+
(dm/export gsb/calc-bool-content)
;; Constraints
@@ -216,3 +209,4 @@
;; Modifiers
(dm/export gsm/set-objects-modifiers)
+
diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc
index f404e680c..a3a464544 100644
--- a/common/src/app/common/geom/shapes/bool.cljc
+++ b/common/src/app/common/geom/shapes/bool.cljc
@@ -7,8 +7,6 @@
(ns app.common.geom.shapes.bool
(:require
[app.common.data :as d]
- [app.common.geom.shapes.path :as gsp]
- [app.common.geom.shapes.transforms :as gtr]
[app.common.path.bool :as pb]
[app.common.path.shapes-to-path :as stp]))
@@ -25,17 +23,5 @@
(into [] extract-content-xf (:shapes shape))]
(pb/content-bool (:bool-type shape) shapes-content)))
-(defn update-bool-selrect
- "Calculates the selrect+points for the boolean shape"
- [shape children objects]
- (let [bool-content (calc-bool-content shape objects)
- shape (assoc shape :bool-content bool-content)
- [points selrect] (gsp/content->points+selrect shape bool-content)]
-
- (if (and (some? selrect) (d/not-empty? points))
- (-> shape
- (assoc :selrect selrect)
- (assoc :points points))
- (gtr/update-group-selrect shape children))))
diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc
index 8cb899f04..710c15e78 100644
--- a/common/src/app/common/geom/shapes/common.cljc
+++ b/common/src/app/common/geom/shapes/common.cljc
@@ -8,7 +8,8 @@
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
- [app.common.geom.point :as gpt]))
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.rect :as gpr]))
(defn center-rect
[{:keys [x y width height]}]
@@ -31,6 +32,22 @@
(gpt/point (/ (+ minx maxx) 2.0)
(/ (+ miny maxy) 2.0))))
+(defn center-bounds [[a b c d]]
+ (let [xa (:x a)
+ ya (:y a)
+ xb (:x b)
+ yb (:y b)
+ xc (:x c)
+ yc (:y c)
+ xd (:x d)
+ yd (:y d)
+ minx (min xa xb xc xd)
+ miny (min ya yb yc yd)
+ maxx (max xa xb xc xd)
+ maxy (max ya yb yc yd)]
+ (gpt/point (/ (+ minx maxx) 2.0)
+ (/ (+ miny maxy) 2.0))))
+
(defn center-shape
"Calculate the center of the shape."
[shape]
@@ -49,3 +66,8 @@
(gpt/transform point (gmt/multiply prev matrix post)))]
(mapv tr-point points))
points)))
+
+(defn transform-selrect
+ [{:keys [x1 y1 x2 y2] :as sr} matrix]
+ (let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)]
+ (gpr/corners->selrect c1 c2)))
diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc
index 79821b364..2ced1cce1 100644
--- a/common/src/app/common/geom/shapes/constraints.cljc
+++ b/common/src/app/common/geom/shapes/constraints.cljc
@@ -6,14 +6,20 @@
(ns app.common.geom.shapes.constraints
(:require
- [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
+ [app.common.geom.shapes.intersect :as gsi]
+ [app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.rect :as gre]
+ [app.common.geom.shapes.transforms :as gst]
[app.common.math :as mth]
+ [app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
;; Auxiliary methods to work in an specifica axis
+(defn other-axis [axis]
+ (if (= :x axis) :y :x))
+
(defn get-delta-start [axis rect tr-rect]
(if (= :x axis)
(- (:x1 tr-rect) (:x1 rect))
@@ -29,6 +35,11 @@
(- (:width tr-rect) (:width rect))
(- (:height tr-rect) (:height rect))))
+(defn get-delta-scale [axis rect tr-rect]
+ (if (= :x axis)
+ (/ (:width tr-rect) (:width rect))
+ (/ (:height tr-rect) (:height rect))))
+
(defn get-delta-center [axis center tr-center]
(if (= :x axis)
(- (:x tr-center) (:x center))
@@ -53,78 +64,160 @@
(:width rect)
(:height rect)))
+(defn right-vector
+ [child-points parent-points]
+ (let [[p0 p1 p2 _] parent-points
+ [_c0 c1 _ _] child-points
+ dir-v (gpt/to-vec p0 p1)
+ cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1 p2)]
+ (gpt/to-vec c1 cp)))
+
+(defn left-vector
+ [child-points parent-points]
+
+ (let [[p0 p1 _ p3] parent-points
+ [_ _ _ c3] child-points
+ dir-v (gpt/to-vec p0 p1)
+ cp (gsi/line-line-intersect c3 (gpt/add c3 dir-v) p0 p3)]
+ (gpt/to-vec c3 cp)))
+
+(defn top-vector
+ [child-points parent-points]
+
+ (let [[p0 p1 _ p3] parent-points
+ [c0 _ _ _] child-points
+ dir-v (gpt/to-vec p0 p3)
+ cp (gsi/line-line-intersect c0 (gpt/add c0 dir-v) p0 p1)]
+ (gpt/to-vec c0 cp)))
+
+(defn bottom-vector
+ [child-points parent-points]
+
+ (let [[p0 _ p2 p3] parent-points
+ [_ _ c2 _] child-points
+ dir-v (gpt/to-vec p0 p3)
+ cp (gsi/line-line-intersect c2 (gpt/add c2 dir-v) p2 p3)]
+ (gpt/to-vec c2 cp)))
+
+(defn center-horizontal-vector
+ [child-points parent-points]
+
+ (let [[p0 p1 _ p3] parent-points
+ [_ c1 _ _] child-points
+
+ dir-v (gpt/to-vec p0 p1)
+
+ p1c (gpt/add p0 (gpt/scale dir-v 0.5))
+ p2c (gpt/add p3 (gpt/scale dir-v 0.5))
+
+ cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1c p2c)]
+
+ (gpt/to-vec c1 cp)))
+
+(defn center-vertical-vector
+ [child-points parent-points]
+ (let [[p0 p1 p2 _] parent-points
+ [_ c1 _ _] child-points
+
+ dir-v (gpt/to-vec p1 p2)
+
+ p3c (gpt/add p0 (gpt/scale dir-v 0.5))
+ p2c (gpt/add p1 (gpt/scale dir-v 0.5))
+
+ cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p3c p2c)]
+
+ (gpt/to-vec c1 cp)))
+
+(defn start-vector
+ [axis child-points parent-points]
+ (let [pos-vector
+ (cond (= :x axis) left-vector
+ (= :y axis) top-vector)]
+ (pos-vector child-points parent-points)))
+
+(defn end-vector
+ [axis child-points parent-points]
+ (let [pos-vector
+ (cond (= :x axis) right-vector
+ (= :y axis) bottom-vector)]
+ (pos-vector child-points parent-points)))
+
+(defn center-vector
+ [axis child-points parent-points]
+ ((if (= :x axis) center-horizontal-vector center-vertical-vector) child-points parent-points))
+
+(defn displacement
+ [before-v after-v]
+ (let [angl (gpt/angle-with-other before-v after-v)
+ sign (if (mth/close? angl 180) -1 1)
+ length (* sign (gpt/length before-v))]
+ (if (mth/almost-zero? length)
+ after-v
+ (gpt/subtract after-v (gpt/scale (gpt/unit after-v) length)))))
+
+(defn side-vector
+ [axis [c0 c1 _ c3]]
+ (if (= axis :x)
+ (gpt/to-vec c0 c1)
+ (gpt/to-vec c0 c3)))
+
+(defn side-vector-resize
+ [axis [c0 c1 _ c3] start-vector end-vector]
+ (if (= axis :x)
+ (gpt/to-vec (gpt/add c0 start-vector) (gpt/add c1 end-vector))
+ (gpt/to-vec (gpt/add c0 start-vector) (gpt/add c3 end-vector))))
+
;; Constraint function definitions
(defmulti constraint-modifier (fn [type & _] type))
(defmethod constraint-modifier :start
- [_ axis parent _ _ transformed-parent-rect]
-
- (let [parent-rect (:selrect parent)
- delta-start (get-delta-start axis parent-rect transformed-parent-rect)]
- (if-not (mth/almost-zero? delta-start)
- {:displacement (get-displacement axis delta-start)}
- {})))
+ [_ axis child-points-before parent-points-before child-points-after parent-points-after]
+ (let [start-before (start-vector axis child-points-before parent-points-before)
+ start-after (start-vector axis child-points-after parent-points-after)]
+ (ctm/move-modifiers (displacement start-before start-after))))
(defmethod constraint-modifier :end
- [_ axis parent _ _ transformed-parent-rect]
- (let [parent-rect (:selrect parent)
- delta-end (get-delta-end axis parent-rect transformed-parent-rect)]
- (if-not (mth/almost-zero? delta-end)
- {:displacement (get-displacement axis delta-end)}
- {})))
+ [_ axis child-points-before parent-points-before child-points-after parent-points-after]
+ (let [end-before (end-vector axis child-points-before parent-points-before)
+ end-after (end-vector axis child-points-after parent-points-after)]
+ (ctm/move-modifiers (displacement end-before end-after))))
(defmethod constraint-modifier :fixed
- [_ axis parent child _ transformed-parent-rect]
- (let [parent-rect (:selrect parent)
- child-rect (gre/points->rect (:points child))
+ [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent]
+ (let [;; Same as constraint end
+ end-before (end-vector axis child-points-before parent-points-before)
+ end-after (end-vector axis child-points-after parent-points-after)
+ start-before (start-vector axis child-points-before parent-points-before)
+ start-after (start-vector axis child-points-after parent-points-after)
- delta-start (get-delta-start axis parent-rect transformed-parent-rect)
- delta-size (get-delta-size axis parent-rect transformed-parent-rect)
- child-size (get-size axis child-rect)]
- (if (or (not (mth/almost-zero? delta-start))
- (not (mth/almost-zero? delta-size)))
+ disp-end (displacement end-before end-after)
+ disp-start (displacement start-before start-after)
- {:displacement (get-displacement axis delta-start)
- :resize-origin (get-displacement axis delta-start (:x child-rect) (:y child-rect))
- :resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))}
- {})))
+ ;; We get the current axis side and grow it on both side by the end+start displacements
+ before-vec (side-vector axis child-points-after)
+ after-vec (side-vector-resize axis child-points-after disp-start disp-end)
+
+ ;; after-vec will contain the side length of the grown side
+ ;; we scale the shape by the diference and translate it by the start
+ ;; displacement (so its left+top position is constant)
+ scale (/ (gpt/length after-vec) (gpt/length before-vec))
+
+ resize-origin (first child-points-after)
+ {:keys [transform transform-inverse]} transformed-parent]
+
+ (-> (ctm/empty)
+ (ctm/resize (get-scale axis scale) resize-origin transform transform-inverse)
+ (ctm/move disp-start))))
(defmethod constraint-modifier :center
- [_ axis parent _ _ transformed-parent-rect]
- (let [parent-rect (:selrect parent)
- parent-center (gco/center-rect parent-rect)
- transformed-parent-center (gco/center-rect transformed-parent-rect)
- delta-center (get-delta-center axis parent-center transformed-parent-center)]
- (if-not (mth/almost-zero? delta-center)
- {:displacement (get-displacement axis delta-center)}
- {})))
-
-(defmethod constraint-modifier :scale
- [_ axis _ _ modifiers _]
- (let [{:keys [resize-vector resize-vector-2 displacement]} modifiers]
- (cond-> {}
- (and (some? resize-vector)
- (not= (axis resize-vector) 1))
- (assoc :resize-origin (:resize-origin modifiers)
- :resize-vector (if (= :x axis)
- (gpt/point (:x resize-vector) 1)
- (gpt/point 1 (:y resize-vector))))
-
- (and (= :y axis) (some? resize-vector-2)
- (not (mth/close? (:y resize-vector-2) 1)))
- (assoc :resize-origin (:resize-origin-2 modifiers)
- :resize-vector (gpt/point 1 (:y resize-vector-2)))
-
- (some? displacement)
- (assoc :displacement
- (get-displacement axis (-> (gpt/point 0 0)
- (gpt/transform displacement)
- (gpt/transform (:resize-transform-inverse modifiers (gmt/matrix)))
- axis))))))
+ [_ axis child-points-before parent-points-before child-points-after parent-points-after]
+ (let [center-before (center-vector axis child-points-before parent-points-before)
+ center-after (center-vector axis child-points-after parent-points-after)]
+ (ctm/move-modifiers (displacement center-before center-after))))
(defmethod constraint-modifier :default [_ _ _ _ _]
- {})
+ [])
(def const->type+axis
{:left :start
@@ -152,77 +245,73 @@
:top
:scale)))
-(defn clean-modifiers
- "Remove redundant modifiers"
- [{:keys [displacement resize-vector resize-vector-2] :as modifiers}]
+(defn bounding-box-parent-transform
+ "Returns a bounding box for the child in the same coordinate system
+ as the parent.
+ Returns a points array"
+ [child parent]
+ (-> child
+ :points
+ (gco/transform-points (:transform-inverse parent))
+ (gre/points->rect)
+ (gre/rect->points) ;; Restore to points so we can transform them
+ (gco/transform-points (:transform parent))))
- (cond-> modifiers
- ;; Displacement with value 0. We don't move in any direction
- (and (some? displacement)
- (mth/almost-zero? (:e displacement))
- (mth/almost-zero? (:f displacement)))
- (dissoc :displacement)
+(defn normalize-modifiers
+ "Before aplying constraints we need to remove the deformation caused by the resizing of the parent"
+ [constraints-h constraints-v modifiers child parent transformed-child {:keys [transform transform-inverse] :as transformed-parent}]
- ;; Resize with value very close to 1 means no resize
- (and (some? resize-vector)
- (mth/almost-zero? (- 1.0 (:x resize-vector)))
- (mth/almost-zero? (- 1.0 (:y resize-vector))))
- (dissoc :resize-origin :resize-vector)
+ (let [child-bb-before (gst/parent-coords-rect child parent)
+ child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
+ scale-x (/ (:width child-bb-before) (:width child-bb-after))
+ scale-y (/ (:height child-bb-before) (:height child-bb-after))
+ resize-origin (-> transformed-parent :points gpo/origin)]
- (and (some? resize-vector)
- (mth/almost-zero? (- 1.0 (:x resize-vector-2)))
- (mth/almost-zero? (- 1.0 (:y resize-vector-2))))
- (dissoc :resize-origin-2 :resize-vector-2)))
+ (cond-> modifiers
+ (not= :scale constraints-h)
+ (ctm/resize (gpt/point scale-x 1) resize-origin transform transform-inverse)
+
+ (not= :scale constraints-v)
+ (ctm/resize (gpt/point 1 scale-y) resize-origin transform transform-inverse))))
(defn calc-child-modifiers
- [parent child modifiers ignore-constraints transformed-parent-rect]
+ [parent child modifiers ignore-constraints transformed-parent]
- (if (and (nil? (:resize-vector modifiers))
- (nil? (:resize-vector-2 modifiers)))
- ;; If we don't have a resize modifier we return the same modifiers
- modifiers
- (let [constraints-h
- (if-not ignore-constraints
- (:constraints-h child (default-constraints-h child))
- :scale)
+ (let [modifiers (ctm/select-child-modifiers modifiers)
- constraints-v
- (if-not ignore-constraints
- (:constraints-v child (default-constraints-v child))
- :scale)
+ constraints-h
+ (if-not ignore-constraints
+ (:constraints-h child (default-constraints-h child))
+ :scale)
- modifiers-h (constraint-modifier (constraints-h const->type+axis) :x parent child modifiers transformed-parent-rect)
- modifiers-v (constraint-modifier (constraints-v const->type+axis) :y parent child modifiers transformed-parent-rect)]
+ constraints-v
+ (if-not ignore-constraints
+ (:constraints-v child (default-constraints-v child))
+ :scale)]
- ;; Build final child modifiers. Apply transform again to the result, to get the
- ;; real modifiers that need to be applied to the child, including rotation as needed.
- (cond-> {}
- (some? (:displacement-after modifiers))
- (assoc :displacement-after (:displacement-after modifiers))
+ (if (and (= :scale constraints-h) (= :scale constraints-v))
+ modifiers
- (or (contains? modifiers-h :displacement)
- (contains? modifiers-v :displacement))
- (assoc :displacement (cond-> (gpt/point (get-in modifiers-h [:displacement :x] 0)
- (get-in modifiers-v [:displacement :y] 0))
- (some? (:resize-transform modifiers))
- (gpt/transform (:resize-transform modifiers))
+ (let [transformed-child (gst/transform-shape child (ctm/select-child-modifiers modifiers))
+ modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent)
- :always
- (gmt/translate-matrix)))
+ transformed-child (gst/transform-shape child modifiers)
- (:resize-vector modifiers-h)
- (assoc :resize-origin (:resize-origin modifiers-h)
- :resize-vector (gpt/point (get-in modifiers-h [:resize-vector :x] 1)
- (get-in modifiers-h [:resize-vector :y] 1)))
+ parent-points-before (bounding-box-parent-transform parent parent)
+ child-points-before (bounding-box-parent-transform child parent)
+ parent-points-after (bounding-box-parent-transform transformed-parent transformed-parent)
+ child-points-after (bounding-box-parent-transform transformed-child transformed-parent)
- (:resize-vector modifiers-v)
- (assoc :resize-origin-2 (:resize-origin modifiers-v)
- :resize-vector-2 (gpt/point (get-in modifiers-v [:resize-vector :x] 1)
- (get-in modifiers-v [:resize-vector :y] 1)))
+ modifiers-h (constraint-modifier (constraints-h const->type+axis) :x
+ child-points-before parent-points-before
+ child-points-after parent-points-after
+ transformed-parent)
- (:resize-transform modifiers)
- (assoc :resize-transform (:resize-transform modifiers)
- :resize-transform-inverse (:resize-transform-inverse modifiers))
+ modifiers-v (constraint-modifier (constraints-v const->type+axis) :y
+ child-points-before parent-points-before
+ child-points-after parent-points-after
+ transformed-parent)]
- :always
- (clean-modifiers)))))
+ (-> modifiers
+ (ctm/add-modifiers modifiers-h)
+ (ctm/add-modifiers modifiers-v))))))
diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc
new file mode 100644
index 000000000..b61eabf09
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout.cljc
@@ -0,0 +1,21 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout
+ (:require
+ [app.common.data.macros :as dm]
+ [app.common.geom.shapes.flex-layout.bounds :as fbo]
+ [app.common.geom.shapes.flex-layout.drop-area :as fdr]
+ [app.common.geom.shapes.flex-layout.lines :as fli]
+ [app.common.geom.shapes.flex-layout.modifiers :as fmo]))
+
+(dm/export fbo/layout-content-bounds)
+(dm/export fdr/get-drop-index)
+(dm/export fdr/layout-drop-areas)
+(dm/export fli/calc-layout-data)
+(dm/export fmo/layout-child-modifiers)
+(dm/export fmo/normalize-child-modifiers)
+
diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc
new file mode 100644
index 000000000..07552e540
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc
@@ -0,0 +1,112 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout.bounds
+ (:require
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.common :as gco]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.geom.shapes.rect :as gre]
+ [app.common.math :as mth]
+ [app.common.types.shape.layout :as ctl]))
+
+(defn- child-layout-bound-points
+ "Returns the bounds of the children as points"
+ [parent child]
+
+ (let [row? (ctl/row? parent)
+ col? (ctl/col? parent)
+
+ hv (partial gpo/start-hv (:points parent))
+ vv (partial gpo/start-vv (:points parent))
+
+ v-start? (ctl/v-start? parent)
+ v-center? (ctl/v-center? parent)
+ v-end? (ctl/v-end? parent)
+ h-start? (ctl/h-start? parent)
+ h-center? (ctl/h-center? parent)
+ h-end? (ctl/h-end? parent)
+
+ base-p (first (:points child))
+
+ width (-> child :selrect :width)
+ height (-> child :selrect :height)
+
+ min-width (if (ctl/fill-width? child)
+ (ctl/child-min-width child)
+ width)
+
+ min-height (if (ctl/fill-height? child)
+ (ctl/child-min-height child)
+ height)
+
+ ;; This is the leftmost (when row) or topmost (when col) point
+ ;; Will be added always to the bounds and then calculated the other limits
+ ;; from there
+ base-p (cond-> base-p
+ (and row? v-center?)
+ (gpt/add (vv (/ height 2)))
+
+ (and row? v-end?)
+ (gpt/add (vv height))
+
+ (and col? h-center?)
+ (gpt/add (hv (/ width 2)))
+
+ (and col? h-end?)
+ (gpt/add (hv width)))]
+
+ (cond-> [base-p]
+ (and (mth/almost-zero? min-width) (mth/almost-zero? min-height))
+ (conj (cond-> base-p
+ row?
+ (gpt/add (hv width))
+
+ col?
+ (gpt/add (vv height))))
+
+ (not (mth/almost-zero? min-width))
+ (conj (cond-> base-p
+ (or row? h-start?)
+ (gpt/add (hv min-width))
+
+ (and col? h-center?)
+ (gpt/add (hv (/ min-width 2)))
+
+ (and col? h-center?)
+ (gpt/subtract (hv min-width))))
+
+ (not (mth/almost-zero? min-height))
+ (conj (cond-> base-p
+ (or col? v-start?)
+ (gpt/add (vv min-height))
+
+ (and row? v-center?)
+ (gpt/add (vv (/ min-height 2)))
+
+ (and row? v-end?)
+ (gpt/subtract (vv min-height)))))))
+
+(defn layout-content-bounds
+ [{:keys [layout-padding] :as parent} children]
+
+ (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
+ pad-top (or pad-top 0)
+ pad-right (or pad-right 0)
+ pad-bottom (or pad-bottom 0)
+ pad-left (or pad-left 0)
+
+ child-bounds
+ (fn [{:keys [points] :as child}]
+ (if (or (ctl/fill-height? child) (ctl/fill-height? child))
+ (child-layout-bound-points parent child)
+ points))]
+
+ (-> (mapcat child-bounds children)
+ (gco/transform-points (gco/center-shape parent) (:transform-inverse parent))
+ (gre/squared-points)
+ (gpo/pad-points (- pad-top) (- pad-right) (- pad-bottom) (- pad-left))
+ (gre/points->rect))))
diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc
new file mode 100644
index 000000000..00de11f3a
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc
@@ -0,0 +1,194 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout.drop-area
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.matrix :as gmt]
+ [app.common.geom.shapes.common :as gco]
+ [app.common.geom.shapes.flex-layout.lines :as fli]
+ [app.common.geom.shapes.rect :as gsr]
+ [app.common.pages.helpers :as cph]
+ [app.common.types.shape.layout :as ctl]))
+
+(defn drop-child-areas
+ [{:keys [transform-inverse] :as frame} parent-rect child index reverse? prev-x prev-y last?]
+
+ (let [col? (ctl/col? frame)
+ row? (ctl/row? frame)
+ [layout-gap-row layout-gap-col] (ctl/gaps frame)
+
+ start-p (-> child :points first)
+ center (gco/center-shape frame)
+ start-p (gmt/transform-point-center start-p center transform-inverse)
+
+ box-x (:x start-p)
+ box-y (:y start-p)
+ box-width (-> child :selrect :width)
+ box-height (-> child :selrect :height)
+
+ x (if col? (:x parent-rect) prev-x)
+ y (if row? (:y parent-rect) prev-y)
+
+ width
+ (cond
+ (and row? last?)
+ (- (+ (:x parent-rect) (:width parent-rect)) x)
+
+ col?
+ (:width parent-rect)
+
+ :else
+ (+ box-width (- box-x prev-x) (/ layout-gap-row 2)))
+
+ height
+ (cond
+ (and col? last?)
+ (- (+ (:y parent-rect) (:height parent-rect)) y)
+
+ row?
+ (:height parent-rect)
+
+ :else
+ (+ box-height (- box-y prev-y) (/ layout-gap-col 2)))]
+
+ (if row?
+ (let [half-point-width (+ (- box-x x) (/ box-width 2))]
+ [(gsr/make-rect x y width height)
+ (-> (gsr/make-rect x y half-point-width height)
+ (assoc :index (if reverse? (inc index) index)))
+ (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height)
+ (assoc :index (if reverse? index (inc index))))])
+ (let [half-point-height (+ (- box-y y) (/ box-height 2))]
+ [(gsr/make-rect x y width height)
+ (-> (gsr/make-rect x y width half-point-height)
+ (assoc :index (if reverse? (inc index) index)))
+ (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height))
+ (assoc :index (if reverse? index (inc index))))]))))
+
+(defn drop-line-area
+ [{:keys [transform-inverse margin-x margin-y] :as frame}
+ {:keys [start-p layout-gap-row layout-gap-col num-children line-width line-height] :as line-data}
+ prev-x prev-y last?]
+
+ (let [col? (ctl/col? frame)
+ row? (ctl/row? frame)
+ h-center? (and row? (ctl/h-center? frame))
+ h-end? (and row? (ctl/h-end? frame))
+ v-center? (and col? (ctl/v-center? frame))
+ v-end? (and row? (ctl/v-end? frame))
+
+ center (gco/center-shape frame)
+ start-p (gmt/transform-point-center start-p center transform-inverse)
+
+ line-width
+ (if row?
+ (:width frame)
+ (+ line-width margin-x
+ (if row? (* layout-gap-row (dec num-children)) 0)))
+
+ line-height
+ (if col?
+ (:height frame)
+ (+ line-height margin-y
+ (if col?
+ (* layout-gap-col (dec num-children))
+ 0)))
+
+ box-x
+ (- (:x start-p)
+ (cond
+ h-center? (/ line-width 2)
+ h-end? line-width
+ :else 0))
+
+ box-y
+ (- (:y start-p)
+ (cond
+ v-center? (/ line-height 2)
+ v-end? line-height
+ :else 0))
+
+ x (if row? (:x frame) prev-x)
+ y (if col? (:y frame) prev-y)
+
+ width (cond
+ (and col? last?)
+ (- (+ (:x frame) (:width frame)) x)
+
+ row?
+ (:width frame)
+
+ :else
+ (+ line-width (- box-x prev-x) (/ layout-gap-row 2)))
+
+ height (cond
+ (and row? last?)
+ (- (+ (:y frame) (:height frame)) y)
+
+ col?
+ (:height frame)
+
+ :else
+ (+ line-height (- box-y prev-y) (/ layout-gap-col 2)))]
+ (gsr/make-rect x y width height)))
+
+(defn layout-drop-areas
+ "Retrieve the layout drop areas to move shapes inside layouts"
+ [frame layout-data children]
+
+ (let [reverse? (:reverse? layout-data)
+ children (vec (cond->> (d/enumerate children) reverse? reverse))
+ lines (:layout-lines layout-data)]
+
+ (loop [areas []
+ from-idx 0
+ prev-line-x (:x frame)
+ prev-line-y (:y frame)
+
+ current-line (first lines)
+ lines (rest lines)]
+
+ (if (nil? current-line)
+ areas
+
+ (let [line-area (drop-line-area frame current-line prev-line-x prev-line-y (nil? (first lines)))
+ children (subvec children from-idx (+ from-idx (:num-children current-line)))
+
+ next-areas
+ (loop [areas areas
+ prev-child-x (:x line-area)
+ prev-child-y (:y line-area)
+ [index child] (first children)
+ children (rest children)]
+
+ (if (nil? child)
+ areas
+
+ (let [[child-area child-area-start child-area-end]
+ (drop-child-areas frame line-area child index reverse? prev-child-x prev-child-y (nil? (first children)))]
+ (recur (conj areas child-area-start child-area-end)
+ (+ (:x child-area) (:width child-area))
+ (+ (:y child-area) (:height child-area))
+ (first children)
+ (rest children)))))]
+
+ (recur next-areas
+ (+ from-idx (:num-children current-line))
+ (+ (:x line-area) (:width line-area))
+ (+ (:y line-area) (:height line-area))
+ (first lines)
+ (rest lines)))))))
+
+(defn get-drop-index
+ [frame-id objects position]
+ (let [frame (get objects frame-id)
+ position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))
+ children (cph/get-immediate-children objects frame-id)
+ layout-data (fli/calc-layout-data frame children)
+ drop-areas (layout-drop-areas frame layout-data children)
+ area (d/seek #(gsr/contains-point? % position) drop-areas)]
+ (:index area)))
diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc
new file mode 100644
index 000000000..5b0fafdbc
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc
@@ -0,0 +1,319 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout.lines
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.shapes.flex-layout.positions :as flp]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.geom.shapes.transforms :as gst]
+ [app.common.math :as mth]
+ [app.common.types.shape.layout :as ctl]))
+
+(def conjv (fnil conj []))
+
+(defn layout-bounds
+ [{:keys [layout-padding layout-padding-type] :as shape}]
+ (let [;; Add padding to the bounds
+ {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
+ [pad-top pad-right pad-bottom pad-left]
+ (if (= layout-padding-type :multiple)
+ [pad-top pad-right pad-bottom pad-left]
+ [pad-top pad-top pad-top pad-top])]
+ (gpo/pad-points (:points shape) pad-top pad-right pad-bottom pad-left)))
+
+(defn init-layout-lines
+ "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation"
+ [shape children layout-bounds]
+
+ (let [col? (ctl/col? shape)
+ row? (ctl/row? shape)
+
+ wrap? (and (ctl/wrap? shape)
+ (or col? (not (ctl/auto-width? shape)))
+ (or row? (not (ctl/auto-height? shape))))
+
+ [layout-gap-row layout-gap-col] (ctl/gaps shape)
+
+ layout-width (gpo/width-points layout-bounds)
+ layout-height (gpo/height-points layout-bounds)]
+
+ (loop [line-data nil
+ result []
+ child (first children)
+ children (rest children)]
+
+ (if (nil? child)
+ (cond-> result (some? line-data) (conj line-data))
+
+ (let [{:keys [line-min-width line-min-height
+ line-max-width line-max-height
+ num-children
+ children-data]} line-data
+
+ child-bounds (gst/parent-coords-points child shape)
+ child-width (gpo/width-points child-bounds)
+ child-height (gpo/height-points child-bounds)
+ child-min-width (ctl/child-min-width child)
+ child-min-height (ctl/child-min-height child)
+ child-max-width (ctl/child-max-width child)
+ child-max-height (ctl/child-max-height child)
+
+ [child-margin-top child-margin-right child-margin-bottom child-margin-left]
+ (ctl/child-margins child)
+
+ child-margin-width (+ child-margin-left child-margin-right)
+ child-margin-height (+ child-margin-top child-margin-bottom)
+
+ fill-width? (ctl/fill-width? child)
+ fill-height? (ctl/fill-height? child)
+
+ ;; We need this info later to calculate the child resizes when fill
+ child-data {:id (:id child)
+ :child-min-width (if fill-width? child-min-width child-width)
+ :child-min-height (if fill-height? child-min-height child-height)
+ :child-max-width (if fill-width? child-max-width child-width)
+ :child-max-height (if fill-height? child-max-height child-height)}
+
+ next-min-width (+ child-margin-width (if fill-width? child-min-width child-width))
+ next-min-height (+ child-margin-height (if fill-height? child-min-height child-height))
+ next-max-width (+ child-margin-width (if fill-width? child-max-width child-width))
+ next-max-height (+ child-margin-height (if fill-height? child-max-height child-height))
+
+ next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children))
+ next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))]
+
+ (if (and (some? line-data)
+ (or (not wrap?)
+ (and row? (<= next-line-min-width layout-width))
+ (and col? (<= next-line-min-height layout-height))))
+
+ (recur {:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width))
+ :line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width))
+ :line-min-height (if col? (+ line-min-height next-min-height) (max line-min-height next-min-height))
+ :line-max-height (if col? (+ line-max-height next-max-height) (max line-max-height next-max-height))
+ :num-children (inc num-children)
+ :children-data (conjv children-data child-data)}
+ result
+ (first children)
+ (rest children))
+
+ (recur {:line-min-width next-min-width
+ :line-min-height next-min-height
+ :line-max-width next-max-width
+ :line-max-height next-max-height
+ :num-children 1
+ :children-data [child-data]}
+ (cond-> result (some? line-data) (conj line-data))
+ (first children)
+ (rest children))))))))
+
+(defn add-space-to-items
+ ;; Distributes the remainder space between the lines
+ [prop prop-min prop-max to-share items]
+ (let [num-items (->> items (remove #(mth/close? (get % prop) (get % prop-max))) count)
+ per-line-target (/ to-share num-items)]
+ (loop [current (first items)
+ items (rest items)
+ remainder to-share
+ result []]
+ (if (nil? current)
+ [result remainder]
+ (let [cur-val (or (get current prop) (get current prop-min) 0)
+ max-val (get current prop-max)
+ cur-inc (if (> (+ cur-val per-line-target) max-val)
+ (- max-val cur-val)
+ per-line-target)
+ current (assoc current prop (+ cur-val cur-inc))
+ remainder (- remainder cur-inc)
+ result (conj result current)]
+ (recur (first items) (rest items) remainder result))))))
+
+(defn distribute-space
+ [prop prop-min prop-max min-value bound-value items]
+ (loop [to-share (- bound-value min-value)
+ items items]
+ (if (<= to-share 0)
+ items
+ (let [[items remainder] (add-space-to-items prop prop-min prop-max to-share items)]
+ (assert (<= remainder to-share) (str remainder ">" to-share))
+ (if (or (<= remainder 0) (= remainder to-share))
+ items
+ (recur remainder items))))))
+
+(defn add-lines-positions
+ [parent layout-bounds layout-lines]
+
+ (let [row? (ctl/row? parent)
+ col? (ctl/col? parent)
+
+ [layout-gap-row layout-gap-col] (ctl/gaps parent)
+
+ layout-width (gpo/width-points layout-bounds)
+ layout-height (gpo/height-points layout-bounds)]
+
+ (letfn [(add-lines [[total-width total-height]
+ {:keys [line-width line-height]}]
+ [(+ total-width line-width) (+ total-height line-height)])
+
+ (add-ranges [[total-min-width total-min-height total-max-width total-max-height]
+ {:keys [line-min-width line-min-height line-max-width line-max-height]}]
+ [(+ total-min-width line-min-width)
+ (+ total-min-height line-min-height)
+ (+ total-max-width line-max-width)
+ (+ total-max-height line-max-height)])
+
+ (add-starts [total-width total-height num-lines [result base-p] layout-line]
+ (let [start-p (flp/get-start-line parent layout-bounds layout-line base-p total-width total-height num-lines)
+ next-p (flp/get-next-line parent layout-bounds layout-line base-p total-width total-height num-lines)]
+
+ [(conj result (assoc layout-line :start-p start-p))
+ next-p]))]
+
+ (let [[total-min-width total-min-height total-max-width total-max-height]
+ (->> layout-lines (reduce add-ranges [0 0 0 0]))
+
+ get-layout-width (fn [{:keys [num-children]}] (- layout-width (* layout-gap-row (dec num-children))))
+ get-layout-height (fn [{:keys [num-children]}] (- layout-height (* layout-gap-col (dec num-children))))
+
+ num-lines (count layout-lines)
+
+ ;; When align-items is stretch we need to adjust the main axis size to grow for the full content
+ stretch-width-fix
+ (if (and col? (ctl/content-stretch? parent))
+ (/ (- layout-width (* layout-gap-row (dec num-lines)) total-max-width) num-lines)
+ 0)
+
+ stretch-height-fix
+ (if (and row? (ctl/content-stretch? parent))
+ (/ (- layout-height (* layout-gap-col (dec num-lines)) total-max-height) num-lines)
+ 0)
+
+ ;; Distributes the space between the layout lines based on its max/min constraints
+ layout-lines
+ (cond->> layout-lines
+ row?
+ (map #(assoc % :line-width (max (:line-min-width %) (min (get-layout-width %) (:line-max-width %)))))
+
+ col?
+ (map #(assoc % :line-height (max (:line-min-height %) (min (get-layout-height %) (:line-max-height %)))))
+
+ (and row? (>= total-min-height layout-height))
+ (map #(assoc % :line-height (:line-min-height %)))
+
+ (and row? (<= total-max-height layout-height))
+ (map #(assoc % :line-height (+ (:line-max-height %) stretch-height-fix)))
+
+ (and row? (< total-min-height layout-height total-max-height))
+ (distribute-space :line-height :line-min-height :line-max-height total-min-height (- layout-height (* (dec num-lines) layout-gap-col)))
+
+ (and col? (>= total-min-width layout-width))
+ (map #(assoc % :line-width (:line-min-width %)))
+
+ (and col? (<= total-max-width layout-width))
+ (map #(assoc % :line-width (+ (:line-max-width %) stretch-width-fix)))
+
+ (and col? (< total-min-width layout-width total-max-width))
+ (distribute-space :line-width :line-min-width :line-max-width total-min-width (- layout-width (* (dec num-lines) layout-gap-row))))
+
+ [total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
+
+ base-p (flp/get-base-line parent layout-bounds total-width total-height num-lines)]
+
+ (first (reduce (partial add-starts total-width total-height num-lines) [[] base-p] layout-lines))))))
+
+(defn add-line-spacing
+ "Calculates the baseline for a flex layout"
+ [shape layout-bounds {:keys [num-children line-width line-height] :as line-data}]
+
+ (let [width (gpo/width-points layout-bounds)
+ height (gpo/height-points layout-bounds)
+
+ row? (ctl/row? shape)
+ col? (ctl/col? shape)
+ space-between? (ctl/space-between? shape)
+ space-around? (ctl/space-around? shape)
+
+ [layout-gap-row layout-gap-col] (ctl/gaps shape)
+
+ layout-gap-row
+ (cond (and row? space-around?)
+ 0
+
+ (and row? space-between?)
+ (/ (- width line-width) (dec num-children))
+
+ :else
+ layout-gap-row)
+
+ layout-gap-col
+ (cond (and col? space-around?)
+ 0
+
+ (and col? space-between?)
+ (/ (- height line-height) (dec num-children))
+
+ :else
+ layout-gap-col)
+
+ margin-x
+ (if (and row? space-around?)
+ (/ (- width line-width) (inc num-children))
+ 0)
+
+ margin-y
+ (if (and col? space-around?)
+ (/ (- height line-height) (inc num-children))
+ 0)]
+ (assoc line-data
+ :layout-bounds layout-bounds
+ :layout-gap-row layout-gap-row
+ :layout-gap-col layout-gap-col
+ :margin-x margin-x
+ :margin-y margin-y)))
+
+(defn add-children-resizes
+ [shape {:keys [line-min-width line-width line-min-height line-height] :as line-data}]
+
+ (let [row? (ctl/row? shape)
+ col? (ctl/col? shape)]
+ (update line-data :children-data
+ (fn [children-data]
+ (cond->> children-data
+ row?
+ (map #(assoc % :child-width (:child-min-width %)))
+
+ col?
+ (map #(assoc % :child-height (:child-min-height %)))
+
+ row?
+ (distribute-space :child-width :child-min-width :child-max-width line-min-width line-width)
+
+ col?
+ (distribute-space :child-height :child-min-height :child-max-height line-min-height line-height)
+
+ :always
+ (d/index-by :id))))))
+
+(defn calc-layout-data
+ "Digest the layout data to pass it to the constrains"
+ [shape children]
+
+ (let [layout-bounds (layout-bounds shape)
+ reverse? (ctl/reverse? shape)
+ children (cond->> children reverse? reverse)
+
+ ;; Creates the layout lines information
+ layout-lines
+ (->> (init-layout-lines shape children layout-bounds)
+ (add-lines-positions shape layout-bounds)
+ (into []
+ (comp (map (partial add-line-spacing shape layout-bounds))
+ (map (partial add-children-resizes shape)))))]
+
+ {:layout-lines layout-lines
+ :layout-bounds layout-bounds
+ :reverse? reverse?}))
diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc
new file mode 100644
index 000000000..039f17f98
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc
@@ -0,0 +1,100 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout.modifiers
+ (:require
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.flex-layout.positions :as fpo]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.geom.shapes.transforms :as gst]
+ [app.common.types.modifiers :as ctm]
+ [app.common.types.shape.layout :as ctl]))
+
+(defn normalize-child-modifiers
+ "Apply the modifiers and then normalized them against the parent coordinates"
+ [modifiers parent child {:keys [transform transform-inverse] :as transformed-parent}]
+
+ (let [transformed-child (gst/transform-shape child modifiers)
+ child-bb-before (gst/parent-coords-rect child parent)
+ child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
+ scale-x (/ (:width child-bb-before) (:width child-bb-after))
+ scale-y (/ (:height child-bb-before) (:height child-bb-after))
+ resize-origin (-> transformed-parent :points gpo/origin)
+ resize-vector (gpt/point scale-x scale-y)]
+ (-> modifiers
+ (ctm/select-child-modifiers)
+ (ctm/resize resize-vector resize-origin transform transform-inverse))))
+
+(defn calc-fill-width-data
+ "Calculates the size and modifiers for the width of an auto-fill child"
+ [{:keys [transform transform-inverse] :as parent}
+ child
+ child-origin child-width
+ {:keys [children-data line-width] :as layout-data}]
+
+ (cond
+ (ctl/row? parent)
+ (let [target-width (max (get-in children-data [(:id child) :child-width]) 0.01)
+ fill-scale (/ target-width child-width)]
+ {:width target-width
+ :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)})
+
+ (ctl/col? parent)
+ (let [target-width (max (- line-width (ctl/child-width-margin child)) 0.01)
+ max-width (ctl/child-max-width child)
+ target-width (min max-width target-width)
+ fill-scale (/ target-width child-width)]
+ {:width target-width
+ :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)})))
+
+(defn calc-fill-height-data
+ "Calculates the size and modifiers for the height of an auto-fill child"
+ [{:keys [transform transform-inverse] :as parent}
+ child
+ child-origin child-height
+ {:keys [children-data line-height] :as layout-data}]
+
+ (cond
+ (ctl/col? parent)
+ (let [target-height (max (get-in children-data [(:id child) :child-height]) 0.01)
+ fill-scale (/ target-height child-height)]
+ {:height target-height
+ :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)})
+
+ (ctl/row? parent)
+ (let [target-height (max (- line-height (ctl/child-height-margin child)) 0.01)
+ max-height (ctl/child-max-height child)
+ target-height (min max-height target-height)
+ fill-scale (/ target-height child-height)]
+ {:height target-height
+ :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))
+
+(defn layout-child-modifiers
+ "Calculates the modifiers for the layout"
+ [parent child layout-line]
+ (let [child-bounds (gst/parent-coords-points child parent)
+
+ child-origin (gpo/origin child-bounds)
+ child-width (gpo/width-points child-bounds)
+ child-height (gpo/height-points child-bounds)
+
+ fill-width (when (ctl/fill-width? child) (calc-fill-width-data parent child child-origin child-width layout-line))
+ fill-height (when (ctl/fill-height? child) (calc-fill-height-data parent child child-origin child-height layout-line))
+
+ child-width (or (:width fill-width) child-width)
+ child-height (or (:height fill-height) child-height)
+
+ [corner-p layout-line] (fpo/get-child-position parent child child-width child-height layout-line)
+
+ move-vec (gpt/to-vec child-origin corner-p)
+
+ modifiers
+ (-> (ctm/empty)
+ (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width)))
+ (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height)))
+ (ctm/move move-vec))]
+
+ [modifiers layout-line]))
diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc
new file mode 100644
index 000000000..3c710f669
--- /dev/null
+++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc
@@ -0,0 +1,277 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.flex-layout.positions
+ (:require
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.types.shape.layout :as ctl]))
+
+(defn get-base-line
+ [parent layout-bounds total-width total-height num-lines]
+
+ (let [layout-width (gpo/width-points layout-bounds)
+ layout-height (gpo/height-points layout-bounds)
+ row? (ctl/row? parent)
+ col? (ctl/col? parent)
+ hv (partial gpo/start-hv layout-bounds)
+ vv (partial gpo/start-vv layout-bounds)
+
+ end? (ctl/content-end? parent)
+ center? (ctl/content-center? parent)
+ around? (ctl/content-around? parent)
+
+ ;; Adjust the totals so it takes into account the gaps
+ [layout-gap-row layout-gap-col] (ctl/gaps parent)
+ lines-gap-row (* (dec num-lines) layout-gap-row)
+ lines-gap-col (* (dec num-lines) layout-gap-col)
+
+ free-width-gap (- layout-width total-width lines-gap-row)
+ free-height-gap (- layout-height total-height lines-gap-col)
+ free-width (- layout-width total-width)
+ free-height (- layout-height total-height)]
+
+ (cond-> (gpo/origin layout-bounds)
+ row?
+ (cond-> center?
+ (gpt/add (vv (/ free-height-gap 2)))
+
+ end?
+ (gpt/add (vv free-height-gap))
+
+ around?
+ (gpt/add (vv (/ free-height (inc num-lines)))))
+
+ col?
+ (cond-> center?
+ (gpt/add (hv (/ free-width-gap 2)))
+
+ end?
+ (gpt/add (hv free-width-gap))
+
+ around?
+ (gpt/add (hv (/ free-width (inc num-lines))))))))
+
+(defn get-next-line
+ [parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines]
+
+ (let [layout-width (gpo/width-points layout-bounds)
+ layout-height (gpo/height-points layout-bounds)
+ row? (ctl/row? parent)
+ col? (ctl/col? parent)
+
+ [layout-gap-row layout-gap-col] (ctl/gaps parent)
+
+ hv #(gpo/start-hv layout-bounds %)
+ vv #(gpo/start-vv layout-bounds %)
+
+ stretch? (ctl/content-stretch? parent)
+ between? (ctl/content-between? parent)
+ around? (ctl/content-around? parent)
+
+ free-width (- layout-width total-width)
+ free-height (- layout-height total-height)
+
+ line-gap-row
+ (cond
+ stretch?
+ (/ free-width num-lines)
+
+ between?
+ (/ free-width (dec num-lines))
+
+ around?
+ (/ free-width (inc num-lines))
+
+ :else
+ layout-gap-row)
+
+ line-gap-col
+ (cond
+ stretch?
+ (/ free-height num-lines)
+
+ between?
+ (/ free-height (dec num-lines))
+
+ around?
+ (/ free-height (inc num-lines))
+
+ :else
+ layout-gap-col)]
+
+ (cond-> base-p
+ row?
+ (gpt/add (vv (+ line-height (max layout-gap-col line-gap-col))))
+
+ col?
+ (gpt/add (hv (+ line-width (max layout-gap-row line-gap-row)))))))
+
+(defn get-start-line
+ "Cross axis line. It's position is fixed along the different lines"
+ [parent layout-bounds {:keys [line-width line-height num-children]} base-p total-width total-height num-lines]
+
+ (let [layout-width (gpo/width-points layout-bounds)
+ layout-height (gpo/height-points layout-bounds)
+ [layout-gap-row layout-gap-col] (ctl/gaps parent)
+ row? (ctl/row? parent)
+ col? (ctl/col? parent)
+ space-between? (ctl/space-between? parent)
+ space-around? (ctl/space-around? parent)
+ h-center? (ctl/h-center? parent)
+ h-end? (ctl/h-end? parent)
+ v-center? (ctl/v-center? parent)
+ v-end? (ctl/v-end? parent)
+ content-stretch? (ctl/content-stretch? parent)
+ hv (partial gpo/start-hv layout-bounds)
+ vv (partial gpo/start-vv layout-bounds)
+ children-gap-width (* layout-gap-row (dec num-children))
+ children-gap-height (* layout-gap-col (dec num-children))
+
+ line-height
+ (if (and row? content-stretch?)
+ (+ line-height (/ (- layout-height total-height) num-lines))
+ line-height)
+
+ line-width
+ (if (and col? content-stretch?)
+ (+ line-width (/ (- layout-width total-width) num-lines))
+ line-width)
+
+ start-p
+ (cond-> base-p
+ ;; X AXIS
+ (and row? h-center? (not space-around?) (not space-between?))
+ (-> (gpt/add (hv (/ layout-width 2)))
+ (gpt/subtract (hv (/ (+ line-width children-gap-width) 2))))
+
+ (and row? h-end? (not space-around?) (not space-between?))
+ (-> (gpt/add (hv layout-width))
+ (gpt/subtract (hv (+ line-width children-gap-width))))
+
+ ;; Y AXIS
+ (and col? v-center? (not space-around?) (not space-between?))
+ (-> (gpt/add (vv (/ layout-height 2)))
+ (gpt/subtract (vv (/ (+ line-height children-gap-height) 2))))
+
+ (and col? v-end? (not space-around?) (not space-between?))
+ (-> (gpt/add (vv layout-height))
+ (gpt/subtract (vv (+ line-height children-gap-height)))))]
+
+ start-p))
+
+(defn get-child-position
+ "Calculates the position for the current shape given the layout-data context"
+ [parent child
+ child-width child-height
+ {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y line-height line-width] :as layout-data}]
+
+ (let [row? (ctl/row? parent)
+ col? (ctl/col? parent)
+ h-start? (ctl/h-start? parent)
+ h-center? (ctl/h-center? parent)
+ h-end? (ctl/h-end? parent)
+ v-start? (ctl/v-start? parent)
+ v-center? (ctl/v-center? parent)
+ v-end? (ctl/v-end? parent)
+
+ self-start? (ctl/align-self-start? child)
+ self-end? (ctl/align-self-end? child)
+ self-center? (ctl/align-self-center? child)
+ align-self? (or self-start? self-end? self-center?)
+
+ v-start? (if (or col? (not align-self?)) v-start? self-start?)
+ v-center? (if (or col? (not align-self?)) v-center? self-center?)
+ v-end? (if (or col? (not align-self?)) v-end? self-end?)
+
+ h-start? (if (or row? (not align-self?)) h-start? self-start?)
+ h-center? (if (or row? (not align-self?)) h-center? self-center?)
+ h-end? (if (or row? (not align-self?)) h-end? self-end?)
+
+ [margin-top margin-right margin-bottom margin-left] (ctl/child-margins child)
+
+ points (:points parent)
+ hv (partial gpo/start-hv points)
+ vv (partial gpo/start-vv points)
+
+ corner-p
+ (cond-> start-p
+ ;; COLUMN DIRECTION
+ col?
+ (cond-> (some? margin-top)
+ (gpt/add (vv margin-top))
+
+ h-center?
+ (gpt/add (hv (- (/ child-width 2))))
+
+ h-end?
+ (gpt/add (hv (- child-width)))
+
+ h-start?
+ (gpt/add (hv margin-left))
+
+ h-center?
+ (gpt/add (hv (+ (/ line-width 2) (/ (- margin-left margin-right) 2))))
+
+ h-end?
+ (gpt/add (hv (+ line-width (- margin-right)))))
+
+ ;; ROW DIRECTION
+ row?
+ (cond-> v-center?
+ (gpt/add (vv (- (/ child-height 2))))
+
+ v-end?
+ (gpt/add (vv (- child-height)))
+
+ (some? margin-left)
+ (gpt/add (hv margin-left))
+
+ v-start?
+ (gpt/add (vv margin-top))
+
+ v-center?
+ (gpt/add (vv (+ (/ line-height 2) (/ (- margin-top margin-bottom) 2))))
+
+ v-end?
+ (gpt/add (vv (+ line-height (- margin-bottom)))))
+
+ ;; Margins
+ (some? margin-x)
+ (gpt/add (hv margin-x))
+
+ (some? margin-y)
+ (gpt/add (vv margin-y)))
+
+ ;; Fix position when layout is flipped
+ corner-p
+ (cond-> corner-p
+ (:flip-x parent)
+ (gpt/add (hv child-width))
+
+ (:flip-y parent)
+ (gpt/add (vv child-height)))
+
+ next-p
+ (cond-> start-p
+ row?
+ (-> (gpt/add (hv (+ child-width layout-gap-row)))
+ (gpt/add (hv (+ margin-left margin-right))))
+
+ col?
+ (-> (gpt/add (vv (+ margin-top margin-bottom)))
+ (gpt/add (vv (+ child-height layout-gap-col))))
+
+ (some? margin-x)
+ (gpt/add (hv margin-x))
+
+ (some? margin-y)
+ (gpt/add (vv margin-y)))
+
+ layout-data
+ (assoc layout-data :start-p next-p)]
+
+ [corner-p layout-data]))
diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc
index 34b5ec7f6..72d5bf47a 100644
--- a/common/src/app/common/geom/shapes/intersect.cljc
+++ b/common/src/app/common/geom/shapes/intersect.cljc
@@ -348,3 +348,25 @@
:points
(every? (partial has-point-rect? rect))))
+
+(defn line-line-intersect
+ "Calculates the interesection point for two lines given by the points a-b and b-c"
+ [a b c d]
+
+ (let [;; Line equation representation: ax + by + c = 0
+ a1 (- (:y b) (:y a))
+ b1 (- (:x a) (:x b))
+ c1 (+ (* a1 (:x a)) (* b1 (:y a)))
+
+ a2 (- (:y d) (:y c))
+ b2 (- (:x c) (:x d))
+ c2 (+ (* a2 (:x c)) (* b2 (:y c)))
+
+ ;; Cramer's rule
+ det (- (* a1 b2) (* a2 b1))]
+
+ ;; If almost zero the lines are parallel
+ (when (not (mth/almost-zero? det))
+ (let [x (/ (- (* b2 c1) (* b1 c2)) det)
+ y (/ (- (* c2 a1) (* c1 a2)) det)]
+ (gpt/point x y)))))
diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc
deleted file mode 100644
index dc27fb429..000000000
--- a/common/src/app/common/geom/shapes/layout.cljc
+++ /dev/null
@@ -1,327 +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 INC
-
-(ns app.common.geom.shapes.layout
- (:require
- [app.common.geom.matrix :as gmt]
- [app.common.geom.point :as gpt]
- [app.common.geom.shapes.rect :as gre]))
-
-;; :layout ;; true if active, false if not
-;; :layout-flex-dir ;; :row, :column, :reverse-row, :reverse-column
-;; :layout-gap ;; number could be negative
-;; :layout-type ;; :packed, :space-between, :space-around
-;; :layout-wrap-type ;; :wrap, :no-wrap
-;; :layout-padding-type ;; :simple, :multiple
-;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
-;; :layout-h-orientation ;; :top, :center, :bottom
-;; :layout-v-orientation ;; :left, :center, :right
-
-(defn col?
- [{:keys [layout-flex-dir]}]
- (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
-
-(defn row?
- [{:keys [layout-flex-dir]}]
- (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
-
-(defn h-start?
- [{:keys [layout-h-orientation]}]
- (= layout-h-orientation :left))
-
-(defn h-center?
- [{:keys [layout-h-orientation]}]
- (= layout-h-orientation :center))
-
-(defn h-end?
- [{:keys [layout-h-orientation]}]
- (= layout-h-orientation :right))
-
-(defn v-start?
- [{:keys [layout-v-orientation]}]
- (= layout-v-orientation :top))
-
-(defn v-center?
- [{:keys [layout-v-orientation]}]
- (= layout-v-orientation :center))
-
-(defn v-end?
- [{:keys [layout-v-orientation]}]
- (= layout-v-orientation :bottom))
-
-(defn add-padding [transformed-rect {:keys [layout-padding-type layout-padding]}]
- (let [{:keys [p1 p2 p3 p4]} layout-padding
- [p1 p2 p3 p4]
- (if (= layout-padding-type :multiple)
- [p1 p2 p3 p4]
- [p1 p1 p1 p1])]
-
- (-> transformed-rect
- (update :y + p1)
- (update :width - p2 p3)
- (update :x + p3)
- (update :height - p1 p4))))
-
-(defn calc-layout-lines
- [{:keys [layout-gap layout-wrap-type] :as shape} children {:keys [width height] :as layout-bounds}]
-
- (let [wrap? (= layout-wrap-type :wrap)
-
- reduce-fn
- (fn [[{:keys [line-width line-height num-children] :as line-data} result] child]
- (let [child-bounds (-> child :points gre/points->rect)
- next-width (-> child-bounds :width)
- next-height (-> child-bounds :height)]
-
- (if (and (some? line-data)
- (or (not wrap?)
- (and (col? shape) (<= (+ line-width next-width (* layout-gap num-children)) width))
- (and (row? shape) (<= (+ line-height next-height (* layout-gap num-children)) height))))
-
- [{:line-width (if (col? shape) (+ line-width next-width) (max line-width next-width))
- :line-height (if (row? shape) (+ line-height next-height) (max line-height next-height))
- :num-children (inc num-children)}
- result]
-
- [{:line-width next-width
- :line-height next-height
- :num-children 1}
- (cond-> result (some? line-data) (conj line-data))])))
-
- [line-data layout-lines] (reduce reduce-fn [nil []] children)]
-
- (cond-> layout-lines (some? line-data) (conj line-data))))
-
-(defn calc-layout-lines-position
- [{:keys [layout-gap layout-type] :as shape} {:keys [x y width height]} layout-lines]
-
- (letfn [(get-base-line
- [total-width total-height]
-
- (let [base-x
- (cond
- (and (row? shape) (h-center? shape))
- (+ x (/ (- width total-width) 2))
-
- (and (row? shape) (h-end? shape))
- (+ x width (- total-width))
-
- :else x)
-
- base-y
- (cond
- (and (col? shape) (v-center? shape))
- (+ y (/ (- height total-height) 2))
-
- (and (col? shape) (v-end? shape))
- (+ y height (- total-height))
-
- :else y)]
-
- [base-x base-y]))
-
- (get-start-line
- [{:keys [line-width line-height num-children]} base-x base-y]
-
- (let [children-gap (* layout-gap (dec num-children))
-
- start-x
- (cond
- (or (and (col? shape) (= :space-between layout-type))
- (and (col? shape) (= :space-around layout-type)))
- x
-
- (and (col? shape) (h-center? shape))
- (- (+ x (/ width 2)) (/ (+ line-width children-gap) 2))
-
- (and (col? shape) (h-end? shape))
- (- (+ x width) (+ line-width children-gap))
-
- (and (row? shape) (h-center? shape))
- (+ base-x (/ line-width 2))
-
- (and (row? shape) (h-end? shape))
- (+ base-x line-width)
-
- (row? shape)
- base-x
-
- :else
- x)
-
- start-y
- (cond
- (or (and (row? shape) (= :space-between layout-type))
- (and (row? shape) (= :space-around layout-type)))
- y
-
- (and (row? shape) (v-center? shape))
- (- (+ y (/ height 2)) (/ (+ line-height children-gap) 2))
-
- (and (row? shape) (v-end? shape))
- (- (+ y height) (+ line-height children-gap))
-
- (and (col? shape) (v-center? shape))
- (+ base-y (/ line-height 2))
-
- (and (col? shape) (v-end? shape))
- (+ base-y line-height)
-
- (col? shape)
- base-y
-
- :else
- y)]
- [start-x start-y]))
-
- (get-next-line
- [{:keys [line-width line-height]} base-x base-y]
- (let [next-x (if (col? shape) base-x (+ base-x line-width layout-gap))
- next-y (if (row? shape) base-y (+ base-y line-height layout-gap))]
- [next-x next-y]))
-
- (add-lines [[total-width total-height] {:keys [line-width line-height]}]
- [(+ total-width line-width)
- (+ total-height line-height)])
-
- (add-starts [[result base-x base-y] layout-line]
- (let [[start-x start-y] (get-start-line layout-line base-x base-y)
- [next-x next-y] (get-next-line layout-line base-x base-y)]
- [(conj result
- (assoc layout-line
- :start-x start-x
- :start-y start-y))
- next-x
- next-y]))]
-
- (let [[total-width total-height]
- (->> layout-lines (reduce add-lines [0 0]))
-
- total-width (+ total-width (* layout-gap (dec (count layout-lines))))
- total-height (+ total-height (* layout-gap (dec (count layout-lines))))
-
- [base-x base-y]
- (get-base-line total-width total-height)
-
- [layout-lines _ _ _ _]
- (reduce add-starts [[] base-x base-y] layout-lines)]
- layout-lines)))
-
-(defn calc-layout-line-data
- [{:keys [layout-type layout-gap] :as shape}
- {:keys [width height] :as layout-bounds}
- {:keys [num-children line-width line-height] :as line-data}]
-
- (let [layout-gap
- (cond
- (= :packed layout-type)
- layout-gap
-
- (= :space-around layout-type)
- 0
-
- (and (col? shape) (= :space-between layout-type))
- (/ (- width line-width) (dec num-children))
-
- (and (row? shape) (= :space-between layout-type))
- (/ (- height line-height) (dec num-children)))
-
- margin-x
- (if (and (col? shape) (= :space-around layout-type))
- (/ (- width line-width) (inc num-children) )
- 0)
-
- margin-y
- (if (and (row? shape) (= :space-around layout-type))
- (/ (- height line-height) (inc num-children))
- 0)]
-
- (assoc line-data
- :layout-gap layout-gap
- :margin-x margin-x
- :margin-y margin-y)))
-
-
-(defn calc-layout-data
- "Digest the layout data to pass it to the constrains"
- [{:keys [layout-flex-dir] :as shape} children layout-bounds]
-
- (let [reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir))
- layout-bounds (-> layout-bounds (add-padding shape))
- children (cond->> children reverse? reverse)
- layout-lines
- (->> (calc-layout-lines shape children layout-bounds)
- (calc-layout-lines-position shape layout-bounds)
- (map (partial calc-layout-line-data shape layout-bounds)))]
-
- {:layout-lines layout-lines
- :reverse? reverse?}))
-
-(defn next-p
- "Calculates the position for the current shape given the layout-data context"
- [shape
- {:keys [width height]}
- {:keys [start-x start-y layout-gap margin-x margin-y] :as layout-data}]
-
- (let [pos-x
- (cond
- (and (row? shape) (h-center? shape))
- (- start-x (/ width 2))
-
- (and (row? shape) (h-end? shape))
- (- start-x width)
-
- :else
- start-x)
-
- pos-y
- (cond
- (and (col? shape) (v-center? shape))
- (- start-y (/ height 2))
-
- (and (col? shape) (v-end? shape))
- (- start-y height)
-
- :else
- start-y)
-
- pos-x (cond-> pos-x (some? margin-x) (+ margin-x))
- pos-y (cond-> pos-y (some? margin-y) (+ margin-y))
-
- corner-p (gpt/point pos-x pos-y)
-
- next-x
- (if (col? shape)
- (+ start-x width layout-gap)
- start-x)
-
- next-y
- (if (row? shape)
- (+ start-y height layout-gap)
- start-y)
-
- next-x (cond-> next-x (some? margin-x) (+ margin-x))
- next-y (cond-> next-y (some? margin-y) (+ margin-y))
-
- layout-data
- (assoc layout-data :start-x next-x :start-y next-y)]
- [corner-p layout-data]))
-
-(defn calc-layout-modifiers
- "Calculates the modifiers for the layout"
- [parent transform child layout-data]
-
- (let [bounds (-> child :points gre/points->selrect)
-
- [corner-p layout-data] (next-p parent bounds layout-data)
-
- delta-p (-> corner-p
- (gpt/subtract (gpt/point bounds))
- (cond-> (some? transform) (gpt/transform transform)))
-
- modifiers {:displacement-after (gmt/translate-matrix delta-p)}]
-
- [modifiers layout-data]))
diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc
index f93929abe..36265536a 100644
--- a/common/src/app/common/geom/shapes/modifiers.cljc
+++ b/common/src/app/common/geom/shapes/modifiers.cljc
@@ -7,188 +7,161 @@
(ns app.common.geom.shapes.modifiers
(:require
[app.common.data :as d]
- [app.common.geom.matrix :as gmt]
+ [app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
- [app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.constraints :as gct]
- [app.common.geom.shapes.layout :as gcl]
- [app.common.geom.shapes.rect :as gpr]
+ [app.common.geom.shapes.flex-layout :as gcl]
+ [app.common.geom.shapes.pixel-precision :as gpp]
[app.common.geom.shapes.transforms :as gtr]
- [app.common.math :as mth]
+ [app.common.pages.helpers :as cph]
+ [app.common.spec :as us]
+ [app.common.types.modifiers :as ctm]
+ [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]))
-(defn set-pixel-precision
- "Adjust modifiers so they adjust to the pixel grid"
- [modifiers shape]
+;;#?(:cljs
+;; (defn modif->js
+;; [modif-tree objects]
+;; (clj->js (into {}
+;; (map (fn [[k v]]
+;; [(get-in objects [k :name]) v]))
+;; modif-tree))))
- (if (and (some? (:resize-transform modifiers))
- (not (gmt/unit? (:resize-transform modifiers))))
- ;; If we're working with a rotation we don't handle pixel precision because
- ;; the transformation won't have the precision anyway
- modifiers
+(defn resolve-tree-sequence
+ "Given the ids that have changed search for layout roots to recalculate"
+ [ids objects]
- (let [center (gco/center-shape shape)
- base-bounds (-> (:points shape) (gpr/points->rect))
+ (us/assert!
+ :expr (or (nil? ids) (set? ids))
+ :hint (dm/str "tree sequence from not set: " ids))
- raw-bounds
- (-> (gtr/transform-bounds (:points shape) center modifiers)
- (gpr/points->rect))
+ (letfn [(get-tree-root ;; Finds the tree root for the current id
+ [id]
- flip-x? (neg? (get-in modifiers [:resize-vector :x]))
- flip-y? (or (neg? (get-in modifiers [:resize-vector :y]))
- (neg? (get-in modifiers [:resize-vector-2 :y])))
+ (loop [current id
+ result id]
+ (let [shape (get objects current)
+ parent (get objects (:parent-id shape))]
+ (cond
+ (or (not shape) (= uuid/zero current))
+ result
- path? (= :path (:type shape))
- vertical-line? (and path? (<= (:width raw-bounds) 0.01))
- horizontal-line? (and path? (<= (:height raw-bounds) 0.01))
+ ;; Frame found, but not layout we return the last layout found (or the id)
+ (and (= :frame (:type parent))
+ (not (ctl/layout? parent)))
+ result
- target-width (if vertical-line?
- (:width raw-bounds)
- (max 1 (mth/round (:width raw-bounds))))
+ ;; Layout found. We continue upward but we mark this layout
+ (ctl/layout? parent)
+ (recur (:id parent) (:id parent))
- target-height (if horizontal-line?
- (:height raw-bounds)
- (max 1 (mth/round (:height raw-bounds))))
+ ;; If group or boolean or other type of group we continue with the last result
+ :else
+ (recur (:id parent) result)))))
- target-p (cond-> (gpt/round (gpt/point raw-bounds))
- flip-x?
- (update :x + target-width)
+ (calculate-common-roots ;; Given some roots retrieves the minimum number of tree roots
+ [result id]
+ (if (= id uuid/zero)
+ result
+ (let [root (get-tree-root id)
- flip-y?
- (update :y + target-height))
+ ;; Remove the children from the current root
+ result
+ (into #{} (remove #(cph/is-child? objects root %)) result)
- ratio-width (/ target-width (:width raw-bounds))
- ratio-height (/ target-height (:height raw-bounds))
+ contains-parent?
+ (some #(cph/is-child? objects % root) result)]
- modifiers
- (-> modifiers
- (d/without-nils)
- (d/update-in-when
- [:resize-vector :x] #(* % ratio-width))
+ (cond-> result
+ (not contains-parent?)
+ (conj root)))))
- ;; If the resize-vector-2 modifier arrives means the resize-vector
- ;; will only resize on the x axis
- (cond-> (nil? (:resize-vector-2 modifiers))
- (d/update-in-when
- [:resize-vector :y] #(* % ratio-height)))
+ (generate-tree ;; Generate a tree sequence from a given root id
+ [id]
+ (->> (tree-seq
+ #(d/not-empty? (dm/get-in objects [% :shapes]))
+ #(dm/get-in objects [% :shapes])
+ id)
+ (map #(get objects %))))]
- (d/update-in-when
- [:resize-vector-2 :y] #(* % ratio-height)))
+ (let [roots (->> ids (reduce calculate-common-roots #{}))]
+ (concat
+ (when (contains? ids uuid/zero) [(get objects uuid/zero)])
+ (mapcat generate-tree roots)))))
- origin (get modifiers :resize-origin)
- origin-2 (get modifiers :resize-origin-2)
+(defn- set-children-modifiers
+ "Propagates the modifiers from a parent too its children applying constraints if necesary"
+ [modif-tree objects parent transformed-parent ignore-constraints]
+ (let [children (:shapes parent)
+ modifiers (dm/get-in modif-tree [(:id parent) :modifiers])]
- resize-v (get modifiers :resize-vector)
- resize-v-2 (get modifiers :resize-vector-2)
- displacement (get modifiers :displacement)
+ (if (ctm/only-move? modifiers)
+ ;; Move modifiers don't need to calculate constraints
+ (loop [modif-tree modif-tree
+ children (seq children)]
+ (if-let [current (first children)]
+ (recur (update-in modif-tree [current :modifiers] ctm/add-modifiers modifiers)
+ (rest children))
+ modif-tree))
- target-p-inv
- (-> target-p
- (gpt/transform
- (cond-> (gmt/matrix)
- (some? displacement)
- (gmt/multiply (gmt/inverse displacement))
+ ;; Check the constraints, then resize
+ (let [parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers))]
+ (loop [modif-tree modif-tree
+ children (seq children)]
+ (if-let [current (first children)]
+ (let [child-modifiers (gct/calc-child-modifiers parent (get objects current) modifiers ignore-constraints transformed-parent)]
+ (recur (cond-> modif-tree
+ (not (ctm/empty? child-modifiers))
+ (update-in [current :modifiers] ctm/add-modifiers child-modifiers))
+ (rest children)))
+ modif-tree))))))
- (and (some? resize-v) (some? origin))
- (gmt/scale (gpt/inverse resize-v) origin)
-
- (and (some? resize-v-2) (some? origin-2))
- (gmt/scale (gpt/inverse resize-v-2) origin-2))))
-
- delta-v (gpt/subtract target-p-inv (gpt/point base-bounds))
-
- modifiers
- (-> modifiers
- (d/update-when :displacement #(gmt/multiply (gmt/translate-matrix delta-v) %))
- (cond-> (nil? (:displacement modifiers))
- (assoc :displacement (gmt/translate-matrix delta-v))))]
- modifiers)))
-
-
-(defn set-children-modifiers
- [modif-tree shape objects ignore-constraints snap-pixel?]
- (letfn [(set-child [transformed-rect snap-pixel? modif-tree child]
- (let [modifiers (get-in modif-tree [(:id shape) :modifiers])
- child-modifiers (gct/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)
- child-modifiers (cond-> child-modifiers snap-pixel? (set-pixel-precision child))]
+(defn- process-layout-children
+ [modif-tree objects parent transformed-parent]
+ (letfn [(process-child [modif-tree child]
+ (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers])
+ child-modifiers (-> modifiers
+ (ctm/select-child-geometry-modifiers)
+ (gcl/normalize-child-modifiers parent child transformed-parent))]
(cond-> modif-tree
- (not (gtr/empty-modifiers? child-modifiers))
- (update-in [(:id child) :modifiers] #(merge child-modifiers %)))))]
- (let [children (map (d/getf objects) (:shapes shape))
- modifiers (get-in modif-tree [(:id shape) :modifiers])
- transformed-rect (gtr/transform-selrect (:selrect shape) modifiers)
- resize-modif? (or (:resize-vector modifiers) (:resize-vector-2 modifiers))]
- (reduce (partial set-child transformed-rect (and snap-pixel? resize-modif?)) modif-tree children))))
+ (not (ctm/empty? child-modifiers))
+ (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))]
+ (let [children (map (d/getf objects) (:shapes transformed-parent))]
+ (reduce process-child modif-tree children))))
-(defn group? [shape]
- (or (= :group (:type shape))
- (= :bool (:type shape))))
+(defn- set-layout-modifiers
+ [modif-tree objects parent]
-(defn merge-modifiers
- [modif-tree ids modifiers]
- (reduce
- (fn [modif-tree id]
- (update-in modif-tree [id :modifiers] #(merge % modifiers)))
- modif-tree
- ids))
+ (letfn [(apply-modifiers [modif-tree child]
+ (let [modifiers (-> (dm/get-in modif-tree [(:id child) :modifiers])
+ (ctm/select-geometry))]
+ (cond
+ (cph/group-like-shape? child)
+ (gtr/apply-group-modifiers child objects modif-tree)
-(defn set-layout-modifiers
- [modif-tree objects id]
+ (some? modifiers)
+ (gtr/transform-shape child modifiers)
- (letfn [(transform-child [parent child]
- (let [modifiers (get modif-tree (:id child))
+ :else
+ child)))
- child
- (cond-> child
- (not (group? child))
- (-> (merge modifiers) gtr/transform-shape)
-
- (group? child)
- (gtr/apply-group-modifiers objects modif-tree))
-
- child
- (-> child
- (gtr/apply-transform (gmt/transform-in (gco/center-shape parent) (:transform-inverse parent))))]
-
- child))
-
- (set-layout-modifiers [parent transform [layout-data modif-tree] child]
- (let [[modifiers layout-data]
- (gcl/calc-layout-modifiers parent transform child layout-data)
+ (set-child-modifiers [parent [layout-line modif-tree] child]
+ (let [[modifiers layout-line]
+ (gcl/layout-child-modifiers parent child layout-line)
modif-tree
(cond-> modif-tree
- (not (gtr/empty-modifiers? modifiers))
- (merge-modifiers [(:id child)] modifiers)
+ (d/not-empty? modifiers)
+ (update-in [(:id child) :modifiers] ctm/add-modifiers modifiers))]
- (and (not (gtr/empty-modifiers? modifiers)) (group? child))
- (merge-modifiers (:shapes child) modifiers))]
+ [layout-line modif-tree]))]
- [layout-data modif-tree]))]
-
- (let [modifiers (get modif-tree id)
-
- shape (-> (get objects id) (merge modifiers) gtr/transform-shape)
-
-
- children (->> (:shapes shape)
- (map (d/getf objects))
- (map (partial transform-child shape)))
-
- center (gco/center-shape shape)
- {:keys [transform transform-inverse]} shape
-
- shape
- (-> shape
- (gtr/apply-transform (gmt/transform-in center transform-inverse)))
-
- transformed-rect (:selrect shape)
-
- layout-data (gcl/calc-layout-data shape children transformed-rect)
- children (into [] (cond-> children (:reverse? layout-data) reverse))
-
- max-idx (dec (count children))
- layout-lines (:layout-lines layout-data)]
+ (let [children (map (d/getf objects) (:shapes parent))
+ children (->> children (map (partial apply-modifiers modif-tree)))
+ layout-data (gcl/calc-layout-data parent children)
+ children (into [] (cond-> children (:reverse? layout-data) reverse))
+ max-idx (dec (count children))
+ layout-lines (:layout-lines layout-data)]
(loop [modif-tree modif-tree
layout-line (first layout-lines)
@@ -199,92 +172,146 @@
children (subvec children from-idx to-idx)
[_ modif-tree]
- (reduce (partial set-layout-modifiers shape transform) [layout-line modif-tree] children)]
-
+ (reduce (partial set-child-modifiers parent) [layout-line modif-tree] children)]
(recur modif-tree (first pending) (rest pending) to-idx))
modif-tree)))))
-(defn get-first-layout
- [id objects]
+(defn- calc-auto-modifiers
+ "Calculates the modifiers to adjust the bounds for auto-width/auto-height shapes"
+ [objects parent]
+ (letfn [(set-parent-auto-width
+ [modifiers auto-width]
+ (let [origin (-> parent :points first)
+ scale-width (/ auto-width (-> parent :selrect :width) )]
+ (-> modifiers
+ (ctm/resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent)))))
- (loop [current id
- result id]
- (let [shape (get objects current)
- parent (get objects (:parent-id shape))]
- (cond
- (or (not shape) (= uuid/zero current))
- result
+ (set-parent-auto-height
+ [modifiers auto-height]
+ (let [origin (-> parent :points first)
+ scale-height (/ auto-height (-> parent :selrect :height) )]
+ (-> modifiers
+ (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))]
- ;; Frame found, but not layout we return the last layout found (or the id)
- (and (= :frame (:type parent))
- (not (:layout parent)))
- result
+ (let [children (->> parent :shapes (map (d/getf objects)))
- ;; Layout found. We continue upward but we mark this layout
- (and (= :frame (:type parent))
- (:layout parent))
- (:id parent)
+ {auto-width :width auto-height :height}
+ (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent)))
+ (gcl/layout-content-bounds parent children))]
- ;; If group or boolean or other type of group we continue with the last result
- :else
- (recur (:id parent) result)))))
+ (cond-> (ctm/empty)
+ (and (some? auto-width) (ctl/auto-width? parent))
+ (set-parent-auto-width auto-width)
-(defn resolve-layout-ids
- "Given a list of ids, resolve the parent layouts that will need to update. This will go upwards
- in the tree while a layout is found"
- [ids objects]
+ (and (some? auto-height) (ctl/auto-height? parent))
+ (set-parent-auto-height auto-height)))))
- (into (d/ordered-set)
- (map #(get-first-layout % objects))
- ids))
+(defn- propagate-modifiers
+ "Propagate modifiers to its children"
+ [objects ignore-constraints [modif-tree autolayouts] parent]
+ (let [parent-id (:id parent)
+ root? (= uuid/zero parent-id)
+ modifiers (-> (dm/get-in modif-tree [parent-id :modifiers])
+ (ctm/select-geometry))
+ transformed-parent (gtr/transform-shape parent modifiers)
+
+ has-modifiers? (ctm/child-modifiers? modifiers)
+ layout? (ctl/layout? parent)
+ auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent))
+ parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent))
+
+ ;; If the current child is inside the layout we ignore the constraints
+ inside-layout? (ctl/inside-layout? objects parent)]
+
+ [(cond-> modif-tree
+ (and (not layout?) has-modifiers? parent? (not root?))
+ (set-children-modifiers objects parent transformed-parent (or ignore-constraints inside-layout?))
+
+ layout?
+ (-> (process-layout-children objects parent transformed-parent)
+ (set-layout-modifiers objects transformed-parent)))
+
+ ;; Auto-width/height can change the positions in the parent so we need to recalculate
+ (cond-> autolayouts auto? (conj (:id parent)))]))
+
+(defn- apply-structure-modifiers
+ [objects modif-tree]
+ (letfn [(apply-shape [objects [id {:keys [modifiers]}]]
+ (cond-> objects
+ (ctm/has-structure? modifiers)
+ (update id ctm/apply-structure-modifiers modifiers)))]
+ (reduce apply-shape objects modif-tree)))
+
+(defn- apply-partial-objects-modifiers
+ [objects tree-seq modif-tree]
+
+ (letfn [(apply-shape [objects {:keys [id] :as shape}]
+ (if (cph/group-shape? shape)
+ (let [children (cph/get-children objects id)]
+ (assoc objects id
+ (cond
+ (cph/mask-shape? shape)
+ (gtr/update-mask-selrect shape children)
+
+ :else
+ (gtr/update-group-selrect shape children))))
+
+ (let [modifiers (get-in modif-tree [id :modifiers])
+ object (cond-> shape
+ (some? modifiers)
+ (gtr/transform-shape modifiers))]
+ (assoc objects id object))))]
+
+ (reduce apply-shape objects (reverse tree-seq))))
+
+(defn merge-modif-tree
+ [modif-tree other-tree]
+ (reduce (fn [modif-tree [id {:keys [modifiers]}]]
+ (update-in modif-tree [id :modifiers] ctm/add-modifiers modifiers))
+ modif-tree
+ other-tree))
+
+(defn sizing-auto-modifiers
+ "Recalculates the layouts to adjust the sizing: auto new sizes"
+ [modif-tree sizing-auto-layouts objects ignore-constraints]
+ (loop [modif-tree modif-tree
+ sizing-auto-layouts (reverse sizing-auto-layouts)]
+ (if-let [current (first sizing-auto-layouts)]
+ (let [parent-base (get objects current)
+ tree-seq (resolve-tree-sequence #{current} objects)
+
+ ;; Apply the current stack of transformations so we can calculate the auto-layouts
+ objects (apply-partial-objects-modifiers objects tree-seq modif-tree)
+
+ resize-modif-tree
+ {current {:modifiers (calc-auto-modifiers objects parent-base)}}
+
+ tree-seq (resolve-tree-sequence #{current} objects)
+
+ [resize-modif-tree _]
+ (reduce (partial propagate-modifiers objects ignore-constraints) [resize-modif-tree #{}] tree-seq)
+
+ modif-tree (merge-modif-tree modif-tree resize-modif-tree)]
+ (recur modif-tree (rest sizing-auto-layouts)))
+ modif-tree)))
(defn set-objects-modifiers
- [ids objects get-modifier ignore-constraints snap-pixel?]
+ [modif-tree objects ignore-constraints snap-pixel?]
- (let [set-modifiers
- (fn [modif-tree id]
- (let [shape (get objects id)
- modifiers (cond-> (get-modifier shape) snap-pixel? (set-pixel-precision shape))]
- (-> modif-tree
- (assoc id {:modifiers modifiers}))))
+ (let [objects (apply-structure-modifiers objects modif-tree)
+ shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects)
- modif-tree (reduce set-modifiers {} ids)
+ [modif-tree sizing-auto-layouts]
+ (reduce (partial propagate-modifiers objects ignore-constraints) [modif-tree #{}] shapes-tree)
- ids (resolve-layout-ids ids objects)
+ ;; Calculate hug layouts positions
+ modif-tree (sizing-auto-modifiers modif-tree sizing-auto-layouts objects ignore-constraints)
- ;; First: Calculate children modifiers (constraints, etc)
- [modif-tree touched-layouts]
- (loop [current (first ids)
- pending (rest ids)
- modif-tree modif-tree
- touched-layouts (d/ordered-set)]
- (if (some? current)
- (let [shape (get objects current)
- pending (concat pending (:shapes shape))
-
- touched-layouts
- (cond-> touched-layouts
- (:layout shape)
- (conj (:id shape)))
-
- modif-tree
- (-> modif-tree
- (set-children-modifiers shape objects ignore-constraints snap-pixel?))]
-
- (recur (first pending) (rest pending) modif-tree touched-layouts))
-
- [modif-tree touched-layouts]))
-
- ;; Second: Calculate layout positioning
modif-tree
- (loop [current (first touched-layouts)
- pending (rest touched-layouts)
- modif-tree modif-tree]
-
- (if (some? current)
- (let [modif-tree (set-layout-modifiers modif-tree objects current)]
- (recur (first pending) (rest pending) modif-tree))
- modif-tree))]
+ (cond-> modif-tree
+ snap-pixel? (gpp/adjust-pixel-precision objects))]
+ ;;#?(:cljs
+ ;; (.log js/console ">result" (modif->js modif-tree objects)))
modif-tree))
diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc
new file mode 100644
index 000000000..93dd26dcd
--- /dev/null
+++ b/common/src/app/common/geom/shapes/pixel_precision.cljc
@@ -0,0 +1,71 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.pixel-precision
+ (:require
+ [app.common.data :as d]
+ [app.common.data.macros :as dm]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.geom.shapes.rect :as gpr]
+ [app.common.geom.shapes.transforms :as gtr]
+ [app.common.math :as mth]
+ [app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]))
+
+(defn size-pixel-precision
+ [modifiers {:keys [points transform transform-inverse] :as shape}]
+ (let [origin (gpo/origin points)
+ curr-width (gpo/width-points points)
+ curr-height (gpo/height-points points)
+
+ path? (cph/path-shape? shape)
+ vertical-line? (and path? (<= curr-width 0.01))
+ horizontal-line? (and path? (<= curr-height 0.01))
+
+ target-width (if vertical-line? curr-width (max 1 (mth/round curr-width)))
+ target-height (if horizontal-line? curr-height (max 1 (mth/round curr-height)))
+
+ ratio-width (/ target-width curr-width)
+ ratio-height (/ target-height curr-height)
+ scalev (gpt/point ratio-width ratio-height)]
+ (-> modifiers
+ (ctm/resize scalev origin transform transform-inverse))))
+
+(defn position-pixel-precision
+ [modifiers {:keys [points]}]
+ (let [bounds (gpr/points->rect points)
+ corner (gpt/point bounds)
+ target-corner (gpt/round corner)
+ deltav (gpt/to-vec corner target-corner)]
+ (-> modifiers
+ (ctm/move deltav))))
+
+(defn set-pixel-precision
+ "Adjust modifiers so they adjust to the pixel grid"
+ [modifiers shape]
+ (let [move? (ctm/only-move? modifiers)]
+ (cond-> modifiers
+ (not move?)
+ (size-pixel-precision shape)
+
+ :always
+ (position-pixel-precision shape))))
+
+(defn adjust-pixel-precision
+ [modif-tree objects]
+ (let [update-modifiers
+ (fn [modif-tree shape]
+ (let [modifiers (dm/get-in modif-tree [(:id shape) :modifiers])]
+ (if-not (ctm/has-geometry? modifiers)
+ modif-tree
+ (let [shape (gtr/transform-shape shape modifiers)]
+ (-> modif-tree
+ (update-in [(:id shape) :modifiers] set-pixel-precision shape))))))]
+
+ (->> (keys modif-tree)
+ (map (d/getf objects))
+ (reduce update-modifiers modif-tree))))
diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc
new file mode 100644
index 000000000..adae25ace
--- /dev/null
+++ b/common/src/app/common/geom/shapes/points.cljc
@@ -0,0 +1,62 @@
+;; 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 INC
+
+(ns app.common.geom.shapes.points
+ (:require
+ [app.common.geom.point :as gpt]))
+
+(defn origin
+ [points]
+ (nth points 0))
+
+(defn start-hv
+ "Horizontal vector from the origin with a magnitude `val`"
+ [[p0 p1 _ _] val]
+ (-> (gpt/to-vec p0 p1)
+ (gpt/unit)
+ (gpt/scale val)))
+
+(defn end-hv
+ "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`"
+ [[p0 p1 _ _] val]
+ (-> (gpt/to-vec p1 p0)
+ (gpt/unit)
+ (gpt/scale val)))
+
+(defn start-vv
+ "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`"
+ [[p0 _ _ p3] val]
+ (-> (gpt/to-vec p0 p3)
+ (gpt/unit)
+ (gpt/scale val)))
+
+(defn end-vv
+ "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`"
+ [[p0 _ _ p3] val]
+ (-> (gpt/to-vec p3 p0)
+ (gpt/unit)
+ (gpt/scale val)))
+
+(defn width-points
+ [[p0 p1 _ _]]
+ (gpt/length (gpt/to-vec p0 p1)))
+
+(defn height-points
+ [[p0 _ _ p3]]
+ (gpt/length (gpt/to-vec p0 p3)))
+
+(defn pad-points
+ [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left]
+ (when (some? points)
+ (let [top-v (start-vv points pad-top)
+ right-v (end-hv points pad-right)
+ bottom-v (end-vv points pad-bottom)
+ left-v (start-hv points pad-left)]
+
+ [(-> p0 (gpt/add left-v) (gpt/add top-v))
+ (-> p1 (gpt/add right-v) (gpt/add top-v))
+ (-> p2 (gpt/add right-v) (gpt/add bottom-v))
+ (-> p3 (gpt/add left-v) (gpt/add bottom-v))])))
diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc
index 6637b7533..be8d5a5b5 100644
--- a/common/src/app/common/geom/shapes/rect.cljc
+++ b/common/src/app/common/geom/shapes/rect.cljc
@@ -11,14 +11,25 @@
[app.common.math :as mth]))
(defn make-rect
- [x y width height]
- (when (d/num? x y width height)
- (let [width (max width 0.01)
- height (max height 0.01)]
- {:x x
- :y y
- :width width
- :height height})))
+ ([p1 p2]
+ (let [xp1 (:x p1)
+ yp1 (:y p1)
+ xp2 (:x p2)
+ yp2 (:y p2)
+ x1 (min xp1 xp2)
+ y1 (min yp1 yp2)
+ x2 (max xp1 xp2)
+ y2 (max yp1 yp2)]
+ (make-rect x1 y1 (- x2 x1) (- y2 y1))))
+
+ ([x y width height]
+ (when (d/num? x y width height)
+ (let [width (max width 0.01)
+ height (max height 0.01)]
+ {:x x
+ :y y
+ :width width
+ :height height}))))
(defn make-selrect
[x y width height]
@@ -80,6 +91,19 @@
(when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny))))))
+(defn squared-points
+ [points]
+ (when (d/not-empty? points)
+ (let [minx (transduce (keep :x) min ##Inf points)
+ miny (transduce (keep :y) min ##Inf points)
+ maxx (transduce (keep :x) max ##-Inf points)
+ maxy (transduce (keep :y) max ##-Inf points)]
+ (when (d/num? minx miny maxx maxy)
+ [(gpt/point minx miny)
+ (gpt/point maxx miny)
+ (gpt/point maxx maxy)
+ (gpt/point minx maxy)]))))
+
(defn points->selrect [points]
(when-let [rect (points->rect points)]
(let [{:keys [x y width height]} rect]
@@ -168,3 +192,10 @@
(>= (:y1 sr2) (:y1 sr1))
(<= (:y2 sr2) (:y2 sr1))))
+(defn corners->selrect
+ [p1 p2]
+ (let [xp1 (:x p1)
+ xp2 (:x p2)
+ yp1 (:y p1)
+ yp2 (:y p2)]
+ (make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2)))))
diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc
index be2792109..e8c942fd6 100644
--- a/common/src/app/common/geom/shapes/transforms.cljc
+++ b/common/src/app/common/geom/shapes/transforms.cljc
@@ -10,12 +10,14 @@
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes.bool :as gshb]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]
- [app.common.spec :as us]
- [app.common.text :as txt]))
+ [app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]
+ [app.common.uuid :as uuid]))
(def ^:dynamic *skip-adjust* false)
@@ -76,14 +78,6 @@
; ---- Geometric operations
-(defn- normalize-scale
- "We normalize the scale so it's not too close to 0"
- [scale]
- (cond
- (and (< scale 0) (> scale -0.01)) -0.01
- (and (>= scale 0) (< scale 0.01)) 0.01
- :else scale))
-
(defn- calculate-skew-angle
"Calculates the skew angle of the parallelogram given by the points"
[[p1 _ p3 p4]]
@@ -99,6 +93,7 @@
(defn- calculate-height
"Calculates the height of a parallelogram given by the points"
[[p1 _ _ p4]]
+
(-> (gpt/to-vec p4 p1)
(gpt/length)))
@@ -182,17 +177,6 @@
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/translate (gpt/negate center)))))
-(defn transform-point-center
- "Transform a point around the shape center"
- [point center matrix]
- (if (and (some? point) (some? matrix) (some? center))
- (gpt/transform
- point
- (gmt/multiply (gmt/translate-matrix center)
- matrix
- (gmt/translate-matrix (gpt/negate center))))
- point))
-
(defn transform-rect
"Transform a rectangles and changes its attributes"
[rect matrix]
@@ -205,55 +189,52 @@
"Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that
after applying them the end result is the `shape-path-temp`.
This is compose of three transformations: skew, resize and rotation"
- ([points-temp points-rec]
- (calculate-adjust-matrix points-temp points-rec false false))
+ [points-temp points-rec flip-x flip-y]
+ (let [center (gco/center-bounds points-temp)
- ([points-temp points-rec flip-x flip-y]
- (let [center (gco/center-points points-temp)
+ stretch-matrix (gmt/matrix)
- stretch-matrix (gmt/matrix)
+ skew-angle (calculate-skew-angle points-temp)
- skew-angle (calculate-skew-angle points-temp)
+ ;; When one of the axis is flipped we have to reverse the skew
+ ;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle )
+ skew-angle (if (and (or flip-x flip-y)
+ (not (and flip-x flip-y))) (- skew-angle) skew-angle )
+ skew-angle (if (mth/nan? skew-angle) 0 skew-angle)
- ;; When one of the axis is flipped we have to reverse the skew
- ;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle )
- skew-angle (if (and (or flip-x flip-y)
- (not (and flip-x flip-y))) (- skew-angle) skew-angle )
- skew-angle (if (mth/nan? skew-angle) 0 skew-angle)
+ stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
- stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
+ h1 (max 1 (calculate-height points-temp))
+ h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix)))
+ h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1)
+ h3 (if (mth/nan? h3) 1 h3)
- h1 (max 1 (calculate-height points-temp))
- h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix)))
- h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1)
- h3 (if (mth/nan? h3) 1 h3)
+ w1 (max 1 (calculate-width points-temp))
+ w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
+ w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
+ w3 (if (mth/nan? w3) 1 w3)
- w1 (max 1 (calculate-width points-temp))
- w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
- w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
- w3 (if (mth/nan? w3) 1 w3)
+ stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3)))
- stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3)))
+ rotation-angle (calculate-rotation
+ center
+ (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix)
+ points-temp
+ flip-x
+ flip-y)
- rotation-angle (calculate-rotation
- center
- (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix)
- points-temp
- flip-x
- flip-y)
+ stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
- stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
-
- ;; This is the inverse to be able to remove the transformation
- stretch-matrix-inverse
- (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
- (gmt/skew-matrix (- skew-angle) 0)
- (gmt/rotate-matrix (- rotation-angle)))]
- [stretch-matrix stretch-matrix-inverse rotation-angle])))
+ ;; This is the inverse to be able to remove the transformation
+ stretch-matrix-inverse
+ (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
+ (gmt/skew-matrix (- skew-angle) 0)
+ (gmt/rotate-matrix (- rotation-angle)))]
+ [stretch-matrix stretch-matrix-inverse rotation-angle]))
(defn- adjust-rotated-transform
[{:keys [transform transform-inverse flip-x flip-y]} points]
- (let [center (gco/center-points points)
+ (let [center (gco/center-bounds points)
points-temp (cond-> points
(some? transform-inverse)
@@ -273,13 +254,59 @@
(if transform (gmt/multiply transform matrix) matrix)
(if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)]))
-(defn apply-transform
+(defn- adjust-shape-flips
+ "After some tranformations the flip-x/flip-y flags can change we need
+ to check this before adjusting the selrect"
+ [shape points]
+
+ (let [points' (:points shape)
+
+ xv1 (gpt/to-vec (nth points' 0) (nth points' 1))
+ xv2 (gpt/to-vec (nth points 0) (nth points 1))
+ dot-x (gpt/dot xv1 xv2)
+
+ yv1 (gpt/to-vec (nth points' 0) (nth points' 3))
+ yv2 (gpt/to-vec (nth points 0) (nth points 3))
+ dot-y (gpt/dot yv1 yv2)]
+
+ (cond-> shape
+ (neg? dot-x)
+ (-> (update :flip-x not)
+ (update :rotation -))
+
+ (neg? dot-y)
+ (-> (update :flip-y not)
+ (update :rotation -)))))
+
+(defn- apply-transform-move
+ "Given a new set of points transformed, set up the rectangle so it keeps
+ its properties. We adjust de x,y,width,height and create a custom transform"
+ [shape transform-mtx]
+ (let [bool? (= (:type shape) :bool)
+ path? (= (:type shape) :path)
+ points (gco/transform-points (:points shape) transform-mtx)
+ selrect (gco/transform-selrect (:selrect shape) transform-mtx)]
+ (-> shape
+ (cond-> bool?
+ (update :bool-content gpa/transform-content transform-mtx))
+ (cond-> path?
+ (update :content gpa/transform-content transform-mtx))
+ (cond-> (not path?)
+ (assoc :x (:x selrect)
+ :y (:y selrect)
+ :width (:width selrect)
+ :height (:height selrect)))
+ (assoc :selrect selrect)
+ (assoc :points points))))
+
+(defn- apply-transform-generic
"Given a new set of points transformed, set up the rectangle so it keeps
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx]
(let [points' (:points shape)
points (gco/transform-points points' transform-mtx)
+ shape (-> shape (adjust-shape-flips points))
bool? (= (:type shape) :bool)
path? (= (:type shape) :path)
@@ -289,14 +316,16 @@
base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
rotation (mod (+ base-rotation modif-rotation) 360)]
-
(-> shape
(cond-> bool?
(update :bool-content gpa/transform-content transform-mtx))
(cond-> path?
(update :content gpa/transform-content transform-mtx))
(cond-> (not path?)
- (-> (merge (select-keys selrect [:x :y :width :height]))))
+ (assoc :x (:x selrect)
+ :y (:y selrect)
+ :width (:width selrect)
+ :height (:height selrect)))
(cond-> transform
(-> (assoc :transform transform)
(assoc :transform-inverse transform-inverse)))
@@ -304,10 +333,19 @@
(dissoc :transform :transform-inverse))
(cond-> (some? selrect)
(assoc :selrect selrect))
+
(cond-> (d/not-empty? points)
(assoc :points points))
(assoc :rotation rotation))))
+(defn- apply-transform
+ "Given a new set of points transformed, set up the rectangle so it keeps
+ its properties. We adjust de x,y,width,height and create a custom transform"
+ [shape transform-mtx]
+ (if (gmt/move? transform-mtx)
+ (apply-transform-move shape transform-mtx)
+ (apply-transform-generic shape transform-mtx)))
+
(defn- update-group-viewbox
"Updates the viewbox for groups imported from SVG's"
[{:keys [selrect svg-viewbox] :as group} new-selrect]
@@ -332,6 +370,9 @@
;; Points for every shape inside the group
points (->> children (mapcat :points))
+ ;; Fixed problem with empty groups. Should not happen (but it does)
+ points (if (empty? points) (:points group) points)
+
;; Invert to get the points minus the transforms applied to the group
base-points (gco/transform-points points shape-center (:transform-inverse group (gmt/matrix)))
@@ -368,284 +409,74 @@
(assoc :flip-x (-> mask :flip-x))
(assoc :flip-y (-> mask :flip-y)))))
-;; --- Modifiers
+(defn update-bool-selrect
+ "Calculates the selrect+points for the boolean shape"
+ [shape children objects]
-;; The `modifiers` structure contains a list of transformations to
-;; do make to a shape, in this order:
-;;
-;; - resize-origin (gpt/point) + resize-vector (gpt/point)
-;; apply a scale vector to all points of the shapes, starting
-;; from the origin point.
-;;
-;; - resize-origin-2 + resize-vector-2
-;; same as the previous one, for cases in that we need to make
-;; two vectors from different origin points.
-;;
-;; - displacement (gmt/matrix)
-;; apply a translation matrix to the shape
-;;
-;; - rotation (gmt/matrix)
-;; apply a rotation matrix to the shape
-;;
-;; - resize-transform (gmt/matrix) + resize-transform-inverse (gmt/matrix)
-;; a copy of the rotation matrix currently applied to the shape;
-;; this is needed temporarily to apply the resize vectors.
-;;
-;; - resize-scale-text (bool)
-;; tells if the resize vectors must be applied to text shapes
-;; or not.
+ (let [bool-content (gshb/calc-bool-content shape objects)
+ shape (assoc shape :bool-content bool-content)
+ [points selrect] (gpa/content->points+selrect shape bool-content)]
-(defn empty-modifiers? [modifiers]
- (empty? (dissoc modifiers :ignore-geometry?)))
-
-(defn resize-modifiers
- [shape attr value]
- (us/assert map? shape)
- (us/assert #{:width :height} attr)
- (us/assert number? value)
- (let [{:keys [proportion proportion-lock]} shape
- size (select-keys (:selrect shape) [:width :height])
- new-size (if-not proportion-lock
- (assoc size attr value)
- (if (= attr :width)
- (-> size
- (assoc :width value)
- (assoc :height (/ value proportion)))
- (-> size
- (assoc :height value)
- (assoc :width (* value proportion)))))
- width (:width new-size)
- height (:height new-size)
-
- shape-transform (:transform shape)
- shape-transform-inv (:transform-inverse shape)
- shape-center (gco/center-shape shape)
- {sr-width :width sr-height :height} (:selrect shape)
-
- origin (cond-> (gpt/point (:selrect shape))
- (some? shape-transform)
- (transform-point-center shape-center shape-transform))
-
- scalev (gpt/divide (gpt/point width height)
- (gpt/point sr-width sr-height))]
- {:resize-vector scalev
- :resize-origin origin
- :resize-transform shape-transform
- :resize-transform-inverse shape-transform-inv}))
-
-(defn change-orientation-modifiers
- [shape orientation]
- (us/assert map? shape)
- (us/verify #{:horiz :vert} orientation)
- (let [width (:width shape)
- height (:height shape)
- new-width (if (= orientation :horiz) (max width height) (min width height))
- new-height (if (= orientation :horiz) (min width height) (max width height))
-
- shape-transform (:transform shape)
- shape-transform-inv (:transform-inverse shape)
- shape-center (gco/center-shape shape)
- {sr-width :width sr-height :height} (:selrect shape)
-
- origin (cond-> (gpt/point (:selrect shape))
- (some? shape-transform)
- (transform-point-center shape-center shape-transform))
-
- scalev (gpt/divide (gpt/point new-width new-height)
- (gpt/point sr-width sr-height))]
- {:resize-vector scalev
- :resize-origin origin
- :resize-transform shape-transform
- :resize-transform-inverse shape-transform-inv}))
-
-(defn rotation-modifiers
- [shape center angle]
- (let [displacement (let [shape-center (gco/center-shape shape)]
- (-> (gmt/matrix)
- (gmt/rotate angle center)
- (gmt/rotate (- angle) shape-center)))]
- {:rotation angle
- :displacement displacement}))
-
-(defn merge-modifiers
- [objects modifiers]
-
- (let [set-modifier
- (fn [objects [id modifiers]]
- (-> objects
- (d/update-when id merge modifiers)))]
- (->> modifiers
- (reduce set-modifier objects))))
-
-(defn modifiers->transform
- ([modifiers]
- (modifiers->transform nil modifiers))
-
- ([center modifiers]
- (let [displacement (:displacement modifiers)
- displacement-after (:displacement-after modifiers)
- resize-v1 (:resize-vector modifiers)
- resize-v2 (:resize-vector-2 modifiers)
- origin-1 (:resize-origin modifiers (gpt/point))
- origin-2 (:resize-origin-2 modifiers (gpt/point))
-
- ;; Normalize x/y vector coordinates because scale by 0 is infinite
- resize-1 (when (some? resize-v1)
- (gpt/point (normalize-scale (:x resize-v1))
- (normalize-scale (:y resize-v1))))
-
- resize-2 (when (some? resize-v2)
- (gpt/point (normalize-scale (:x resize-v2))
- (normalize-scale (:y resize-v2))))
-
-
- resize-transform (:resize-transform modifiers)
- resize-transform-inverse (:resize-transform-inverse modifiers)
-
- rt-modif (:rotation modifiers)]
-
- (cond-> (gmt/matrix)
- (some? displacement-after)
- (gmt/multiply displacement-after)
-
- (some? resize-1)
- (-> (gmt/translate origin-1)
- (cond-> (some? resize-transform)
- (gmt/multiply resize-transform))
- (gmt/scale resize-1)
- (cond-> (some? resize-transform-inverse)
- (gmt/multiply resize-transform-inverse))
- (gmt/translate (gpt/negate origin-1)))
-
- (some? resize-2)
- (-> (gmt/translate origin-2)
- (cond-> (some? resize-transform)
- (gmt/multiply resize-transform))
- (gmt/scale resize-2)
- (cond-> (some? resize-transform-inverse)
- (gmt/multiply resize-transform-inverse))
- (gmt/translate (gpt/negate origin-2)))
-
- (some? displacement)
- (gmt/multiply displacement)
-
- (some? rt-modif)
- (-> (gmt/translate center)
- (gmt/multiply (gmt/rotate-matrix rt-modif))
- (gmt/translate (gpt/negate center)))))))
-
-(defn- set-flip [shape modifiers]
- (let [rv1x (or (get-in modifiers [:resize-vector :x]) 1)
- rv1y (or (get-in modifiers [:resize-vector :y]) 1)
- rv2x (or (get-in modifiers [:resize-vector-2 :x]) 1)
- rv2y (or (get-in modifiers [:resize-vector-2 :y]) 1)]
- (cond-> shape
- (or (neg? rv1x) (neg? rv2x))
- (-> (update :flip-x not)
- (update :rotation -))
- (or (neg? rv1y) (neg? rv2y))
- (-> (update :flip-y not)
- (update :rotation -)))))
-
-(defn- apply-displacement [shape]
- (let [modifiers (:modifiers shape)]
- (if (contains? modifiers :displacement)
- (let [mov-vec (-> (gpt/point 0 0)
- (gpt/transform (:displacement modifiers)))
- shape (move shape mov-vec)
- modifiers (dissoc modifiers :displacement)]
- (-> shape
- (assoc :modifiers modifiers)
- (cond-> (empty-modifiers? modifiers)
- (dissoc :modifiers))))
- shape)))
-
-(defn- apply-text-resize
- [shape modifiers]
- (if (and (= (:type shape) :text)
- (:resize-scale-text modifiers))
- (let [merge-attrs (fn [attrs]
- (let [font-size (-> (get attrs :font-size 14)
- (d/parse-double)
- (* (get-in modifiers [:resize-vector :x] 1))
- (* (get-in modifiers [:resize-vector-2 :x] 1))
- (str))]
- (d/txt-merge attrs {:font-size font-size})))]
- (update shape :content #(txt/transform-nodes
- txt/is-text-node?
- merge-attrs
- %)))
- shape))
-
-(defn apply-modifiers
- [shape modifiers]
- (let [center (gco/center-shape shape)
- transform (modifiers->transform center modifiers)]
- (apply-transform shape transform)))
+ (if (and (some? selrect) (d/not-empty? points))
+ (-> shape
+ (assoc :selrect selrect)
+ (assoc :points points))
+ (update-group-selrect shape children))))
(defn transform-shape
- [shape]
- (let [modifiers (:modifiers shape)]
- (cond
- (nil? modifiers)
- shape
+ ([shape]
+ (let [modifiers (:modifiers shape)]
+ (-> shape
+ (dissoc :modifiers)
+ (transform-shape modifiers))))
- (empty-modifiers? modifiers)
- (dissoc shape :modifiers)
+ ([shape modifiers]
+ (letfn [(apply-modifiers
+ [shape modifiers]
+ (if (ctm/empty? modifiers)
+ shape
+ (let [transform (ctm/modifiers->transform modifiers)]
+ (cond-> shape
+ (and (some? transform) (not= uuid/zero (:id shape))) ;; Never transform the root frame
+ (apply-transform transform)
- :else
- (let [shape (apply-displacement shape)
- modifiers (:modifiers shape)]
- (cond-> shape
- (not (empty-modifiers? modifiers))
- (-> (set-flip modifiers)
- (apply-modifiers modifiers)
- (apply-text-resize modifiers))
+ (ctm/has-structure? modifiers)
+ (ctm/apply-structure-modifiers modifiers)))))]
- :always
- (dissoc :modifiers))))))
+ (cond-> shape
+ (and (some? modifiers) (not (ctm/empty? modifiers)))
+ (apply-modifiers modifiers)))))
+
+(defn apply-objects-modifiers
+ [objects modifiers]
+
+ (loop [objects objects
+ entry (first modifiers)
+ modifiers (rest modifiers)]
+
+ (if (nil? entry)
+ objects
+ (let [[id modifier] entry]
+ (recur (d/update-when objects id transform-shape (:modifiers modifier))
+ (first modifiers)
+ (rest modifiers))))))
(defn transform-bounds
- [points center {:keys [displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
- ;; FIXME: Improve Performance
- (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
+ ([points modifiers]
+ (transform-bounds points nil modifiers))
- displacement
- (when (some? displacement)
- (gmt/multiply resize-transform-inverse displacement))
-
- resize-origin
- (when (some? resize-origin)
- (transform-point-center resize-origin center resize-transform-inverse))
-
- resize-origin-2
- (when (some? resize-origin-2)
- (transform-point-center resize-origin-2 center resize-transform-inverse))
- ]
-
- (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2) (nil? displacement-after))
- points
-
- (cond-> points
- (some? displacement)
- (gco/transform-points displacement)
-
- (some? resize-origin)
- (gco/transform-points resize-origin (gmt/scale-matrix resize-vector))
-
- (some? resize-origin-2)
- (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2))
-
- (some? displacement-after)
- (gco/transform-points displacement-after)))))
+ ([points center modifiers]
+ (let [transform (ctm/modifiers->transform modifiers)]
+ (cond-> points
+ (some? transform)
+ (gco/transform-points center transform)))))
(defn transform-selrect
[selrect modifiers]
- (let [center (gco/center-selrect selrect)]
- (-> selrect
- (gpr/rect->points)
- (transform-bounds center modifiers)
- (gpr/points->selrect))))
+ (-> selrect
+ (gpr/rect->points)
+ (transform-bounds modifiers)
+ (gpr/points->selrect)))
(defn transform-selrect-matrix
[selrect mtx]
@@ -662,17 +493,62 @@
(map (comp gpr/points->selrect :points transform-shape))
(gpr/join-selrects)))
+(declare apply-group-modifiers)
+
+(defn apply-children-modifiers
+ [objects modif-tree parent-modifiers children propagate?]
+ (->> children
+ (map (fn [child]
+ (let [modifiers (cond-> (get-in modif-tree [(:id child) :modifiers])
+ propagate? (ctm/add-modifiers parent-modifiers))
+ child (transform-shape child modifiers)
+ parent? (cph/group-like-shape? child)
+
+ modif-tree
+ (cond-> modif-tree
+ propagate?
+ (assoc-in [(:id child) :modifiers] modifiers))]
+
+ (cond-> child
+ parent?
+ (apply-group-modifiers objects modif-tree propagate?)))))))
+
(defn apply-group-modifiers
"Apply the modifiers to the group children to calculate its selection rect"
- [group objects modif-tree]
+ ([group objects modif-tree]
+ (apply-group-modifiers group objects modif-tree true))
- (let [children
- (->> (:shapes group)
- (map (d/getf objects))
- (map (fn [shape]
- (let [modifiers (get modif-tree (:id shape))
- shape (-> shape (merge modifiers) transform-shape)]
- (if (= :group (:type shape))
- (apply-group-modifiers shape objects modif-tree)
- shape)))))]
- (update-group-selrect group children)))
+ ([group objects modif-tree propagate?]
+ (let [modifiers (get-in modif-tree [(:id group) :modifiers])
+ children
+ (as-> (:shapes group) $
+ (map (d/getf objects) $)
+ (apply-children-modifiers objects modif-tree modifiers $ propagate?))]
+ (cond
+ (cph/mask-shape? group)
+ (update-mask-selrect group children)
+
+ (cph/bool-shape? group)
+ (transform-shape group modifiers)
+
+ (cph/group-shape? group)
+ (update-group-selrect group children)
+
+ :else
+ group))))
+
+(defn parent-coords-rect
+ [child parent]
+ (-> child
+ :points
+ (gco/transform-points (:transform-inverse parent))
+ (gpr/points->rect)))
+
+(defn parent-coords-points
+ [child parent]
+ (-> child
+ :points
+ (gco/transform-points (:transform-inverse parent))
+ (gpr/points->rect)
+ (gpr/rect->points)
+ (gco/transform-points (:transform parent))))
diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc
index d32531a26..e5483b118 100644
--- a/common/src/app/common/math.cljc
+++ b/common/src/app/common/math.cljc
@@ -156,7 +156,7 @@
(if (> num to) to num)))
(defn almost-zero? [num]
- (< (abs (double num)) 1e-5))
+ (< (abs (double num)) 1e-4))
(defonce float-equal-precision 0.001)
@@ -174,3 +174,8 @@
(defn max-abs
[a b]
(max (abs a) (abs b)))
+
+(defn sign
+ "Get the sign (+1 / -1) for the number"
+ [n]
+ (if (neg? n) -1 1))
diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc
index 9e60acfd0..cee62de19 100644
--- a/common/src/app/common/pages/changes.cljc
+++ b/common/src/app/common/pages/changes.cljc
@@ -11,7 +11,6 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh]
- [app.common.geom.shapes.bool :as gshb]
[app.common.math :as mth]
[app.common.pages.common :refer [component-sync-attrs]]
[app.common.pages.helpers :as cph]
@@ -170,7 +169,7 @@
group
(= :bool (:type group))
- (gshb/update-bool-selrect group children objects)
+ (gsh/update-bool-selrect group children objects)
(:masked-group? group)
(set-mask-selrect group children)
diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc
index 52eae30f8..77e232f3b 100644
--- a/common/src/app/common/pages/changes_builder.cljc
+++ b/common/src/app/common/pages/changes_builder.cljc
@@ -12,8 +12,6 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
- [app.common.geom.shapes.bool :as gshb]
- [app.common.geom.shapes.rect :as gshr]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
@@ -419,7 +417,7 @@
(every? #(apply gpt/close? %) (d/zip old-val new-val))
(= attr :selrect)
- (gshr/close-selrect? old-val new-val)
+ (gsh/close-selrect? old-val new-val)
:else
(= old-val new-val))]
@@ -438,7 +436,7 @@
nil ;; so it does not need resize
(= (:type parent) :bool)
- (gshb/update-bool-selrect parent children objects)
+ (gsh/update-bool-selrect parent children objects)
(= (:type parent) :group)
(if (:masked-group? parent)
diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc
index 181cf32cf..de34e6b74 100644
--- a/common/src/app/common/pages/common.cljc
+++ b/common/src/app/common/pages/common.cljc
@@ -71,7 +71,28 @@
:constraints-h :constraints-group
:constraints-v :constraints-group
:fixed-scroll :constraints-group
- :exports :exports-group})
+ :exports :exports-group
+
+ :layout :layout-container
+ :layout-dir :layout-container
+ :layout-gap :layout-container
+ :layout-type :layout-container
+ :layout-wrap-type :layout-container
+ :layout-padding-type :layout-container
+ :layout-padding :layout-container
+ :layout-h-orientation :layout-container
+ :layout-v-orientation :layout-container
+
+ :layout-item-margin :layout-item
+ :layout-item-margin-type :layout-item
+ :layout-item-h-sizing :layout-item
+ :layout-item-v-sizing :layout-item
+ :layout-item-max-h :layout-item
+ :layout-item-min-h :layout-item
+ :layout-item-max-w :layout-item
+ :layout-item-min-w :layout-item
+ :layout-item-align-self :layout-item
+ })
;; Attributes that may directly be edited by the user with forms
(def editable-attrs
@@ -111,31 +132,62 @@
:stroke-cap-start
:stroke-cap-end
- :exports}
+ :exports
- :group #{:proportion-lock
- :width :height
- :x :y
- :rotation
- :selrect
- :points
+ :layout
+ :layout-flex-dir
+ :layout-gap
+ :layout-gap-type
+ :layout-align-items
+ :layout-justify-content
+ :layout-align-content
+ :layout-wrap-type
+ :layout-padding-type
+ :layout-padding
- :constraints-h
- :constraints-v
- :fixed-scroll
- :parent-id
- :frame-id
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
- :opacity
- :blend-mode
- :blocked
- :hidden
+ :group #{:proportion-lock
+ :width :height
+ :x :y
+ :rotation
+ :selrect
+ :points
- :shadow
+ :constraints-h
+ :constraints-v
+ :fixed-scroll
+ :parent-id
+ :frame-id
- :blur
+ :opacity
+ :blend-mode
+ :blocked
+ :hidden
- :exports}
+ :shadow
+
+ :blur
+
+ :exports
+
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
:rect #{:proportion-lock
:width :height
@@ -180,7 +232,17 @@
:blur
- :exports}
+ :exports
+
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
:circle #{:proportion-lock
:width :height
@@ -223,118 +285,239 @@
:blur
- :exports}
+ :exports
- :path #{:proportion-lock
- :width :height
- :x :y
- :rotation
- :selrect
- :points
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
- :constraints-h
- :constraints-v
- :fixed-scroll
- :parent-id
- :frame-id
+ :path #{:proportion-lock
+ :width :height
+ :x :y
+ :rotation
+ :selrect
+ :points
- :opacity
- :blend-mode
- :blocked
- :hidden
+ :constraints-h
+ :constraints-v
+ :fixed-scroll
+ :parent-id
+ :frame-id
- :fills
- :fill-color
- :fill-opacity
- :fill-color-ref-id
- :fill-color-ref-file
- :fill-color-gradient
+ :opacity
+ :blend-mode
+ :blocked
+ :hidden
- :strokes
- :stroke-style
- :stroke-alignment
- :stroke-width
- :stroke-color
- :stroke-color-ref-id
- :stroke-color-ref-file
- :stroke-opacity
- :stroke-color-gradient
- :stroke-cap-start
- :stroke-cap-end
+ :fills
+ :fill-color
+ :fill-opacity
+ :fill-color-ref-id
+ :fill-color-ref-file
+ :fill-color-gradient
- :shadow
+ :strokes
+ :stroke-style
+ :stroke-alignment
+ :stroke-width
+ :stroke-color
+ :stroke-color-ref-id
+ :stroke-color-ref-file
+ :stroke-opacity
+ :stroke-color-gradient
+ :stroke-cap-start
+ :stroke-cap-end
- :blur
+ :shadow
- :exports}
+ :blur
- :text #{:proportion-lock
- :width :height
- :x :y
- :rotation
- :selrect
- :points
+ :exports
- :constraints-h
- :constraints-v
- :fixed-scroll
- :parent-id
- :frame-id
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
- :opacity
- :blend-mode
- :blocked
- :hidden
+ :text #{:proportion-lock
+ :width :height
+ :x :y
+ :rotation
+ :selrect
+ :points
- :fill-color
- :fill-opacity
- :fill-color-ref-id
- :fill-color-ref-file
- :fill-color-gradient
+ :constraints-h
+ :constraints-v
+ :fixed-scroll
+ :parent-id
+ :frame-id
- :stroke-style
- :stroke-alignment
- :stroke-width
- :stroke-color
- :stroke-color-ref-id
- :stroke-color-ref-file
- :stroke-opacity
- :stroke-color-gradient
- :stroke-cap-start
- :stroke-cap-end
+ :opacity
+ :blend-mode
+ :blocked
+ :hidden
- :shadow
+ :fill-color
+ :fill-opacity
+ :fill-color-ref-id
+ :fill-color-ref-file
+ :fill-color-gradient
- :blur
+ :stroke-style
+ :stroke-alignment
+ :stroke-width
+ :stroke-color
+ :stroke-color-ref-id
+ :stroke-color-ref-file
+ :stroke-opacity
+ :stroke-color-gradient
+ :stroke-cap-start
+ :stroke-cap-end
- :typography-ref-id
- :typography-ref-file
+ :shadow
- :font-id
- :font-family
- :font-variant-id
- :font-size
- :font-weight
- :font-style
+ :blur
- :text-align
+ :typography-ref-id
+ :typography-ref-file
- :text-direction
+ :font-id
+ :font-family
+ :font-variant-id
+ :font-size
+ :font-weight
+ :font-style
- :line-height
- :letter-spacing
+ :text-align
- :vertical-align
+ :text-direction
- :text-decoration
+ :line-height
+ :letter-spacing
- :text-transform
+ :vertical-align
- :grow-type
+ :text-decoration
- :exports}
+ :text-transform
- :image #{:proportion-lock
+ :grow-type
+
+ :exports
+
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
+
+ :image #{:proportion-lock
+ :width :height
+ :x :y
+ :rotation
+ :rx :ry
+ :r1 :r2 :r3 :r4
+ :selrect
+ :points
+
+ :constraints-h
+ :constraints-v
+ :fixed-scroll
+ :parent-id
+ :frame-id
+
+ :opacity
+ :blend-mode
+ :blocked
+ :hidden
+
+ :shadow
+
+ :blur
+
+ :exports
+
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
+
+ :svg-raw #{:proportion-lock
+ :width :height
+ :x :y
+ :rotation
+ :rx :ry
+ :r1 :r2 :r3 :r4
+ :selrect
+ :points
+
+ :constraints-h
+ :constraints-v
+ :fixed-scroll
+ :parent-id
+ :frame-id
+
+ :opacity
+ :blend-mode
+ :blocked
+ :hidden
+
+ :fills
+ :fill-color
+ :fill-opacity
+ :fill-color-ref-id
+ :fill-color-ref-file
+ :fill-color-gradient
+
+ :strokes
+ :stroke-style
+ :stroke-alignment
+ :stroke-width
+ :stroke-color
+ :stroke-color-ref-id
+ :stroke-color-ref-file
+ :stroke-opacity
+ :stroke-color-gradient
+ :stroke-cap-start
+ :stroke-cap-end
+
+ :shadow
+
+ :blur
+
+ :exports
+
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}
+
+ :bool #{:proportion-lock
:width :height
:x :y
:rotation
@@ -354,97 +537,36 @@
:blocked
:hidden
+ :fill-color
+ :fill-opacity
+ :fill-color-ref-id
+ :fill-color-ref-file
+ :fill-color-gradient
+
+ :stroke-style
+ :stroke-alignment
+ :stroke-width
+ :stroke-color
+ :stroke-color-ref-id
+ :stroke-color-ref-file
+ :stroke-opacity
+ :stroke-color-gradient
+ :stroke-cap-start
+ :stroke-cap-end
+
:shadow
:blur
- :exports}
+ :exports
- :svg-raw #{:proportion-lock
- :width :height
- :x :y
- :rotation
- :rx :ry
- :r1 :r2 :r3 :r4
- :selrect
- :points
-
- :constraints-h
- :constraints-v
- :fixed-scroll
- :parent-id
- :frame-id
-
- :opacity
- :blend-mode
- :blocked
- :hidden
-
- :fills
- :fill-color
- :fill-opacity
- :fill-color-ref-id
- :fill-color-ref-file
- :fill-color-gradient
-
- :strokes
- :stroke-style
- :stroke-alignment
- :stroke-width
- :stroke-color
- :stroke-color-ref-id
- :stroke-color-ref-file
- :stroke-opacity
- :stroke-color-gradient
- :stroke-cap-start
- :stroke-cap-end
-
- :shadow
-
- :blur
-
- :exports}
-
- :bool #{:proportion-lock
- :width :height
- :x :y
- :rotation
- :rx :ry
- :r1 :r2 :r3 :r4
- :selrect
- :points
-
- :constraints-h
- :constraints-v
- :fixed-scroll
- :parent-id
- :frame-id
-
- :opacity
- :blend-mode
- :blocked
- :hidden
-
- :fill-color
- :fill-opacity
- :fill-color-ref-id
- :fill-color-ref-file
- :fill-color-gradient
-
- :stroke-style
- :stroke-alignment
- :stroke-width
- :stroke-color
- :stroke-color-ref-id
- :stroke-color-ref-file
- :stroke-opacity
- :stroke-color-gradient
- :stroke-cap-start
- :stroke-cap-end
-
- :shadow
-
- :blur
-
- :exports}})
+ :layout-item-margin
+ :layout-item-margin-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ :layout-item-max-h
+ :layout-item-min-h
+ :layout-item-max-w
+ :layout-item-min-w
+ :layout-item-align-self}})
diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc
index a680833d2..76e234f57 100644
--- a/common/src/app/common/pages/helpers.cljc
+++ b/common/src/app/common/pages/helpers.cljc
@@ -28,17 +28,27 @@
(= frame-id uuid/zero)))
(defn frame-shape?
- [{:keys [type]}]
- (= type :frame))
+ ([objects id]
+ (frame-shape? (get objects id)))
+ ([{:keys [type]}]
+ (= type :frame)))
(defn group-shape?
[{:keys [type]}]
(= type :group))
+(defn mask-shape?
+ [{:keys [type masked-group?]}]
+ (and (= type :group) masked-group?))
+
(defn bool-shape?
[{:keys [type]}]
(= type :bool))
+(defn group-like-shape?
+ [{:keys [type]}]
+ (or (= :group type) (= :bool type)))
+
(defn text-shape?
[{:keys [type]}]
(= type :text))
@@ -63,9 +73,12 @@
(defn get-children-ids
[objects id]
- (if-let [shapes (-> (get objects id) :shapes (some-> vec))]
- (into shapes (mapcat #(get-children-ids objects %)) shapes)
- []))
+ (letfn [(get-children-ids-rec
+ [id processed]
+ (when (not (contains? processed id))
+ (when-let [shapes (-> (get objects id) :shapes (some-> vec))]
+ (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
+ (get-children-ids-rec id #{})))
(defn get-children
[objects id]
@@ -456,7 +469,6 @@
(defn selected-with-children
[objects selected]
-
(into selected
(mapcat #(get-children-ids objects %))
selected))
diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc
index 79c9de1c8..6ff873c8e 100644
--- a/common/src/app/common/pages/migrations.cljc
+++ b/common/src/app/common/pages/migrations.cljc
@@ -102,13 +102,7 @@
(fix-frames-selrects)
(and (empty? (:points object)) (not= (:id object) uuid/zero))
- (fix-empty-points)
-
- ;; Setup an empty transformation to re-calculate selrects
- ;; and points data
- :always
- (-> (assoc :modifiers {:displacement (gmt/matrix)})
- (gsh/transform-shape))))
+ (fix-empty-points)))
(update-page [page]
(update page :objects d/update-vals update-object))]
diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc
new file mode 100644
index 000000000..d95d0d31f
--- /dev/null
+++ b/common/src/app/common/types/modifiers.cljc
@@ -0,0 +1,500 @@
+;; 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) UXBOX Labs SL
+
+(ns app.common.types.modifiers
+ (:refer-clojure :exclude [empty empty?])
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.matrix :as gmt]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes.common :as gco]
+ [app.common.math :as mth]
+ [app.common.pages.helpers :as cph]
+ [app.common.spec :as us]
+ [app.common.text :as txt]
+ #?(:cljs [cljs.core :as c]
+ :clj [clojure.core :as c])))
+
+;; --- Modifiers
+
+;; Moodifiers types
+;; - geometry-parent: Geometry non-recursive
+;; * move
+;; * resize
+;; * rotation
+;; - geometry-child: Geometry recursive
+;; * move
+;; * resize
+;; * rotation
+;; - structure-parent: Structure non recursive
+;; * add-children
+;; * remove-children
+;; * reflow
+;; - structure-child: Structure recursive
+;; * scale-content
+;; * rotation
+;; * change-properties
+
+;; Private aux functions
+
+(def conjv (fnil conj []))
+
+(defn- move-vec? [vector]
+ (or (not (mth/almost-zero? (:x vector)))
+ (not (mth/almost-zero? (:y vector)))))
+
+(defn- resize-vec? [vector]
+ (or (not (mth/almost-zero? (- (:x vector) 1)))
+ (not (mth/almost-zero? (- (:y vector) 1)))))
+
+
+(defn- mergeable-move?
+ [op1 op2]
+ (and (= :move (:type op1))
+ (= :move (:type op2))))
+
+(defn- mergeable-resize?
+ [op1 op2]
+ (and (= :resize (:type op1))
+ (= :resize (:type op2))
+
+ ;; Same transforms
+ (gmt/close? (or (:transform op1) (gmt/matrix)) (or (:transform op2) (gmt/matrix)))
+ (gmt/close? (or (:transform-inverse op1) (gmt/matrix)) (or (:transform-inverse op2) (gmt/matrix)))
+
+ ;; Same origin
+ (gpt/close? (:origin op1) (:origin op2))))
+
+(defn- merge-move
+ [op1 op2]
+ {:type :move
+ :vector (gpt/add (:vector op1) (:vector op2))})
+
+(defn- merge-resize
+ [op1 op2]
+ (let [vector (gpt/point (* (-> op1 :vector :x) (-> op2 :vector :x))
+ (* (-> op1 :vector :y) (-> op2 :vector :y)))]
+ (assoc op1 :vector vector)))
+
+(defn- maybe-add-move
+ "Check the last operation to check if we can stack it over the last one"
+ [operations op]
+ (if (c/empty? operations)
+ [op]
+ (let [head (peek operations)]
+ (if (mergeable-move? head op)
+ (let [item (merge-move head op)]
+ (cond-> (pop operations)
+ (move-vec? (:vector item))
+ (conj item)))
+ (conj operations op)))))
+
+(defn- maybe-add-resize
+ "Check the last operation to check if we can stack it over the last one"
+ [operations op]
+
+ (if (c/empty? operations)
+ [op]
+ (let [head (peek operations)]
+ (if (mergeable-resize? head op)
+ (let [item (merge-resize head op)]
+ (cond-> (pop operations)
+ (resize-vec? (:vector item))
+ (conj item)))
+ (conj operations op)))))
+
+;; Public builder API
+
+(defn empty []
+ {})
+
+(defn move-parent
+ ([modifiers x y]
+ (move-parent modifiers (gpt/point x y)))
+
+ ([modifiers vector]
+ (cond-> modifiers
+ (move-vec? vector)
+ (update :geometry-parent conjv {:type :move :vector vector}))))
+
+(defn resize-parent
+ ([modifiers vector origin]
+ (cond-> modifiers
+ (resize-vec? vector)
+ (update :geometry-parent maybe-add-resize {:type :resize
+ :vector vector
+ :origin origin})))
+
+ ([modifiers vector origin transform transform-inverse]
+ (cond-> modifiers
+ (resize-vec? vector)
+ (update :geometry-parent maybe-add-resize {:type :resize
+ :vector vector
+ :origin origin
+ :transform transform
+ :transform-inverse transform-inverse}))))
+(defn move
+ ([modifiers x y]
+ (move modifiers (gpt/point x y)))
+
+ ([modifiers vector]
+ (cond-> modifiers
+ (move-vec? vector)
+ (update :geometry-child maybe-add-move {:type :move :vector vector}))))
+
+(defn resize
+ ([modifiers vector origin]
+ (cond-> modifiers
+ (resize-vec? vector)
+ (update :geometry-child maybe-add-resize {:type :resize
+ :vector vector
+ :origin origin})))
+
+ ([modifiers vector origin transform transform-inverse]
+ (cond-> modifiers
+ (resize-vec? vector)
+ (update :geometry-child maybe-add-resize {:type :resize
+ :vector vector
+ :origin origin
+ :transform transform
+ :transform-inverse transform-inverse}))))
+
+(defn rotation
+ [modifiers center angle]
+ (cond-> modifiers
+ (not (mth/close? angle 0))
+ (-> (update :structure-child conjv {:type :rotation
+ :rotation angle})
+ (update :geometry-child conjv {:type :rotation
+ :center center
+ :rotation angle}))))
+
+(defn remove-children
+ [modifiers shapes]
+ (cond-> modifiers
+ (d/not-empty? shapes)
+ (update :structure-parent conjv {:type :remove-children
+ :value shapes})))
+
+(defn add-children
+ [modifiers shapes index]
+ (cond-> modifiers
+ (d/not-empty? shapes)
+ (update :structure-parent conjv {:type :add-children
+ :value shapes
+ :index index})))
+
+(defn reflow
+ [modifiers]
+ (-> modifiers
+ (update :structure-parent conjv {:type :reflow})))
+
+(defn scale-content
+ [modifiers value]
+ (-> modifiers
+ (update :structure-child conjv {:type :scale-content :value value})))
+
+(defn change-property
+ [modifiers property value]
+ (-> modifiers
+ (update :structure-child conjv {:type :change-property
+ :property property
+ :value value})))
+
+(defn add-modifiers
+ [modifiers new-modifiers]
+
+ (cond-> modifiers
+ (some? (:geometry-child new-modifiers))
+ (update :geometry-child #(d/concat-vec [] % (:geometry-child new-modifiers)))
+
+ (some? (:geometry-parent new-modifiers))
+ (update :geometry-parent #(d/concat-vec [] % (:geometry-parent new-modifiers)))
+
+ (some? (:structure-parent new-modifiers))
+ (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers)))
+
+ (some? (:structure-child new-modifiers))
+ (update :structure-child #(d/concat-vec [] % (:structure-child new-modifiers)))))
+
+
+;; These are convenience methods to create single operation modifiers without the builder
+
+(defn move-modifiers
+ ([x y]
+ (move (empty) (gpt/point x y)))
+
+ ([vector]
+ (move (empty) vector)))
+
+(defn move-parent-modifiers
+ ([x y]
+ (move-parent (empty) (gpt/point x y)))
+
+ ([vector]
+ (move-parent (empty) vector)))
+
+(defn resize-modifiers
+ ([vector origin]
+ (resize (empty) vector origin))
+
+ ([vector origin transform transform-inverse]
+ (resize (empty) vector origin transform transform-inverse)))
+
+(defn resize-parent-modifiers
+ ([vector origin]
+ (resize-parent (empty) vector origin))
+
+ ([vector origin transform transform-inverse]
+ (resize-parent (empty) vector origin transform transform-inverse)))
+
+(defn rotation-modifiers
+ [shape center angle]
+ (let [shape-center (gco/center-shape shape)
+ ;; Translation caused by the rotation
+ move-vec
+ (gpt/transform
+ (gpt/point 0 0)
+ (-> (gmt/matrix)
+ (gmt/rotate angle center)
+ (gmt/rotate (- angle) shape-center)))]
+
+ (-> (empty)
+ (rotation shape-center angle)
+ (move move-vec))))
+
+(defn remove-children-modifiers
+ [shapes]
+ (-> (empty)
+ (remove-children shapes)))
+
+(defn add-children-modifiers
+ [shapes index]
+ (-> (empty)
+ (add-children shapes index)))
+
+(defn reflow-modifiers
+ []
+ (-> (empty)
+ (reflow)))
+
+(defn scale-content-modifiers
+ [value]
+ (-> (empty)
+ (scale-content value)))
+
+(defn change-dimensions-modifiers
+ [{:keys [transform transform-inverse] :as shape} attr value]
+ (us/assert map? shape)
+ (us/assert #{:width :height} attr)
+ (us/assert number? value)
+
+ (let [{:keys [proportion proportion-lock]} shape
+ size (select-keys (:selrect shape) [:width :height])
+ new-size (if-not proportion-lock
+ (assoc size attr value)
+ (if (= attr :width)
+ (-> size
+ (assoc :width value)
+ (assoc :height (/ value proportion)))
+ (-> size
+ (assoc :height value)
+ (assoc :width (* value proportion)))))
+
+ width (:width new-size)
+ height (:height new-size)
+
+ {sr-width :width sr-height :height} (:selrect shape)
+
+ origin (-> shape :points first)
+ scalex (/ width sr-width)
+ scaley (/ height sr-height)]
+
+ (resize-modifiers (gpt/point scalex scaley) origin transform transform-inverse)))
+
+(defn change-orientation-modifiers
+ [shape orientation]
+ (us/assert map? shape)
+ (us/verify #{:horiz :vert} orientation)
+ (let [width (:width shape)
+ height (:height shape)
+ new-width (if (= orientation :horiz) (max width height) (min width height))
+ new-height (if (= orientation :horiz) (min width height) (max width height))
+
+ shape-transform (:transform shape)
+ shape-transform-inv (:transform-inverse shape)
+ shape-center (gco/center-shape shape)
+ {sr-width :width sr-height :height} (:selrect shape)
+
+ origin (cond-> (gpt/point (:selrect shape))
+ (some? shape-transform)
+ (gmt/transform-point-center shape-center shape-transform))
+
+ scalev (gpt/divide (gpt/point new-width new-height)
+ (gpt/point sr-width sr-height))]
+
+ (resize-modifiers scalev origin shape-transform shape-transform-inv)))
+
+;; Predicates
+
+(defn empty?
+ [modifiers]
+ (and (c/empty? (:geometry-child modifiers))
+ (c/empty? (:geometry-parent modifiers))
+ (c/empty? (:structure-parent modifiers))
+ (c/empty? (:structure-child modifiers))))
+
+(defn child-modifiers?
+ [{:keys [geometry-child structure-child]}]
+ (or (d/not-empty? geometry-child)
+ (d/not-empty? structure-child)))
+
+(defn only-move?
+ "Returns true if there are only move operations"
+ [{:keys [geometry-child geometry-parent]}]
+ (and (every? #(= :move (:type %)) geometry-child)
+ (every? #(= :move (:type %)) geometry-parent)))
+
+(defn has-geometry?
+ [{:keys [geometry-parent geometry-child]}]
+ (or (d/not-empty? geometry-parent)
+ (d/not-empty? geometry-child)))
+
+(defn has-structure?
+ [{:keys [structure-parent structure-child]}]
+ (or (d/not-empty? structure-parent)
+ (d/not-empty? structure-child)))
+
+;; Extract subsets of modifiers
+
+(defn select-child-modifiers
+ [modifiers]
+ (select-keys modifiers [:geometry-child :structure-child]))
+
+(defn select-child-geometry-modifiers
+ [modifiers]
+ (select-keys modifiers [:geometry-child]))
+
+(defn select-parent-modifiers
+ [modifiers]
+ (select-keys modifiers [:geometry-parent :structure-parent]))
+
+(defn select-structure
+ [modifiers]
+ (select-keys modifiers [:structure-parent :structure-child]))
+
+(defn select-geometry
+ [modifiers]
+ (select-keys modifiers [:geometry-parent :geometry-child]))
+
+(defn added-children-frames
+ "Returns the frames that have an 'add-children' operation"
+ [modif-tree]
+ (let [structure-changes
+ (into {}
+ (comp (filter (fn [[_ val]] (-> val :modifiers :structure-parent some?)))
+ (map (fn [[key val]]
+ [key (-> val :modifiers :structure-parent)])))
+ modif-tree)]
+ (into []
+ (mapcat (fn [[frame-id changes]]
+ (->> changes
+ (filter (fn [{:keys [type]}] (= type :add-children)))
+ (mapcat (fn [{:keys [value]}]
+ (->> value (map (fn [id] {:frame frame-id :shape id}))))))))
+ structure-changes)))
+
+;; Main transformation functions
+
+(defn modifiers->transform
+ "Given a set of modifiers returns its transformation matrix"
+ [modifiers]
+ (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}]
+ (case type
+ :move
+ (gmt/multiply (gmt/translate-matrix vector) matrix)
+
+ :resize
+ (gmt/multiply
+ (-> (gmt/matrix)
+ (gmt/translate origin)
+ (cond-> (some? transform)
+ (gmt/multiply transform))
+ (gmt/scale vector)
+ (cond-> (some? transform-inverse)
+ (gmt/multiply transform-inverse))
+ (gmt/translate (gpt/negate origin)))
+ matrix)
+
+ :rotation
+ (gmt/multiply
+ (-> (gmt/matrix)
+ (gmt/translate center)
+ (gmt/multiply (gmt/rotate-matrix rotation))
+ (gmt/translate (gpt/negate center)))
+ matrix)))]
+ (let [modifiers (if (d/not-empty? (:geometry-parent modifiers))
+ (d/concat-vec (:geometry-parent modifiers) (:geometry-child modifiers))
+ (:geometry-child modifiers))]
+ (when (d/not-empty? modifiers)
+ (->> modifiers
+ (reduce apply-modifier (gmt/matrix)))))))
+
+(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?
+ (fn [attrs]
+ (let [font-size (-> (get attrs :font-size 14)
+ (d/parse-double)
+ (* value)
+ (str)) ]
+ (d/txt-merge attrs {:font-size font-size}))))))
+
+ (apply-scale-content
+ [shape value]
+
+ (cond-> shape
+ (cph/text-shape? shape)
+ (update :content scale-text-content value)))]
+ (let [remove-children
+ (fn [shapes children-to-remove]
+ (let [remove? (set children-to-remove)]
+ (d/removev remove? shapes)))
+
+ apply-modifier
+ (fn [shape {:keys [type property value index rotation]}]
+ (cond-> shape
+ (= type :rotation)
+ (update :rotation #(mod (+ % rotation) 360))
+
+ (and (= type :add-children) (some? index))
+ (update :shapes
+ (fn [shapes]
+ (if (vector? shapes)
+ (cph/insert-at-index shapes index value)
+ (d/concat-vec shapes value))))
+
+ (and (= type :add-children) (nil? index))
+ (update :shapes d/concat-vec value)
+
+ (= type :remove-children)
+ (update :shapes remove-children value)
+
+ (= type :scale-content)
+ (apply-scale-content value)
+
+ (= type :change-property)
+ (assoc property value)))]
+
+ (as-> shape $
+ (reduce apply-modifier $ (:structure-parent modifiers))
+ (reduce apply-modifier $ (:structure-child modifiers))))))
diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc
index 498df01ec..b318191c0 100644
--- a/common/src/app/common/types/shape/layout.cljc
+++ b/common/src/app/common/types/shape/layout.cljc
@@ -9,12 +9,33 @@
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
+;; :layout ;; :flex, :grid in the future
+;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
+;; :layout-gap-type ;; :simple, :multiple
+;; :layout-gap ;; {:row-gap number , :column-gap number}
+;; :layout-align-items ;; :start :end :center :stretch
+;; :layout-justify-content ;; :start :center :end :space-between :space-around
+;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
+;; :layout-wrap-type ;; :wrap, :no-wrap
+;; :layout-padding-type ;; :simple, :multiple
+;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
+
+;; ITEMS
+;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
+;; :layout-item-margin-type ;; :simple :multiple
+;; :layout-item-h-sizing ;; :fill :fix :auto
+;; :layout-item-v-sizing ;; :fill :fix :auto
+;; :layout-item-max-h ;; num
+;; :layout-item-min-h ;; num
+;; :layout-item-max-w ;; num
+;; :layout-item-min-w
+
(s/def ::layout #{:flex :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
-(s/def ::layout-align-items #{:start :end :center :strech})
-(s/def ::layout-align-content #{:start :end :center :space-between :space-around :strech})
+(s/def ::layout-align-items #{:start :end :center :stretch})
+(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-wrap-type #{:wrap :no-wrap})
(s/def ::layout-padding-type #{:simple :multiple})
@@ -25,15 +46,14 @@
(s/def ::p4 ::us/safe-number)
(s/def ::layout-padding
- (s/keys :req-un [::p1]
- :opt-un [::p2 ::p3 ::p4]))
+ (s/keys :opt-un [::p1 ::p2 ::p3 ::p4]))
(s/def ::row-gap ::us/safe-number)
(s/def ::column-gap ::us/safe-number)
(s/def ::layout-type #{:flex :grid})
(s/def ::layout-gap
- (s/keys :req-un [::row-gap ::column-gap]))
+ (s/keys :opt-un [::row-gap ::column-gap]))
(s/def ::layout-container-props
(s/keys :opt-un [::layout
@@ -53,25 +73,223 @@
(s/def ::m3 ::us/safe-number)
(s/def ::m4 ::us/safe-number)
-(s/def ::layout-margin (s/keys :req-un [::m1]
- :opt-un [::m2 ::m3 ::m4]))
+(s/def ::layout-item-margin (s/keys :opt-un [::m1 ::m2 ::m3 ::m4]))
-(s/def ::layout-margin-type #{:simple :multiple})
-(s/def ::layout-h-behavior #{:fill :fix :auto})
-(s/def ::layout-v-behavior #{:fill :fix :auto})
-(s/def ::layout-align-self #{:start :end :center :strech :baseline})
-(s/def ::layout-max-h ::us/safe-number)
-(s/def ::layout-min-h ::us/safe-number)
-(s/def ::layout-max-w ::us/safe-number)
-(s/def ::layout-min-w ::us/safe-number)
+(s/def ::layout-item-margin-type #{:simple :multiple})
+(s/def ::layout-item-h-sizing #{:fill :fix :auto})
+(s/def ::layout-item-v-sizing #{:fill :fix :auto})
+(s/def ::layout-item-align-self #{:start :end :center :stretch})
+(s/def ::layout-item-max-h ::us/safe-number)
+(s/def ::layout-item-min-h ::us/safe-number)
+(s/def ::layout-item-max-w ::us/safe-number)
+(s/def ::layout-item-min-w ::us/safe-number)
(s/def ::layout-child-props
- (s/keys :opt-un [::layout-margin
- ::layout-margin-type
- ::layout-h-behavior
- ::layout-v-behavior
- ::layout-max-h
- ::layout-min-h
- ::layout-max-w
- ::layout-min-w
- ::layout-align-self]))
+ (s/keys :opt-un [::layout-item-margin
+ ::layout-item-margin-type
+ ::layout-item-h-sizing
+ ::layout-item-v-sizing
+ ::layout-item-max-h
+ ::layout-item-min-h
+ ::layout-item-max-w
+ ::layout-item-min-w
+ ::layout-item-align-self]))
+
+(defn layout?
+ ([objects id]
+ (layout? (get objects id)))
+ ([shape]
+ (and (= :frame (:type shape)) (= :flex (:layout shape)))))
+
+(defn layout-child? [objects shape]
+ (let [parent-id (:parent-id shape)
+ parent (get objects parent-id)]
+ (layout? parent)))
+
+(defn inside-layout?
+ "Check if the shape is inside a layout"
+ [objects shape]
+
+ (loop [current-id (:id shape)]
+ (let [current (get objects current-id)]
+ (cond
+ (or (nil? current) (= current-id (:parent-id current)))
+ false
+
+ (= :frame (:type current))
+ (:layout current)
+
+ :else
+ (recur (:parent-id current))))))
+
+(defn wrap? [{:keys [layout-wrap-type]}]
+ (= layout-wrap-type :wrap))
+
+(defn fill-width? [child]
+ (= :fill (:layout-item-h-sizing child)))
+
+(defn fill-height? [child]
+ (= :fill (:layout-item-v-sizing child)))
+
+(defn auto-width? [child]
+ (= :auto (:layout-item-h-sizing child)))
+
+(defn auto-height? [child]
+ (= :auto (:layout-item-v-sizing child)))
+
+(defn col?
+ [{:keys [layout-flex-dir]}]
+ (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
+
+(defn row?
+ [{:keys [layout-flex-dir]}]
+ (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
+
+(defn gaps
+ [{:keys [layout-gap layout-gap-type]}]
+ (let [layout-gap-row (or (-> layout-gap :row-gap) 0)
+ layout-gap-col (if (= layout-gap-type :simple)
+ layout-gap-row
+ (or (-> layout-gap :column-gap) 0))]
+ [layout-gap-row layout-gap-col]))
+
+(defn child-min-width
+ [child]
+ (if (and (fill-width? child)
+ (some? (:layout-item-min-w child)))
+ (max 0 (:layout-item-min-w child))
+ 0))
+
+(defn child-max-width
+ [child]
+ (if (and (fill-width? child)
+ (some? (:layout-item-max-w child)))
+ (max 0 (:layout-item-max-w child))
+ ##Inf))
+
+(defn child-min-height
+ [child]
+ (if (and (fill-height? child)
+ (some? (:layout-item-min-h child)))
+ (max 0 (:layout-item-min-h child))
+ 0))
+
+(defn child-max-height
+ [child]
+ (if (and (fill-height? child)
+ (some? (:layout-item-max-h child)))
+ (max 0 (:layout-item-max-h child))
+ ##Inf))
+
+(defn child-margins
+ [{{:keys [m1 m2 m3 m4]} :layout-item-margin :keys [layout-item-margin-type]}]
+ (let [m1 (or m1 0)
+ m2 (or m2 0)
+ m3 (or m3 0)
+ m4 (or m4 0)]
+ (if (= layout-item-margin-type :multiple)
+ [m1 m2 m3 m4]
+ [m1 m1 m1 m1])))
+
+(defn child-height-margin
+ [child]
+ (let [[top _ bottom _] (child-margins child)]
+ (+ top bottom)))
+
+(defn child-width-margin
+ [child]
+ (let [[_ right _ left] (child-margins child)]
+ (+ right left)))
+
+(defn h-start?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (col? shape)
+ (= layout-align-items :start))
+ (and (row? shape)
+ (= layout-justify-content :start))))
+
+(defn h-center?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (col? shape)
+ (= layout-align-items :center))
+ (and (row? shape)
+ (= layout-justify-content :center))))
+
+(defn h-end?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (col? shape)
+ (= layout-align-items :end))
+ (and (row? shape)
+ (= layout-justify-content :end))))
+
+(defn v-start?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (row? shape)
+ (= layout-align-items :start))
+ (and (col? shape)
+ (= layout-justify-content :start))))
+
+(defn v-center?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (row? shape)
+ (= layout-align-items :center))
+ (and (col? shape)
+ (= layout-justify-content :center))))
+
+(defn v-end?
+ [{:keys [layout-align-items layout-justify-content] :as shape}]
+ (or (and (row? shape)
+ (= layout-align-items :end))
+ (and (col? shape)
+ (= layout-justify-content :end))))
+
+(defn content-start?
+ [{:keys [layout-align-content]}]
+ (= :start layout-align-content))
+
+(defn content-center?
+ [{:keys [layout-align-content]}]
+ (= :center layout-align-content))
+
+(defn content-end?
+ [{:keys [layout-align-content]}]
+ (= :end layout-align-content))
+
+(defn content-between?
+ [{:keys [layout-align-content]}]
+ (= :space-between layout-align-content))
+
+(defn content-around?
+ [{:keys [layout-align-content]}]
+ (= :space-around layout-align-content))
+
+(defn content-stretch?
+ [{:keys [layout-align-content]}]
+ (or (= :stretch layout-align-content)
+ (nil? layout-align-content)))
+
+
+(defn reverse?
+ [{:keys [layout-flex-dir]}]
+ (or (= :reverse-row layout-flex-dir)
+ (= :reverse-column layout-flex-dir)))
+
+(defn space-between?
+ [{:keys [layout-justify-content]}]
+ (= layout-justify-content :space-between))
+
+(defn space-around?
+ [{:keys [layout-justify-content]}]
+ (= layout-justify-content :space-around))
+
+(defn align-self-start? [{:keys [layout-item-align-self]}]
+ (= :start layout-item-align-self))
+
+(defn align-self-end? [{:keys [layout-item-align-self]}]
+ (= :end layout-item-align-self))
+
+(defn align-self-center? [{:keys [layout-item-align-self]}]
+ (= :center layout-item-align-self))
+
+(defn align-self-stretch? [{:keys [layout-item-align-self]}]
+ (= :stretch layout-item-align-self))
diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc
index e39869c98..d894d77ef 100644
--- a/common/src/app/common/types/shape_tree.cljc
+++ b/common/src/app/common/types/shape_tree.cljc
@@ -224,8 +224,31 @@
"Search for the top nested frame for positioning shapes when moving or creating.
Looks for all the frames in a position and then goes in depth between the top-most and its
children to find the target."
- [objects position]
- (let [frame-ids (all-frames-by-position objects position)
+ ([objects position]
+ (top-nested-frame objects position nil))
+
+ ([objects position excluded]
+ (assert (or (nil? excluded) (set? excluded)))
+
+ (let [frame-ids (cond->> (all-frames-by-position objects position)
+ (some? excluded)
+ (remove excluded))
+
+ frame-set (set frame-ids)]
+
+ (loop [current-id (first frame-ids)]
+ (let [current-shape (get objects current-id)
+ child-frame-id (d/seek #(contains? frame-set %)
+ (-> (:shapes current-shape) reverse))]
+ (if (nil? child-frame-id)
+ (or current-id uuid/zero)
+ (recur child-frame-id)))))))
+
+(defn top-nested-frame-ids
+ "Search the top nested frame in a list of ids"
+ [objects ids]
+
+ (let [frame-ids (->> ids (filter #(cph/frame-shape? objects %)))
frame-set (set frame-ids)]
(loop [current-id (first frame-ids)]
(let [current-shape (get objects current-id)
diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc
index f86487de2..864bf6b8c 100644
--- a/common/test/common_tests/geom_shapes_test.cljc
+++ b/common/test/common_tests/geom_shapes_test.cljc
@@ -10,6 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth :refer [close?]]
+ [app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts]
[clojure.test :as t]))
@@ -59,7 +60,7 @@
(t/testing "Transform shape with translation modifiers"
(t/are [type]
- (let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}]
+ (let [modifiers (ctm/move-modifiers (gpt/point 10 -10))]
(let [shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)]
(t/is (not= shape-before shape-after))
@@ -91,9 +92,7 @@
(t/testing "Transform shape with resize modifiers"
(t/are [type]
- (let [modifiers {:resize-origin (gpt/point 0 0)
- :resize-vector (gpt/point 2 2)
- :resize-transform (gmt/matrix)}
+ (let [modifiers (ctm/resize-modifiers (gpt/point 2 2) (gpt/point 0 0))
shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)]
(t/is (not= shape-before shape-after))
@@ -113,9 +112,7 @@
(t/testing "Transform with empty resize"
(t/are [type]
- (let [modifiers {:resize-origin (gpt/point 0 0)
- :resize-vector (gpt/point 1 1)
- :resize-transform (gmt/matrix)}
+ (let [modifiers (ctm/resize-modifiers (gpt/point 1 1) (gpt/point 0 0))
shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)]
(t/are [prop]
@@ -126,9 +123,7 @@
(t/testing "Transform with resize=0"
(t/are [type]
- (let [modifiers {:resize-origin (gpt/point 0 0)
- :resize-vector (gpt/point 0 0)
- :resize-transform (gmt/matrix)}
+ (let [modifiers (ctm/resize-modifiers (gpt/point 0 0) (gpt/point 0 0))
shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)]
(t/is (> (get-in shape-before [:selrect :width])
@@ -142,13 +137,11 @@
(t/testing "Transform shape with rotation modifiers"
(t/are [type]
- (let [modifiers {:rotation 30}
- shape-before (create-test-shape type {:modifiers modifiers})
+ (let [shape-before (create-test-shape type)
+ modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30 )
+ shape-before (assoc shape-before :modifiers modifiers)
shape-after (gsh/transform-shape shape-before)]
- (t/is (not= shape-before shape-after))
-
- ;; Selrect won't change with a rotation, but points will
(t/is (close? (get-in shape-before [:selrect :x])
(get-in shape-after [:selrect :x])))
@@ -166,9 +159,9 @@
(t/testing "Transform shape with rotation = 0 should leave equal selrect"
(t/are [type]
- (let [modifiers {:rotation 0}
- shape-before (create-test-shape type {:modifiers modifiers})
- shape-after (gsh/transform-shape shape-before)]
+ (let [shape-before (create-test-shape type)
+ modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 0)
+ shape-after (gsh/transform-shape (assoc shape-before :modifiers modifiers))]
(t/are [prop]
(t/is (close? (get-in shape-before [:selrect prop])
(get-in shape-after [:selrect prop])))
@@ -177,12 +170,12 @@
(t/testing "Transform shape with invalid selrect fails gracefully"
(t/are [type selrect]
- (let [modifiers {:displacement (gmt/matrix)}
- shape-before (-> (create-test-shape type {:modifiers modifiers})
- (assoc :selrect selrect))
- shape-after (gsh/transform-shape shape-before)]
- (= (:selrect shape-before)
- (:selrect shape-after)))
+ (let [modifiers (ctm/move-modifiers 0 0)
+ shape-before (-> (create-test-shape type) (assoc :selrect selrect))
+ shape-after (gsh/transform-shape shape-before modifiers)]
+
+ (t/is (= (:selrect shape-before)
+ (:selrect shape-after))))
:rect {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}
:path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}
diff --git a/frontend/resources/images/icons/auto-row-column.svg b/frontend/resources/images/icons/auto-row-column.svg
deleted file mode 100644
index 9023c8fcf..000000000
--- a/frontend/resources/images/icons/auto-row-column.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/frontend/resources/images/icons/layout-columns.svg b/frontend/resources/images/icons/layout-columns.svg
new file mode 100644
index 000000000..eb4b7ba55
--- /dev/null
+++ b/frontend/resources/images/icons/layout-columns.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/auto-flex.svg b/frontend/resources/images/icons/layout-rows.svg
similarity index 100%
rename from frontend/resources/images/icons/auto-flex.svg
rename to frontend/resources/images/icons/layout-rows.svg
diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss
index 3c785391e..7e956838d 100644
--- a/frontend/resources/styles/main/partials/sidebar-element-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss
@@ -182,6 +182,10 @@
&.error {
border-color: $color-danger;
}
+
+ &[disabled] {
+ color: $color-gray-30;
+ }
}
.input-select {
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 895c5799e..eb8916711 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -25,6 +25,7 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
+ [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.comments :as dcm]
@@ -52,8 +53,8 @@
[app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
- [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
+ [app.main.data.workspace.shapes-update-layout :as dwul]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.thumbnails :as dwth]
@@ -686,11 +687,16 @@
shapes-to-detach
shapes-to-reroot
shapes-to-deroot
- ids)]
+ ids)
+
+ layouts-to-update
+ (into #{}
+ (filter (partial ctl/layout? objects))
+ (concat [parent-id] (cph/get-parent-ids objects parent-id)))]
(rx/of (dch/commit-changes changes)
(dwco/expand-collapse parent-id)
- (dwsl/update-layout-positions [parent-id]))))))
+ (dwul/update-layout-positions layouts-to-update))))))
(defn relocate-selected-shapes
[parent-id to-index]
@@ -1571,37 +1577,6 @@
(rx/of (dch/commit-changes changes))))))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Artboard
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(defn create-artboard-from-selection
- []
- (ptk/reify ::create-artboard-from-selection
- ptk/WatchEvent
- (watch [_ state _]
- (let [page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- selected (wsh/lookup-selected state)
- selected-objs (map #(get objects %) selected)]
- (when (d/not-empty? selected)
- (let [srect (gsh/selection-rect selected-objs)
- frame-id (get-in objects [(first selected) :frame-id])
- parent-id (get-in objects [(first selected) :parent-id])
- shape (-> (cts/make-minimal-shape :frame)
- (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
- (assoc :frame-id frame-id :parent-id parent-id)
- (cond-> (not= frame-id uuid/zero)
- (assoc :fills [] :hide-in-viewer true))
- (cts/setup-rect-selrect))]
- (rx/of
- (dwu/start-undo-transaction)
- (dwsh/add-shape shape)
- (dwsh/move-shapes-into-frame (:id shape) selected)
-
- (dwu/commit-undo-transaction))))))))
-
-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Remove graphics
;; TODO: this should be deprecated and removed together with components-v2
diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs
index c87150b31..7fddf1c78 100644
--- a/frontend/src/app/main/data/workspace/comments.cljs
+++ b/frontend/src/app/main/data/workspace/comments.cljs
@@ -159,8 +159,8 @@
build-move-event
(fn [comment-thread]
(let [frame (get objects (:frame-id comment-thread))
- frame' (-> (merge frame (get object-modifiers (:frame-id comment-thread)))
- (gsh/transform-shape))
+ modifiers (get object-modifiers (:frame-id comment-thread))
+ frame' (gsh/transform-shape frame modifiers)
moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
(gpt/point (:x frame') (:y frame')))
position (get-in threads-position-map [(:id comment-thread) :position])
diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs
index de2a290ac..c586af8c4 100644
--- a/frontend/src/app/main/data/workspace/drawing/box.cljs
+++ b/frontend/src/app/main/data/workspace/drawing/box.cljs
@@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
@@ -20,29 +21,38 @@
[beicon.core :as rx]
[potok.core :as ptk]))
-(defn truncate-zero [num default]
- (if (mth/almost-zero? num) default num))
+(defn adjust-ratio
+ [point initial]
+ (let [v (gpt/to-vec point initial)
+ dx (mth/abs (:x v))
+ dy (mth/abs (:y v))
+ sx (mth/sign (:x v))
+ sy (mth/sign (:y v))]
+
+ (cond-> point
+ (> dx dy)
+ (assoc :y (- (:y point) (* sy (- dx dy))))
+
+ (> dy dx)
+ (assoc :x (- (:x point) (* sx (- dy dx)))))))
+
+(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?]
+ (let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
+ shape-rect (gsh/make-rect x y width height)
+
+ scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
+ (/ (:height draw-rect) (:height shape-rect)))
+
+ movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
-(defn resize-shape [{:keys [x y width height] :as shape} point lock?]
- (let [;; The new shape behaves like a resize on the bottom-right corner
- initial (gpt/point (+ x width) (+ y height))
- shapev (gpt/point width height)
- deltav (gpt/to-vec initial point)
- scalev (-> (gpt/divide (gpt/add shapev deltav) shapev)
- (update :x truncate-zero 0.01)
- (update :y truncate-zero 0.01))
- scalev (if lock?
- (let [v (max (:x scalev) (:y scalev))]
- (gpt/point v v))
- scalev)]
(-> shape
(assoc :click-draw? false)
- (assoc-in [:modifiers :resize-vector] scalev)
- (assoc-in [:modifiers :resize-origin] (gpt/point x y))
- (assoc-in [:modifiers :resize-rotation] 0))))
+ (gsh/transform-shape (-> (ctm/empty)
+ (ctm/resize scalev (gpt/point x y))
+ (ctm/move movev))))))
-(defn update-drawing [state point lock?]
- (update-in state [:workspace-drawing :object] resize-shape point lock?))
+(defn update-drawing [state initial point lock?]
+ (update-in state [:workspace-drawing :object] resize-shape initial point lock?))
(defn move-drawing
[{:keys [x y]}]
@@ -58,8 +68,7 @@
layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)
- initial (cond-> @ms/mouse-position
- snap-pixel? gpt/round)
+ initial (cond-> @ms/mouse-position snap-pixel? gpt/round)
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
@@ -97,7 +106,7 @@
(rx/map #(conj current %)))))
(rx/map
(fn [[_ shift? point]]
- #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?)))
+ #(update-drawing % initial (cond-> point snap-pixel? gpt/round) shift?)))
(rx/take-until stoper))
(rx/of (common/handle-finish-drawing)))))))
diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs
index 1742bfd05..ad0da2563 100644
--- a/frontend/src/app/main/data/workspace/drawing/common.cljs
+++ b/frontend/src/app/main/data/workspace/drawing/common.cljs
@@ -6,10 +6,10 @@
(ns app.main.data.workspace.drawing.common
(:require
- [app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
@@ -51,8 +51,7 @@
(and click-draw? (not text?))
(-> (assoc :width min-side :height min-side)
- (assoc-in [:modifiers :displacement]
- (gmt/translate-matrix (- (/ min-side 2)) (- (/ min-side 2)))))
+ (gsh/transform-shape (ctm/move-modifiers (- (/ min-side 2)) (- (/ min-side 2)))))
(and click-draw? text?)
(assoc :height 17 :width 4 :grow-type :auto-width)
@@ -61,8 +60,7 @@
(cts/setup-rect-selrect)
:always
- (-> (gsh/transform-shape)
- (dissoc :initialized? :click-draw?)))]
+ (dissoc :initialized? :click-draw?))]
;; Add & select the created shape to the workspace
(rx/concat
(if (= :text (:type shape))
diff --git a/frontend/src/app/main/data/workspace/guides.cljs b/frontend/src/app/main/data/workspace/guides.cljs
index 35bae09d3..0ef6e5a0b 100644
--- a/frontend/src/app/main/data/workspace/guides.cljs
+++ b/frontend/src/app/main/data/workspace/guides.cljs
@@ -79,8 +79,7 @@
build-move-event
(fn [guide]
(let [frame (get objects (:frame-id guide))
- frame' (-> (merge frame (get object-modifiers (:frame-id guide)))
- (gsh/transform-shape))
+ frame' (gsh/transform-shape (get object-modifiers (:frame-id guide)))
moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
(gpt/point (:x frame') (:y frame')))
diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs
new file mode 100644
index 000000000..9b3766e59
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/modifiers.cljs
@@ -0,0 +1,297 @@
+;; 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 INC
+
+(ns app.main.data.workspace.modifiers
+ "Events related with shapes transformations"
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes :as gsh]
+ [app.common.math :as mth]
+ [app.common.pages.common :as cpc]
+ [app.common.pages.helpers :as cph]
+ [app.common.spec :as us]
+ [app.common.types.modifiers :as ctm]
+ [app.common.types.shape.layout :as ctl]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.comments :as-alias dwcm]
+ [app.main.data.workspace.guides :as-alias dwg]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [app.main.data.workspace.undo :as dwu]
+ [beicon.core :as rx]
+ [cljs.spec.alpha :as s]
+ [potok.core :as ptk]))
+
+;; -- temporary modifiers -------------------------------------------
+
+;; During an interactive transformation of shapes (e.g. when resizing or rotating
+;; a group with the mouse), there are a lot of objects that need to be modified
+;; (in this case, the group and all its children).
+;;
+;; To avoid updating the shapes theirselves, and forcing redraw of all components
+;; that depend on the "objects" global state, we set a "modifiers" structure, with
+;; the changes that need to be applied, and store it in :workspace-modifiers global
+;; variable. The viewport reads this and merges it into the objects list it uses to
+;; paint the viewport content, redrawing only the objects that have new modifiers.
+;;
+;; When the interaction is finished (e.g. user releases mouse button), the
+;; 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 its relative position respect the
+ root of the component, and see if it changes after applying a transformation."
+ [shape root transformed-shape transformed-root objects modif-tree]
+ (let [root
+ (cond
+ (:component-root? shape)
+ shape
+
+ (nil? root)
+ (cph/get-root-shape objects shape)
+
+ :else root)
+
+ transformed-root
+ (cond
+ (:component-root? transformed-shape)
+ transformed-shape
+
+ (nil? transformed-root)
+ (as-> (cph/get-root-shape objects transformed-shape) $
+ (gsh/transform-shape (merge $ (get modif-tree (:id $)))))
+
+ :else transformed-root)
+
+ shape-delta
+ (when root
+ (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root))
+ (- (gsh/top-bound shape) (gsh/top-bound root))))
+
+ transformed-shape-delta
+ (when transformed-root
+ (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
+ (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
+
+ ;; 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.
+ distance (if (and shape-delta transformed-shape-delta)
+ (gpt/distance-vector shape-delta transformed-shape-delta)
+ (gpt/point 0 0))
+
+ ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))]
+
+ [root transformed-root ignore-geometry?]))
+
+(defn- get-ignore-tree
+ "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers"
+ ([modif-tree objects shape]
+ (get-ignore-tree modif-tree objects shape nil nil {}))
+
+ ([modif-tree objects shape root transformed-root ignore-tree]
+ (let [children (map (d/getf objects) (:shapes shape))
+
+ shape-id (:id shape)
+ transformed-shape (gsh/transform-shape shape (get modif-tree shape-id))
+
+ [root transformed-root ignore-geometry?]
+ (check-delta shape root transformed-shape transformed-root objects modif-tree)
+
+ ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
+
+ set-child
+ (fn [ignore-tree child]
+ (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
+
+ (reduce set-child ignore-tree children))))
+
+(defn- update-grow-type
+ [shape old-shape]
+ (let [auto-width? (= :auto-width (:grow-type shape))
+ auto-height? (= :auto-height (:grow-type shape))
+
+ changed-width? (not (mth/close? (:width shape) (:width old-shape)))
+ changed-height? (not (mth/close? (:height shape) (:height old-shape)))
+
+ change-to-fixed? (or (and auto-width? (or changed-height? changed-width?))
+ (and auto-height? changed-height?))]
+ (cond-> shape
+ change-to-fixed?
+ (assoc :grow-type :fixed))))
+
+(defn- clear-local-transform []
+ (ptk/reify ::clear-local-transform
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (dissoc :workspace-modifiers)
+ (dissoc :app.main.data.workspace.transforms/current-move-selected)))))
+
+(defn create-modif-tree
+ [ids modifiers]
+ (us/verify (s/coll-of uuid?) ids)
+ (into {} (map #(vector % {:modifiers modifiers})) ids))
+
+(defn build-modif-tree
+ [ids objects get-modifier]
+ (us/verify (s/coll-of uuid?) ids)
+ (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids))
+
+(defn build-change-frame-modifiers
+ [modif-tree objects selected target-frame drop-index]
+
+ (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id])))
+ child-set (set (get-in objects [target-frame :shapes]))
+ layout? (ctl/layout? objects target-frame)
+
+ set-parent-ids
+ (fn [modif-tree shapes target-frame]
+ (reduce
+ (fn [modif-tree id]
+ (update-in
+ modif-tree
+ [id :modifiers]
+ #(-> %
+ (ctm/change-property :frame-id target-frame)
+ (ctm/change-property :parent-id target-frame))))
+ modif-tree
+ shapes))
+
+ update-frame-modifiers
+ (fn [modif-tree [original-frame shapes]]
+ (let [shapes (->> shapes (d/removev #(= target-frame %)))
+ shapes (cond->> shapes
+ (and layout? (= original-frame target-frame))
+ ;; When movining inside a layout frame remove the shapes that are not immediate children
+ (filterv #(contains? child-set %)))]
+ (cond-> modif-tree
+ (not= original-frame target-frame)
+ (-> (update-in [original-frame :modifiers] ctm/remove-children shapes)
+ (update-in [target-frame :modifiers] ctm/add-children shapes drop-index)
+ (set-parent-ids shapes target-frame))
+
+ (and layout? (= original-frame target-frame))
+ (update-in [target-frame :modifiers] ctm/add-children shapes drop-index))))]
+
+ (reduce update-frame-modifiers modif-tree origin-frame-ids)))
+
+(defn modif->js
+ [modif-tree objects]
+ (clj->js (into {}
+ (map (fn [[k v]]
+ [(get-in objects [k :name]) v]))
+ modif-tree)))
+
+(defn set-modifiers
+ ([modif-tree]
+ (set-modifiers modif-tree false))
+
+ ([modif-tree ignore-constraints]
+ (set-modifiers modif-tree ignore-constraints false))
+
+ ([modif-tree ignore-constraints ignore-snap-pixel]
+ (ptk/reify ::set-modifiers
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [objects
+ (wsh/lookup-page-objects state)
+
+ snap-pixel?
+ (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
+
+ modif-tree
+ (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)]
+
+ (assoc state :workspace-modifiers modif-tree))))))
+
+;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
+(defn set-rotation-modifiers
+ ([angle shapes]
+ (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
+
+ ([angle shapes center]
+ (ptk/reify ::set-rotation-modifiers
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [objects (wsh/lookup-page-objects state)
+ ids
+ (->> shapes
+ (remove #(get % :blocked false))
+ (filter #((cpc/editable-attrs (:type %)) :rotation))
+ (map :id))
+
+ get-modifier
+ (fn [shape]
+ (ctm/rotation-modifiers shape center angle))
+
+ modif-tree
+ (-> (build-modif-tree ids objects get-modifier)
+ (gsh/set-objects-modifiers objects false false))]
+
+ (assoc state :workspace-modifiers modif-tree))))))
+
+
+(defn apply-modifiers
+ ([]
+ (apply-modifiers nil))
+
+ ([{:keys [undo-transation?] :or {undo-transation? true}}]
+ (ptk/reify ::apply-modifiers
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ object-modifiers (get state :workspace-modifiers)
+
+ ids (or (keys object-modifiers) [])
+ ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids)
+
+ shapes (map (d/getf objects) ids)
+ ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes)
+ (reduce merge {}))]
+
+ (rx/concat
+ (if undo-transation?
+ (rx/of (dwu/start-undo-transaction))
+ (rx/empty))
+ (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
+ (ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
+ (dch/update-shapes
+ ids
+ (fn [shape]
+ (let [modif (get-in object-modifiers [(:id shape) :modifiers])
+ text-shape? (cph/text-shape? shape)]
+ (-> shape
+ (gsh/transform-shape modif)
+ (cond-> text-shape?
+ (update-grow-type shape)))))
+ {:reg-objects? true
+ :ignore-tree ignore-tree
+ ;; Attributes that can change in the transform. This way we don't have to check
+ ;; all the attributes
+ :attrs [:selrect
+ :points
+ :x
+ :y
+ :width
+ :height
+ :content
+ :transform
+ :transform-inverse
+ :rotation
+ :position-data
+ :flip-x
+ :flip-y
+ :grow-type
+ :layout-item-h-sizing
+ :layout-item-v-sizing
+ ]})
+ (clear-local-transform))
+ (if undo-transation?
+ (rx/of (dwu/commit-undo-transaction))
+ (rx/empty))))))))
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index e662eb486..8791777c1 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -21,6 +21,7 @@
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
+ [app.main.data.workspace.shapes-update-layout :as dwul]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.zoom :as dwz]
@@ -552,8 +553,11 @@
(filter #(= :frame (get-in % [:obj :type])))
(map #(vector (:old-id %) (get-in % [:obj :id]))))
- id-duplicated (first new-selected)]
+ id-duplicated (first new-selected)
+ frames (into #{}
+ (map #(get-in objects [% :frame-id]))
+ selected)]
(rx/concat
(->> (rx/from dup-frames)
(rx/map (fn [[old-id new-id]] (dwt/duplicate-thumbnail old-id new-id))))
@@ -561,6 +565,7 @@
;; Warning: This order is important for the focus mode.
(rx/of (dch/commit-changes changes)
(select-shapes new-selected)
+ (dwul/update-layout-positions frames)
(memorize-duplicated id-original id-duplicated))))))))))
(defn change-hover-state
diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs
index 710995c9b..d1f171b3d 100644
--- a/frontend/src/app/main/data/workspace/shape_layout.cljs
+++ b/frontend/src/app/main/data/workspace/shape_layout.cljs
@@ -8,9 +8,13 @@
(:require
[app.common.data :as d]
[app.common.pages.helpers :as cph]
+ [app.common.types.shape.layout :as ctl]
+ [app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dwc]
+ [app.main.data.workspace.selection :as dwse]
+ [app.main.data.workspace.shapes :as dws]
+ [app.main.data.workspace.shapes-update-layout :as wsul]
[app.main.data.workspace.state-helpers :as wsh]
- [app.main.data.workspace.transforms :as dwt]
[beicon.core :as rx]
[potok.core :as ptk]))
@@ -25,8 +29,7 @@
:layout-wrap-type
:layout-padding-type
:layout-padding
- ])
-
+ :layout-gap-type])
(def initial-flex-layout
{:layout :flex
@@ -35,7 +38,7 @@
:layout-gap {:row-gap 0 :column-gap 0}
:layout-align-items :start
:layout-justify-content :start
- :layout-align-content :strech
+ :layout-align-content :stretch
:layout-wrap-type :no-wrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}})
@@ -43,30 +46,50 @@
(def initial-grid-layout ;; TODO
{:layout :grid})
-(defn update-layout-positions
- [ids]
- (ptk/reify ::update-layout-positions
- ptk/WatchEvent
- (watch [_ state _]
- (let [objects (wsh/lookup-page-objects state)
- ids (->> ids (filter #(get-in objects [% :layout])))]
- (if (d/not-empty? ids)
- (rx/of (dwt/set-modifiers ids)
- (dwt/apply-modifiers))
- (rx/empty))))))
+(defn get-layout-initializer
+ [type]
+ (let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)]
+ (fn [shape]
+ (-> shape
+ (merge shape initial-layout-data)))))
-;; TODO: Remove constraints from children
(defn create-layout
[ids type]
(ptk/reify ::create-layout
ptk/WatchEvent
- (watch [_ _ _]
- (if (= type :flex)
- (rx/of (dwc/update-shapes ids #(merge % initial-flex-layout))
- (update-layout-positions ids))
- (rx/of (dwc/update-shapes ids #(merge % initial-grid-layout))
- (update-layout-positions ids))))))
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)]
+ (rx/of (dwc/update-shapes ids (get-layout-initializer type))
+ (wsul/update-layout-positions ids)
+ (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v)))))))
+(defn create-layout-from-selection
+ [type]
+ (ptk/reify ::create-layout-from-selection
+ ptk/WatchEvent
+ (watch [_ state _]
+
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected (wsh/lookup-selected state)
+ selected-shapes (map (d/getf objects) selected)
+ single? (= (count selected-shapes) 1)
+ has-group? (->> selected-shapes (d/seek cph/group-shape?))
+ is-group? (and single? has-group?)]
+ (if is-group?
+ (let [new-shape-id (uuid/next)
+ parent-id (:parent-id (first selected-shapes))
+ shapes-ids (:shapes (first selected-shapes))
+ ordered-ids (into (d/ordered-set) shapes-ids)]
+ (rx/of (dwse/select-shapes ordered-ids)
+ (dws/create-artboard-from-selection new-shape-id parent-id)
+ (create-layout [new-shape-id] type)
+ (dws/delete-shapes page-id selected)))
+
+ (let [new-shape-id (uuid/next)]
+ (rx/of (dws/create-artboard-from-selection new-shape-id)
+ (create-layout [new-shape-id] type))))))))
(defn remove-layout
[ids]
@@ -74,7 +97,23 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys))
- (update-layout-positions ids)))))
+ (wsul/update-layout-positions ids)))))
+
+(defn toogle-layout-flex
+ []
+ (ptk/reify ::toogle-layout-flex
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected (wsh/lookup-selected state)
+ selected-shapes (map (d/getf objects) selected)
+ single? (= (count selected-shapes) 1)
+ has-flex-layout? (and single? (= :flex (:layout (first selected-shapes))))]
+
+ (if has-flex-layout?
+ (rx/of (remove-layout selected))
+ (rx/of (create-layout-from-selection :flex)))))))
(defn update-layout
[ids changes]
@@ -82,7 +121,7 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwc/update-shapes ids #(d/deep-merge % changes))
- (update-layout-positions ids)))))
+ (wsul/update-layout-positions ids)))))
(defn update-layout-child
[ids changes]
@@ -90,6 +129,7 @@
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
- parent-ids (->> ids (map #(cph/get-parent-id objects %)))]
+ parent-ids (->> ids (map #(cph/get-parent-id objects %)))
+ layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))]
(rx/of (dwc/update-shapes ids #(d/deep-merge (or % {}) changes))
- (update-layout-positions parent-ids))))))
+ (wsul/update-layout-positions (d/concat-vec layout-ids parent-ids)))))))
diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs
index e7c8e6705..72012c8d6 100644
--- a/frontend/src/app/main/data/workspace/shapes.cljs
+++ b/frontend/src/app/main/data/workspace/shapes.cljs
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.proportions :as gpr]
+ [app.common.geom.shapes :as gsh]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@@ -16,13 +17,15 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
+ [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.comments :as dc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws]
- [app.main.data.workspace.shape-layout :as dwsl]
+ [app.main.data.workspace.shapes-update-layout :as dwsul]
[app.main.data.workspace.state-helpers :as wsh]
+ [app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.streams :as ms]
[beicon.core :as rx]
@@ -101,7 +104,7 @@
(rx/concat
(rx/of (dch/commit-changes changes)
- (dwsl/update-layout-positions [(:parent-id shape)])
+ (dwsul/update-layout-positions [(:parent-id shape)])
(when-not no-select?
(dws/select-shapes (d/ordered-set id))))
(when (= :text (:type attrs))
@@ -146,6 +149,10 @@
ids (cph/clean-loops objects ids)
lookup (d/getf objects)
+ layout-ids (->> ids
+ (mapcat (partial cph/get-parent-ids objects))
+ (filter (partial ctl/layout? objects)))
+
components-v2 (features/active-feature? state :components-v2)
groups-to-unmask
@@ -265,8 +272,9 @@
(reduce ctp/remove-flow flows))))))]
(rx/of (dc/detach-comment-thread ids)
- (dwsl/update-layout-positions all-parents)
- (dch/commit-changes changes)))))))
+ (dwsul/update-layout-positions all-parents)
+ (dch/commit-changes changes)
+ (dwsul/update-layout-positions layout-ids)))))))
(defn- viewport-center
[state]
@@ -292,3 +300,38 @@
(assoc :frame-id frame-id)
(cts/setup-rect-selrect))]
(rx/of (add-shape shape))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Artboard
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn create-artboard-from-selection
+ ([]
+ (create-artboard-from-selection nil))
+ ([id]
+ (create-artboard-from-selection id nil))
+ ([id parent-id]
+ (ptk/reify ::create-artboard-from-selection
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected (wsh/lookup-selected state)
+ selected-objs (map #(get objects %) selected)]
+ (when (d/not-empty? selected)
+ (let [srect (gsh/selection-rect selected-objs)
+ frame-id (get-in objects [(first selected) :frame-id])
+ parent-id (or parent-id (get-in objects [(first selected) :parent-id]))
+ shape (-> (cts/make-minimal-shape :frame)
+ (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
+ (cond-> id
+ (assoc :id id))
+ (assoc :frame-id frame-id :parent-id parent-id)
+ (cond-> (not= frame-id uuid/zero)
+ (assoc :fills [] :hide-in-viewer true))
+ (cts/setup-rect-selrect))]
+ (rx/of
+ (dwu/start-undo-transaction)
+ (add-shape shape)
+ (move-shapes-into-frame (:id shape) selected)
+ (dwu/commit-undo-transaction)))))))))
diff --git a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs
new file mode 100644
index 000000000..3274d2356
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs
@@ -0,0 +1,28 @@
+;; 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 INC
+
+(ns app.main.data.workspace.shapes-update-layout
+ (:require
+ [app.common.data :as d]
+ [app.common.types.modifiers :as ctm]
+ [app.common.types.shape.layout :as ctl]
+ [app.main.data.workspace.modifiers :as dwm]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [beicon.core :as rx]
+ [potok.core :as ptk]))
+
+(defn update-layout-positions
+ [ids]
+ (ptk/reify ::update-layout-positions
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ ids (->> ids (filter (partial ctl/layout? objects)))]
+ (if (d/not-empty? ids)
+ (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))]
+ (rx/of (dwm/set-modifiers modif-tree)
+ (dwm/apply-modifiers)))
+ (rx/empty))))))
diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs
index 4f2728d9f..31fc056ee 100644
--- a/frontend/src/app/main/data/workspace/shortcuts.cljs
+++ b/frontend/src/app/main/data/workspace/shortcuts.cljs
@@ -15,6 +15,8 @@
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl]
+ [app.main.data.workspace.shape-layout :as dwsl]
+ [app.main.data.workspace.shapes :as dws]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
@@ -204,7 +206,12 @@
:artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g")
:subsections [:modify-layers]
- :fn #(st/emit! (dw/create-artboard-from-selection))}
+ :fn #(st/emit! (dws/create-artboard-from-selection))}
+
+ :toogle-layout-flex {:tooltip (ds/shift "F")
+ :command "shift+f"
+ :subsections [:modify-layers]
+ :fn #(st/emit! (dwsl/toogle-layout-flex))}
;; TOOLS
@@ -382,7 +389,7 @@
:show-shortcuts {:tooltip "?"
:command "?"
:subsections [:main-menu]
- :fn #(st/emit! (toggle-layout-flag :shortcuts)) }
+ :fn #(st/emit! (toggle-layout-flag :shortcuts))}
;; PANELS
diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs
index c2f68bd62..5804e1434 100644
--- a/frontend/src/app/main/data/workspace/state_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/state_helpers.cljs
@@ -127,15 +127,15 @@
(defn select-bool-children
[parent-id state]
(let [objects (lookup-page-objects state)
- selected (lookup-selected-raw state)
modifiers (:workspace-modifiers state)
-
children-ids (cph/get-children-ids objects parent-id)
- selected-children (into [] (filter selected) children-ids)
-
- modifiers (select-keys modifiers selected-children)
- children (select-keys objects children-ids)]
+ children
+ (-> (select-keys objects children-ids)
+ (d/update-vals
+ (fn [child]
+ (cond-> child
+ (contains? modifiers (:id child))
+ (gsh/transform-shape (get-in modifiers [(:id child) :modifiers]))))))]
(as-> children $
- (gsh/merge-modifiers $ modifiers)
(d/mapm (set-content-modifiers state) $))))
diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs
index 653b03abc..d4cf5995f 100644
--- a/frontend/src/app/main/data/workspace/texts.cljs
+++ b/frontend/src/app/main/data/workspace/texts.cljs
@@ -13,6 +13,7 @@
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
+ [app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
@@ -318,18 +319,14 @@
(watch [_ _ _]
(letfn [(update-fn [shape]
(let [{:keys [selrect grow-type]} shape
- {shape-width :width shape-height :height} selrect
- modifier-width (gsh/resize-modifiers shape :width new-width)
- modifier-height (gsh/resize-modifiers shape :height new-height)]
+ {shape-width :width shape-height :height} selrect]
(cond-> shape
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
- (-> (assoc :modifiers modifier-width)
- (gsh/transform-shape))
+ (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width))
(and (not-changed? shape-height new-height)
(or (= grow-type :auto-height) (= grow-type :auto-width)))
- (-> (assoc :modifiers modifier-height)
- (gsh/transform-shape)))))]
+ (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))]
(rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}))))))
@@ -346,18 +343,13 @@
(defn apply-text-modifier
[shape {:keys [width height position-data]}]
- (let [modifier-width (when width (gsh/resize-modifiers shape :width width))
- modifier-height (when height (gsh/resize-modifiers shape :height height))
-
- new-shape
+ (let [new-shape
(cond-> shape
- (some? modifier-width)
- (-> (assoc :modifiers modifier-width)
- (gsh/transform-shape))
+ (some? width)
+ (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width width))
- (some? modifier-height)
- (-> (assoc :modifiers modifier-height)
- (gsh/transform-shape))
+ (some? height)
+ (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height height))
(some? position-data)
(assoc :position-data position-data))
@@ -402,7 +394,7 @@
ptk/UpdateEvent
(update [_ state]
(let [ids (keys (::update-position-data state))]
- (update state :workspace-text-modifiers #(apply dissoc % ids))))
+ (update state :workspace-text-modifier #(apply dissoc % ids))))
ptk/WatchEvent
(watch [_ state _]
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 0842f1885..543757c1f 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -11,16 +11,17 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
+ [app.common.geom.shapes.flex-layout :as gsl]
[app.common.math :as mth]
[app.common.pages.changes-builder :as pcb]
- [app.common.pages.common :as cpc]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
+ [app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst]
+ [app.common.types.shape.layout :as ctl]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
- [app.main.data.workspace.comments :as-alias dwcm]
- [app.main.data.workspace.guides :as-alias dwg]
+ [app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
@@ -95,233 +96,12 @@
(update state :workspace-local dissoc :transform))))
-;; -- Temporary modifiers -------------------------------------------
-
-;; During an interactive transformation of shapes (e.g. when resizing or rotating
-;; a group with the mouse), there are a lot of objects that need to be modified
-;; (in this case, the group and all its children).
-;;
-;; To avoid updating the shapes theirselves, and forcing redraw of all components
-;; that depend on the "objects" global state, we set a "modifiers" structure, with
-;; the changes that need to be applied, and store it in :workspace-modifiers global
-;; variable. The viewport reads this and merges it into the objects list it uses to
-;; paint the viewport content, redrawing only the objects that have new modifiers.
-;;
-;; When the interaction is finished (e.g. user releases mouse button), the
-;; apply-modifiers event is done, that consolidates all modifiers into the base
-;; geometric attributes of the shapes.
-
-(declare clear-local-transform)
-
-(declare get-ignore-tree)
-
-(defn set-modifiers
- ([ids]
- (set-modifiers ids nil false))
-
- ([ids modifiers]
- (set-modifiers ids modifiers false))
-
- ([ids modifiers ignore-constraints]
- (set-modifiers ids modifiers ignore-constraints false))
-
- ([ids modifiers ignore-constraints ignore-snap-pixel]
- (us/verify (s/coll-of uuid?) ids)
- (ptk/reify ::set-modifiers
- ptk/UpdateEvent
- (update [_ state]
- (let [objects (wsh/lookup-page-objects state)
- ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
-
- snap-pixel? (and (not ignore-snap-pixel)
- (contains? (:workspace-layout state) :snap-pixel-grid))
-
- modif-tree
- (gsh/set-objects-modifiers ids objects (constantly modifiers) ignore-constraints snap-pixel?)]
-
- (update state :workspace-modifiers merge modif-tree))))))
-
-;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
-(defn- set-rotation-modifiers
- ([angle shapes]
- (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
-
- ([angle shapes center]
- (ptk/reify ::set-rotation-modifiers
- ptk/UpdateEvent
- (update [_ state]
- (let [objects (wsh/lookup-page-objects state)
- ids
- (->> shapes
- (remove #(get % :blocked false))
- (mapcat #(cph/get-children objects (:id %)))
- (concat shapes)
- (filter #((cpc/editable-attrs (:type %)) :rotation))
- (map :id))
-
- get-modifier
- (fn [shape]
- (gsh/rotation-modifiers shape center angle))
-
- modif-tree
- (gsh/set-objects-modifiers ids objects get-modifier false false)]
-
- (update state :workspace-modifiers merge modif-tree))))))
-
-(defn- update-grow-type
- [shape old-shape]
- (let [auto-width? (= :auto-width (:grow-type shape))
- auto-height? (= :auto-height (:grow-type shape))
-
- changed-width? (not (mth/close? (:width shape) (:width old-shape)))
- changed-height? (not (mth/close? (:height shape) (:height old-shape)))
-
- change-to-fixed? (or (and auto-width? (or changed-height? changed-width?))
- (and auto-height? changed-height?))]
- (cond-> shape
- change-to-fixed?
- (assoc :grow-type :fixed))))
-
-(defn apply-modifiers
- ([]
- (apply-modifiers nil))
-
- ([{:keys [undo-transation?] :or {undo-transation? true}}]
- (ptk/reify ::apply-modifiers
- ptk/WatchEvent
- (watch [_ state _]
- (let [objects (wsh/lookup-page-objects state)
- object-modifiers (get state :workspace-modifiers)
-
- ids (keys object-modifiers)
- ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids)
-
- shapes (map (d/getf objects) ids)
- ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes)
- (reduce merge {}))]
-
- (rx/concat
- (if undo-transation?
- (rx/of (dwu/start-undo-transaction))
- (rx/empty))
- (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
- (ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
- (dch/update-shapes
- ids
- (fn [shape]
- (let [modif (get object-modifiers (:id shape))
- text-shape? (cph/text-shape? shape)]
- (-> shape
- (merge modif)
- (gsh/transform-shape)
- (cond-> text-shape?
- (update-grow-type shape)))))
- {:reg-objects? true
- :ignore-tree ignore-tree
- ;; Attributes that can change in the transform. This way we don't have to check
- ;; all the attributes
- :attrs [:selrect
- :points
- :x
- :y
- :width
- :height
- :content
- :transform
- :transform-inverse
- :rotation
- :position-data
- :flip-x
- :flip-y
- :grow-type]})
- (clear-local-transform))
- (if undo-transation?
- (rx/of (dwu/commit-undo-transaction))
- (rx/empty))))))))
-
-(defn- check-delta
- "If the shape is a component instance, check its relative position respect the
- root of the component, and see if it changes after applying a transformation."
- [shape root transformed-shape transformed-root objects modif-tree]
- (let [root
- (cond
- (:component-root? shape)
- shape
-
- (nil? root)
- (cph/get-root-shape objects shape)
-
- :else root)
-
- transformed-root
- (cond
- (:component-root? transformed-shape)
- transformed-shape
-
- (nil? transformed-root)
- (as-> (cph/get-root-shape objects transformed-shape) $
- (gsh/transform-shape (merge $ (get modif-tree (:id $)))))
-
- :else transformed-root)
-
- shape-delta
- (when root
- (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root))
- (- (gsh/top-bound shape) (gsh/top-bound root))))
-
- transformed-shape-delta
- (when transformed-root
- (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
- (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
-
- ;; 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.
- distance (if (and shape-delta transformed-shape-delta)
- (gpt/distance-vector shape-delta transformed-shape-delta)
- (gpt/point 0 0))
-
- ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))]
-
- [root transformed-root ignore-geometry?]))
-
-(defn- get-ignore-tree
- "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers"
- ([modif-tree objects shape]
- (get-ignore-tree modif-tree objects shape nil nil {}))
-
- ([modif-tree objects shape root transformed-root ignore-tree]
- (let [children (map (d/getf objects) (:shapes shape))
-
- shape-id (:id shape)
- transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id)))
-
- [root transformed-root ignore-geometry?]
- (check-delta shape root transformed-shape transformed-root objects modif-tree)
-
- ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
-
- set-child
- (fn [ignore-tree child]
- (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
-
- (reduce set-child ignore-tree children))))
-
-(defn- clear-local-transform []
- (ptk/reify ::clear-local-transform
- ptk/UpdateEvent
- (update [_ state]
- (-> state
- (dissoc :workspace-modifiers)
- (dissoc ::current-move-selected)))))
-
;; -- Resize --------------------------------------------------------
(defn start-resize
"Enter mouse resize mode, until mouse button is released."
[handler ids shape]
- (letfn [(resize [shape initial layout [point lock? center? point-snap]]
+ (letfn [(resize [shape objects initial layout [point lock? center? point-snap]]
(let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape
@@ -331,10 +111,10 @@
rotation (or rotation 0)
- initial (gsh/transform-point-center initial shape-center shape-transform-inverse)
+ initial (gmt/transform-point-center initial shape-center shape-transform-inverse)
initial (fix-init-point initial handler shape)
- point (gsh/transform-point-center (if (= rotation 0) point-snap point)
+ point (gmt/transform-point-center (if (= rotation 0) point-snap point)
shape-center shape-transform-inverse)
shapev (-> (gpt/point width height))
@@ -381,20 +161,46 @@
(gpt/transform shape-transform)))
resize-origin
- (cond-> (gsh/transform-point-center handler-origin shape-center shape-transform)
+ (cond-> (gmt/transform-point-center handler-origin shape-center shape-transform)
(some? displacement)
(gpt/add displacement))
- displacement (when (some? displacement)
- (gmt/translate-matrix displacement))]
+ ;; When the horizontal/vertical scale a flex children with auto/fill
+ ;; we change it too fixed
+ layout? (ctl/layout? shape)
+ layout-child? (ctl/layout-child? objects shape)
+ auto-width? (ctl/auto-width? shape)
+ fill-width? (ctl/fill-width? shape)
+ auto-height? (ctl/auto-height? shape)
+ fill-height? (ctl/fill-height? shape)
- (rx/of (set-modifiers ids
- {:displacement displacement
- :resize-vector scalev
- :resize-origin resize-origin
- :resize-transform shape-transform
- :resize-scale-text scale-text
- :resize-transform-inverse shape-transform-inverse}))))
+ set-fix-width?
+ (and (not (mth/close? (:x scalev) 1))
+ (or (and (or layout? layout-child?) auto-width?)
+ (and layout-child? fill-width?)))
+
+ set-fix-height?
+ (and (not (mth/close? (:y scalev) 1))
+ (or (and (or layout? layout-child?) auto-height?)
+ (and layout-child? fill-height?)))
+
+ modifiers
+ (-> (ctm/empty)
+ (cond-> displacement
+ (ctm/move displacement))
+ (ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
+
+ (cond-> set-fix-width?
+ (ctm/change-property :layout-item-h-sizing :fix))
+
+ (cond-> set-fix-height?
+ (ctm/change-property :layout-item-v-sizing :fix))
+
+ (cond-> scale-text
+ (ctm/scale-content (:x scalev))))
+
+ modif-tree (dwm/create-modif-tree ids modifiers)]
+ (rx/of (dwm/set-modifiers modif-tree))))
;; Unifies the instantaneous proportion lock modifier
;; activated by Shift key and the shapes own proportion
@@ -418,6 +224,7 @@
zoom (get-in state [:workspace-local :zoom] 1)
objects (wsh/lookup-page-objects state page-id)
resizing-shapes (map #(get objects %) ids)]
+
(rx/concat
(->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
@@ -425,9 +232,9 @@
(rx/switch-map (fn [[point _ _ :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point)
(rx/map #(conj current %)))))
- (rx/mapcat (partial resize shape initial-position layout))
+ (rx/mapcat (partial resize shape objects initial-position layout))
(rx/take-until stoper))
- (rx/of (apply-modifiers)
+ (rx/of (dwm/apply-modifiers)
(finish-transform))))))))
(defn update-dimensions
@@ -444,16 +251,17 @@
snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid)
(int? value))
get-modifier
- (fn [shape] (gsh/resize-modifiers shape attr value))
+ (fn [shape] (ctm/change-dimensions-modifiers shape attr value))
modif-tree
- (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)]
+ (-> (dwm/build-modif-tree ids objects get-modifier)
+ (gsh/set-objects-modifiers objects false snap-pixel?))]
(assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent
(watch [_ _ _]
- (rx/of (apply-modifiers)))))
+ (rx/of (dwm/apply-modifiers)))))
(defn change-orientation
"Change orientation of shapes, from the sidebar options form.
@@ -468,16 +276,17 @@
snap-pixel? (contains? (get state :workspace-layout) :snap-pixel-grid)
get-modifier
- (fn [shape] (gsh/change-orientation-modifiers shape orientation))
+ (fn [shape] (ctm/change-orientation-modifiers shape orientation))
modif-tree
- (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)]
+ (-> (dwm/build-modif-tree ids objects get-modifier)
+ (gsh/set-objects-modifiers objects false snap-pixel?))]
(assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent
(watch [_ _ _]
- (rx/of (apply-modifiers)))))
+ (rx/of (dwm/apply-modifiers)))))
;; -- Rotate --------------------------------------------------------
@@ -518,9 +327,9 @@
(rx/map
(fn [[[pos mod?] shift?]]
(let [delta-angle (calculate-angle pos mod? shift?)]
- (set-rotation-modifiers delta-angle shapes group-center))))
+ (dwm/set-rotation-modifiers delta-angle shapes group-center))))
(rx/take-until stoper))
- (rx/of (apply-modifiers)
+ (rx/of (dwm/apply-modifiers)
(finish-transform)))))))
(defn increase-rotation
@@ -534,17 +343,17 @@
objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))]
- (set-rotation-modifiers delta [shape])))]
+ (dwm/set-rotation-modifiers delta [shape])))]
(rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
- (rx/of (apply-modifiers)))))))
+ (rx/of (dwm/apply-modifiers)))))))
;; -- Move ----------------------------------------------------------
(declare start-move)
(declare start-move-duplicate)
-(declare calculate-frame-for-move)
+(declare move-shapes-to-frame)
(declare get-displacement)
(defn start-move-selected
@@ -626,13 +435,18 @@
zoom (get-in state [:workspace-local :zoom] 1)
focus (:workspace-focus-selected state)
- fix-axis (fn [[position shift?]]
- (let [delta (gpt/to-vec from-position position)]
- (if shift?
- (if (> (mth/abs (:x delta)) (mth/abs (:y delta)))
- (gpt/point (:x delta) 0)
- (gpt/point 0 (:y delta)))
- delta)))
+ exclude-frames (into #{}
+ (filter (partial cph/frame-shape? objects))
+ (cph/selected-with-children objects selected))
+
+ fix-axis
+ (fn [[position shift?]]
+ (let [delta (gpt/to-vec from-position position)]
+ (if shift?
+ (if (> (mth/abs (:x delta)) (mth/abs (:y delta)))
+ (gpt/point (:x delta) 0)
+ (gpt/point 0 (:y delta)))
+ delta)))
position (->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift)
@@ -649,21 +463,43 @@
(rx/map #(vector pos %)))))))]
(if (empty? shapes)
(rx/of (finish-transform))
- (rx/concat
- (->> position
- ;; We ask for the snap position but we continue even if the result is not available
- (rx/with-latest vector snap-delta)
- ;; We try to use the previous snap so we don't have to wait for the result of the new
- (rx/map snap/correct-snap-point)
- (rx/map #(hash-map :displacement (gmt/translate-matrix %)))
- (rx/map (partial set-modifiers ids))
- (rx/take-until stopper))
+ (let [move-stream
+ (->> position
+ ;; We ask for the snap position but we continue even if the result is not available
+ (rx/with-latest vector snap-delta)
- (rx/of (dwu/start-undo-transaction)
- (calculate-frame-for-move ids)
- (apply-modifiers {:undo-transation? false})
- (finish-transform)
- (dwu/commit-undo-transaction)))))))))
+ ;; We try to use the previous snap so we don't have to wait for the result of the new
+ (rx/map snap/correct-snap-point)
+
+ (rx/map
+ (fn [move-vector]
+ (let [position (gpt/add from-position move-vector)
+ target-frame (ctst/top-nested-frame objects position exclude-frames)
+ layout? (ctl/layout? objects target-frame)
+ drop-index (when layout? (gsl/get-drop-index target-frame objects position))]
+ [move-vector target-frame drop-index])))
+
+ (rx/take-until stopper))]
+
+ (rx/merge
+ ;; Temporary modifiers stream
+ (->> move-stream
+ (rx/map
+ (fn [[move-vector target-frame drop-index]]
+ (-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
+ (dwm/build-change-frame-modifiers objects selected target-frame drop-index)
+ (dwm/set-modifiers)))))
+
+ ;; Last event will write the modifiers creating the changes
+ (->> move-stream
+ (rx/last)
+ (rx/mapcat
+ (fn [[_ target-frame drop-index]]
+ (rx/of (dwu/start-undo-transaction)
+ (move-shapes-to-frame ids target-frame drop-index)
+ (dwm/apply-modifiers {:undo-transation? false})
+ (finish-transform)
+ (dwu/commit-undo-transaction)))))))))))))
(s/def ::direction #{:up :down :right :left})
@@ -704,12 +540,12 @@
(rx/merge
(->> move-events
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
- (rx/map #(hash-map :displacement (gmt/translate-matrix %)))
- (rx/map (partial set-modifiers selected))
+ (rx/map #(dwm/create-modif-tree selected (ctm/move-modifiers %)))
+ (rx/map (partial dwm/set-modifiers))
(rx/take-until stopper))
(rx/of (move-selected direction shift?)))
- (rx/of (apply-modifiers)
+ (rx/of (dwm/apply-modifiers)
(finish-transform))))
(rx/empty))))))
@@ -736,34 +572,39 @@
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
delta (gpt/subtract pos cpos)
- displ (gmt/translate-matrix delta)]
- (rx/of (set-modifiers [id] {:displacement displ} false true)
- (apply-modifiers [id]))))))
+ modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
-(defn- calculate-frame-for-move
- [ids]
- (ptk/reify ::calculate-frame-for-move
+ (rx/of (dwm/set-modifiers modif-tree)
+ (dwm/apply-modifiers))))))
+
+(defn- move-shapes-to-frame
+ [ids frame-id drop-index]
+ (ptk/reify ::move-shapes-to-frame
ptk/WatchEvent
(watch [it state _]
- (let [position @ms/mouse-position
- page-id (:current-page-id state)
+ (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
- frame-id (ctst/top-nested-frame objects position)
+ layout? (get-in objects [frame-id :layout])
lookup (d/getf objects)
+ shapes (->> ids (cph/clean-loops objects) (keep lookup))
+
moving-shapes
- (->> ids
- (cph/clean-loops objects)
- (keep lookup)
- (remove #(= (:frame-id %) frame-id)))
+ (cond->> shapes
+ (not layout?)
+ (remove #(= (:frame-id %) frame-id))
+
+ layout?
+ (remove #(and (= (:frame-id %) frame-id)
+ (not= (:parent-id %) frame-id))))
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
- (pcb/change-parent frame-id moving-shapes))]
+ (pcb/change-parent frame-id moving-shapes drop-index))]
- (when-not (empty? changes)
+ (when (and (some? frame-id) (d/not-empty? changes))
(rx/of (dch/commit-changes changes)
(dwc/expand-collapse frame-id)))))))
@@ -787,15 +628,17 @@
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
- selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape)))
- origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))]
+ selrect (gsh/selection-rect shapes)
+ origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))
- (rx/of (set-modifiers selected
- {:resize-vector (gpt/point -1.0 1.0)
- :resize-origin origin
- :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))}
- true)
- (apply-modifiers))))))
+ modif-tree (dwm/create-modif-tree
+ selected
+ (-> (ctm/empty)
+ (ctm/resize (gpt/point -1.0 1.0) origin)
+ (ctm/move (gpt/point (:width selrect) 0))))]
+
+ (rx/of (dwm/set-modifiers modif-tree true)
+ (dwm/apply-modifiers))))))
(defn flip-vertical-selected []
(ptk/reify ::flip-vertical-selected
@@ -804,12 +647,14 @@
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
- selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape)))
- origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))]
+ selrect (gsh/selection-rect shapes)
+ origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))
- (rx/of (set-modifiers selected
- {:resize-vector (gpt/point 1.0 -1.0)
- :resize-origin origin
- :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}
- true)
- (apply-modifiers))))))
+ modif-tree (dwm/create-modif-tree
+ selected
+ (-> (ctm/empty)
+ (ctm/resize (gpt/point 1.0 -1.0) origin)
+ (ctm/move (gpt/point 0 (:height selrect)))))]
+
+ (rx/of (dwm/set-modifiers modif-tree true)
+ (dwm/apply-modifiers))))))
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index fb66b3b6b..72e4420d1 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctt]
+ [app.common.types.shape.layout :as ctl]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st]
[okulary.core :as l]))
@@ -265,6 +266,14 @@
[ids]
(l/derived #(into [] (keep (d/getf %)) ids) workspace-page-objects =))
+(defn parents-by-ids
+ [ids]
+ (l/derived
+ (fn [objects]
+ (let [parent-ids (into #{} (keep #(get-in objects [% :parent-id])) ids)]
+ (into [] (keep #(get objects %)) parent-ids)))
+ workspace-page-objects =))
+
(defn children-objects
[id]
(l/derived
@@ -434,7 +443,8 @@
(l/derived
(fn [objects]
(->> ids
- (some #(-> (cph/get-parent objects %) :layout))))
+ (map (d/getf objects))
+ (some (partial ctl/layout-child? objects))))
workspace-page-objects))
(defn get-flex-child-viewer?
@@ -442,7 +452,10 @@
(l/derived
(fn [state]
(let [objects (wsh/lookup-viewer-objects state page-id)]
- (filterv #(= :flex (:layout (cph/get-parent objects %))) ids)))
+ (into []
+ (comp (filter (partial ctl/layout-child? objects))
+ (map (d/getf objects)))
+ ids)))
st/state =))
(def colorpicker
diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs
index c750538dc..41073f6e2 100644
--- a/frontend/src/app/main/render.cljs
+++ b/frontend/src/app/main/render.cljs
@@ -15,12 +15,12 @@
["react-dom/server" :as rds]
[app.common.colors :as clr]
[app.common.data.macros :as dm]
- [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.bounds :as gsb]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst]
[app.config :as cfg]
[app.main.fonts :as fonts]
@@ -81,8 +81,7 @@
[{:keys [shape] :as props}]
(let [render-thumbnails? (mf/use-ctx muc/render-thumbnails)
- childs (mapv #(get objects %) (:shapes shape))
- shape (gsh/transform-shape shape)]
+ childs (mapv #(get objects %) (:shapes shape))]
(if (and render-thumbnails? (some? (:thumbnail shape)))
[:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}]
[:& frame-shape {:shape shape :childs childs}])))))
@@ -135,8 +134,7 @@
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
(when (and shape (not (:hidden shape)))
- (let [shape (gsh/transform-shape shape)
- opts #js {:shape shape}
+ (let [opts #js {:shape shape}
svg-raw? (= :svg-raw (:type shape))]
(if-not svg-raw?
[:> shape-container {:shape shape}
@@ -166,8 +164,7 @@
[objects object]
(let [shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes)
- object (merge object (select-keys srect [:x :y :width :height]))
- object (gsh/transform-shape object)]
+ object (merge object (select-keys srect [:x :y :width :height]))]
(assoc object :fill-color "#f0f0f0")))
(defn adapt-objects-for-shape
@@ -180,14 +177,12 @@
;; Replace the previous object with the new one
objects (assoc objects object-id object)
- modifier (-> (gpt/point (:x object) (:y object))
- (gpt/negate)
- (gmt/translate-matrix))
+ vector (-> (gpt/point (:x object) (:y object))
+ (gpt/negate))
mod-ids (cons object-id (cph/get-children-ids objects object-id))
- updt-fn #(-> %1
- (assoc-in [%2 :modifiers :displacement] modifier)
- (update %2 gsh/transform-shape))]
+
+ updt-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(reduce updt-fn objects mod-ids)))
@@ -247,24 +242,21 @@
bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur))
delta-bounds (gpt/point (:x bounds) (:y bounds))
-
- modifier (gmt/translate-matrix (gpt/negate delta-bounds))
+ vector (gpt/negate delta-bounds)
children-ids
(cph/get-children-ids objects frame-id)
objects
- (mf/with-memo [frame-id objects modifier]
- (let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
+ (mf/with-memo [frame-id objects vector]
+ (let [update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(->> children-ids
(into [frame-id])
(reduce update-fn objects))))
frame
- (mf/with-memo [modifier]
- (-> frame
- (assoc-in [:modifiers :displacement] modifier)
- (gsh/transform-shape)))
+ (mf/with-memo [vector]
+ (gsh/transform-shape frame (ctm/move-modifiers vector)))
frame
(cond-> frame
@@ -293,7 +285,6 @@
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
:fill "none"}
-
[:& shape-wrapper {:shape frame}]]]))
@@ -305,22 +296,20 @@
(let [group-id (:id group)
include-metadata? (mf/use-ctx export/include-metadata-ctx)
- modifier
+ vector
(mf/use-memo
(mf/deps (:x group) (:y group))
(fn []
(-> (gpt/point (:x group) (:y group))
- (gpt/negate)
- (gmt/translate-matrix))))
+ (gpt/negate))))
objects
(mf/use-memo
- (mf/deps modifier objects group-id)
+ (mf/deps vector objects group-id)
(fn []
- (let [modifier-ids (cons group-id (cph/get-children-ids objects group-id))
- update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
- modifiers (reduce update-fn {} modifier-ids)]
- (gsh/merge-modifiers objects modifiers))))
+ (let [children-ids (cons group-id (cph/get-children-ids objects group-id))
+ update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
+ (reduce update-fn objects children-ids))))
group (get objects group-id)
width (* (:width group) zoom)
diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs
index ce2b97c24..f9585b5bf 100644
--- a/frontend/src/app/main/snap.cljs
+++ b/frontend/src/app/main/snap.cljs
@@ -358,7 +358,8 @@
"Snaps a position given an old snap to a different position. We use this to provide a temporal
snap while the new is being processed."
[[position [snap-pos snap-delta]]]
- (if (some? snap-delta)
+ (if (nil? snap-delta)
+ position
(let [dx (if (not= 0 (:x snap-delta))
(- (+ (:x snap-pos) (:x snap-delta)) (:x position))
0)
@@ -372,6 +373,4 @@
dy (if (> (mth/abs dy) snap-accuracy) 0 dy)]
(-> position
(update :x + dx)
- (update :y + dy)))
-
- position))
+ (update :y + dy)))))
diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs
index 27a8794b9..ab42a2e4c 100644
--- a/frontend/src/app/main/ui/components/shape_icon.cljs
+++ b/frontend/src/app/main/ui/components/shape_icon.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.components.shape-icon
(:require
+ [app.common.types.shape.layout :as ctl]
[app.main.ui.icons :as i]
[rumext.v2 :as mf]))
@@ -13,7 +14,15 @@
(mf/defc element-icon
[{:keys [shape main-instance?] :as props}]
(case (:type shape)
- :frame i/artboard
+ :frame (cond
+ (and (ctl/layout? shape) (ctl/col? shape))
+ i/layout-columns
+
+ (and (ctl/layout? shape) (ctl/row? shape))
+ i/layout-rows
+
+ :else
+ i/artboard)
:image i/image
:line i/line
:circle i/circle
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 69dd22c14..e6c073122 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -133,6 +133,8 @@
(def justify-content-row-end (icon-xref :justify-content-row-end))
(def justify-content-row-start (icon-xref :justify-content-row-start))
(def layers (icon-xref :layers))
+(def layout-columns (icon-xref :layout-columns))
+(def layout-rows (icon-xref :layout-rows))
(def letter-spacing (icon-xref :letter-spacing))
(def libraries (icon-xref :libraries))
(def library (icon-xref :library))
diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs
index 94a76d893..d725a00ae 100644
--- a/frontend/src/app/main/ui/shapes/bool.cljs
+++ b/frontend/src/app/main/ui/shapes/bool.cljs
@@ -6,7 +6,6 @@
(ns app.main.ui.shapes.bool
(:require
- [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.main.ui.hooks :refer [use-equal-memo]]
@@ -34,9 +33,7 @@
(:bool-content shape)
(some? childs)
- (->> childs
- (d/mapm #(gsh/transform-shape %2))
- (gsh/calc-bool-content shape)))))]
+ (gsh/calc-bool-content shape childs))))]
[:*
(when (some? bool-content)
diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs
index 493c43691..d0685a5f5 100644
--- a/frontend/src/app/main/ui/shapes/mask.cljs
+++ b/frontend/src/app/main/ui/shapes/mask.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.shapes.mask
(:require
[app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc]
[cuerdas.core :as str]
@@ -50,9 +51,7 @@
render-id (mf/use-ctx muc/render-id)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
- mask-bb (-> (gsh/transform-shape mask)
- (:points))
-
+ mask-bb (:points mask)
mask-bb-rect (gsh/points->rect mask-bb)]
[:defs
[:filter {:id (filter-id render-id mask)}
@@ -68,7 +67,7 @@
[:clipPath {:class "mask-clip-path"
:id (clip-id render-id mask)}
[:polyline {:points (->> mask-bb
- (map #(str (:x %) "," (:y %)))
+ (map #(dm/str (:x %) "," (:y %)))
(str/join " "))}]]
;; When te shape is a text we pass to the shape the info and disable the filter.
diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs
index 0567bf654..d32844940 100644
--- a/frontend/src/app/main/ui/viewer/handoff/render.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs
@@ -88,8 +88,6 @@
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
- shape (gsh/transform-shape shape)
-
props (-> (obj/create)
(obj/merge! props)
(obj/merge! #js {:shape shape
@@ -171,8 +169,7 @@
(mf/use-memo (mf/deps objects)
#(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape)))
- (let [shape (-> (gsh/transform-shape shape)
- (gsh/translate-to-frame frame))
+ (let [shape (gsh/translate-to-frame shape frame)
opts #js {:shape shape
:frame frame}]
(case (:type shape)
diff --git a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
index 0b7af463a..9d3325710 100644
--- a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
@@ -57,8 +57,6 @@
shapes (resolve-shapes objects [hover])
hover-shape (or (first shapes) frame)
- hover-shape (gsh/translate-to-frame hover-shape size)
-
selected-shapes (resolve-shapes objects selected)
selrect (gsh/selection-rect selected-shapes)]
diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs
index fafb5cf53..47bd71143 100644
--- a/frontend/src/app/main/ui/viewer/interactions.cljs
+++ b/frontend/src/app/main/ui/viewer/interactions.cljs
@@ -8,9 +8,10 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
- [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
+ [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
+ [app.common.types.modifiers :as ctm]
[app.common.types.page :as ctp]
[app.common.uuid :as uuid]
[app.main.data.comments :as dcm]
@@ -29,14 +30,10 @@
(defn prepare-objects
[frame size objects]
- (let [
- frame-id (:id frame)
- modifier (-> (gpt/point (:x size) (:y size))
- (gpt/negate)
- (gmt/translate-matrix))
-
- update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)]
-
+ (let [frame-id (:id frame)
+ vector (-> (gpt/point (:x size) (:y size))
+ (gpt/negate))
+ update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(->> (cph/get-children-ids objects frame-id)
(into [frame-id])
(reduce update-fn objects))))
diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs
index b884653ba..e43ff5780 100644
--- a/frontend/src/app/main/ui/viewer/shapes.cljs
+++ b/frontend/src/app/main/ui/viewer/shapes.cljs
@@ -350,7 +350,6 @@
[props]
(let [shape (obj/get props "shape")
childs (mapv #(get objects %) (:shapes shape))
- shape (gsh/transform-shape shape)
props (obj/merge! #js {} props
#js {:shape shape
:childs childs
@@ -429,7 +428,8 @@
(mf/with-memo [objects]
(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape)))
- (let [shape (-> (gsh/transform-shape shape)
+ (let [shape (-> shape
+ #_(gsh/transform-shape)
(gsh/translate-to-frame frame))
opts #js {:shape shape
diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs
index 39b8cfc10..ab69b3879 100644
--- a/frontend/src/app/main/ui/workspace/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/context_menu.cljs
@@ -19,6 +19,8 @@
[app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
+ [app.main.data.workspace.shape-layout :as dwsl]
+ [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -212,7 +214,7 @@
(let [multiple? (> (count shapes) 1)
single? (= (count shapes) 1)
- do-create-artboard-from-selection #(st/emit! (dw/create-artboard-from-selection))
+ do-create-artboard-from-selection #(st/emit! (dwsh/create-artboard-from-selection))
has-frame? (->> shapes (d/seek cph/frame-shape?))
has-group? (->> shapes (d/seek cph/group-shape?))
@@ -364,6 +366,31 @@
[:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
:on-click do-add-flow}])))))
+(mf/defc context-menu-flex
+ [{:keys [shapes]}]
+ (let [single? (= (count shapes) 1)
+ has-frame? (->> shapes (d/seek cph/frame-shape?))
+ is-frame? (and single? has-frame?)
+ is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
+ has-group? (->> shapes (d/seek cph/group-shape?))
+ is-group? (and single? has-group?)
+ ids (->> shapes (map :id))
+ add-flex #(st/emit! (dwsl/create-layout-from-selection :flex))
+ remove-flex #(st/emit! (dwsl/remove-layout ids))]
+ (cond
+ (or (not single?) (and is-frame? (not is-flex-container?)) is-group?)
+ [:*
+ [:& menu-separator]
+ [:& menu-entry {:title (tr "workspace.shape.menu.add-flex")
+ :shortcut (sc/get-tooltip :toogle-layout-flex)
+ :on-click add-flex}]]
+
+ is-flex-container?
+ [:*
+ [:& menu-separator]
+ [:& menu-entry {:title (tr "workspace.shape.menu.remove-flex")
+ :shortcut (sc/get-tooltip :toogle-layout-flex)
+ :on-click remove-flex}]])))
(mf/defc context-menu-component
[{:keys [shapes]}]
@@ -517,6 +544,7 @@
[:> context-menu-path props]
[:> context-menu-layer-options props]
[:> context-menu-prototype props]
+ [:> context-menu-flex props]
[:> context-menu-component props]
[:> context-menu-delete props]])))
diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs
index 2e71107b3..0b92d475e 100644
--- a/frontend/src/app/main/ui/workspace/shapes.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes.cljs
@@ -12,7 +12,9 @@
others are defined using a generic wrapper implemented in
common."
(:require
+ [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
+ [app.common.uuid :as uuid]
[app.main.ui.context :as ctx]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.image :as image]
@@ -55,33 +57,33 @@
(mf/deps objects)
#(cph/objects-by-frame objects))]
- [:& (mf/provider ctx/active-frames) {:value active-frames}
- ;; Render font faces only for shapes that are part of the root
- ;; frame but don't belongs to any other frame.
- (let [xform (comp
- (remove cph/frame-shape?)
- (mapcat #(cph/get-children-with-self objects (:id %))))]
- [:& ff/fontfaces-style {:shapes (into [] xform shapes)}])
+ [:g {:id (dm/str "shape-" uuid/zero)}
+ [:& (mf/provider ctx/active-frames) {:value active-frames}
+ ;; Render font faces only for shapes that are part of the root
+ ;; frame but don't belongs to any other frame.
+ (let [xform (comp
+ (remove cph/frame-shape?)
+ (mapcat #(cph/get-children-with-self objects (:id %))))]
+ [:& ff/fontfaces-style {:shapes (into [] xform shapes)}])
- (for [shape shapes]
- (cond
- (not (cph/frame-shape? shape))
- [:& shape-wrapper
- {:shape shape
- :key (:id shape)}]
+ [:g.frame-children
+ (for [shape shapes]
+ [:g.ws-shape-wrapper {:key (:id shape)}
+ (cond
+ (not (cph/frame-shape? shape))
+ [:& shape-wrapper
+ {:shape shape}]
- (cph/root-frame? shape)
- [:& root-frame-wrapper
- {:shape shape
- :key (:id shape)
- :objects (get frame-objects (:id shape))
- :thumbnail? (not (contains? active-frames (:id shape)))}]
+ (cph/root-frame? shape)
+ [:& root-frame-wrapper
+ {:shape shape
+ :objects (get frame-objects (:id shape))
+ :thumbnail? (not (contains? active-frames (:id shape)))}]
- :else
- [:& nested-frame-wrapper
- {:shape shape
- :key (:id shape)
- :objects (get frame-objects (:id shape))}]))]))
+ :else
+ [:& nested-frame-wrapper
+ {:shape shape
+ :objects (get frame-objects (:id shape))}])])]]]))
(mf/defc shape-wrapper
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
@@ -98,20 +100,21 @@
opts #js {:shape shape :thumbnail? thumbnail?}]
(when (and (some? shape) (not (:hidden shape)))
- (case (:type shape)
- :path [:> path/path-wrapper opts]
- :text [:> text/text-wrapper opts]
- :group [:> group-wrapper opts]
- :rect [:> rect-wrapper opts]
- :image [:> image-wrapper opts]
- :circle [:> circle-wrapper opts]
- :svg-raw [:> svg-raw-wrapper opts]
- :bool [:> bool-wrapper opts]
+ [:g.workspace-shape-wrapper
+ (case (:type shape)
+ :path [:> path/path-wrapper opts]
+ :text [:> text/text-wrapper opts]
+ :group [:> group-wrapper opts]
+ :rect [:> rect-wrapper opts]
+ :image [:> image-wrapper opts]
+ :circle [:> circle-wrapper opts]
+ :svg-raw [:> svg-raw-wrapper opts]
+ :bool [:> bool-wrapper opts]
- ;; Only used when drawing a new frame.
- :frame [:> nested-frame-wrapper opts]
+ ;; Only used when drawing a new frame.
+ :frame [:> nested-frame-wrapper opts]
- nil))))
+ nil)])))
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs
index 2f4207e14..c4df18c29 100644
--- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs
@@ -11,79 +11,28 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
+ [app.common.types.modifiers :as ctm]
[app.main.store :as st]
+ [app.main.ui.hooks :as hooks]
[app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom]
+ [app.util.globals :as globals]
[rumext.v2 :as mf]))
-(defn- transform-no-resize
- "If we apply a scale directly to the texts it will show deformed so we need to create this
- correction matrix to \"undo\" the resize but keep the other transformations."
- [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers]
+(defn get-shape-node
+ ([id]
+ (get-shape-node js/document id))
- (let [corner-pt (first points)
- corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse))
-
- resize-x? (some? (:resize-vector modifiers))
- resize-y? (some? (:resize-vector-2 modifiers))
-
- flip-x? (neg? (get-in modifiers [:resize-vector :x]))
- flip-y? (or (neg? (get-in modifiers [:resize-vector :y]))
- (neg? (get-in modifiers [:resize-vector-2 :y])))
-
- result (cond-> (gmt/matrix)
- (and (some? transform) (or resize-x? resize-y?))
- (gmt/multiply transform)
-
- resize-x?
- (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt)
-
- resize-y?
- (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt)
-
- flip-x?
- (gmt/scale (gpt/point -1 1) corner-pt)
-
- flip-y?
- (gmt/scale (gpt/point 1 -1) corner-pt)
-
- (and (some? transform) (or resize-x? resize-y?))
- (gmt/multiply transform-inverse))
-
- [width height]
- (if (or resize-x? resize-y?)
- (let [pc (cond-> (gpt/point x y)
- (some? transform)
- (gpt/transform transform)
-
- (some? current-transform)
- (gpt/transform current-transform))
-
- pw (cond-> (gpt/point (+ x width) y)
- (some? transform)
- (gpt/transform transform)
-
- (some? current-transform)
- (gpt/transform current-transform))
-
- ph (cond-> (gpt/point x (+ y height))
- (some? transform)
- (gpt/transform transform)
-
- (some? current-transform)
- (gpt/transform current-transform))]
- [(gpt/distance pc pw) (gpt/distance pc ph)])
- [width height])]
-
- [result width height]))
+ ([base-node id]
+ (if (= (.-id base-node) (dm/str "shape-" id))
+ base-node
+ (dom/query base-node (dm/str "#shape-" id)))))
(defn get-nodes
"Retrieve the DOM nodes to apply the matrix transformation"
[base-node {:keys [id type masked-group?] :as shape}]
(when (some? base-node)
- (let [shape-node (if (= (.-id base-node) (dm/str "shape-" id))
- base-node
- (dom/query base-node (dm/str "#shape-" id)))
+ (let [shape-node (get-shape-node base-node id)
frame? (= :frame type)
group? (= :group type)
@@ -110,8 +59,7 @@
(dom/query-all shape-defs ".svg-mask-wrapper")))
text?
- [shape-node
- (dom/query shape-node ".text-container")]
+ [shape-node]
:else
[shape-node]))))
@@ -164,7 +112,7 @@
(defn set-transform-att!
[node att value]
-
+
(let [old-att (dom/get-attribute node (dm/str "data-old-" att))
new-value (if (some? old-att)
(dm/str value " " old-att)
@@ -177,37 +125,20 @@
(defn update-transform!
[base-node shapes transforms modifiers]
- (doseq [{:keys [id type] :as shape} shapes]
+ (doseq [{:keys [id _type] :as shape} shapes]
(when-let [nodes (get-nodes base-node shape)]
(let [transform (get transforms id)
- modifiers (get-in modifiers [id :modifiers])
- text? (= type :text)
- transform-text? (and text? (and (nil? (:resize-vector modifiers)) (nil? (:resize-vector-2 modifiers))))]
+ modifiers (get-in modifiers [id :modifiers])]
(doseq [node nodes]
(cond
- ;; Text shapes need special treatment because their resize only change
- ;; the text area, not the change size/position
- (dom/class? node "frame-thumbnail")
- (let [[transform] (transform-no-resize shape transform modifiers)]
- (set-transform-att! node "transform" transform))
-
(dom/class? node "frame-children")
(set-transform-att! node "transform" (gmt/inverse transform))
- (dom/class? node "text-container")
- (let [modifiers (dissoc modifiers :displacement :rotation)]
- (when (not (gsh/empty-modifiers? modifiers))
- (let [mtx (-> shape
- (assoc :modifiers modifiers)
- (gsh/transform-shape)
- (gsh/transform-matrix {:no-flip true}))]
- (override-transform-att! node "transform" mtx))))
-
(dom/class? node "frame-title")
- (let [shape (-> shape (assoc :modifiers modifiers) gsh/transform-shape)
- zoom (get-in @st/state [:workspace-local :zoom] 1)
- mtx (vwu/title-transform shape zoom)]
+ (let [shape (gsh/transform-shape shape modifiers)
+ zoom (get-in @st/state [:workspace-local :zoom] 1)
+ mtx (vwu/title-transform shape zoom)]
(override-transform-att! node "transform" mtx))
(or (= (dom/get-tag-name node) "mask")
@@ -221,7 +152,7 @@
(= (dom/get-tag-name node) "pattern")
(set-transform-att! node "patternTransform" transform)
- (and (some? transform) (some? node) (or (not text?) transform-text?))
+ (and (some? transform) (some? node))
(set-transform-att! node "transform" transform)))))))
(defn remove-transform!
@@ -249,6 +180,16 @@
(dom/remove-attribute! node "data-old-transform")
(dom/remove-attribute! node "transform")))))))))
+(defn adapt-text-modifiers
+ [modifiers shape]
+ (let [shape' (gsh/transform-shape shape modifiers)
+ scalev
+ (gpt/point (/ (:width shape) (:width shape'))
+ (/ (:height shape) (:height shape')))]
+ ;; Reverse the change in size so we can recalculate the layout
+ (-> modifiers
+ (ctm/resize scalev (-> shape' :points first) (:transform shape') (:transform-inverse shape')))))
+
(defn use-dynamic-modifiers
[objects node modifiers]
@@ -259,41 +200,71 @@
(when (some? modifiers)
(d/mapm (fn [id {modifiers :modifiers}]
(let [shape (get objects id)
- center (gsh/center-shape shape)
- modifiers (cond-> modifiers
- ;; For texts we only use the displacement because
- ;; resize needs to recalculate the text layout
- (= :text (:type shape))
- (select-keys [:displacement :rotation]))]
- (gsh/modifiers->transform center modifiers)))
+ adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? modifiers)))
+ modifiers (cond-> modifiers adapt-text? (adapt-text-modifiers shape))]
+ (ctm/modifiers->transform modifiers)))
modifiers))))
+ add-children (mf/use-memo (mf/deps modifiers) #(ctm/added-children-frames modifiers))
+ add-children (hooks/use-equal-memo add-children)
+ add-children-prev (hooks/use-previous add-children)
+
shapes
(mf/use-memo
(mf/deps transforms)
(fn []
(->> (keys transforms)
+ (filter #(some? (get transforms %)))
(mapv (d/getf objects)))))
prev-shapes (mf/use-var nil)
prev-modifiers (mf/use-var nil)
prev-transforms (mf/use-var nil)]
+ (mf/use-effect
+ (mf/deps add-children)
+ (fn []
+ (doseq [{:keys [frame shape]} add-children-prev]
+ (let [frame-node (get-shape-node node frame)
+ shape-node (get-shape-node shape)
+ mirror-node (dom/query frame-node (dm/fmt ".mirror-shape[href='#shape-%'" shape))]
+ (when mirror-node (.remove mirror-node))
+ (dom/remove-attribute! (dom/get-parent shape-node) "display")))
+
+ (doseq [{:keys [frame shape]} add-children]
+ (let [frame-node (get-shape-node node frame)
+ shape-node (get-shape-node shape)
+
+ use-node
+ (.createElementNS globals/document "http://www.w3.org/2000/svg" "use")
+
+ contents-node
+ (or (dom/query frame-node ".frame-children") frame-node)]
+
+ (dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape))
+ (dom/add-class! use-node "mirror-shape")
+ (dom/append-child! contents-node use-node)
+ (dom/set-attribute! (dom/get-parent shape-node) "display" "none")))))
+
(mf/use-layout-effect
(mf/deps transforms)
(fn []
- (let [is-prev-val? (d/not-empty? @prev-modifiers)
- is-cur-val? (d/not-empty? modifiers)]
- (when (and (not is-prev-val?) is-cur-val?)
- (start-transform! node shapes))
+ (let [curr-shapes-set (into #{} (map :id) shapes)
+ prev-shapes-set (into #{} (map :id) @prev-shapes)
- (when is-cur-val?
+ new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %))))
+ removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))]
+
+ (when (d/not-empty? new-shapes)
+ (start-transform! node new-shapes))
+
+ (when (d/not-empty? shapes)
(update-transform! node shapes transforms modifiers))
- (when (and is-prev-val? (not is-cur-val?))
- (remove-transform! node @prev-shapes))
+ (when (d/not-empty? removed-shapes)
+ (remove-transform! node @prev-shapes)))
- (reset! prev-modifiers modifiers)
- (reset! prev-transforms transforms)
- (reset! prev-shapes shapes))))))
+ (reset! prev-modifiers modifiers)
+ (reset! prev-transforms transforms)
+ (reset! prev-shapes shapes)))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs
index 5c06eb06b..1715b887a 100644
--- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs
@@ -199,6 +199,8 @@
(mf/use-effect
(mf/deps disable?)
(fn []
+ (when (and disable? (not @disable-ref?))
+ (rx/push! updates-str :update))
(reset! disable-ref? disable?)))
(mf/use-effect
diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
index d6f2a1dd1..8a991bf3f 100644
--- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs
@@ -6,6 +6,8 @@
(ns app.main.ui.workspace.shapes.path.editor
(:require
+ [app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp]
[app.common.path.commands :as upc]
@@ -183,8 +185,9 @@
matches (concat (second (:x snap-matches)) (second (:y snap-matches)))]
[:g.snap-paths
- (for [[from to] matches]
- [:line {:x1 (:x from)
+ (for [[idx [from to]] (d/enumerate matches)]
+ [:line {:key (dm/str "snap-" idx "-" from "-" to)
+ :x1 (:x from)
:y1 (:y from)
:x2 (:x to)
:y2 (:y to)
@@ -308,7 +311,7 @@
:start-path? start-p?
:zoom zoom}]])
- (for [position points]
+ (for [[index position] (d/enumerate points)]
(let [show-handler?
(fn [[index prefix]]
(let [handler-position (upc/handler->point content index prefix)]
@@ -322,7 +325,7 @@
pos-handlers (->> pos-handlers (filter show-handler?))
curve? (boolean (seq pos-handlers))]
- [:g.path-node
+ [:g.path-node {:key (dm/str index "-" (:x position) "-" (:y position))}
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
(for [[index prefix] pos-handlers]
(let [handler-position (upc/handler->point content index prefix)
diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs
index 53ed5d9ac..b51432b5b 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs
@@ -58,7 +58,7 @@
:height height
:style {:fill "none" :stroke "red"}}]
- ;; Text baselineazo
+ ;; Text baseline
[:line {:x1 (mth/round x)
:y1 (mth/round (- (:y data) (:height data)))
:x2 (mth/round (+ x width))
diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
index 6ee6b3ddc..8dc6f1e82 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
@@ -8,11 +8,13 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
+ [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.text :as gsht]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
+ [app.common.types.modifiers :as ctm]
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
[app.main.refs :as refs]
@@ -31,22 +33,24 @@
(-> shape
(cond-> (some? (meta (:position-data shape)))
(with-meta (meta (:position-data shape))))
- (dissoc :position-data :transform :transform-inverse)))
+ (dissoc :position-data)))
-(defn strip-modifier
- [modifier]
- (if (or (some? (dm/get-in modifier [:modifiers :resize-vector]))
- (some? (dm/get-in modifier [:modifiers :resize-vector-2])))
- modifier
- (d/update-when modifier :modifiers dissoc :displacement :rotation)))
+(defn fix-position [shape modifier]
+ (let [shape' (-> shape
+ (assoc :grow-type :fixed)
+ (gsh/transform-shape modifier))
+
+ deltav (gpt/to-vec (gpt/point (:selrect shape'))
+ (gpt/point (:selrect shape)))]
+ (gsh/transform-shape shape' (ctm/move-modifiers deltav))))
(defn process-shape [modifiers {:keys [id] :as shape}]
- (let [modifier (-> (get modifiers id) strip-modifier)
- shape (cond-> shape
- (not (gsh/empty-modifiers? (:modifiers modifier)))
- (-> (assoc :grow-type :fixed)
- (merge modifier) gsh/transform-shape))]
+ (let [modifier (dm/get-in modifiers [id :modifiers])]
(-> shape
+ (cond-> (and (some? modifier)
+ (not (ctm/only-move? modifier)))
+ (fix-position modifier))
+
(cond-> (nil? (:position-data shape))
(assoc :migrate true))
strip-position-data)))
@@ -88,20 +92,26 @@
(defn- update-text-modifier
[{:keys [grow-type id]} node]
- (p/let [position-data (tsp/calc-position-data id)
- props {:position-data position-data}
+ (ts/raf
+ #(p/let [position-data (tsp/calc-position-data id)
+ props {:position-data position-data}
- props
- (if (contains? #{:auto-height :auto-width} grow-type)
- (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
- width (mth/ceil width)
- height (mth/ceil height)]
- (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
- (assoc props :width width :height height)
- props))
- props)]
+ props
+ (if (contains? #{:auto-height :auto-width} grow-type)
+ (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
+ width (mth/ceil width)
+ height (mth/ceil height)]
+ (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
+ (cond-> props
+ (= grow-type :auto-width)
+ (assoc :width width)
- (st/emit! (dwt/update-text-modifier id props))))
+ (or (= grow-type :auto-height) (= grow-type :auto-width))
+ (assoc :height height))
+ props))
+ props)]
+
+ (st/emit! (dwt/update-text-modifier id props)))))
(mf/defc text-container
{::mf/wrap-props false
@@ -136,8 +146,8 @@
(fn [id]
(let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id)
- old-modifiers (-> (get prev-modifiers id) strip-modifier)
- new-modifiers (-> (get modifiers id) strip-modifier)
+ old-modifiers (get prev-modifiers id)
+ new-modifiers (get modifiers id)
remote? (some? (-> new-shape meta :session-id)) ]
@@ -159,10 +169,9 @@
handle-update-modifier (mf/use-callback update-text-modifier)
handle-update-shape (mf/use-callback update-text-shape)]
-
[:*
(for [{:keys [id] :as shape} changed-texts]
- [:& text-container {:shape (gsh/transform-shape shape)
+ [:& text-container {:shape shape
:on-update (if (some? (get modifiers (:id shape)))
handle-update-modifier
handle-update-shape)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
index 50ea9fa1e..360151a9c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
@@ -37,35 +37,35 @@
(mf/defc shape-options
{::mf/wrap [#(mf/throttle % 60)]}
[{:keys [shape shapes-with-children page-id file-id shared-libs]}]
- [:*
- (case (:type shape)
- :frame [:& frame/options {:shape shape}]
- :group [:& group/options {:shape shape :shape-with-children shapes-with-children :file-id file-id :shared-libs shared-libs}]
- :text [:& text/options {:shape shape :file-id file-id :shared-libs shared-libs}]
- :rect [:& rect/options {:shape shape}]
- :circle [:& circle/options {:shape shape}]
- :path [:& path/options {:shape shape}]
- :image [:& image/options {:shape shape}]
- :svg-raw [:& svg-raw/options {:shape shape}]
- :bool [:& bool/options {:shape shape}]
- nil)
- [:& exports-menu
- {:ids [(:id shape)]
- :values (select-keys shape [:exports])
- :shape shape
- :page-id page-id
- :file-id file-id}]])
+ (let [workspace-modifiers (mf/deref refs/workspace-modifiers)
+ modifiers (get-in workspace-modifiers [(:id shape) :modifiers])
+ shape (gsh/transform-shape shape modifiers)]
+ [:*
+ (case (:type shape)
+ :frame [:& frame/options {:shape shape}]
+ :group [:& group/options {:shape shape :shape-with-children shapes-with-children :file-id file-id :shared-libs shared-libs}]
+ :text [:& text/options {:shape shape :file-id file-id :shared-libs shared-libs}]
+ :rect [:& rect/options {:shape shape}]
+ :circle [:& circle/options {:shape shape}]
+ :path [:& path/options {:shape shape}]
+ :image [:& image/options {:shape shape}]
+ :svg-raw [:& svg-raw/options {:shape shape}]
+ :bool [:& bool/options {:shape shape}]
+ nil)
+ [:& exports-menu
+ {:ids [(:id shape)]
+ :values (select-keys shape [:exports])
+ :shape shape
+ :page-id page-id
+ :file-id file-id}]]))
(mf/defc options-content
{::mf/wrap [mf/memo]}
[{:keys [selected section shapes shapes-with-children page-id file-id]}]
(let [drawing (mf/deref refs/workspace-drawing)
- base-objects (-> (mf/deref refs/workspace-page-objects))
+ objects (mf/deref refs/workspace-page-objects)
shared-libs (mf/deref refs/workspace-libraries)
- modifiers (mf/deref refs/workspace-modifiers)
- objects-modified (mf/with-memo [base-objects modifiers]
- (gsh/merge-modifiers base-objects modifiers))
- selected-shapes (into [] (keep (d/getf objects-modified)) selected)]
+ selected-shapes (into [] (keep (d/getf objects)) selected)]
[:div.tool-window
[:div.tool-window-content
[:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs
index d7ea7c715..a6089f49d 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs
@@ -30,7 +30,6 @@
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
shapes (as-> old-shapes $
- (map gsh/transform-shape $)
(map gsh/translate-to-frame $ frames))
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
index 1d1191a74..68452f9df 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs
@@ -18,12 +18,12 @@
(def layout-container-flex-attrs
[:layout ;; :flex, :grid in the future
- :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
+ :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
:layout-gap-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number}
- :layout-align-items ;; :start :end :center :strech
+ :layout-align-items ;; :start :end :center :stretch
:layout-justify-content ;; :start :center :end :space-between :space-around
- :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default)
+ :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
:layout-wrap-type ;; :wrap, :no-wrap
:layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -37,13 +37,13 @@
:start i/align-items-column-start
:end i/align-items-column-end
:center i/align-items-column-center
- :strech i/align-items-column-strech
+ :stretch i/align-items-column-strech
:baseline i/align-items-column-baseline)
(case val
:start i/align-items-row-start
:end i/align-items-row-end
:center i/align-items-row-center
- :strech i/align-items-row-strech
+ :stretch i/align-items-row-strech
:baseline i/align-items-row-baseline))
:justify-content (if is-col?
(case val
@@ -66,29 +66,29 @@
:center i/align-content-column-center
:space-around i/align-content-column-around
:space-between i/align-content-column-between
- :strech nil)
-
+ :stretch nil)
+
(case val
:start i/align-content-row-start
:end i/align-content-row-end
:center i/align-content-row-center
:space-around i/align-content-row-around
:space-between i/align-content-row-between
- :strech nil))
+ :stretch nil))
:align-self (if is-col?
- (case val
- :start i/align-self-column-top
- :end i/align-self-column-bottom
- :center i/align-self-column-center
- :strech i/align-self-column-strech
- :baseline i/align-self-column-baseline)
(case val
:start i/align-self-row-left
:end i/align-self-row-right
:center i/align-self-row-center
- :strech i/align-self-row-strech
- :baseline i/align-self-row-baseline))))
+ :stretch i/align-self-row-strech
+ :baseline i/align-self-row-baseline)
+ (case val
+ :start i/align-self-column-top
+ :end i/align-self-column-bottom
+ :center i/align-self-column-center
+ :stretch i/align-self-column-strech
+ :baseline i/align-self-column-baseline))))
(mf/defc direction-btn
[{:keys [dir saved-dir set-direction] :as props}]
@@ -129,7 +129,7 @@
[{:keys [is-col? align-items set-align] :as props}]
[:div.align-items-style
- (for [align [:start :center :end :strech]]
+ (for [align [:start :center :end #_:stretch #_:baseline]]
[:button.align-start.tooltip
{:class (dom/classnames :active (= align-items align)
:tooltip-bottom-left (not= align :start)
@@ -170,7 +170,8 @@
[{:keys [values on-change-style on-change] :as props}]
(let [padding-type (:layout-padding-type values)
- rx (if (apply = (vals (:layout-padding values)))
+ rx (if (and (not (= :multiple (:layout-padding values)))
+ (apply = (vals (:layout-padding values))))
(:p1 (:layout-padding values))
"--")]
@@ -274,13 +275,14 @@
(st/emit! (dwsl/remove-layout ids))
(reset! open? false))
- set-flex (fn []
- (st/emit! (dwsl/remove-layout ids))
- (on-add-layout :flex))
-
- set-grid (fn []
- (st/emit! (dwsl/remove-layout ids))
- (on-add-layout :grid))
+ ;; Uncomment when activating the grid options
+ ;; set-flex (fn []
+ ;; (st/emit! (dwsl/remove-layout ids))
+ ;; (on-add-layout :flex))
+ ;;
+ ;; set-grid (fn []
+ ;; (st/emit! (dwsl/remove-layout ids))
+ ;; (on-add-layout :grid))
;; Flex-direction
@@ -312,7 +314,7 @@
align-content (:layout-align-content values)
set-align-content (fn [value]
(if (= align-content value)
- (st/emit! (dwsl/update-layout ids {:layout-align-content :strech}))
+ (st/emit! (dwsl/update-layout ids {:layout-align-content :stretch}))
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))))
;; Gap
@@ -365,7 +367,7 @@
[:span "Layout"]
(if (:layout values)
[:div.title-actions
- [:div.layout-btns
+ #_[:div.layout-btns
[:button {:on-click set-flex
:class (dom/classnames
:active (= :flex layout-type))} "Flex"]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
index 78b9ccbd4..97aff0879 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -8,7 +8,9 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
+ [app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
+ [app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
@@ -26,13 +28,13 @@
:layout-item-min-h ;; num
:layout-item-max-w ;; num
:layout-item-min-w ;; num
- :layout-item-align-self ;; :start :end :center :strech :baseline
+ :layout-item-align-self ;; :start :end :center :stretch :baseline
])
(mf/defc margin-section
[{:keys [values change-margin-style on-margin-change] :as props}]
- (let [margin-type (or (:layout-margin-type values) :simple)]
+ (let [margin-type (or (:layout-item-margin-type values) :simple)]
[:div.margin-row
[:div.margin-icons
@@ -57,7 +59,7 @@
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-margin-change :simple)
- :value (or (-> values :layout-margin :m1) 0)}]]]
+ :value (or (-> values :layout-item-margin :m1) 0)}]]]
(= margin-type :multiple)
(for [num [:m1 :m2 :m3 :m4]]
@@ -73,95 +75,100 @@
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-margin-change num)
- :value (or (-> values :layout-margin num) 0)}]]]))]]))
+ :value (or (-> values :layout-item-margin num) 0)}]]]))]]))
(mf/defc element-behavior
- [{:keys [is-layout-container? is-layout-child? layout-h-behavior layout-v-behavior on-change-behavior] :as props}]
+ [{:keys [is-layout-container? is-layout-child? layout-item-h-sizing layout-item-v-sizing on-change-behavior] :as props}]
(let [fill? is-layout-child?
- auto? is-layout-container?]
+ auto? is-layout-container?]
[:div.btn-wrapper
[:div.layout-behavior.horizontal
[:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fix width"
- :class (dom/classnames :activated (= layout-h-behavior :fix))
+ :class (dom/classnames :active (= layout-item-h-sizing :fix))
:on-click #(on-change-behavior :h :fix)}
i/auto-fix-layout]
(when fill?
[:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Width 100%"
- :class (dom/classnames :activated (= layout-h-behavior :fill))
+ :class (dom/classnames :active (= layout-item-h-sizing :fill))
:on-click #(on-change-behavior :h :fill)}
i/auto-fill])
(when auto?
[:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fit content"
- :class (dom/classnames :activated (= layout-v-behavior :auto))
+ :class (dom/classnames :active (= layout-item-h-sizing :auto))
:on-click #(on-change-behavior :h :auto)}
i/auto-hug])]
[:div.layout-behavior
[:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fix height"
- :class (dom/classnames :activated (= layout-v-behavior :fix))
+ :class (dom/classnames :active (= layout-item-v-sizing :fix))
:on-click #(on-change-behavior :v :fix)}
i/auto-fix-layout]
(when fill?
[:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Height 100%"
- :class (dom/classnames :activated (= layout-v-behavior :fill))
+ :class (dom/classnames :active (= layout-item-v-sizing :fill))
:on-click #(on-change-behavior :v :fill)}
i/auto-fill])
(when auto?
[:button.behavior-btn.tooltip.tooltip-bottom-left
{:alt "Fit content"
- :class (dom/classnames :activated (= layout-v-behavior :auto))
+ :class (dom/classnames :active (= layout-item-v-sizing :auto))
:on-click #(on-change-behavior :v :auto)}
i/auto-hug])]]))
(mf/defc align-self-row
[{:keys [is-col? align-self set-align-self] :as props}]
- (let [dir-v [:start :center :end :strech :baseline]]
+ (let [dir-v [:start :center :end #_:stretch #_:baseline]]
[:div.align-self-style
(for [align dir-v]
[:button.align-self.tooltip.tooltip-bottom
{:class (dom/classnames :active (= align-self align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
- :alt (dm/str "Align self " (d/name align)) ;; TODO añadir lineas de texto a tradus
+ :alt (dm/str "Align self " (d/name align))
:on-click #(set-align-self align)
:key (str "align-self" align)}
(get-layout-flex-icon :align-self align is-col?)])]))
(mf/defc layout-item-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]}
- [{:keys [ids _type values is-layout-child? is-layout-container?] :as props}]
+ [{:keys [ids values is-layout-child? is-layout-container?] :as props}]
+
(let [open? (mf/use-state false)
toggle-open (fn [] (swap! open? not))
+ selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
+ selection-parents (mf/deref selection-parents-ref)
+
change-margin-style
(fn [type]
- (st/emit! (dwsl/update-layout-child ids {:layout-margin-type type})))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-margin-type type})))
- align-self (:layout-align-self values)
+ align-self (:layout-item-align-self values)
set-align-self (fn [value]
- (st/emit! (dwsl/update-layout-child ids {:layout-align-self value})))
+ (if (= align-self value)
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil}))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value}))))
- saved-dir (:layout-flex-dir values)
- is-col? (or (= :column saved-dir) (= :reverse-column saved-dir))
+ is-col? (every? ctl/col? selection-parents)
on-margin-change
(fn [type val]
(if (= type :simple)
- (st/emit! (dwsl/update-layout-child ids {:layout-margin {:m1 val :m2 val :m3 val :m4 val}}))
- (st/emit! (dwsl/update-layout-child ids {:layout-margin {type val}}))))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-margin {:m1 val :m2 val :m3 val :m4 val}}))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-margin {type val}}))))
on-change-behavior
(fn [dir value]
(if (= dir :h)
- (st/emit! (dwsl/update-layout-child ids {:layout-h-behavior value}))
- (st/emit! (dwsl/update-layout-child ids {:layout-v-behavior value}))))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-h-sizing value}))
+ (st/emit! (dwsl/update-layout-child ids {:layout-item-v-sizing value}))))
on-size-change
(fn [measure value]
@@ -176,14 +183,16 @@
[:div.row-title "Sizing"]
[:& element-behavior {:is-layout-child? is-layout-child?
:is-layout-container? is-layout-container?
- :layout-v-behavior (or (:layout-v-behavior values) :fix)
- :layout-h-behavior (or (:layout-h-behavior values) :fix)
+ :layout-item-v-sizing (or (:layout-item-v-sizing values) :fix)
+ :layout-item-h-sizing (or (:layout-item-h-sizing values) :fix)
:on-change-behavior on-change-behavior}]]
-
- [:& margin-section {:values values
- :change-margin-style change-margin-style
- :on-margin-change on-margin-change}]
+
+ (when is-layout-child?
+ [:& margin-section {:values values
+ :change-margin-style change-margin-style
+ :on-margin-change on-margin-change}])
+
[:div.advanced-ops-container
[:button.advanced-ops.toltip.tooltip-bottom
{:on-click toggle-open
@@ -193,21 +202,22 @@
(when @open?
[:div.advanced-ops-body
- [:div.layout-row
- [:div.direction-wrap.row-title "Align"]
- [:div.btn-wrapper
- [:& align-self-row {:is-col? is-col?
- :align-self align-self
- :set-align-self set-align-self}]]]
+ (when is-layout-child?
+ [:div.layout-row
+ [:div.direction-wrap.row-title "Align"]
+ [:div.btn-wrapper
+ [:& align-self-row {:is-col? is-col?
+ :align-self align-self
+ :set-align-self set-align-self}]]])
[:div.input-wrapper
- (for [item [:layout-max-h :layout-min-h :layout-max-w :layout-min-w]]
+ (for [item [:layout-item-max-h :layout-item-min-h :layout-item-max-w :layout-item-min-w]]
[:div.tooltip.tooltip-bottom
{:key (d/name item)
:alt (tr (dm/str "workspace.options.layout-item.title." (d/name item)))
- :class (dom/classnames "maxH" (= item :layout-max-h)
- "minH" (= item :layout-min-h)
- "maxW" (= item :layout-max-w)
- "minW" (= item :layout-min-w))}
+ :class (dom/classnames "maxH" (= item :layout-item-max-h)
+ "minH" (= item :layout-item-min-h)
+ "maxW" (= item :layout-item-max-w)
+ "minW" (= item :layout-item-min-w))}
[:div.input-element
{:alt (tr (dm/str "workspace.options.layout-item." (d/name item)))}
[:> numeric-input
@@ -217,6 +227,5 @@
:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-size-change item)
- ;; :value (get values item)
- :value 100}]]])]])]]
- ))
+ :value (get values item)
+ :nillable true}]]])]])]]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
index 307b19164..5c0f2fa7c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
+ [app.common.types.shape.layout :as ctl]
[app.common.types.shape.radius :as ctsr]
[app.main.constants :refer [size-presets]]
[app.main.data.workspace :as udw]
@@ -79,11 +80,21 @@
[shape])
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
+ selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids))
+ selection-parents (mf/deref selection-parents-ref)
+
+ flex-child? (->> selection-parents (some ctl/layout?))
+
+ flex-container? (ctl/layout? shape)
+ flex-auto-width? (ctl/auto-width? shape)
+ flex-fill-width? (ctl/fill-width? shape)
+ flex-auto-height? (ctl/auto-height? shape)
+ flex-fill-height? (ctl/fill-height? shape)
+
;; To show interactively the measures while the user is manipulating
;; the shape with the mouse, generate a copy of the shapes applying
;; the transient transformations.
shapes (as-> old-shapes $
- (map gsh/transform-shape $)
(map gsh/translate-to-frame $ frames))
;; For rotated or stretched shapes, the origin point we show in the menu
@@ -296,6 +307,7 @@
:placeholder "--"
:on-click select-all
:on-change on-width-change
+ :disabled (and (or flex-child? flex-container?) (or flex-auto-width? flex-fill-width?))
:value (:width values)}]]
[:div.input-element.height {:title (tr "workspace.options.height")}
@@ -304,6 +316,7 @@
:placeholder "--"
:on-click select-all
:on-change on-height-change
+ :disabled (and (or flex-child? flex-container?) (or flex-auto-height? flex-fill-height?))
:value (:height values)}]]
[:div.lock-size {:class (dom/classnames
@@ -323,11 +336,13 @@
:placeholder "--"
:on-click select-all
:on-change on-pos-x-change
+ :disabled flex-child?
:value (:x values)}]]
[:div.input-element.Yaxis {:title (tr "workspace.options.y")}
[:> numeric-input {:no-validate true
:placeholder "--"
:on-click select-all
+ :disabled flex-child?
:on-change on-pos-y-change
:value (:y values)}]]])
@@ -441,5 +456,3 @@
(tr "workspace.options.show-in-viewer")]])
]]]))
-
-
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
index bc804fad9..fb83ff3c3 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.rows.stroke-row
(:require
[app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.hooks :as h]
@@ -132,9 +133,10 @@
:on-close (close-caps-select start-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state)
:left (:left @start-caps-state)}}
- (for [[value label separator] (stroke-cap-names)]
+ (for [[idx [value label separator]] (d/enumerate (stroke-cap-names))]
(let [img (value->img value)]
- [:li {:class (dom/classnames :separator separator)
+ [:li {:key (dm/str "start-cap-" idx)
+ :class (dom/classnames :separator separator)
:on-click #(on-stroke-cap-start-change index value)}
(when img [:img {:src (value->img value)}])
label]))]]
@@ -151,9 +153,10 @@
:on-close (close-caps-select end-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state)
:left (:left @end-caps-state)}}
- (for [[value label separator] (stroke-cap-names)]
+ (for [[idx [value label separator]] (d/enumerate (stroke-cap-names))]
(let [img (value->img value)]
- [:li {:class (dom/classnames :separator separator)
+ [:li {:key (dm/str "end-cap-" idx)
+ :class (dom/classnames :separator separator)
:on-click #(on-stroke-cap-end-change index value)}
(when img [:img {:src (value->img value)}])
label]))]]])]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs
index 1c45a1bf2..ff84041d0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs
@@ -41,8 +41,10 @@
:values layout-item-values
:is-layout-child? true
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& layer-menu {:ids ids
:type type
:values layer-values}]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs
index af0878b7e..812e3c8d9 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs
@@ -43,8 +43,9 @@
:is-layout-child? true
:is-layout-container? false
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& layer-menu {:ids ids
:type type
:values layer-values}]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
index bd9df2411..1fcb34ed6 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.frame
(:require
+ [app.common.types.shape.layout :as ctl]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
@@ -35,26 +36,27 @@
layout-item-values (select-keys shape layout-item-attrs)
is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids))
- is-layout-child? (mf/deref is-layout-child-ref)]
+ is-layout-child? (mf/deref is-layout-child-ref)
+ is-layout-container? (ctl/layout? shape)]
[:*
[:& measures-menu {:ids [(:id shape)]
:values measure-values
:type type
:shape shape}]
- [:& constraints-menu {:ids ids
- :values constraint-values}]
- (when layout-active?
- [:*
- [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
+ (when (or layout-active? is-layout-container?)
+ [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}])
- (when (or (:layout shape) is-layout-child?)
- [:& layout-item-menu
- {:ids ids
- :type type
- :values layout-item-values
- :is-layout-child? is-layout-child?
- :is-layout-container? (:layout shape)
- :shape shape}])])
+ (when (and layout-active? (or is-layout-child? is-layout-container?))
+ [:& layout-item-menu
+ {:ids ids
+ :type type
+ :values layout-item-values
+ :is-layout-child? is-layout-child?
+ :is-layout-container? is-layout-container?
+ :shape shape}])
[:& layer-menu {:ids ids
:type type
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs
index b81a4a46a..6f921a4b0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs
@@ -62,7 +62,8 @@
:is-layout-container? false
:values layout-item-values}])
- [:& constraints-menu {:ids constraint-ids :values constraint-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids constraint-ids :values constraint-values}])
[:& layer-menu {:type type :ids layer-ids :values layer-values}]
(when-not (empty? fill-ids)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs
index d2b1a8358..0f99ca979 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs
@@ -44,8 +44,9 @@
:is-layout-child? true
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& layer-menu {:ids ids
:type type
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
index 09a34eb25..06425fd9e 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
@@ -34,116 +34,134 @@
;; - text: read it from all the content nodes, and then merging it.
(def type->read-mode
{:frame
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :children
- :blur :children
- :stroke :shape
- :text :children
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :children
+ :blur :children
+ :stroke :shape
+ :text :children
+ :exports :shape
+ :layout-container :shape
+ :layout-item :shape}
:group
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :children
- :shadow :shape
- :blur :shape
- :stroke :children
- :text :children
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :children
+ :shadow :shape
+ :blur :shape
+ :stroke :children
+ :text :children
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:path
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :ignore
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:text
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :text
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :text
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :text
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :text
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:image
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :ignore
- :shadow :shape
- :blur :shape
- :stroke :ignore
- :text :ignore
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :ignore
+ :shadow :shape
+ :blur :shape
+ :stroke :ignore
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:rect
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :ignore
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:circle
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :ignore
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:svg-raw
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :ignore
- :exports :shape}
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}
:bool
- {:measure :shape
- :layer :shape
- :constraint :shape
- :fill :shape
- :shadow :shape
- :blur :shape
- :stroke :shape
- :text :ignore
- :exports :shape}})
+ {:measure :shape
+ :layer :shape
+ :constraint :shape
+ :fill :shape
+ :shadow :shape
+ :blur :shape
+ :stroke :shape
+ :text :ignore
+ :exports :shape
+ :layout-container :ignore
+ :layout-item :shape}})
(def group->attrs
- {:measure measure-attrs
- :layer layer-attrs
- :constraint constraint-attrs
- :fill fill-attrs
- :shadow shadow-attrs
- :blur blur-attrs
- :stroke stroke-attrs
- :text ot/attrs
- :exports exports-attrs
- :layout layout-container-flex-attrs
- :layout-item layout-item-attrs})
+ {:measure measure-attrs
+ :layer layer-attrs
+ :constraint constraint-attrs
+ :fill fill-attrs
+ :shadow shadow-attrs
+ :blur blur-attrs
+ :stroke stroke-attrs
+ :text ot/attrs
+ :exports exports-attrs
+ :layout-container layout-container-flex-attrs
+ :layout-item layout-item-attrs})
(def shadow-keys [:style :color :offset-x :offset-y :blur :spread])
@@ -235,6 +253,10 @@
[props]
(let [shapes (unchecked-get props "shapes")
shapes-with-children (unchecked-get props "shapes-with-children")
+
+ workspace-modifiers (mf/deref refs/workspace-modifiers)
+ shapes (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])) shapes)
+
page-id (unchecked-get props "page-id")
file-id (unchecked-get props "file-id")
shared-libs (unchecked-get props "shared-libs")
@@ -254,20 +276,20 @@
is-layout-child? (mf/deref is-layout-child-ref)
has-text? (contains? all-types :text)
-
+
[measure-ids measure-values] (get-attrs shapes objects :measure)
- [layer-ids layer-values
- constraint-ids constraint-values
- fill-ids fill-values
- shadow-ids shadow-values
- blur-ids blur-values
- stroke-ids stroke-values
- text-ids text-values
- exports-ids exports-values
- layout-ids layout-container-values
- layout-item-ids layout-item-values]
+ [layer-ids layer-values
+ constraint-ids constraint-values
+ fill-ids fill-values
+ shadow-ids shadow-values
+ blur-ids blur-values
+ stroke-ids stroke-values
+ text-ids text-values
+ exports-ids exports-values
+ layout-container-ids layout-container-values
+ layout-item-ids layout-item-values]
(mf/use-memo
(mf/deps objects-no-measures)
(fn []
@@ -282,7 +304,7 @@
(get-attrs shapes objects-no-measures :stroke)
(get-attrs shapes objects-no-measures :text)
(get-attrs shapes objects-no-measures :exports)
- (get-attrs shapes objects-no-measures :layout)
+ (get-attrs shapes objects-no-measures :layout-container)
(get-attrs shapes objects-no-measures :layout-item)
])))]
@@ -291,8 +313,8 @@
[:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}])
(when (:layout layout-container-values)
- [:& layout-container-menu {:type type :ids layout-ids :values layout-container-values}])
-
+ [:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values}])
+
(when is-layout-child?
[:& layout-item-menu
{:type type
@@ -301,7 +323,7 @@
:is-layout-container? true
:values layout-item-values}])
- (when-not (empty? constraint-ids)
+ (when-not (or (empty? constraint-ids) is-layout-child?)
[:& constraints-menu {:ids constraint-ids :values constraint-values}])
(when-not (empty? layer-ids)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs
index 22f750df4..5b1a89426 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs
@@ -43,8 +43,9 @@
:is-layout-child? true
:is-layout-container? false
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& layer-menu {:ids ids
:type type
:values layer-values}]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs
index 654aad11b..582f476d8 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs
@@ -44,8 +44,10 @@
:values layout-item-values
:is-layout-child? true
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& layer-menu {:ids ids
:type type
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
index bd7a2c138..e05bf68d8 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
@@ -120,8 +120,9 @@
:is-layout-child? true
:shape shape}])
- [:& constraints-menu {:ids ids
- :values constraint-values}]
+ (when (not is-layout-child?)
+ [:& constraints-menu {:ids ids
+ :values constraint-values}])
[:& fill-menu {:ids ids
:type type
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
index 620832b1d..7c84cdeb5 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
@@ -78,9 +78,11 @@
:values layout-item-values
:is-layout-child? true
:shape shape}])
- [:& constraints-menu
- {:ids ids
- :values (select-keys shape constraint-attrs)}]
+
+ (when (not is-layout-child?)
+ [:& constraints-menu
+ {:ids ids
+ :values (select-keys shape constraint-attrs)}])
[:& layer-menu {:ids ids
:type type
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index 3cf07759b..80217083d 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -23,6 +23,7 @@
[app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh]
[app.main.ui.workspace.viewport.actions :as actions]
[app.main.ui.workspace.viewport.comments :as comments]
+ [app.main.ui.workspace.viewport.debug :as wvd]
[app.main.ui.workspace.viewport.drawarea :as drawarea]
[app.main.ui.workspace.viewport.frame-grid :as frame-grid]
[app.main.ui.workspace.viewport.gradients :as gradients]
@@ -79,8 +80,7 @@
modifiers (mf/deref refs/workspace-modifiers)
objects-modified (mf/with-memo [base-objects modifiers]
- (gsh/merge-modifiers base-objects modifiers))
-
+ (gsh/apply-objects-modifiers base-objects modifiers))
background (get options :background clr/canvas)
;; STATE
@@ -91,6 +91,7 @@
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
hover-disabled? (mf/use-state false)
+ hover-top-frame-id (mf/use-state nil)
frame-hover (mf/use-state nil)
active-frames (mf/use-state #{})
@@ -187,7 +188,7 @@
(hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?)
(hooks/setup-keyboard alt? mod? space?)
- (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom)
+ (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom)
(hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
@@ -285,7 +286,7 @@
(when show-frame-outline?
[:& outline/shape-outlines
- {:objects base-objects
+ {:objects objects-modified
:hover #{(->> @hover-ids
(filter #(cph/frame-shape? (get base-objects %)))
(remove selected)
@@ -336,10 +337,9 @@
(when show-prototypes?
[:& widgets/frame-flows
{:flows (:flows options)
- :objects base-objects
+ :objects objects-modified
:selected selected
:zoom zoom
- :modifiers modifiers
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}])
@@ -348,8 +348,7 @@
[:& drawarea/draw-area
{:shape drawing-obj
:zoom zoom
- :tool drawing-tool
- :modifiers modifiers}])
+ :tool drawing-tool}])
(when show-grids?
[:& frame-grid/frame-grid
@@ -371,9 +370,8 @@
:zoom zoom
:page-id page-id
:selected selected
- :objects base-objects
- :focus focus
- :modifiers modifiers}])
+ :objects objects-modified
+ :focus focus}])
(when show-snap-distance?
[:& snap-distances/snap-distances
@@ -416,9 +414,21 @@
{:zoom zoom
:vbox vbox
:hover-frame frame-parent
- :modifiers modifiers
:disabled-guides? disabled-guides?}])
+ ;; DEBUG LAYOUT DROP-ZONES
+ (when (debug? :layout-drop-zones)
+ [:& wvd/debug-drop-zones {:selected-shapes selected-shapes
+ :objects objects-modified
+ :hover-top-frame-id @hover-top-frame-id
+ :zoom zoom}])
+
+ (when (debug? :layout-lines)
+ [:& wvd/debug-layout {:selected-shapes selected-shapes
+ :objects objects-modified
+ :hover-top-frame-id @hover-top-frame-id
+ :zoom zoom}])
+
(when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
[:defs
diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs
new file mode 100644
index 000000000..f1fcecf0e
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs
@@ -0,0 +1,137 @@
+;; 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 INC
+
+(ns app.main.ui.workspace.viewport.debug
+ (:require
+ [app.common.data :as d]
+ [app.common.data.macros :as dm]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes :as gsh]
+ [app.common.geom.shapes.flex-layout :as gsl]
+ [app.common.geom.shapes.points :as gpo]
+ [app.common.pages.helpers :as cph]
+ [app.common.types.shape.layout :as ctl]
+ [cuerdas.core :as str]
+ [rumext.v2 :as mf]))
+
+;; Helper to debug the bounds when set the "hug" content property
+#_(mf/defc debug-layout
+ "Debug component to show the auto-layout drop areas"
+ {::mf/wrap-props false}
+ [props]
+
+ (let [objects (unchecked-get props "objects")
+ selected-shapes (unchecked-get props "selected-shapes")
+ hover-top-frame-id (unchecked-get props "hover-top-frame-id")
+
+ selected-frame
+ (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type)))
+ (first selected-shapes))
+
+ shape (or selected-frame (get objects hover-top-frame-id))]
+
+ (when (and shape (:layout shape))
+ (let [children (cph/get-immediate-children objects (:id shape))
+ layout-data (gsl/calc-layout-data shape children)
+
+ {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} (:layout-padding shape)
+ pad-top (or pad-top 0)
+ pad-right (or pad-right 0)
+ pad-bottom (or pad-bottom 0)
+ pad-left (or pad-left 0)
+
+ layout-bounds (gsl/layout-content-bounds shape children)]
+ [:g.debug-layout {:pointer-events "none"
+ :transform (gsh/transform-str shape)}
+
+
+ [:rect {:x (:x layout-bounds)
+ :y (:y layout-bounds)
+ :width (:width layout-bounds)
+ :height (:height layout-bounds)
+ :style {:stroke "red"
+ :fill "none"}}]]))))
+
+(mf/defc debug-layout
+ "Debug component to show the auto-layout drop areas"
+ {::mf/wrap-props false}
+ [props]
+
+ (let [objects (unchecked-get props "objects")
+ zoom (unchecked-get props "zoom")
+ selected-shapes (unchecked-get props "selected-shapes")
+ hover-top-frame-id (unchecked-get props "hover-top-frame-id")
+
+ selected-frame
+ (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type)))
+ (first selected-shapes))
+
+ shape (or selected-frame (get objects hover-top-frame-id))]
+
+ (when (and shape (ctl/layout? shape))
+ (let [row? (ctl/row? shape)
+ col? (ctl/col? shape)
+
+ children (cph/get-immediate-children objects (:id shape))
+ layout-data (gsl/calc-layout-data shape children)
+
+ layout-bounds (:layout-bounds layout-data)
+ xv #(gpo/start-hv layout-bounds %)
+ yv #(gpo/start-vv layout-bounds %)]
+ [:g.debug-layout {:pointer-events "none"}
+ (for [[idx {:keys [start-p line-width line-height layout-gap-row layout-gap-col num-children]}] (d/enumerate (:layout-lines layout-data))]
+ (let [line-width (if row? (+ line-width (* (dec num-children) layout-gap-row)) line-width)
+ line-height (if col? (+ line-height (* (dec num-children) layout-gap-col)) line-height)
+
+ points [start-p
+ (-> start-p (gpt/add (xv line-width)))
+ (-> start-p (gpt/add (xv line-width)) (gpt/add (yv line-height)))
+ (-> start-p (gpt/add (yv line-height)))
+ ]]
+ [:g.layout-line {:key (dm/str "line-" idx)}
+ [:polygon {:points (->> points (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " "))
+ :style {:stroke "red" :stroke-width (/ 2 zoom) :stroke-dasharray (dm/str (/ 10 zoom) " " (/ 5 zoom))}}]]))]))))
+
+(mf/defc debug-drop-zones
+ "Debug component to show the auto-layout drop areas"
+ {::mf/wrap-props false}
+ [props]
+
+ (let [objects (unchecked-get props "objects")
+ zoom (unchecked-get props "objects")
+ selected-shapes (unchecked-get props "selected-shapes")
+ hover-top-frame-id (unchecked-get props "hover-top-frame-id")
+
+ selected-frame
+ (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type)))
+ (first selected-shapes))
+
+ shape (or selected-frame (get objects hover-top-frame-id))]
+
+ (when (and shape (:layout shape))
+ (let [children (cph/get-immediate-children objects (:id shape))
+ layout-data (gsl/calc-layout-data shape children)
+ drop-areas (gsl/layout-drop-areas shape layout-data children)]
+ [:g.debug-layout {:pointer-events "none"
+ :transform (gsh/transform-str shape)}
+ (for [[idx drop-area] (d/enumerate drop-areas)]
+ [:g.drop-area {:key (dm/str "drop-area-" idx)}
+ [:rect {:x (:x drop-area)
+ :y (:y drop-area)
+ :width (:width drop-area)
+ :height (:height drop-area)
+ :style {:fill "blue"
+ :fill-opacity 0.3
+ :stroke "red"
+ :stroke-width (/ zoom 1)
+ :stroke-dasharray (dm/str (/ 3 zoom) " " (/ 6 zoom))}}]
+ [:text {:x (:x drop-area)
+ :y (:y drop-area)
+ :width (:width drop-area)
+ :height (:height drop-area)
+ :alignment-baseline "hanging"
+ :fill "black"}
+ (:index drop-area)]])]))))
diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
index 8d31ae5ac..356af918e 100644
--- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
@@ -7,7 +7,6 @@
(ns app.main.ui.workspace.viewport.drawarea
"Drawing components."
(:require
- [app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.main.ui.shapes.path :refer [path-shape]]
[app.main.ui.workspace.shapes :as shapes]
@@ -22,7 +21,7 @@
[:g.draw-area
[:g {:style {:pointer-events "none"}}
- [:& shapes/shape-wrapper {:shape (gsh/transform-shape shape)}]]
+ [:& shapes/shape-wrapper {:shape shape}]]
(case tool
:path [:& path-editor {:shape shape :zoom zoom}]
@@ -31,7 +30,7 @@
(mf/defc generic-draw-area
[{:keys [shape zoom]}]
- (let [{:keys [x y width height]} (:selrect (gsh/transform-shape shape))]
+ (let [{:keys [x y width height]} (:selrect shape)]
(when (and x y
(not (mth/nan? x))
(not (mth/nan? y)))
diff --git a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs
index a26d7c6ab..c7b403286 100644
--- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs
@@ -7,7 +7,6 @@
(ns app.main.ui.workspace.viewport.frame-grid
(:require
[app.common.data :as d]
- [app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
@@ -138,4 +137,4 @@
(or (empty? focus) (contains? focus (:id frame))))
[:& grid-display-frame {:key (str "grid-" (:id frame))
:zoom zoom
- :frame (gsh/transform-shape frame)}]))]))
+ :frame frame}]))]))
diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs
index efefcaf7a..5d158ee22 100644
--- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs
@@ -278,7 +278,7 @@
frame]} (use-guide handle-change-position get-hover-frame zoom guide)
base-frame (or frame hover-frame)
- frame (gsh/transform-shape (merge base-frame frame-modifier))
+ frame (gsh/transform-shape base-frame frame-modifier)
move-vec (gpt/to-vec (gpt/point (:x base-frame) (:y base-frame))
(gpt/point (:x frame) (:y frame)))
diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
index c9071afc8..2ea2310da 100644
--- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
@@ -11,6 +11,7 @@
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctt]
+ [app.common.uuid :as uuid]
[app.main.data.shortcuts :as dsc]
[app.main.data.workspace :as dw]
[app.main.data.workspace.path.shortcuts :as psc]
@@ -104,7 +105,8 @@
(some #(cph/is-parent? objects % group-id))
(not))))
-(defn setup-hover-shapes [page-id move-stream objects transform selected mod? hover hover-ids hover-disabled? focus zoom]
+(defn setup-hover-shapes
+ [page-id move-stream objects transform selected mod? hover hover-ids hover-top-frame-id hover-disabled? focus zoom]
(let [;; We use ref so we don't recreate the stream on a change
zoom-ref (mf/use-ref zoom)
mod-ref (mf/use-ref @mod?)
@@ -143,9 +145,10 @@
(rx/map #(deref last-point-ref)))
(->> move-stream
+ (rx/tap #(reset! last-point-ref %))
;; When transforming shapes we stop querying the worker
(rx/merge-map query-point)
- (rx/tap #(reset! last-point-ref %))))))]
+ ))))]
;; Refresh the refs on a value change
(mf/use-effect
@@ -213,7 +216,8 @@
(first)
(get objects))]
(reset! hover hover-shape)
- (reset! hover-ids ids))))))
+ (reset! hover-ids ids)
+ (reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref))))))))
(defn setup-viewport-modifiers
[modifiers objects]
@@ -221,7 +225,7 @@
(mf/use-memo
(mf/deps objects)
#(ctt/get-root-shapes-ids objects))
- modifiers (select-keys modifiers root-frame-ids)]
+ modifiers (select-keys modifiers (conj root-frame-ids uuid/zero))]
(sfd/use-dynamic-modifiers objects globals/document modifiers)))
(defn inside-vbox [vbox objects frame-id]
diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
index 4ba7555ba..7ecc5989a 100644
--- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
@@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
- [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.shape.interactions :as ctsi]
[app.main.data.workspace :as dw]
@@ -254,13 +253,11 @@
(mf/defc interactions
[{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
(let [active-shapes (into []
- (comp (filter #(seq (:interactions %)))
- (map gsh/transform-shape))
+ (comp (filter #(seq (:interactions %))))
(vals objects))
selected-shapes (into []
- (comp (map (d/getf objects))
- (map gsh/transform-shape))
+ (map (d/getf objects))
selected)
{:keys [editing-interaction-index
diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs
index a8fd6c2df..416f71625 100644
--- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs
@@ -72,7 +72,7 @@
"var(--color-primary)" "var(--color-component-highlight)")]
(for [shape shapes]
[:& outline {:key (str "outline-" (:id shape))
- :shape (gsh/transform-shape shape)
+ :shape shape
:zoom zoom
:color color}])))
diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
index 65d1958e4..0a1cb7821 100644
--- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
@@ -341,7 +341,6 @@
(let [shape (mf/use-memo
(mf/deps shapes)
#(->> shapes
- (map gsh/transform-shape)
(gsh/selection-rect)
(cts/setup-shape)))
on-resize
@@ -369,7 +368,6 @@
(let [shape (mf/use-memo
(mf/deps shapes)
#(->> shapes
- (map gsh/transform-shape)
(gsh/selection-rect)
(cts/setup-shape)))]
@@ -384,7 +382,6 @@
(mf/defc single-handlers
[{:keys [shape zoom color disable-handlers] :as props}]
(let [shape-id (:id shape)
- shape (gsh/transform-shape shape)
on-resize
(fn [current-position _initial-position event]
@@ -408,14 +405,13 @@
(mf/defc single-selection
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}]
- (let [shape (gsh/transform-shape shape)]
- [:& controls-selection
- {:shape shape
- :zoom zoom
- :color color
- :disable-handlers disable-handlers
- :on-move-selected on-move-selected
- :on-context-menu on-context-menu}]))
+ [:& controls-selection
+ {:shape shape
+ :zoom zoom
+ :color color
+ :disable-handlers disable-handlers
+ :on-move-selected on-move-selected
+ :on-context-menu on-context-menu}])
(mf/defc selection-area
{::mf/wrap [mf/memo]}
diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
index 95cc5693c..7854fd8c8 100644
--- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
@@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
+ [app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.ui.formats :as fmt]
[app.main.worker :as uw]
@@ -272,18 +273,20 @@
frame-id (-> selected-shapes first :frame-id)
frame (mf/deref (refs/object-by-id frame-id))
selrect (gsh/selection-rect selected-shapes)]
- [:g.distance
- [:& shape-distance
- {:selrect selrect
- :page-id page-id
- :frame frame
- :zoom zoom
- :coord :x
- :selected selected}]
- [:& shape-distance
- {:selrect selrect
- :page-id page-id
- :frame frame
- :zoom zoom
- :coord :y
- :selected selected}]]))
+
+ (when-not (ctl/layout? frame)
+ [:g.distance
+ [:& shape-distance
+ {:selrect selrect
+ :page-id page-id
+ :frame frame
+ :zoom zoom
+ :coord :x
+ :selected selected}]
+ [:& shape-distance
+ {:selrect selrect
+ :page-id page-id
+ :frame frame
+ :zoom zoom
+ :coord :y
+ :selected selected}]])))
diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
index 1ef1bf79c..88fe7bc81 100644
--- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
@@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
+ [app.common.types.shape.layout :as ctl]
[app.main.snap :as snap]
[app.util.geom.snap-points :as sp]
[beicon.core :as rx]
@@ -50,20 +51,14 @@
:opacity line-opacity}])
(defn get-snap
- [coord {:keys [shapes page-id remove-snap? zoom modifiers]}]
- (let [shapes-sr
- (->> shapes
- ;; Merge modifiers into shapes
- (map #(merge % (get modifiers (:id %))))
- ;; Create the bounding rectangle for the shapes
- (gsh/selection-rect))
+ [coord {:keys [shapes page-id remove-snap? zoom]}]
+ (let [bounds (gsh/selection-rect shapes)
+ frame-id (snap/snap-frame-id shapes)]
- frame-id (snap/snap-frame-id shapes)]
-
- (->> (rx/of shapes-sr)
+ (->> (rx/of bounds)
(rx/flat-map
- (fn [selrect]
- (->> (sp/selrect-snap-points selrect)
+ (fn [bounds]
+ (->> (sp/selrect-snap-points bounds)
(map #(vector frame-id %)))))
(rx/flat-map
@@ -159,7 +154,7 @@
(mf/defc snap-points
{::mf/wrap [mf/memo]}
- [{:keys [layout zoom objects selected page-id drawing modifiers focus] :as props}]
+ [{:keys [layout zoom objects selected page-id drawing focus] :as props}]
(us/assert set? selected)
(let [shapes (into [] (keep (d/getf objects)) selected)
@@ -178,10 +173,11 @@
(and (= type :layout) (= grid :square))
(= type :guide))))
- shapes (if drawing [drawing] shapes)]
- [:& snap-feedback {:shapes shapes
- :page-id page-id
- :remove-snap? remove-snap?
- :zoom zoom
- :modifiers modifiers}]))
+ shapes (if drawing [drawing] shapes)
+ frame-id (snap/snap-frame-id shapes)]
+ (when-not (ctl/layout? objects frame-id)
+ [:& snap-feedback {:shapes shapes
+ :page-id page-id
+ :remove-snap? remove-snap?
+ :zoom zoom}])))
diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
index 3c36704cc..40b54f43c 100644
--- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
@@ -53,9 +53,7 @@
(defn text-transform
[{:keys [x y]} zoom]
(let [inv-zoom (/ 1 zoom)]
- (str
- "scale(" inv-zoom ", " inv-zoom ") "
- "translate(" (* zoom x) ", " (* zoom y) ")")))
+ (dm/fmt "scale(%, %) translate(%, %)" inv-zoom inv-zoom (* zoom x) (* zoom y))))
(defn title-transform [frame zoom]
(let [frame-transform (gsh/transform-str frame {:no-flip true})
diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
index 42596402e..d78b53463 100644
--- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
@@ -9,7 +9,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
- [app.common.geom.shapes :as gsh]
[app.common.types.shape-tree :as ctt]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
@@ -22,6 +21,7 @@
[app.main.ui.workspace.viewport.path-actions :refer [path-actions]]
[app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom]
+ [debug :refer [debug?]]
[rumext.v2 :as mf]))
(mf/defc pixel-grid
@@ -35,8 +35,8 @@
:pattern-units "userSpaceOnUse"}
[:path {:d "M 1 0 L 0 0 0 1"
:style {:fill "none"
- :stroke "var(--color-info)"
- :stroke-opacity "0.2"
+ :stroke (if (debug? :pixel-grid) "red" "var(--color-info)")
+ :stroke-opacity (if (debug? :pixel-grid) 1 "0.2")
:stroke-width (str (/ 1 zoom))}}]]]
[:rect {:x (:x vbox)
:y (:y vbox)
@@ -182,8 +182,8 @@
:on-frame-select on-frame-select}]))]))
(mf/defc frame-flow
- [{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
- (let [{:keys [x y]} (gsh/transform-shape frame)
+ [{:keys [flow frame selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
+ (let [{:keys [x y]} frame
flow-pos (gpt/point x (- y (/ 35 zoom)))
on-mouse-down
@@ -217,9 +217,7 @@
:y -15
:width 100000
:height 24
- :transform (str (when (and selected? modifiers)
- (str (:displacement modifiers) " " ))
- (vwu/text-transform flow-pos zoom))}
+ :transform (vwu/text-transform flow-pos zoom)}
[:div.flow-badge {:class (dom/classnames :selected selected?)}
[:div.content {:on-mouse-down on-mouse-down
:on-double-click on-double-click
@@ -234,7 +232,6 @@
(let [flows (unchecked-get props "flows")
objects (unchecked-get props "objects")
zoom (unchecked-get props "zoom")
- modifiers (unchecked-get props "modifiers")
selected (or (unchecked-get props "selected") #{})
on-frame-enter (unchecked-get props "on-frame-enter")
@@ -248,7 +245,6 @@
:frame frame
:selected? (contains? selected (:id frame))
:zoom zoom
- :modifiers modifiers
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]))]))
diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs
index ea2084c87..a6120d50f 100644
--- a/frontend/src/app/util/geom/snap_points.cljs
+++ b/frontend/src/app/util/geom/snap_points.cljs
@@ -29,10 +29,9 @@
(defn shape-snap-points
[{:keys [hidden blocked] :as shape}]
(when (and (not blocked) (not hidden))
- (let [shape (gsh/transform-shape shape)]
- (case (:type shape)
- :frame (-> shape :points gsh/points->selrect frame-snap-points)
- (into #{(gsh/center-shape shape)} (:points shape))))))
+ (case (:type shape)
+ :frame (-> shape :points gsh/points->selrect frame-snap-points)
+ (into #{(gsh/center-shape shape)} (:points shape)))))
(defn guide-snap-points
[guide frame]
diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs
index 865162aa3..1de3b7a25 100644
--- a/frontend/src/app/util/snap_data.cljs
+++ b/frontend/src/app/util/snap_data.cljs
@@ -13,6 +13,7 @@
[app.common.pages.diff :as diff]
[app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctst]
+ [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.util.geom.grid :as gg]
[app.util.geom.snap-points :as snap]
@@ -70,7 +71,7 @@
(mapv grid->snap)))))
(defn- add-frame
- [page-data frame]
+ [objects page-data frame]
(let [frame-id (:id frame)
parent-id (:parent-id frame)
frame-data (->> (snap/shape-snap-points frame)
@@ -79,21 +80,24 @@
:pt %)))
grid-x-data (get-grids-snap-points frame :x)
grid-y-data (get-grids-snap-points frame :y)]
- (-> page-data
- ;; Update root frame information
- (assoc-in [uuid/zero :objects-data frame-id] frame-data)
- (update-in [parent-id :x] (make-insert-tree-data frame-data :x))
- (update-in [parent-id :y] (make-insert-tree-data frame-data :y))
- ;; Update frame information
- (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data))
- (update-in [frame-id :x] #(or % (rt/make-tree)))
- (update-in [frame-id :y] #(or % (rt/make-tree)))
- (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x))
- (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y)))))
+ (cond-> page-data
+ (not (ctl/layout-child? objects frame))
+
+ (-> ;; Update root frame information
+ (assoc-in [uuid/zero :objects-data frame-id] frame-data)
+ (update-in [parent-id :x] (make-insert-tree-data frame-data :x))
+ (update-in [parent-id :y] (make-insert-tree-data frame-data :y))
+
+ ;; Update frame information
+ (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data))
+ (update-in [frame-id :x] #(or % (rt/make-tree)))
+ (update-in [frame-id :y] #(or % (rt/make-tree)))
+ (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x))
+ (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y))))))
(defn- add-shape
- [page-data shape]
+ [objects page-data shape]
(let [frame-id (:frame-id shape)
snap-points (snap/shape-snap-points shape)
shape-data (->> snap-points
@@ -101,11 +105,11 @@
:type :shape
:id (:id shape)
:pt %)))]
- (-> page-data
- (assoc-in [frame-id :objects-data (:id shape)] shape-data)
- (update-in [frame-id :x] (make-insert-tree-data shape-data :x))
- (update-in [frame-id :y] (make-insert-tree-data shape-data :y)))))
-
+ (cond-> page-data
+ (not (ctl/layout-child? objects shape))
+ (-> (assoc-in [frame-id :objects-data (:id shape)] shape-data)
+ (update-in [frame-id :x] (make-insert-tree-data shape-data :x))
+ (update-in [frame-id :y] (make-insert-tree-data shape-data :y))))))
(defn- add-guide
[objects page-data guide]
@@ -164,22 +168,22 @@
(update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))))
(defn- update-frame
- [page-data [_ new-frame]]
+ [objects page-data [_ new-frame]]
(let [frame-id (:id new-frame)
root-data (get-in page-data [uuid/zero :objects-data frame-id])
frame-data (get-in page-data [frame-id :objects-data frame-id])]
- (-> page-data
- (update-in [uuid/zero :x] (make-delete-tree-data root-data :x))
- (update-in [uuid/zero :y] (make-delete-tree-data root-data :y))
- (update-in [frame-id :x] (make-delete-tree-data frame-data :x))
- (update-in [frame-id :y] (make-delete-tree-data frame-data :y))
- (add-frame new-frame))))
+ (as-> page-data $
+ (update-in $ [uuid/zero :x] (make-delete-tree-data root-data :x))
+ (update-in $ [uuid/zero :y] (make-delete-tree-data root-data :y))
+ (update-in $ [frame-id :x] (make-delete-tree-data frame-data :x))
+ (update-in $ [frame-id :y] (make-delete-tree-data frame-data :y))
+ (add-frame objects $ new-frame))))
(defn- update-shape
- [page-data [old-shape new-shape]]
- (-> page-data
- (remove-shape old-shape)
- (add-shape new-shape)))
+ [objects page-data [old-shape new-shape]]
+ (as-> page-data $
+ (remove-shape $ old-shape)
+ (add-shape objects $ new-shape)))
(defn- update-guide
[objects page-data [old-guide new-guide]]
@@ -205,8 +209,8 @@
page-data
(as-> {} $
(add-root-frame $)
- (reduce add-frame $ frames)
- (reduce add-shape $ shapes)
+ (reduce (partial add-frame objects) $ frames)
+ (reduce (partial add-shape objects) $ shapes)
(reduce (partial add-guide objects) $ guides))]
(assoc snap-data (:id page) page-data)))
@@ -233,16 +237,16 @@
(diff/calculate-page-diff old-page page snap-attrs)]
(as-> page-data $
- (reduce update-shape $ change-frame-shapes)
+ (reduce (partial update-shape objects) $ change-frame-shapes)
(reduce remove-frame $ removed-frames)
(reduce remove-shape $ removed-shapes)
- (reduce update-frame $ updated-frames)
- (reduce update-shape $ updated-shapes)
- (reduce add-frame $ new-frames)
- (reduce add-shape $ new-shapes)
- (reduce remove-guide $ removed-guides)
+ (reduce (partial update-frame objects) $ updated-frames)
+ (reduce (partial update-shape objects) $ updated-shapes)
+ (reduce (partial add-frame objects) $ new-frames)
+ (reduce (partial add-shape objects) $ new-shapes)
;; Guides functions. Need objects to get its frame data
+ (reduce remove-guide $ removed-guides)
(reduce (partial update-guide objects) $ change-frame-guides)
(reduce (partial update-guide objects) $ updated-guides)
(reduce (partial add-guide objects) $ new-guides)))))
diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs
index a87d0cbac..f64e98f4b 100644
--- a/frontend/src/debug.cljs
+++ b/frontend/src/debug.cljs
@@ -67,6 +67,15 @@
;; Disable frame thumbnails
:disable-frame-thumbnails
+
+ ;; Enable a widget to show the auto-layout drop-zones
+ :layout-drop-zones
+
+ ;; Display the layout lines
+ :layout-lines
+
+ ;; Makes the pixel grid red so its more visibile
+ :pixel-grid
})
;; These events are excluded when we activate the :events flag
@@ -74,7 +83,7 @@
#{:app.main.data.workspace.notifications/handle-pointer-update
:app.main.data.workspace.selection/change-hover-state})
-(defonce ^:dynamic *debug* (atom #{#_:events #_:text-outline}))
+(defonce ^:dynamic *debug* (atom #{#_:events}))
(defn debug-all! [] (reset! *debug* debug-options))
(defn debug-none! [] (reset! *debug* #{}))
@@ -294,9 +303,16 @@
num-nodes (->> (dom/seq-nodes root-node) count)]
#js {:number num-nodes}))
-#_(defn modif->js
+(defn modif->js
[modif-tree objects]
(clj->js (into {}
(map (fn [[k v]]
[(get-in objects [k :name]) v]))
modif-tree)))
+
+(defn ^:export dump-modifiers
+ []
+ (let [page-id (get @st/state :current-page-id)
+ objects (get-in @st/state [:workspace-data :pages-index page-id :objects])]
+ (.log js/console (modif->js (:workspace-modifiers @st/state) objects)))
+ nil)
diff --git a/frontend/translations/de.po b/frontend/translations/de.po
index f046c5808..22c2a2649 100644
--- a/frontend/translations/de.po
+++ b/frontend/translations/de.po
@@ -3827,19 +3827,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Erweiterte Optionen"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Max.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Max.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3847,19 +3847,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Elementgröße ändern"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maximale Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maximale Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Mindesthöhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Mindestbreite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4701,4 +4701,4 @@ msgid "workspace.updates.update"
msgstr "Aktualisieren"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Klicken Sie, um den Pfad zu schließen"
\ No newline at end of file
+msgstr "Klicken Sie, um den Pfad zu schließen"
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 3bccfefad..ebf0fefaa 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -3490,35 +3490,35 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Advanced options"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Max.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Max.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maximum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maximum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Minimum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Minimum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4077,6 +4077,14 @@ msgstr "Update main components"
msgid "workspace.shape.menu.update-main"
msgstr "Update main component"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.add-flex"
+msgstr "Add layout flex"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.remove-flex"
+msgstr "Remove layout flex"
+
msgid "workspace.sidebar.collapse"
msgstr "Collapse sidebar"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index efed7f480..0e833ab0b 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -3889,35 +3889,35 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Opciones avanzadas"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Altura.Max"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Ancho.Max"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Altura.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Ancho.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Altura máxima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Ancho máximo"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Altura mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Ancho mínimo"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4518,6 +4518,14 @@ msgstr "Actualizar componentes"
msgid "workspace.shape.menu.update-main"
msgstr "Actualizar componente principal"
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.add-flex"
+msgstr "Añadir layout flex"
+
+#: src/app/main/ui/workspace/context_menu.cljs
+msgid "workspace.shape.menu.remove-flex"
+msgstr "Eliminar layout flex"
+
msgid "workspace.sidebar.collapse"
msgstr "Cerrar barra lateral"
diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po
index c2064616d..148c2066c 100644
--- a/frontend/translations/eu.po
+++ b/frontend/translations/eu.po
@@ -3682,19 +3682,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Aukera aurreratuak"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Gehieneko altuera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Gehieneko zabalera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Gutxieneko altuera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Gutxieneko zabalera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3706,19 +3706,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Elementuaren tamaina aldatzea"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Gehieneko altuera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Gehieneko zabalaera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Gutxieneko altuera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Gutxieneko zabalera"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -4524,4 +4524,4 @@ msgid "workspace.updates.update"
msgstr "Eguneratu"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Egin klik bidea ixteko"
\ No newline at end of file
+msgstr "Egin klik bidea ixteko"
diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po
index 0efd2d560..05cc81b4f 100644
--- a/frontend/translations/fr.po
+++ b/frontend/translations/fr.po
@@ -3404,19 +3404,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Options avancées"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Hauteur max"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Largeur max"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Hauteur min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Largeur min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3424,19 +3424,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Redimensionnement de l'élément"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Hauteur maximale"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Largeur maximale"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Hauteur minimale"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Largeur minimale"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4131,4 +4131,4 @@ msgid "workspace.updates.update"
msgstr "Actualiser"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Cliquez pour fermer le chemin"
\ No newline at end of file
+msgstr "Cliquez pour fermer le chemin"
diff --git a/frontend/translations/he.po b/frontend/translations/he.po
index 5c58ec20c..b49a94924 100644
--- a/frontend/translations/he.po
+++ b/frontend/translations/he.po
@@ -3782,19 +3782,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "אפשרויות מתקדמות"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "גובה מר."
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "רוחב מר."
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "גובה מז."
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "רוחב מז."
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3810,19 +3810,19 @@ msgid "workspace.options.layout-item.title"
msgstr "שינוי גודל רכיבים"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "גובה מרבי"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "רוחב מרבי"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "גובה מזערי"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "רוחב מזערי"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -4660,4 +4660,4 @@ msgid "workspace.updates.update"
msgstr "עדכון"
msgid "workspace.viewport.click-to-close-path"
-msgstr "לחיצה תסגור את הנתיב"
\ No newline at end of file
+msgstr "לחיצה תסגור את הנתיב"
diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po
index 83f662528..473d978e1 100644
--- a/frontend/translations/hr.po
+++ b/frontend/translations/hr.po
@@ -3731,19 +3731,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Napredne opcije"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Max.visina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Max.širina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.visina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.širina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3751,19 +3751,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Promjena veličine elementa"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maksimalna visina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maksimalna širina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Minimalna visina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Minimalna širina"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4602,4 +4602,4 @@ msgstr "Ažuriraj"
#, fuzzy
msgid "workspace.viewport.click-to-close-path"
-msgstr "Pritisni da zatvoriš path"
\ No newline at end of file
+msgstr "Pritisni da zatvoriš path"
diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po
index 3fdb268ff..72e0fa8b7 100644
--- a/frontend/translations/pl.po
+++ b/frontend/translations/pl.po
@@ -3545,27 +3545,27 @@ msgid "workspace.options.layer-options.title.multiple"
msgstr "Wybrane warstwy"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.Wysokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.Szerokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maksymalna wysokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maksymalna szerokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Minimalna wysokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Minimalna szerokość"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4320,4 +4320,4 @@ msgid "workspace.updates.update"
msgstr "Aktualizuj"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Kliknij, aby zamknąć ścieżkę"
\ No newline at end of file
+msgstr "Kliknij, aby zamknąć ścieżkę"
diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po
index 0b8347697..47b4ba86d 100644
--- a/frontend/translations/pt_BR.po
+++ b/frontend/translations/pt_BR.po
@@ -3681,19 +3681,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Opções avançadas"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Altura.Máx"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Largura.Máx"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Altura.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Altura.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3701,19 +3701,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Redimensionar elemento"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Altura máxima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Largura máxima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Altura mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Largura mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4527,4 +4527,4 @@ msgid "workspace.updates.update"
msgstr "Atualizar"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Clique para fechar o caminho"
\ No newline at end of file
+msgstr "Clique para fechar o caminho"
diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po
index 423d51d11..d3bae6133 100644
--- a/frontend/translations/pt_PT.po
+++ b/frontend/translations/pt_PT.po
@@ -3678,19 +3678,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Opções avançadas"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Altura.Máx"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Largura.Máx"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Altura.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Largura.Min"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3698,19 +3698,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Redimensionar elementos"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Altura máxima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Largura máxima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Altura mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Largura mínima"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4526,4 +4526,4 @@ msgid "workspace.updates.update"
msgstr "Atualizar"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Clica para fechar o caminho"
\ No newline at end of file
+msgstr "Clica para fechar o caminho"
diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po
index 239d4c315..14d9d75b9 100644
--- a/frontend/translations/tr.po
+++ b/frontend/translations/tr.po
@@ -3820,19 +3820,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Gelişmiş seçenekler"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Azami Yükseklik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Azami Genişlik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Asgari Yükseklik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Asgari Genişlik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3848,19 +3848,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Öge yeniden boyutlandırma"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Azami yükseklik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Azami genişlik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Asgari yükseklik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Asgari genişlik"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -4700,4 +4700,4 @@ msgid "workspace.updates.update"
msgstr "Güncelle"
msgid "workspace.viewport.click-to-close-path"
-msgstr "Yolu kapatmak için tıklayın"
\ No newline at end of file
+msgstr "Yolu kapatmak için tıklayın"
diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po
index 9f55473b3..cf3e34c85 100644
--- a/frontend/translations/zh_CN.po
+++ b/frontend/translations/zh_CN.po
@@ -3565,19 +3565,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "高级选项"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-h"
+msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "最大高度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-max-w"
+msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "最大宽度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-h"
+msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "最小高度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.layout-min-w"
+msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "最小宽度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@@ -3585,19 +3585,19 @@ msgid "workspace.options.layout-item.title"
msgstr "调整大小"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-h"
+msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "最大高度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-max-w"
+msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "最大宽度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-h"
+msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "最小高度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
-msgid "workspace.options.layout-item.title.layout-min-w"
+msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "最小宽度"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@@ -4405,4 +4405,4 @@ msgid "workspace.updates.update"
msgstr "更新"
msgid "workspace.viewport.click-to-close-path"
-msgstr "单击以闭合路径"
\ No newline at end of file
+msgstr "单击以闭合路径"