Merge remote-tracking branch 'origin/main' into develop

This commit is contained in:
alonso.torres
2022-05-20 11:10:14 +02:00
110 changed files with 1833 additions and 1006 deletions

View File

@@ -5,7 +5,49 @@
;; Copyright (c) UXBOX Labs SL
(ns app.common.attrs
(:refer-clojure :exclude [merge]))
(:require
[app.common.geom.shapes.transforms :as gst]
[app.common.math :as mth]))
(defn- get-attr
[obj attr]
(if (= (get obj attr) :multiple)
:multiple
(cond
;; For rotated or stretched shapes, the origin point we show in the menu
;; is not the (:x :y) shape attribute, but the top left coordinate of the
;; wrapping recangle (see measures.cljs). As the :points attribute cannot
;; be merged for several objects, we calculate the origin point in two fake
;; attributes to be used in the measures menu.
(#{:ox :oy} attr)
(if-let [value (get obj attr)]
value
(if-let [points (:points obj)]
(if (not= points :multiple)
(let [rect (gst/selection-rect [obj])]
(if (= attr :ox) (:x rect) (:y rect)))
:multiple)
(get obj attr ::unset)))
;; Not all shapes have width and height (e.g. paths), so we extract
;; them from the :selrect attribute.
(#{:width :height} attr)
(if-let [value (get obj attr)]
value
(if-let [selrect (:selrect obj)]
(if (not= selrect :multiple)
(get (:selrect obj) attr)
:multiple)
(get obj attr ::unset)))
:else
(get obj attr ::unset))))
(defn- default-equal
[val1 val2]
(if (and (number? val1) (number? val2))
(mth/close? val1 val2)
(= val1 val2)))
;; Extract some attributes of a list of shapes.
;; For each attribute, if the value is the same in all shapes,
@@ -36,13 +78,11 @@
;; :rx nil
;; :ry nil}
;;
(defn get-attrs-multi
([objs attrs]
(get-attrs-multi objs attrs = identity))
(get-attrs-multi objs attrs default-equal identity))
([objs attrs eqfn sel]
(loop [attr (first attrs)
attrs (rest attrs)
result (transient {})]
@@ -50,34 +90,25 @@
(let [value
(loop [curr (first objs)
objs (rest objs)
value ::undefined]
value ::unset]
(if (and curr (not= value :multiple))
;;
(let [new-val (get curr attr ::undefined)
(let [new-val (get-attr curr attr)
value (cond
(= new-val ::undefined) value
(= new-val :multiple) :multiple
(= value ::undefined) (sel new-val)
(eqfn new-val value) value
:else :multiple)]
(= new-val ::unset) value
(= new-val :multiple) :multiple
(= value ::unset) (sel new-val)
(eqfn new-val value) value
:else :multiple)]
(recur (first objs) (rest objs) value))
;;
value))]
(recur (first attrs)
(rest attrs)
(cond-> result
(not= value ::undefined)
(not= value ::unset)
(assoc! attr value))))
(persistent! result)))))
(defn merge
"Attrs specific merge function."
[obj attrs]
(reduce-kv (fn [obj k v]
(if (nil? v)
(dissoc obj k)
(assoc obj k v)))
obj
attrs))

View File

@@ -336,6 +336,16 @@
[& maps]
(reduce conj (or (first maps) {}) (rest maps)))
(defn txt-merge
"Text attrs specific merge function."
[obj attrs]
(reduce-kv (fn [obj k v]
(if (nil? v)
(dissoc obj k)
(assoc obj k v)))
obj
attrs))
(defn distinct-xf
[f]
(fn [rf]
@@ -574,17 +584,20 @@
(assert (string? basename))
(assert (set? used))
(let [[prefix initial] (extract-numeric-suffix basename)]
(if (and (not prefix-first?)
(not (contains? used basename)))
basename
(loop [counter initial]
(let [candidate (if (and (= 1 counter) prefix-first?)
(str prefix)
(str prefix "-" counter))]
(if (contains? used candidate)
(recur (inc counter))
candidate)))))))
(if (> (count basename) 1000)
;; We skip generating names for long strings. If the name is too long the regex can hang
basename
(let [[prefix initial] (extract-numeric-suffix basename)]
(if (and (not prefix-first?)
(not (contains? used basename)))
basename
(loop [counter initial]
(let [candidate (if (and (= 1 counter) prefix-first?)
(str prefix)
(str prefix "-" counter))]
(if (contains? used candidate)
(recur (inc counter))
candidate))))))))
(defn deep-mapm
"Applies a map function to an associative map and recurses over its children

View File

@@ -219,3 +219,13 @@
e' (/ (- (* c f) (* d e)) det)
f' (/ (- (* b e) (* a f)) det)]
(Matrix. a' b' c' d' e' f')))
(defn round
[mtx]
(-> mtx
(update :a mth/precision 4)
(update :b mth/precision 4)
(update :c mth/precision 4)
(update :d mth/precision 4)
(update :e mth/precision 4)
(update :f mth/precision 4)))

