Merge pull request #2537 from penpot/alotor-autolayout-v2

Autolayout & refactor transformations
This commit is contained in:
Andrey Antukh 2022-11-17 11:35:41 +01:00 committed by GitHub
commit 3de217a52e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 4700 additions and 2443 deletions

View File

@ -4,8 +4,11 @@
### :boom: Breaking changes & Deprecations ### :boom: Breaking changes & Deprecations
### :sparkles: New features ### :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) - 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) - Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475)
### :bug: Bugs fixed ### :bug: Bugs fixed
- Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218) - Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218)

View File

@ -252,3 +252,21 @@
(update :d mth/precision 4) (update :d mth/precision 4)
(update :e mth/precision 4) (update :e mth/precision 4)
(update :f 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))))

View File

@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.geom.point (ns app.common.geom.point
(:refer-clojure :exclude [divide min max]) (:refer-clojure :exclude [divide min max abs])
(:require (:require
#?(:cljs [cljs.pprint :as pp] #?(:cljs [cljs.pprint :as pp]
:clj [clojure.pprint :as pp]) :clj [clojure.pprint :as pp])
@ -328,6 +328,13 @@
(update :x #(if (mth/almost-zero? %) 0.001 %)) (update :x #(if (mth/almost-zero? %) 0.001 %))
(update :y #(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 ;; --- Debug
(defmethod pp/simple-dispatch Point [obj] (pr obj)) (defmethod pp/simple-dispatch Point [obj] (pr obj))

View File

@ -14,7 +14,6 @@
[app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.corners :as gsc] [app.common.geom.shapes.corners :as gsc]
[app.common.geom.shapes.intersect :as gin] [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.modifiers :as gsm]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.rect :as gpr]
@ -28,7 +27,7 @@
rotation of each shape. Mainly used for multiple selection." rotation of each shape. Mainly used for multiple selection."
[shapes] [shapes]
(->> shapes (->> shapes
(map (comp gpr/points->selrect :points gtr/transform-shape)) (map (comp gpr/points->selrect :points))
(gpr/join-selrects))) (gpr/join-selrects)))
(defn translate-to-frame (defn translate-to-frame
@ -160,37 +159,31 @@
(dm/export gpr/join-rects) (dm/export gpr/join-rects)
(dm/export gpr/join-selrects) (dm/export gpr/join-selrects)
(dm/export gpr/contains-selrect?) (dm/export gpr/contains-selrect?)
(dm/export gpr/contains-point?)
(dm/export gpr/close-selrect?)
(dm/export gtr/move) (dm/export gtr/move)
(dm/export gtr/absolute-move) (dm/export gtr/absolute-move)
(dm/export gtr/transform-matrix) (dm/export gtr/transform-matrix)
(dm/export gtr/transform-str) (dm/export gtr/transform-str)
(dm/export gtr/inverse-transform-matrix) (dm/export gtr/inverse-transform-matrix)
(dm/export gtr/transform-point-center)
(dm/export gtr/transform-rect) (dm/export gtr/transform-rect)
(dm/export gtr/calculate-adjust-matrix) (dm/export gtr/calculate-adjust-matrix)
(dm/export gtr/update-group-selrect) (dm/export gtr/update-group-selrect)
(dm/export gtr/update-mask-selrect) (dm/export gtr/update-mask-selrect)
(dm/export gtr/resize-modifiers) (dm/export gtr/update-bool-selrect)
(dm/export gtr/change-orientation-modifiers)
(dm/export gtr/rotation-modifiers)
(dm/export gtr/merge-modifiers)
(dm/export gtr/transform-shape) (dm/export gtr/transform-shape)
(dm/export gtr/transform-selrect) (dm/export gtr/transform-selrect)
(dm/export gtr/transform-selrect-matrix) (dm/export gtr/transform-selrect-matrix)
(dm/export gtr/transform-bounds) (dm/export gtr/transform-bounds)
(dm/export gtr/modifiers->transform)
(dm/export gtr/empty-modifiers?)
(dm/export gtr/move-position-data) (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 ;; Constratins
(dm/export gct/calc-child-modifiers) (dm/export gct/calc-child-modifiers)
;; Layout
(dm/export gcl/calc-layout-data)
(dm/export gcl/calc-layout-modifiers)
;; PATHS ;; PATHS
(dm/export gsp/content->selrect) (dm/export gsp/content->selrect)
(dm/export gsp/transform-content) (dm/export gsp/transform-content)
@ -203,7 +196,7 @@
(dm/export gin/rect-contains-shape?) (dm/export gin/rect-contains-shape?)
;; Bool ;; Bool
(dm/export gsb/update-bool-selrect)
(dm/export gsb/calc-bool-content) (dm/export gsb/calc-bool-content)
;; Constraints ;; Constraints
@ -216,3 +209,4 @@
;; Modifiers ;; Modifiers
(dm/export gsm/set-objects-modifiers) (dm/export gsm/set-objects-modifiers)

View File

@ -7,8 +7,6 @@
(ns app.common.geom.shapes.bool (ns app.common.geom.shapes.bool
(:require (:require
[app.common.data :as d] [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.bool :as pb]
[app.common.path.shapes-to-path :as stp])) [app.common.path.shapes-to-path :as stp]))
@ -25,17 +23,5 @@
(into [] extract-content-xf (:shapes shape))] (into [] extract-content-xf (:shapes shape))]
(pb/content-bool (:bool-type shape) shapes-content))) (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))))

View File

@ -8,7 +8,8 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.matrix :as gmt] [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 (defn center-rect
[{:keys [x y width height]}] [{:keys [x y width height]}]
@ -31,6 +32,22 @@
(gpt/point (/ (+ minx maxx) 2.0) (gpt/point (/ (+ minx maxx) 2.0)
(/ (+ miny maxy) 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 (defn center-shape
"Calculate the center of the shape." "Calculate the center of the shape."
[shape] [shape]
@ -49,3 +66,8 @@
(gpt/transform point (gmt/multiply prev matrix post)))] (gpt/transform point (gmt/multiply prev matrix post)))]
(mapv tr-point points)) (mapv tr-point points))
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)))

View File

@ -6,14 +6,20 @@
(ns app.common.geom.shapes.constraints (ns app.common.geom.shapes.constraints
(:require (:require
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco] [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.rect :as gre]
[app.common.geom.shapes.transforms :as gst]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
;; Auxiliary methods to work in an specifica axis ;; 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] (defn get-delta-start [axis rect tr-rect]
(if (= :x axis) (if (= :x axis)
(- (:x1 tr-rect) (:x1 rect)) (- (:x1 tr-rect) (:x1 rect))
@ -29,6 +35,11 @@
(- (:width tr-rect) (:width rect)) (- (:width tr-rect) (:width rect))
(- (:height tr-rect) (:height 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] (defn get-delta-center [axis center tr-center]
(if (= :x axis) (if (= :x axis)
(- (:x tr-center) (:x center)) (- (:x tr-center) (:x center))
@ -53,78 +64,160 @@
(:width rect) (:width rect)
(:height 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 ;; Constraint function definitions
(defmulti constraint-modifier (fn [type & _] type)) (defmulti constraint-modifier (fn [type & _] type))
(defmethod constraint-modifier :start (defmethod constraint-modifier :start
[_ axis parent _ _ transformed-parent-rect] [_ 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)
(let [parent-rect (:selrect parent) start-after (start-vector axis child-points-after parent-points-after)]
delta-start (get-delta-start axis parent-rect transformed-parent-rect)] (ctm/move-modifiers (displacement start-before start-after))))
(if-not (mth/almost-zero? delta-start)
{:displacement (get-displacement axis delta-start)}
{})))
(defmethod constraint-modifier :end (defmethod constraint-modifier :end
[_ axis parent _ _ transformed-parent-rect] [_ axis child-points-before parent-points-before child-points-after parent-points-after]
(let [parent-rect (:selrect parent) (let [end-before (end-vector axis child-points-before parent-points-before)
delta-end (get-delta-end axis parent-rect transformed-parent-rect)] end-after (end-vector axis child-points-after parent-points-after)]
(if-not (mth/almost-zero? delta-end) (ctm/move-modifiers (displacement end-before end-after))))
{:displacement (get-displacement axis delta-end)}
{})))
(defmethod constraint-modifier :fixed (defmethod constraint-modifier :fixed
[_ axis parent child _ transformed-parent-rect] [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent]
(let [parent-rect (:selrect parent) (let [;; Same as constraint end
child-rect (gre/points->rect (:points child)) 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) disp-end (displacement end-before end-after)
delta-size (get-delta-size axis parent-rect transformed-parent-rect) disp-start (displacement start-before start-after)
child-size (get-size axis child-rect)]
(if (or (not (mth/almost-zero? delta-start))
(not (mth/almost-zero? delta-size)))
{:displacement (get-displacement axis delta-start) ;; We get the current axis side and grow it on both side by the end+start displacements
:resize-origin (get-displacement axis delta-start (:x child-rect) (:y child-rect)) before-vec (side-vector axis child-points-after)
:resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))} 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 (defmethod constraint-modifier :center
[_ axis parent _ _ transformed-parent-rect] [_ axis child-points-before parent-points-before child-points-after parent-points-after]
(let [parent-rect (:selrect parent) (let [center-before (center-vector axis child-points-before parent-points-before)
parent-center (gco/center-rect parent-rect) center-after (center-vector axis child-points-after parent-points-after)]
transformed-parent-center (gco/center-rect transformed-parent-rect) (ctm/move-modifiers (displacement center-before center-after))))
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))))))
(defmethod constraint-modifier :default [_ _ _ _ _] (defmethod constraint-modifier :default [_ _ _ _ _]
{}) [])
(def const->type+axis (def const->type+axis
{:left :start {:left :start
@ -152,77 +245,73 @@
:top :top
:scale))) :scale)))
(defn clean-modifiers (defn bounding-box-parent-transform
"Remove redundant modifiers" "Returns a bounding box for the child in the same coordinate system
[{:keys [displacement resize-vector resize-vector-2] :as modifiers}] 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 (defn normalize-modifiers
;; Displacement with value 0. We don't move in any direction "Before aplying constraints we need to remove the deformation caused by the resizing of the parent"
(and (some? displacement) [constraints-h constraints-v modifiers child parent transformed-child {:keys [transform transform-inverse] :as transformed-parent}]
(mth/almost-zero? (:e displacement))
(mth/almost-zero? (:f displacement)))
(dissoc :displacement)
;; Resize with value very close to 1 means no resize (let [child-bb-before (gst/parent-coords-rect child parent)
(and (some? resize-vector) child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
(mth/almost-zero? (- 1.0 (:x resize-vector))) scale-x (/ (:width child-bb-before) (:width child-bb-after))
(mth/almost-zero? (- 1.0 (:y resize-vector)))) scale-y (/ (:height child-bb-before) (:height child-bb-after))
(dissoc :resize-origin :resize-vector) resize-origin (-> transformed-parent :points gpo/origin)]
(and (some? resize-vector) (cond-> modifiers
(mth/almost-zero? (- 1.0 (:x resize-vector-2))) (not= :scale constraints-h)
(mth/almost-zero? (- 1.0 (:y resize-vector-2)))) (ctm/resize (gpt/point scale-x 1) resize-origin transform transform-inverse)
(dissoc :resize-origin-2 :resize-vector-2)))
(not= :scale constraints-v)
(ctm/resize (gpt/point 1 scale-y) resize-origin transform transform-inverse))))
(defn calc-child-modifiers (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)) (let [modifiers (ctm/select-child-modifiers 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)
constraints-v constraints-h
(if-not ignore-constraints (if-not ignore-constraints
(:constraints-v child (default-constraints-v child)) (:constraints-h child (default-constraints-h child))
:scale) :scale)
modifiers-h (constraint-modifier (constraints-h const->type+axis) :x parent child modifiers transformed-parent-rect) constraints-v
modifiers-v (constraint-modifier (constraints-v const->type+axis) :y parent child modifiers transformed-parent-rect)] (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 (if (and (= :scale constraints-h) (= :scale constraints-v))
;; real modifiers that need to be applied to the child, including rotation as needed. modifiers
(cond-> {}
(some? (:displacement-after modifiers))
(assoc :displacement-after (:displacement-after modifiers))
(or (contains? modifiers-h :displacement) (let [transformed-child (gst/transform-shape child (ctm/select-child-modifiers modifiers))
(contains? modifiers-v :displacement)) modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent)
(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))
:always transformed-child (gst/transform-shape child modifiers)
(gmt/translate-matrix)))
(:resize-vector modifiers-h) parent-points-before (bounding-box-parent-transform parent parent)
(assoc :resize-origin (:resize-origin modifiers-h) child-points-before (bounding-box-parent-transform child parent)
:resize-vector (gpt/point (get-in modifiers-h [:resize-vector :x] 1) parent-points-after (bounding-box-parent-transform transformed-parent transformed-parent)
(get-in modifiers-h [:resize-vector :y] 1))) child-points-after (bounding-box-parent-transform transformed-child transformed-parent)
(:resize-vector modifiers-v) modifiers-h (constraint-modifier (constraints-h const->type+axis) :x
(assoc :resize-origin-2 (:resize-origin modifiers-v) child-points-before parent-points-before
:resize-vector-2 (gpt/point (get-in modifiers-v [:resize-vector :x] 1) child-points-after parent-points-after
(get-in modifiers-v [:resize-vector :y] 1))) transformed-parent)
(:resize-transform modifiers) modifiers-v (constraint-modifier (constraints-v const->type+axis) :y
(assoc :resize-transform (:resize-transform modifiers) child-points-before parent-points-before
:resize-transform-inverse (:resize-transform-inverse modifiers)) child-points-after parent-points-after
transformed-parent)]
:always (-> modifiers
(clean-modifiers))))) (ctm/add-modifiers modifiers-h)
(ctm/add-modifiers modifiers-v))))))

View File

@ -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)

View File

@ -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))))

View File

@ -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)))

View File

@ -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?}))

View File

@ -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]))

View File

@ -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]))

View File

@ -348,3 +348,25 @@
:points :points
(every? (partial has-point-rect? rect)))) (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)))))

View File

@ -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]))

View File

