0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-18 10:41:29 -05:00

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
### :sparkles: New features
- Adds layout flex functionality for boards
- Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386)
- Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475)
### :bug: Bugs fixed
- Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218)

View file

@ -252,3 +252,21 @@
(update :d mth/precision 4)
(update :e mth/precision 4)
(update :f mth/precision 4)))
(defn transform-point-center
"Transform a point around the shape center"
[point center matrix]
(if (and (some? point) (some? matrix) (some? center))
(gpt/transform
point
(multiply (translate-matrix center)
matrix
(translate-matrix (gpt/negate center))))
point))
(defn move?
[{:keys [a b c d _ _]}]
(and (mth/almost-zero? (- a 1))
(mth/almost-zero? b)
(mth/almost-zero? c)
(mth/almost-zero? (- d 1))))

View file

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

View file

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

View file

@ -7,8 +7,6 @@
(ns app.common.geom.shapes.bool
(:require
[app.common.data :as d]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.transforms :as gtr]
[app.common.path.bool :as pb]
[app.common.path.shapes-to-path :as stp]))
@ -25,17 +23,5 @@
(into [] extract-content-xf (:shapes shape))]
(pb/content-bool (:bool-type shape) shapes-content)))
(defn update-bool-selrect
"Calculates the selrect+points for the boolean shape"
[shape children objects]
(let [bool-content (calc-bool-content shape objects)
shape (assoc shape :bool-content bool-content)
[points selrect] (gsp/content->points+selrect shape bool-content)]
(if (and (some? selrect) (d/not-empty? points))
(-> shape
(assoc :selrect selrect)
(assoc :points points))
(gtr/update-group-selrect shape children))))

View file

@ -8,7 +8,8 @@
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]))
[app.common.geom.point :as gpt]
[app.common.geom.shapes.rect :as gpr]))
(defn center-rect
[{:keys [x y width height]}]
@ -31,6 +32,22 @@
(gpt/point (/ (+ minx maxx) 2.0)
(/ (+ miny maxy) 2.0))))
(defn center-bounds [[a b c d]]
(let [xa (:x a)
ya (:y a)
xb (:x b)
yb (:y b)
xc (:x c)
yc (:y c)
xd (:x d)
yd (:y d)
minx (min xa xb xc xd)
miny (min ya yb yc yd)
maxx (max xa xb xc xd)
maxy (max ya yb yc yd)]
(gpt/point (/ (+ minx maxx) 2.0)
(/ (+ miny maxy) 2.0))))
(defn center-shape
"Calculate the center of the shape."
[shape]
@ -49,3 +66,8 @@
(gpt/transform point (gmt/multiply prev matrix post)))]
(mapv tr-point points))
points)))
(defn transform-selrect
[{:keys [x1 y1 x2 y2] :as sr} matrix]
(let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)]
(gpr/corners->selrect c1 c2)))

View file

