Change resize to use DOM transformations

This commit is contained in:
alonso.torres 2021-12-21 12:32:04 +01:00
parent fa09fff2b5
commit b2211aec59
38 changed files with 839 additions and 717 deletions

View file

@ -51,7 +51,7 @@
(get-in file [:data :components (:current-component-id file) :objects])
(get-in file [:data :pages-index (:current-page-id file) :objects]))))
(defn- lookup-shape [file shape-id]
(defn lookup-shape [file shape-id]
(-> (lookup-objects file)
(get shape-id)))
@ -321,16 +321,11 @@
(update :parent-stack pop))))
(defn create-shape [file type data]
(let [frame-id (:current-frame-id file)
frame (when-not (= frame-id root-frame)
(lookup-shape file frame-id))
obj (-> (init/make-minimal-shape type)
(let [obj (-> (init/make-minimal-shape type)
(merge data)
(check-name file :type)
(setup-selrect)
(d/without-nils))
obj (cond-> obj
frame (gsh/translate-from-frame frame))]
(d/without-nils))]
(-> file
(commit-shape obj)
(assoc :last-id (:id obj))

View file

@ -10,6 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes.bool :as gsb]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.intersect :as gin]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr]
@ -163,8 +164,12 @@
(d/export gtr/rotation-modifiers)
(d/export gtr/merge-modifiers)
(d/export gtr/transform-shape)
(d/export gtr/calc-transformed-parent-rect)
(d/export gtr/calc-child-modifiers)
(d/export gtr/transform-selrect)
(d/export gtr/modifiers->transform)
(d/export gtr/empty-modifiers?)
;; Constratins
(d/export gct/calc-child-modifiers)
;; PATHS
(d/export gsp/content->selrect)

View file

@ -50,6 +50,22 @@
:width width
:height height})
(defn make-centered-selrect
"Creates a rect given a center and a width and height"
[center width height]
(let [x1 (- (:x center) (/ width 2.0))
y1 (- (:y center) (/ height 2.0))
x2 (+ x1 width)
y2 (+ y1 height)]
{:x x1
:y y1
:x1 x1
:x2 x2
:y1 y1
:y2 y2
:width width
:height height}))
(defn transform-points
([points matrix]
(transform-points points nil matrix))

View file

@ -0,0 +1,182 @@
;; 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.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.transforms :as gtr]
[app.common.math :as mth]
[app.common.pages.spec :as spec]))
;; Auxiliary methods to work in an specifica axis
(defn get-delta-start [axis rect tr-rect]
(if (= :x axis)
(- (:x1 tr-rect) (:x1 rect))
(- (:y1 tr-rect) (:y1 rect))))
(defn get-delta-end [axis rect tr-rect]
(if (= :x axis)
(- (:x2 tr-rect) (:x2 rect))
(- (:y2 tr-rect) (:y2 rect))))
(defn get-delta-size [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))
(- (:y tr-center) (:y center))))
(defn get-displacement
([axis delta]
(get-displacement axis delta 0 0))
([axis delta init-x init-y]
(if (= :x axis)
(gpt/point (+ init-x delta) init-y)
(gpt/point init-x (+ init-y delta)))))
(defn get-scale [axis scale]
(if (= :x axis)
(gpt/point scale 1)
(gpt/point 1 scale)))
(defn get-size [axis rect]
(if (= :x axis)
(:width rect)
(:height rect)))
;; 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)}
{})))
(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)}
{})))
(defmethod constraint-modifier :fixed
[_ axis parent child _ transformed-parent-rect]
(let [parent-rect (:selrect parent)
child-rect (:selrect child)
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)
child-center (gco/center-rect child-rect)]
(if (or (not (mth/almost-zero? delta-start))
(not (mth/almost-zero? delta-size)))
{:displacement (get-displacement axis delta-start)
:resize-origin (-> (get-displacement axis delta-start (:x1 child-rect) (:y1 child-rect))
(gtr/transform-point-center child-center (:transform child (gmt/matrix))))
:resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))}
{})))
(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 (mth/close? (axis resize-vector) 1)))
(assoc :resize-origin (:resize-origin modifiers)
:resize-vector (if (= :x axis)
(gpt/point (:x resize-vector) 1)
(gpt/point 1 (:y resize-vector))))
(and (= :y axis) (some? resize-vector-2)
(not (mth/close? (:y resize-vector-2) 1)))
(assoc :resize-origin (:resize-origin-2 modifiers)
:resize-vector (gpt/point 1 (:y resize-vector-2)))
(some? displacement)
(assoc :displacement
(get-displacement axis (-> (gpt/point 0 0)
(gpt/transform displacement)
(gpt/transform (:resize-transform-inverse modifiers (gmt/matrix)))
axis))))))
(defmethod constraint-modifier :default [_ _ _ _ _]
{})
(def const->type+axis
{:left :start
:top :start
:right :end
:bottom :end
:leftright :fixed
:topbottom :fixed
:center :center
:scale :scale})
(defn calc-child-modifiers
[parent child modifiers ignore-constraints transformed-parent-rect]
(let [constraints-h
(if-not ignore-constraints
(:constraints-h child (spec/default-constraints-h child))
:scale)
constraints-v
(if-not ignore-constraints
(:constraints-v child (spec/default-constraints-v 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)]
;; 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-> {}
(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))
:always
(gmt/translate-matrix)))
(: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)))
(: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)))
(:resize-transform modifiers)
(assoc :resize-transform (:resize-transform modifiers)
:resize-transform-inverse (:resize-transform-inverse modifiers)))))

