diff --git a/backend/tests/app/tests/test_common_geom_shapes.clj b/backend/tests/app/tests/test_common_geom_shapes.clj index b53d3ebc5..8019da994 100644 --- a/backend/tests/app/tests/test_common_geom_shapes.clj +++ b/backend/tests/app/tests/test_common_geom_shapes.clj @@ -52,7 +52,7 @@ (t/testing "Shape without modifiers should stay the same" (t/are [type] (let [shape-before (create-test-shape type) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (= shape-before shape-after)) :rect :path)) @@ -61,7 +61,7 @@ (t/are [type] (let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}] (let [shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/is (not= shape-before shape-after)) (t/is (close? (get-in shape-before [:selrect :x]) @@ -82,7 +82,7 @@ (t/are [type] (let [modifiers {:displacement (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -95,7 +95,7 @@ :resize-vector (gpt/point 2 2) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/is (not= shape-before shape-after)) (t/is (close? (get-in shape-before [:selrect :x]) @@ -117,7 +117,7 @@ :resize-vector (gpt/point 1 1) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -130,7 +130,7 @@ :resize-vector (gpt/point 0 0) :resize-transform (gmt/matrix)} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/is (> (get-in shape-before [:selrect :width]) (get-in shape-after [:selrect :width]))) (t/is (> (get-in shape-after [:selrect :width]) 0)) @@ -144,7 +144,7 @@ (t/are [type] (let [modifiers {:rotation 30} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/is (not= shape-before shape-after)) @@ -168,7 +168,7 @@ (t/are [type] (let [modifiers {:rotation 0} shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -180,7 +180,7 @@ (let [modifiers {:displacement (gmt/matrix)} shape-before (-> (create-test-shape type {:modifiers modifiers}) (assoc :selrect selrect)) - shape-after (gsh/transform-shape shape-before)] + shape-after (gsh/transform-shape shape-before {:round-coords? false})] (= (:selrect shape-before) (:selrect shape-after))) :rect {:x 0 :y 0 :width ##Inf :height ##Inf} diff --git a/common/app/common/geom/shapes/transforms.cljc b/common/app/common/geom/shapes/transforms.cljc index dcfdd31fc..6929ed057 100644 --- a/common/app/common/geom/shapes/transforms.cljc +++ b/common/app/common/geom/shapes/transforms.cljc @@ -266,7 +266,7 @@ (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] + [shape transform round-coords?] ;; (let [points (-> shape :points (transform-points transform)) center (gco/center-points points) @@ -283,6 +283,11 @@ center (:width points-temp-dim) (:height points-temp-dim)) + (cond-> round-coords? + (-> (update :x #(mth/precision % 0)) + (update :y #(mth/precision % 0)) + (update :width #(mth/precision % 0)) + (update :height #(mth/precision % 0)))) (update :width max 1) (update :height max 1)) @@ -297,11 +302,7 @@ :else (-> shape - (merge rect-shape) - (update :x #(mth/precision % 0)) - (update :y #(mth/precision % 0)) - (update :width #(mth/precision % 0)) - (update :height #(mth/precision % 0))))] + (merge rect-shape)))] (as-> shape $ (update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix)) (update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix)))) @@ -347,18 +348,23 @@ %))) shape)) -(defn transform-shape [shape] - (let [shape (apply-displacement shape) - center (gco/center-shape shape) - modifiers (:modifiers shape)] - (if (and modifiers center) - (let [transform (modifiers->transform center modifiers)] - (-> shape - (set-flip modifiers) - (apply-transform transform) - (apply-text-resize shape modifiers) - (dissoc :modifiers))) - shape))) +(defn transform-shape + ([shape] + (transform-shape shape nil)) + + ([shape {:keys [round-coords?] + :or {round-coords? true}}] + (let [shape (apply-displacement shape) + center (gco/center-shape shape) + modifiers (:modifiers shape)] + (if (and modifiers center) + (let [transform (modifiers->transform center modifiers)] + (-> shape + (set-flip modifiers) + (apply-transform transform round-coords?) + (apply-text-resize shape modifiers) + (dissoc :modifiers))) + shape)))) (defn update-group-viewbox "Updates the viewbox for groups imported from SVG's" @@ -407,5 +413,5 @@ ;; need to remove the flip flags (assoc :flip-x false) (assoc :flip-y false) - (apply-transform (gmt/matrix))))) + (apply-transform (gmt/matrix) true)))) diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index 1fce3111b..87299b2a3 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -461,8 +461,9 @@ (defn merge-modifiers [objects modifiers] - (let [set-modifier (fn [objects [id modifiers]] - (-> objects - (d/update-when id assoc :modifiers modifiers)))] + (let [set-modifier + (fn [objects [id modifiers]] + (-> objects + (d/update-when id merge modifiers)))] (->> modifiers (reduce set-modifier objects)))) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index c7efb8627..9ce4a3d3f 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -6,7 +6,8 @@ (ns app.main.data.workspace.state-helpers (:require - [app.common.data :as d])) + [app.common.data :as d] + [app.common.pages :as cp])) (defn lookup-page-objects ([state] @@ -26,8 +27,9 @@ (defn lookup-selected [state] - (let [selected (get-in state [:workspace-local :selected]) - objects (lookup-page-objects state) + (let [objects (lookup-page-objects state) + selected (->> (get-in state [:workspace-local :selected]) + (cp/clean-loops objects)) is-present? (fn [id] (contains? objects id))] (into (d/ordered-set) (filter is-present?) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index e2b3b99b8..e2c0ad42f 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -72,6 +72,27 @@ :bottom-left [ex sy])] (gpt/point x y))) +(defn- fix-init-point + "Fix the initial point so the resizes are accurate" + [initial handler shape] + (let [{:keys [x y width height]} (:selrect shape) + {:keys [rotation]} shape + rotation (or rotation 0)] + (if (= rotation 0) + (cond-> initial + (contains? #{:left :top-left :bottom-left} handler) + (assoc :x x) + + (contains? #{:right :top-right :bottom-right} handler) + (assoc :x (+ x width)) + + (contains? #{:top :top-right :top-left} handler) + (assoc :y y) + + (contains? #{:bottom :bottom-right :bottom-left} handler) + (assoc :y (+ y height))) + initial))) + (defn finish-transform [] (ptk/reify ::finish-transform ptk/UpdateEvent @@ -84,6 +105,10 @@ (letfn [(resize [shape initial resizing-shapes layout [point lock? point-snap]] (let [{:keys [width height]} (:selrect shape) {:keys [rotation]} shape + rotation (or rotation 0) + + initial (fix-init-point initial handler shape) + shapev (-> (gpt/point width height)) scale-text (:scale-text layout) @@ -253,6 +278,7 @@ frame-id (cp/frame-id-by-position objects position) moving-shapes (->> ids + (cp/clean-loops objects) (map #(get objects %)) (remove #(= (:frame-id %) frame-id))) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 9b30723fd..48b6f00e8 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -72,7 +72,7 @@ [props] (let [shape (obj/get props "shape") frame (obj/get props "frame") - shape (-> (geom/transform-shape shape) + shape (-> (geom/transform-shape shape {:round-coords? false}) (geom/translate-to-frame frame)) opts #js {:shape shape :frame frame} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2adbe4189..392232143 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.viewport (:require [app.common.data :as d] + [app.common.pages :as cp] [app.common.geom.shapes :as gsh] [app.main.refs :as refs] [app.main.ui.context :as ctx] @@ -61,7 +62,9 @@ options (mf/deref refs/workspace-page-options) objects (mf/deref refs/workspace-page-objects) object-modifiers (mf/deref refs/workspace-modifiers) - objects (d/deep-merge objects object-modifiers) + objects (mf/use-memo + (mf/deps objects object-modifiers) + #(cp/merge-modifiers objects object-modifiers)) ;; STATE alt? (mf/use-state false) @@ -135,7 +138,9 @@ show-prototypes? (= options-mode :prototype) show-selection-handlers? (seq selected) show-snap-distance? (and (contains? layout :dynamic-alignment) (= transform :move) (not (empty? selected))) - show-snap-points? (and (contains? layout :dynamic-alignment) (or drawing-obj transform)) + show-snap-points? (and (or (contains? layout :dynamic-alignment) + (contains? layout :snap-grid)) + (or drawing-obj transform)) show-selrect? (and selrect (empty? drawing)) show-measures? (and (not transform) (not path-editing?) show-distances?)] @@ -283,6 +288,7 @@ :zoom zoom :page-id page-id :selected selected + :objects objects :modifiers modifiers}]) (when show-snap-distance? diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index e292b16dc..6c8733776 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -29,7 +29,7 @@ (mf/deps shape) #(when path? (upf/format-path (:content shape)))) - {:keys [id x y width height]} shape + {:keys [id x y width height selrect]} shape outline-type (case (:type shape) :circle "ellipse" @@ -53,10 +53,10 @@ {:d path-data :transform nil} - {:x x - :y y - :width width - :height height})] + {:x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect)})] [:> outline-type (map->obj (merge common props))])) diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index fa23c720c..85b91a8ff 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -241,8 +241,7 @@ disable-handlers (obj/get props "disable-handlers") current-transform (mf/deref refs/current-transform) - selrect (-> (:selrect shape) - minimum-selrect) + selrect (:selrect shape) transform (geom/transform-matrix shape {:no-flip true})] (when (not (#{:move :rotate} current-transform)) @@ -327,7 +326,7 @@ (mf/defc single-selection-handlers [{:keys [shape zoom color disable-handlers on-move-selected] :as props}] (let [shape-id (:id shape) - shape (geom/transform-shape shape) + shape (geom/transform-shape shape {:round-coords? false}) frame (mf/deref (refs/object-by-id (:frame-id shape))) frame (when-not (= (:id frame) uuid/zero) frame) diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index f9c4f18ca..9dda3ac8f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.viewport.snap-points (:require + [app.common.pages :as cp] [app.common.math :as mth] [app.common.data :as d] [app.common.geom.point :as gpt] @@ -151,15 +152,25 @@ (mf/defc snap-points {::mf/wrap [mf/memo]} - [{:keys [layout zoom selected page-id drawing transform modifiers] :as props}] - (let [shapes (mf/deref (refs/objects-by-id selected)) - filter-shapes (mf/deref refs/selected-shapes-with-children) + [{:keys [layout zoom objects selected page-id drawing transform modifiers] :as props}] + + (let [;; shapes (mf/deref (refs/objects-by-id selected)) + ;; filter-shapes (mf/deref refs/selected-shapes-with-children) + + shapes (->> selected + (map #(get objects %)) + (filterv (comp not nil?))) + filter-shapes (into #{} + (mapcat #(cp/get-object-with-children % objects)) + selected) + filter-shapes (fn [id] (if (= id :layout) (or (not (contains? layout :display-grid)) (not (contains? layout :snap-grid))) (or (filter-shapes id) (not (contains? layout :dynamic-alignment))))) + shapes (if drawing [drawing] shapes)] (when (or drawing transform) [:& snap-feedback {:shapes shapes diff --git a/frontend/src/app/util/geom/grid.cljs b/frontend/src/app/util/geom/grid.cljs index c1f882204..bd8f58bd1 100644 --- a/frontend/src/app/util/geom/grid.cljs +++ b/frontend/src/app/util/geom/grid.cljs @@ -88,7 +88,9 @@ (defn grid-snap-points "Returns the snap points for a given grid" - ([shape coord] (mapcat #(grid-snap-points shape % coord) (:grids shape))) + ([shape coord] + (mapcat #(grid-snap-points shape % coord) (:grids shape))) + ([shape {:keys [type display params] :as grid} coord] (when (:display grid) (case type diff --git a/frontend/src/app/worker/snaps.cljs b/frontend/src/app/worker/snaps.cljs index aa531dd4f..a2c395448 100644 --- a/frontend/src/app/worker/snaps.cljs +++ b/frontend/src/app/worker/snaps.cljs @@ -71,13 +71,23 @@ (fn [frame-id shapes] {:x (-> (rt/make-tree) (add-coord-data frame-id shapes :x)) :y (-> (rt/make-tree) (add-coord-data frame-id shapes :y))})] - (d/mapm create-index shapes-data))) +;; Attributes that will change the values of their snap +(def snap-attrs [:x :y :width :height :selrect :grids]) + (defn- update-snap-data [snap-data old-objects new-objects] - (let [changed? #(not= (get old-objects %) (get new-objects %)) + (let [changed? (fn [id] + (let [oldv (get old-objects id) + newv (get new-objects id)] + ;; Check first without select-keys because is faster if they are + ;; the same reference + (and (not= oldv newv) + (not= (select-keys oldv snap-attrs) + (select-keys newv snap-attrs))))) + is-deleted-frame? #(and (not= uuid/zero %) (contains? old-objects %) (not (contains? new-objects %)) @@ -119,9 +129,9 @@ :y (rt/make-tree)}))] (as-> snap-data $ + (reduce delete-data $ to-delete) (reduce add-frames $ frames-to-add) (reduce add-data $ to-add) - (reduce delete-data $ to-delete) (reduce delete-frames $ frames-to-delete)))) (defn- log-state @@ -160,8 +170,10 @@ (defmethod impl/handler :snaps/update-index [{:keys [page-id old-objects new-objects] :as message}] - ;; TODO: Check the difference and update the index acordingly (swap! state update-page page-id old-objects new-objects) + + ;; Uncomment this to regenerate the index everytime + #_(swap! state index-page page-id new-objects) ;; (log-state) nil)