@ -7,188 +7,161 @@
(ns app.common.geom.shapes.modifiers (ns app.common.geom.shapes.modifiers
(:require (:require
[app.common.data :as d] [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.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.layout :as gcl] [app.common.geom.shapes.flex-layout :as gcl]
[app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.pixel-precision :as gpp]
[app.common.geom.shapes.transforms :as gtr] [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])) [app.common.uuid :as uuid]))
(defn set-pixel-precision ;;#?(:cljs
"Adjust modifiers so they adjust to the pixel grid" ;; (defn modif->js
[modifiers shape] ;; [modif-tree objects]
;; (clj->js (into {}
;; (map (fn [[k v]]
;; [(get-in objects [k :name]) v]))
;; modif-tree))))
(if (and (some? (:resize-transform modifiers)) (defn resolve-tree-sequence
(not (gmt/unit? (:resize-transform modifiers)))) "Given the ids that have changed search for layout roots to recalculate"
;; If we're working with a rotation we don't handle pixel precision because [ids objects]
;; the transformation won't have the precision anyway
modifiers
(let [center (gco/center-shape shape) (us/assert!
base-bounds (-> (:points shape) (gpr/points->rect)) :expr (or (nil? ids) (set? ids))
:hint (dm/str "tree sequence from not set: " ids))
raw-bounds (letfn [(get-tree-root ;; Finds the tree root for the current id
(-> (gtr/transform-bounds (:points shape) center modifiers) [id]
(gpr/points->rect))
flip-x? (neg? (get-in modifiers [:resize-vector :x])) (loop [current id
flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) result id]
(neg? (get-in modifiers [:resize-vector-2 :y]))) (let [shape (get objects current)
parent (get objects (:parent-id shape))]
(cond
(or (not shape) (= uuid/zero current))
result
path? (= :path (:type shape)) ;; Frame found, but not layout we return the last layout found (or the id)
vertical-line? (and path? (<= (:width raw-bounds) 0.01)) (and (= :frame (:type parent))
horizontal-line? (and path? (<= (:height raw-bounds) 0.01)) (not (ctl/layout? parent)))
result
target-width (if vertical-line? ;; Layout found. We continue upward but we mark this layout
(:width raw-bounds) (ctl/layout? parent)
(max 1 (mth/round (:width raw-bounds)))) (recur (:id parent) (:id parent))
target-height (if horizontal-line? ;; If group or boolean or other type of group we continue with the last result
(:height raw-bounds) :else
(max 1 (mth/round (:height raw-bounds)))) (recur (:id parent) result)))))
target-p (cond-> (gpt/round (gpt/point raw-bounds)) (calculate-common-roots ;; Given some roots retrieves the minimum number of tree roots
flip-x? [result id]
(update :x + target-width) (if (= id uuid/zero)
result
(let [root (get-tree-root id)
flip-y? ;; Remove the children from the current root
(update :y + target-height)) result
(into #{} (remove #(cph/is-child? objects root %)) result)
ratio-width (/ target-width (:width raw-bounds)) contains-parent?
ratio-height (/ target-height (:height raw-bounds)) (some #(cph/is-child? objects % root) result)]
modifiers (cond-> result
(-> modifiers (not contains-parent?)
(d/without-nils) (conj root)))))
(d/update-in-when
[:resize-vector :x] #(* % ratio-width))
;; If the resize-vector-2 modifier arrives means the resize-vector (generate-tree ;; Generate a tree sequence from a given root id
;; will only resize on the x axis [id]
(cond-> (nil? (:resize-vector-2 modifiers)) (->> (tree-seq
(d/update-in-when #(d/not-empty? (dm/get-in objects [% :shapes]))
[:resize-vector :y] #(* % ratio-height))) #(dm/get-in objects [% :shapes])
id)
(map #(get objects %))))]
(d/update-in-when (let [roots (->> ids (reduce calculate-common-roots #{}))]
[:resize-vector-2 :y] #(* % ratio-height))) (concat
(when (contains? ids uuid/zero) [(get objects uuid/zero)])
(mapcat generate-tree roots)))))
origin (get modifiers :resize-origin) (defn- set-children-modifiers
origin-2 (get modifiers :resize-origin-2) "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) (if (ctm/only-move? modifiers)
resize-v-2 (get modifiers :resize-vector-2) ;; Move modifiers don't need to calculate constraints
displacement (get modifiers :displacement) (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 ;; Check the constraints, then resize
(-> target-p (let [parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers))]
(gpt/transform (loop [modif-tree modif-tree
(cond-> (gmt/matrix) children (seq children)]
(some? displacement) (if-let [current (first children)]
(gmt/multiply (gmt/inverse displacement)) (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)) (defn- process-layout-children
(gmt/scale (gpt/inverse resize-v) origin) [modif-tree objects parent transformed-parent]
(letfn [(process-child [modif-tree child]
(and (some? resize-v-2) (some? origin-2)) (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers])
(gmt/scale (gpt/inverse resize-v-2) origin-2)))) child-modifiers (-> modifiers
(ctm/select-child-geometry-modifiers)
delta-v (gpt/subtract target-p-inv (gpt/point base-bounds)) (gcl/normalize-child-modifiers parent child transformed-parent))]
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))]
(cond-> modif-tree (cond-> modif-tree
(not (gtr/empty-modifiers? child-modifiers)) (not (ctm/empty? child-modifiers))
(update-in [(:id child) :modifiers] #(merge child-modifiers %)))))] (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))]
(let [children (map (d/getf objects) (:shapes shape)) (let [children (map (d/getf objects) (:shapes transformed-parent))]
modifiers (get-in modif-tree [(:id shape) :modifiers]) (reduce process-child modif-tree children))))
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))))
(defn group? [shape] (defn- set-layout-modifiers
(or (= :group (:type shape)) [modif-tree objects parent]
(= :bool (:type shape))))
(defn merge-modifiers (letfn [(apply-modifiers [modif-tree child]
[modif-tree ids modifiers] (let [modifiers (-> (dm/get-in modif-tree [(:id child) :modifiers])
(reduce (ctm/select-geometry))]
(fn [modif-tree id] (cond
(update-in modif-tree [id :modifiers] #(merge % modifiers))) (cph/group-like-shape? child)
modif-tree (gtr/apply-group-modifiers child objects modif-tree)
ids))
(defn set-layout-modifiers (some? modifiers)
[modif-tree objects id] (gtr/transform-shape child modifiers)
(letfn [(transform-child [parent child] :else
(let [modifiers (get modif-tree (:id child)) child)))
child (set-child-modifiers [parent [layout-line modif-tree] child]
(cond-> child (let [[modifiers layout-line]
(not (group? child)) (gcl/layout-child-modifiers parent child layout-line)
(-> (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)
modif-tree modif-tree
(cond-> modif-tree (cond-> modif-tree
(not (gtr/empty-modifiers? modifiers)) (d/not-empty? modifiers)
(merge-modifiers [(:id child)] modifiers) (update-in [(:id child) :modifiers] ctm/add-modifiers modifiers))]
(and (not (gtr/empty-modifiers? modifiers)) (group? child)) [layout-line modif-tree]))]
(merge-modifiers (:shapes child) modifiers))]
[layout-data modif-tree]))] (let [children (map (d/getf objects) (:shapes parent))
children (->> children (map (partial apply-modifiers modif-tree)))
(let [modifiers (get modif-tree id) layout-data (gcl/calc-layout-data parent children)
children (into [] (cond-> children (:reverse? layout-data) reverse))
shape (-> (get objects id) (merge modifiers) gtr/transform-shape) max-idx (dec (count children))
layout-lines (:layout-lines layout-data)]
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)]
(loop [modif-tree modif-tree (loop [modif-tree modif-tree
layout-line (first layout-lines) layout-line (first layout-lines)
@ -199,92 +172,146 @@
children (subvec children from-idx to-idx) children (subvec children from-idx to-idx)
[_ modif-tree] [_ 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)) (recur modif-tree (first pending) (rest pending) to-idx))
modif-tree))))) modif-tree)))))
(defn get-first-layout (defn- calc-auto-modifiers
[id objects] "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 (set-parent-auto-height
result id] [modifiers auto-height]
(let [shape (get objects current) (let [origin (-> parent :points first)
parent (get objects (:parent-id shape))] scale-height (/ auto-height (-> parent :selrect :height) )]
(cond (-> modifiers
(or (not shape) (= uuid/zero current)) (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))]
result
;; Frame found, but not layout we return the last layout found (or the id) (let [children (->> parent :shapes (map (d/getf objects)))
(and (= :frame (:type parent))
(not (:layout parent)))
result
;; Layout found. We continue upward but we mark this layout {auto-width :width auto-height :height}
(and (= :frame (:type parent)) (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent)))
(:layout parent)) (gcl/layout-content-bounds parent children))]
(:id parent)
;; If group or boolean or other type of group we continue with the last result (cond-> (ctm/empty)
:else (and (some? auto-width) (ctl/auto-width? parent))
(recur (:id parent) result))))) (set-parent-auto-width auto-width)
(defn resolve-layout-ids (and (some? auto-height) (ctl/auto-height? parent))
"Given a list of ids, resolve the parent layouts that will need to update. This will go upwards (set-parent-auto-height auto-height)))))
in the tree while a layout is found"
[ids objects]
(into (d/ordered-set) (defn- propagate-modifiers
(map #(get-first-layout % objects)) "Propagate modifiers to its children"
ids)) [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 (defn set-objects-modifiers
[ids objects get-modifier ignore-constraints snap-pixel?] [modif-tree objects ignore-constraints snap-pixel?]
(let [set-modifiers (let [objects (apply-structure-modifiers objects modif-tree)
(fn [modif-tree id] shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects)
(let [shape (get objects id)
modifiers (cond-> (get-modifier shape) snap-pixel? (set-pixel-precision shape))]
(-> modif-tree
(assoc id {:modifiers modifiers}))))
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 modif-tree
(loop [current (first touched-layouts) (cond-> modif-tree
pending (rest touched-layouts) snap-pixel? (gpp/adjust-pixel-precision objects))]
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))]
;;#?(:cljs
;; (.log js/console ">result" (modif->js modif-tree objects)))
modif-tree)) modif-tree))

View File

@ -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))))

View File

@ -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))])))

View File

@ -11,14 +11,25 @@
[app.common.math :as mth])) [app.common.math :as mth]))
(defn make-rect (defn make-rect
[x y width height] ([p1 p2]
(when (d/num? x y width height) (let [xp1 (:x p1)
(let [width (max width 0.01) yp1 (:y p1)
height (max height 0.01)] xp2 (:x p2)
{:x x yp2 (:y p2)
:y y x1 (min xp1 xp2)
:width width y1 (min yp1 yp2)
:height height}))) 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 (defn make-selrect
[x y width height] [x y width height]
@ -80,6 +91,19 @@
(when (d/num? minx miny maxx maxy) (when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny)))))) (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] (defn points->selrect [points]
(when-let [rect (points->rect points)] (when-let [rect (points->rect points)]
(let [{:keys [x y width height]} rect] (let [{:keys [x y width height]} rect]
@ -168,3 +192,10 @@
(>= (:y1 sr2) (:y1 sr1)) (>= (:y1 sr2) (:y1 sr1))
(<= (:y2 sr2) (:y2 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)))))

View File

@ -10,12 +10,14 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.bool :as gshb]
[app.common.geom.shapes.common :as gco] [app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.spec :as us] [app.common.pages.helpers :as cph]
[app.common.text :as txt])) [app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
(def ^:dynamic *skip-adjust* false) (def ^:dynamic *skip-adjust* false)
@ -76,14 +78,6 @@
; ---- Geometric operations ; ---- 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 (defn- calculate-skew-angle
"Calculates the skew angle of the parallelogram given by the points" "Calculates the skew angle of the parallelogram given by the points"
[[p1 _ p3 p4]] [[p1 _ p3 p4]]
@ -99,6 +93,7 @@
(defn- calculate-height (defn- calculate-height
"Calculates the height of a parallelogram given by the points" "Calculates the height of a parallelogram given by the points"
[[p1 _ _ p4]] [[p1 _ _ p4]]
(-> (gpt/to-vec p4 p1) (-> (gpt/to-vec p4 p1)
(gpt/length))) (gpt/length)))
@ -182,17 +177,6 @@
(gmt/multiply (:transform-inverse shape (gmt/matrix))) (gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/translate (gpt/negate center))))) (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 (defn transform-rect
"Transform a rectangles and changes its attributes" "Transform a rectangles and changes its attributes"
[rect matrix] [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 "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`. after applying them the end result is the `shape-path-temp`.
This is compose of three transformations: skew, resize and rotation" This is compose of three transformations: skew, resize and rotation"
([points-temp points-rec] [points-temp points-rec flip-x flip-y]
(calculate-adjust-matrix points-temp points-rec false false)) (let [center (gco/center-bounds points-temp)
([points-temp points-rec flip-x flip-y] stretch-matrix (gmt/matrix)
(let [center (gco/center-points points-temp)
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 stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
;; 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)) 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)) w1 (max 1 (calculate-width points-temp))
h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix))) w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1) w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
h3 (if (mth/nan? h3) 1 h3) w3 (if (mth/nan? w3) 1 w3)
w1 (max 1 (calculate-width points-temp)) stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3)))
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))) 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 stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
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) ;; This is the inverse to be able to remove the transformation
stretch-matrix-inverse
;; This is the inverse to be able to remove the transformation (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
stretch-matrix-inverse (gmt/skew-matrix (- skew-angle) 0)
(gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3))) (gmt/rotate-matrix (- rotation-angle)))]
(gmt/skew-matrix (- skew-angle) 0) [stretch-matrix stretch-matrix-inverse rotation-angle]))
(gmt/rotate-matrix (- rotation-angle)))]
[stretch-matrix stretch-matrix-inverse rotation-angle])))
(defn- adjust-rotated-transform (defn- adjust-rotated-transform
[{:keys [transform transform-inverse flip-x flip-y]} points] [{: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 points-temp (cond-> points
(some? transform-inverse) (some? transform-inverse)
@ -273,13 +254,59 @@
(if transform (gmt/multiply transform matrix) matrix) (if transform (gmt/multiply transform matrix) matrix)
(if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)])) (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 "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" its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx] [shape transform-mtx]
(let [points' (:points shape) (let [points' (:points shape)
points (gco/transform-points points' transform-mtx) points (gco/transform-points points' transform-mtx)
shape (-> shape (adjust-shape-flips points))
bool? (= (:type shape) :bool) bool? (= (:type shape) :bool)
path? (= (:type shape) :path) path? (= (:type shape) :path)
@ -289,14 +316,16 @@
base-rotation (or (:rotation shape) 0) base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
rotation (mod (+ base-rotation modif-rotation) 360)] rotation (mod (+ base-rotation modif-rotation) 360)]
(-> shape (-> shape
(cond-> bool? (cond-> bool?
(update :bool-content gpa/transform-content transform-mtx)) (update :bool-content gpa/transform-content transform-mtx))
(cond-> path? (cond-> path?
(update :content gpa/transform-content transform-mtx)) (update :content gpa/transform-content transform-mtx))
(cond-> (not path?) (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 (cond-> transform
(-> (assoc :transform transform) (-> (assoc :transform transform)
(assoc :transform-inverse transform-inverse))) (assoc :transform-inverse transform-inverse)))
@ -304,10 +333,19 @@
(dissoc :transform :transform-inverse)) (dissoc :transform :transform-inverse))
(cond-> (some? selrect) (cond-> (some? selrect)
(assoc :selrect selrect)) (assoc :selrect selrect))
(cond-> (d/not-empty? points) (cond-> (d/not-empty? points)
(assoc :points points)) (assoc :points points))
(assoc :rotation rotation)))) (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 (defn- update-group-viewbox
"Updates the viewbox for groups imported from SVG's" "Updates the viewbox for groups imported from SVG's"
[{:keys [selrect svg-viewbox] :as group} new-selrect] [{:keys [selrect svg-viewbox] :as group} new-selrect]
@ -332,6 +370,9 @@
;; Points for every shape inside the group ;; Points for every shape inside the group
points (->> children (mapcat :points)) 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 ;; 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))) 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-x (-> mask :flip-x))
(assoc :flip-y (-> mask :flip-y))))) (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 (let [bool-content (gshb/calc-bool-content shape objects)
;; do make to a shape, in this order: shape (assoc shape :bool-content bool-content)
;; [points selrect] (gpa/content->points+selrect shape bool-content)]
;; - 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.
(defn empty-modifiers? [modifiers] (if (and (some? selrect) (d/not-empty? points))
(empty? (dissoc modifiers :ignore-geometry?))) (-> shape
(assoc :selrect selrect)
(defn resize-modifiers (assoc :points points))
[shape attr value] (update-group-selrect shape children))))
(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)))
(defn transform-shape (defn transform-shape
[shape] ([shape]
(let [modifiers (:modifiers shape)] (let [modifiers (:modifiers shape)]
(cond (-> shape
(nil? modifiers) (dissoc :modifiers)
shape (transform-shape modifiers))))
(empty-modifiers? modifiers) ([shape modifiers]
(dissoc 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 (ctm/has-structure? modifiers)
(let [shape (apply-displacement shape) (ctm/apply-structure-modifiers modifiers)))))]
modifiers (:modifiers shape)]
(cond-> shape
(not (empty-modifiers? modifiers))
(-> (set-flip modifiers)
(apply-modifiers modifiers)
(apply-text-resize modifiers))
:always (cond-> shape
(dissoc :modifiers)))))) (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 (defn transform-bounds
[points center {:keys [displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] ([points modifiers]
;; FIXME: Improve Performance (transform-bounds points nil modifiers))
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
displacement ([points center modifiers]
(when (some? displacement) (let [transform (ctm/modifiers->transform modifiers)]
(gmt/multiply resize-transform-inverse displacement)) (cond-> points
(some? transform)
resize-origin (gco/transform-points center transform)))))
(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)))))
(defn transform-selrect (defn transform-selrect
[selrect modifiers] [selrect modifiers]
(let [center (gco/center-selrect selrect)] (-> selrect
(-> selrect (gpr/rect->points)
(gpr/rect->points) (transform-bounds modifiers)
(transform-bounds center modifiers) (gpr/points->selrect)))
(gpr/points->selrect))))
(defn transform-selrect-matrix (defn transform-selrect-matrix
[selrect mtx] [selrect mtx]
@ -662,17 +493,62 @@
(map (comp gpr/points->selrect :points transform-shape)) (map (comp gpr/points->selrect :points transform-shape))
(gpr/join-selrects))) (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 (defn apply-group-modifiers
"Apply the modifiers to the group children to calculate its selection rect" "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 ([group objects modif-tree propagate?]
(->> (:shapes group) (let [modifiers (get-in modif-tree [(:id group) :modifiers])
(map (d/getf objects)) children
(map (fn [shape] (as-> (:shapes group) $
(let [modifiers (get modif-tree (:id shape)) (map (d/getf objects) $)
shape (-> shape (merge modifiers) transform-shape)] (apply-children-modifiers objects modif-tree modifiers $ propagate?))]
(if (= :group (:type shape)) (cond
(apply-group-modifiers shape objects modif-tree) (cph/mask-shape? group)
shape)))))] (update-mask-selrect group children)
(update-group-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))))

View File

@ -156,7 +156,7 @@
(if (> num to) to num))) (if (> num to) to num)))
(defn almost-zero? [num] (defn almost-zero? [num]
(< (abs (double num)) 1e-5)) (< (abs (double num)) 1e-4))
(defonce float-equal-precision 0.001) (defonce float-equal-precision 0.001)
@ -174,3 +174,8 @@
(defn max-abs (defn max-abs
[a b] [a b]
(max (abs a) (abs b))) (max (abs a) (abs b)))
(defn sign
"Get the sign (+1 / -1) for the number"
[n]
(if (neg? n) -1 1))

View File

@ -11,7 +11,6 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.bool :as gshb]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.common :refer [component-sync-attrs]] [app.common.pages.common :refer [component-sync-attrs]]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
@ -170,7 +169,7 @@
group group
(= :bool (:type group)) (= :bool (:type group))
(gshb/update-bool-selrect group children objects) (gsh/update-bool-selrect group children objects)
(:masked-group? group) (:masked-group? group)
(set-mask-selrect group children) (set-mask-selrect group children)

View File

@ -12,8 +12,6 @@
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [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.math :as mth]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
@ -419,7 +417,7 @@
(every? #(apply gpt/close? %) (d/zip old-val new-val)) (every? #(apply gpt/close? %) (d/zip old-val new-val))
(= attr :selrect) (= attr :selrect)
(gshr/close-selrect? old-val new-val) (gsh/close-selrect? old-val new-val)
:else :else
(= old-val new-val))] (= old-val new-val))]
@ -438,7 +436,7 @@
nil ;; so it does not need resize nil ;; so it does not need resize
(= (:type parent) :bool) (= (:type parent) :bool)
(gshb/update-bool-selrect parent children objects) (gsh/update-bool-selrect parent children objects)
(= (:type parent) :group) (= (:type parent) :group)
(if (:masked-group? parent) (if (:masked-group? parent)

View File

@ -71,7 +71,28 @@
:constraints-h :constraints-group :constraints-h :constraints-group
:constraints-v :constraints-group :constraints-v :constraints-group
:fixed-scroll :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 ;; Attributes that may directly be edited by the user with forms
(def editable-attrs (def editable-attrs
@ -111,31 +132,62 @@
:stroke-cap-start :stroke-cap-start
:stroke-cap-end :stroke-cap-end
:exports} :exports
:group #{:proportion-lock :layout
:width :height :layout-flex-dir
:x :y :layout-gap
:rotation :layout-gap-type
:selrect :layout-align-items
:points :layout-justify-content
:layout-align-content
:layout-wrap-type
:layout-padding-type
:layout-padding
:constraints-h :layout-item-margin
:constraints-v :layout-item-margin-type
:fixed-scroll :layout-item-h-sizing
:parent-id :layout-item-v-sizing
:frame-id :layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self}
:opacity :group #{:proportion-lock
:blend-mode :width :height
:blocked :x :y
:hidden :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 :rect #{:proportion-lock
:width :height :width :height
@ -180,7 +232,17 @@
:blur :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 :circle #{:proportion-lock
:width :height :width :height
@ -223,118 +285,239 @@
:blur :blur
:exports} :exports
:path #{:proportion-lock :layout-item-margin
:width :height :layout-item-margin-type
:x :y :layout-item-h-sizing
:rotation :layout-item-v-sizing
:selrect :layout-item-max-h
:points :layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self}
:constraints-h :path #{:proportion-lock
:constraints-v :width :height
:fixed-scroll :x :y
:parent-id :rotation
:frame-id :selrect
:points
:opacity :constraints-h
:blend-mode :constraints-v
:blocked :fixed-scroll
:hidden :parent-id
:frame-id
:fills :opacity
:fill-color :blend-mode
:fill-opacity :blocked
:fill-color-ref-id :hidden
:fill-color-ref-file
:fill-color-gradient
:strokes :fills
:stroke-style :fill-color
:stroke-alignment :fill-opacity
:stroke-width :fill-color-ref-id
:stroke-color :fill-color-ref-file
:stroke-color-ref-id :fill-color-gradient
:stroke-color-ref-file
:stroke-opacity
:stroke-color-gradient
:stroke-cap-start
:stroke-cap-end
: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 :exports
:width :height
:x :y
:rotation
:selrect
:points
:constraints-h :layout-item-margin
:constraints-v :layout-item-margin-type
:fixed-scroll :layout-item-h-sizing
:parent-id :layout-item-v-sizing
:frame-id :layout-item-max-h
:layout-item-min-h
:layout-item-max-w
:layout-item-min-w
:layout-item-align-self}
:opacity :text #{:proportion-lock
:blend-mode :width :height
:blocked :x :y
:hidden :rotation
:selrect
:points
:fill-color :constraints-h
:fill-opacity :constraints-v
:fill-color-ref-id :fixed-scroll
:fill-color-ref-file :parent-id
:fill-color-gradient :frame-id
:stroke-style :opacity
:stroke-alignment :blend-mode
:stroke-width :blocked
:stroke-color :hidden
:stroke-color-ref-id
:stroke-color-ref-file
:stroke-opacity
:stroke-color-gradient
:stroke-cap-start
:stroke-cap-end
: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 :shadow
:typography-ref-file
:font-id :blur
:font-family
:font-variant-id
:font-size
:font-weight
:font-style
: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 :text-align
:letter-spacing
: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 :width :height
:x :y :x :y
:rotation :rotation
@ -354,97 +537,36 @@
:blocked :blocked
:hidden :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 :shadow
:blur :blur
:exports} :exports
:svg-raw #{:proportion-lock :layout-item-margin
:width :height :layout-item-margin-type
:x :y :layout-item-h-sizing
:rotation :layout-item-v-sizing
:rx :ry :layout-item-max-h
:r1 :r2 :r3 :r4 :layout-item-min-h
:selrect :layout-item-max-w
:points :layout-item-min-w
:layout-item-align-self}})
: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}})