@ -6,14 +6,20 @@
(ns app.common.geom.shapes.constraints
(:require
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.intersect :as gsi]
[app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.rect :as gre]
[app.common.geom.shapes.transforms :as gst]
[app.common.math :as mth]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
;; Auxiliary methods to work in an specifica axis
(defn other-axis [axis]
(if (= :x axis) :y :x))
(defn get-delta-start [axis rect tr-rect]
(if (= :x axis)
(- (:x1 tr-rect) (:x1 rect))
@ -29,6 +35,11 @@
(- (:width tr-rect) (:width rect))
(- (:height tr-rect) (:height rect))))
(defn get-delta-scale [axis rect tr-rect]
(if (= :x axis)
(/ (:width tr-rect) (:width rect))
(/ (:height tr-rect) (:height rect))))
(defn get-delta-center [axis center tr-center]
(if (= :x axis)
(- (:x tr-center) (:x center))
@ -53,78 +64,160 @@
(:width rect)
(:height rect)))
(defn right-vector
[child-points parent-points]
(let [[p0 p1 p2 _] parent-points
[_c0 c1 _ _] child-points
dir-v (gpt/to-vec p0 p1)
cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1 p2)]
(gpt/to-vec c1 cp)))
(defn left-vector
[child-points parent-points]
(let [[p0 p1 _ p3] parent-points
[_ _ _ c3] child-points
dir-v (gpt/to-vec p0 p1)
cp (gsi/line-line-intersect c3 (gpt/add c3 dir-v) p0 p3)]
(gpt/to-vec c3 cp)))
(defn top-vector
[child-points parent-points]
(let [[p0 p1 _ p3] parent-points
[c0 _ _ _] child-points
dir-v (gpt/to-vec p0 p3)
cp (gsi/line-line-intersect c0 (gpt/add c0 dir-v) p0 p1)]
(gpt/to-vec c0 cp)))
(defn bottom-vector
[child-points parent-points]
(let [[p0 _ p2 p3] parent-points
[_ _ c2 _] child-points
dir-v (gpt/to-vec p0 p3)
cp (gsi/line-line-intersect c2 (gpt/add c2 dir-v) p2 p3)]
(gpt/to-vec c2 cp)))
(defn center-horizontal-vector
[child-points parent-points]
(let [[p0 p1 _ p3] parent-points
[_ c1 _ _] child-points
dir-v (gpt/to-vec p0 p1)
p1c (gpt/add p0 (gpt/scale dir-v 0.5))
p2c (gpt/add p3 (gpt/scale dir-v 0.5))
cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1c p2c)]
(gpt/to-vec c1 cp)))
(defn center-vertical-vector
[child-points parent-points]
(let [[p0 p1 p2 _] parent-points
[_ c1 _ _] child-points
dir-v (gpt/to-vec p1 p2)
p3c (gpt/add p0 (gpt/scale dir-v 0.5))
p2c (gpt/add p1 (gpt/scale dir-v 0.5))
cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p3c p2c)]
(gpt/to-vec c1 cp)))
(defn start-vector
[axis child-points parent-points]
(let [pos-vector
(cond (= :x axis) left-vector
(= :y axis) top-vector)]
(pos-vector child-points parent-points)))
(defn end-vector
[axis child-points parent-points]
(let [pos-vector
(cond (= :x axis) right-vector
(= :y axis) bottom-vector)]
(pos-vector child-points parent-points)))
(defn center-vector
[axis child-points parent-points]
((if (= :x axis) center-horizontal-vector center-vertical-vector) child-points parent-points))
(defn displacement
[before-v after-v]
(let [angl (gpt/angle-with-other before-v after-v)
sign (if (mth/close? angl 180) -1 1)
length (* sign (gpt/length before-v))]
(if (mth/almost-zero? length)
after-v
(gpt/subtract after-v (gpt/scale (gpt/unit after-v) length)))))
(defn side-vector
[axis [c0 c1 _ c3]]
(if (= axis :x)
(gpt/to-vec c0 c1)
(gpt/to-vec c0 c3)))
(defn side-vector-resize
[axis [c0 c1 _ c3] start-vector end-vector]
(if (= axis :x)
(gpt/to-vec (gpt/add c0 start-vector) (gpt/add c1 end-vector))
(gpt/to-vec (gpt/add c0 start-vector) (gpt/add c3 end-vector))))
;; Constraint function definitions
(defmulti constraint-modifier (fn [type & _] type))
(defmethod constraint-modifier :start
[_ axis parent _ _ transformed-parent-rect]
(let [parent-rect (:selrect parent)
delta-start (get-delta-start axis parent-rect transformed-parent-rect)]
(if-not (mth/almost-zero? delta-start)
{:displacement (get-displacement axis delta-start)}
{})))
[_ axis child-points-before parent-points-before child-points-after parent-points-after]
(let [start-before (start-vector axis child-points-before parent-points-before)
start-after (start-vector axis child-points-after parent-points-after)]
(ctm/move-modifiers (displacement start-before start-after))))
(defmethod constraint-modifier :end
[_ axis parent _ _ transformed-parent-rect]
(let [parent-rect (:selrect parent)
delta-end (get-delta-end axis parent-rect transformed-parent-rect)]
(if-not (mth/almost-zero? delta-end)
{:displacement (get-displacement axis delta-end)}
{})))
[_ axis child-points-before parent-points-before child-points-after parent-points-after]
(let [end-before (end-vector axis child-points-before parent-points-before)
end-after (end-vector axis child-points-after parent-points-after)]
(ctm/move-modifiers (displacement end-before end-after))))
(defmethod constraint-modifier :fixed
[_ axis parent child _ transformed-parent-rect]
(let [parent-rect (:selrect parent)
child-rect (gre/points->rect (:points child))
[_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent]
(let [;; Same as constraint end
end-before (end-vector axis child-points-before parent-points-before)
end-after (end-vector axis child-points-after parent-points-after)
start-before (start-vector axis child-points-before parent-points-before)
start-after (start-vector axis child-points-after parent-points-after)
delta-start (get-delta-start axis parent-rect transformed-parent-rect)
delta-size (get-delta-size axis parent-rect transformed-parent-rect)
child-size (get-size axis child-rect)]
(if (or (not (mth/almost-zero? delta-start))
(not (mth/almost-zero? delta-size)))
disp-end (displacement end-before end-after)
disp-start (displacement start-before start-after)
{:displacement (get-displacement axis delta-start)
:resize-origin (get-displacement axis delta-start (:x child-rect) (:y child-rect))
:resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))}
{})))
;; We get the current axis side and grow it on both side by the end+start displacements
before-vec (side-vector axis child-points-after)
after-vec (side-vector-resize axis child-points-after disp-start disp-end)
;; after-vec will contain the side length of the grown side
;; we scale the shape by the diference and translate it by the start
;; displacement (so its left+top position is constant)
scale (/ (gpt/length after-vec) (gpt/length before-vec))
resize-origin (first child-points-after)
{:keys [transform transform-inverse]} transformed-parent]
(-> (ctm/empty)
(ctm/resize (get-scale axis scale) resize-origin transform transform-inverse)
(ctm/move disp-start))))
(defmethod constraint-modifier :center
[_ axis parent _ _ transformed-parent-rect]
(let [parent-rect (:selrect parent)
parent-center (gco/center-rect parent-rect)
transformed-parent-center (gco/center-rect transformed-parent-rect)
delta-center (get-delta-center axis parent-center transformed-parent-center)]
(if-not (mth/almost-zero? delta-center)
{:displacement (get-displacement axis delta-center)}
{})))
(defmethod constraint-modifier :scale
[_ axis _ _ modifiers _]
(let [{:keys [resize-vector resize-vector-2 displacement]} modifiers]
(cond-> {}
(and (some? resize-vector)
(not= (axis resize-vector) 1))
(assoc :resize-origin (:resize-origin modifiers)
:resize-vector (if (= :x axis)
(gpt/point (:x resize-vector) 1)
(gpt/point 1 (:y resize-vector))))
(and (= :y axis) (some? resize-vector-2)
(not (mth/close? (:y resize-vector-2) 1)))
(assoc :resize-origin (:resize-origin-2 modifiers)
:resize-vector (gpt/point 1 (:y resize-vector-2)))
(some? displacement)
(assoc :displacement
(get-displacement axis (-> (gpt/point 0 0)
(gpt/transform displacement)
(gpt/transform (:resize-transform-inverse modifiers (gmt/matrix)))
axis))))))
[_ axis child-points-before parent-points-before child-points-after parent-points-after]
(let [center-before (center-vector axis child-points-before parent-points-before)
center-after (center-vector axis child-points-after parent-points-after)]
(ctm/move-modifiers (displacement center-before center-after))))
(defmethod constraint-modifier :default [_ _ _ _ _]
{})
[])
(def const->type+axis
{:left :start
@ -152,77 +245,73 @@
:top
:scale)))
(defn clean-modifiers
"Remove redundant modifiers"
[{:keys [displacement resize-vector resize-vector-2] :as modifiers}]
(defn bounding-box-parent-transform
"Returns a bounding box for the child in the same coordinate system
as the parent.
Returns a points array"
[child parent]
(-> child
:points
(gco/transform-points (:transform-inverse parent))
(gre/points->rect)
(gre/rect->points) ;; Restore to points so we can transform them
(gco/transform-points (:transform parent))))
(cond-> modifiers
;; Displacement with value 0. We don't move in any direction
(and (some? displacement)
(mth/almost-zero? (:e displacement))
(mth/almost-zero? (:f displacement)))
(dissoc :displacement)
(defn normalize-modifiers
"Before aplying constraints we need to remove the deformation caused by the resizing of the parent"
[constraints-h constraints-v modifiers child parent transformed-child {:keys [transform transform-inverse] :as transformed-parent}]
;; Resize with value very close to 1 means no resize
(and (some? resize-vector)
(mth/almost-zero? (- 1.0 (:x resize-vector)))
(mth/almost-zero? (- 1.0 (:y resize-vector))))
(dissoc :resize-origin :resize-vector)
(let [child-bb-before (gst/parent-coords-rect child parent)
child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
scale-x (/ (:width child-bb-before) (:width child-bb-after))
scale-y (/ (:height child-bb-before) (:height child-bb-after))
resize-origin (-> transformed-parent :points gpo/origin)]
(and (some? resize-vector)
(mth/almost-zero? (- 1.0 (:x resize-vector-2)))
(mth/almost-zero? (- 1.0 (:y resize-vector-2))))
(dissoc :resize-origin-2 :resize-vector-2)))
(cond-> modifiers
(not= :scale constraints-h)
(ctm/resize (gpt/point scale-x 1) resize-origin transform transform-inverse)
(not= :scale constraints-v)
(ctm/resize (gpt/point 1 scale-y) resize-origin transform transform-inverse))))
(defn calc-child-modifiers
[parent child modifiers ignore-constraints transformed-parent-rect]
[parent child modifiers ignore-constraints transformed-parent]
(if (and (nil? (:resize-vector modifiers))
(nil? (:resize-vector-2 modifiers)))
;; If we don't have a resize modifier we return the same modifiers
modifiers
(let [constraints-h
(if-not ignore-constraints
(:constraints-h child (default-constraints-h child))
:scale)
(let [modifiers (ctm/select-child-modifiers modifiers)
constraints-v
(if-not ignore-constraints
(:constraints-v child (default-constraints-v child))
:scale)
constraints-h
(if-not ignore-constraints
(:constraints-h child (default-constraints-h child))
:scale)
modifiers-h (constraint-modifier (constraints-h const->type+axis) :x parent child modifiers transformed-parent-rect)
modifiers-v (constraint-modifier (constraints-v const->type+axis) :y parent child modifiers transformed-parent-rect)]
constraints-v
(if-not ignore-constraints
(:constraints-v child (default-constraints-v child))
:scale)]
;; Build final child modifiers. Apply transform again to the result, to get the
;; real modifiers that need to be applied to the child, including rotation as needed.
(cond-> {}
(some? (:displacement-after modifiers))
(assoc :displacement-after (:displacement-after modifiers))
(if (and (= :scale constraints-h) (= :scale constraints-v))
modifiers
(or (contains? modifiers-h :displacement)
(contains? modifiers-v :displacement))
(assoc :displacement (cond-> (gpt/point (get-in modifiers-h [:displacement :x] 0)
(get-in modifiers-v [:displacement :y] 0))
(some? (:resize-transform modifiers))
(gpt/transform (:resize-transform modifiers))
(let [transformed-child (gst/transform-shape child (ctm/select-child-modifiers modifiers))
modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent)
:always
(gmt/translate-matrix)))
transformed-child (gst/transform-shape child modifiers)
(:resize-vector modifiers-h)
(assoc :resize-origin (:resize-origin modifiers-h)
:resize-vector (gpt/point (get-in modifiers-h [:resize-vector :x] 1)
(get-in modifiers-h [:resize-vector :y] 1)))
parent-points-before (bounding-box-parent-transform parent parent)
child-points-before (bounding-box-parent-transform child parent)
parent-points-after (bounding-box-parent-transform transformed-parent transformed-parent)
child-points-after (bounding-box-parent-transform transformed-child transformed-parent)
(:resize-vector modifiers-v)
(assoc :resize-origin-2 (:resize-origin modifiers-v)
:resize-vector-2 (gpt/point (get-in modifiers-v [:resize-vector :x] 1)
(get-in modifiers-v [:resize-vector :y] 1)))
modifiers-h (constraint-modifier (constraints-h const->type+axis) :x
child-points-before parent-points-before
child-points-after parent-points-after
transformed-parent)
(:resize-transform modifiers)
(assoc :resize-transform (:resize-transform modifiers)
:resize-transform-inverse (:resize-transform-inverse modifiers))
modifiers-v (constraint-modifier (constraints-v const->type+axis) :y
child-points-before parent-points-before
child-points-after parent-points-after
transformed-parent)]
:always
(clean-modifiers)))))
(-> modifiers
(ctm/add-modifiers modifiers-h)
(ctm/add-modifiers modifiers-v))))))

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

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]))
(defn make-rect
[x y width height]
(when (d/num? x y width height)
(let [width (max width 0.01)
height (max height 0.01)]
{:x x
:y y
:width width
:height height})))
([p1 p2]
(let [xp1 (:x p1)
yp1 (:y p1)
xp2 (:x p2)
yp2 (:y p2)
x1 (min xp1 xp2)
y1 (min yp1 yp2)
x2 (max xp1 xp2)
y2 (max yp1 yp2)]
(make-rect x1 y1 (- x2 x1) (- y2 y1))))
([x y width height]
(when (d/num? x y width height)
(let [width (max width 0.01)
height (max height 0.01)]
{:x x
:y y
:width width
:height height}))))
(defn make-selrect
[x y width height]
@ -80,6 +91,19 @@
(when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny))))))
(defn squared-points
[points]
(when (d/not-empty? points)
(let [minx (transduce (keep :x) min ##Inf points)
miny (transduce (keep :y) min ##Inf points)
maxx (transduce (keep :x) max ##-Inf points)
maxy (transduce (keep :y) max ##-Inf points)]
(when (d/num? minx miny maxx maxy)
[(gpt/point minx miny)
(gpt/point maxx miny)
(gpt/point maxx maxy)
(gpt/point minx maxy)]))))
(defn points->selrect [points]
(when-let [rect (points->rect points)]
(let [{:keys [x y width height]} rect]
@ -168,3 +192,10 @@
(>= (:y1 sr2) (:y1 sr1))
(<= (:y2 sr2) (:y2 sr1))))
(defn corners->selrect
[p1 p2]
(let [xp1 (:x p1)
xp2 (:x p2)
yp1 (:y p1)
yp2 (:y p2)]
(make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2)))))

View file

@ -10,12 +10,14 @@
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.bool :as gshb]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]
[app.common.spec :as us]
[app.common.text :as txt]))
[app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
(def ^:dynamic *skip-adjust* false)
@ -76,14 +78,6 @@
; ---- Geometric operations
(defn- normalize-scale
"We normalize the scale so it's not too close to 0"
[scale]
(cond
(and (< scale 0) (> scale -0.01)) -0.01
(and (>= scale 0) (< scale 0.01)) 0.01
:else scale))
(defn- calculate-skew-angle
"Calculates the skew angle of the parallelogram given by the points"
[[p1 _ p3 p4]]
@ -99,6 +93,7 @@
(defn- calculate-height
"Calculates the height of a parallelogram given by the points"
[[p1 _ _ p4]]
(-> (gpt/to-vec p4 p1)
(gpt/length)))
@ -182,17 +177,6 @@
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/translate (gpt/negate center)))))
(defn transform-point-center
"Transform a point around the shape center"
[point center matrix]
(if (and (some? point) (some? matrix) (some? center))
(gpt/transform
point
(gmt/multiply (gmt/translate-matrix center)
matrix
(gmt/translate-matrix (gpt/negate center))))
point))
(defn transform-rect
"Transform a rectangles and changes its attributes"
[rect matrix]
@ -205,55 +189,52 @@
"Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that
after applying them the end result is the `shape-path-temp`.
This is compose of three transformations: skew, resize and rotation"
([points-temp points-rec]
(calculate-adjust-matrix points-temp points-rec false false))
[points-temp points-rec flip-x flip-y]
(let [center (gco/center-bounds points-temp)
([points-temp points-rec flip-x flip-y]
(let [center (gco/center-points points-temp)
stretch-matrix (gmt/matrix)
stretch-matrix (gmt/matrix)
skew-angle (calculate-skew-angle points-temp)
skew-angle (calculate-skew-angle points-temp)
;; When one of the axis is flipped we have to reverse the skew
;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle )
skew-angle (if (and (or flip-x flip-y)
(not (and flip-x flip-y))) (- skew-angle) skew-angle )
skew-angle (if (mth/nan? skew-angle) 0 skew-angle)
;; When one of the axis is flipped we have to reverse the skew
;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle )
skew-angle (if (and (or flip-x flip-y)
(not (and flip-x flip-y))) (- skew-angle) skew-angle )
skew-angle (if (mth/nan? skew-angle) 0 skew-angle)
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
h1 (max 1 (calculate-height points-temp))
h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix)))
h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1)
h3 (if (mth/nan? h3) 1 h3)
h1 (max 1 (calculate-height points-temp))
h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix)))
h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1)
h3 (if (mth/nan? h3) 1 h3)
w1 (max 1 (calculate-width points-temp))
w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
w3 (if (mth/nan? w3) 1 w3)
w1 (max 1 (calculate-width points-temp))
w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix)))
w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1)
w3 (if (mth/nan? w3) 1 w3)
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3)))
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3)))
rotation-angle (calculate-rotation
center
(gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix)
points-temp
flip-x
flip-y)
rotation-angle (calculate-rotation
center
(gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix)
points-temp
flip-x
flip-y)
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
;; This is the inverse to be able to remove the transformation
stretch-matrix-inverse
(gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
(gmt/skew-matrix (- skew-angle) 0)
(gmt/rotate-matrix (- rotation-angle)))]
[stretch-matrix stretch-matrix-inverse rotation-angle])))
;; This is the inverse to be able to remove the transformation
stretch-matrix-inverse
(gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3)))
(gmt/skew-matrix (- skew-angle) 0)
(gmt/rotate-matrix (- rotation-angle)))]
[stretch-matrix stretch-matrix-inverse rotation-angle]))
(defn- adjust-rotated-transform
[{:keys [transform transform-inverse flip-x flip-y]} points]
(let [center (gco/center-points points)
(let [center (gco/center-bounds points)
points-temp (cond-> points
(some? transform-inverse)
@ -273,13 +254,59 @@
(if transform (gmt/multiply transform matrix) matrix)
(if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)]))
(defn apply-transform
(defn- adjust-shape-flips
"After some tranformations the flip-x/flip-y flags can change we need
to check this before adjusting the selrect"
[shape points]
(let [points' (:points shape)
xv1 (gpt/to-vec (nth points' 0) (nth points' 1))
xv2 (gpt/to-vec (nth points 0) (nth points 1))
dot-x (gpt/dot xv1 xv2)
yv1 (gpt/to-vec (nth points' 0) (nth points' 3))
yv2 (gpt/to-vec (nth points 0) (nth points 3))
dot-y (gpt/dot yv1 yv2)]
(cond-> shape
(neg? dot-x)
(-> (update :flip-x not)
(update :rotation -))
(neg? dot-y)
(-> (update :flip-y not)
(update :rotation -)))))
(defn- apply-transform-move
"Given a new set of points transformed, set up the rectangle so it keeps
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx]
(let [bool? (= (:type shape) :bool)
path? (= (:type shape) :path)
points (gco/transform-points (:points shape) transform-mtx)
selrect (gco/transform-selrect (:selrect shape) transform-mtx)]
(-> shape
(cond-> bool?
(update :bool-content gpa/transform-content transform-mtx))
(cond-> path?
(update :content gpa/transform-content transform-mtx))
(cond-> (not path?)
(assoc :x (:x selrect)
:y (:y selrect)
:width (:width selrect)
:height (:height selrect)))
(assoc :selrect selrect)
(assoc :points points))))
(defn- apply-transform-generic
"Given a new set of points transformed, set up the rectangle so it keeps
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx]
(let [points' (:points shape)
points (gco/transform-points points' transform-mtx)
shape (-> shape (adjust-shape-flips points))
bool? (= (:type shape) :bool)
path? (= (:type shape) :path)
@ -289,14 +316,16 @@
base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
rotation (mod (+ base-rotation modif-rotation) 360)]
(-> shape
(cond-> bool?
(update :bool-content gpa/transform-content transform-mtx))
(cond-> path?
(update :content gpa/transform-content transform-mtx))
(cond-> (not path?)
(-> (merge (select-keys selrect [:x :y :width :height]))))
(assoc :x (:x selrect)
:y (:y selrect)
:width (:width selrect)
:height (:height selrect)))
(cond-> transform
(-> (assoc :transform transform)
(assoc :transform-inverse transform-inverse)))
@ -304,10 +333,19 @@
(dissoc :transform :transform-inverse))
(cond-> (some? selrect)
(assoc :selrect selrect))
(cond-> (d/not-empty? points)
(assoc :points points))
(assoc :rotation rotation))))
(defn- apply-transform
"Given a new set of points transformed, set up the rectangle so it keeps
its properties. We adjust de x,y,width,height and create a custom transform"
[shape transform-mtx]
(if (gmt/move? transform-mtx)
(apply-transform-move shape transform-mtx)
(apply-transform-generic shape transform-mtx)))
(defn- update-group-viewbox
"Updates the viewbox for groups imported from SVG's"
[{:keys [selrect svg-viewbox] :as group} new-selrect]
@ -332,6 +370,9 @@
;; Points for every shape inside the group
points (->> children (mapcat :points))
;; Fixed problem with empty groups. Should not happen (but it does)
points (if (empty? points) (:points group) points)
;; Invert to get the points minus the transforms applied to the group
base-points (gco/transform-points points shape-center (:transform-inverse group (gmt/matrix)))
@ -368,284 +409,74 @@
(assoc :flip-x (-> mask :flip-x))
(assoc :flip-y (-> mask :flip-y)))))
;; --- Modifiers
(defn update-bool-selrect
"Calculates the selrect+points for the boolean shape"
[shape children objects]
;; The `modifiers` structure contains a list of transformations to
;; do make to a shape, in this order:
;;
;; - resize-origin (gpt/point) + resize-vector (gpt/point)
;; apply a scale vector to all points of the shapes, starting
;; from the origin point.
;;
;; - resize-origin-2 + resize-vector-2
;; same as the previous one, for cases in that we need to make
;; two vectors from different origin points.
;;
;; - displacement (gmt/matrix)
;; apply a translation matrix to the shape
;;
;; - rotation (gmt/matrix)
;; apply a rotation matrix to the shape
;;
;; - resize-transform (gmt/matrix) + resize-transform-inverse (gmt/matrix)
;; a copy of the rotation matrix currently applied to the shape;
;; this is needed temporarily to apply the resize vectors.
;;
;; - resize-scale-text (bool)
;; tells if the resize vectors must be applied to text shapes
;; or not.
(let [bool-content (gshb/calc-bool-content shape objects)
shape (assoc shape :bool-content bool-content)
[points selrect] (gpa/content->points+selrect shape bool-content)]
(defn empty-modifiers? [modifiers]
(empty? (dissoc modifiers :ignore-geometry?)))
(defn resize-modifiers
[shape attr value]
(us/assert map? shape)
(us/assert #{:width :height} attr)
(us/assert number? value)
(let [{:keys [proportion proportion-lock]} shape
size (select-keys (:selrect shape) [:width :height])
new-size (if-not proportion-lock
(assoc size attr value)
(if (= attr :width)
(-> size
(assoc :width value)
(assoc :height (/ value proportion)))
(-> size
(assoc :height value)
(assoc :width (* value proportion)))))
width (:width new-size)
height (:height new-size)
shape-transform (:transform shape)
shape-transform-inv (:transform-inverse shape)
shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape)
origin (cond-> (gpt/point (:selrect shape))
(some? shape-transform)
(transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point width height)
(gpt/point sr-width sr-height))]
{:resize-vector scalev
:resize-origin origin
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv}))
(defn change-orientation-modifiers
[shape orientation]
(us/assert map? shape)
(us/verify #{:horiz :vert} orientation)
(let [width (:width shape)
height (:height shape)
new-width (if (= orientation :horiz) (max width height) (min width height))
new-height (if (= orientation :horiz) (min width height) (max width height))
shape-transform (:transform shape)
shape-transform-inv (:transform-inverse shape)
shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape)
origin (cond-> (gpt/point (:selrect shape))
(some? shape-transform)
(transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point new-width new-height)
(gpt/point sr-width sr-height))]
{:resize-vector scalev
:resize-origin origin
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv}))
(defn rotation-modifiers
[shape center angle]
(let [displacement (let [shape-center (gco/center-shape shape)]
(-> (gmt/matrix)
(gmt/rotate angle center)
(gmt/rotate (- angle) shape-center)))]
{:rotation angle
:displacement displacement}))
(defn merge-modifiers
[objects modifiers]
(let [set-modifier
(fn [objects [id modifiers]]
(-> objects
(d/update-when id merge modifiers)))]
(->> modifiers
(reduce set-modifier objects))))
(defn modifiers->transform
([modifiers]
(modifiers->transform nil modifiers))
([center modifiers]
(let [displacement (:displacement modifiers)
displacement-after (:displacement-after modifiers)
resize-v1 (:resize-vector modifiers)
resize-v2 (:resize-vector-2 modifiers)
origin-1 (:resize-origin modifiers (gpt/point))
origin-2 (:resize-origin-2 modifiers (gpt/point))
;; Normalize x/y vector coordinates because scale by 0 is infinite
resize-1 (when (some? resize-v1)
(gpt/point (normalize-scale (:x resize-v1))
(normalize-scale (:y resize-v1))))
resize-2 (when (some? resize-v2)
(gpt/point (normalize-scale (:x resize-v2))
(normalize-scale (:y resize-v2))))
resize-transform (:resize-transform modifiers)
resize-transform-inverse (:resize-transform-inverse modifiers)
rt-modif (:rotation modifiers)]
(cond-> (gmt/matrix)
(some? displacement-after)
(gmt/multiply displacement-after)
(some? resize-1)
(-> (gmt/translate origin-1)
(cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-1)
(cond-> (some? resize-transform-inverse)
(gmt/multiply resize-transform-inverse))
(gmt/translate (gpt/negate origin-1)))
(some? resize-2)
(-> (gmt/translate origin-2)
(cond-> (some? resize-transform)
(gmt/multiply resize-transform))
(gmt/scale resize-2)
(cond-> (some? resize-transform-inverse)
(gmt/multiply resize-transform-inverse))
(gmt/translate (gpt/negate origin-2)))
(some? displacement)
(gmt/multiply displacement)
(some? rt-modif)
(-> (gmt/translate center)
(gmt/multiply (gmt/rotate-matrix rt-modif))
(gmt/translate (gpt/negate center)))))))
(defn- set-flip [shape modifiers]
(let [rv1x (or (get-in modifiers [:resize-vector :x]) 1)
rv1y (or (get-in modifiers [:resize-vector :y]) 1)
rv2x (or (get-in modifiers [:resize-vector-2 :x]) 1)
rv2y (or (get-in modifiers [:resize-vector-2 :y]) 1)]
(cond-> shape
(or (neg? rv1x) (neg? rv2x))
(-> (update :flip-x not)
(update :rotation -))
(or (neg? rv1y) (neg? rv2y))
(-> (update :flip-y not)
(update :rotation -)))))
(defn- apply-displacement [shape]
(let [modifiers (:modifiers shape)]
(if (contains? modifiers :displacement)
(let [mov-vec (-> (gpt/point 0 0)
(gpt/transform (:displacement modifiers)))
shape (move shape mov-vec)
modifiers (dissoc modifiers :displacement)]
(-> shape
(assoc :modifiers modifiers)
(cond-> (empty-modifiers? modifiers)
(dissoc :modifiers))))
shape)))
(defn- apply-text-resize
[shape modifiers]
(if (and (= (:type shape) :text)
(:resize-scale-text modifiers))
(let [merge-attrs (fn [attrs]
(let [font-size (-> (get attrs :font-size 14)
(d/parse-double)
(* (get-in modifiers [:resize-vector :x] 1))
(* (get-in modifiers [:resize-vector-2 :x] 1))
(str))]
(d/txt-merge attrs {:font-size font-size})))]
(update shape :content #(txt/transform-nodes
txt/is-text-node?
merge-attrs
%)))
shape))
(defn apply-modifiers
[shape modifiers]
(let [center (gco/center-shape shape)
transform (modifiers->transform center modifiers)]
(apply-transform shape transform)))
(if (and (some? selrect) (d/not-empty? points))
(-> shape
(assoc :selrect selrect)
(assoc :points points))
(update-group-selrect shape children))))
(defn transform-shape
[shape]
(let [modifiers (:modifiers shape)]
(cond
(nil? modifiers)
shape
([shape]
(let [modifiers (:modifiers shape)]
(-> shape
(dissoc :modifiers)
(transform-shape modifiers))))
(empty-modifiers? modifiers)
(dissoc shape :modifiers)
([shape modifiers]
(letfn [(apply-modifiers
[shape modifiers]
(if (ctm/empty? modifiers)
shape
(let [transform (ctm/modifiers->transform modifiers)]
(cond-> shape
(and (some? transform) (not= uuid/zero (:id shape))) ;; Never transform the root frame
(apply-transform transform)
:else
(let [shape (apply-displacement shape)
modifiers (:modifiers shape)]
(cond-> shape
(not (empty-modifiers? modifiers))
(-> (set-flip modifiers)
(apply-modifiers modifiers)
(apply-text-resize modifiers))
(ctm/has-structure? modifiers)
(ctm/apply-structure-modifiers modifiers)))))]
:always
(dissoc :modifiers))))))
(cond-> shape
(and (some? modifiers) (not (ctm/empty? modifiers)))
(apply-modifiers modifiers)))))
(defn apply-objects-modifiers
[objects modifiers]
(loop [objects objects
entry (first modifiers)
modifiers (rest modifiers)]
(if (nil? entry)
objects
(let [[id modifier] entry]
(recur (d/update-when objects id transform-shape (:modifiers modifier))
(first modifiers)
(rest modifiers))))))
(defn transform-bounds
[points center {:keys [displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
;; FIXME: Improve Performance
(let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix))
([points modifiers]
(transform-bounds points nil modifiers))
displacement
(when (some? displacement)
(gmt/multiply resize-transform-inverse displacement))
resize-origin
(when (some? resize-origin)
(transform-point-center resize-origin center resize-transform-inverse))
resize-origin-2
(when (some? resize-origin-2)
(transform-point-center resize-origin-2 center resize-transform-inverse))
]
(if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2) (nil? displacement-after))
points
(cond-> points
(some? displacement)
(gco/transform-points displacement)
(some? resize-origin)
(gco/transform-points resize-origin (gmt/scale-matrix resize-vector))
(some? resize-origin-2)
(gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2))
(some? displacement-after)
(gco/transform-points displacement-after)))))
([points center modifiers]
(let [transform (ctm/modifiers->transform modifiers)]
(cond-> points
(some? transform)
(gco/transform-points center transform)))))
(defn transform-selrect
[selrect modifiers]
(let [center (gco/center-selrect selrect)]
(-> selrect
(gpr/rect->points)
(transform-bounds center modifiers)
(gpr/points->selrect))))
(-> selrect
(gpr/rect->points)
(transform-bounds modifiers)
(gpr/points->selrect)))
(defn transform-selrect-matrix
[selrect mtx]
@ -662,17 +493,62 @@
(map (comp gpr/points->selrect :points transform-shape))
(gpr/join-selrects)))
(declare apply-group-modifiers)
(defn apply-children-modifiers
[objects modif-tree parent-modifiers children propagate?]
(->> children
(map (fn [child]
(let [modifiers (cond-> (get-in modif-tree [(:id child) :modifiers])
propagate? (ctm/add-modifiers parent-modifiers))
child (transform-shape child modifiers)
parent? (cph/group-like-shape? child)
modif-tree
(cond-> modif-tree
propagate?
(assoc-in [(:id child) :modifiers] modifiers))]
(cond-> child
parent?
(apply-group-modifiers objects modif-tree propagate?)))))))
(defn apply-group-modifiers
"Apply the modifiers to the group children to calculate its selection rect"
[group objects modif-tree]
([group objects modif-tree]
(apply-group-modifiers group objects modif-tree true))
(let [children
(->> (:shapes group)
(map (d/getf objects))
(map (fn [shape]
(let [modifiers (get modif-tree (:id shape))
shape (-> shape (merge modifiers) transform-shape)]
(if (= :group (:type shape))
(apply-group-modifiers shape objects modif-tree)
shape)))))]
(update-group-selrect group children)))
([group objects modif-tree propagate?]
(let [modifiers (get-in modif-tree [(:id group) :modifiers])
children
(as-> (:shapes group) $
(map (d/getf objects) $)
(apply-children-modifiers objects modif-tree modifiers $ propagate?))]
(cond
(cph/mask-shape? group)
(update-mask-selrect group children)
(cph/bool-shape? group)
(transform-shape group modifiers)
(cph/group-shape? group)
(update-group-selrect group children)
:else
group))))
(defn parent-coords-rect
[child parent]
(-> child
:points
(gco/transform-points (:transform-inverse parent))
(gpr/points->rect)))
(defn parent-coords-points
[child parent]
(-> child
:points
(gco/transform-points (:transform-inverse parent))
(gpr/points->rect)
(gpr/rect->points)
(gco/transform-points (:transform parent))))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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]
[clojure.spec.alpha :as s]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
;; :layout-wrap-type ;; :wrap, :no-wrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
;; ITEMS
;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
;; :layout-item-margin-type ;; :simple :multiple
;; :layout-item-h-sizing ;; :fill :fix :auto
;; :layout-item-v-sizing ;; :fill :fix :auto
;; :layout-item-max-h ;; num
;; :layout-item-min-h ;; num
;; :layout-item-max-w ;; num
;; :layout-item-min-w
(s/def ::layout #{:flex :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :strech})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :strech})
(s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-wrap-type #{:wrap :no-wrap})
(s/def ::layout-padding-type #{:simple :multiple})
@ -25,15 +46,14 @@
(s/def ::p4 ::us/safe-number)
(s/def ::layout-padding
(s/keys :req-un [::p1]
:opt-un [::p2 ::p3 ::p4]))
(s/keys :opt-un [::p1 ::p2 ::p3 ::p4]))
(s/def ::row-gap ::us/safe-number)
(s/def ::column-gap ::us/safe-number)
(s/def ::layout-type #{:flex :grid})
(s/def ::layout-gap
(s/keys :req-un [::row-gap ::column-gap]))
(s/keys :opt-un [::row-gap ::column-gap]))
(s/def ::layout-container-props
(s/keys :opt-un [::layout
@ -53,25 +73,223 @@
(s/def ::m3 ::us/safe-number)
(s/def ::m4 ::us/safe-number)
(s/def ::layout-margin (s/keys :req-un [::m1]
:opt-un [::m2 ::m3 ::m4]))
(s/def ::layout-item-margin (s/keys :opt-un [::m1 ::m2 ::m3 ::m4]))
(s/def ::layout-margin-type #{:simple :multiple})
(s/def ::layout-h-behavior #{:fill :fix :auto})
(s/def ::layout-v-behavior #{:fill :fix :auto})
(s/def ::layout-align-self #{:start :end :center :strech :baseline})
(s/def ::layout-max-h ::us/safe-number)
(s/def ::layout-min-h ::us/safe-number)
(s/def ::layout-max-w ::us/safe-number)
(s/def ::layout-min-w ::us/safe-number)
(s/def ::layout-item-margin-type #{:simple :multiple})
(s/def ::layout-item-h-sizing #{:fill :fix :auto})
(s/def ::layout-item-v-sizing #{:fill :fix :auto})
(s/def ::layout-item-align-self #{:start :end :center :stretch})
(s/def ::layout-item-max-h ::us/safe-number)
(s/def ::layout-item-min-h ::us/safe-number)
(s/def ::layout-item-max-w ::us/safe-number)
(s/def ::layout-item-min-w ::us/safe-number)
(s/def ::layout-child-props
(s/keys :opt-un [::layout-margin
::layout-margin-type
::layout-h-behavior
::layout-v-behavior
::layout-max-h
::layout-min-h
::layout-max-w
::layout-min-w
::layout-align-self]))
(s/keys :opt-un [::layout-item-margin
::layout-item-margin-type
::layout-item-h-sizing
::layout-item-v-sizing
::layout-item-max-h
::layout-item-min-h
::layout-item-max-w
::layout-item-min-w
::layout-item-align-self]))
(defn layout?
([objects id]
(layout? (get objects id)))
([shape]
(and (= :frame (:type shape)) (= :flex (:layout shape)))))
(defn layout-child? [objects shape]
(let [parent-id (:parent-id shape)
parent (get objects parent-id)]
(layout? parent)))
(defn inside-layout?
"Check if the shape is inside a layout"
[objects shape]
(loop [current-id (:id shape)]
(let [current (get objects current-id)]
(cond
(or (nil? current) (= current-id (:parent-id current)))
false
(= :frame (:type current))
(:layout current)
:else
(recur (:parent-id current))))))
(defn wrap? [{:keys [layout-wrap-type]}]
(= layout-wrap-type :wrap))
(defn fill-width? [child]
(= :fill (:layout-item-h-sizing child)))
(defn fill-height? [child]
(= :fill (:layout-item-v-sizing child)))
(defn auto-width? [child]
(= :auto (:layout-item-h-sizing child)))
(defn auto-height? [child]
(= :auto (:layout-item-v-sizing child)))
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
(defn gaps
[{:keys [layout-gap layout-gap-type]}]
(let [layout-gap-row (or (-> layout-gap :row-gap) 0)
layout-gap-col (if (= layout-gap-type :simple)
layout-gap-row
(or (-> layout-gap :column-gap) 0))]
[layout-gap-row layout-gap-col]))
(defn child-min-width
[child]
(if (and (fill-width? child)
(some? (:layout-item-min-w child)))
(max 0 (:layout-item-min-w child))
0))
(defn child-max-width
[child]
(if (and (fill-width? child)
(some? (:layout-item-max-w child)))
(max 0 (:layout-item-max-w child))
##Inf))
(defn child-min-height
[child]
(if (and (fill-height? child)
(some? (:layout-item-min-h child)))
(max 0 (:layout-item-min-h child))
0))
(defn child-max-height
[child]
(if (and (fill-height? child)
(some? (:layout-item-max-h child)))
(max 0 (:layout-item-max-h child))
##Inf))
(defn child-margins
[{{:keys [m1 m2 m3 m4]} :layout-item-margin :keys [layout-item-margin-type]}]
(let [m1 (or m1 0)
m2 (or m2 0)
m3 (or m3 0)
m4 (or m4 0)]
(if (= layout-item-margin-type :multiple)
[m1 m2 m3 m4]
[m1 m1 m1 m1])))
(defn child-height-margin
[child]
(let [[top _ bottom _] (child-margins child)]
(+ top bottom)))
(defn child-width-margin
[child]
(let [[_ right _ left] (child-margins child)]
(+ right left)))
(defn h-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :start))
(and (row? shape)
(= layout-justify-content :start))))
(defn h-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :center))
(and (row? shape)
(= layout-justify-content :center))))
(defn h-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :end))
(and (row? shape)
(= layout-justify-content :end))))
(defn v-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :start))
(and (col? shape)
(= layout-justify-content :start))))
(defn v-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :center))
(and (col? shape)
(= layout-justify-content :center))))
(defn v-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :end))
(and (col? shape)
(= layout-justify-content :end))))
(defn content-start?
[{:keys [layout-align-content]}]
(= :start layout-align-content))
(defn content-center?
[{:keys [layout-align-content]}]
(= :center layout-align-content))
(defn content-end?
[{:keys [layout-align-content]}]
(= :end layout-align-content))
(defn content-between?
[{:keys [layout-align-content]}]
(= :space-between layout-align-content))
(defn content-around?
[{:keys [layout-align-content]}]
(= :space-around layout-align-content))
(defn content-stretch?
[{:keys [layout-align-content]}]
(or (= :stretch layout-align-content)
(nil? layout-align-content)))
(defn reverse?
[{:keys [layout-flex-dir]}]
(or (= :reverse-row layout-flex-dir)
(= :reverse-column layout-flex-dir)))
(defn space-between?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-between))
(defn space-around?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-around))
(defn align-self-start? [{:keys [layout-item-align-self]}]
(= :start layout-item-align-self))
(defn align-self-end? [{:keys [layout-item-align-self]}]
(= :end layout-item-align-self))
(defn align-self-center? [{:keys [layout-item-align-self]}]
(= :center layout-item-align-self))
(defn align-self-stretch? [{:keys [layout-item-align-self]}]
(= :stretch layout-item-align-self))