View File

@@ -100,7 +100,6 @@
(assert (point? other))
(Point. (/ x ox) (/ y oy)))
(defn min
([] (min nil nil))
([p1] (min p1 nil))
@@ -139,6 +138,15 @@
(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)))
(defn length
[{x :x y :y :as p}]
(assert (point? p))

View File

@@ -6,6 +6,7 @@
(ns app.common.geom.shapes
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.bool :as gsb]
@@ -38,6 +39,14 @@
;; --- Helpers
(defn left-bound
[shape]
(get shape :x (:x (:selrect shape)))) ; Paths don't have :x attribute
(defn top-bound
[shape]
(get shape :y (:y (:selrect shape)))) ; Paths don't have :y attribute
(defn fully-contained?
"Checks if one rect is fully inside the other"
[rect other]
@@ -96,6 +105,37 @@
(mth/sqrt (* 2 stroke-width stroke-width))
(- (mth/sqrt (* 2 stroke-width stroke-width)) stroke-width)))
(defn close-attrs?
"Compares two shapes attributes to see if they are equal or almost
equal (in case of numeric). Takes into account attributes that are
data structures with numbers inside."
([attr val1 val2]
(close-attrs? attr val1 val2 mth/float-equal-precision))
([attr val1 val2 precision]
(let [close-val? (fn [num1 num2]
(when (and (number? num1) (number? num2))
(< (mth/abs (- num1 num2)) precision)))]
(cond
(and (number? val1) (number? val2))
(close-val? val1 val2)
(= attr :selrect)
(every? #(close-val? (get val1 %) (get val2 %))
[:x :y :x1 :y1 :x2 :y2 :width :height])
(= attr :points)
(every? #(and (close-val? (:x (first %)) (:x (second %)))
(close-val? (:y (first %)) (:y (second %))))
(d/zip val1 val2))
(= attr :position-data)
(every? #(and (close-val? (:x (first %)) (:x (second %)))
(close-val? (:y (first %)) (:y (second %))))
(d/zip val1 val2))
:else
(= val1 val2)))))
;; EXPORTS
(dm/export gco/center-shape)

View File

@@ -152,45 +152,74 @@
:top
:scale)))
(defn clean-modifiers
"Remove redundant modifiers"
[{:keys [displacement resize-vector resize-vector-2] :as modifiers}]
(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)
;; 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)
(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)))
(defn calc-child-modifiers
[parent child modifiers ignore-constraints transformed-parent-rect]
(let [constraints-h
(if-not ignore-constraints
(:constraints-h child (default-constraints-h child))
:scale)
constraints-v
(if-not ignore-constraints
(:constraints-v child (default-constraints-v child))
:scale)
(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)
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-> {}
(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))
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)]
:always
(gmt/translate-matrix)))
;; 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))
(: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)))
:always
(gmt/translate-matrix)))
(: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-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-transform modifiers)
(assoc :resize-transform (:resize-transform modifiers)
:resize-transform-inverse (:resize-transform-inverse modifiers)))))
(: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))
:always
(clean-modifiers)))))

View File

@@ -28,4 +28,3 @@
[shape]
(gpr/points->selrect (position-data-points shape)))

View File