View File

@ -28,17 +28,27 @@
(= frame-id uuid/zero))) (= frame-id uuid/zero)))
(defn frame-shape? (defn frame-shape?
[{:keys [type]}] ([objects id]
(= type :frame)) (frame-shape? (get objects id)))
([{:keys [type]}]
(= type :frame)))
(defn group-shape? (defn group-shape?
[{:keys [type]}] [{:keys [type]}]
(= type :group)) (= type :group))
(defn mask-shape?
[{:keys [type masked-group?]}]
(and (= type :group) masked-group?))
(defn bool-shape? (defn bool-shape?
[{:keys [type]}] [{:keys [type]}]
(= type :bool)) (= type :bool))
(defn group-like-shape?
[{:keys [type]}]
(or (= :group type) (= :bool type)))
(defn text-shape? (defn text-shape?
[{:keys [type]}] [{:keys [type]}]
(= type :text)) (= type :text))
@ -63,9 +73,12 @@
(defn get-children-ids (defn get-children-ids
[objects id] [objects id]
(if-let [shapes (-> (get objects id) :shapes (some-> vec))] (letfn [(get-children-ids-rec
(into shapes (mapcat #(get-children-ids objects %)) shapes) [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 (defn get-children
[objects id] [objects id]
@ -456,7 +469,6 @@
(defn selected-with-children (defn selected-with-children
[objects selected] [objects selected]
(into selected (into selected
(mapcat #(get-children-ids objects %)) (mapcat #(get-children-ids objects %))
selected)) selected))

View File

@ -102,13 +102,7 @@
(fix-frames-selrects) (fix-frames-selrects)
(and (empty? (:points object)) (not= (:id object) uuid/zero)) (and (empty? (:points object)) (not= (:id object) uuid/zero))
(fix-empty-points) (fix-empty-points)))
;; Setup an empty transformation to re-calculate selrects
;; and points data
:always
(-> (assoc :modifiers {:displacement (gmt/matrix)})
(gsh/transform-shape))))
(update-page [page] (update-page [page]
(update page :objects d/update-vals update-object))] (update page :objects d/update-vals update-object))]

View File

@ -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))))))

View File

@ -9,12 +9,33 @@
[app.common.spec :as us] [app.common.spec :as us]
[clojure.spec.alpha :as s])) [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 :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) (s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-gap-type #{:simple :multiple}) (s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number) (s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :strech}) (s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :strech}) (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-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-wrap-type #{:wrap :no-wrap}) (s/def ::layout-wrap-type #{:wrap :no-wrap})
(s/def ::layout-padding-type #{:simple :multiple}) (s/def ::layout-padding-type #{:simple :multiple})
@ -25,15 +46,14 @@
(s/def ::p4 ::us/safe-number) (s/def ::p4 ::us/safe-number)
(s/def ::layout-padding (s/def ::layout-padding
(s/keys :req-un [::p1] (s/keys :opt-un [::p1 ::p2 ::p3 ::p4]))
:opt-un [::p2 ::p3 ::p4]))
(s/def ::row-gap ::us/safe-number) (s/def ::row-gap ::us/safe-number)
(s/def ::column-gap ::us/safe-number) (s/def ::column-gap ::us/safe-number)
(s/def ::layout-type #{:flex :grid}) (s/def ::layout-type #{:flex :grid})
(s/def ::layout-gap (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/def ::layout-container-props
(s/keys :opt-un [::layout (s/keys :opt-un [::layout
@ -53,25 +73,223 @@
(s/def ::m3 ::us/safe-number) (s/def ::m3 ::us/safe-number)
(s/def ::m4 ::us/safe-number) (s/def ::m4 ::us/safe-number)
(s/def ::layout-margin (s/keys :req-un [::m1] (s/def ::layout-item-margin (s/keys :opt-un [::m1 ::m2 ::m3 ::m4]))
:opt-un [::m2 ::m3 ::m4]))
(s/def ::layout-margin-type #{:simple :multiple}) (s/def ::layout-item-margin-type #{:simple :multiple})
(s/def ::layout-h-behavior #{:fill :fix :auto}) (s/def ::layout-item-h-sizing #{:fill :fix :auto})
(s/def ::layout-v-behavior #{:fill :fix :auto}) (s/def ::layout-item-v-sizing #{:fill :fix :auto})
(s/def ::layout-align-self #{:start :end :center :strech :baseline}) (s/def ::layout-item-align-self #{:start :end :center :stretch})
(s/def ::layout-max-h ::us/safe-number) (s/def ::layout-item-max-h ::us/safe-number)
(s/def ::layout-min-h ::us/safe-number) (s/def ::layout-item-min-h ::us/safe-number)
(s/def ::layout-max-w ::us/safe-number) (s/def ::layout-item-max-w ::us/safe-number)
(s/def ::layout-min-w ::us/safe-number) (s/def ::layout-item-min-w ::us/safe-number)
(s/def ::layout-child-props (s/def ::layout-child-props
(s/keys :opt-un [::layout-margin (s/keys :opt-un [::layout-item-margin
::layout-margin-type ::layout-item-margin-type
::layout-h-behavior ::layout-item-h-sizing
::layout-v-behavior ::layout-item-v-sizing
::layout-max-h ::layout-item-max-h
::layout-min-h ::layout-item-min-h
::layout-max-w ::layout-item-max-w
::layout-min-w ::layout-item-min-w
::layout-align-self])) ::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))

View File

@ -224,8 +224,31 @@
"Search for the top nested frame for positioning shapes when moving or creating. "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 Looks for all the frames in a position and then goes in depth between the top-most and its
children to find the target." children to find the target."
[objects position] ([objects position]
(let [frame-ids (all-frames-by-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)] frame-set (set frame-ids)]
(loop [current-id (first frame-ids)] (loop [current-id (first frame-ids)]
(let [current-shape (get objects current-id) (let [current-shape (get objects current-id)

View File

@ -10,6 +10,7 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth :refer [close?]] [app.common.math :as mth :refer [close?]]
[app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[clojure.test :as t])) [clojure.test :as t]))
@ -59,7 +60,7 @@
(t/testing "Transform shape with translation modifiers" (t/testing "Transform shape with translation modifiers"
(t/are [type] (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}) (let [shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)] shape-after (gsh/transform-shape shape-before)]
(t/is (not= shape-before shape-after)) (t/is (not= shape-before shape-after))
@ -91,9 +92,7 @@
(t/testing "Transform shape with resize modifiers" (t/testing "Transform shape with resize modifiers"
(t/are [type] (t/are [type]
(let [modifiers {:resize-origin (gpt/point 0 0) (let [modifiers (ctm/resize-modifiers (gpt/point 2 2) (gpt/point 0 0))
:resize-vector (gpt/point 2 2)
:resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)] shape-after (gsh/transform-shape shape-before)]
(t/is (not= shape-before shape-after)) (t/is (not= shape-before shape-after))
@ -113,9 +112,7 @@
(t/testing "Transform with empty resize" (t/testing "Transform with empty resize"
(t/are [type] (t/are [type]
(let [modifiers {:resize-origin (gpt/point 0 0) (let [modifiers (ctm/resize-modifiers (gpt/point 1 1) (gpt/point 0 0))
:resize-vector (gpt/point 1 1)
:resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)] shape-after (gsh/transform-shape shape-before)]
(t/are [prop] (t/are [prop]
@ -126,9 +123,7 @@
(t/testing "Transform with resize=0" (t/testing "Transform with resize=0"
(t/are [type] (t/are [type]
(let [modifiers {:resize-origin (gpt/point 0 0) (let [modifiers (ctm/resize-modifiers (gpt/point 0 0) (gpt/point 0 0))
:resize-vector (gpt/point 0 0)
:resize-transform (gmt/matrix)}
shape-before (create-test-shape type {:modifiers modifiers}) shape-before (create-test-shape type {:modifiers modifiers})
shape-after (gsh/transform-shape shape-before)] shape-after (gsh/transform-shape shape-before)]
(t/is (> (get-in shape-before [:selrect :width]) (t/is (> (get-in shape-before [:selrect :width])
@ -142,13 +137,11 @@
(t/testing "Transform shape with rotation modifiers" (t/testing "Transform shape with rotation modifiers"
(t/are [type] (t/are [type]
(let [modifiers {:rotation 30} (let [shape-before (create-test-shape type)
shape-before (create-test-shape type {:modifiers modifiers}) 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)] 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]) (t/is (close? (get-in shape-before [:selrect :x])
(get-in shape-after [:selrect :x]))) (get-in shape-after [:selrect :x])))
@ -166,9 +159,9 @@
(t/testing "Transform shape with rotation = 0 should leave equal selrect" (t/testing "Transform shape with rotation = 0 should leave equal selrect"
(t/are [type] (t/are [type]
(let [modifiers {:rotation 0} (let [shape-before (create-test-shape type)
shape-before (create-test-shape type {:modifiers modifiers}) modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 0)
shape-after (gsh/transform-shape shape-before)] shape-after (gsh/transform-shape (assoc shape-before :modifiers modifiers))]
(t/are [prop] (t/are [prop]
(t/is (close? (get-in shape-before [:selrect prop]) (t/is (close? (get-in shape-before [:selrect prop])
(get-in shape-after [:selrect prop]))) (get-in shape-after [:selrect prop])))
@ -177,12 +170,12 @@
(t/testing "Transform shape with invalid selrect fails gracefully" (t/testing "Transform shape with invalid selrect fails gracefully"
(t/are [type selrect] (t/are [type selrect]
(let [modifiers {:displacement (gmt/matrix)} (let [modifiers (ctm/move-modifiers 0 0)
shape-before (-> (create-test-shape type {:modifiers modifiers}) shape-before (-> (create-test-shape type) (assoc :selrect selrect))
(assoc :selrect selrect)) shape-after (gsh/transform-shape shape-before modifiers)]
shape-after (gsh/transform-shape shape-before)]
(= (:selrect shape-before) (t/is (= (:selrect shape-before)
(:selrect shape-after))) (:selrect shape-after))))
:rect {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} :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} :path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v132.292h132.292V0H0zm11.232 11.232H121.06v109.827H11.232V11.232zm17.876 18.346v73.136h29.726V29.578H29.108zm44.45 0v73.136h29.726V29.578H73.558z"/>
</svg>

Before

Width:  |  Height:  |  Size: 267 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path transform="rotate(90, 66.146, 66.146)" d="M0 0v132.292h132.292V0H0zm11.232 11.232H121.06v109.827H11.232V11.232zm17.876 18.346v73.136h29.726V29.578H29.108zm44.45 0v73.136h29.726V29.578H73.558z"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

Before

Width:  |  Height:  |  Size: 267 B

After

Width:  |  Height:  |  Size: 267 B

View File

@ -182,6 +182,10 @@
&.error { &.error {
border-color: $color-danger; border-color: $color-danger;
} }
&[disabled] {
color: $color-gray-30;
}
} }
.input-select { .input-select {

View File

@ -25,6 +25,7 @@
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
@ -52,8 +53,8 @@
[app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws] [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 :as dwsh]
[app.main.data.workspace.shapes-update-layout :as dwul]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.svg-upload :as svg] [app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.thumbnails :as dwth]
@ -686,11 +687,16 @@
shapes-to-detach shapes-to-detach
shapes-to-reroot shapes-to-reroot
shapes-to-deroot 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) (rx/of (dch/commit-changes changes)
(dwco/expand-collapse parent-id) (dwco/expand-collapse parent-id)
(dwsl/update-layout-positions [parent-id])))))) (dwul/update-layout-positions layouts-to-update))))))
(defn relocate-selected-shapes (defn relocate-selected-shapes
[parent-id to-index] [parent-id to-index]
@ -1571,37 +1577,6 @@
(rx/of (dch/commit-changes changes)))))) (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 ;; Remove graphics
;; TODO: this should be deprecated and removed together with components-v2 ;; TODO: this should be deprecated and removed together with components-v2

View File

@ -159,8 +159,8 @@
build-move-event build-move-event
(fn [comment-thread] (fn [comment-thread]
(let [frame (get objects (:frame-id comment-thread)) (let [frame (get objects (:frame-id comment-thread))
frame' (-> (merge frame (get object-modifiers (:frame-id comment-thread))) modifiers (get object-modifiers (:frame-id comment-thread))
(gsh/transform-shape)) frame' (gsh/transform-shape frame modifiers)
moved (gpt/to-vec (gpt/point (:x frame) (:y frame)) moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
(gpt/point (:x frame') (:y frame'))) (gpt/point (:x frame') (:y frame')))
position (get-in threads-position-map [(:id comment-thread) :position]) position (get-in threads-position-map [(:id comment-thread) :position])

View File

@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -20,29 +21,38 @@
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk])) [potok.core :as ptk]))
(defn truncate-zero [num default] (defn adjust-ratio
(if (mth/almost-zero? num) default num)) [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 (-> shape
(assoc :click-draw? false) (assoc :click-draw? false)
(assoc-in [:modifiers :resize-vector] scalev) (gsh/transform-shape (-> (ctm/empty)
(assoc-in [:modifiers :resize-origin] (gpt/point x y)) (ctm/resize scalev (gpt/point x y))
(assoc-in [:modifiers :resize-rotation] 0)))) (ctm/move movev))))))
(defn update-drawing [state point lock?] (defn update-drawing [state initial point lock?]
(update-in state [:workspace-drawing :object] resize-shape point lock?)) (update-in state [:workspace-drawing :object] resize-shape initial point lock?))
(defn move-drawing (defn move-drawing
[{:keys [x y]}] [{:keys [x y]}]
@ -58,8 +68,7 @@
layout (get state :workspace-layout) layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid) snap-pixel? (contains? layout :snap-pixel-grid)
initial (cond-> @ms/mouse-position initial (cond-> @ms/mouse-position snap-pixel? gpt/round)
snap-pixel? gpt/round)
page-id (:current-page-id state) page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
@ -97,7 +106,7 @@
(rx/map #(conj current %))))) (rx/map #(conj current %)))))
(rx/map (rx/map
(fn [[_ shift? point]] (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/take-until stoper))
(rx/of (common/handle-finish-drawing))))))) (rx/of (common/handle-finish-drawing)))))))

View File

@ -6,10 +6,10 @@
(ns app.main.data.workspace.drawing.common (ns app.main.data.workspace.drawing.common
(:require (:require
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
@ -51,8 +51,7 @@
(and click-draw? (not text?)) (and click-draw? (not text?))
(-> (assoc :width min-side :height min-side) (-> (assoc :width min-side :height min-side)
(assoc-in [:modifiers :displacement] (gsh/transform-shape (ctm/move-modifiers (- (/ min-side 2)) (- (/ min-side 2)))))
(gmt/translate-matrix (- (/ min-side 2)) (- (/ min-side 2)))))
(and click-draw? text?) (and click-draw? text?)
(assoc :height 17 :width 4 :grow-type :auto-width) (assoc :height 17 :width 4 :grow-type :auto-width)
@ -61,8 +60,7 @@
(cts/setup-rect-selrect) (cts/setup-rect-selrect)
:always :always
(-> (gsh/transform-shape) (dissoc :initialized? :click-draw?))]
(dissoc :initialized? :click-draw?)))]
;; Add & select the created shape to the workspace ;; Add & select the created shape to the workspace
(rx/concat (rx/concat
(if (= :text (:type shape)) (if (= :text (:type shape))

View File

@ -79,8 +79,7 @@
build-move-event build-move-event
(fn [guide] (fn [guide]
(let [frame (get objects (:frame-id guide)) (let [frame (get objects (:frame-id guide))
frame' (-> (merge frame (get object-modifiers (:frame-id guide))) frame' (gsh/transform-shape (get object-modifiers (:frame-id guide)))
(gsh/transform-shape))
moved (gpt/to-vec (gpt/point (:x frame) (:y frame)) moved (gpt/to-vec (gpt/point (:x frame) (:y frame))
(gpt/point (:x frame') (:y frame'))) (gpt/point (:x frame') (:y frame')))

View File

@ -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))))))))

View File

@ -21,6 +21,7 @@
[app.main.data.modal :as md] [app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc] [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.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.zoom :as dwz] [app.main.data.workspace.zoom :as dwz]
@ -552,8 +553,11 @@
(filter #(= :frame (get-in % [:obj :type]))) (filter #(= :frame (get-in % [:obj :type])))
(map #(vector (:old-id %) (get-in % [:obj :id])))) (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/concat
(->> (rx/from dup-frames) (->> (rx/from dup-frames)
(rx/map (fn [[old-id new-id]] (dwt/duplicate-thumbnail old-id new-id)))) (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. ;; Warning: This order is important for the focus mode.
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(select-shapes new-selected) (select-shapes new-selected)
(dwul/update-layout-positions frames)
(memorize-duplicated id-original id-duplicated)))))))))) (memorize-duplicated id-original id-duplicated))))))))))
(defn change-hover-state (defn change-hover-state

View File

@ -8,9 +8,13 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.pages.helpers :as cph] [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.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.state-helpers :as wsh]
[app.main.data.workspace.transforms :as dwt]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk])) [potok.core :as ptk]))
@ -25,8 +29,7 @@
:layout-wrap-type :layout-wrap-type
:layout-padding-type :layout-padding-type
:layout-padding :layout-padding
]) :layout-gap-type])
(def initial-flex-layout (def initial-flex-layout
{:layout :flex {:layout :flex
@ -35,7 +38,7 @@
:layout-gap {:row-gap 0 :column-gap 0} :layout-gap {:row-gap 0 :column-gap 0}
:layout-align-items :start :layout-align-items :start
:layout-justify-content :start :layout-justify-content :start
:layout-align-content :strech :layout-align-content :stretch
:layout-wrap-type :no-wrap :layout-wrap-type :no-wrap
:layout-padding-type :simple :layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}})
@ -43,30 +46,50 @@
(def initial-grid-layout ;; TODO (def initial-grid-layout ;; TODO
{:layout :grid}) {:layout :grid})
(defn update-layout-positions (defn get-layout-initializer
[ids] [type]
(ptk/reify ::update-layout-positions (let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)]
ptk/WatchEvent (fn [shape]
(watch [_ state _] (-> shape
(let [objects (wsh/lookup-page-objects state) (merge shape initial-layout-data)))))
ids (->> ids (filter #(get-in objects [% :layout])))]
(if (d/not-empty? ids)
(rx/of (dwt/set-modifiers ids)
(dwt/apply-modifiers))
(rx/empty))))))
;; TODO: Remove constraints from children
(defn create-layout (defn create-layout
[ids type] [ids type]
(ptk/reify ::create-layout (ptk/reify ::create-layout
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ state _]
(if (= type :flex) (let [objects (wsh/lookup-page-objects state)
(rx/of (dwc/update-shapes ids #(merge % initial-flex-layout)) children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)]
(update-layout-positions ids)) (rx/of (dwc/update-shapes ids (get-layout-initializer type))
(rx/of (dwc/update-shapes ids #(merge % initial-grid-layout)) (wsul/update-layout-positions ids)
(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 (defn remove-layout
[ids] [ids]
@ -74,7 +97,23 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys)) (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 (defn update-layout
[ids changes] [ids changes]
@ -82,7 +121,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (dwc/update-shapes ids #(d/deep-merge % changes)) (rx/of (dwc/update-shapes ids #(d/deep-merge % changes))
(update-layout-positions ids))))) (wsul/update-layout-positions ids)))))
(defn update-layout-child (defn update-layout-child
[ids changes] [ids changes]
@ -90,6 +129,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [objects (wsh/lookup-page-objects 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)) (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)))))))

View File

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.proportions :as gpr] [app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
@ -16,13 +17,15 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.comments :as dc] [app.main.data.comments :as dc]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe] [app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws] [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.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features] [app.main.features :as features]
[app.main.streams :as ms] [app.main.streams :as ms]
[beicon.core :as rx] [beicon.core :as rx]
@ -101,7 +104,7 @@
(rx/concat (rx/concat
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwsl/update-layout-positions [(:parent-id shape)]) (dwsul/update-layout-positions [(:parent-id shape)])
(when-not no-select? (when-not no-select?
(dws/select-shapes (d/ordered-set id)))) (dws/select-shapes (d/ordered-set id))))
(when (= :text (:type attrs)) (when (= :text (:type attrs))
@ -146,6 +149,10 @@
ids (cph/clean-loops objects ids) ids (cph/clean-loops objects ids)
lookup (d/getf objects) 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) components-v2 (features/active-feature? state :components-v2)
groups-to-unmask groups-to-unmask
@ -265,8 +272,9 @@
(reduce ctp/remove-flow flows))))))] (reduce ctp/remove-flow flows))))))]
(rx/of (dc/detach-comment-thread ids) (rx/of (dc/detach-comment-thread ids)
(dwsl/update-layout-positions all-parents) (dwsul/update-layout-positions all-parents)
(dch/commit-changes changes))))))) (dch/commit-changes changes)
(dwsul/update-layout-positions layout-ids)))))))
(defn- viewport-center (defn- viewport-center
[state] [state]
@ -292,3 +300,38 @@
(assoc :frame-id frame-id) (assoc :frame-id frame-id)
(cts/setup-rect-selrect))] (cts/setup-rect-selrect))]
(rx/of (add-shape shape)))))) (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)))))))))