View file

@ -224,8 +224,31 @@
"Search for the top nested frame for positioning shapes when moving or creating.
Looks for all the frames in a position and then goes in depth between the top-most and its
children to find the target."
[objects position]
(let [frame-ids (all-frames-by-position objects position)
([objects position]
(top-nested-frame objects position nil))
([objects position excluded]
(assert (or (nil? excluded) (set? excluded)))
(let [frame-ids (cond->> (all-frames-by-position objects position)
(some? excluded)
(remove excluded))
frame-set (set frame-ids)]
(loop [current-id (first frame-ids)]
(let [current-shape (get objects current-id)
child-frame-id (d/seek #(contains? frame-set %)
(-> (:shapes current-shape) reverse))]
(if (nil? child-frame-id)
(or current-id uuid/zero)
(recur child-frame-id)))))))
(defn top-nested-frame-ids
"Search the top nested frame in a list of ids"
[objects ids]
(let [frame-ids (->> ids (filter #(cph/frame-shape? objects %)))
frame-set (set frame-ids)]
(loop [current-id (first frame-ids)]
(let [current-shape (get objects current-id)

View file

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

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 {
border-color: $color-danger;
}
&[disabled] {
color: $color-gray-30;
}
}
.input-select {

View file

@ -25,6 +25,7 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.comments :as dcm]
@ -52,8 +53,8 @@
[app.main.data.workspace.path.shapes-to-path :as dwps]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shapes-update-layout :as dwul]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.thumbnails :as dwth]
@ -686,11 +687,16 @@
shapes-to-detach
shapes-to-reroot
shapes-to-deroot
ids)]
ids)
layouts-to-update
(into #{}
(filter (partial ctl/layout? objects))
(concat [parent-id] (cph/get-parent-ids objects parent-id)))]
(rx/of (dch/commit-changes changes)
(dwco/expand-collapse parent-id)
(dwsl/update-layout-positions [parent-id]))))))
(dwul/update-layout-positions layouts-to-update))))))
(defn relocate-selected-shapes
[parent-id to-index]
@ -1571,37 +1577,6 @@
(rx/of (dch/commit-changes changes))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Artboard
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn create-artboard-from-selection
[]
(ptk/reify ::create-artboard-from-selection
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
selected-objs (map #(get objects %) selected)]
(when (d/not-empty? selected)
(let [srect (gsh/selection-rect selected-objs)
frame-id (get-in objects [(first selected) :frame-id])
parent-id (get-in objects [(first selected) :parent-id])
shape (-> (cts/make-minimal-shape :frame)
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
(assoc :frame-id frame-id :parent-id parent-id)
(cond-> (not= frame-id uuid/zero)
(assoc :fills [] :hide-in-viewer true))
(cts/setup-rect-selrect))]
(rx/of
(dwu/start-undo-transaction)
(dwsh/add-shape shape)
(dwsh/move-shapes-into-frame (:id shape) selected)
(dwu/commit-undo-transaction))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Remove graphics
;; TODO: this should be deprecated and removed together with components-v2

View file

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

View file

@ -10,6 +10,7 @@
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
@ -20,29 +21,38 @@
[beicon.core :as rx]
[potok.core :as ptk]))
(defn truncate-zero [num default]
(if (mth/almost-zero? num) default num))
(defn adjust-ratio
[point initial]
(let [v (gpt/to-vec point initial)
dx (mth/abs (:x v))
dy (mth/abs (:y v))
sx (mth/sign (:x v))
sy (mth/sign (:y v))]
(cond-> point
(> dx dy)
(assoc :y (- (:y point) (* sy (- dx dy))))
(> dy dx)
(assoc :x (- (:x point) (* sx (- dy dx)))))))
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?]
(let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
shape-rect (gsh/make-rect x y width height)
scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
(/ (:height draw-rect) (:height shape-rect)))
movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
(defn resize-shape [{:keys [x y width height] :as shape} point lock?]
(let [;; The new shape behaves like a resize on the bottom-right corner
initial (gpt/point (+ x width) (+ y height))
shapev (gpt/point width height)
deltav (gpt/to-vec initial point)
scalev (-> (gpt/divide (gpt/add shapev deltav) shapev)
(update :x truncate-zero 0.01)
(update :y truncate-zero 0.01))
scalev (if lock?
(let [v (max (:x scalev) (:y scalev))]
(gpt/point v v))
scalev)]
(-> shape
(assoc :click-draw? false)
(assoc-in [:modifiers :resize-vector] scalev)
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
(assoc-in [:modifiers :resize-rotation] 0))))
(gsh/transform-shape (-> (ctm/empty)
(ctm/resize scalev (gpt/point x y))
(ctm/move movev))))))
(defn update-drawing [state point lock?]
(update-in state [:workspace-drawing :object] resize-shape point lock?))
(defn update-drawing [state initial point lock?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock?))
(defn move-drawing
[{:keys [x y]}]
@ -58,8 +68,7 @@
layout (get state :workspace-layout)
snap-pixel? (contains? layout :snap-pixel-grid)
initial (cond-> @ms/mouse-position
snap-pixel? gpt/round)
initial (cond-> @ms/mouse-position snap-pixel? gpt/round)
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
@ -97,7 +106,7 @@
(rx/map #(conj current %)))))
(rx/map
(fn [[_ shift? point]]
#(update-drawing % (cond-> point snap-pixel? gpt/round) shift?)))
#(update-drawing % initial (cond-> point snap-pixel? gpt/round) shift?)))
(rx/take-until stoper))
(rx/of (common/handle-finish-drawing)))))))

