0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 09:08:31 -05:00

Improved performance of snap to distances

This commit is contained in:
alonso.torres 2023-01-05 15:43:27 +01:00
parent 2c1fb1424c
commit c459c56f37
4 changed files with 73 additions and 71 deletions

View file

@ -82,24 +82,12 @@
(update :height (comp inc inc))))))
(defn selrect->areas [bounds selrect]
(let [make-selrect
(fn [x1 y1 x2 y2]
(let [x1 (min x1 x2)
x2 (max x1 x2)
y1 (min y1 y2)
y2 (max y1 y2)]
{:x1 x1 :y1 y1
:x2 x2 :y2 y2
:x x1 :y y1
:width (- x2 x1)
:height (- y2 y1)
:type :rect}))
{bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds
(let [{bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds
{sr-x1 :x1 sr-x2 :x2 sr-y1 :y1 sr-y2 :y2} selrect]
{:left (make-selrect bound-x1 sr-y1 sr-x1 sr-y2)
:top (make-selrect sr-x1 bound-y1 sr-x2 sr-y1)
:right (make-selrect sr-x2 sr-y1 bound-x2 sr-y2)
:bottom (make-selrect sr-x1 sr-y2 sr-x2 bound-y2)}))
{:left (gpr/corners->selrect bound-x1 sr-y1 sr-x1 sr-y2)
:top (gpr/corners->selrect sr-x1 bound-y1 sr-x2 sr-y1)
:right (gpr/corners->selrect sr-x2 sr-y1 bound-x2 sr-y2)
:bottom (gpr/corners->selrect sr-x1 sr-y2 sr-x2 bound-y2)}))
(defn distance-selrect [selrect other]
(let [{:keys [x1 y1]} other
@ -161,6 +149,7 @@
(dm/export gpr/contains-selrect?)
(dm/export gpr/contains-point?)
(dm/export gpr/close-selrect?)
(dm/export gpr/clip-selrect)
(dm/export gtr/move)
(dm/export gtr/absolute-move)

View file

@ -212,9 +212,13 @@
(<= (:y2 sr2) (:y2 sr1))))
(defn corners->selrect
[p1 p2]
(let [xp1 (:x p1)
xp2 (:x p2)
yp1 (:y p1)
yp2 (:y p2)]
(make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2)))))
([p1 p2]
(corners->selrect (:x p1) (:y p1) (:x p2) (:y p2)))
([xp1 yp1 xp2 yp2]
(make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2)))))
(defn clip-selrect
[{:keys [x1 y1 x2 y2] :as sr} bounds]
(when (some? sr)
(let [{bx1 :x1 by1 :y1 bx2 :x2 by2 :y2} (rect->selrect bounds)]
(corners->selrect (max bx1 x1) (max by1 y1) (min bx2 x2) (min by2 y2)))))

View file

