Merge pull request #2610 from penpot/alotor-performance-enhance

Alotor performance enhance
This commit is contained in:
Andrey Antukh 2022-11-30 10:42:09 +01:00 committed by GitHub
commit c98635bca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1255 additions and 474 deletions

View File

@ -57,9 +57,13 @@
([a & more] (into (queue) (cons a more))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Structures Manipulation
;; Data Structures Access & Manipulation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn not-empty?
[coll]
(boolean (seq coll)))
(defn editable-collection?
[m]
#?(:clj (instance? clojure.lang.IEditableCollection m)
@ -145,6 +149,16 @@
(rest items)
(conj! res [idx (first items)]))))))
(defn group-by
([kf coll] (group-by kf identity [] coll))
([kf vf coll] (group-by kf vf [] coll))
([kf vf iv coll]
(let [conj (fnil conj iv)]
(reduce (fn [result item]
(update result (kf item) conj (vf item)))
{}
coll))))
(defn seek
([pred coll]
(seek pred coll nil))
@ -243,12 +257,12 @@
(defn filterm
"Filter values of a map that satisfy a predicate"
[pred coll]
(into {} (filter pred coll)))
(into {} (filter pred) coll))
(defn removem
"Remove values of a map that satisfy a predicate"
[pred coll]
(into {} (remove pred coll)))
(into {} (remove pred) coll))
(defn map-perm
"Maps a function to each pair of values that can be combined inside the
@ -373,6 +387,81 @@
(do (vswap! seen conj input*)
(rf result input)))))))))
(defn with-next
"Given a collection will return a new collection where each element
is paired with the next item in the collection
(with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]"
[coll]
(map vector
coll
(c/concat (rest coll) [nil])))
(defn with-prev
"Given a collection will return a new collection where each element
is paired with the previous item in the collection
(with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]"
[coll]
(map vector
coll
(c/cons nil coll)))
(defn with-prev-next
"Given a collection will return a new collection where every item is paired
with the previous and the next item of a collection
(with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]"
[coll]
(map vector
coll
(c/cons nil coll)
(c/concat (rest coll) [nil])))
(defn deep-mapm
"Applies a map function to an associative map and recurses over its children
when it's a vector or a map"
[mfn m]
(let [do-map
(fn [entry]
(let [[k v] (mfn entry)]
(cond
(or (vector? v) (map? v))
[k (deep-mapm mfn v)]
:else
(mfn [k v]))))]
(cond
(map? m)
(into {} (map do-map) m)
(vector? m)
(into [] (map (partial deep-mapm mfn)) m)
:else
m)))
(defn iteration
"Creates a totally lazy seqable via repeated calls to step, a
function of some (continuation token) 'k'. The first call to step
will be passed initk, returning 'ret'. If (somef ret) is true, (vf
ret) will be included in the iteration, else iteration will
terminate and vf/kf will not be called. If (kf ret) is non-nil it
will be passed to the next step call, else iteration will terminate.
This can be used e.g. to consume APIs that return paginated or batched data.
step - (possibly impure) fn of 'k' -> 'ret'
:somef - fn of 'ret' -> logical true/false, default 'some?'
:vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity'
:kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity'
:initk - the first value passed to step, default 'nil'
It is presumed that step with non-initk is
unreproducible/non-idempotent. If step with initk is unreproducible
it is on the consumer to not consume twice."
[& args]
(->> (apply c/iteration args)
(concat-all)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Parsing / Conversion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -439,8 +528,9 @@
(or val default))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Parsing / Conversion
;; Utilities
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn nilf
"Returns a new function that if you pass nil as any argument will
return nil"
@ -457,22 +547,32 @@
(defn num?
"Checks if a value `val` is a number but not an Infinite or NaN"
([val]
(and (number? val)
(mth/finite? val)
(not (mth/nan? val))))
([val & vals]
(and (num? val)
(->> vals (every? num?)))))
([a]
(mth/finite? a))
([a b]
(and (mth/finite? a)
(mth/finite? b)))
([a b c]
(and (mth/finite? a)
(mth/finite? b)
(mth/finite? c)))
([a b c d]
(and (mth/finite? a)
(mth/finite? b)
(mth/finite? c)
(mth/finite? d)))
([a b c d & others]
(and (mth/finite? a)
(mth/finite? b)
(mth/finite? c)
(mth/finite? d)
(every? mth/finite? others))))
(defn check-num
"Function that checks if a number is nil or nan. Will return 0 when not
valid and the number otherwise."
([v]
(check-num v 0))
([v default]
(if (num? v) v default)))
([v] (mth/finite v 0))
([v default] (mth/finite v default)))
(defn any-key? [element & rest]
(some #(contains? element %) rest))
@ -494,34 +594,6 @@
(or default-value
(str maybe-keyword)))))
(defn with-next
"Given a collection will return a new collection where each element
is paired with the next item in the collection
(with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]"
[coll]
(map vector
coll
(c/concat (rest coll) [nil])))
(defn with-prev
"Given a collection will return a new collection where each element
is paired with the previous item in the collection
(with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]"
[coll]
(map vector
coll
(c/concat [nil] coll)))
(defn with-prev-next
"Given a collection will return a new collection where every item is paired
with the previous and the next item of a collection
(with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]"
[coll]
(map vector
coll
(c/concat [nil] coll)
(c/concat (rest coll) [nil])))
(defn prefix-keyword
"Given a keyword and a prefix will return a new keyword with the prefix attached
(prefix-keyword \"prefix\" :test) => :prefix-test"
@ -612,33 +684,6 @@
(recur (inc counter))
candidate))))))))
(defn deep-mapm
"Applies a map function to an associative map and recurses over its children
when it's a vector or a map"
[mfn m]
(let [do-map
(fn [entry]
(let [[k v] (mfn entry)]
(cond
(or (vector? v) (map? v))
[k (deep-mapm mfn v)]
:else
(mfn [k v]))))]
(cond
(map? m)
(into {} (map do-map) m)
(vector? m)
(into [] (map (partial deep-mapm mfn)) m)
:else
m)))
(defn not-empty?
[coll]
(boolean (seq coll)))
(defn kebab-keys [m]
(->> m
(deep-mapm
@ -647,40 +692,6 @@
[(keyword (str/kebab (name k))) v]
[k v])))))
(defn group-by
([kf coll] (group-by kf identity [] coll))
([kf vf coll] (group-by kf vf [] coll))
([kf vf iv coll]
(let [conj (fnil conj iv)]
(reduce (fn [result item]
(update result (kf item) conj (vf item)))
{}
coll))))
(defn iteration
"Creates a totally lazy seqable via repeated calls to step, a
function of some (continuation token) 'k'. The first call to step
will be passed initk, returning 'ret'. If (somef ret) is true, (vf
ret) will be included in the iteration, else iteration will
terminate and vf/kf will not be called. If (kf ret) is non-nil it
will be passed to the next step call, else iteration will terminate.
This can be used e.g. to consume APIs that return paginated or batched data.
step - (possibly impure) fn of 'k' -> 'ret'
:somef - fn of 'ret' -> logical true/false, default 'some?'
:vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity'
:kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity'
:initk - the first value passed to step, default 'nil'
It is presumed that step with non-initk is
unreproducible/non-idempotent. If step with initk is unreproducible
it is on the consumer to not consume twice."
[& args]
(->> (apply c/iteration args)
(concat-all)))
(defn toggle-selection
([set value]
(toggle-selection set value false))

View File

@ -107,3 +107,15 @@
(d/close! ~(first bindings))))))
`(do ~@body)
(reverse (partition 2 bindings))))
(defmacro get-prop
"A macro based, optimized variant of `get` that access the property
directly on CLJS, on CLJ works as get."
[obj prop]
;; `(do
;; (when-not (record? ~obj)
;; (js/console.trace (pr-str ~obj)))
;; (c/get ~obj ~prop)))
(if (:ns &env)
(list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop))))
`(c/get ~obj ~prop)))

View File

@ -9,6 +9,7 @@
#?(:cljs [cljs.pprint :as pp]
:clj [clojure.pprint :as pp])
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.spec :as us]
@ -123,6 +124,35 @@
([m1 m2 & others]
(reduce multiply (multiply m1 m2) others)))
(defn multiply!
[^Matrix m1 ^Matrix m2]
(let [m1a (.-a m1)
m1b (.-b m1)
m1c (.-c m1)
m1d (.-d m1)
m1e (.-e m1)
m1f (.-f m1)
m2a (.-a m2)
m2b (.-b m2)
m2c (.-c m2)
m2d (.-d m2)
m2e (.-e m2)
m2f (.-f m2)]
#?@(:cljs [(set! (.-a m1) (+ (* m1a m2a) (* m1c m2b)))
(set! (.-b m1) (+ (* m1b m2a) (* m1d m2b)))
(set! (.-c m1) (+ (* m1a m2c) (* m1c m2d)))
(set! (.-d m1) (+ (* m1b m2c) (* m1d m2d)))
(set! (.-e m1) (+ (* m1a m2e) (* m1c m2f) m1e))
(set! (.-f m1) (+ (* m1b m2e) (* m1d m2f) m1f))
m1]
:clj [(Matrix.
(+ (* m1a m2a) (* m1c m2b))
(+ (* m1b m2a) (* m1d m2b))
(+ (* m1a m2c) (* m1c m2d))
(+ (* m1b m2c) (* m1d m2d))
(+ (* m1a m2e) (* m1c m2f) m1e)
(+ (* m1b m2e) (* m1d m2f) m1f))])))
(defn add-translate
"Given two TRANSLATE matrixes (only e and f have significative
values), combine them. Quicker than multiplying them, for this
@ -147,26 +177,31 @@
(= v base))
(defn translate-matrix
([{x :x y :y :as pt}]
([pt]
(assert (gpt/point? pt))
(Matrix. 1 0 0 1 x y))
(Matrix. 1 0 0 1
(dm/get-prop pt :x)
(dm/get-prop pt :y)))
([x y]
(translate-matrix (gpt/point x y))))
(Matrix. 1 0 0 1 x y)))
(defn scale-matrix
([pt center]
(multiply (translate-matrix center)
(scale-matrix pt)
(translate-matrix (gpt/negate center))))
([{x :x y :y :as pt}]
(-> (matrix)
(multiply! (translate-matrix center))
(multiply! (scale-matrix pt))
(multiply! (translate-matrix (gpt/negate center)))))
([pt]
(assert (gpt/point? pt))
(Matrix. x 0 0 y 0 0)))
(Matrix. (dm/get-prop pt :x) 0 0 (dm/get-prop pt :y) 0 0)))
(defn rotate-matrix
([angle point] (multiply (translate-matrix point)
(rotate-matrix angle)
(translate-matrix (gpt/negate point))))
([angle point]
(-> (matrix)
(multiply! (translate-matrix point))
(multiply! (rotate-matrix angle))
(multiply! (translate-matrix (gpt/negate point)))))
([angle]
(let [a (mth/radians angle)]
(Matrix. (mth/cos a)
@ -200,11 +235,23 @@
([m scale center]
(multiply m (scale-matrix scale center))))
(defn scale!
"Apply scale transformation to the matrix."
([m scale]
(multiply! m (scale-matrix scale)))
([m scale center]
(multiply! m (scale-matrix scale center))))
(defn translate
"Apply translate transformation to the matrix."
[m pt]
(multiply m (translate-matrix pt)))
(defn translate!
"Apply translate transformation to the matrix."
[m pt]
(multiply! m (translate-matrix pt)))
(defn skew
"Apply translate transformation to the matrix."
([m angle-x angle-y]

View File

@ -11,6 +11,8 @@
:clj [clojure.pprint :as pp])
#?(:cljs [cljs.core :as c]
:clj [clojure.core :as c])
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.spec :as us]
[clojure.spec.alpha :as s]
@ -20,18 +22,20 @@
(defrecord Point [x y])
(defn s [{:keys [x y]}] (str "(" x "," y ")"))
(defn s
[pt]
(dm/str "(" (dm/get-prop pt :x) "," (dm/get-prop pt :y) ")"))
(defn point?
"Return true if `v` is Point instance."
[v]
(or (instance? Point v)
(and (map? v) (contains? v :x) (contains? v :y))))
(instance? Point v))
(s/def ::x ::us/safe-number)
(s/def ::y ::us/safe-number)
(s/def ::point-attrs (s/keys :req-un [::x ::y]))
(s/def ::point-attrs
(s/keys :req-un [::x ::y]))
(s/def ::point
(s/with-gen (s/and ::point-attrs point?)
@ -40,10 +44,8 @@
(defn point-like?
[{:keys [x y] :as v}]
(and (map? v)
(not (nil? x))
(not (nil? y))
(number? x)
(number? y)))
(d/num? x)
(d/num? y)))
(defn point
"Create a Point instance."
@ -51,13 +53,13 @@
([v]
(cond
(point? v)
(Point. (:x v) (:y v))
v
(number? v)
(point v v)
(point-like? v)
(point (:x v) (:y v))
(map->Point v)
:else
(throw (ex-info "Invalid arguments" {:v v}))))
@ -66,128 +68,178 @@
(defn close?
[p1 p2]
(and (mth/close? (:x p1) (:x p2))
(mth/close? (:y p1) (:y p2))))
(and (mth/close? (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(mth/close? (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn angle->point [{:keys [x y]} angle distance]
(defn angle->point
[pt angle distance]
(point
(+ x (* distance (mth/cos angle)))
(- y (* distance (mth/sin angle)))))
(+ (dm/get-prop pt :x) (* distance (mth/cos angle)))
(- (dm/get-prop pt :y) (* distance (mth/sin angle)))))
(defn add
"Returns the addition of the supplied value to both
coordinates of the point as a new point."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(Point. (+ x ox) (+ y oy)))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (+ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(+ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn subtract
"Returns the subtraction of the supplied value to both
coordinates of the point as a new point."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(Point. (- x ox) (- y oy)))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (- (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn multiply
"Returns the subtraction of the supplied value to both
coordinates of the point as a new point."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(Point. (* x ox) (* y oy)))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (* (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(* (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn divide
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(Point. (/ x ox) (/ y oy)))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (/ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(/ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn min
([] (min nil nil))
([p1] (min p1 nil))
([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
([] nil)
([p1] p1)
([p1 p2]
(cond
(nil? p1) p2
(nil? p2) p1
:else (Point. (c/min x1 x2) (c/min y1 y2)))))
:else (Point. (c/min (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/min (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
(defn max
([] (max nil nil))
([p1] (max p1 nil))
([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
([] nil)
([p1] p1)
([p1 p2]
(cond
(nil? p1) p2
(nil? p2) p1
:else (Point. (c/max x1 x2) (c/max y1 y2)))))
:else (Point. (c/max (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/max (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
(defn inverse
[{:keys [x y] :as p}]
(assert (point? p))
(Point. (/ 1 x) (/ 1 y)))
[pt]
(assert (point? pt) "point instance expected")
(Point. (/ 1.0 (dm/get-prop pt :x))
(/ 1.0 (dm/get-prop pt :y))))
(defn negate
[{x :x y :y :as p}]
(assert (point? p))
(Point. (- x) (- y)))
[pt]
(assert (point? pt) "point instance expected")
(Point. (- (dm/get-prop pt :x))
(- (dm/get-prop pt :y))))
(defn distance
"Calculate the distance between two points."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(let [dx (- x ox)
dy (- y oy)]
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be point instances")
(let [dx (- (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
dy (- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))]
(mth/sqrt (+ (mth/pow dx 2)
(mth/pow dy 2)))))
(defn distance-vector
"Calculate the distance, separated x and y."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(let [dx (mth/abs (- x ox))
dy (mth/abs (- y oy))]
(Point. dx dy)))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be point instances")
(let [dx (- (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
dy (- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))]
(Point. (mth/abs dx)
(mth/abs dy))))
(defn length
[{x :x y :y :as p}]
(assert (point? p))
(mth/sqrt (+ (mth/pow x 2)
(mth/pow y 2))))
[pt]
(assert (point? pt) "point instance expected")
(let [x (dm/get-prop pt :x)
y (dm/get-prop pt :y)]
(mth/sqrt (+ (mth/pow x 2)
(mth/pow y 2)))))
(defn angle
"Returns the smaller angle between two vectors.
If the second vector is not provided, the angle
will be measured from x-axis."
([{x :x y :y :as p}]
(-> (mth/atan2 y x)
(mth/degrees)))
([p center]
(angle (subtract p center))))
([pt]
(assert (point? pt) "point instance expected")
(let [x (dm/get-prop pt :x)
y (dm/get-prop pt :y)]
(-> (mth/atan2 y x)
(mth/degrees))))
([pt center]
(assert (point? pt) "point instance expected")
(assert (point? center) "point instance expected")
(let [x (- (dm/get-prop pt :x)
(dm/get-prop center :x))
y (- (dm/get-prop pt :y)
(dm/get-prop center :y))]
(-> (mth/atan2 y x)
(mth/degrees)))))
(defn angle-with-other
"Consider point as vector and calculate
the angle between two vectors."
[{x :x y :y :as p} {ox :x oy :y :as other}]
(assert (point? p))
(assert (point? other))
(let [length-p (length p)
length-other (length other)]
(if (or (mth/almost-zero? length-p)
(mth/almost-zero? length-other))
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be point instances")
(let [length-p1 (length p1)
length-p2 (length p2)]
(if (or (mth/almost-zero? length-p1)
(mth/almost-zero? length-p2))
0
(let [a (/ (+ (* x ox)
(* y oy))
(* length-p length-other))
(let [a (/ (+ (* (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(* (dm/get-prop p1 :y)
(dm/get-prop p2 :y)))
(* length-p1 length-p2))
a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a)))
d (mth/degrees a)]
(if (mth/nan? d) 0 d)))))
(defn angle-sign [v1 v2]
(if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1))
(defn angle-sign
[p1 p2]
(if (> (* (dm/get-prop p1 :y) (dm/get-prop p2 :x))
(* (dm/get-prop p1 :x) (dm/get-prop p2 :y)))
-1
1))
(defn signed-angle-with-other
[v1 v2]
@ -196,70 +248,89 @@
(defn update-angle
"Update the angle of the point."
[p angle]
(assert (point? p))
(assert (number? angle))
(let [len (length p)
(assert (number? angle) "expected number")
(let [len (length p)
angle (mth/radians angle)]
(Point. (* (mth/cos angle) len)
(* (mth/sin angle) len))))
(defn quadrant
"Return the quadrant of the angle of the point."
[{:keys [x y] :as p}]
(assert (point? p))
(if (>= x 0)
(if (>= y 0) 1 4)
(if (>= y 0) 2 3)))
[p]
(assert (point? p) "expected point instance")
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)]
(if (>= x 0)
(if (>= y 0) 1 4)
(if (>= y 0) 2 3))))
(defn round
"Round the coordinates of the point to a precision"
([point]
(round point 0))
([{:keys [x y] :as p} decimals]
(assert (point? p))
(assert (number? decimals))
(Point. (mth/precision x decimals)
(mth/precision y decimals))))
([pt decimals]
(assert (point? pt) "expected point instance")
(assert (number? decimals) "expected number instance")
(Point. (mth/precision (dm/get-prop pt :x) decimals)
(mth/precision (dm/get-prop pt :y) decimals))))
(defn half-round
"Round the coordinates to the closest half-point"
[{:keys [x y] :as p}]
(assert (point? p))
(Point. (mth/half-round x)
(mth/half-round y)))
[pt]
(assert (point? pt) "expected point instance")
(Point. (mth/half-round (dm/get-prop pt :x))
(mth/half-round (dm/get-prop pt :y))))
(defn transform
"Transform a point applying a matrix transformation."
[{:keys [x y] :as p} {:keys [a b c d e f]}]
(assert (point? p))
(Point. (+ (* x a) (* y c) e)
(+ (* x b) (* y d) f)))
[p m]
(when (point? p)
(if (nil? m)
p
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)
a (dm/get-prop m :a)
b (dm/get-prop m :b)
c (dm/get-prop m :c)
d (dm/get-prop m :d)
e (dm/get-prop m :e)
f (dm/get-prop m :f)]
(Point. (+ (* x a) (* y c) e)
(+ (* x b) (* y d) f))))))
;; Vector functions
(defn to-vec [p1 p2]
(subtract p2 p1))
(defn scale [v scalar]
(-> v
(update :x * scalar)
(update :y * scalar)))
(defn scale
[p scalar]
(Point. (* (dm/get-prop p :x) scalar)
(* (dm/get-prop p :y) scalar)))
(defn dot [{x1 :x y1 :y} {x2 :x y2 :y}]
(+ (* x1 x2) (* y1 y2)))
(defn dot
[p1 p2]
(+ (* (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(* (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn unit [v]
(let [v-length (length v)]
(divide v (point v-length v-length))))
(defn unit
[p1]
(let [p-length (length p1)]
(Point. (/ (dm/get-prop p1 :x) p-length)
(/ (dm/get-prop p1 :y) p-length))))
(defn perpendicular
[{:keys [x y]}]
(Point. (- y) x))
[pt]
(Point. (- (dm/get-prop pt :y))
(dm/get-prop pt :x)))
(defn project
"V1 perpendicular projection on vector V2"
[v1 v2]
(let [v2-unit (unit v2)
(let [v2-unit (unit v2)
scalar-proj (dot v1 v2-unit)]
(scale v2-unit scalar-proj)))
@ -282,43 +353,53 @@
(defn point-line-distance
"Returns the distance from a point to a line defined by two points"
[point line-point1 line-point2]
(let [{x0 :x y0 :y} point
{x1 :x y1 :y} line-point1
{x2 :x y2 :y} line-point2
num (mth/abs
(+ (* x0 (- y2 y1))
(- (* y0 (- x2 x1)))
(* x2 y1)
(- (* y2 x1))))
dist (distance line-point2 line-point1)]
(/ num dist)))
(let [x0 (dm/get-prop point :x)
y0 (dm/get-prop point :y)
x1 (dm/get-prop line-point1 :x)
y1 (dm/get-prop line-point1 :y)
x2 (dm/get-prop line-point2 :x)
y2 (dm/get-prop line-point2 :y)]
(/ (mth/abs (+ (* x0 (- y2 y1))
(- (* y0 (- x2 x1)))
(* x2 y1)
(- (* y2 x1))))
(distance line-point2 line-point1))))
(defn almost-zero? [{:keys [x y] :as p}]
(assert (point? p))
(and (mth/almost-zero? x)
(mth/almost-zero? y)))
(defn almost-zero?
[p]
(assert (point? p) "point instance expected")
(and ^boolean (mth/almost-zero? (dm/get-prop p :x))
^boolean (mth/almost-zero? (dm/get-prop p :y))))
(defn lerp
"Calculates a linear interpolation between two points given a tvalue"
[p1 p2 t]
(let [x (mth/lerp (:x p1) (:x p2) t)
y (mth/lerp (:y p1) (:y p2) t)]
(point x y)))
(let [x (mth/lerp (dm/get-prop p1 :x) (dm/get-prop p2 :x) t)
y (mth/lerp (dm/get-prop p1 :y) (dm/get-prop p2 :y) t)]
(Point. x y)))
(defn rotate
"Rotates the point around center with an angle"
[{px :x py :y} {cx :x cy :y} angle]
[p c angle]
(prn "ROTATE" p c angle)
(assert (point? p) "point instance expected")
(assert (point? c) "point instance expected")
(let [angle (mth/radians angle)
px (dm/get-prop p :x)
py (dm/get-prop p :y)
cx (dm/get-prop c :x)
cy (dm/get-prop c :y)
x (+ (* (mth/cos angle) (- px cx))
(* (mth/sin angle) (- py cy) -1)
cx)
y (+ (* (mth/sin angle) (- px cx))
(* (mth/cos angle) (- py cy))
cy)]
(point x y)))
sa (mth/sin angle)
ca (mth/cos angle)
x (+ (* ca (- px cx))
(* sa (- py cy) -1)
cx)
y (+ (* sa (- px cx))
(* ca (- py cy))
cy)]
(Point. x y)))
(defn scale-from
"Moves a point in the vector that creates with center with a scale
@ -331,10 +412,11 @@
(defn no-zeros
"Remove zero values from either coordinate"
[point]
(-> point
(update :x #(if (mth/almost-zero? %) 0.001 %))
(update :y #(if (mth/almost-zero? %) 0.001 %))))
[p]
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)]
(Point. (if (mth/almost-zero? x) 0.001 x)
(if (mth/almost-zero? y) 0.001 y))))
(defn abs

View File

@ -282,7 +282,7 @@
(defn calc-child-modifiers
[parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds]
(let [modifiers (ctm/select-child-modifiers modifiers)
(let [modifiers (ctm/select-child modifiers)
constraints-h
(if-not ignore-constraints
@ -299,7 +299,7 @@
(let [transformed-parent-bounds @transformed-parent-bounds
modifiers (ctm/select-child-modifiers modifiers)
modifiers (ctm/select-child modifiers)
transformed-child-bounds (gtr/transform-bounds child-bounds modifiers)
modifiers (normalize-modifiers constraints-h constraints-v
modifiers parent

View File

@ -34,7 +34,7 @@
resize-origin (gpo/origin transformed-child-bounds)]
(-> modifiers
(ctm/select-child-modifiers)
(ctm/select-child)
(ctm/resize
resize-vector
resize-origin

View File

@ -107,7 +107,7 @@
;; Check the constraints, then resize
(let [parent-id (:id parent)
parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent-modifiers modifiers))]
parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent modifiers))]
(loop [modif-tree modif-tree
children (seq children)]
(if (empty? children)
@ -236,7 +236,24 @@
(and (some? auto-height) (ctl/auto-height? parent))
(set-parent-auto-height auto-height))))
(defn- propagate-modifiers
(defn- propagate-modifiers-constraints
"Propagate modifiers to its children"
[objects bounds ignore-constraints modif-tree parent]
(let [parent-id (:id parent)
root? (= uuid/zero parent-id)
modifiers (-> (dm/get-in modif-tree [parent-id :modifiers])
(ctm/select-geometry))
has-modifiers? (ctm/child-modifiers? modifiers)
layout? (ctl/layout? parent)
parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent))
transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))]
(cond-> modif-tree
(and (not layout?) has-modifiers? parent? (not root?))
(set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints))))
(defn- propagate-modifiers-layout
"Propagate modifiers to its children"
[objects bounds ignore-constraints [modif-tree autolayouts] parent]
(let [parent-id (:id parent)
@ -248,14 +265,11 @@
auto? (or (ctl/auto-height? parent) (ctl/auto-width? 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)
transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))]
[(cond-> modif-tree
(and (not layout?) has-modifiers? parent? (not root?))
(set-children-modifiers objects bounds parent transformed-parent-bounds (or ignore-constraints inside-layout?))
(set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints)
layout?
(-> (process-layout-children objects bounds parent transformed-parent-bounds)
@ -280,18 +294,19 @@
other-tree))
(defn transform-bounds
[bounds objects modif-tree]
([bounds objects modif-tree]
(transform-bounds bounds objects modif-tree (->> (keys modif-tree) (map #(get objects %)))))
([bounds objects modif-tree tree-seq]
(loop [result bounds
ids (keys modif-tree)]
(if (empty? ids)
result
(loop [result bounds
shapes (reverse tree-seq)]
(if (empty? shapes)
result
(let [id (first ids)
shape (get objects id)
new-bounds (delay (get-group-bounds objects bounds modif-tree shape))
result (assoc result id new-bounds)]
(recur result (rest ids))))))
(let [shape (first shapes)
new-bounds (delay (get-group-bounds objects bounds modif-tree shape))
result (assoc result (:id shape) new-bounds)]
(recur result (rest shapes)))))))
(defn sizing-auto-modifiers
"Recalculates the layouts to adjust the sizing: auto new sizes"
@ -308,7 +323,7 @@
tree-seq (resolve-tree-sequence #{current} objects)
[resize-modif-tree _]
(reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq)
(reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq)
bounds (transform-bounds bounds objects resize-modif-tree)
@ -320,12 +335,18 @@
[modif-tree objects ignore-constraints snap-pixel?]
(let [objects (apply-structure-modifiers objects modif-tree)
bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points]))
bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points]))
shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects)
[modif-tree sizing-auto-layouts]
(reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [modif-tree #{}] shapes-tree)
;; Calculate the input transformation and constraints
modif-tree' (reduce #(propagate-modifiers-constraints objects bounds ignore-constraints %1 %2) modif-tree shapes-tree)
bounds (transform-bounds bounds objects modif-tree' shapes-tree)
[modif-tree-layout sizing-auto-layouts]
(reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}] shapes-tree)
modif-tree (merge-modif-tree modif-tree' modif-tree-layout)
;; Calculate hug layouts positions
modif-tree

View File

@ -41,8 +41,8 @@
corner (gpt/point bounds)
target-corner (gpt/round corner)
deltav (gpt/to-vec corner target-corner)]
(-> modifiers
(ctm/move deltav))))
(ctm/move modifiers deltav)))
(defn set-pixel-precision
"Adjust modifiers so they adjust to the pixel grid"

View File

@ -7,6 +7,7 @@
(ns app.common.geom.shapes.rect
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.math :as mth]))
@ -83,13 +84,22 @@
(defn points->rect
[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)
(make-rect minx miny (- maxx minx) (- maxy miny))))))
(when-let [points (seq points)]
(loop [minx ##Inf
miny ##Inf
maxx ##-Inf
maxy ##-Inf
pts points]
(if-let [pt (first pts)]
(let [x (dm/get-prop pt :x)
y (dm/get-prop pt :y)]
(recur (min minx x)
(min miny y)
(max maxx x)
(max maxy y)
(rest pts)))
(when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny)))))))
(defn bounds->rect
[[{ax :x ay :y} {bx :x by :y} {cx :x cy :y} {dx :x dy :y}]]

View File

@ -483,9 +483,9 @@
([points center modifiers]
(let [transform (ctm/modifiers->transform modifiers)]
(cond-> points
(some? transform)
(gco/transform-points center transform)))))
(cond-> points
(some? transform)
(gco/transform-points center transform)))))
(defn transform-selrect
[selrect modifiers]

View File

@ -19,10 +19,13 @@
#?(:cljs (js/isNaN v)
:clj (Double/isNaN v)))
;; NOTE: on cljs we don't need to check for `number?` so we explicitly
;; ommit it for performance reasons.
(defn finite?
[v]
#?(:cljs (and (not (nil? v)) (js/isFinite v))
:clj (and (not (nil? v)) (Double/isFinite v))))
:clj (and (not (nil? v)) (number? v) (Double/isFinite v))))
(defn finite
[v default]

View File

@ -39,44 +39,123 @@
;; * rotation
;; * change-properties
(defrecord Modifiers
[geometry-parent
geometry-child
structure-parent
structure-child])
(defrecord GeometricOperation
[type
vector
origin
transform
transform-inverse
rotation
center])
(defrecord StructureOperation
[type
property
value
index])
;; Record constructors
(defn move-op
[vector]
(GeometricOperation. :move vector nil nil nil nil nil))
(defn resize-op
([vector origin]
(GeometricOperation. :resize vector origin nil nil nil nil))
([vector origin transform transform-inverse]
(GeometricOperation. :resize vector origin transform transform-inverse nil nil)))
(defn rotation-geom-op
[center angle]
(GeometricOperation. :rotation nil nil nil nil angle center))
(defn rotation-struct-op
[angle]
(StructureOperation. :rotation nil angle nil))
(defn remove-children-op
[shapes]
(StructureOperation. :remove-children nil shapes nil))
(defn add-children-op
[shapes index]
(StructureOperation. :add-children nil shapes index))
(defn reflow-op
[]
(StructureOperation. :reflow nil nil nil))
(defn scale-content-op
[value]
(StructureOperation. :scale-content nil value nil))
(defn change-property-op
[property value]
(StructureOperation. :change-property property value nil))
;; Private aux functions
(def conjv (fnil conj []))
(defn- move-vec?
[vector]
(or (not (mth/almost-zero? (dm/get-prop vector :x)))
(not (mth/almost-zero? (dm/get-prop vector :y)))))
(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- resize-vec?
[vector]
(or (not (mth/almost-zero? (- (dm/get-prop vector :x) 1)))
(not (mth/almost-zero? (- (dm/get-prop vector :y) 1)))))
(defn- mergeable-move?
[op1 op2]
(and (= :move (:type op1))
(= :move (:type op2))))
(let [type-op1 (dm/get-prop op1 :type)
type-op2 (dm/get-prop op2 :type)]
(and (= :move type-op1) (= :move type-op2))))
(defn- mergeable-resize?
[op1 op2]
(and (= :resize (:type op1))
(= :resize (:type op2))
(let [type-op1 (dm/get-prop op1 :type)
transform-op1 (or (dm/get-prop op1 :transform) (gmt/matrix))
transform-inv-op1 (or (dm/get-prop op1 :transform-inverse) (gmt/matrix))
origin-op1 (dm/get-prop op1 :origin)
;; 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)))
type-op2 (dm/get-prop op2 :type)
transform-op2 (or (dm/get-prop op2 :transform) (gmt/matrix))
transform-inv-op2 (or (dm/get-prop op2 :transform-inverse) (gmt/matrix))
origin-op2 (dm/get-prop op2 :origin)]
(and (= :resize type-op1) (= :resize type-op2)
;; Same origin
(gpt/close? (:origin op1) (:origin op2))))
;; Same origin
(gpt/close? origin-op1 origin-op2)
;; Same transforms
(gmt/close? transform-op1 transform-op2)
(gmt/close? transform-inv-op1 transform-inv-op2))))
(defn- merge-move
[op1 op2]
{:type :move
:vector (gpt/add (:vector op1) (:vector op2))})
(let [vector-op1 (dm/get-prop op1 :vector)
vector-op2 (dm/get-prop op2 :vector)]
(move-op (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)))]
(let [op1-vector (dm/get-prop op1 :vector)
op1-x (dm/get-prop op1-vector :x)
op1-y (dm/get-prop op1-vector :y)
op2-vector (dm/get-prop op2 :vector)
op2-x (dm/get-prop op2-vector :x)
op2-y (dm/get-prop op2-vector :y)
vector (gpt/point (* op1-x op2-x) (* op1-y op2-y))]
(assoc op1 :vector vector)))
(defn- maybe-add-move
@ -88,7 +167,7 @@
(if (mergeable-move? head op)
(let [item (merge-move head op)]
(cond-> (pop operations)
(move-vec? (:vector item))
(move-vec? (dm/get-prop item :vector))
(conj item)))
(conj operations op)))))
@ -102,21 +181,23 @@
(if (mergeable-resize? head op)
(let [item (merge-resize head op)]
(cond-> (pop operations)
(resize-vec? (:vector item))
(resize-vec? (dm/get-prop item :vector))
(conj item)))
(conj operations op)))))
(defn valid-vector?
[{:keys [x y]}]
(and (some? x)
(some? y)
(not (mth/nan? x))
(not (mth/nan? y))))
[vector]
(let [x (dm/get-prop vector :x)
y (dm/get-prop vector :y)]
(and (some? x)
(some? y)
(not (mth/nan? x))
(not (mth/nan? y)))))
;; Public builder API
(defn empty []
{})
(Modifiers. [] [] [] []))
(defn move-parent
([modifiers x y]
@ -124,114 +205,118 @@
([modifiers vector]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(move-vec? vector)
(update :geometry-parent maybe-add-move {:type :move :vector vector}))))
(update :geometry-parent maybe-add-move (move-op vector)))))
(defn resize-parent
([modifiers vector origin]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(resize-vec? vector)
(update :geometry-parent maybe-add-resize {:type :resize
:vector vector
:origin origin})))
(update :geometry-parent maybe-add-resize (resize-op vector origin))))
([modifiers vector origin transform transform-inverse]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(resize-vec? vector)
(update :geometry-parent maybe-add-resize {:type :resize
:vector vector
:origin origin
:transform transform
:transform-inverse transform-inverse}))))
(update :geometry-parent maybe-add-resize (resize-op vector origin transform transform-inverse)))))
(defn move
([modifiers x y]
(move modifiers (gpt/point x y)))
([modifiers vector]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(move-vec? vector)
(update :geometry-child maybe-add-move {:type :move :vector vector}))))
(update :geometry-child maybe-add-move (move-op vector)))))
(defn resize
([modifiers vector origin]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(resize-vec? vector)
(update :geometry-child maybe-add-resize {:type :resize
:vector vector
:origin origin})))
(update :geometry-child maybe-add-resize (resize-op vector origin))))
([modifiers vector origin transform transform-inverse]
(assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector)))
(cond-> modifiers
(cond-> (or modifiers (empty))
(resize-vec? vector)
(update :geometry-child maybe-add-resize {:type :resize
:vector vector
:origin origin
:transform transform
:transform-inverse transform-inverse}))))
(update :geometry-child maybe-add-resize (resize-op vector origin transform transform-inverse)))))
(defn rotation
[modifiers center angle]
(cond-> modifiers
(cond-> (or modifiers (empty))
(not (mth/close? angle 0))
(-> (update :structure-child conjv {:type :rotation
:rotation angle})
(update :geometry-child conjv {:type :rotation
:center center
:rotation angle}))))
(-> (update :structure-child conj (rotation-struct-op angle))
(update :geometry-child conj (rotation-geom-op center angle)))))
(defn remove-children
[modifiers shapes]
(cond-> modifiers
(cond-> (or modifiers (empty))
(d/not-empty? shapes)
(update :structure-parent conjv {:type :remove-children
:value shapes})))
(update :structure-parent conj (remove-children-op shapes))))
(defn add-children
[modifiers shapes index]
(cond-> modifiers
(cond-> (or modifiers (empty))
(d/not-empty? shapes)
(update :structure-parent conjv {:type :add-children
:value shapes
:index index})))
(update :structure-parent conj (add-children-op shapes index))))
(defn reflow
[modifiers]
(-> modifiers
(update :structure-parent conjv {:type :reflow})))
(-> (or modifiers (empty))
(update :structure-parent conj (reflow-op))))
(defn scale-content
[modifiers value]
(-> modifiers
(update :structure-child conjv {:type :scale-content :value value})))
(-> (or modifiers (empty))
(update :structure-child conj (scale-content-op value))))
(defn change-property
[modifiers property value]
(-> modifiers
(update :structure-child conjv {:type :change-property
:property property
:value value})))
(-> (or modifiers (empty))
(update :structure-child conj (change-property-op property value))))
(defn- merge-geometry
[operations other]
(cond
(c/empty? operations)
other
(c/empty? other)
operations
:else
(loop [result operations
operations (seq other)]
(if (c/empty? operations)
result
(let [current (first operations)
result
(cond
(= :move (dm/get-prop current :type))
(maybe-add-move result current)
(= :resize (dm/get-prop current :type))
(maybe-add-resize result current)
:else
(conj result current))]
(recur result (rest operations)))))))
(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)))))
(let [modifiers (or modifiers (empty))
new-modifiers (or new-modifiers (empty))]
(-> modifiers
(update :geometry-child merge-geometry (dm/get-prop new-modifiers :geometry-child))
(update :geometry-parent merge-geometry (dm/get-prop new-modifiers :geometry-parent))
(update :structure-parent #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-parent)))
(update :structure-child #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-child))))))
;; These are convenience methods to create single operation modifiers without the builder
@ -355,27 +440,27 @@
(defn empty?
[modifiers]
(and (c/empty? (:geometry-child modifiers))
(c/empty? (:geometry-parent modifiers))
(c/empty? (:structure-parent modifiers))
(c/empty? (:structure-child modifiers))))
(and (c/empty? (dm/get-prop modifiers :geometry-child))
(c/empty? (dm/get-prop modifiers :geometry-parent))
(c/empty? (dm/get-prop modifiers :structure-parent))
(c/empty? (dm/get-prop modifiers :structure-child))))
(defn child-modifiers?
[{:keys [geometry-child structure-child]}]
(or (d/not-empty? geometry-child)
(d/not-empty? structure-child)))
[modifiers]
(or (d/not-empty? (dm/get-prop modifiers :geometry-child))
(d/not-empty? (dm/get-prop modifiers :structure-child))))
(defn only-move?
"Returns true if there are only move operations"
[{:keys [geometry-child geometry-parent]}]
(let [move-op? #(= :move (:type %))]
(and (every? move-op? geometry-child)
(every? move-op? geometry-parent))))
[modifiers]
(let [move-op? #(= :move (dm/get-prop % :type))]
(and (every? move-op? (dm/get-prop modifiers :geometry-child))
(every? move-op? (dm/get-prop modifiers :geometry-parent)))))
(defn has-geometry?
[{:keys [geometry-parent geometry-child]}]
(or (d/not-empty? geometry-parent)
(d/not-empty? geometry-child)))
[modifiers]
(or (d/not-empty? (dm/get-prop modifiers :geometry-parent))
(d/not-empty? (dm/get-prop modifiers :geometry-child))))
(defn has-structure?
[{:keys [structure-parent structure-child]}]
@ -384,25 +469,25 @@
;; Extract subsets of modifiers
(defn select-child-modifiers
(defn select-child
[modifiers]
(select-keys modifiers [:geometry-child :structure-child]))
(assoc (or modifiers (empty)) :geometry-parent [] :structure-parent []))
(defn select-child-geometry-modifiers
(defn select-parent
[modifiers]
(select-keys modifiers [:geometry-child]))
(defn select-parent-modifiers
[modifiers]
(select-keys modifiers [:geometry-parent :structure-parent]))
(assoc (or modifiers (empty)) :geometry-child [] :structure-child []))
(defn select-structure
[modifiers]
(select-keys modifiers [:structure-parent :structure-child]))
(assoc (or modifiers (empty)) :geometry-child [] :geometry-parent []))
(defn select-geometry
[modifiers]
(select-keys modifiers [:geometry-parent :geometry-child]))
(assoc (or modifiers (empty)) :structure-child [] :structure-parent []))
(defn select-child-geometry-modifiers
[modifiers]
(-> modifiers select-child select-geometry))
(defn added-children-frames
"Returns the frames that have an 'add-children' operation"
@ -426,39 +511,53 @@
(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)
(let [modifiers (concat (dm/get-prop modifiers :geometry-parent)
(dm/get-prop modifiers :geometry-child))]
:resize
(let [origin (cond-> origin
(or (some? transform-inverse)(some? transform))
(gpt/transform transform-inverse))]
(gmt/multiply
(-> (gmt/matrix)
(cond-> (some? transform)
(gmt/multiply transform))
(gmt/translate origin)
(gmt/scale vector)
(gmt/translate (gpt/negate origin))
(cond-> (some? transform-inverse)
(gmt/multiply transform-inverse)))
matrix))
(loop [matrix (gmt/matrix)
modifiers (seq modifiers)]
(if (c/empty? modifiers)
matrix
(let [modifier (first modifiers)
type (dm/get-prop modifier :type)
: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)))))))
matrix
(case type
:move
(-> (dm/get-prop modifier :vector)
(gmt/translate-matrix)
(gmt/multiply! matrix))
:resize
(let [tf (dm/get-prop modifier :transform)
tfi (dm/get-prop modifier :transform-inverse)
vector (dm/get-prop modifier :vector)
origin (dm/get-prop modifier :origin)
origin (if ^boolean (some? tfi)
(gpt/transform origin tfi)
origin)]
(gmt/multiply!
(-> (gmt/matrix)
(cond-> ^boolean (some? tf)
(gmt/multiply! tf))
(gmt/translate! origin)
(gmt/scale! vector)
(gmt/translate! (gpt/negate origin))
(cond-> ^boolean (some? tfi)
(gmt/multiply! tfi)))
matrix))
:rotation
(let [center (dm/get-prop modifier :center)
rotation (dm/get-prop modifier :rotation)]
(gmt/multiply!
(-> (gmt/matrix)
(gmt/translate! center)
(gmt/multiply! (gmt/rotate-matrix rotation))
(gmt/translate! (gpt/negate center)))
matrix)))]
(recur matrix (next modifiers)))))))
(defn apply-structure-modifiers
"Apply structure changes to a shape"
@ -482,36 +581,48 @@
(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))
(fn [shape operation]
(let [type (dm/get-prop operation :type)]
(case type
:rotation
(let [rotation (dm/get-prop operation :value)]
(update shape :rotation #(mod (+ (or % 0) 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))))
:add-children
(let [value (dm/get-prop operation :value)
index (dm/get-prop operation :index)]
(if (some? index)
(update shape :shapes
(fn [shapes]
(if (vector? shapes)
(cph/insert-at-index shapes index value)
(d/concat-vec shapes value))))
(update shape :shapes d/concat-vec value)))
(and (= type :add-children) (nil? index))
(update :shapes d/concat-vec value)
:remove-children
(let [value (dm/get-prop operation :value)]
(update shape :shapes remove-children value))
(= type :remove-children)
(update :shapes remove-children value)
(= type :scale-content)
(apply-scale-content value)
:scale-content
(let [value (dm/get-prop operation :value)]
(apply-scale-content shape value))
(= type :change-property)
(assoc property value)))]
:change-property
(let [property (dm/get-prop operation :property)
value (dm/get-prop operation :value)]
(assoc shape property value))
;; :default => no change to shape
shape)))]
(as-> shape $
(reduce apply-modifier $ (:structure-parent modifiers))
(reduce apply-modifier $ (:structure-child modifiers))))))
(reduce apply-modifier $ (dm/get-prop modifiers :structure-parent))
(reduce apply-modifier $ (dm/get-prop modifiers :structure-child))))))

View File

@ -24,8 +24,10 @@ goog.scope(function() {
};
} else if (typeof require === "function") {
const crypto = require("crypto");
const randomBytes = crypto["randomBytes"];
return (buf) => {
const bytes = crypto.randomBytes(buf.length);
const bytes = randomBytes(buf.length);
buf.set(bytes)
return buf;
};

View File

@ -51,3 +51,17 @@
(t/is (= [1 10 100 2 20 200 3 30 300]
(d/join [1 2 3] [1 10 100] *))))
(t/deftest num-predicate
(t/is (not (d/num? ##NaN)))
(t/is (not (d/num? nil)))
(t/is (d/num? 1))
(t/is (d/num? -0.3))
(t/is (not (d/num? {}))))
(t/deftest check-num-helper
(t/is (= 1 (d/check-num 1 0)))
(t/is (= 0 (d/check-num ##NaN 0)))
(t/is (= 0 (d/check-num {} 0)))
(t/is (= 0 (d/check-num [] 0)))
(t/is (= 0 (d/check-num :foo 0)))
(t/is (= 0 (d/check-num nil 0))))

View File

@ -0,0 +1,296 @@
;; 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 common-tests.geom-point-test
(:require
[app.common.math :as mth]
[app.common.geom.point :as gpt]
[clojure.test :as t]))
(t/deftest add-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/add p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 3 (:x rs)))
(t/is (mth/close? 5 (:y rs)))))
(t/deftest substract-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/subtract p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? -1 (:x rs)))
(t/is (mth/close? -1 (:y rs)))))
(t/deftest multiply-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/multiply p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 2 (:x rs)))
(t/is (mth/close? 6 (:y rs)))))
(t/deftest divide-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 5)
rs (gpt/divide p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 0.5 (:x rs)))
(t/is (mth/close? 0.4 (:y rs)))))
(t/deftest min-point
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 5)]
(let [rs (gpt/min)]
(t/is (nil? rs)))
(let [rs (gpt/min p1)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/min nil p1)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/min p1 nil)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/min p1 p2)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/min p2 p1)]
(t/is (gpt/close? rs p1)))
))
(t/deftest max-point
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 5)]
(let [rs (gpt/max)]
(t/is (nil? rs)))
(let [rs (gpt/max p1)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/max nil p1)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/max p1 nil)]
(t/is (gpt/close? rs p1)))
(let [rs (gpt/max p1 p2)]
(t/is (gpt/close? rs p2)))
(let [rs (gpt/max p2 p1)]
(t/is (gpt/close? rs p2)))
))
(t/deftest inverse-point
(let [p1 (gpt/point 1 2)
rs (gpt/inverse p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1 (:x rs)))
(t/is (mth/close? 0.5 (:y rs)))))
(t/deftest negate-point
(let [p1 (gpt/point 1 2)
rs (gpt/negate p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? -1 (:x rs)))
(t/is (mth/close? -2 (:y rs)))))
(t/deftest distance-between-two-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 4 6)
rs (gpt/distance p1 p2)]
(t/is (number? rs))
(t/is (mth/close? 5 rs))))
(t/deftest distance-vector-between-two-points
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/distance-vector p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1 (:x rs)))
(t/is (mth/close? 1 (:y rs)))))
(t/deftest point-length
(let [p1 (gpt/point 1 10)
rs (gpt/length p1)]
(t/is (number? rs))
(t/is (mth/close? 10.04987562112089 rs))))
(t/deftest point-angle-1
(let [p1 (gpt/point 1 3)
rs (gpt/angle p1)]
(t/is (number? rs))
(t/is (mth/close? 71.56505117707799 rs))))
(t/deftest point-angle-2
(let [p1 (gpt/point 1 3)
p2 (gpt/point 2 4)
rs (gpt/angle p1 p2)]
(t/is (number? rs))
(t/is (mth/close? -135 rs))))
(t/deftest point-angle-with-other
(let [p1 (gpt/point 1 3)
p2 (gpt/point 1 5)
rs (gpt/angle-with-other p1 p2)]
(t/is (number? rs))
(t/is (mth/close? 7.125016348901757 rs))))
(t/deftest point-angle-sign
(let [p1 (gpt/point 1 3)
p2 (gpt/point 1 5)
rs (gpt/angle-sign p1 p2)]
(t/is (number? rs))
(t/is (mth/close? 1 rs)))
(let [p1 (gpt/point -11 -3)
p2 (gpt/point 1 5)
rs (gpt/angle-sign p1 p2)]
(t/is (number? rs))
(t/is (mth/close? -1 rs)))
)
(t/deftest update-angle
(let [p1 (gpt/point 1 3)
rs (gpt/update-angle p1 10)]
(t/is (gpt/point? rs))
(t/is (mth/close? 3.1142355569111246 (:x rs)))
(t/is (mth/close? 0.5491237529650835 (:y rs)))))
(t/deftest point-quadrant
(let [p1 (gpt/point 1 3)
rs (gpt/quadrant p1)]
(t/is (number? rs))
(t/is (mth/close? 1 rs)))
(let [p1 (gpt/point 1 -3)
rs (gpt/quadrant p1)]
(t/is (number? rs))
(t/is (mth/close? 4 rs)))
(let [p1 (gpt/point -1 3)
rs (gpt/quadrant p1)]
(t/is (number? rs))
(t/is (mth/close? 2 rs)))
(let [p1 (gpt/point -1 -3)
rs (gpt/quadrant p1)]
(t/is (number? rs))
(t/is (mth/close? 3 rs)))
)
(t/deftest round-point
(let [p1 (gpt/point 1.34567 3.34567)
rs (gpt/round p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1 (:x rs)))
(t/is (mth/close? 3 (:y rs))))
(let [p1 (gpt/point 1.34567 3.34567)
rs (gpt/round p1 2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1.35 (:x rs)))
(t/is (mth/close? 3.35 (:y rs))))
)
(t/deftest halft-round-point
(let [p1 (gpt/point 1.34567 3.34567)
rs (gpt/half-round p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1.5 (:x rs)))
(t/is (mth/close? 3.5 (:y rs)))))
(t/deftest transform-point
;;todo
)
(t/deftest scale-point
(let [p1 (gpt/point 1.5 3)
rs (gpt/scale p1 2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 3 (:x rs)))
(t/is (mth/close? 6 (:y rs)))))
(t/deftest dot-point
(let [p1 (gpt/point 1.5 3)
p2 (gpt/point 2 6)
rs (gpt/dot p1 p2)]
(t/is (number? rs))
(t/is (mth/close? 21 rs))))
(t/deftest unit-point
(let [p1 (gpt/point 2 3)
rs (gpt/unit p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? 0.5547001962252291 (:x rs)))
(t/is (mth/close? 0.8320502943378437 (:y rs)))))
(t/deftest project-point
(let [p1 (gpt/point 1 3)
p2 (gpt/point 1 6)
rs (gpt/project p1 p2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 0.5135135135135135 (:x rs)))
(t/is (mth/close? 3.081081081081081 (:y rs)))))
(t/deftest center-points
(let [points [(gpt/point 0.5 0.5)
(gpt/point -1 -2)
(gpt/point 20 65.2)
(gpt/point 12 -10)]
rs (gpt/center-points points)]
(t/is (mth/close? 7.875 (:x rs)))
(t/is (mth/close? 13.425 (:y rs)))))
(t/deftest normal-left-point
(let [p1 (gpt/point 2 3)
rs (gpt/normal-left p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? -0.8320502943378437 (:x rs)))
(t/is (mth/close? 0.5547001962252291 (:y rs)))))
(t/deftest normal-right-point
(let [p1 (gpt/point 2 3)
rs (gpt/normal-right p1)]
(t/is (gpt/point? rs))
(t/is (mth/close? 0.8320502943378437 (:x rs)))
(t/is (mth/close? -0.5547001962252291 (:y rs)))))
(t/deftest point-line-distance
(let [p1 (gpt/point 2 -3)
p2 (gpt/point -1 4)
p3 (gpt/point 5 6)
rs (gpt/point-line-distance p1 p2 p3)]
(t/is (number? rs))
(t/is (mth/close? 7.58946638440411 rs))))
(t/deftest almost-zero-predicate
(let [p1 (gpt/point 0.000001 0.0000002)
p2 (gpt/point 0.001 -0.0003)]
(t/is (gpt/almost-zero? p1))
(t/is (not (gpt/almost-zero? p2)))))
(t/deftest lerp-point
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/lerp p1 p2 2)]
(t/is (gpt/point? rs))
(t/is (mth/close? 3 (:x rs)))
(t/is (mth/close? 4 (:y rs)))))
(t/deftest rotate-point
(let [p1 (gpt/point 1 2)
p2 (gpt/point 2 3)
rs (gpt/rotate p1 p2 11)]
(t/is (gpt/point? rs))
(t/is (mth/close? 1.2091818119288809 (:x rs)))
(t/is (mth/close? 1.8275638211757912 (:y rs)))))

View File

@ -21,7 +21,8 @@
{:command :curve-to :params {:x 40 :y 40 :c1x 35 :c1y 35 :c2x 45 :c2y 45}}
{:command :close-path}])
(defn add-path-data [shape]
(defn add-path-data
[shape]
(let [content (:content shape default-path)
selrect (gsh/content->selrect content)
points (gsh/rect->points selrect)]
@ -30,7 +31,8 @@
:selrect selrect
:points points)))
(defn add-rect-data [shape]
(defn add-rect-data
[shape]
(let [shape (-> shape
(assoc :width 20 :height 20))
selrect (gsh/rect->selrect shape)
@ -49,7 +51,7 @@
(not= type :path) (add-rect-data)))))
(t/deftest transform-shape-tests
(t/deftest transform-shapes
(t/testing "Shape without modifiers should stay the same"
(t/are [type]
(let [shape-before (create-test-shape type)
@ -138,7 +140,7 @@
(t/testing "Transform shape with rotation modifiers"
(t/are [type]
(let [shape-before (create-test-shape type)
modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30 )
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)]
@ -181,3 +183,18 @@
:path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf}
:rect nil
:path nil)))
(t/deftest points-to-selrect
(let [points [(gpt/point 0.5 0.5)
(gpt/point -1 -2)
(gpt/point 20 65.2)
(gpt/point 12 -10)]
result (gsh/points->rect points)
expect {:x -1, :y -10, :width 21, :height 75.2}]
(t/is (= (:x expect) (:x result)))
(t/is (= (:y expect) (:y result)))
(t/is (= (:width expect) (:width result)))
(t/is (= (:height expect) (:height result)))
))

View File

@ -0,0 +1,26 @@
;; 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 common-tests.types-modifiers-test
(:require
[clojure.test :as t]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.types.modifiers :as ctm]))
(t/deftest test-modifiers->transform
(let [modifiers
(-> (ctm/empty)
(ctm/move (gpt/point 100 200))
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))
(ctm/move (gpt/point -100 -200))
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))
(ctm/rotation (gpt/point 0 0) -100)
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)))
transform (ctm/modifiers->transform modifiers)]
(t/is (not (gmt/close? (gmt/matrix) transform)))))

65
frontend/dev/bench.cljs Normal file
View File

@ -0,0 +1,65 @@
(ns bench
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.rect :as gsr]
[app.common.perf :as perf]
[app.common.types.modifiers :as ctm]
[clojure.spec.alpha :as s]
[clojure.test.check.generators :as gen]))
(def points
(gen/sample (s/gen ::gpt/point) 20))
(defn bench-points
[]
#_(perf/benchmark
:f #(gpt/center-points-old points)
:samples 20
:max-iterations 500000
:name "base")
(perf/benchmark
:f #(gpt/center-points points)
:max-iterations 500000
:samples 20
:name "optimized"))
(def modifiers
(-> (ctm/empty)
(ctm/move (gpt/point 100 200))
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))
(ctm/move (gpt/point -100 -200))
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))
(ctm/rotation (gpt/point 0 0) -100)
(ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))))
(defn bench-modifiers
[]
(perf/benchmark
:f #(ctm/modifiers->transform modifiers)
:max-iterations 50000
:samples 20
:name "current")
#_(perf/benchmark
:f #(ctm/modifiers->transform-2 modifiers)
:max-iterations 50000
:samples 20
:name "optimized"))
;; (ctm/modifiers->transform-2 modifiers)
(defn ^:dev/after-load after-load
[]
#_(bench-modifiers))
(defn main
[& [name]]
(case name
"points" (bench-points)
"modifiers" (bench-modifiers)
(println "available: points"))
#_(.exit js/process 0))

View File

@ -69,10 +69,10 @@
{:target :node-script
:output-to "target/bench.js"
:output-dir "target/bench/"
:main cljs.user/main
:main bench/main
:compiler-options
{:output-feature-set :es8
{:output-feature-set :es2020
:output-wrapper false
:warnings {:fn-deprecated false}}

View File

@ -297,8 +297,8 @@
(mapv (fn [[value points]]
[(- value pval)
(->> points (mapv #(vector point %)))])))))]
{:x (query-coord point :x)
:y (query-coord point :y)}))
(gpt/point (query-coord point :x)
(query-coord point :y))))
(defn merge-matches
([] {:x nil :y nil})

View File

@ -47,10 +47,8 @@
(defn process-shape [modifiers {:keys [id] :as shape}]
(let [modifier (dm/get-in modifiers [id :modifiers])]
(-> shape
(cond-> (and (some? modifier)
(not (ctm/only-move? modifier)))
(cond-> (and (some? modifier) (not (ctm/only-move? modifier)))
(fix-position modifier))
(cond-> (nil? (:position-data shape))
(assoc :migrate true))
strip-position-data)))
@ -132,6 +130,21 @@
:shape shape
:grow-type (:grow-type shape)}]))
(defn text-properties-equal?
[shape other]
(or (identical? shape other)
(and
;; Check if both shapes are equivalent removing their geometry data
(= (dissoc shape :migrate :points :selrect :height :width :x :y)
(dissoc other :migrate :points :selrect :height :width :x :y))
;; Check if the position and size is close. If any of these changes the shape has changed
;; and if not there is no geometry relevant change
(mth/close? (:x shape) (:x other))
(mth/close? (:y shape) (:y other))
(mth/close? (:width shape) (:width other))
(mth/close? (:height shape) (:height other)))))
(mf/defc viewport-texts-wrapper
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
@ -149,12 +162,9 @@
old-modifiers (ctm/select-geometry (get prev-modifiers id))
new-modifiers (ctm/select-geometry (get modifiers id))
remote? (some? (-> new-shape meta :session-id)) ]
remote? (some? (-> new-shape meta :session-id))]
(or (and (not remote?)
(not (identical? old-shape new-shape))
(not= (dissoc old-shape :migrate)
(dissoc new-shape :migrate)))
(not (text-properties-equal? old-shape new-shape)))
(and (not= new-modifiers old-modifiers)
(or (not (ctm/only-move? new-modifiers))
@ -172,6 +182,7 @@
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 shape

View File

@ -0,0 +1,53 @@
(ns frontend-tests.test-helpers-shapes
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.main.data.workspace.libraries :as dwl]
[app.test-helpers.events :as the]
[app.test-helpers.libraries :as thl]
[app.test-helpers.pages :as thp]
[beicon.core :as rx]
[cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true]
[clojure.stacktrace :as stk]
[linked.core :as lks]
[potok.core :as ptk]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
(t/deftest test-create-page
(t/testing "create page"
(let [state (-> thp/initial-state
(thp/sample-page))
page (thp/current-page state)]
(t/is (= (:name page) "page1")))))
(t/deftest test-create-shape
(t/testing "create shape"
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"}))
shape (thp/get-shape state :shape1)]
(t/is (= (:name shape) "Rect 1")))))
(t/deftest asynctest
(t/testing "asynctest"
(t/async done
(let [state {}
color {:color clr/white}
store (the/prepare-store state done
(fn [new-state]
(t/is (= (get-in new-state [:workspace-data
:recent-colors])
[color]))))]
(ptk/emit!
store
(dwl/add-recent-color color)
:the/end)))))