View file

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

View file

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

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

View file

@ -8,9 +8,13 @@
(:require
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.selection :as dwse]
[app.main.data.workspace.shapes :as dws]
[app.main.data.workspace.shapes-update-layout :as wsul]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.transforms :as dwt]
[beicon.core :as rx]
[potok.core :as ptk]))
@ -25,8 +29,7 @@
:layout-wrap-type
:layout-padding-type
:layout-padding
])
:layout-gap-type])
(def initial-flex-layout
{:layout :flex
@ -35,7 +38,7 @@
:layout-gap {:row-gap 0 :column-gap 0}
:layout-align-items :start
:layout-justify-content :start
:layout-align-content :strech
:layout-align-content :stretch
:layout-wrap-type :no-wrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}})
@ -43,30 +46,50 @@
(def initial-grid-layout ;; TODO
{:layout :grid})
(defn update-layout-positions
[ids]
(ptk/reify ::update-layout-positions
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
ids (->> ids (filter #(get-in objects [% :layout])))]
(if (d/not-empty? ids)
(rx/of (dwt/set-modifiers ids)
(dwt/apply-modifiers))
(rx/empty))))))
(defn get-layout-initializer
[type]
(let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)]
(fn [shape]
(-> shape
(merge shape initial-layout-data)))))
;; TODO: Remove constraints from children
(defn create-layout
[ids type]
(ptk/reify ::create-layout
ptk/WatchEvent
(watch [_ _ _]
(if (= type :flex)
(rx/of (dwc/update-shapes ids #(merge % initial-flex-layout))
(update-layout-positions ids))
(rx/of (dwc/update-shapes ids #(merge % initial-grid-layout))
(update-layout-positions ids))))))
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)]
(rx/of (dwc/update-shapes ids (get-layout-initializer type))
(wsul/update-layout-positions ids)
(dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v)))))))
(defn create-layout-from-selection
[type]
(ptk/reify ::create-layout-from-selection
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected)
single? (= (count selected-shapes) 1)
has-group? (->> selected-shapes (d/seek cph/group-shape?))
is-group? (and single? has-group?)]
(if is-group?
(let [new-shape-id (uuid/next)
parent-id (:parent-id (first selected-shapes))
shapes-ids (:shapes (first selected-shapes))
ordered-ids (into (d/ordered-set) shapes-ids)]
(rx/of (dwse/select-shapes ordered-ids)
(dws/create-artboard-from-selection new-shape-id parent-id)
(create-layout [new-shape-id] type)
(dws/delete-shapes page-id selected)))
(let [new-shape-id (uuid/next)]
(rx/of (dws/create-artboard-from-selection new-shape-id)
(create-layout [new-shape-id] type))))))))
(defn remove-layout
[ids]
@ -74,7 +97,23 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys))
(update-layout-positions ids)))))
(wsul/update-layout-positions ids)))))
(defn toogle-layout-flex
[]
(ptk/reify ::toogle-layout-flex
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected)
single? (= (count selected-shapes) 1)
has-flex-layout? (and single? (= :flex (:layout (first selected-shapes))))]
(if has-flex-layout?
(rx/of (remove-layout selected))
(rx/of (create-layout-from-selection :flex)))))))
(defn update-layout
[ids changes]
@ -82,7 +121,7 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwc/update-shapes ids #(d/deep-merge % changes))
(update-layout-positions ids)))))
(wsul/update-layout-positions ids)))))
(defn update-layout-child
[ids changes]
@ -90,6 +129,7 @@
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
parent-ids (->> ids (map #(cph/get-parent-id objects %)))]
parent-ids (->> ids (map #(cph/get-parent-id objects %)))
layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))]
(rx/of (dwc/update-shapes ids #(d/deep-merge (or % {}) changes))
(update-layout-positions parent-ids))))))
(wsul/update-layout-positions (d/concat-vec layout-ids parent-ids)))))))

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@ -16,13 +17,15 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.comments :as dc]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes-update-layout :as dwsul]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.streams :as ms]
[beicon.core :as rx]
@ -101,7 +104,7 @@
(rx/concat
(rx/of (dch/commit-changes changes)
(dwsl/update-layout-positions [(:parent-id shape)])
(dwsul/update-layout-positions [(:parent-id shape)])
(when-not no-select?
(dws/select-shapes (d/ordered-set id))))
(when (= :text (:type attrs))
@ -146,6 +149,10 @@
ids (cph/clean-loops objects ids)
lookup (d/getf objects)
layout-ids (->> ids
(mapcat (partial cph/get-parent-ids objects))
(filter (partial ctl/layout? objects)))
components-v2 (features/active-feature? state :components-v2)
groups-to-unmask
@ -265,8 +272,9 @@
(reduce ctp/remove-flow flows))))))]
(rx/of (dc/detach-comment-thread ids)
(dwsl/update-layout-positions all-parents)
(dch/commit-changes changes)))))))
(dwsul/update-layout-positions all-parents)
(dch/commit-changes changes)
(dwsul/update-layout-positions layout-ids)))))))
(defn- viewport-center
[state]
@ -292,3 +300,38 @@
(assoc :frame-id frame-id)
(cts/setup-rect-selrect))]
(rx/of (add-shape shape))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Artboard
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn create-artboard-from-selection
([]
(create-artboard-from-selection nil))
([id]
(create-artboard-from-selection id nil))
([id parent-id]
(ptk/reify ::create-artboard-from-selection
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
selected-objs (map #(get objects %) selected)]
(when (d/not-empty? selected)
(let [srect (gsh/selection-rect selected-objs)
frame-id (get-in objects [(first selected) :frame-id])
parent-id (or parent-id (get-in objects [(first selected) :parent-id]))
shape (-> (cts/make-minimal-shape :frame)
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
(cond-> id
(assoc :id id))
(assoc :frame-id frame-id :parent-id parent-id)
(cond-> (not= frame-id uuid/zero)
(assoc :fills [] :hide-in-viewer true))
(cts/setup-rect-selrect))]
(rx/of
(dwu/start-undo-transaction)
(add-shape shape)
(move-shapes-into-frame (:id shape) selected)
(dwu/commit-undo-transaction)))))))))

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