@@ -6,7 +6,6 @@
(ns app.common.geom.shapes.transforms
(:require
[app.common.attrs :as attrs]
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@@ -533,7 +532,7 @@
(* (get-in modifiers [:resize-vector :x] 1))
(* (get-in modifiers [:resize-vector-2 :x] 1))
(str))]
(attrs/merge attrs {:font-size font-size})))]
(d/txt-merge attrs {:font-size font-size})))]
(update shape :content #(txt/transform-nodes
txt/is-text-node?
merge-attrs

View File

@@ -11,6 +11,7 @@
[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]
[app.common.pages.init :as init]
@@ -433,25 +434,35 @@
(defmethod process-operation :set
[shape op]
(let [attr (:attr op)
val (:val op)
ignore (:ignore-touched op)
(let [attr (:attr op)
group (get component-sync-attrs attr)
val (:val op)
shape-val (get shape attr)
ignore (:ignore-touched op)
ignore-geometry (:ignore-geometry op)
shape-ref (:shape-ref shape)
group (get component-sync-attrs attr)
root-name? (and (= group :name-group)
(:component-root? shape))]
is-geometry? (and (or (= group :geometry-group)
(and (= group :content-group) (= (:type shape) :path)))
(not (#{:width :height} attr))) ;; :content in paths are also considered geometric
shape-ref (:shape-ref shape)
root-name? (and (= group :name-group)
(:component-root? shape))
;; For geometric attributes, there are cases in that the value changes
;; slightly (e.g. when rounding to pixel, or when recalculating text
;; positions in different zoom levels). To take this into account, we
;; ignore geometric changes smaller than 1 pixel.
equal? (if is-geometry?
(gsh/close-attrs? attr val shape-val 1)
(gsh/close-attrs? attr val shape-val))]
(cond-> shape
;; Depending on the origin of the attribute change, we need or not to
;; set the "touched" flag for the group the attribute belongs to.
;; In some cases we need to ignore touched only if the attribute is
;; geometric (position, width or transformation).
(and shape-ref group (not ignore) (not= val (get shape attr))
(and shape-ref group (not ignore) (not equal?)
(not root-name?)
(not (and ignore-geometry
(and (= group :geometry-group)
(not (#{:width :height} attr))))))
(not (and ignore-geometry is-geometry?)))
(->
(update :touched cph/set-touched-group group)
(dissoc :remote-synced?))

View File

@@ -25,6 +25,7 @@
:content :content-group
:hidden :visibility-group
:blocked :modifiable-group
:grow-type :text-font-group
:font-family :text-font-group
:font-size :text-font-group
:font-style :text-font-group
@@ -58,8 +59,10 @@
:y :geometry-group
:width :geometry-group
:height :geometry-group
:rotation :geometry-group
:transform :geometry-group
:transform-inverse :geometry-group
:position-data :geometry-group
:opacity :layer-effects-group
:blend-mode :layer-effects-group
:shadow :shadow-group
@@ -78,6 +81,7 @@
:rx :ry
:r1 :r2 :r3 :r4
:selrect
:points
:opacity
:blend-mode
@@ -111,6 +115,7 @@
:x :y
:rotation
:selrect
:points
:constraints-h
:constraints-v
@@ -136,6 +141,7 @@
:rx :ry
:r1 :r2 :r3 :r4
:selrect
:points
:constraints-h
:constraints-v
@@ -178,6 +184,7 @@
:x :y
:rotation
:selrect
:points
:constraints-h
:constraints-v
@@ -220,6 +227,7 @@
:x :y
:rotation
:selrect
:points
:constraints-h
:constraints-v
@@ -262,6 +270,7 @@
:x :y
:rotation
:selrect
:points
:constraints-h
:constraints-v
@@ -329,6 +338,7 @@
:rx :ry
:r1 :r2 :r3 :r4
:selrect
:points
:constraints-h
:constraints-v
@@ -354,6 +364,7 @@
:rx :ry
:r1 :r2 :r3 :r4
:selrect
:points
:constraints-h
:constraints-v
@@ -398,6 +409,7 @@
:rx :ry
:r1 :r2 :r3 :r4
:selrect
:points
:constraints-h
:constraints-v