diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 9472fdce3..7c82ee0c9 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -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)) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index e201d3181..ac645e920 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -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,35 @@ (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] (< (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) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 8ca215bde..7d511d9ff 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -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?)) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 2ac202d73..30351a666 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -59,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 diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 6b2cc9d22..c73384c54 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -252,15 +252,23 @@ shape-delta (when root - (gpt/point (- (:x shape) (:x root)) - (- (:y shape) (:y root)))) + (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root)) + (- (gsh/top-bound shape) (gsh/top-bound root)))) transformed-shape-delta (when transformed-root - (gpt/point (- (:x transformed-shape) (:x transformed-root)) - (- (:y transformed-shape) (:y transformed-root)))) + (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root)) + (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root)))) - ignore-geometry? (= shape-delta transformed-shape-delta)] + ;; There are cases in that the coordinates change slightly (e.g. when + ;; rounding to pixel, or when recalculating text positions in different + ;; zoom levels). To take this into account, we ignore movements smaller + ;; than 1 pixel. + distance (if (and shape-delta transformed-shape-delta) + (gpt/distance-vector shape-delta transformed-shape-delta) + (gpt/point 0 0)) + + ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))] [root transformed-root ignore-geometry?])) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs index f799bf63b..b8e726951 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs @@ -72,7 +72,6 @@ (defn- update-text-modifier [{:keys [grow-type id]} node] - (let [position-data (utp/calc-position-data node) props {:position-data position-data}