View File

@ -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))))))

View File

@ -15,6 +15,8 @@
[app.main.data.workspace.drawing :as dwd] [app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.layers :as dwly] [app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl] [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.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
@ -204,7 +206,12 @@
:artboard-selection {:tooltip (ds/meta (ds/alt "G")) :artboard-selection {:tooltip (ds/meta (ds/alt "G"))
:command (ds/c-mod "alt+g") :command (ds/c-mod "alt+g")
:subsections [:modify-layers] :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 ;; TOOLS
@ -382,7 +389,7 @@
:show-shortcuts {:tooltip "?" :show-shortcuts {:tooltip "?"
:command "?" :command "?"
:subsections [:main-menu] :subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :shortcuts)) } :fn #(st/emit! (toggle-layout-flag :shortcuts))}
;; PANELS ;; PANELS

View File

@ -127,15 +127,15 @@
(defn select-bool-children (defn select-bool-children
[parent-id state] [parent-id state]
(let [objects (lookup-page-objects state) (let [objects (lookup-page-objects state)
selected (lookup-selected-raw state)
modifiers (:workspace-modifiers state) modifiers (:workspace-modifiers state)
children-ids (cph/get-children-ids objects parent-id) children-ids (cph/get-children-ids objects parent-id)
selected-children (into [] (filter selected) children-ids) children
(-> (select-keys objects children-ids)
modifiers (select-keys modifiers selected-children) (d/update-vals
children (select-keys objects children-ids)] (fn [child]
(cond-> child
(contains? modifiers (:id child))
(gsh/transform-shape (get-in modifiers [(:id child) :modifiers]))))))]
(as-> children $ (as-> children $
(gsh/merge-modifiers $ modifiers)
(d/mapm (set-content-modifiers state) $)))) (d/mapm (set-content-modifiers state) $))))

View File

@ -13,6 +13,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
@ -318,18 +319,14 @@
(watch [_ _ _] (watch [_ _ _]
(letfn [(update-fn [shape] (letfn [(update-fn [shape]
(let [{:keys [selrect grow-type]} shape (let [{:keys [selrect grow-type]} shape
{shape-width :width shape-height :height} selrect {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)]
(cond-> shape (cond-> shape
(and (not-changed? shape-width new-width) (= grow-type :auto-width)) (and (not-changed? shape-width new-width) (= grow-type :auto-width))
(-> (assoc :modifiers modifier-width) (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width))
(gsh/transform-shape))
(and (not-changed? shape-height new-height) (and (not-changed? shape-height new-height)
(or (= grow-type :auto-height) (= grow-type :auto-width))) (or (= grow-type :auto-height) (= grow-type :auto-width)))
(-> (assoc :modifiers modifier-height) (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))]
(gsh/transform-shape)))))]
(rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))) (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}))))))
@ -346,18 +343,13 @@
(defn apply-text-modifier (defn apply-text-modifier
[shape {:keys [width height position-data]}] [shape {:keys [width height position-data]}]
(let [modifier-width (when width (gsh/resize-modifiers shape :width width)) (let [new-shape
modifier-height (when height (gsh/resize-modifiers shape :height height))
new-shape
(cond-> shape (cond-> shape
(some? modifier-width) (some? width)
(-> (assoc :modifiers modifier-width) (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width width))
(gsh/transform-shape))
(some? modifier-height) (some? height)
(-> (assoc :modifiers modifier-height) (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height height))
(gsh/transform-shape))
(some? position-data) (some? position-data)
(assoc :position-data position-data)) (assoc :position-data position-data))
@ -402,7 +394,7 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [ids (keys (::update-position-data 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 ptk/WatchEvent
(watch [_ state _] (watch [_ state _]

View File

@ -11,16 +11,17 @@
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.flex-layout :as gsl]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.common :as cpc]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst] [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.changes :as dch]
[app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.comments :as-alias dwcm] [app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.guides :as-alias dwg]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
@ -95,233 +96,12 @@
(update state :workspace-local dissoc :transform)))) (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 -------------------------------------------------------- ;; -- Resize --------------------------------------------------------
(defn start-resize (defn start-resize
"Enter mouse resize mode, until mouse button is released." "Enter mouse resize mode, until mouse button is released."
[handler ids shape] [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) (let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape {:keys [rotation]} shape
@ -331,10 +111,10 @@
rotation (or rotation 0) 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) 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) shape-center shape-transform-inverse)
shapev (-> (gpt/point width height)) shapev (-> (gpt/point width height))
@ -381,20 +161,46 @@
(gpt/transform shape-transform))) (gpt/transform shape-transform)))
resize-origin 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) (some? displacement)
(gpt/add displacement)) (gpt/add displacement))
displacement (when (some? displacement) ;; When the horizontal/vertical scale a flex children with auto/fill
(gmt/translate-matrix displacement))] ;; 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 set-fix-width?
{:displacement displacement (and (not (mth/close? (:x scalev) 1))
:resize-vector scalev (or (and (or layout? layout-child?) auto-width?)
:resize-origin resize-origin (and layout-child? fill-width?)))
:resize-transform shape-transform
:resize-scale-text scale-text set-fix-height?
:resize-transform-inverse shape-transform-inverse})))) (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 ;; Unifies the instantaneous proportion lock modifier
;; activated by Shift key and the shapes own proportion ;; activated by Shift key and the shapes own proportion
@ -418,6 +224,7 @@
zoom (get-in state [:workspace-local :zoom] 1) zoom (get-in state [:workspace-local :zoom] 1)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
resizing-shapes (map #(get objects %) ids)] resizing-shapes (map #(get objects %) ids)]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt) (rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
@ -425,9 +232,9 @@
(rx/switch-map (fn [[point _ _ :as current]] (rx/switch-map (fn [[point _ _ :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point) (->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point)
(rx/map #(conj current %))))) (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/take-until stoper))
(rx/of (apply-modifiers) (rx/of (dwm/apply-modifiers)
(finish-transform)))))))) (finish-transform))))))))
(defn update-dimensions (defn update-dimensions
@ -444,16 +251,17 @@
snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid) snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid)
(int? value)) (int? value))
get-modifier get-modifier
(fn [shape] (gsh/resize-modifiers shape attr value)) (fn [shape] (ctm/change-dimensions-modifiers shape attr value))
modif-tree 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))) (assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (apply-modifiers))))) (rx/of (dwm/apply-modifiers)))))
(defn change-orientation (defn change-orientation
"Change orientation of shapes, from the sidebar options form. "Change orientation of shapes, from the sidebar options form.
@ -468,16 +276,17 @@
snap-pixel? (contains? (get state :workspace-layout) :snap-pixel-grid) snap-pixel? (contains? (get state :workspace-layout) :snap-pixel-grid)
get-modifier get-modifier
(fn [shape] (gsh/change-orientation-modifiers shape orientation)) (fn [shape] (ctm/change-orientation-modifiers shape orientation))
modif-tree 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))) (assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (apply-modifiers))))) (rx/of (dwm/apply-modifiers)))))
;; -- Rotate -------------------------------------------------------- ;; -- Rotate --------------------------------------------------------
@ -518,9 +327,9 @@
(rx/map (rx/map
(fn [[[pos mod?] shift?]] (fn [[[pos mod?] shift?]]
(let [delta-angle (calculate-angle 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/take-until stoper))
(rx/of (apply-modifiers) (rx/of (dwm/apply-modifiers)
(finish-transform))))))) (finish-transform)))))))
(defn increase-rotation (defn increase-rotation
@ -534,17 +343,17 @@
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape] rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))] (let [delta (- rotation (:rotation shape))]
(set-rotation-modifiers delta [shape])))] (dwm/set-rotation-modifiers delta [shape])))]
(rx/concat (rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape))) (rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
(rx/of (apply-modifiers))))))) (rx/of (dwm/apply-modifiers)))))))
;; -- Move ---------------------------------------------------------- ;; -- Move ----------------------------------------------------------
(declare start-move) (declare start-move)
(declare start-move-duplicate) (declare start-move-duplicate)
(declare calculate-frame-for-move) (declare move-shapes-to-frame)
(declare get-displacement) (declare get-displacement)
(defn start-move-selected (defn start-move-selected
@ -626,13 +435,18 @@
zoom (get-in state [:workspace-local :zoom] 1) zoom (get-in state [:workspace-local :zoom] 1)
focus (:workspace-focus-selected state) focus (:workspace-focus-selected state)
fix-axis (fn [[position shift?]] exclude-frames (into #{}
(let [delta (gpt/to-vec from-position position)] (filter (partial cph/frame-shape? objects))
(if shift? (cph/selected-with-children objects selected))
(if (> (mth/abs (:x delta)) (mth/abs (:y delta)))
(gpt/point (:x delta) 0) fix-axis
(gpt/point 0 (:y delta))) (fn [[position shift?]]
delta))) (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 position (->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift) (rx/with-latest-from ms/mouse-position-shift)
@ -649,21 +463,43 @@
(rx/map #(vector pos %)))))))] (rx/map #(vector pos %)))))))]
(if (empty? shapes) (if (empty? shapes)
(rx/of (finish-transform)) (rx/of (finish-transform))
(rx/concat (let [move-stream
(->> position (->> position
;; We ask for the snap position but we continue even if the result is not available ;; We ask for the snap position but we continue even if the result is not available
(rx/with-latest vector snap-delta) (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))
(rx/of (dwu/start-undo-transaction) ;; We try to use the previous snap so we don't have to wait for the result of the new
(calculate-frame-for-move ids) (rx/map snap/correct-snap-point)
(apply-modifiers {:undo-transation? false})
(finish-transform) (rx/map
(dwu/commit-undo-transaction))))))))) (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}) (s/def ::direction #{:up :down :right :left})
@ -704,12 +540,12 @@
(rx/merge (rx/merge
(->> move-events (->> move-events
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
(rx/map #(hash-map :displacement (gmt/translate-matrix %))) (rx/map #(dwm/create-modif-tree selected (ctm/move-modifiers %)))
(rx/map (partial set-modifiers selected)) (rx/map (partial dwm/set-modifiers))
(rx/take-until stopper)) (rx/take-until stopper))
(rx/of (move-selected direction shift?))) (rx/of (move-selected direction shift?)))
(rx/of (apply-modifiers) (rx/of (dwm/apply-modifiers)
(finish-transform)))) (finish-transform))))
(rx/empty)))))) (rx/empty))))))
@ -736,34 +572,39 @@
pos (gpt/point (or (:x position) (:x bbox)) pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox))) (or (:y position) (:y bbox)))
delta (gpt/subtract pos cpos) delta (gpt/subtract pos cpos)
displ (gmt/translate-matrix delta)]
(rx/of (set-modifiers [id] {:displacement displ} false true) modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
(apply-modifiers [id]))))))
(defn- calculate-frame-for-move (rx/of (dwm/set-modifiers modif-tree)
[ids] (dwm/apply-modifiers))))))
(ptk/reify ::calculate-frame-for-move
(defn- move-shapes-to-frame
[ids frame-id drop-index]
(ptk/reify ::move-shapes-to-frame
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [position @ms/mouse-position (let [page-id (:current-page-id state)
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) 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) lookup (d/getf objects)
shapes (->> ids (cph/clean-loops objects) (keep lookup))
moving-shapes moving-shapes
(->> ids (cond->> shapes
(cph/clean-loops objects) (not layout?)
(keep lookup) (remove #(= (:frame-id %) frame-id))
(remove #(= (:frame-id %) frame-id)))
layout?
(remove #(and (= (:frame-id %) frame-id)
(not= (:parent-id %) frame-id))))
changes changes
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (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) (rx/of (dch/commit-changes changes)
(dwc/expand-collapse frame-id))))))) (dwc/expand-collapse frame-id)))))))
@ -787,15 +628,17 @@
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true}) selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected) shapes (map #(get objects %) selected)
selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape))) selrect (gsh/selection-rect shapes)
origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))] origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))
(rx/of (set-modifiers selected modif-tree (dwm/create-modif-tree
{:resize-vector (gpt/point -1.0 1.0) selected
:resize-origin origin (-> (ctm/empty)
:displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))} (ctm/resize (gpt/point -1.0 1.0) origin)
true) (ctm/move (gpt/point (:width selrect) 0))))]
(apply-modifiers))))))
(rx/of (dwm/set-modifiers modif-tree true)
(dwm/apply-modifiers))))))
(defn flip-vertical-selected [] (defn flip-vertical-selected []
(ptk/reify ::flip-vertical-selected (ptk/reify ::flip-vertical-selected
@ -804,12 +647,14 @@
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true}) selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected) shapes (map #(get objects %) selected)
selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape))) selrect (gsh/selection-rect shapes)
origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))] origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))
(rx/of (set-modifiers selected modif-tree (dwm/create-modif-tree
{:resize-vector (gpt/point 1.0 -1.0) selected
:resize-origin origin (-> (ctm/empty)
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))} (ctm/resize (gpt/point 1.0 -1.0) origin)
true) (ctm/move (gpt/point 0 (:height selrect)))))]
(apply-modifiers))))))
(rx/of (dwm/set-modifiers modif-tree true)
(dwm/apply-modifiers))))))

View File