@ -144,17 +144,6 @@
dist-lt (fn [other] (sr-distance coord (:selrect other) selrect))
dist-gt (fn [other] (sr-distance coord selrect (:selrect other)))
;; 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))
;; Calculates the distance between all the shapes given as argument
inner-distance
(fn [selrects]
@ -164,14 +153,6 @@
#(overlap? coord %1 %2)
#{})))
best-snap
(fn [acc val]
;; Using a number is faster than accessing the variable.
;; Keep up to date with `snap-distance-accuracy`
(if (and (<= val snap-distance-accuracy) (>= val (- snap-distance-accuracy)))
(min acc val)
acc))
;; Distance between the elements in an area, these are the snap
;; candidates to either side
lt-cand (inner-distance (mapv :selrect shapes-lt))
@ -182,17 +163,46 @@
lt-dist (into #{} (map dist-lt) shapes-lt)
gt-dist (into #{} (map dist-gt) shapes-gt)
;; 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 -)
get-side-snaps
(fn [candidates distances]
;; We add to the range tree the distrances between elements
;; then, for each distance from the selection we query the tree
;; to find a snap
(let [range-tree (rt/make-tree)
range-tree
(->> candidates
(reduce #(rt/insert %1 %2 %2) range-tree))]
(->> distances
(mapcat
(fn [cd]
(->> (rt/range-query
range-tree
(- cd snap-distance-accuracy)
(+ cd snap-distance-accuracy))
(map #(- (first %) cd ))))))))
;; Calculate snap-between
between-snap (->> (d/join shapes-lt shapes-gt)
(map between-snap))
get-middle-snaps
(fn [lt-dist gt-dist]
(let [range-tree (rt/make-tree)
range-tree (->> lt-dist
(reduce #(rt/insert %1 %2 %2) range-tree))]
(->> gt-dist
(mapcat (fn [cd]
(->> (rt/range-query
range-tree
(- cd (* snap-distance-accuracy 2))
(+ cd (* snap-distance-accuracy 2)))
(map #(/ (- cd (first %)) 2))))))))
;; Calculate the snaps, we need to reverse depending on area
lt-snap (get-side-snaps lt-cand lt-dist)
gt-snap (get-side-snaps gt-dist gt-cand)
md-snap (get-middle-snaps lt-dist gt-dist)
;; Search the minimum snap
snap-list (d/concat-vec lt-snap gt-snap between-snap)
min-snap (reduce best-snap ##Inf snap-list)]
snap-list (d/concat-vec lt-snap gt-snap md-snap)
min-snap (reduce min ##Inf snap-list)]
(if (d/num? min-snap) [0 min-snap] nil)))
@ -202,14 +212,14 @@
(calculate-snap coord selrect shapes-lt shapes-gt zoom)))))
(defn select-shapes-area
[page-id shapes objects area-selrect]
[page-id frame-id selected objects area]
(->> (uw/ask! {:cmd :selection/query
:page-id page-id
:frame-id (->> shapes first :frame-id)
:frame-id frame-id
:include-frames? true
:rect area-selrect})
:rect area})
(rx/map #(cph/clean-loops objects %))
(rx/map #(set/difference % (into #{} (map :id shapes))))
(rx/map #(set/difference % selected))
(rx/map #(map (d/getf objects) %))))
(defn closest-distance-snap
@ -220,9 +230,14 @@
(->> (rx/of (vector frame selrect))
(rx/merge-map
(fn [[frame selrect]]
(let [areas (->> (gsh/selrect->areas (or (:selrect frame)
(gsh/rect->selrect @refs/vbox)) selrect)
(d/mapm #(select-shapes-area page-id shapes objects %2)))
(let [vbox (gsh/rect->selrect @refs/vbox)
frame-id (->> shapes first :frame-id)
selected (into #{} (map :id shapes))
areas (->> (gsh/selrect->areas
(or (gsh/clip-selrect (:selrect frame) vbox)
vbox)
selrect)
(d/mapm #(select-shapes-area page-id frame-id selected objects %2)))
snap-x (search-snap-distance selrect :x (:left areas) (:right areas) zoom)
snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas) zoom)]
(rx/combine-latest snap-x snap-y))))

View file

@ -9,11 +9,10 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.refs :as refs]
[app.main.snap :as ams]
[app.main.ui.formats :as fmt]
[app.main.worker :as uw]
[beicon.core :as rx]
[clojure.set :as set]
[cuerdas.core :as str]
@ -152,7 +151,7 @@
check-in-set
(fn [value number-set]
(->> number-set
(some #(<= (mth/abs (- value %)) 0.01))))
(some #(<= (mth/abs (- value %)) 1.5))))
;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter)
@ -218,21 +217,16 @@
(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)
vbox (gsh/rect->selrect @refs/vbox)
areas (gsh/selrect->areas
(or (gsh/clip-selrect (:selrect frame) vbox) vbox)
selrect)
query-side (fn [side]
(let [rect (get areas side)]
(if (and (> (:width rect) 0) (> (:height rect) 0))
(->> (uw/ask! {:cmd :selection/query
:page-id page-id
:frame-id (:id frame)
:include-frames? true
:rect rect})
(rx/map #(cph/clean-loops @refs/workspace-page-objects %))
(rx/map #(set/difference % selected))
(rx/map #(->> % (map (partial get @refs/workspace-page-objects)))))
(ams/select-shapes-area page-id (:id frame) selected @refs/workspace-page-objects rect)
(rx/of nil))))]
(rx/combine-latest (query-side lt-side)
(query-side gt-side))))