View file

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

View file

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

View file

@ -11,16 +11,17 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.flex-layout :as gsl]
[app.common.math :as mth]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.common :as cpc]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.modifiers :as ctm]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.collapse :as dwc]
[app.main.data.workspace.comments :as-alias dwcm]
[app.main.data.workspace.guides :as-alias dwg]
[app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
@ -95,233 +96,12 @@
(update state :workspace-local dissoc :transform))))
;; -- Temporary modifiers -------------------------------------------
;; During an interactive transformation of shapes (e.g. when resizing or rotating
;; a group with the mouse), there are a lot of objects that need to be modified
;; (in this case, the group and all its children).
;;
;; To avoid updating the shapes theirselves, and forcing redraw of all components
;; that depend on the "objects" global state, we set a "modifiers" structure, with
;; the changes that need to be applied, and store it in :workspace-modifiers global
;; variable. The viewport reads this and merges it into the objects list it uses to
;; paint the viewport content, redrawing only the objects that have new modifiers.
;;
;; When the interaction is finished (e.g. user releases mouse button), the
;; apply-modifiers event is done, that consolidates all modifiers into the base
;; geometric attributes of the shapes.
(declare clear-local-transform)
(declare get-ignore-tree)
(defn set-modifiers
([ids]
(set-modifiers ids nil false))
([ids modifiers]
(set-modifiers ids modifiers false))
([ids modifiers ignore-constraints]
(set-modifiers ids modifiers ignore-constraints false))
([ids modifiers ignore-constraints ignore-snap-pixel]
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::set-modifiers
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
snap-pixel? (and (not ignore-snap-pixel)
(contains? (:workspace-layout state) :snap-pixel-grid))
modif-tree
(gsh/set-objects-modifiers ids objects (constantly modifiers) ignore-constraints snap-pixel?)]
(update state :workspace-modifiers merge modif-tree))))))
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
(defn- set-rotation-modifiers
([angle shapes]
(set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect)))
([angle shapes center]
(ptk/reify ::set-rotation-modifiers
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
ids
(->> shapes
(remove #(get % :blocked false))
(mapcat #(cph/get-children objects (:id %)))
(concat shapes)
(filter #((cpc/editable-attrs (:type %)) :rotation))
(map :id))
get-modifier
(fn [shape]
(gsh/rotation-modifiers shape center angle))
modif-tree
(gsh/set-objects-modifiers ids objects get-modifier false false)]
(update state :workspace-modifiers merge modif-tree))))))
(defn- update-grow-type
[shape old-shape]
(let [auto-width? (= :auto-width (:grow-type shape))
auto-height? (= :auto-height (:grow-type shape))
changed-width? (not (mth/close? (:width shape) (:width old-shape)))
changed-height? (not (mth/close? (:height shape) (:height old-shape)))
change-to-fixed? (or (and auto-width? (or changed-height? changed-width?))
(and auto-height? changed-height?))]
(cond-> shape
change-to-fixed?
(assoc :grow-type :fixed))))
(defn apply-modifiers
([]
(apply-modifiers nil))
([{:keys [undo-transation?] :or {undo-transation? true}}]
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
object-modifiers (get state :workspace-modifiers)
ids (keys object-modifiers)
ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids)
shapes (map (d/getf objects) ids)
ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes)
(reduce merge {}))]
(rx/concat
(if undo-transation?
(rx/of (dwu/start-undo-transaction))
(rx/empty))
(rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
(dch/update-shapes
ids
(fn [shape]
(let [modif (get object-modifiers (:id shape))
text-shape? (cph/text-shape? shape)]
(-> shape
(merge modif)
(gsh/transform-shape)
(cond-> text-shape?
(update-grow-type shape)))))
{:reg-objects? true
:ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect
:points
:x
:y
:width
:height
:content
:transform
:transform-inverse
:rotation
:position-data
:flip-x
:flip-y
:grow-type]})
(clear-local-transform))
(if undo-transation?
(rx/of (dwu/commit-undo-transaction))
(rx/empty))))))))
(defn- check-delta
"If the shape is a component instance, check its relative position respect the
root of the component, and see if it changes after applying a transformation."
[shape root transformed-shape transformed-root objects modif-tree]
(let [root
(cond
(:component-root? shape)
shape
(nil? root)
(cph/get-root-shape objects shape)
:else root)
transformed-root
(cond
(:component-root? transformed-shape)
transformed-shape
(nil? transformed-root)
(as-> (cph/get-root-shape objects transformed-shape) $
(gsh/transform-shape (merge $ (get modif-tree (:id $)))))
:else transformed-root)
shape-delta
(when root
(gpt/point (- (gsh/left-bound shape) (gsh/left-bound root))
(- (gsh/top-bound shape) (gsh/top-bound root))))
transformed-shape-delta
(when transformed-root
(gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
(- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
;; There are cases in that the coordinates change slightly (e.g. when
;; rounding to pixel, or when recalculating text positions in different
;; zoom levels). To take this into account, we ignore movements smaller
;; than 1 pixel.
distance (if (and shape-delta transformed-shape-delta)
(gpt/distance-vector shape-delta transformed-shape-delta)
(gpt/point 0 0))
ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))]
[root transformed-root ignore-geometry?]))
(defn- get-ignore-tree
"Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers"
([modif-tree objects shape]
(get-ignore-tree modif-tree objects shape nil nil {}))
([modif-tree objects shape root transformed-root ignore-tree]
(let [children (map (d/getf objects) (:shapes shape))
shape-id (:id shape)
transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id)))
[root transformed-root ignore-geometry?]
(check-delta shape root transformed-shape transformed-root objects modif-tree)
ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
set-child
(fn [ignore-tree child]
(get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
(reduce set-child ignore-tree children))))
(defn- clear-local-transform []
(ptk/reify ::clear-local-transform
ptk/UpdateEvent
(update [_ state]
(-> state
(dissoc :workspace-modifiers)
(dissoc ::current-move-selected)))))
;; -- Resize --------------------------------------------------------
(defn start-resize
"Enter mouse resize mode, until mouse button is released."
[handler ids shape]
(letfn [(resize [shape initial layout [point lock? center? point-snap]]
(letfn [(resize [shape objects initial layout [point lock? center? point-snap]]
(let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape
@ -331,10 +111,10 @@
rotation (or rotation 0)
initial (gsh/transform-point-center initial shape-center shape-transform-inverse)
initial (gmt/transform-point-center initial shape-center shape-transform-inverse)
initial (fix-init-point initial handler shape)
point (gsh/transform-point-center (if (= rotation 0) point-snap point)
point (gmt/transform-point-center (if (= rotation 0) point-snap point)
shape-center shape-transform-inverse)
shapev (-> (gpt/point width height))
@ -381,20 +161,46 @@
(gpt/transform shape-transform)))
resize-origin
(cond-> (gsh/transform-point-center handler-origin shape-center shape-transform)
(cond-> (gmt/transform-point-center handler-origin shape-center shape-transform)
(some? displacement)
(gpt/add displacement))
displacement (when (some? displacement)
(gmt/translate-matrix displacement))]
;; When the horizontal/vertical scale a flex children with auto/fill
;; we change it too fixed
layout? (ctl/layout? shape)
layout-child? (ctl/layout-child? objects shape)
auto-width? (ctl/auto-width? shape)
fill-width? (ctl/fill-width? shape)
auto-height? (ctl/auto-height? shape)
fill-height? (ctl/fill-height? shape)
(rx/of (set-modifiers ids
{:displacement displacement
:resize-vector scalev
:resize-origin resize-origin
:resize-transform shape-transform
:resize-scale-text scale-text
:resize-transform-inverse shape-transform-inverse}))))
set-fix-width?
(and (not (mth/close? (:x scalev) 1))
(or (and (or layout? layout-child?) auto-width?)
(and layout-child? fill-width?)))
set-fix-height?
(and (not (mth/close? (:y scalev) 1))
(or (and (or layout? layout-child?) auto-height?)
(and layout-child? fill-height?)))
modifiers
(-> (ctm/empty)
(cond-> displacement
(ctm/move displacement))
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
(cond-> set-fix-width?
(ctm/change-property :layout-item-h-sizing :fix))
(cond-> set-fix-height?
(ctm/change-property :layout-item-v-sizing :fix))
(cond-> scale-text
(ctm/scale-content (:x scalev))))
modif-tree (dwm/create-modif-tree ids modifiers)]
(rx/of (dwm/set-modifiers modif-tree))))
;; Unifies the instantaneous proportion lock modifier
;; activated by Shift key and the shapes own proportion
@ -418,6 +224,7 @@
zoom (get-in state [:workspace-local :zoom] 1)
objects (wsh/lookup-page-objects state page-id)
resizing-shapes (map #(get objects %) ids)]
(rx/concat
(->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
@ -425,9 +232,9 @@
(rx/switch-map (fn [[point _ _ :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point)
(rx/map #(conj current %)))))
(rx/mapcat (partial resize shape initial-position layout))
(rx/mapcat (partial resize shape objects initial-position layout))
(rx/take-until stoper))
(rx/of (apply-modifiers)
(rx/of (dwm/apply-modifiers)
(finish-transform))))))))
(defn update-dimensions
@ -444,16 +251,17 @@
snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid)
(int? value))
get-modifier
(fn [shape] (gsh/resize-modifiers shape attr value))
(fn [shape] (ctm/change-dimensions-modifiers shape attr value))
modif-tree
(gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)]
(-> (dwm/build-modif-tree ids objects get-modifier)
(gsh/set-objects-modifiers objects false snap-pixel?))]
(assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (apply-modifiers)))))
(rx/of (dwm/apply-modifiers)))))
(defn change-orientation
"Change orientation of shapes, from the sidebar options form.
@ -468,16 +276,17 @@
snap-pixel? (contains? (get state :workspace-layout) :snap-pixel-grid)
get-modifier
(fn [shape] (gsh/change-orientation-modifiers shape orientation))
(fn [shape] (ctm/change-orientation-modifiers shape orientation))
modif-tree
(gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)]
(-> (dwm/build-modif-tree ids objects get-modifier)
(gsh/set-objects-modifiers objects false snap-pixel?))]
(assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (apply-modifiers)))))
(rx/of (dwm/apply-modifiers)))))
;; -- Rotate --------------------------------------------------------
@ -518,9 +327,9 @@
(rx/map
(fn [[[pos mod?] shift?]]
(let [delta-angle (calculate-angle pos mod? shift?)]
(set-rotation-modifiers delta-angle shapes group-center))))
(dwm/set-rotation-modifiers delta-angle shapes group-center))))
(rx/take-until stoper))
(rx/of (apply-modifiers)
(rx/of (dwm/apply-modifiers)
(finish-transform)))))))
(defn increase-rotation
@ -534,17 +343,17 @@
objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape]
(let [delta (- rotation (:rotation shape))]
(set-rotation-modifiers delta [shape])))]
(dwm/set-rotation-modifiers delta [shape])))]
(rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
(rx/of (apply-modifiers)))))))
(rx/of (dwm/apply-modifiers)))))))
;; -- Move ----------------------------------------------------------
(declare start-move)
(declare start-move-duplicate)
(declare calculate-frame-for-move)
(declare move-shapes-to-frame)
(declare get-displacement)
(defn start-move-selected
@ -626,13 +435,18 @@
zoom (get-in state [:workspace-local :zoom] 1)
focus (:workspace-focus-selected state)
fix-axis (fn [[position shift?]]
(let [delta (gpt/to-vec from-position position)]
(if shift?
(if (> (mth/abs (:x delta)) (mth/abs (:y delta)))
(gpt/point (:x delta) 0)
(gpt/point 0 (:y delta)))
delta)))
exclude-frames (into #{}
(filter (partial cph/frame-shape? objects))
(cph/selected-with-children objects selected))
fix-axis
(fn [[position shift?]]
(let [delta (gpt/to-vec from-position position)]
(if shift?
(if (> (mth/abs (:x delta)) (mth/abs (:y delta)))
(gpt/point (:x delta) 0)
(gpt/point 0 (:y delta)))
delta)))
position (->> ms/mouse-position
(rx/with-latest-from ms/mouse-position-shift)
@ -649,21 +463,43 @@
(rx/map #(vector pos %)))))))]
(if (empty? shapes)
(rx/of (finish-transform))
(rx/concat
(->> position
;; We ask for the snap position but we continue even if the result is not available
(rx/with-latest vector snap-delta)
;; We try to use the previous snap so we don't have to wait for the result of the new
(rx/map snap/correct-snap-point)
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
(rx/map (partial set-modifiers ids))
(rx/take-until stopper))
(let [move-stream
(->> position
;; We ask for the snap position but we continue even if the result is not available
(rx/with-latest vector snap-delta)
(rx/of (dwu/start-undo-transaction)
(calculate-frame-for-move ids)
(apply-modifiers {:undo-transation? false})
(finish-transform)
(dwu/commit-undo-transaction)))))))))
;; We try to use the previous snap so we don't have to wait for the result of the new
(rx/map snap/correct-snap-point)
(rx/map
(fn [move-vector]
(let [position (gpt/add from-position move-vector)
target-frame (ctst/top-nested-frame objects position exclude-frames)
layout? (ctl/layout? objects target-frame)
drop-index (when layout? (gsl/get-drop-index target-frame objects position))]
[move-vector target-frame drop-index])))
(rx/take-until stopper))]
(rx/merge
;; Temporary modifiers stream
(->> move-stream
(rx/map
(fn [[move-vector target-frame drop-index]]
(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
(dwm/build-change-frame-modifiers objects selected target-frame drop-index)
(dwm/set-modifiers)))))
;; Last event will write the modifiers creating the changes
(->> move-stream
(rx/last)
(rx/mapcat
(fn [[_ target-frame drop-index]]
(rx/of (dwu/start-undo-transaction)
(move-shapes-to-frame ids target-frame drop-index)
(dwm/apply-modifiers {:undo-transation? false})
(finish-transform)
(dwu/commit-undo-transaction)))))))))))))
(s/def ::direction #{:up :down :right :left})
@ -704,12 +540,12 @@
(rx/merge
(->> move-events
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
(rx/map (partial set-modifiers selected))
(rx/map #(dwm/create-modif-tree selected (ctm/move-modifiers %)))
(rx/map (partial dwm/set-modifiers))
(rx/take-until stopper))
(rx/of (move-selected direction shift?)))
(rx/of (apply-modifiers)
(rx/of (dwm/apply-modifiers)
(finish-transform))))
(rx/empty))))))
@ -736,34 +572,39 @@
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))
delta (gpt/subtract pos cpos)
displ (gmt/translate-matrix delta)]
(rx/of (set-modifiers [id] {:displacement displ} false true)
(apply-modifiers [id]))))))
modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
(defn- calculate-frame-for-move
[ids]
(ptk/reify ::calculate-frame-for-move
(rx/of (dwm/set-modifiers modif-tree)
(dwm/apply-modifiers))))))
(defn- move-shapes-to-frame
[ids frame-id drop-index]
(ptk/reify ::move-shapes-to-frame
ptk/WatchEvent
(watch [it state _]
(let [position @ms/mouse-position
page-id (:current-page-id state)
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
frame-id (ctst/top-nested-frame objects position)
layout? (get-in objects [frame-id :layout])
lookup (d/getf objects)
shapes (->> ids (cph/clean-loops objects) (keep lookup))
moving-shapes
(->> ids
(cph/clean-loops objects)
(keep lookup)
(remove #(= (:frame-id %) frame-id)))
(cond->> shapes
(not layout?)
(remove #(= (:frame-id %) frame-id))
layout?
(remove #(and (= (:frame-id %) frame-id)
(not= (:parent-id %) frame-id))))
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/change-parent frame-id moving-shapes))]
(pcb/change-parent frame-id moving-shapes drop-index))]
(when-not (empty? changes)
(when (and (some? frame-id) (d/not-empty? changes))
(rx/of (dch/commit-changes changes)
(dwc/expand-collapse frame-id)))))))
@ -787,15 +628,17 @@
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape)))
origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))]
selrect (gsh/selection-rect shapes)
origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))
(rx/of (set-modifiers selected
{:resize-vector (gpt/point -1.0 1.0)
:resize-origin origin
:displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))}
true)
(apply-modifiers))))))
modif-tree (dwm/create-modif-tree
selected
(-> (ctm/empty)
(ctm/resize (gpt/point -1.0 1.0) origin)
(ctm/move (gpt/point (:width selrect) 0))))]
(rx/of (dwm/set-modifiers modif-tree true)
(dwm/apply-modifiers))))))
(defn flip-vertical-selected []
(ptk/reify ::flip-vertical-selected
@ -804,12 +647,14 @@
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape)))
origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))]
selrect (gsh/selection-rect shapes)
origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))
(rx/of (set-modifiers selected
{:resize-vector (gpt/point 1.0 -1.0)
:resize-origin origin
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}
true)
(apply-modifiers))))))
modif-tree (dwm/create-modif-tree
selected
(-> (ctm/empty)
(ctm/resize (gpt/point 1.0 -1.0) origin)
(ctm/move (gpt/point 0 (:height selrect)))))]
(rx/of (dwm/set-modifiers modif-tree true)
(dwm/apply-modifiers))))))

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@
(ns app.main.ui.components.shape-icon
(:require
[app.common.types.shape.layout :as ctl]
[app.main.ui.icons :as i]
[rumext.v2 :as mf]))
@ -13,7 +14,15 @@
(mf/defc element-icon
[{:keys [shape main-instance?] :as props}]
(case (:type shape)
:frame i/artboard
:frame (cond
(and (ctl/layout? shape) (ctl/col? shape))
i/layout-columns
(and (ctl/layout? shape) (ctl/row? shape))
i/layout-rows
:else
i/artboard)
:image i/image
:line i/line
:circle i/circle

View file

@ -133,6 +133,8 @@
(def justify-content-row-end (icon-xref :justify-content-row-end))
(def justify-content-row-start (icon-xref :justify-content-row-start))
(def layers (icon-xref :layers))
(def layout-columns (icon-xref :layout-columns))
(def layout-rows (icon-xref :layout-rows))
(def letter-spacing (icon-xref :letter-spacing))
(def libraries (icon-xref :libraries))
(def library (icon-xref :library))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,8 @@
[app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features]
[app.main.refs :as refs]
@ -212,7 +214,7 @@
(let [multiple? (> (count shapes) 1)
single? (= (count shapes) 1)
do-create-artboard-from-selection #(st/emit! (dw/create-artboard-from-selection))
do-create-artboard-from-selection #(st/emit! (dwsh/create-artboard-from-selection))
has-frame? (->> shapes (d/seek cph/frame-shape?))
has-group? (->> shapes (d/seek cph/group-shape?))
@ -364,6 +366,31 @@
[:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
:on-click do-add-flow}])))))
(mf/defc context-menu-flex
[{:keys [shapes]}]
(let [single? (= (count shapes) 1)
has-frame? (->> shapes (d/seek cph/frame-shape?))
is-frame? (and single? has-frame?)
is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
has-group? (->> shapes (d/seek cph/group-shape?))
is-group? (and single? has-group?)
ids (->> shapes (map :id))
add-flex #(st/emit! (dwsl/create-layout-from-selection :flex))
remove-flex #(st/emit! (dwsl/remove-layout ids))]
(cond
(or (not single?) (and is-frame? (not is-flex-container?)) is-group?)
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.add-flex")
:shortcut (sc/get-tooltip :toogle-layout-flex)
:on-click add-flex}]]
is-flex-container?
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.remove-flex")
:shortcut (sc/get-tooltip :toogle-layout-flex)
:on-click remove-flex}]])))
(mf/defc context-menu-component
[{:keys [shapes]}]
@ -517,6 +544,7 @@
[:> context-menu-path props]
[:> context-menu-layer-options props]
[:> context-menu-prototype props]
[:> context-menu-flex props]
[:> context-menu-component props]
[:> context-menu-delete props]])))