@ -11,6 +11,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st] [app.main.store :as st]
[okulary.core :as l])) [okulary.core :as l]))
@ -265,6 +266,14 @@
[ids] [ids]
(l/derived #(into [] (keep (d/getf %)) ids) workspace-page-objects =)) (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 (defn children-objects
[id] [id]
(l/derived (l/derived
@ -434,7 +443,8 @@
(l/derived (l/derived
(fn [objects] (fn [objects]
(->> ids (->> ids
(some #(-> (cph/get-parent objects %) :layout)))) (map (d/getf objects))
(some (partial ctl/layout-child? objects))))
workspace-page-objects)) workspace-page-objects))
(defn get-flex-child-viewer? (defn get-flex-child-viewer?
@ -442,7 +452,10 @@
(l/derived (l/derived
(fn [state] (fn [state]
(let [objects (wsh/lookup-viewer-objects state page-id)] (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 =)) st/state =))
(def colorpicker (def colorpicker

View File

@ -15,12 +15,12 @@
["react-dom/server" :as rds] ["react-dom/server" :as rds]
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.bounds :as gsb] [app.common.geom.shapes.bounds :as gsb]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.config :as cfg] [app.config :as cfg]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
@ -81,8 +81,7 @@
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
(let [render-thumbnails? (mf/use-ctx muc/render-thumbnails) (let [render-thumbnails? (mf/use-ctx muc/render-thumbnails)
childs (mapv #(get objects %) (:shapes shape)) childs (mapv #(get objects %) (:shapes shape))]
shape (gsh/transform-shape shape)]
(if (and render-thumbnails? (some? (:thumbnail shape))) (if (and render-thumbnails? (some? (:thumbnail shape)))
[:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}] [:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}]
[:& frame-shape {:shape shape :childs childs}]))))) [:& frame-shape {:shape shape :childs childs}])))))
@ -135,8 +134,7 @@
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects)) bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))] frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
(when (and shape (not (:hidden shape))) (when (and shape (not (:hidden shape)))
(let [shape (gsh/transform-shape shape) (let [opts #js {:shape shape}
opts #js {:shape shape}
svg-raw? (= :svg-raw (:type shape))] svg-raw? (= :svg-raw (:type shape))]
(if-not svg-raw? (if-not svg-raw?
[:> shape-container {:shape shape} [:> shape-container {:shape shape}
@ -166,8 +164,7 @@
[objects object] [objects object]
(let [shapes (cph/get-immediate-children objects) (let [shapes (cph/get-immediate-children objects)
srect (gsh/selection-rect shapes) srect (gsh/selection-rect shapes)
object (merge object (select-keys srect [:x :y :width :height])) object (merge object (select-keys srect [:x :y :width :height]))]
object (gsh/transform-shape object)]
(assoc object :fill-color "#f0f0f0"))) (assoc object :fill-color "#f0f0f0")))
(defn adapt-objects-for-shape (defn adapt-objects-for-shape
@ -180,14 +177,12 @@
;; Replace the previous object with the new one ;; Replace the previous object with the new one
objects (assoc objects object-id object) objects (assoc objects object-id object)
modifier (-> (gpt/point (:x object) (:y object)) vector (-> (gpt/point (:x object) (:y object))
(gpt/negate) (gpt/negate))
(gmt/translate-matrix))
mod-ids (cons object-id (cph/get-children-ids objects object-id)) mod-ids (cons object-id (cph/get-children-ids objects object-id))
updt-fn #(-> %1
(assoc-in [%2 :modifiers :displacement] modifier) updt-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(update %2 gsh/transform-shape))]
(reduce updt-fn objects mod-ids))) (reduce updt-fn objects mod-ids)))
@ -247,24 +242,21 @@
bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur)) bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur))
delta-bounds (gpt/point (:x bounds) (:y bounds)) delta-bounds (gpt/point (:x bounds) (:y bounds))
vector (gpt/negate delta-bounds)
modifier (gmt/translate-matrix (gpt/negate delta-bounds))
children-ids children-ids
(cph/get-children-ids objects frame-id) (cph/get-children-ids objects frame-id)
objects objects
(mf/with-memo [frame-id objects modifier] (mf/with-memo [frame-id objects vector]
(let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)] (let [update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(->> children-ids (->> children-ids
(into [frame-id]) (into [frame-id])
(reduce update-fn objects)))) (reduce update-fn objects))))
frame frame
(mf/with-memo [modifier] (mf/with-memo [vector]
(-> frame (gsh/transform-shape frame (ctm/move-modifiers vector)))
(assoc-in [:modifiers :displacement] modifier)
(gsh/transform-shape)))
frame frame
(cond-> frame (cond-> frame
@ -293,7 +285,6 @@
:xmlnsXlink "http://www.w3.org/1999/xlink" :xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
:fill "none"} :fill "none"}
[:& shape-wrapper {:shape frame}]]])) [:& shape-wrapper {:shape frame}]]]))
@ -305,22 +296,20 @@
(let [group-id (:id group) (let [group-id (:id group)
include-metadata? (mf/use-ctx export/include-metadata-ctx) include-metadata? (mf/use-ctx export/include-metadata-ctx)
modifier vector
(mf/use-memo (mf/use-memo
(mf/deps (:x group) (:y group)) (mf/deps (:x group) (:y group))
(fn [] (fn []
(-> (gpt/point (:x group) (:y group)) (-> (gpt/point (:x group) (:y group))
(gpt/negate) (gpt/negate))))
(gmt/translate-matrix))))
objects objects
(mf/use-memo (mf/use-memo
(mf/deps modifier objects group-id) (mf/deps vector objects group-id)
(fn [] (fn []
(let [modifier-ids (cons group-id (cph/get-children-ids objects group-id)) (let [children-ids (cons group-id (cph/get-children-ids objects group-id))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
modifiers (reduce update-fn {} modifier-ids)] (reduce update-fn objects children-ids))))
(gsh/merge-modifiers objects modifiers))))
group (get objects group-id) group (get objects group-id)
width (* (:width group) zoom) width (* (:width group) zoom)

View File

@ -358,7 +358,8 @@
"Snaps a position given an old snap to a different position. We use this to provide a temporal "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." snap while the new is being processed."
[[position [snap-pos snap-delta]]] [[position [snap-pos snap-delta]]]
(if (some? snap-delta) (if (nil? snap-delta)
position
(let [dx (if (not= 0 (:x snap-delta)) (let [dx (if (not= 0 (:x snap-delta))
(- (+ (:x snap-pos) (:x snap-delta)) (:x position)) (- (+ (:x snap-pos) (:x snap-delta)) (:x position))
0) 0)
@ -372,6 +373,4 @@
dy (if (> (mth/abs dy) snap-accuracy) 0 dy)] dy (if (> (mth/abs dy) snap-accuracy) 0 dy)]
(-> position (-> position
(update :x + dx) (update :x + dx)
(update :y + dy))) (update :y + dy)))))
position))

View File

@ -6,6 +6,7 @@
(ns app.main.ui.components.shape-icon (ns app.main.ui.components.shape-icon
(:require (:require
[app.common.types.shape.layout :as ctl]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -13,7 +14,15 @@
(mf/defc element-icon (mf/defc element-icon
[{:keys [shape main-instance?] :as props}] [{:keys [shape main-instance?] :as props}]
(case (:type shape) (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 :image i/image
:line i/line :line i/line
:circle i/circle :circle i/circle

View File

@ -133,6 +133,8 @@
(def justify-content-row-end (icon-xref :justify-content-row-end)) (def justify-content-row-end (icon-xref :justify-content-row-end))
(def justify-content-row-start (icon-xref :justify-content-row-start)) (def justify-content-row-start (icon-xref :justify-content-row-start))
(def layers (icon-xref :layers)) (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 letter-spacing (icon-xref :letter-spacing))
(def libraries (icon-xref :libraries)) (def libraries (icon-xref :libraries))
(def library (icon-xref :library)) (def library (icon-xref :library))

View File

@ -6,7 +6,6 @@
(ns app.main.ui.shapes.bool (ns app.main.ui.shapes.bool
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.main.ui.hooks :refer [use-equal-memo]] [app.main.ui.hooks :refer [use-equal-memo]]
@ -34,9 +33,7 @@
(:bool-content shape) (:bool-content shape)
(some? childs) (some? childs)
(->> childs (gsh/calc-bool-content shape childs))))]
(d/mapm #(gsh/transform-shape %2))
(gsh/calc-bool-content shape)))))]
[:* [:*
(when (some? bool-content) (when (some? bool-content)

View File

@ -7,6 +7,7 @@
(ns app.main.ui.shapes.mask (ns app.main.ui.shapes.mask
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -50,9 +51,7 @@
render-id (mf/use-ctx muc/render-id) render-id (mf/use-ctx muc/render-id)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
mask-bb (-> (gsh/transform-shape mask) mask-bb (:points mask)
(:points))
mask-bb-rect (gsh/points->rect mask-bb)] mask-bb-rect (gsh/points->rect mask-bb)]
[:defs [:defs
[:filter {:id (filter-id render-id mask)} [:filter {:id (filter-id render-id mask)}
@ -68,7 +67,7 @@
[:clipPath {:class "mask-clip-path" [:clipPath {:class "mask-clip-path"
:id (clip-id render-id mask)} :id (clip-id render-id mask)}
[:polyline {:points (->> mask-bb [:polyline {:points (->> mask-bb
(map #(str (:x %) "," (:y %))) (map #(dm/str (:x %) "," (:y %)))
(str/join " "))}]] (str/join " "))}]]
;; When te shape is a text we pass to the shape the info and disable the filter. ;; When te shape is a text we pass to the shape the info and disable the filter.

View File

@ -88,8 +88,6 @@
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape)) childs (mapv #(get objects %) (:shapes shape))
shape (gsh/transform-shape shape)
props (-> (obj/create) props (-> (obj/create)
(obj/merge! props) (obj/merge! props)
(obj/merge! #js {:shape shape (obj/merge! #js {:shape shape
@ -171,8 +169,7 @@
(mf/use-memo (mf/deps objects) (mf/use-memo (mf/deps objects)
#(svg-raw-container-factory objects))] #(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape))) (when (and shape (not (:hidden shape)))
(let [shape (-> (gsh/transform-shape shape) (let [shape (gsh/translate-to-frame shape frame)
(gsh/translate-to-frame frame))
opts #js {:shape shape opts #js {:shape shape
:frame frame}] :frame frame}]
(case (:type shape) (case (:type shape)

View File

@ -57,8 +57,6 @@
shapes (resolve-shapes objects [hover]) shapes (resolve-shapes objects [hover])
hover-shape (or (first shapes) frame) hover-shape (or (first shapes) frame)
hover-shape (gsh/translate-to-frame hover-shape size)
selected-shapes (resolve-shapes objects selected) selected-shapes (resolve-shapes objects selected)
selrect (gsh/selection-rect selected-shapes)] selrect (gsh/selection-rect selected-shapes)]

View File

@ -8,9 +8,10 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
@ -29,14 +30,10 @@
(defn prepare-objects (defn prepare-objects
[frame size objects] [frame size objects]
(let [ (let [frame-id (:id frame)
frame-id (:id frame) vector (-> (gpt/point (:x size) (:y size))
modifier (-> (gpt/point (:x size) (:y size)) (gpt/negate))
(gpt/negate) update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(gmt/translate-matrix))
update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)]
(->> (cph/get-children-ids objects frame-id) (->> (cph/get-children-ids objects frame-id)
(into [frame-id]) (into [frame-id])
(reduce update-fn objects)))) (reduce update-fn objects))))

View File

@ -350,7 +350,6 @@
[props] [props]
(let [shape (obj/get props "shape") (let [shape (obj/get props "shape")
childs (mapv #(get objects %) (:shapes shape)) childs (mapv #(get objects %) (:shapes shape))
shape (gsh/transform-shape shape)
props (obj/merge! #js {} props props (obj/merge! #js {} props
#js {:shape shape #js {:shape shape
:childs childs :childs childs
@ -429,7 +428,8 @@
(mf/with-memo [objects] (mf/with-memo [objects]
(svg-raw-container-factory objects))] (svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape))) (when (and shape (not (:hidden shape)))
(let [shape (-> (gsh/transform-shape shape) (let [shape (-> shape
#_(gsh/transform-shape)
(gsh/translate-to-frame frame)) (gsh/translate-to-frame frame))
opts #js {:shape shape opts #js {:shape shape

View File

@ -19,6 +19,8 @@
[app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws] [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.data.workspace.shortcuts :as sc]
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -212,7 +214,7 @@
(let [multiple? (> (count shapes) 1) (let [multiple? (> (count shapes) 1)
single? (= (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-frame? (->> shapes (d/seek cph/frame-shape?))
has-group? (->> shapes (d/seek cph/group-shape?)) has-group? (->> shapes (d/seek cph/group-shape?))
@ -364,6 +366,31 @@
[:& menu-entry {:title (tr "workspace.shape.menu.flow-start") [:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
:on-click do-add-flow}]))))) :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 (mf/defc context-menu-component
[{:keys [shapes]}] [{:keys [shapes]}]
@ -517,6 +544,7 @@
[:> context-menu-path props] [:> context-menu-path props]
[:> context-menu-layer-options props] [:> context-menu-layer-options props]
[:> context-menu-prototype props] [:> context-menu-prototype props]
[:> context-menu-flex props]
[:> context-menu-component props] [:> context-menu-component props]
[:> context-menu-delete props]]))) [:> context-menu-delete props]])))

View File

@ -12,7 +12,9 @@
others are defined using a generic wrapper implemented in others are defined using a generic wrapper implemented in
common." common."
(:require (:require
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.uuid :as uuid]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.image :as image] [app.main.ui.shapes.image :as image]
@ -55,33 +57,33 @@
(mf/deps objects) (mf/deps objects)
#(cph/objects-by-frame objects))] #(cph/objects-by-frame objects))]
[:& (mf/provider ctx/active-frames) {:value active-frames} [:g {:id (dm/str "shape-" uuid/zero)}
;; Render font faces only for shapes that are part of the root [:& (mf/provider ctx/active-frames) {:value active-frames}
;; frame but don't belongs to any other frame. ;; Render font faces only for shapes that are part of the root
(let [xform (comp ;; frame but don't belongs to any other frame.
(remove cph/frame-shape?) (let [xform (comp
(mapcat #(cph/get-children-with-self objects (:id %))))] (remove cph/frame-shape?)
[:& ff/fontfaces-style {:shapes (into [] xform shapes)}]) (mapcat #(cph/get-children-with-self objects (:id %))))]
[:& ff/fontfaces-style {:shapes (into [] xform shapes)}])
(for [shape shapes] [:g.frame-children
(cond (for [shape shapes]
(not (cph/frame-shape? shape)) [:g.ws-shape-wrapper {:key (:id shape)}
[:& shape-wrapper (cond
{:shape shape (not (cph/frame-shape? shape))
:key (:id shape)}] [:& shape-wrapper
{:shape shape}]
(cph/root-frame? shape) (cph/root-frame? shape)
[:& root-frame-wrapper [:& root-frame-wrapper
{:shape shape {:shape shape
:key (:id shape) :objects (get frame-objects (:id shape))
:objects (get frame-objects (:id shape)) :thumbnail? (not (contains? active-frames (:id shape)))}]
:thumbnail? (not (contains? active-frames (:id shape)))}]
:else :else
[:& nested-frame-wrapper [:& nested-frame-wrapper
{:shape shape {:shape shape
:key (:id shape) :objects (get frame-objects (:id shape))}])])]]]))
:objects (get frame-objects (:id shape))}]))]))
(mf/defc shape-wrapper (mf/defc shape-wrapper
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))] {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
@ -98,20 +100,21 @@
opts #js {:shape shape :thumbnail? thumbnail?}] opts #js {:shape shape :thumbnail? thumbnail?}]
(when (and (some? shape) (not (:hidden shape))) (when (and (some? shape) (not (:hidden shape)))
(case (:type shape) [:g.workspace-shape-wrapper
:path [:> path/path-wrapper opts] (case (:type shape)
:text [:> text/text-wrapper opts] :path [:> path/path-wrapper opts]
:group [:> group-wrapper opts] :text [:> text/text-wrapper opts]
:rect [:> rect-wrapper opts] :group [:> group-wrapper opts]
:image [:> image-wrapper opts] :rect [:> rect-wrapper opts]
:circle [:> circle-wrapper opts] :image [:> image-wrapper opts]
:svg-raw [:> svg-raw-wrapper opts] :circle [:> circle-wrapper opts]
:bool [:> bool-wrapper opts] :svg-raw [:> svg-raw-wrapper opts]
:bool [:> bool-wrapper opts]
;; Only used when drawing a new frame. ;; Only used when drawing a new frame.
:frame [:> nested-frame-wrapper opts] :frame [:> nested-frame-wrapper opts]
nil)))) nil)])))
(def group-wrapper (group/group-wrapper-factory shape-wrapper)) (def group-wrapper (group/group-wrapper-factory shape-wrapper))
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper)) (def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))

View File

@ -11,79 +11,28 @@
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.types.modifiers :as ctm]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.viewport.utils :as vwu] [app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as globals]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn- transform-no-resize (defn get-shape-node
"If we apply a scale directly to the texts it will show deformed so we need to create this ([id]
correction matrix to \"undo\" the resize but keep the other transformations." (get-shape-node js/document id))
[{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers]
(let [corner-pt (first points) ([base-node id]
corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) (if (= (.-id base-node) (dm/str "shape-" id))
base-node
resize-x? (some? (:resize-vector modifiers)) (dom/query base-node (dm/str "#shape-" id)))))
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]))
(defn get-nodes (defn get-nodes
"Retrieve the DOM nodes to apply the matrix transformation" "Retrieve the DOM nodes to apply the matrix transformation"
[base-node {:keys [id type masked-group?] :as shape}] [base-node {:keys [id type masked-group?] :as shape}]
(when (some? base-node) (when (some? base-node)
(let [shape-node (if (= (.-id base-node) (dm/str "shape-" id)) (let [shape-node (get-shape-node base-node id)
base-node
(dom/query base-node (dm/str "#shape-" id)))
frame? (= :frame type) frame? (= :frame type)
group? (= :group type) group? (= :group type)
@ -110,8 +59,7 @@
(dom/query-all shape-defs ".svg-mask-wrapper"))) (dom/query-all shape-defs ".svg-mask-wrapper")))
text? text?
[shape-node [shape-node]
(dom/query shape-node ".text-container")]
:else :else
[shape-node])))) [shape-node]))))
@ -177,37 +125,20 @@
(defn update-transform! (defn update-transform!
[base-node shapes transforms modifiers] [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)] (when-let [nodes (get-nodes base-node shape)]
(let [transform (get transforms id) (let [transform (get transforms id)
modifiers (get-in modifiers [id :modifiers]) modifiers (get-in modifiers [id :modifiers])]
text? (= type :text)
transform-text? (and text? (and (nil? (:resize-vector modifiers)) (nil? (:resize-vector-2 modifiers))))]
(doseq [node nodes] (doseq [node nodes]
(cond (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") (dom/class? node "frame-children")
(set-transform-att! node "transform" (gmt/inverse transform)) (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") (dom/class? node "frame-title")
(let [shape (-> shape (assoc :modifiers modifiers) gsh/transform-shape) (let [shape (gsh/transform-shape shape modifiers)
zoom (get-in @st/state [:workspace-local :zoom] 1) zoom (get-in @st/state [:workspace-local :zoom] 1)
mtx (vwu/title-transform shape zoom)] mtx (vwu/title-transform shape zoom)]
(override-transform-att! node "transform" mtx)) (override-transform-att! node "transform" mtx))
(or (= (dom/get-tag-name node) "mask") (or (= (dom/get-tag-name node) "mask")
@ -221,7 +152,7 @@
(= (dom/get-tag-name node) "pattern") (= (dom/get-tag-name node) "pattern")
(set-transform-att! node "patternTransform" transform) (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))))))) (set-transform-att! node "transform" transform)))))))
(defn remove-transform! (defn remove-transform!
@ -249,6 +180,16 @@
(dom/remove-attribute! node "data-old-transform") (dom/remove-attribute! node "data-old-transform")
(dom/remove-attribute! node "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 (defn use-dynamic-modifiers
[objects node modifiers] [objects node modifiers]
@ -259,41 +200,71 @@
(when (some? modifiers) (when (some? modifiers)
(d/mapm (fn [id {modifiers :modifiers}] (d/mapm (fn [id {modifiers :modifiers}]
(let [shape (get objects id) (let [shape (get objects id)
center (gsh/center-shape shape) adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? modifiers)))
modifiers (cond-> modifiers modifiers (cond-> modifiers adapt-text? (adapt-text-modifiers shape))]
;; For texts we only use the displacement because (ctm/modifiers->transform modifiers)))
;; resize needs to recalculate the text layout
(= :text (:type shape))
(select-keys [:displacement :rotation]))]
(gsh/modifiers->transform center modifiers)))
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 shapes
(mf/use-memo (mf/use-memo
(mf/deps transforms) (mf/deps transforms)
(fn [] (fn []
(->> (keys transforms) (->> (keys transforms)
(filter #(some? (get transforms %)))
(mapv (d/getf objects))))) (mapv (d/getf objects)))))
prev-shapes (mf/use-var nil) prev-shapes (mf/use-var nil)
prev-modifiers (mf/use-var nil) prev-modifiers (mf/use-var nil)
prev-transforms (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/use-layout-effect
(mf/deps transforms) (mf/deps transforms)
(fn [] (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?) (let [curr-shapes-set (into #{} (map :id) shapes)
(start-transform! node 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)) (update-transform! node shapes transforms modifiers))
(when (and is-prev-val? (not is-cur-val?)) (when (d/not-empty? removed-shapes)
(remove-transform! node @prev-shapes)) (remove-transform! node @prev-shapes)))
(reset! prev-modifiers modifiers) (reset! prev-modifiers modifiers)
(reset! prev-transforms transforms) (reset! prev-transforms transforms)
(reset! prev-shapes shapes)))))) (reset! prev-shapes shapes)))))

View File

@ -199,6 +199,8 @@
(mf/use-effect (mf/use-effect
(mf/deps disable?) (mf/deps disable?)
(fn [] (fn []
(when (and disable? (not @disable-ref?))
(rx/push! updates-str :update))
(reset! disable-ref? disable?))) (reset! disable-ref? disable?)))
(mf/use-effect (mf/use-effect

View File

@ -6,6 +6,8 @@
(ns app.main.ui.workspace.shapes.path.editor (ns app.main.ui.workspace.shapes.path.editor
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.path.commands :as upc] [app.common.path.commands :as upc]
@ -183,8 +185,9 @@
matches (concat (second (:x snap-matches)) (second (:y snap-matches)))] matches (concat (second (:x snap-matches)) (second (:y snap-matches)))]
[:g.snap-paths [:g.snap-paths
(for [[from to] matches] (for [[idx [from to]] (d/enumerate matches)]
[:line {:x1 (:x from) [:line {:key (dm/str "snap-" idx "-" from "-" to)
:x1 (:x from)
:y1 (:y from) :y1 (:y from)
:x2 (:x to) :x2 (:x to)
:y2 (:y to) :y2 (:y to)
@ -308,7 +311,7 @@
:start-path? start-p? :start-path? start-p?
:zoom zoom}]]) :zoom zoom}]])
(for [position points] (for [[index position] (d/enumerate points)]
(let [show-handler? (let [show-handler?
(fn [[index prefix]] (fn [[index prefix]]
(let [handler-position (upc/handler->point content index prefix)] (let [handler-position (upc/handler->point content index prefix)]
@ -322,7 +325,7 @@
pos-handlers (->> pos-handlers (filter show-handler?)) pos-handlers (->> pos-handlers (filter show-handler?))
curve? (boolean (seq pos-handlers))] 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")} [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
(for [[index prefix] pos-handlers] (for [[index prefix] pos-handlers]
(let [handler-position (upc/handler->point content index prefix) (let [handler-position (upc/handler->point content index prefix)

View File

@ -58,7 +58,7 @@
:height height :height height
:style {:fill "none" :stroke "red"}}] :style {:fill "none" :stroke "red"}}]
;; Text baselineazo ;; Text baseline
[:line {:x1 (mth/round x) [:line {:x1 (mth/round x)
:y1 (mth/round (- (:y data) (:height data))) :y1 (mth/round (- (:y data) (:height data)))
:x2 (mth/round (+ x width)) :x2 (mth/round (+ x width))

View File

@ -8,11 +8,13 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.text :as gsht] [app.common.geom.shapes.text :as gsht]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.modifiers :as ctm]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -31,22 +33,24 @@
(-> shape (-> shape
(cond-> (some? (meta (:position-data shape))) (cond-> (some? (meta (:position-data shape)))
(with-meta (meta (:position-data shape)))) (with-meta (meta (:position-data shape))))
(dissoc :position-data :transform :transform-inverse))) (dissoc :position-data)))
(defn strip-modifier (defn fix-position [shape modifier]
[modifier] (let [shape' (-> shape
(if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) (assoc :grow-type :fixed)
(some? (dm/get-in modifier [:modifiers :resize-vector-2]))) (gsh/transform-shape modifier))
modifier
(d/update-when modifier :modifiers dissoc :displacement :rotation))) 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}] (defn process-shape [modifiers {:keys [id] :as shape}]
(let [modifier (-> (get modifiers id) strip-modifier) (let [modifier (dm/get-in modifiers [id :modifiers])]
shape (cond-> shape
(not (gsh/empty-modifiers? (:modifiers modifier)))
(-> (assoc :grow-type :fixed)
(merge modifier) gsh/transform-shape))]
(-> shape (-> shape
(cond-> (and (some? modifier)
(not (ctm/only-move? modifier)))
(fix-position modifier))
(cond-> (nil? (:position-data shape)) (cond-> (nil? (:position-data shape))
(assoc :migrate true)) (assoc :migrate true))
strip-position-data))) strip-position-data)))
@ -88,20 +92,26 @@
(defn- update-text-modifier (defn- update-text-modifier
[{:keys [grow-type id]} node] [{:keys [grow-type id]} node]
(p/let [position-data (tsp/calc-position-data id) (ts/raf
props {:position-data position-data} #(p/let [position-data (tsp/calc-position-data id)
props {:position-data position-data}
props props
(if (contains? #{:auto-height :auto-width} grow-type) (if (contains? #{:auto-height :auto-width} grow-type)
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
width (mth/ceil width) width (mth/ceil width)
height (mth/ceil height)] height (mth/ceil height)]
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
(assoc props :width width :height height) (cond-> props
props)) (= grow-type :auto-width)
props)] (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/defc text-container
{::mf/wrap-props false {::mf/wrap-props false
@ -136,8 +146,8 @@
(fn [id] (fn [id]
(let [new-shape (get text-shapes id) (let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id) old-shape (get prev-text-shapes id)
old-modifiers (-> (get prev-modifiers id) strip-modifier) old-modifiers (get prev-modifiers id)
new-modifiers (-> (get modifiers id) strip-modifier) new-modifiers (get modifiers id)
remote? (some? (-> new-shape meta :session-id)) ] remote? (some? (-> new-shape meta :session-id)) ]
@ -159,10 +169,9 @@
handle-update-modifier (mf/use-callback update-text-modifier) handle-update-modifier (mf/use-callback update-text-modifier)
handle-update-shape (mf/use-callback update-text-shape)] handle-update-shape (mf/use-callback update-text-shape)]
[:* [:*
(for [{:keys [id] :as shape} changed-texts] (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))) :on-update (if (some? (get modifiers (:id shape)))
handle-update-modifier handle-update-modifier
handle-update-shape) handle-update-shape)

View File

@ -37,35 +37,35 @@
(mf/defc shape-options (mf/defc shape-options
{::mf/wrap [#(mf/throttle % 60)]} {::mf/wrap [#(mf/throttle % 60)]}
[{:keys [shape shapes-with-children page-id file-id shared-libs]}] [{:keys [shape shapes-with-children page-id file-id shared-libs]}]
[:* (let [workspace-modifiers (mf/deref refs/workspace-modifiers)
(case (:type shape) modifiers (get-in workspace-modifiers [(:id shape) :modifiers])
:frame [:& frame/options {:shape shape}] shape (gsh/transform-shape shape modifiers)]
: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}] (case (:type shape)
:rect [:& rect/options {:shape shape}] :frame [:& frame/options {:shape shape}]
:circle [:& circle/options {:shape shape}] :group [:& group/options {:shape shape :shape-with-children shapes-with-children :file-id file-id :shared-libs shared-libs}]
:path [:& path/options {:shape shape}] :text [:& text/options {:shape shape :file-id file-id :shared-libs shared-libs}]
:image [:& image/options {:shape shape}] :rect [:& rect/options {:shape shape}]
:svg-raw [:& svg-raw/options {:shape shape}] :circle [:& circle/options {:shape shape}]
:bool [:& bool/options {:shape shape}] :path [:& path/options {:shape shape}]
nil) :image [:& image/options {:shape shape}]
[:& exports-menu :svg-raw [:& svg-raw/options {:shape shape}]
{:ids [(:id shape)] :bool [:& bool/options {:shape shape}]
:values (select-keys shape [:exports]) nil)
:shape shape [:& exports-menu
:page-id page-id {:ids [(:id shape)]
:file-id file-id}]]) :values (select-keys shape [:exports])
:shape shape
:page-id page-id
:file-id file-id}]]))
(mf/defc options-content (mf/defc options-content
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [selected section shapes shapes-with-children page-id file-id]}] [{:keys [selected section shapes shapes-with-children page-id file-id]}]
(let [drawing (mf/deref refs/workspace-drawing) (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) shared-libs (mf/deref refs/workspace-libraries)
modifiers (mf/deref refs/workspace-modifiers) selected-shapes (into [] (keep (d/getf objects)) selected)]
objects-modified (mf/with-memo [base-objects modifiers]
(gsh/merge-modifiers base-objects modifiers))
selected-shapes (into [] (keep (d/getf objects-modified)) selected)]
[:div.tool-window [:div.tool-window
[:div.tool-window-content [:div.tool-window-content
[:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %)) [:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %))

View File

@ -30,7 +30,6 @@
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
shapes (as-> old-shapes $ shapes (as-> old-shapes $
(map gsh/transform-shape $)
(map gsh/translate-to-frame $ frames)) (map gsh/translate-to-frame $ frames))
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)] values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]

View File

@ -21,9 +21,9 @@
: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-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number} :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-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-wrap-type ;; :wrap, :no-wrap
:layout-padding-type ;; :simple, :multiple :layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@ -37,13 +37,13 @@
:start i/align-items-column-start :start i/align-items-column-start
:end i/align-items-column-end :end i/align-items-column-end
:center i/align-items-column-center :center i/align-items-column-center
:strech i/align-items-column-strech :stretch i/align-items-column-strech
:baseline i/align-items-column-baseline) :baseline i/align-items-column-baseline)
(case val (case val
:start i/align-items-row-start :start i/align-items-row-start
:end i/align-items-row-end :end i/align-items-row-end
:center i/align-items-row-center :center i/align-items-row-center
:strech i/align-items-row-strech :stretch i/align-items-row-strech
:baseline i/align-items-row-baseline)) :baseline i/align-items-row-baseline))
:justify-content (if is-col? :justify-content (if is-col?
(case val (case val
@ -66,7 +66,7 @@
:center i/align-content-column-center :center i/align-content-column-center
:space-around i/align-content-column-around :space-around i/align-content-column-around
:space-between i/align-content-column-between :space-between i/align-content-column-between
:strech nil) :stretch nil)
(case val (case val
:start i/align-content-row-start :start i/align-content-row-start
@ -74,21 +74,21 @@
:center i/align-content-row-center :center i/align-content-row-center
:space-around i/align-content-row-around :space-around i/align-content-row-around
:space-between i/align-content-row-between :space-between i/align-content-row-between
:strech nil)) :stretch nil))
:align-self (if is-col? :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 (case val
:start i/align-self-row-left :start i/align-self-row-left
:end i/align-self-row-right :end i/align-self-row-right
:center i/align-self-row-center :center i/align-self-row-center
:strech i/align-self-row-strech :stretch i/align-self-row-strech
:baseline i/align-self-row-baseline)))) :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 (mf/defc direction-btn
[{:keys [dir saved-dir set-direction] :as props}] [{:keys [dir saved-dir set-direction] :as props}]
@ -129,7 +129,7 @@
[{:keys [is-col? align-items set-align] :as props}] [{:keys [is-col? align-items set-align] :as props}]
[:div.align-items-style [:div.align-items-style
(for [align [:start :center :end :strech]] (for [align [:start :center :end #_:stretch #_:baseline]]
[:button.align-start.tooltip [:button.align-start.tooltip
{:class (dom/classnames :active (= align-items align) {:class (dom/classnames :active (= align-items align)
:tooltip-bottom-left (not= align :start) :tooltip-bottom-left (not= align :start)
@ -170,7 +170,8 @@
[{:keys [values on-change-style on-change] :as props}] [{:keys [values on-change-style on-change] :as props}]
(let [padding-type (:layout-padding-type values) (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)) (:p1 (:layout-padding values))
"--")] "--")]
@ -274,13 +275,14 @@
(st/emit! (dwsl/remove-layout ids)) (st/emit! (dwsl/remove-layout ids))
(reset! open? false)) (reset! open? false))
set-flex (fn [] ;; Uncomment when activating the grid options
(st/emit! (dwsl/remove-layout ids)) ;; set-flex (fn []
(on-add-layout :flex)) ;; (st/emit! (dwsl/remove-layout ids))
;; (on-add-layout :flex))
set-grid (fn [] ;;
(st/emit! (dwsl/remove-layout ids)) ;; set-grid (fn []
(on-add-layout :grid)) ;; (st/emit! (dwsl/remove-layout ids))
;; (on-add-layout :grid))
;; Flex-direction ;; Flex-direction
@ -312,7 +314,7 @@
align-content (:layout-align-content values) align-content (:layout-align-content values)
set-align-content (fn [value] set-align-content (fn [value]
(if (= align-content 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})))) (st/emit! (dwsl/update-layout ids {:layout-align-content value}))))
;; Gap ;; Gap
@ -365,7 +367,7 @@
[:span "Layout"] [:span "Layout"]
(if (:layout values) (if (:layout values)
[:div.title-actions [:div.title-actions
[:div.layout-btns #_[:div.layout-btns
[:button {:on-click set-flex [:button {:on-click set-flex
:class (dom/classnames :class (dom/classnames
:active (= :flex layout-type))} "Flex"] :active (= :flex layout-type))} "Flex"]

View File

@ -8,7 +8,9 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -26,13 +28,13 @@
:layout-item-min-h ;; num :layout-item-min-h ;; num
:layout-item-max-w ;; num :layout-item-max-w ;; num
:layout-item-min-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 (mf/defc margin-section
[{:keys [values change-margin-style on-margin-change] :as props}] [{: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-row
[:div.margin-icons [:div.margin-icons
@ -57,7 +59,7 @@
{:placeholder "--" {:placeholder "--"
:on-click #(dom/select-target %) :on-click #(dom/select-target %)
:on-change (partial on-margin-change :simple) :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) (= margin-type :multiple)
(for [num [:m1 :m2 :m3 :m4]] (for [num [:m1 :m2 :m3 :m4]]
@ -73,95 +75,100 @@
{:placeholder "--" {:placeholder "--"
:on-click #(dom/select-target %) :on-click #(dom/select-target %)
:on-change (partial on-margin-change num) :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 (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? (let [fill? is-layout-child?
auto? is-layout-container?] auto? is-layout-container?]
[:div.btn-wrapper [:div.btn-wrapper
[:div.layout-behavior.horizontal [:div.layout-behavior.horizontal
[:button.behavior-btn.tooltip.tooltip-bottom [:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fix width" {: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)} :on-click #(on-change-behavior :h :fix)}
i/auto-fix-layout] i/auto-fix-layout]
(when fill? (when fill?
[:button.behavior-btn.tooltip.tooltip-bottom [:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Width 100%" {: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)} :on-click #(on-change-behavior :h :fill)}
i/auto-fill]) i/auto-fill])
(when auto? (when auto?
[:button.behavior-btn.tooltip.tooltip-bottom [:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fit content" {: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)} :on-click #(on-change-behavior :h :auto)}
i/auto-hug])] i/auto-hug])]
[:div.layout-behavior [:div.layout-behavior
[:button.behavior-btn.tooltip.tooltip-bottom [:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Fix height" {: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)} :on-click #(on-change-behavior :v :fix)}
i/auto-fix-layout] i/auto-fix-layout]
(when fill? (when fill?
[:button.behavior-btn.tooltip.tooltip-bottom [:button.behavior-btn.tooltip.tooltip-bottom
{:alt "Height 100%" {: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)} :on-click #(on-change-behavior :v :fill)}
i/auto-fill]) i/auto-fill])
(when auto? (when auto?
[:button.behavior-btn.tooltip.tooltip-bottom-left [:button.behavior-btn.tooltip.tooltip-bottom-left
{:alt "Fit content" {: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)} :on-click #(on-change-behavior :v :auto)}
i/auto-hug])]])) i/auto-hug])]]))
(mf/defc align-self-row (mf/defc align-self-row
[{:keys [is-col? align-self set-align-self] :as props}] [{: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 [:div.align-self-style
(for [align dir-v] (for [align dir-v]
[:button.align-self.tooltip.tooltip-bottom [:button.align-self.tooltip.tooltip-bottom
{:class (dom/classnames :active (= align-self align) {:class (dom/classnames :active (= align-self align)
:tooltip-bottom-left (not= align :start) :tooltip-bottom-left (not= align :start)
:tooltip-bottom (= 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) :on-click #(set-align-self align)
:key (str "align-self" align)} :key (str "align-self" align)}
(get-layout-flex-icon :align-self align is-col?)])])) (get-layout-flex-icon :align-self align is-col?)])]))
(mf/defc layout-item-menu (mf/defc layout-item-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} {::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) (let [open? (mf/use-state false)
toggle-open (fn [] (swap! open? not)) 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 change-margin-style
(fn [type] (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] 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? (every? ctl/col? selection-parents)
is-col? (or (= :column saved-dir) (= :reverse-column saved-dir))
on-margin-change on-margin-change
(fn [type val] (fn [type val]
(if (= type :simple) (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-item-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 {type val}}))))
on-change-behavior on-change-behavior
(fn [dir value] (fn [dir value]
(if (= dir :h) (if (= dir :h)
(st/emit! (dwsl/update-layout-child ids {:layout-h-behavior value})) (st/emit! (dwsl/update-layout-child ids {:layout-item-h-sizing value}))
(st/emit! (dwsl/update-layout-child ids {:layout-v-behavior value})))) (st/emit! (dwsl/update-layout-child ids {:layout-item-v-sizing value}))))
on-size-change on-size-change
(fn [measure value] (fn [measure value]
@ -176,14 +183,16 @@
[:div.row-title "Sizing"] [:div.row-title "Sizing"]
[:& element-behavior {:is-layout-child? is-layout-child? [:& element-behavior {:is-layout-child? is-layout-child?
:is-layout-container? is-layout-container? :is-layout-container? is-layout-container?
:layout-v-behavior (or (:layout-v-behavior values) :fix) :layout-item-v-sizing (or (:layout-item-v-sizing values) :fix)
:layout-h-behavior (or (:layout-h-behavior values) :fix) :layout-item-h-sizing (or (:layout-item-h-sizing values) :fix)
:on-change-behavior on-change-behavior}]] :on-change-behavior on-change-behavior}]]
[:& margin-section {:values values (when is-layout-child?
:change-margin-style change-margin-style [:& margin-section {:values values
:on-margin-change on-margin-change}] :change-margin-style change-margin-style
:on-margin-change on-margin-change}])
[:div.advanced-ops-container [:div.advanced-ops-container
[:button.advanced-ops.toltip.tooltip-bottom [:button.advanced-ops.toltip.tooltip-bottom
{:on-click toggle-open {:on-click toggle-open
@ -193,21 +202,22 @@
(when @open? (when @open?
[:div.advanced-ops-body [:div.advanced-ops-body
[:div.layout-row (when is-layout-child?
[:div.direction-wrap.row-title "Align"] [:div.layout-row
[:div.btn-wrapper [:div.direction-wrap.row-title "Align"]
[:& align-self-row {:is-col? is-col? [:div.btn-wrapper
:align-self align-self [:& align-self-row {:is-col? is-col?
:set-align-self set-align-self}]]] :align-self align-self
:set-align-self set-align-self}]]])
[:div.input-wrapper [: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 [:div.tooltip.tooltip-bottom
{:key (d/name item) {:key (d/name item)
:alt (tr (dm/str "workspace.options.layout-item.title." (d/name item))) :alt (tr (dm/str "workspace.options.layout-item.title." (d/name item)))
:class (dom/classnames "maxH" (= item :layout-max-h) :class (dom/classnames "maxH" (= item :layout-item-max-h)
"minH" (= item :layout-min-h) "minH" (= item :layout-item-min-h)
"maxW" (= item :layout-max-w) "maxW" (= item :layout-item-max-w)
"minW" (= item :layout-min-w))} "minW" (= item :layout-item-min-w))}
[:div.input-element [:div.input-element
{:alt (tr (dm/str "workspace.options.layout-item." (d/name item)))} {:alt (tr (dm/str "workspace.options.layout-item." (d/name item)))}
[:> numeric-input [:> numeric-input
@ -217,6 +227,5 @@
:placeholder "--" :placeholder "--"
:on-click #(dom/select-target %) :on-click #(dom/select-target %)
:on-change (partial on-size-change item) :on-change (partial on-size-change item)
;; :value (get values item) :value (get values item)
:value 100}]]])]])]] :nillable true}]]])]])]]))
))

View File

@ -8,6 +8,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.types.shape.layout :as ctl]
[app.common.types.shape.radius :as ctsr] [app.common.types.shape.radius :as ctsr]
[app.main.constants :refer [size-presets]] [app.main.constants :refer [size-presets]]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
@ -79,11 +80,21 @@
[shape]) [shape])
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) 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 ;; To show interactively the measures while the user is manipulating
;; the shape with the mouse, generate a copy of the shapes applying ;; the shape with the mouse, generate a copy of the shapes applying
;; the transient transformations. ;; the transient transformations.
shapes (as-> old-shapes $ shapes (as-> old-shapes $
(map gsh/transform-shape $)
(map gsh/translate-to-frame $ frames)) (map gsh/translate-to-frame $ frames))
;; For rotated or stretched shapes, the origin point we show in the menu ;; For rotated or stretched shapes, the origin point we show in the menu
@ -296,6 +307,7 @@
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-width-change :on-change on-width-change
:disabled (and (or flex-child? flex-container?) (or flex-auto-width? flex-fill-width?))
:value (:width values)}]] :value (:width values)}]]
[:div.input-element.height {:title (tr "workspace.options.height")} [:div.input-element.height {:title (tr "workspace.options.height")}
@ -304,6 +316,7 @@
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-height-change :on-change on-height-change
:disabled (and (or flex-child? flex-container?) (or flex-auto-height? flex-fill-height?))
:value (:height values)}]] :value (:height values)}]]
[:div.lock-size {:class (dom/classnames [:div.lock-size {:class (dom/classnames
@ -323,11 +336,13 @@
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:on-change on-pos-x-change :on-change on-pos-x-change
:disabled flex-child?
:value (:x values)}]] :value (:x values)}]]
[:div.input-element.Yaxis {:title (tr "workspace.options.y")} [:div.input-element.Yaxis {:title (tr "workspace.options.y")}
[:> numeric-input {:no-validate true [:> numeric-input {:no-validate true
:placeholder "--" :placeholder "--"
:on-click select-all :on-click select-all
:disabled flex-child?
:on-change on-pos-y-change :on-change on-pos-y-change
:value (:y values)}]]]) :value (:y values)}]]])
@ -441,5 +456,3 @@
(tr "workspace.options.show-in-viewer")]]) (tr "workspace.options.show-in-viewer")]])
]]])) ]]]))

