From d19dc1cf5692c708295c5551f77fd4544ec2958c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 19 Jan 2021 23:02:51 +0100 Subject: [PATCH] :sparkles: Improved snap performance --- common/app/common/data.cljc | 8 +- frontend/src/app/main/snap.cljs | 98 +++++++++++-------- .../app/main/ui/workspace/snap_distances.cljs | 86 ++++++++-------- 3 files changed, 101 insertions(+), 91 deletions(-) diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index 871a13b4f..994b0a82e 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -177,15 +177,15 @@ (join [1 2 3] [:a :b]) => ([1 :a] [1 :b] [2 :a] [2 :b] [3 :a] [3 :b]) You can pass a function to merge the items. By default is `vector`: (join [1 2 3] [1 10 100] *) => (1 10 100 2 20 200 3 30 300)" - ([col1 col2] (join col1 col2 vector '())) - ([col1 col2 join-fn] (join col1 col2 join-fn '())) + ([col1 col2] (join col1 col2 vector [])) + ([col1 col2 join-fn] (join col1 col2 join-fn [])) ([col1 col2 join-fn acc] (cond (empty? col1) acc (empty? col2) acc :else (recur (rest col1) col2 join-fn - (core/concat acc (map (partial join-fn (first col1)) col2)))))) - + (let [other (mapv (partial join-fn (first col1)) col2)] + (concat acc other)))))) (def sentinel #?(:clj (Object.) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index e20db5f63..d12ed81bf 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -20,8 +20,8 @@ [app.main.refs :as refs] [app.util.geom.snap-points :as sp])) -(def ^:private snap-accuracy 5) -(def ^:private snap-distance-accuracy 10) +(defonce ^:private snap-accuracy 5) +(defonce ^:private snap-distance-accuracy 10) (defn- remove-from-snap-points [remove-id?] @@ -96,57 +96,69 @@ ;; snap-x is the second parameter because is the "source" to combine (rx/combine-latest snap->vector snap-y snap-x))) -(defn search-snap-distance [selrect coord shapes-lt shapes-gt] +(defn calculate-snap [coord selrect shapes-lt shapes-gt] (let [dist (fn [[sh1 sh2]] (-> sh1 (gsh/distance-shapes sh2) coord)) dist-lt (fn [other] (-> (:selrect other) (gsh/distance-selrect selrect) coord)) dist-gt (fn [other] (-> selrect (gsh/distance-selrect (:selrect other)) coord)) - ;; Calculates the distance between all the shapes given as argument - inner-distance (fn [shapes] - (->> shapes - (sort-by coord) - (d/map-perm vector) - (filter (fn [[sh1 sh2]] (gsh/overlap-coord? coord sh1 sh2))) - (map dist) - (filter #(> % 0)))) - ;; Calculates the snap distance when in the middle of two shapes - between-snap (fn [[sh-lt sh-gt]] - ;; To calculate the middle snap. - ;; Given x, the distance to a left shape and y to a right shape - ;; x - v = y + v => v = (x - y)/2 - ;; v will be the vector that we need to move the shape so it "snaps" - ;; in the middle - (/ (- (dist-gt sh-gt) - (dist-lt sh-lt)) 2)) - ] - (->> shapes-lt - (rx/combine-latest vector shapes-gt) - (rx/map (fn [[shapes-lt shapes-gt]] - (let [;; Distance between the elements in an area, these are the snap - ;; candidates to either side - lt-cand (inner-distance shapes-lt) - gt-cand (inner-distance shapes-gt) + between-snap + (fn [[sh-lt sh-gt]] + ;; To calculate the middle snap. + ;; Given x, the distance to a left shape and y to a right shape + ;; x - v = y + v => v = (x - y)/2 + ;; v will be the vector that we need to move the shape so it "snaps" + ;; in the middle + (/ (- (dist-gt sh-gt) + (dist-lt sh-lt)) 2)) - ;; Distance between the elements to either side and the current shape - ;; this is the distance that will "snap" - lt-dist (map dist-lt shapes-lt) - gt-dist (map dist-gt shapes-gt) + ;; Calculates the distance between all the shapes given as argument + inner-distance + (fn [shapes] + (->> shapes + (sort-by coord) + (d/map-perm vector) + (filter (fn [[sh1 sh2]] (gsh/overlap-coord? coord sh1 sh2))) + (map dist) + (filterv #(> % 0)))) - ;; Calculate the snaps, we need to reverse depending on area - lt-snap (d/join lt-cand lt-dist -) - gt-snap (d/join gt-dist gt-cand -) + best-snap + (fn [acc val] + ;; Using a number is faster than accesing the variable. + ;; Keep up to date with `snap-distance-accuracy` + (if (and (<= val 10) (>= val (- 10))) + (min acc val) + acc)) - ;; Calculate snap-between - between-snap (->> (d/join shapes-lt shapes-gt) - (map between-snap)) + ;; Distance between the elements in an area, these are the snap + ;; candidates to either side + lt-cand (inner-distance shapes-lt) + gt-cand (inner-distance shapes-gt) - ;; Search the minimum snap - min-snap (->> (concat lt-snap gt-snap between-snap) - (filter #(<= (mth/abs %) snap-distance-accuracy)) - (reduce min ##Inf))] + ;; Distance between the elements to either side and the current shape + ;; this is the distance that will "snap" + lt-dist (mapv dist-lt shapes-lt) + gt-dist (mapv dist-gt shapes-gt) - (if (mth/finite? min-snap) [0 min-snap] nil))))))) + ;; Calculate the snaps, we need to reverse depending on area + lt-snap (d/join lt-cand lt-dist -) + gt-snap (d/join gt-dist gt-cand -) + + ;; Calculate snap-between + between-snap (->> (d/join shapes-lt shapes-gt) + (map between-snap)) + + ;; Search the minimum snap + snap-list (-> [] (d/concat lt-snap) (d/concat gt-snap) (d/concat between-snap)) + min-snap (reduce best-snap ##Inf snap-list)] + + (if (mth/finite? min-snap) [0 min-snap] nil))) + +(defn search-snap-distance [selrect coord shapes-lt shapes-gt] + (->> shapes-lt + (rx/combine-latest vector shapes-gt) + (rx/map (fn [[shapes-lt shapes-gt]] + (calculate-snap coord selrect shapes-lt shapes-gt))))) (defn select-shapes-area [page-id shapes objects area-selrect] diff --git a/frontend/src/app/main/ui/workspace/snap_distances.cljs b/frontend/src/app/main/ui/workspace/snap_distances.cljs index 139b9c289..f501a71d6 100644 --- a/frontend/src/app/main/ui/workspace/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/snap_distances.cljs @@ -112,52 +112,11 @@ :x2 x2 :y2 y2 :style {:stroke line-color :stroke-width (str (/ 1 zoom))}}])])) -(mf/defc shape-distance - {::mf/wrap-props false} - [props] - (let [frame (unchecked-get props "frame") - selrect (unchecked-get props "selrect") - page-id (unchecked-get props "page-id") - zoom (unchecked-get props "zoom") - coord (unchecked-get props "coord") - selected (unchecked-get props "selected") - - subject (mf/use-memo #(rx/subject)) - to-measure (mf/use-state []) - - pair->distance+pair +(defn calculate-segments [coord selrect lt-shapes gt-shapes] + (let [pair->distance+pair (fn [[sh1 sh2]] [(-> (gsh/distance-shapes sh1 sh2) coord (mth/precision 0)) [sh1 sh2]]) - contains-selected? - (fn [selected pairs] - (let [has-selected? - (fn [[_ [sh1 sh2]]] - (or (selected (:id sh1)) - (selected (:id sh2))))] - (some has-selected? pairs))) - - query-worker - (fn [[selrect selected frame]] - (let [lt-side (if (= coord :x) :left :top) - gt-side (if (= coord :x) :right :bottom) - container-selrec (or (:selrect frame) - (gsh/rect->selrect @refs/vbox)) - areas (gsh/selrect->areas container-selrec selrect) - query-side (fn [side] - (let [rect (gsh/pad-selrec (areas side))] - (if (and (> (:width rect) 0) (> (:height rect) 0)) - (->> (uw/ask! {:cmd :selection/query - :page-id page-id - :frame-id (:id frame) - :rect rect}) - (rx/map #(set/difference % selected)) - (rx/map #(->> % (map (partial get @refs/workspace-page-objects))))) - (rx/of nil))))] - - (->> (query-side lt-side) - (rx/combine-latest vector (query-side gt-side))))) - distance-to-selrect (fn [shape] (let [sr (:selrect shape)] @@ -183,7 +142,6 @@ (some #(<= (mth/abs (- value %)) 1)))) ;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter - [lt-shapes gt-shapes] @to-measure ;; Gets the distance to the current selection lt-distances (->> lt-shapes (map distance-to-selrect) (filter pos?) (into #{})) @@ -225,6 +183,46 @@ (map #(vector selrect (:selrect %)))) segments-to-display (d/concat #{} other-shapes-segments selection-segments)] + segments-to-display)) + +(mf/defc shape-distance + {::mf/wrap-props false} + [props] + (let [frame (unchecked-get props "frame") + selrect (unchecked-get props "selrect") + page-id (unchecked-get props "page-id") + zoom (unchecked-get props "zoom") + coord (unchecked-get props "coord") + selected (unchecked-get props "selected") + + subject (mf/use-memo #(rx/subject)) + to-measure (mf/use-state []) + + query-worker + (fn [[selrect selected frame]] + (let [lt-side (if (= coord :x) :left :top) + gt-side (if (= coord :x) :right :bottom) + container-selrec (or (:selrect frame) + (gsh/rect->selrect @refs/vbox)) + areas (gsh/selrect->areas container-selrec selrect) + query-side (fn [side] + (let [rect (gsh/pad-selrec (areas side))] + (if (and (> (:width rect) 0) (> (:height rect) 0)) + (->> (uw/ask! {:cmd :selection/query + :page-id page-id + :frame-id (:id frame) + :rect rect}) + (rx/map #(set/difference % selected)) + (rx/map #(->> % (map (partial get @refs/workspace-page-objects))))) + (rx/of nil))))] + + (->> (query-side lt-side) + (rx/combine-latest vector (query-side gt-side))))) + + + [lt-shapes gt-shapes] @to-measure + + segments-to-display (calculate-segments coord selrect lt-shapes gt-shapes)] (mf/use-effect (fn []