View file

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

View file

@ -11,79 +11,28 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.types.modifiers :as ctm]
[app.main.store :as st]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.viewport.utils :as vwu]
[app.util.dom :as dom]
[app.util.globals :as globals]
[rumext.v2 :as mf]))
(defn- transform-no-resize
"If we apply a scale directly to the texts it will show deformed so we need to create this
correction matrix to \"undo\" the resize but keep the other transformations."
[{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers]
(defn get-shape-node
([id]
(get-shape-node js/document id))
(let [corner-pt (first points)
corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse))
resize-x? (some? (:resize-vector modifiers))
resize-y? (some? (:resize-vector-2 modifiers))
flip-x? (neg? (get-in modifiers [:resize-vector :x]))
flip-y? (or (neg? (get-in modifiers [:resize-vector :y]))
(neg? (get-in modifiers [:resize-vector-2 :y])))
result (cond-> (gmt/matrix)
(and (some? transform) (or resize-x? resize-y?))
(gmt/multiply transform)
resize-x?
(gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt)
resize-y?
(gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt)
flip-x?
(gmt/scale (gpt/point -1 1) corner-pt)
flip-y?
(gmt/scale (gpt/point 1 -1) corner-pt)
(and (some? transform) (or resize-x? resize-y?))
(gmt/multiply transform-inverse))
[width height]
(if (or resize-x? resize-y?)
(let [pc (cond-> (gpt/point x y)
(some? transform)
(gpt/transform transform)
(some? current-transform)
(gpt/transform current-transform))
pw (cond-> (gpt/point (+ x width) y)
(some? transform)
(gpt/transform transform)
(some? current-transform)
(gpt/transform current-transform))
ph (cond-> (gpt/point x (+ y height))
(some? transform)
(gpt/transform transform)
(some? current-transform)
(gpt/transform current-transform))]
[(gpt/distance pc pw) (gpt/distance pc ph)])
[width height])]
[result width height]))
([base-node id]
(if (= (.-id base-node) (dm/str "shape-" id))
base-node
(dom/query base-node (dm/str "#shape-" id)))))
(defn get-nodes
"Retrieve the DOM nodes to apply the matrix transformation"
[base-node {:keys [id type masked-group?] :as shape}]
(when (some? base-node)
(let [shape-node (if (= (.-id base-node) (dm/str "shape-" id))
base-node
(dom/query base-node (dm/str "#shape-" id)))
(let [shape-node (get-shape-node base-node id)
frame? (= :frame type)
group? (= :group type)
@ -110,8 +59,7 @@
(dom/query-all shape-defs ".svg-mask-wrapper")))
text?
[shape-node
(dom/query shape-node ".text-container")]
[shape-node]
:else
[shape-node]))))
@ -164,7 +112,7 @@
(defn set-transform-att!
[node att value]
(let [old-att (dom/get-attribute node (dm/str "data-old-" att))
new-value (if (some? old-att)
(dm/str value " " old-att)
@ -177,37 +125,20 @@
(defn update-transform!
[base-node shapes transforms modifiers]
(doseq [{:keys [id type] :as shape} shapes]
(doseq [{:keys [id _type] :as shape} shapes]
(when-let [nodes (get-nodes base-node shape)]
(let [transform (get transforms id)
modifiers (get-in modifiers [id :modifiers])
text? (= type :text)
transform-text? (and text? (and (nil? (:resize-vector modifiers)) (nil? (:resize-vector-2 modifiers))))]
modifiers (get-in modifiers [id :modifiers])]
(doseq [node nodes]
(cond
;; Text shapes need special treatment because their resize only change
;; the text area, not the change size/position
(dom/class? node "frame-thumbnail")
(let [[transform] (transform-no-resize shape transform modifiers)]
(set-transform-att! node "transform" transform))
(dom/class? node "frame-children")
(set-transform-att! node "transform" (gmt/inverse transform))
(dom/class? node "text-container")
(let [modifiers (dissoc modifiers :displacement :rotation)]
(when (not (gsh/empty-modifiers? modifiers))
(let [mtx (-> shape
(assoc :modifiers modifiers)
(gsh/transform-shape)
(gsh/transform-matrix {:no-flip true}))]
(override-transform-att! node "transform" mtx))))
(dom/class? node "frame-title")
(let [shape (-> shape (assoc :modifiers modifiers) gsh/transform-shape)
zoom (get-in @st/state [:workspace-local :zoom] 1)
mtx (vwu/title-transform shape zoom)]
(let [shape (gsh/transform-shape shape modifiers)
zoom (get-in @st/state [:workspace-local :zoom] 1)
mtx (vwu/title-transform shape zoom)]
(override-transform-att! node "transform" mtx))
(or (= (dom/get-tag-name node) "mask")
@ -221,7 +152,7 @@
(= (dom/get-tag-name node) "pattern")
(set-transform-att! node "patternTransform" transform)
(and (some? transform) (some? node) (or (not text?) transform-text?))
(and (some? transform) (some? node))
(set-transform-att! node "transform" transform)))))))
(defn remove-transform!
@ -249,6 +180,16 @@
(dom/remove-attribute! node "data-old-transform")
(dom/remove-attribute! node "transform")))))))))
(defn adapt-text-modifiers
[modifiers shape]
(let [shape' (gsh/transform-shape shape modifiers)
scalev
(gpt/point (/ (:width shape) (:width shape'))
(/ (:height shape) (:height shape')))]
;; Reverse the change in size so we can recalculate the layout
(-> modifiers
(ctm/resize scalev (-> shape' :points first) (:transform shape') (:transform-inverse shape')))))
(defn use-dynamic-modifiers
[objects node modifiers]
@ -259,41 +200,71 @@
(when (some? modifiers)
(d/mapm (fn [id {modifiers :modifiers}]
(let [shape (get objects id)
center (gsh/center-shape shape)
modifiers (cond-> modifiers
;; For texts we only use the displacement because
;; resize needs to recalculate the text layout
(= :text (:type shape))
(select-keys [:displacement :rotation]))]
(gsh/modifiers->transform center modifiers)))
adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? modifiers)))
modifiers (cond-> modifiers adapt-text? (adapt-text-modifiers shape))]
(ctm/modifiers->transform modifiers)))
modifiers))))
add-children (mf/use-memo (mf/deps modifiers) #(ctm/added-children-frames modifiers))
add-children (hooks/use-equal-memo add-children)
add-children-prev (hooks/use-previous add-children)
shapes
(mf/use-memo
(mf/deps transforms)
(fn []
(->> (keys transforms)
(filter #(some? (get transforms %)))
(mapv (d/getf objects)))))
prev-shapes (mf/use-var nil)
prev-modifiers (mf/use-var nil)
prev-transforms (mf/use-var nil)]
(mf/use-effect
(mf/deps add-children)
(fn []
(doseq [{:keys [frame shape]} add-children-prev]
(let [frame-node (get-shape-node node frame)
shape-node (get-shape-node shape)
mirror-node (dom/query frame-node (dm/fmt ".mirror-shape[href='#shape-%'" shape))]
(when mirror-node (.remove mirror-node))
(dom/remove-attribute! (dom/get-parent shape-node) "display")))
(doseq [{:keys [frame shape]} add-children]
(let [frame-node (get-shape-node node frame)
shape-node (get-shape-node shape)
use-node
(.createElementNS globals/document "http://www.w3.org/2000/svg" "use")
contents-node
(or (dom/query frame-node ".frame-children") frame-node)]
(dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape))
(dom/add-class! use-node "mirror-shape")
(dom/append-child! contents-node use-node)
(dom/set-attribute! (dom/get-parent shape-node) "display" "none")))))
(mf/use-layout-effect
(mf/deps transforms)
(fn []
(let [is-prev-val? (d/not-empty? @prev-modifiers)
is-cur-val? (d/not-empty? modifiers)]
(when (and (not is-prev-val?) is-cur-val?)
(start-transform! node shapes))
(let [curr-shapes-set (into #{} (map :id) shapes)
prev-shapes-set (into #{} (map :id) @prev-shapes)
(when is-cur-val?
new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %))))
removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))]
(when (d/not-empty? new-shapes)
(start-transform! node new-shapes))
(when (d/not-empty? shapes)
(update-transform! node shapes transforms modifiers))
(when (and is-prev-val? (not is-cur-val?))
(remove-transform! node @prev-shapes))
(when (d/not-empty? removed-shapes)
(remove-transform! node @prev-shapes)))
(reset! prev-modifiers modifiers)
(reset! prev-transforms transforms)
(reset! prev-shapes shapes))))))
(reset! prev-modifiers modifiers)
(reset! prev-transforms transforms)
(reset! prev-shapes shapes)))))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.rows.stroke-row
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.hooks :as h]
@ -132,9 +133,10 @@
:on-close (close-caps-select start-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state)
:left (:left @start-caps-state)}}
(for [[value label separator] (stroke-cap-names)]
(for [[idx [value label separator]] (d/enumerate (stroke-cap-names))]
(let [img (value->img value)]
[:li {:class (dom/classnames :separator separator)
[:li {:key (dm/str "start-cap-" idx)
:class (dom/classnames :separator separator)
:on-click #(on-stroke-cap-start-change index value)}
(when img [:img {:src (value->img value)}])
label]))]]
@ -151,9 +153,10 @@
:on-close (close-caps-select end-caps-state)}
[:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state)
:left (:left @end-caps-state)}}
(for [[value label separator] (stroke-cap-names)]
(for [[idx [value label separator]] (d/enumerate (stroke-cap-names))]
(let [img (value->img value)]
[:li {:class (dom/classnames :separator separator)
[:li {:key (dm/str "end-cap-" idx)
:class (dom/classnames :separator separator)
:on-click #(on-stroke-cap-end-change index value)}
(when img [:img {:src (value->img value)}])
label]))]]])]))