View File

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.rows.stroke-row (ns app.main.ui.workspace.sidebar.options.rows.stroke-row
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.hooks :as h] [app.main.ui.hooks :as h]
@ -132,9 +133,10 @@
:on-close (close-caps-select start-caps-state)} :on-close (close-caps-select start-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state) [:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state)
:left (:left @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)] (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)} :on-click #(on-stroke-cap-start-change index value)}
(when img [:img {:src (value->img value)}]) (when img [:img {:src (value->img value)}])
label]))]] label]))]]
@ -151,9 +153,10 @@
:on-close (close-caps-select end-caps-state)} :on-close (close-caps-select end-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state) [:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state)
:left (:left @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)] (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)} :on-click #(on-stroke-cap-end-change index value)}
(when img [:img {:src (value->img value)}]) (when img [:img {:src (value->img value)}])
label]))]]])])) label]))]]])]))

View File

@ -41,8 +41,10 @@
:values layout-item-values :values layout-item-values
:is-layout-child? true :is-layout-child? true
:shape shape}]) :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 [:& layer-menu {:ids ids
:type type :type type
:values layer-values}] :values layer-values}]

View File

@ -43,8 +43,9 @@
:is-layout-child? true :is-layout-child? true
:is-layout-container? false :is-layout-container? false
:shape shape}]) :shape shape}])
[:& constraints-menu {:ids ids (when (not is-layout-child?)
:values constraint-values}] [:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids [:& layer-menu {:ids ids
:type type :type type
:values layer-values}] :values layer-values}]

