mirror of
https://github.com/penpot/penpot.git
synced 2025-03-15 17:21:17 -05:00
Merge pull request #229 from uxbox/243/advanced_dynamic_alignment
Adds snap to distance
This commit is contained in:
commit
751bd5e3d6
8 changed files with 422 additions and 26 deletions
|
@ -110,6 +110,33 @@
|
|||
[mfn coll]
|
||||
(into {} (map (fn [[key val]] [key (mfn key val)]) coll)))
|
||||
|
||||
(defn map-perm
|
||||
"Maps a function to each pair of values that can be combined inside the
|
||||
function without repetition.
|
||||
Example:
|
||||
(map-perm vector [1 2 3 4]) => [[1 2] [1 3] [1 4] [2 3] [2 4] [3 4]]"
|
||||
[mfn coll]
|
||||
(if (empty? coll)
|
||||
[]
|
||||
(core/concat
|
||||
(map (partial mfn (first coll)) (rest coll))
|
||||
(map-perm mfn (rest coll)))))
|
||||
|
||||
(defn join
|
||||
"Returns a new collection with the cartesian product of both collections.
|
||||
For example:
|
||||
(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-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))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -73,12 +73,22 @@
|
|||
|
||||
|
||||
(defn min
|
||||
[{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(Point. (c/min x1 x2) (c/min y1 y2)))
|
||||
([] (min nil nil))
|
||||
([p1] (min p1 nil))
|
||||
([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(cond
|
||||
(nil? p1) p2
|
||||
(nil? p2) p1
|
||||
:else (Point. (c/min x1 x2) (c/min y1 y2)))))
|
||||
|
||||
(defn max
|
||||
[{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(Point. (c/max x1 x2) (c/max y1 y2)))
|
||||
([] (max nil nil))
|
||||
([p1] (max p1 nil))
|
||||
([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}]
|
||||
(cond
|
||||
(nil? p1) p2
|
||||
(nil? p2) p1
|
||||
:else (Point. (c/max x1 x2) (c/max y1 y2)))))
|
||||
|
||||
(defn inverse
|
||||
[{:keys [x y] :as p}]
|
||||
|
|
|
@ -48,7 +48,11 @@
|
|||
(update :y inc-y))))]
|
||||
(-> shape
|
||||
(update :x inc-x)
|
||||
(update :x1 inc-x)
|
||||
(update :x2 inc-x)
|
||||
(update :y inc-y)
|
||||
(update :y1 inc-y)
|
||||
(update :y2 inc-y)
|
||||
(update-in [:selrect :x] inc-x)
|
||||
(update-in [:selrect :x1] inc-x)
|
||||
(update-in [:selrect :x2] inc-x)
|
||||
|
@ -548,7 +552,6 @@
|
|||
:type :rect}]
|
||||
(overlaps? shape selrect)))
|
||||
|
||||
|
||||
(defn calculate-rec-path-skew-angle
|
||||
[path-shape]
|
||||
(let [p1 (get-in path-shape [:segments 2])
|
||||
|
@ -587,6 +590,63 @@
|
|||
rot-sign (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)]
|
||||
(* rot-sign rot-angle)))
|
||||
|
||||
(defn pad-selrec
|
||||
([selrect] (pad-selrec selrect 1))
|
||||
([selrec size]
|
||||
(let [inc #(+ % size)
|
||||
dec #(- % size)]
|
||||
(-> selrec
|
||||
(update :x dec)
|
||||
(update :y dec)
|
||||
(update :x1 dec)
|
||||
(update :y1 dec)
|
||||
(update :x2 inc)
|
||||
(update :y2 inc)
|
||||
(update :width (comp inc inc))
|
||||
(update :height (comp inc inc))))))
|
||||
|
||||
(defn selrect->areas [bounds selrect]
|
||||
(let [make-selrect
|
||||
(fn [x1 y1 x2 y2]
|
||||
{:x1 x1 :y1 y1 :x2 x2 :y2 y2 :x x1 :y y1
|
||||
:width (- x2 x1) :height (- y2 y1) :type :rect})
|
||||
{frame-x1 :x1 frame-x2 :x2 frame-y1 :y1 frame-y2 :y2
|
||||
frame-width :width frame-height :height} bounds
|
||||
{sr-x1 :x1 sr-x2 :x2 sr-y1 :y1 sr-y2 :y2
|
||||
sr-width :width sr-height :height} selrect]
|
||||
{:left (make-selrect frame-x1 sr-y1 sr-x1 sr-y2)
|
||||
:top (make-selrect sr-x1 frame-y1 sr-x2 sr-y1)
|
||||
:right (make-selrect sr-x2 sr-y1 frame-x2 sr-y2)
|
||||
:bottom (make-selrect sr-x1 sr-y2 sr-x2 frame-y2)}))
|
||||
|
||||
(defn distance-selrect [selrect other]
|
||||
(let [{:keys [x1 y1]} other
|
||||
{:keys [x2 y2]} selrect]
|
||||
(gpt/point (- x1 x2) (- y1 y2))))
|
||||
|
||||
(defn distance-shapes [shape other]
|
||||
(distance-selrect (:selrect shape) (:selrect other)))
|
||||
|
||||
(defn overlap-coord?
|
||||
"Checks if two shapes overlap in one axis"
|
||||
[coord shape other]
|
||||
(let [[s1c1 s1c2 s2c1 s2c2]
|
||||
;; If checking if overlaps in x-axis we need to check the y
|
||||
;; coordinates, and the other way around
|
||||
(if (= coord :x)
|
||||
[(get-in shape [:selrect :y1])
|
||||
(get-in shape [:selrect :y2])
|
||||
(get-in other [:selrect :y1])
|
||||
(get-in other [:selrect :y2])]
|
||||
[(get-in shape [:selrect :x1])
|
||||
(get-in shape [:selrect :x2])
|
||||
(get-in other [:selrect :x1])
|
||||
(get-in other [:selrect :x2])])]
|
||||
(or (and (>= s2c1 s1c1) (<= s2c1 s1c2))
|
||||
(and (>= s2c2 s1c1) (<= s2c2 s1c2))
|
||||
(and (>= s1c1 s2c1) (<= s1c1 s2c2))
|
||||
(and (>= s1c2 s2c1) (<= s1c2 s2c2)))))
|
||||
|
||||
(defn transform-shape-point
|
||||
"Transform a point around the shape center"
|
||||
[point shape transform]
|
||||
|
|
|
@ -219,6 +219,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (get state :current-page-id)
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids)
|
||||
stopper (rx/filter ms/mouse-up? stream)
|
||||
layout (get state :workspace-layout)]
|
||||
|
@ -226,7 +227,7 @@
|
|||
(->> ms/mouse-position
|
||||
(rx/take-until stopper)
|
||||
(rx/map #(gpt/to-vec from-position %))
|
||||
(rx/switch-map #(snap/closest-snap-move page-id shapes layout %))
|
||||
(rx/switch-map #(snap/closest-snap-move page-id shapes objects layout %))
|
||||
(rx/map gmt/translate-matrix)
|
||||
(rx/map #(set-modifiers ids {:displacement %})))
|
||||
|
||||
|
|
|
@ -9,14 +9,18 @@
|
|||
|
||||
(ns uxbox.main.snap
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.common.uuid :refer [zero]]
|
||||
[uxbox.common.math :as mth]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.geom.point :as gpt]
|
||||
[uxbox.common.geom.shapes :as gsh]
|
||||
[uxbox.main.worker :as uw]
|
||||
[uxbox.util.geom.snap-points :as sp]))
|
||||
|
||||
(def ^:private snap-accuracy 5)
|
||||
(def ^:private snap-distance-accuracy 10)
|
||||
|
||||
(defn- remove-from-snap-points [remove-id?]
|
||||
(fn [query-result]
|
||||
|
@ -77,16 +81,91 @@
|
|||
(rx/map (remove-from-snap-points filter-shapes))
|
||||
(rx/map (get-min-distance-snap points coord)))))
|
||||
|
||||
(defn snap->vector [[from-x to-x] [from-y to-y]]
|
||||
(when (or from-x to-x from-y to-y)
|
||||
(let [from (gpt/point (or from-x 0) (or from-y 0))
|
||||
to (gpt/point (or to-x 0) (or to-y 0))]
|
||||
(gpt/to-vec from to))))
|
||||
|
||||
(defn- closest-snap
|
||||
[page-id frame-id points filter-shapes]
|
||||
(let [snap-x (search-snap page-id frame-id points :x filter-shapes)
|
||||
snap-y (search-snap page-id frame-id points :y filter-shapes)
|
||||
snap-as-vector (fn [[from-x to-x] [from-y to-y]]
|
||||
(let [from (gpt/point (or from-x 0) (or from-y 0))
|
||||
to (gpt/point (or to-x 0) (or to-y 0))]
|
||||
(gpt/to-vec from to)))]
|
||||
snap-y (search-snap page-id frame-id points :y filter-shapes)]
|
||||
;; snap-x is the second parameter because is the "source" to combine
|
||||
(rx/combine-latest snap-as-vector snap-y snap-x)))
|
||||
(rx/combine-latest snap->vector snap-y snap-x)))
|
||||
|
||||
(defn search-snap-distance [selrect coord 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)
|
||||
|
||||
;; 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)
|
||||
|
||||
;; 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
|
||||
min-snap (->> (concat lt-snap gt-snap between-snap)
|
||||
(filter #(<= (mth/abs %) snap-distance-accuracy))
|
||||
(reduce min ##Inf))]
|
||||
|
||||
(if (mth/finite? min-snap) [0 min-snap] nil))
|
||||
|
||||
)))))
|
||||
|
||||
(defn select-shapes-area [page-id shapes objects area-selrect]
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
:rect area-selrect})
|
||||
(rx/map #(set/difference % (into #{} (map :id shapes))))
|
||||
(rx/map (fn [ids] (map #(get objects %) ids)))))
|
||||
|
||||
(defn closest-distance-snap [page-id shapes objects movev]
|
||||
(->> (rx/of shapes)
|
||||
(rx/map #(vector (->> % first :frame-id (get objects))
|
||||
(-> % gsh/selection-rect (gsh/move movev))))
|
||||
(rx/merge-map
|
||||
(fn [[frame selrect]]
|
||||
(let [areas (->> (gsh/selrect->areas (:selrect frame) selrect)
|
||||
(d/mapm #(select-shapes-area page-id shapes objects %2)))
|
||||
snap-x (search-snap-distance selrect :x (:left areas) (:right areas))
|
||||
snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas))]
|
||||
(rx/combine-latest snap->vector snap-y snap-x))))))
|
||||
|
||||
(defn closest-snap-point
|
||||
[page-id shapes layout point]
|
||||
|
@ -98,11 +177,12 @@
|
|||
(or (filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment)))))]
|
||||
(->> (closest-snap page-id frame-id [point] filter-shapes)
|
||||
(rx/map #(or % (gpt/point 0 0)))
|
||||
(rx/map #(gpt/add point %))
|
||||
(rx/map gpt/round))))
|
||||
|
||||
(defn closest-snap-move
|
||||
[page-id shapes layout movev]
|
||||
[page-id shapes objects layout movev]
|
||||
(let [frame-id (snap-frame-id shapes)
|
||||
filter-shapes (into #{} (map :id shapes))
|
||||
filter-shapes (fn [id] (if (= id :layout)
|
||||
|
@ -116,6 +196,9 @@
|
|||
|
||||
;; Move the points in the translation vector
|
||||
(map #(gpt/add % movev)))]
|
||||
(->> (closest-snap page-id frame-id shapes-points filter-shapes)
|
||||
(rx/map #(gpt/add movev %))
|
||||
(rx/map gpt/round))))
|
||||
(->> (rx/merge (closest-snap page-id frame-id shapes-points filter-shapes)
|
||||
(when (contains? layout :dynamic-alignment)
|
||||
(closest-distance-snap page-id shapes objects movev)))
|
||||
(rx/reduce gpt/min)
|
||||
(rx/map #(or % (gpt/point 0 0)))
|
||||
(rx/map #(gpt/add movev %)))))
|
||||
|
|
212
frontend/src/uxbox/main/ui/workspace/snap_distances.cljs
Normal file
212
frontend/src/uxbox/main/ui/workspace/snap_distances.cljs
Normal file
|
@ -0,0 +1,212 @@
|
|||
(ns uxbox.main.ui.workspace.snap-distances
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.snap :as snap]
|
||||
[uxbox.util.geom.snap-points :as sp]
|
||||
[uxbox.common.geom.point :as gpt]
|
||||
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.common.pages :as cp]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.geom.shapes :as gsh]
|
||||
[uxbox.common.math :as mth]
|
||||
[uxbox.main.worker :as uw]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def ^:private line-color "#D383DA")
|
||||
(def ^:private segment-gap 2)
|
||||
(def ^:private segment-gap-side 5)
|
||||
|
||||
(defn selected->cross-selrec [frame selrect coord]
|
||||
(let [areas (gsh/selrect->areas (:selrect frame) selrect)]
|
||||
(if (= :x coord)
|
||||
[(gsh/pad-selrec (:left areas))
|
||||
(gsh/pad-selrec (:right areas))]
|
||||
[(gsh/pad-selrec (:top areas))
|
||||
(gsh/pad-selrec (:bottom areas))])))
|
||||
|
||||
(defn half-point
|
||||
"Calculates the middle point of the overlap between two selrects in the opposite axis"
|
||||
[coord sr1 sr2]
|
||||
(let [c1 (max (get sr1 (if (= :x coord) :y1 :x1))
|
||||
(get sr2 (if (= :x coord) :y1 :x1)))
|
||||
c2 (min (get sr1 (if (= :x coord) :y2 :x2))
|
||||
(get sr2 (if (= :x coord) :y2 :x2)))
|
||||
half-point (+ c1 (/ (- c2 c1) 2))]
|
||||
half-point))
|
||||
|
||||
|
||||
(mf/defc shape-distance-segment
|
||||
"Displays a segment between two selrects with the distance between them"
|
||||
[{:keys [sr1 sr2 coord zoom]}]
|
||||
(let [from-c (min (get sr1 (if (= :x coord) :x2 :y2))
|
||||
(get sr2 (if (= :x coord) :x2 :y2)))
|
||||
to-c (max (get sr1 (if (= :x coord) :x1 :y1))
|
||||
(get sr2 (if (= :x coord) :x1 :y1)))
|
||||
distance (mth/round (- to-c from-c))
|
||||
half-point (half-point coord sr1 sr2)]
|
||||
|
||||
[:g.distance-segment
|
||||
(let [point [(+ from-c (/ distance 2))
|
||||
(if (= coord :x)
|
||||
(- half-point (/ 3 zoom))
|
||||
(+ half-point (/ 5 zoom)))]
|
||||
[x y] (if (= :x coord) point (reverse point))]
|
||||
[:text {:x x
|
||||
:y y
|
||||
:font-size (/ 12 zoom)
|
||||
:fill line-color
|
||||
:text-anchor (if (= coord :x) "middle" "left")}
|
||||
(mth/round distance)])
|
||||
|
||||
(let [p1 [(+ from-c (/ segment-gap zoom)) (+ half-point (/ segment-gap-side zoom))]
|
||||
p2 [(+ from-c (/ segment-gap zoom)) (- half-point (/ segment-gap-side zoom))]
|
||||
[x1 y1] (if (= :x coord) p1 (reverse p1))
|
||||
[x2 y2] (if (= :x coord) p2 (reverse p2))]
|
||||
[:line {:x1 x1 :y1 y1
|
||||
:x2 x2 :y2 y2
|
||||
:style {:stroke line-color :stroke-width (str (/ 1 zoom))}}])
|
||||
(let [p1 [(- to-c (/ segment-gap zoom)) (+ half-point (/ segment-gap-side zoom))]
|
||||
p2 [(- to-c (/ segment-gap zoom)) (- half-point (/ segment-gap-side zoom))]
|
||||
[x1 y1] (if (= :x coord) p1 (reverse p1))
|
||||
[x2 y2] (if (= :x coord) p2 (reverse p2))]
|
||||
[:line {:x1 x1 :y1 y1
|
||||
:x2 x2 :y2 y2
|
||||
:style {:stroke line-color :stroke-width (str (/ 1 zoom))}}])
|
||||
(let [p1 [(+ from-c (/ segment-gap zoom)) half-point]
|
||||
p2 [(- to-c (/ segment-gap zoom)) half-point]
|
||||
[x1 y1] (if (= :x coord) p1 (reverse p1))
|
||||
[x2 y2] (if (= :x coord) p2 (reverse p2))]
|
||||
[:line {:x1 x1 :y1 y1
|
||||
:x2 x2 :y2 y2
|
||||
:style {:stroke line-color :stroke-width (str (/ 1 zoom))}}])]))
|
||||
|
||||
(mf/defc shape-distance [{:keys [frame selrect page-id zoom coord selected]}]
|
||||
(let [subject (mf/use-memo #(rx/subject))
|
||||
to-measure (mf/use-state [])
|
||||
|
||||
pair->distance+pair
|
||||
(fn [[sh1 sh2]]
|
||||
[(-> (gsh/distance-shapes sh1 sh2) coord mth/round) [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)
|
||||
areas (gsh/selrect->areas (:selrect frame) selrect)
|
||||
query-side (fn [side]
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
:page-id page-id
|
||||
:rect (gsh/pad-selrec (areas side))})
|
||||
(rx/map #(set/difference % selected))
|
||||
(rx/map #(->> % (map (partial get @refs/workspace-objects))))))]
|
||||
|
||||
(->> (query-side lt-side)
|
||||
(rx/combine-latest vector (query-side gt-side)))))
|
||||
|
||||
distance-to-selrect
|
||||
(fn [shape]
|
||||
(let [sr (:selrect shape)]
|
||||
(-> (if (<= (coord sr) (coord selrect))
|
||||
(gsh/distance-selrect sr selrect)
|
||||
(gsh/distance-selrect selrect sr))
|
||||
coord
|
||||
mth/round)))
|
||||
|
||||
get-shapes-match
|
||||
(fn [pred? shapes]
|
||||
(->> shapes
|
||||
(sort-by coord)
|
||||
(d/map-perm vector)
|
||||
(filter (fn [[sh1 sh2]] (gsh/overlap-coord? coord sh1 sh2)))
|
||||
(map pair->distance+pair)
|
||||
(filter (comp pred? first))))
|
||||
|
||||
;; 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 #{}))
|
||||
gt-distances (->> gt-shapes (map distance-to-selrect) (filter pos?) (into #{}))
|
||||
|
||||
;; We'll show the distances that match a distance from the selrect
|
||||
show-candidate? (set/union lt-distances gt-distances)
|
||||
|
||||
;; Checks the distances between elements for distances that match the set of distances
|
||||
distance-coincidences (concat (get-shapes-match show-candidate? lt-shapes)
|
||||
(get-shapes-match show-candidate? gt-shapes))
|
||||
|
||||
;; Show the distances that either match one of the distances from the selrect
|
||||
;; or are from the selrect and go to a shape on the left and to the right
|
||||
show-distance? (into #{} (concat
|
||||
(map first distance-coincidences)
|
||||
(set/intersection lt-distances gt-distances)))
|
||||
|
||||
;; These are the segments whose distance will be displayed
|
||||
|
||||
;; First segments from segments different that the selectio
|
||||
other-shapes-segments (->> distance-coincidences
|
||||
(map second) ;; Retrieves list of [shape,shape] tuples
|
||||
(map #(mapv :selrect %))) ;; Changes [shape,shape] to [selrec,selrec]
|
||||
|
||||
;; Segments from the selection to other
|
||||
selection-segments (->> (concat lt-shapes gt-shapes)
|
||||
(filter #(show-distance? (distance-to-selrect %)))
|
||||
(map #(vector selrect (:selrect %))))
|
||||
|
||||
segments-to-display (concat other-shapes-segments selection-segments)]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(let [sub (->> subject
|
||||
(rx/switch-map query-worker)
|
||||
(rx/subs #(reset! to-measure %)))]
|
||||
;; On unmount dispose
|
||||
#(rx/dispose! sub))))
|
||||
|
||||
(mf/use-effect (mf/deps selrect selected) #(rx/push! subject [selrect selected frame]))
|
||||
|
||||
(for [[sr1 sr2] segments-to-display]
|
||||
[:& shape-distance-segment {:key (str/join "-" [(:x sr1) (:y sr1) (:x sr2) (:y sr2)])
|
||||
:sr1 sr1
|
||||
:sr2 sr2
|
||||
:coord coord
|
||||
:zoom zoom}])))
|
||||
|
||||
(mf/defc snap-distances [{:keys [layout]}]
|
||||
(let [page-id (mf/deref refs/workspace-page-id)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
shapes (->> (refs/objects-by-id selected)
|
||||
(mf/deref)
|
||||
(map gsh/transform-shape))
|
||||
selrect (gsh/selection-rect shapes)
|
||||
frame-id (-> shapes first :frame-id)
|
||||
frame (mf/deref (refs/object-by-id frame-id))
|
||||
zoom (mf/deref refs/selected-zoom)
|
||||
current-transform (mf/deref refs/current-transform)
|
||||
key (->> selected (map str) (str/join "-"))]
|
||||
|
||||
(when (and (contains? layout :dynamic-alignment)
|
||||
(= current-transform :move)
|
||||
(not (empty? selected)))
|
||||
[:g.distance
|
||||
(for [coord [:x :y]]
|
||||
[:& shape-distance
|
||||
{:key (str key (name coord))
|
||||
:selrect selrect
|
||||
:page-id page-id
|
||||
:frame frame
|
||||
:zoom zoom
|
||||
:coord coord
|
||||
:selected selected}])])))
|
|
@ -1,4 +1,4 @@
|
|||
(ns uxbox.main.ui.workspace.snap-feedback
|
||||
(ns uxbox.main.ui.workspace.snap-points
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
|
@ -42,7 +42,7 @@
|
|||
(rx/map #(vector point % coord)))))
|
||||
(rx/reduce conj [])))
|
||||
|
||||
(mf/defc snap-feedback-points
|
||||
(mf/defc snap-feedback
|
||||
[{:keys [shapes page-id filter-shapes zoom] :as props}]
|
||||
(let [state (mf/use-state [])
|
||||
subject (mf/use-memo #(rx/subject))
|
||||
|
@ -84,7 +84,7 @@
|
|||
:point point
|
||||
:zoom zoom}])]))
|
||||
|
||||
(mf/defc snap-feedback [{:keys [layout]}]
|
||||
(mf/defc snap-points [{:keys [layout]}]
|
||||
(let [page-id (mf/deref refs/workspace-page-id)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected-shapes (mf/deref (refs/objects-by-id selected))
|
||||
|
@ -99,9 +99,10 @@
|
|||
snap-data (mf/deref refs/workspace-snap-data)
|
||||
shapes (if drawing [drawing] selected-shapes)
|
||||
zoom (mf/deref refs/selected-zoom)]
|
||||
(when (or drawing current-transform)
|
||||
[:& snap-feedback-points {:shapes shapes
|
||||
:page-id page-id
|
||||
:filter-shapes filter-shapes
|
||||
:zoom zoom}])))
|
||||
|
||||
(when (or drawing current-transform)
|
||||
[:& snap-feedback {:shapes shapes
|
||||
:page-id page-id
|
||||
:filter-shapes filter-shapes
|
||||
:zoom zoom}])))
|
||||
|
|
@ -28,7 +28,8 @@
|
|||
[uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]]
|
||||
[uxbox.main.ui.workspace.selection :refer [selection-handlers]]
|
||||
[uxbox.main.ui.workspace.presence :as presence]
|
||||
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
|
||||
[uxbox.main.ui.workspace.snap-points :refer [snap-points]]
|
||||
[uxbox.main.ui.workspace.snap-distances :refer [snap-distances]]
|
||||
[uxbox.main.ui.workspace.frame-grid :refer [frame-grid]]
|
||||
[uxbox.common.math :as mth]
|
||||
[uxbox.util.dom :as dom]
|
||||
|
@ -389,7 +390,8 @@
|
|||
(when (contains? layout :display-grid)
|
||||
[:& frame-grid {:zoom zoom}])
|
||||
|
||||
[:& snap-feedback {:layout layout}]
|
||||
[:& snap-points {:layout layout}]
|
||||
[:& snap-distances {:layout layout}]
|
||||
|
||||
(when tooltip
|
||||
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])]
|
||||
|
|
Loading…
Add table
Reference in a new issue