View file

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

View file

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

View file

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

View file

@ -62,7 +62,8 @@
:is-layout-container? false
:values layout-item-values}])
[:& constraints-menu {:ids constraint-ids :values constraint-values}]
(when (not is-layout-child?)
[:& constraints-menu {:ids constraint-ids :values constraint-values}])
[:& layer-menu {:type type :ids layer-ids :values layer-values}]
(when-not (empty? fill-ids)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -278,7 +278,7 @@
frame]} (use-guide handle-change-position get-hover-frame zoom guide)
base-frame (or frame hover-frame)
frame (gsh/transform-shape (merge base-frame frame-modifier))
frame (gsh/transform-shape base-frame frame-modifier)
move-vec (gpt/to-vec (gpt/point (:x base-frame) (:y base-frame))
(gpt/point (:x frame) (:y frame)))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -67,6 +67,15 @@
;; Disable frame thumbnails
:disable-frame-thumbnails
;; Enable a widget to show the auto-layout drop-zones
:layout-drop-zones
;; Display the layout lines
:layout-lines
;; Makes the pixel grid red so its more visibile
:pixel-grid
})
;; These events are excluded when we activate the :events flag
@ -74,7 +83,7 @@
#{:app.main.data.workspace.notifications/handle-pointer-update
:app.main.data.workspace.selection/change-hover-state})
(defonce ^:dynamic *debug* (atom #{#_:events #_:text-outline}))
(defonce ^:dynamic *debug* (atom #{#_:events}))
(defn debug-all! [] (reset! *debug* debug-options))
(defn debug-none! [] (reset! *debug* #{}))
@ -294,9 +303,16 @@
num-nodes (->> (dom/seq-nodes root-node) count)]
#js {:number num-nodes}))
#_(defn modif->js
(defn modif->js
[modif-tree objects]
(clj->js (into {}
(map (fn [[k v]]
[(get-in objects [k :name]) v]))
modif-tree)))
(defn ^:export dump-modifiers
[]
(let [page-id (get @st/state :current-page-id)
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])]
(.log js/console (modif->js (:workspace-modifiers @st/state) objects)))
nil)

View file

@ -3827,19 +3827,19 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Erweiterte Optionen"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-max-h"
msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Max.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-max-w"
msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Max.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-min-h"
msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-min-w"
msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
@ -3847,19 +3847,19 @@ msgid "workspace.options.layout-item.title"
msgstr "Elementgröße ändern"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-max-h"
msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maximale Höhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-max-w"
msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maximale Breite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-min-h"
msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Mindesthöhe"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-min-w"
msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Mindestbreite"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@ -4701,4 +4701,4 @@ msgid "workspace.updates.update"
msgstr "Aktualisieren"
msgid "workspace.viewport.click-to-close-path"
msgstr "Klicken Sie, um den Pfad zu schließen"
msgstr "Klicken Sie, um den Pfad zu schließen"

View file

@ -3490,35 +3490,35 @@ msgid "workspace.options.layout-item.advanced-ops"
msgstr "Advanced options"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-max-h"
msgid "workspace.options.layout-item.layout-item-max-h"
msgstr "Max.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-max-w"
msgid "workspace.options.layout-item.layout-item-max-w"
msgstr "Max.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-min-h"
msgid "workspace.options.layout-item.layout-item-min-h"
msgstr "Min.Height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.layout-min-w"
msgid "workspace.options.layout-item.layout-item-min-w"
msgstr "Min.Width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-max-h"
msgid "workspace.options.layout-item.title.layout-item-max-h"
msgstr "Maximum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-max-w"
msgid "workspace.options.layout-item.title.layout-item-max-w"
msgstr "Maximum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-min-h"
msgid "workspace.options.layout-item.title.layout-item-min-h"
msgstr "Minimum height"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
msgid "workspace.options.layout-item.title.layout-min-w"
msgid "workspace.options.layout-item.title.layout-item-min-w"
msgstr "Minimum width"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@ -4077,6 +4077,14 @@ msgstr "Update main components"
msgid "workspace.shape.menu.update-main"
msgstr "Update main component"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.add-flex"
msgstr "Add layout flex"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.remove-flex"
msgstr "Remove layout flex"
msgid "workspace.sidebar.collapse"
msgstr "Collapse sidebar"

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