View File

@ -6,6 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.frame (ns app.main.ui.workspace.sidebar.options.shapes.frame
(:require (:require
[app.common.types.shape.layout :as ctl]
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
@ -35,26 +36,27 @@
layout-item-values (select-keys shape layout-item-attrs) 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-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)] [:& measures-menu {:ids [(:id shape)]
:values measure-values :values measure-values
:type type :type type
:shape shape}] :shape shape}]
[:& constraints-menu {:ids ids (when (not is-layout-child?)
:values constraint-values}] [:& constraints-menu {:ids ids
(when layout-active? :values constraint-values}])
[:* (when (or layout-active? is-layout-container?)
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}] [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}])
(when (or (:layout shape) is-layout-child?) (when (and layout-active? (or is-layout-child? is-layout-container?))
[:& layout-item-menu [:& layout-item-menu
{:ids ids {:ids ids
:type type :type type
:values layout-item-values :values layout-item-values
:is-layout-child? is-layout-child? :is-layout-child? is-layout-child?
:is-layout-container? (:layout shape) :is-layout-container? is-layout-container?
:shape shape}])]) :shape shape}])
[:& layer-menu {:ids ids [:& layer-menu {:ids ids
:type type :type type

View File

@ -62,7 +62,8 @@
:is-layout-container? false :is-layout-container? false
:values layout-item-values}]) :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}] [:& layer-menu {:type type :ids layer-ids :values layer-values}]
(when-not (empty? fill-ids) (when-not (empty? fill-ids)

View File

@ -44,8 +44,9 @@
:is-layout-child? true :is-layout-child? true
:shape shape}]) :shape shape}])
[:& constraints-menu {:ids ids (when (not is-layout-child?)
:values constraint-values}] [:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids [:& layer-menu {:ids ids
:type type :type type

View File

@ -34,116 +34,134 @@
;; - text: read it from all the content nodes, and then merging it. ;; - text: read it from all the content nodes, and then merging it.
(def type->read-mode (def type->read-mode
{:frame {:frame
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :children :shadow :children
:blur :children :blur :children
:stroke :shape :stroke :shape
:text :children :text :children
:exports :shape} :exports :shape
:layout-container :shape
:layout-item :shape}
:group :group
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :children :fill :children
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :children :stroke :children
:text :children :text :children
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:path :path
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :ignore :text :ignore
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:text :text
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :text :fill :text
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :text :text :text
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:image :image
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :ignore :fill :ignore
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :ignore :stroke :ignore
:text :ignore :text :ignore
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:rect :rect
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :ignore :text :ignore
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:circle :circle
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :ignore :text :ignore
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:svg-raw :svg-raw
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :ignore :text :ignore
:exports :shape} :exports :shape
:layout-container :ignore
:layout-item :shape}
:bool :bool
{:measure :shape {:measure :shape
:layer :shape :layer :shape
:constraint :shape :constraint :shape
:fill :shape :fill :shape
:shadow :shape :shadow :shape
:blur :shape :blur :shape
:stroke :shape :stroke :shape
:text :ignore :text :ignore
:exports :shape}}) :exports :shape
:layout-container :ignore
:layout-item :shape}})
(def group->attrs (def group->attrs
{:measure measure-attrs {:measure measure-attrs
:layer layer-attrs :layer layer-attrs
:constraint constraint-attrs :constraint constraint-attrs
:fill fill-attrs :fill fill-attrs
:shadow shadow-attrs :shadow shadow-attrs
:blur blur-attrs :blur blur-attrs
:stroke stroke-attrs :stroke stroke-attrs
:text ot/attrs :text ot/attrs
:exports exports-attrs :exports exports-attrs
:layout layout-container-flex-attrs :layout-container layout-container-flex-attrs
:layout-item layout-item-attrs}) :layout-item layout-item-attrs})
(def shadow-keys [:style :color :offset-x :offset-y :blur :spread]) (def shadow-keys [:style :color :offset-x :offset-y :blur :spread])
@ -235,6 +253,10 @@
[props] [props]
(let [shapes (unchecked-get props "shapes") (let [shapes (unchecked-get props "shapes")
shapes-with-children (unchecked-get props "shapes-with-children") 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") page-id (unchecked-get props "page-id")
file-id (unchecked-get props "file-id") file-id (unchecked-get props "file-id")
shared-libs (unchecked-get props "shared-libs") shared-libs (unchecked-get props "shared-libs")
@ -258,16 +280,16 @@
[measure-ids measure-values] (get-attrs shapes objects :measure) [measure-ids measure-values] (get-attrs shapes objects :measure)
[layer-ids layer-values [layer-ids layer-values
constraint-ids constraint-values constraint-ids constraint-values
fill-ids fill-values fill-ids fill-values
shadow-ids shadow-values shadow-ids shadow-values
blur-ids blur-values blur-ids blur-values
stroke-ids stroke-values stroke-ids stroke-values
text-ids text-values text-ids text-values
exports-ids exports-values exports-ids exports-values
layout-ids layout-container-values layout-container-ids layout-container-values
layout-item-ids layout-item-values] layout-item-ids layout-item-values]
(mf/use-memo (mf/use-memo
(mf/deps objects-no-measures) (mf/deps objects-no-measures)
(fn [] (fn []
@ -282,7 +304,7 @@
(get-attrs shapes objects-no-measures :stroke) (get-attrs shapes objects-no-measures :stroke)
(get-attrs shapes objects-no-measures :text) (get-attrs shapes objects-no-measures :text)
(get-attrs shapes objects-no-measures :exports) (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) (get-attrs shapes objects-no-measures :layout-item)
])))] ])))]
@ -291,7 +313,7 @@
[:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}]) [:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}])
(when (:layout layout-container-values) (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? (when is-layout-child?
[:& layout-item-menu [:& layout-item-menu
@ -301,7 +323,7 @@
:is-layout-container? true :is-layout-container? true
:values layout-item-values}]) :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}]) [:& constraints-menu {:ids constraint-ids :values constraint-values}])
(when-not (empty? layer-ids) (when-not (empty? layer-ids)

View File

@ -43,8 +43,9 @@
:is-layout-child? true :is-layout-child? true
:is-layout-container? false :is-layout-container? false
:shape shape}]) :shape shape}])
[:& constraints-menu {:ids ids (when (not is-layout-child?)
:values constraint-values}] [:& constraints-menu {:ids ids
:values constraint-values}])
[:& layer-menu {:ids ids [:& layer-menu {:ids ids
:type type :type type
:values layer-values}] :values layer-values}]

View File