View file

@ -196,8 +196,8 @@
[point {:keys [cx cy rx ry transform]}]
(let [center (gpt/point cx cy)
transform (gmt/transform-in center transform)
{px :x py :y} (gpt/transform point transform)
transform (when (some? transform) (gmt/transform-in center transform))
{px :x py :y} (if (some? transform) (gpt/transform point transform) point)
;; Ellipse inequality formula
;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse
v (+ (/ (mth/sq (- px cx))
@ -256,10 +256,10 @@
"Checks if a set of lines intersect with an ellipse in any point"
[rect-lines {:keys [cx cy transform] :as ellipse-data}]
(let [center (gpt/point cx cy)
transform (gmt/transform-in center transform)]
transform (when (some? transform) (gmt/transform-in center transform))]
(some (fn [[p1 p2]]
(let [p1 (gpt/transform p1 transform)
p2 (gpt/transform p2 transform)]
(let [p1 (if (some? transform) (gpt/transform p1 transform) p1)
p2 (if (some? transform) (gpt/transform p2 transform) p2)]
(intersects-line-ellipse? [p1 p2] ellipse-data))) rect-lines)))
(defn overlaps-ellipse?

View file

@ -361,25 +361,24 @@
(update :height #(if (mth/almost-zero? %) 1 %)))))
(defn move-content [content move-vec]
(let [set-tr (fn [params px py]
(let [tr-point (-> (gpt/point (get params px) (get params py))
(gpt/add move-vec))]
(assoc params
px (:x tr-point)
py (:y tr-point))))
(let [dx (:x move-vec)
dy (:y move-vec)
set-tr
(fn [params px py]
(assoc params
px (+ (get params px) dx)
py (+ (get params py) dy)))
transform-params
(fn [{:keys [x c1x c2x] :as params}]
(fn [params]
(cond-> params
(not (nil? x)) (set-tr :x :y)
(not (nil? c1x)) (set-tr :c1x :c1y)
(not (nil? c2x)) (set-tr :c2x :c2y)))]
(contains? params :x) (set-tr :x :y)
(contains? params :c1x) (set-tr :c1x :c1y)
(contains? params :c2x) (set-tr :c2x :c2y)))]
(->> content
(mapv (fn [cmd]
(cond-> cmd
(map? cmd)
(update :params transform-params)))))))
(mapv #(d/update-when % :params transform-params)))))
(defn transform-content
[content transform]
@ -393,11 +392,13 @@
transform-params
(fn [{:keys [x c1x c2x] :as params}]
(cond-> params
(not (nil? x)) (set-tr :x :y)
(not (nil? c1x)) (set-tr :c1x :c1y)
(not (nil? c2x)) (set-tr :c2x :c2y)))]
(some? x) (set-tr :x :y)
(some? c1x) (set-tr :c1x :c1y)
(some? c2x) (set-tr :c2x :c2y)))]
(mapv #(update % :params transform-params) content)))
(into []
(map #(update % :params transform-params))
content)))
(defn segments->content
([segments]

View file

@ -129,3 +129,11 @@
(<= (:x2 sr2) (:x2 sr1))
(>= (:y1 sr2) (:y1 sr1))
(<= (:y2 sr2) (:y2 sr1))))
(defn round-selrect
[selrect]
(-> selrect
(update :x mth/round)
(update :y mth/round)
(update :width mth/round)
(update :height mth/round)))

View file

@ -14,20 +14,23 @@
[app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
[app.common.text :as txt]))
(def ^:dynamic *skip-adjust* false)
;; --- Relative Movement
(defn- move-selrect [selrect {dx :x dy :y}]
(-> selrect
(d/update-when :x + dx)
(d/update-when :y + dy)
(d/update-when :x1 + dx)
(d/update-when :y1 + dy)
(d/update-when :x2 + dx)
(d/update-when :y2 + dy)))
(defn- move-selrect [selrect pt]
(let [dx (.-x pt)
dy (.-y pt)]
(-> selrect
(update :x + dx)
(update :y + dy)
(update :x1 + dx)
(update :y1 + dy)
(update :x2 + dx)
(update :y2 + dy))))
(defn- move-points [points move-vec]
(->> points
@ -171,9 +174,11 @@
(defn calculate-adjust-matrix
"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-pathn-temp`.
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]
(calculate-adjust-matrix points-temp points-rec false false))
([points-temp points-rec flip-x flip-y]
(let [center (gco/center-points points-temp)
@ -211,64 +216,73 @@
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/matrix)
(gmt/scale (gpt/point (/ 1 w3) (/ 1 h3)))
(gmt/skew (- skew-angle) 0)
(gmt/rotate (- rotation-angle)))]
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- 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 round-coords?]
;; FIXME: Improve performance
(let [points (-> shape :points (gco/transform-points transform))
center (gco/center-points points)
(defn is-rotated?
[[a b _c _d]]
;; true if either a-b or c-d are parallel to the axis
(not (mth/close? (:y a) (:y b))))
;; Reverse the current transformation stack to get the base rectangle
tr-inverse (:transform-inverse shape (gmt/matrix))
(defn- adjust-rotated-transform
[{:keys [transform transform-inverse flip-x flip-y]} points]
(let [center (gco/center-points points)
points-temp (gco/transform-points points center tr-inverse)
points-temp (cond-> points
(some? transform-inverse)
(gco/transform-points center transform-inverse))
points-temp-dim (calculate-dimensions points-temp)
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
;; to have this width, height, x, y
rect-shape (-> (gco/make-centered-rect
center
(:width points-temp-dim)
(:height points-temp-dim))
(update :width max 1)
(update :height max 1))
new-width (max 1 (:width points-temp-dim))
new-height (max 1 (:height points-temp-dim))
selrect (gco/make-centered-selrect center new-width new-height)
rect-points (gpr/rect->points rect-shape)
rect-points (gpr/rect->points selrect)
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)]
[matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points (:flip-x shape) (:flip-y shape))
[selrect
(if transform (gmt/multiply transform matrix) matrix)
(if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)]))
rect-shape (cond-> rect-shape
round-coords?
(-> (update :x mth/round)
(update :y mth/round)
(update :width mth/round)
(update :height mth/round)))
(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 round-coords?]
shape (cond
(= :path (:type shape))
(-> shape
(update :content #(gpa/transform-content % transform)))
(let [points' (:points shape)
points (gco/transform-points points' transform-mtx)
path? (= (:type shape) :path)
rotated? (is-rotated? points)
:else
(-> shape
(merge rect-shape)))
[selrect transform transform-inverse]
(if (not rotated?)
[(gpr/points->selrect points) nil nil]
(adjust-rotated-transform shape points))
selrect (cond-> selrect
round-coords? gpr/round-selrect)
;; Redondear los points?
base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)]
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)
rotation (mod (+ base-rotation modif-rotation) 360)]
(as-> shape $
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
(assoc $ :points (into [] points))
(assoc $ :selrect (gpr/rect->selrect rect-shape))
(assoc $ :rotation (mod (+ base-rotation modif-rotation) 360)))))
(-> shape
(cond-> path?
(update :content gpa/transform-content transform-mtx))
(cond-> (not path?)
(-> (merge (select-keys selrect [:x :y :width :height]))))
(cond-> transform
(-> (assoc :transform transform)
(assoc :transform-inverse transform-inverse)))
(assoc :selrect selrect)
(assoc :points points)
(assoc :rotation rotation))))
(defn- update-group-viewbox
"Updates the viewbox for groups imported from SVG's"
@ -402,53 +416,54 @@
(def merge-modifiers (memoize merge-modifiers*))
(defn- modifiers->transform
[center modifiers]
(let [ds-modifier (:displacement modifiers (gmt/matrix))
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
{res-x-2 :x res-y-2 :y} (:resize-vector-2 modifiers (gpt/point 1 1))
(defn modifiers->transform
([modifiers]
(modifiers->transform nil modifiers))
;; Normalize x/y vector coordinates because scale by 0 is infinite
res-x (normalize-scale res-x)
res-y (normalize-scale res-y)
resize (gpt/point res-x res-y)
([center modifiers]
(let [displacement (:displacement 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))
res-x-2 (normalize-scale res-x-2)
res-y-2 (normalize-scale res-y-2)
resize-2 (gpt/point res-x-2 res-y-2)
;; 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))))
origin (:resize-origin modifiers (gpt/point 0 0))
origin-2 (:resize-origin-2 modifiers (gpt/point 0 0))
resize-2 (when (some? resize-v2)
(gpt/point (normalize-scale (:x resize-v2))
(normalize-scale (:y resize-v2))))
resize-transform (:resize-transform modifiers (gmt/matrix))
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix))
rt-modif (or (:rotation modifiers) 0)
center (gpt/transform center ds-modifier)
resize-transform (:resize-transform modifiers (gmt/matrix))
resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix))
transform (-> (gmt/matrix)
rt-modif (:rotation modifiers)]
;; Applies the current resize transformation
(gmt/translate origin)
(gmt/multiply resize-transform)
(gmt/scale resize)
(gmt/multiply resize-transform-inverse)
(gmt/translate (gpt/negate origin))
(cond-> (gmt/matrix)
(some? displacement)
(gmt/multiply displacement)
(gmt/translate origin-2)
(gmt/multiply resize-transform)
(gmt/scale resize-2)
(gmt/multiply resize-transform-inverse)
(gmt/translate (gpt/negate origin-2))
(some? resize-1)
(-> (gmt/translate origin-1)
(gmt/multiply resize-transform)
(gmt/scale resize-1)
(gmt/multiply resize-transform-inverse)
(gmt/translate (gpt/negate origin-1)))
;; Applies the stacked transformations
(gmt/translate center)
(gmt/multiply (gmt/rotate-matrix rt-modif))
(gmt/translate (gpt/negate center))
(some? resize-2)
(-> (gmt/translate origin-2)
(gmt/multiply resize-transform)
(gmt/scale resize-2)
(gmt/multiply resize-transform-inverse)
(gmt/translate (gpt/negate origin-2)))
;; Displacement
(gmt/multiply ds-modifier))]
transform))
(some? rt-modif)
(-> (gmt/translate center)
(gmt/multiply (gmt/rotate-matrix rt-modif))
(gmt/translate (gpt/negate center)))))))
(defn- set-flip [shape modifiers]
(let [rx (or (get-in modifiers [:resize-vector :x])
@ -492,51 +507,58 @@
%)))
shape))
(defn -transform-shape
[shape {:keys [round-coords?]
:or {round-coords? true}}]
(if (and (contains? shape :modifiers) (empty-modifiers? (:modifiers shape)))
(dissoc shape :modifiers)
(let [shape (apply-displacement shape)
center (gco/center-shape shape)
modifiers (:modifiers shape)]
(if (and (not (empty-modifiers? modifiers)) center)
(let [transform (modifiers->transform center modifiers)]
(-> shape
(set-flip modifiers)
(apply-transform transform round-coords?)
(apply-text-resize modifiers)
(dissoc :modifiers)))
shape))))
(def transform-shape* (memoize -transform-shape))
(defn apply-modifiers
[shape modifiers round-coords?]
(let [center (gco/center-shape shape)
transform (modifiers->transform center modifiers)]
(apply-transform shape transform round-coords?)))
(defn transform-shape
([shape]
(transform-shape* shape nil))
([shape options]
(transform-shape* shape options)))
(transform-shape shape nil))
(defn calc-transformed-parent-rect
[{:keys [selrect] :as shape} {:keys [displacement resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}]
([shape {:keys [round-coords?] :or {round-coords? true}}]
(let [modifiers (:modifiers shape)]
(cond
(nil? modifiers)
shape
(empty-modifiers? modifiers)
(dissoc shape :modifiers)
:else
(let [shape (apply-displacement shape)
modifiers (:modifiers shape)]
(cond-> shape
(not (empty-modifiers? modifiers))
(-> (set-flip modifiers)
(apply-modifiers modifiers round-coords?)
(apply-text-resize modifiers))
:always
(dissoc :modifiers)))))))
(defn transform-selrect
[selrect {:keys [displacement 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))
displacement
(when (some? displacement)
(-> (gpt/point 0 0)
(gmt/multiply resize-transform-inverse displacement)
#_(-> (gpt/point 0 0)
(gpt/transform displacement)
(gpt/transform resize-transform-inverse)
(gmt/translate-matrix)))
resize-origin
(when (some? resize-origin)
(transform-point-center resize-origin (gco/center-shape shape) resize-transform-inverse))
(transform-point-center resize-origin (gco/center-selrect selrect) resize-transform-inverse))
resize-origin-2
(when (some? resize-origin-2)
(transform-point-center resize-origin-2 (gco/center-shape shape) resize-transform-inverse))]
(transform-point-center resize-origin-2 (gco/center-selrect selrect) resize-transform-inverse))]
(if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2))
selrect
@ -557,177 +579,11 @@
:always
(gpr/points->selrect)))))
(defn calc-child-modifiers
"Given the modifiers to apply to the parent, calculate the corresponding
modifiers for the child, depending on the child constraints."
([parent child parent-modifiers ignore-constraints]
(let [transformed-parent-rect (calc-transformed-parent-rect parent parent-modifiers )]
(calc-child-modifiers parent child parent-modifiers ignore-constraints transformed-parent-rect)))
([parent child parent-modifiers ignore-constraints transformed-parent-rect]
(let [parent-rect (:selrect parent)
child-rect (:selrect child)
;; Apply the modifiers to the parent's selrect, to check the difference with
;; the original, and calculate child transformations from this.
;;
;; Note that a shape's selrect is always "horizontal" (i.e. without applying
;; the shape transform, that may include some rotation and skew). Thus, to
;; apply the modifiers, we first apply to them the transform-inverse.
;; Calculate the modifiers in the horizontal and vertical directions
;; depending on the child constraints.
constraints-h (if-not ignore-constraints
(get child :constraints-h (spec/default-constraints-h child))
:scale)
constraints-v (if-not ignore-constraints
(get child :constraints-v (spec/default-constraints-v child))
:scale)
modifiers-h (case constraints-h
:left
(let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))]
(if-not (mth/almost-zero? delta-left)
{:displacement (gpt/point delta-left 0)} ;; we convert to matrix below
{}))
:right
(let [delta-right (- (:x2 transformed-parent-rect) (:x2 parent-rect))]
(if-not (mth/almost-zero? delta-right)
{:displacement (gpt/point delta-right 0)}
{}))
:leftright
(let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))
delta-width (- (:width transformed-parent-rect) (:width parent-rect))]
(if (or (not (mth/almost-zero? delta-left))
(not (mth/almost-zero? delta-width)))
{:displacement (gpt/point delta-left 0)
:resize-origin (-> (gpt/point (+ (:x1 child-rect) delta-left)
(:y1 child-rect))
(transform-point-center
(gco/center-rect child-rect)
(:transform child (gmt/matrix))))
:resize-vector (gpt/point (/ (+ (:width child-rect) delta-width)
(:width child-rect)) 1)}
{}))
:center
(let [parent-center (gco/center-rect parent-rect)
transformed-parent-center (gco/center-rect transformed-parent-rect)
delta-center (- (:x transformed-parent-center) (:x parent-center))]
(if-not (mth/almost-zero? delta-center)
{:displacement (gpt/point delta-center 0)}
{}))
:scale
(cond-> {}
(and (:resize-vector parent-modifiers)
(not (mth/close? (:x (:resize-vector parent-modifiers)) 1)))
(assoc :resize-origin (:resize-origin parent-modifiers)
:resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1))
;; resize-vector-2 is always for vertical modifiers, so no need to
;; check it here.
(:displacement parent-modifiers)
(assoc :displacement
(gpt/point (-> (gpt/point 0 0)
(gpt/transform (:displacement parent-modifiers))
(gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix)))
(:x))
0)))
{})
modifiers-v (case constraints-v
:top
(let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect))]
(if-not (mth/almost-zero? delta-top)
{:displacement (gpt/point 0 delta-top)} ;; we convert to matrix below
{}))
:bottom
(let [delta-bottom (- (:y2 transformed-parent-rect) (:y2 parent-rect))]
(if-not (mth/almost-zero? delta-bottom)
{:displacement (gpt/point 0 delta-bottom)}
{}))
:topbottom
(let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect))
delta-height (- (:height transformed-parent-rect) (:height parent-rect))]
(if (or (not (mth/almost-zero? delta-top))
(not (mth/almost-zero? delta-height)))
{:displacement (gpt/point 0 delta-top)
:resize-origin (-> (gpt/point (:x1 child-rect)
(+ (:y1 child-rect) delta-top))
(transform-point-center
(gco/center-rect child-rect)
(:transform child (gmt/matrix))))
:resize-vector (gpt/point 1 (/ (+ (:height child-rect) delta-height)
(:height child-rect)))}
{}))
:center
(let [parent-center (gco/center-rect parent-rect)
transformed-parent-center (gco/center-rect transformed-parent-rect)
delta-center (- (:y transformed-parent-center) (:y parent-center))]
(if-not (mth/almost-zero? delta-center)
{:displacement (gpt/point 0 delta-center)}
{}))
:scale
(cond-> {}
(and (:resize-vector parent-modifiers)
(not (mth/close? (:y (:resize-vector parent-modifiers)) 1)))
(assoc :resize-origin (:resize-origin parent-modifiers)
:resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers))))
;; If there is a resize-vector-2, this means that we come from a recursive
;; call, and the resize-vector has no vertical data, so we may override it.
(and (:resize-vector-2 parent-modifiers)
(not (mth/close? (:y (:resize-vector-2 parent-modifiers)) 1)))
(assoc :resize-origin (:resize-origin-2 parent-modifiers)
:resize-vector (gpt/point 1 (:y (:resize-vector-2 parent-modifiers))))
(:displacement parent-modifiers)
(assoc :displacement
(gpt/point 0 (-> (gpt/point 0 0)
(gpt/transform (:displacement parent-modifiers))
(gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix)))
(:y)))))
{})]
;; 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-> {}
(or (:displacement modifiers-h) (:displacement modifiers-v))
(assoc :displacement (gmt/translate-matrix
(-> (gpt/point (get (:displacement modifiers-h) :x 0)
(get (:displacement modifiers-v) :y 0))
(gpt/transform
(:resize-transform parent-modifiers (gmt/matrix))))))
(:resize-vector modifiers-h)
(assoc :resize-origin (:resize-origin modifiers-h)
:resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1)
(get (:resize-vector modifiers-h) :y 1)))
(:resize-vector modifiers-v)
(assoc :resize-origin-2 (:resize-origin modifiers-v)
:resize-vector-2 (gpt/point (get (:resize-vector modifiers-v) :x 1)
(get (:resize-vector modifiers-v) :y 1)))
(:resize-transform parent-modifiers)
(assoc :resize-transform (:resize-transform parent-modifiers)
:resize-transform-inverse (:resize-transform-inverse parent-modifiers))))))
(defn selection-rect
"Returns a rect that contains all the shapes and is aware of the
rotation of each shape. Mainly used for multiple selection."
[shapes]
(->> shapes
(transform-shape)
(map (comp gpr/points->selrect :points))
(map (comp gpr/points->selrect :points transform-shape))
(gpr/join-selrects)))

View file

@ -21,14 +21,15 @@
[key]
(- (timestamp) (get @measures key)))
#?(:cljs
(defn benchmark
"A helper function for perform a unitari benchmark on JS/CLJS. It
(defn benchmark
"A helper function for perform a unitari benchmark on JS/CLJS. It
uses browser native api so it only suitable to be executed in
browser."
[& {:keys [f iterations name]
:or {iterations 10000}}]
(let [end-mark (str name ":end")]
[& _options]
#?(:cljs
(let [{:keys [f iterations name]
:or {iterations 10000}} _options
end-mark (str name ":end")]
(println "=> benchmarking:" name)
(println "--> warming up:" iterations)
(loop [i iterations]
@ -51,3 +52,4 @@
#js {:duration duration
:avg avg}))))