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 "单击以闭合路径"