@ -44,8 +44,10 @@
:values layout-item-values :values layout-item-values
:is-layout-child? true :is-layout-child? true
:shape shape}]) :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 [:& layer-menu {:ids ids
:type type :type type

View File

@ -120,8 +120,9 @@
:is-layout-child? true :is-layout-child? true
:shape shape}]) :shape shape}])
[:& constraints-menu {:ids ids (when (not is-layout-child?)
:values constraint-values}] [:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids [:& fill-menu {:ids ids
:type type :type type

View File

@ -78,9 +78,11 @@
:values layout-item-values :values layout-item-values
:is-layout-child? true :is-layout-child? true
:shape shape}]) :shape shape}])
[:& constraints-menu
{:ids ids (when (not is-layout-child?)
:values (select-keys shape constraint-attrs)}] [:& constraints-menu
{:ids ids
:values (select-keys shape constraint-attrs)}])
[:& layer-menu {:ids ids [:& layer-menu {:ids ids
:type type :type type

View File

@ -23,6 +23,7 @@
[app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh] [app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh]
[app.main.ui.workspace.viewport.actions :as actions] [app.main.ui.workspace.viewport.actions :as actions]
[app.main.ui.workspace.viewport.comments :as comments] [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.drawarea :as drawarea]
[app.main.ui.workspace.viewport.frame-grid :as frame-grid] [app.main.ui.workspace.viewport.frame-grid :as frame-grid]
[app.main.ui.workspace.viewport.gradients :as gradients] [app.main.ui.workspace.viewport.gradients :as gradients]
@ -79,8 +80,7 @@
modifiers (mf/deref refs/workspace-modifiers) modifiers (mf/deref refs/workspace-modifiers)
objects-modified (mf/with-memo [base-objects 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) background (get options :background clr/canvas)
;; STATE ;; STATE
@ -91,6 +91,7 @@
hover-ids (mf/use-state nil) hover-ids (mf/use-state nil)
hover (mf/use-state nil) hover (mf/use-state nil)
hover-disabled? (mf/use-state false) hover-disabled? (mf/use-state false)
hover-top-frame-id (mf/use-state nil)
frame-hover (mf/use-state nil) frame-hover (mf/use-state nil)
active-frames (mf/use-state #{}) active-frames (mf/use-state #{})
@ -187,7 +188,7 @@
(hooks/setup-viewport-size viewport-ref) (hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?)
(hooks/setup-keyboard alt? mod? space?) (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-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?) (hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
@ -285,7 +286,7 @@
(when show-frame-outline? (when show-frame-outline?
[:& outline/shape-outlines [:& outline/shape-outlines
{:objects base-objects {:objects objects-modified
:hover #{(->> @hover-ids :hover #{(->> @hover-ids
(filter #(cph/frame-shape? (get base-objects %))) (filter #(cph/frame-shape? (get base-objects %)))
(remove selected) (remove selected)
@ -336,10 +337,9 @@
(when show-prototypes? (when show-prototypes?
[:& widgets/frame-flows [:& widgets/frame-flows
{:flows (:flows options) {:flows (:flows options)
:objects base-objects :objects objects-modified
:selected selected :selected selected
:zoom zoom :zoom zoom
:modifiers modifiers
:on-frame-enter on-frame-enter :on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave :on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]) :on-frame-select on-frame-select}])
@ -348,8 +348,7 @@
[:& drawarea/draw-area [:& drawarea/draw-area
{:shape drawing-obj {:shape drawing-obj
:zoom zoom :zoom zoom
:tool drawing-tool :tool drawing-tool}])
:modifiers modifiers}])
(when show-grids? (when show-grids?
[:& frame-grid/frame-grid [:& frame-grid/frame-grid
@ -371,9 +370,8 @@
:zoom zoom :zoom zoom
:page-id page-id :page-id page-id
:selected selected :selected selected
:objects base-objects :objects objects-modified
:focus focus :focus focus}])
:modifiers modifiers}])
(when show-snap-distance? (when show-snap-distance?
[:& snap-distances/snap-distances [:& snap-distances/snap-distances
@ -416,9 +414,21 @@
{:zoom zoom {:zoom zoom
:vbox vbox :vbox vbox
:hover-frame frame-parent :hover-frame frame-parent
:modifiers modifiers
:disabled-guides? disabled-guides?}]) :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? (when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"} [:g.selection-handlers {:clipPath "url(#clip-handlers)"}
[:defs [:defs

View File

@ -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)]])]))))

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.viewport.drawarea (ns app.main.ui.workspace.viewport.drawarea
"Drawing components." "Drawing components."
(:require (:require
[app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.ui.shapes.path :refer [path-shape]] [app.main.ui.shapes.path :refer [path-shape]]
[app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes :as shapes]
@ -22,7 +21,7 @@
[:g.draw-area [:g.draw-area
[:g {:style {:pointer-events "none"}} [:g {:style {:pointer-events "none"}}
[:& shapes/shape-wrapper {:shape (gsh/transform-shape shape)}]] [:& shapes/shape-wrapper {:shape shape}]]
(case tool (case tool
:path [:& path-editor {:shape shape :zoom zoom}] :path [:& path-editor {:shape shape :zoom zoom}]
@ -31,7 +30,7 @@
(mf/defc generic-draw-area (mf/defc generic-draw-area
[{:keys [shape zoom]}] [{: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 (when (and x y
(not (mth/nan? x)) (not (mth/nan? x))
(not (mth/nan? y))) (not (mth/nan? y)))

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.viewport.frame-grid (ns app.main.ui.workspace.viewport.frame-grid
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -138,4 +137,4 @@
(or (empty? focus) (contains? focus (:id frame)))) (or (empty? focus) (contains? focus (:id frame))))
[:& grid-display-frame {:key (str "grid-" (:id frame)) [:& grid-display-frame {:key (str "grid-" (:id frame))
:zoom zoom :zoom zoom
:frame (gsh/transform-shape frame)}]))])) :frame frame}]))]))

View File

@ -278,7 +278,7 @@
frame]} (use-guide handle-change-position get-hover-frame zoom guide) frame]} (use-guide handle-change-position get-hover-frame zoom guide)
base-frame (or frame hover-frame) 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)) move-vec (gpt/to-vec (gpt/point (:x base-frame) (:y base-frame))
(gpt/point (:x frame) (:y frame))) (gpt/point (:x frame) (:y frame)))

View File

@ -11,6 +11,7 @@
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.common.uuid :as uuid]
[app.main.data.shortcuts :as dsc] [app.main.data.shortcuts :as dsc]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.path.shortcuts :as psc] [app.main.data.workspace.path.shortcuts :as psc]
@ -104,7 +105,8 @@
(some #(cph/is-parent? objects % group-id)) (some #(cph/is-parent? objects % group-id))
(not)))) (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 (let [;; We use ref so we don't recreate the stream on a change
zoom-ref (mf/use-ref zoom) zoom-ref (mf/use-ref zoom)
mod-ref (mf/use-ref @mod?) mod-ref (mf/use-ref @mod?)
@ -143,9 +145,10 @@
(rx/map #(deref last-point-ref))) (rx/map #(deref last-point-ref)))
(->> move-stream (->> move-stream
(rx/tap #(reset! last-point-ref %))
;; When transforming shapes we stop querying the worker ;; When transforming shapes we stop querying the worker
(rx/merge-map query-point) (rx/merge-map query-point)
(rx/tap #(reset! last-point-ref %))))))] ))))]
;; Refresh the refs on a value change ;; Refresh the refs on a value change
(mf/use-effect (mf/use-effect
@ -213,7 +216,8 @@
(first) (first)
(get objects))] (get objects))]
(reset! hover hover-shape) (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 (defn setup-viewport-modifiers
[modifiers objects] [modifiers objects]
@ -221,7 +225,7 @@
(mf/use-memo (mf/use-memo
(mf/deps objects) (mf/deps objects)
#(ctt/get-root-shapes-ids 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))) (sfd/use-dynamic-modifiers objects globals/document modifiers)))
(defn inside-vbox [vbox objects frame-id] (defn inside-vbox [vbox objects frame-id]

View File

@ -9,7 +9,6 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -254,13 +253,11 @@
(mf/defc interactions (mf/defc interactions
[{:keys [current-transform objects zoom selected hover-disabled?] :as props}] [{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
(let [active-shapes (into [] (let [active-shapes (into []
(comp (filter #(seq (:interactions %))) (comp (filter #(seq (:interactions %))))
(map gsh/transform-shape))
(vals objects)) (vals objects))
selected-shapes (into [] selected-shapes (into []
(comp (map (d/getf objects)) (map (d/getf objects))
(map gsh/transform-shape))
selected) selected)
{:keys [editing-interaction-index {:keys [editing-interaction-index

View File

@ -72,7 +72,7 @@
"var(--color-primary)" "var(--color-component-highlight)")] "var(--color-primary)" "var(--color-component-highlight)")]
(for [shape shapes] (for [shape shapes]
[:& outline {:key (str "outline-" (:id shape)) [:& outline {:key (str "outline-" (:id shape))
:shape (gsh/transform-shape shape) :shape shape
:zoom zoom :zoom zoom
:color color}]))) :color color}])))

View File

@ -341,7 +341,6 @@
(let [shape (mf/use-memo (let [shape (mf/use-memo
(mf/deps shapes) (mf/deps shapes)
#(->> shapes #(->> shapes
(map gsh/transform-shape)
(gsh/selection-rect) (gsh/selection-rect)
(cts/setup-shape))) (cts/setup-shape)))
on-resize on-resize
@ -369,7 +368,6 @@
(let [shape (mf/use-memo (let [shape (mf/use-memo
(mf/deps shapes) (mf/deps shapes)
#(->> shapes #(->> shapes
(map gsh/transform-shape)
(gsh/selection-rect) (gsh/selection-rect)
(cts/setup-shape)))] (cts/setup-shape)))]
@ -384,7 +382,6 @@
(mf/defc single-handlers (mf/defc single-handlers
[{:keys [shape zoom color disable-handlers] :as props}] [{:keys [shape zoom color disable-handlers] :as props}]
(let [shape-id (:id shape) (let [shape-id (:id shape)
shape (gsh/transform-shape shape)
on-resize on-resize
(fn [current-position _initial-position event] (fn [current-position _initial-position event]
@ -408,14 +405,13 @@
(mf/defc single-selection (mf/defc single-selection
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}] [{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}]
(let [shape (gsh/transform-shape shape)] [:& controls-selection
[:& controls-selection {:shape shape
{:shape shape :zoom zoom
:zoom zoom :color color
:color color :disable-handlers disable-handlers
:disable-handlers disable-handlers :on-move-selected on-move-selected
:on-move-selected on-move-selected :on-context-menu on-context-menu}])
:on-context-menu on-context-menu}]))
(mf/defc selection-area (mf/defc selection-area
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}

View File

@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.formats :as fmt] [app.main.ui.formats :as fmt]
[app.main.worker :as uw] [app.main.worker :as uw]
@ -272,18 +273,20 @@
frame-id (-> selected-shapes first :frame-id) frame-id (-> selected-shapes first :frame-id)
frame (mf/deref (refs/object-by-id frame-id)) frame (mf/deref (refs/object-by-id frame-id))
selrect (gsh/selection-rect selected-shapes)] selrect (gsh/selection-rect selected-shapes)]
[:g.distance
[:& shape-distance (when-not (ctl/layout? frame)
{:selrect selrect [:g.distance
:page-id page-id [:& shape-distance
:frame frame {:selrect selrect
:zoom zoom :page-id page-id
:coord :x :frame frame
:selected selected}] :zoom zoom
[:& shape-distance :coord :x
{:selrect selrect :selected selected}]
:page-id page-id [:& shape-distance
:frame frame {:selrect selrect
:zoom zoom :page-id page-id
:coord :y :frame frame
:selected selected}]])) :zoom zoom
:coord :y
:selected selected}]])))

View File

@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.main.snap :as snap] [app.main.snap :as snap]
[app.util.geom.snap-points :as sp] [app.util.geom.snap-points :as sp]
[beicon.core :as rx] [beicon.core :as rx]
@ -50,20 +51,14 @@
:opacity line-opacity}]) :opacity line-opacity}])
(defn get-snap (defn get-snap
[coord {:keys [shapes page-id remove-snap? zoom modifiers]}] [coord {:keys [shapes page-id remove-snap? zoom]}]
(let [shapes-sr (let [bounds (gsh/selection-rect shapes)
(->> shapes frame-id (snap/snap-frame-id shapes)]
;; Merge modifiers into shapes
(map #(merge % (get modifiers (:id %))))
;; Create the bounding rectangle for the shapes
(gsh/selection-rect))
frame-id (snap/snap-frame-id shapes)] (->> (rx/of bounds)
(->> (rx/of shapes-sr)
(rx/flat-map (rx/flat-map
(fn [selrect] (fn [bounds]
(->> (sp/selrect-snap-points selrect) (->> (sp/selrect-snap-points bounds)
(map #(vector frame-id %))))) (map #(vector frame-id %)))))
(rx/flat-map (rx/flat-map
@ -159,7 +154,7 @@
(mf/defc snap-points (mf/defc snap-points
{::mf/wrap [mf/memo]} {::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) (us/assert set? selected)
(let [shapes (into [] (keep (d/getf objects)) selected) (let [shapes (into [] (keep (d/getf objects)) selected)
@ -178,10 +173,11 @@
(and (= type :layout) (= grid :square)) (and (= type :layout) (= grid :square))
(= type :guide)))) (= type :guide))))
shapes (if drawing [drawing] shapes)] shapes (if drawing [drawing] shapes)
[:& snap-feedback {:shapes shapes frame-id (snap/snap-frame-id shapes)]
:page-id page-id (when-not (ctl/layout? objects frame-id)
:remove-snap? remove-snap? [:& snap-feedback {:shapes shapes
:zoom zoom :page-id page-id
:modifiers modifiers}])) :remove-snap? remove-snap?
:zoom zoom}])))

View File

@ -53,9 +53,7 @@
(defn text-transform (defn text-transform
[{:keys [x y]} zoom] [{:keys [x y]} zoom]
(let [inv-zoom (/ 1 zoom)] (let [inv-zoom (/ 1 zoom)]
(str (dm/fmt "scale(%, %) translate(%, %)" inv-zoom inv-zoom (* zoom x) (* zoom y))))
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom x) ", " (* zoom y) ")")))
(defn title-transform [frame zoom] (defn title-transform [frame zoom]
(let [frame-transform (gsh/transform-str frame {:no-flip true}) (let [frame-transform (gsh/transform-str frame {:no-flip true})

View File

@ -9,7 +9,6 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [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.path-actions :refer [path-actions]]
[app.main.ui.workspace.viewport.utils :as vwu] [app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom] [app.util.dom :as dom]
[debug :refer [debug?]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc pixel-grid (mf/defc pixel-grid
@ -35,8 +35,8 @@
:pattern-units "userSpaceOnUse"} :pattern-units "userSpaceOnUse"}
[:path {:d "M 1 0 L 0 0 0 1" [:path {:d "M 1 0 L 0 0 0 1"
:style {:fill "none" :style {:fill "none"
:stroke "var(--color-info)" :stroke (if (debug? :pixel-grid) "red" "var(--color-info)")
:stroke-opacity "0.2" :stroke-opacity (if (debug? :pixel-grid) 1 "0.2")
:stroke-width (str (/ 1 zoom))}}]]] :stroke-width (str (/ 1 zoom))}}]]]
[:rect {:x (:x vbox) [:rect {:x (:x vbox)
:y (:y vbox) :y (:y vbox)
@ -182,8 +182,8 @@
:on-frame-select on-frame-select}]))])) :on-frame-select on-frame-select}]))]))
(mf/defc frame-flow (mf/defc frame-flow
[{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}] [{:keys [flow frame selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
(let [{:keys [x y]} (gsh/transform-shape frame) (let [{:keys [x y]} frame
flow-pos (gpt/point x (- y (/ 35 zoom))) flow-pos (gpt/point x (- y (/ 35 zoom)))
on-mouse-down on-mouse-down
@ -217,9 +217,7 @@
:y -15 :y -15
:width 100000 :width 100000
:height 24 :height 24
:transform (str (when (and selected? modifiers) :transform (vwu/text-transform flow-pos zoom)}
(str (:displacement modifiers) " " ))
(vwu/text-transform flow-pos zoom))}
[:div.flow-badge {:class (dom/classnames :selected selected?)} [:div.flow-badge {:class (dom/classnames :selected selected?)}
[:div.content {:on-mouse-down on-mouse-down [:div.content {:on-mouse-down on-mouse-down
:on-double-click on-double-click :on-double-click on-double-click
@ -234,7 +232,6 @@
(let [flows (unchecked-get props "flows") (let [flows (unchecked-get props "flows")
objects (unchecked-get props "objects") objects (unchecked-get props "objects")
zoom (unchecked-get props "zoom") zoom (unchecked-get props "zoom")
modifiers (unchecked-get props "modifiers")
selected (or (unchecked-get props "selected") #{}) selected (or (unchecked-get props "selected") #{})
on-frame-enter (unchecked-get props "on-frame-enter") on-frame-enter (unchecked-get props "on-frame-enter")
@ -248,7 +245,6 @@
:frame frame :frame frame
:selected? (contains? selected (:id frame)) :selected? (contains? selected (:id frame))
:zoom zoom :zoom zoom
:modifiers modifiers
:on-frame-enter on-frame-enter :on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave :on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]))])) :on-frame-select on-frame-select}]))]))

View File

@ -29,10 +29,9 @@
(defn shape-snap-points (defn shape-snap-points
[{:keys [hidden blocked] :as shape}] [{:keys [hidden blocked] :as shape}]
(when (and (not blocked) (not hidden)) (when (and (not blocked) (not hidden))
(let [shape (gsh/transform-shape shape)] (case (:type shape)
(case (:type shape) :frame (-> shape :points gsh/points->selrect frame-snap-points)
:frame (-> shape :points gsh/points->selrect frame-snap-points) (into #{(gsh/center-shape shape)} (:points shape)))))
(into #{(gsh/center-shape shape)} (:points shape))))))
(defn guide-snap-points (defn guide-snap-points
[guide frame] [guide frame]

View File

@ -13,6 +13,7 @@
[app.common.pages.diff :as diff] [app.common.pages.diff :as diff]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.geom.grid :as gg] [app.util.geom.grid :as gg]
[app.util.geom.snap-points :as snap] [app.util.geom.snap-points :as snap]
@ -70,7 +71,7 @@
(mapv grid->snap))))) (mapv grid->snap)))))
(defn- add-frame (defn- add-frame
[page-data frame] [objects page-data frame]
(let [frame-id (:id frame) (let [frame-id (:id frame)
parent-id (:parent-id frame) parent-id (:parent-id frame)
frame-data (->> (snap/shape-snap-points frame) frame-data (->> (snap/shape-snap-points frame)
@ -79,21 +80,24 @@
:pt %))) :pt %)))
grid-x-data (get-grids-snap-points frame :x) grid-x-data (get-grids-snap-points frame :x)
grid-y-data (get-grids-snap-points frame :y)] 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 (cond-> page-data
(assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data)) (not (ctl/layout-child? objects frame))
(update-in [frame-id :x] #(or % (rt/make-tree)))
(update-in [frame-id :y] #(or % (rt/make-tree))) (-> ;; Update root frame information
(update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x)) (assoc-in [uuid/zero :objects-data frame-id] frame-data)
(update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y))))) (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 (defn- add-shape
[page-data shape] [objects page-data shape]
(let [frame-id (:frame-id shape) (let [frame-id (:frame-id shape)
snap-points (snap/shape-snap-points shape) snap-points (snap/shape-snap-points shape)
shape-data (->> snap-points shape-data (->> snap-points
@ -101,11 +105,11 @@
:type :shape :type :shape
:id (:id shape) :id (:id shape)
:pt %)))] :pt %)))]
(-> page-data (cond-> page-data
(assoc-in [frame-id :objects-data (:id shape)] shape-data) (not (ctl/layout-child? objects shape))
(update-in [frame-id :x] (make-insert-tree-data shape-data :x)) (-> (assoc-in [frame-id :objects-data (:id shape)] shape-data)
(update-in [frame-id :y] (make-insert-tree-data shape-data :y))))) (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 (defn- add-guide
[objects page-data guide] [objects page-data guide]
@ -164,22 +168,22 @@
(update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide))))))) (update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide)))))))
(defn- update-frame (defn- update-frame
[page-data [_ new-frame]] [objects page-data [_ new-frame]]
(let [frame-id (:id new-frame) (let [frame-id (:id new-frame)
root-data (get-in page-data [uuid/zero :objects-data frame-id]) root-data (get-in page-data [uuid/zero :objects-data frame-id])
frame-data (get-in page-data [frame-id :objects-data frame-id])] frame-data (get-in page-data [frame-id :objects-data frame-id])]
(-> page-data (as-> page-data $
(update-in [uuid/zero :x] (make-delete-tree-data root-data :x)) (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 $ [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 :x] (make-delete-tree-data frame-data :x))
(update-in [frame-id :y] (make-delete-tree-data frame-data :y)) (update-in $ [frame-id :y] (make-delete-tree-data frame-data :y))
(add-frame new-frame)))) (add-frame objects $ new-frame))))
(defn- update-shape (defn- update-shape
[page-data [old-shape new-shape]] [objects page-data [old-shape new-shape]]
(-> page-data (as-> page-data $
(remove-shape old-shape) (remove-shape $ old-shape)
(add-shape new-shape))) (add-shape objects $ new-shape)))
(defn- update-guide (defn- update-guide
[objects page-data [old-guide new-guide]] [objects page-data [old-guide new-guide]]
@ -205,8 +209,8 @@
page-data page-data
(as-> {} $ (as-> {} $
(add-root-frame $) (add-root-frame $)
(reduce add-frame $ frames) (reduce (partial add-frame objects) $ frames)
(reduce add-shape $ shapes) (reduce (partial add-shape objects) $ shapes)
(reduce (partial add-guide objects) $ guides))] (reduce (partial add-guide objects) $ guides))]
(assoc snap-data (:id page) page-data))) (assoc snap-data (:id page) page-data)))
@ -233,16 +237,16 @@
(diff/calculate-page-diff old-page page snap-attrs)] (diff/calculate-page-diff old-page page snap-attrs)]
(as-> page-data $ (as-> page-data $
(reduce update-shape $ change-frame-shapes) (reduce (partial update-shape objects) $ change-frame-shapes)
(reduce remove-frame $ removed-frames) (reduce remove-frame $ removed-frames)
(reduce remove-shape $ removed-shapes) (reduce remove-shape $ removed-shapes)
(reduce update-frame $ updated-frames) (reduce (partial update-frame objects) $ updated-frames)
(reduce update-shape $ updated-shapes) (reduce (partial update-shape objects) $ updated-shapes)
(reduce add-frame $ new-frames) (reduce (partial add-frame objects) $ new-frames)
(reduce add-shape $ new-shapes) (reduce (partial add-shape objects) $ new-shapes)
(reduce remove-guide $ removed-guides)
;; Guides functions. Need objects to get its frame data ;; 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) $ change-frame-guides)
(reduce (partial update-guide objects) $ updated-guides) (reduce (partial update-guide objects) $ updated-guides)
(reduce (partial add-guide objects) $ new-guides))))) (reduce (partial add-guide objects) $ new-guides)))))

View File

@ -67,6 +67,15 @@
;; Disable frame thumbnails ;; Disable frame thumbnails
: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 ;; 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.notifications/handle-pointer-update
:app.main.data.workspace.selection/change-hover-state}) :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-all! [] (reset! *debug* debug-options))
(defn debug-none! [] (reset! *debug* #{})) (defn debug-none! [] (reset! *debug* #{}))
@ -294,9 +303,16 @@
num-nodes (->> (dom/seq-nodes root-node) count)] num-nodes (->> (dom/seq-nodes root-node) count)]
#js {:number num-nodes})) #js {:number num-nodes}))
#_(defn modif->js (defn modif->js
[modif-tree objects] [modif-tree objects]
(clj->js (into {} (clj->js (into {}
(map (fn [[k v]] (map (fn [[k v]]
[(get-in objects [k :name]) v])) [(get-in objects [k :name]) v]))
modif-tree))) 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)

View File

@ -3827,19 +3827,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Erweiterte Optionen" msgstr "Erweiterte Optionen"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Max.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Max.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Min.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Min.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Elementgröße ändern"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Maximale Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Maximale Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Mindesthöhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Mindestbreite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs

View File

@ -3490,35 +3490,35 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Advanced options" msgstr "Advanced options"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Max.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Max.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Min.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Min.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Maximum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Maximum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Minimum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #: 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" msgstr "Minimum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@ -4077,6 +4077,14 @@ msgstr "Update main components"
msgid "workspace.shape.menu.update-main" msgid "workspace.shape.menu.update-main"
msgstr "Update main component" 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" msgid "workspace.sidebar.collapse"
msgstr "Collapse sidebar" msgstr "Collapse sidebar"

Some files were not shown because too many files have changed in this diff Show More