Skip to content

Commit 58d5e32

Browse files
committed
enhance snap
- remove subscription from event handler - introduce snapping-points multimethod for tools - explicitly update kd-tree - fix snap on translate with locked direction
1 parent 2d1b810 commit 58d5e32

File tree

21 files changed

+644
-599
lines changed

21 files changed

+644
-599
lines changed

.clj-kondo/metosin/malli-types-cljs/config.edn

+430-384
Large diffs are not rendered by default.

src/renderer/app/db.cljs

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
[:clicked-element {:optional true} [:or Element Handle]]
7878
[:copied-bounds {:optional true} Bounds]
7979
[:copied-elements {:optional true} [:* Element]]
80+
[:kd-tree {:optional true} any?]
8081
[:re-pressed.core/keydown {:optional true} map?]])
8182

8283
(def valid? (m/validator App))

src/renderer/element/events.cljs

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
[renderer.element.handlers :as h]
99
[renderer.history.handlers :refer [finalize]]
1010
[renderer.notification.events :as-alias notification.e]
11+
[renderer.snap.handlers :as snap.h]
1112
[renderer.utils.bounds :as bounds]
1213
[renderer.utils.element :as element]
1314
[renderer.utils.extra :refer [partial-right]]
@@ -19,13 +20,15 @@
1920
::select
2021
[(finalize "Select element")]
2122
(fn [db [_ id multiple]]
22-
(h/select db id multiple)))
23+
(-> (h/select db id multiple)
24+
(snap.h/update-tree))))
2325

2426
(rf/reg-event-db
2527
::select-ids
2628
[(finalize "Select elements")]
2729
(fn [db [_ ids]]
28-
(reduce (partial-right h/assoc-prop :selected true) (h/deselect db) ids)))
30+
(-> (reduce (partial-right h/assoc-prop :selected true) (h/deselect db) ids)
31+
(snap.h/update-tree))))
2932

3033
(rf/reg-event-db
3134
::toggle-prop

src/renderer/element/handlers.cljs

+5
Original file line numberDiff line numberDiff line change
@@ -775,3 +775,8 @@
775775
(-> (add db svg)
776776
(collapse))))
777777

778+
(defn snapping-points
779+
[db els]
780+
(let [options (-> db :snap :options)]
781+
(reduce (fn [points el]
782+
(into points (element/snapping-points el options))) [] els)))

src/renderer/element/impl/box.cljs

+9
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,12 @@
6969
[el]
7070
(let [{{:keys [width height]} :attrs} el]
7171
(apply * (map units/unit->px [width height]))))
72+
73+
(defmethod hierarchy/snapping-points ::hierarchy/box
74+
[el]
75+
(let [{{:keys [x y width height]} :attrs} el
76+
[x y width height] (mapv units/unit->px [x y width height])]
77+
[[x y]
78+
[(+ x width) y]
79+
[(+ x width) (+ y height)]
80+
[x (+ y height)]]))

src/renderer/element/impl/container/canvas.cljs

+4-11
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727

2828
(defmethod hierarchy/render :canvas
2929
[el]
30-
(let [_ @(rf/subscribe [::snap.s/in-viewport-tree]) ; TODO: Remove this.
31-
child-elements @(rf/subscribe [::element.s/filter-visible (:children el)])
30+
(let [child-elements @(rf/subscribe [::element.s/filter-visible (:children el)])
3231
viewbox-attr @(rf/subscribe [::frame.s/viewbox-attr])
3332
{:keys [width height]} @(rf/subscribe [::app.s/dom-rect])
3433
hovered-ids @(rf/subscribe [::element.s/hovered])
@@ -45,10 +44,8 @@
4544
state @(rf/subscribe [::tool.s/state])
4645
pointer-handler #(pointer/event-handler! % el)
4746
pivot-point @(rf/subscribe [::tool.s/pivot-point])
48-
snapping-points @(rf/subscribe [::snap.s/points])
4947
snap-active @(rf/subscribe [::snap.s/active])
5048
nearest-neighbor @(rf/subscribe [::snap.s/nearest-neighbor])
51-
debug @(rf/subscribe [::app.s/debug-info])
5249
transform-active (or (= tool :transform) (= primary-tool :transform))]
5350
[:svg#canvas {:on-pointer-up pointer-handler
5451
:on-pointer-down pointer-handler
@@ -101,8 +98,9 @@
10198
(when (and transform-active pivot-point)
10299
[overlay/times pivot-point])])
103100

104-
(when (or (= tool :edit)
105-
(= primary-tool :edit))
101+
(when (and (= state :idle)
102+
(or (= tool :edit)
103+
(= primary-tool :edit)))
106104
(for [el selected-elements]
107105
^{:key (str (:id el) "-edit-points")}
108106
[:g
@@ -112,11 +110,6 @@
112110

113111
[hierarchy/render temp-element]])
114112

115-
(when debug
116-
[into [:g]
117-
(for [snapping-point snapping-points]
118-
[overlay/point-of-interest snapping-point])])
119-
120113
(when (and snap-active nearest-neighbor)
121114
[overlay/times (:point nearest-neighbor)])
122115

src/renderer/element/impl/shape/line.cljs

+7
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,10 @@
9696
(let [{{:keys [x1 y1 x2 y2]} :attrs} el
9797
[x1 y1 x2 y2] (mapv units/unit->px [x1 y1 x2 y2])]
9898
[(min x1 x2) (min y1 y2) (max x1 x2) (max y1 y2)]))
99+
100+
(defmethod hierarchy/snapping-points :line
101+
[el]
102+
(let [{{:keys [x1 y1 x2 y2]} :attrs} el
103+
[x1 y1 x2 y2] (mapv units/unit->px [x1 y1 x2 y2])]
104+
[[x1 y1]
105+
[x2 y2]]))

src/renderer/element/subs.cljs

-14
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,6 @@
4040
(fn [elements _]
4141
(filter :selected (vals elements))))
4242

43-
(rf/reg-sub
44-
::selected-descendant-ids
45-
(fn [db _]
46-
(h/descendant-ids db)))
47-
48-
(rf/reg-sub
49-
::non-selected-visible
50-
:<- [::document.s/elements]
51-
:<- [::selected-descendant-ids]
52-
(fn [[elements selected-descendant-ids] _]
53-
(filter #(and (not (:selected %))
54-
(not (contains? selected-descendant-ids (:id %)))
55-
(:visible %)) (vals elements))))
56-
5743
(rf/reg-sub
5844
::hovered
5945
:<- [::document.s/elements]

src/renderer/snap/handlers.cljs

+43-32
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
[clojure.core.matrix :as mat]
44
[kdtree :as kdtree]
55
[malli.core :as m]
6-
[re-frame.core :as rf]
76
[renderer.app.db :refer [App]]
7+
[renderer.frame.db :refer [Viewbox]]
8+
[renderer.frame.handlers :as frame.h]
89
[renderer.snap.db :refer [SnapOption NearestNeighbor]]
910
[renderer.snap.subs :as-alias snap.s]
1011
[renderer.tool.hierarchy :as tool.hierarchy]
@@ -17,52 +18,62 @@
1718
(update-in db [:snap :options] disj option)
1819
(update-in db [:snap :options] conj option)))
1920

20-
(m/=> find-nearest-neighbors [:-> App [:sequential NearestNeighbor]])
21-
(defn find-nearest-neighbors
21+
(m/=> in-viewport-tree [:-> any? Viewbox any?])
22+
(defn in-viewport-tree
23+
[tree [x y width height]]
24+
(->> [[x (+ x width)] [y (+ y height)]]
25+
(kdtree/interval-search tree)
26+
(kdtree/build-tree)))
27+
28+
(defn create-tree
29+
[db]
30+
(let [zoom (get-in db [:documents (:active-document db) :zoom])
31+
pan (get-in db [:documents (:active-document db) :pan])
32+
viewbox (frame.h/viewbox zoom pan (:dom-rect db))]
33+
(-> (tool.hierarchy/snapping-points db)
34+
(kdtree/build-tree)
35+
(in-viewport-tree viewbox))))
36+
37+
(m/=> nearest-neighbors [:-> App [:sequential NearestNeighbor]])
38+
(defn nearest-neighbors
2239
[db]
23-
(let [tree @(rf/subscribe [::snap.s/in-viewport-tree])] ; FIXME: Subscription in event.
24-
(map #(let [nearest-neighbor (kdtree/nearest-neighbor tree %)]
25-
(when nearest-neighbor
26-
(assoc nearest-neighbor :base-point %)))
27-
(tool.hierarchy/snapping-bases db))))
40+
(map #(when-let [nneighbor (kdtree/nearest-neighbor (:kd-tree db) %)]
41+
(assoc nneighbor :base-point %))
42+
(tool.hierarchy/snapping-bases db)))
2843

29-
(m/=> find-nearest-neighbor [:-> App [:maybe NearestNeighbor]])
30-
(defn find-nearest-neighbor
44+
(m/=> nearest-neighbor [:-> App [:maybe NearestNeighbor]])
45+
(defn nearest-neighbor
3146
[db]
32-
(let [threshold (-> db :snap :threshold)
33-
nearest-neighbors (find-nearest-neighbors db)
34-
threshold (/ threshold (get-in db [:documents (:active-document db) :zoom]))
35-
nearest-neighbor (reduce
36-
(fn [nearest-neighbor neighbor]
37-
(if (< (:dist-squared neighbor)
38-
(:dist-squared nearest-neighbor))
39-
neighbor
40-
nearest-neighbor))
41-
(first nearest-neighbors)
42-
(rest nearest-neighbors))]
43-
(when (< (:dist-squared nearest-neighbor) (Math/pow threshold 2))
44-
nearest-neighbor)))
47+
(when (-> db :snap :active)
48+
(let [threshold (-> db :snap :threshold)
49+
nneighbors (nearest-neighbors db)
50+
threshold (/ threshold (get-in db [:documents (:active-document db) :zoom]))
51+
nneighbor (apply min-key :dist-squared nneighbors)]
52+
(when (< (:dist-squared nneighbor) (Math/pow threshold 2))
53+
nneighbor))))
4554

46-
(m/=> update-nearest-neighbor [:-> App App])
4755
(defn update-nearest-neighbor
4856
[db]
49-
(let [nearest-neighbor (find-nearest-neighbor db)]
50-
(cond-> db
51-
:always
52-
(dissoc :nearest-neighbor)
57+
(assoc db :nearest-neighbor (nearest-neighbor db)))
5358

54-
(and (-> db :snap :active) nearest-neighbor)
55-
(assoc :nearest-neighbor nearest-neighbor))))
59+
(defn update-tree
60+
[db]
61+
(cond-> db
62+
(-> db :snap :active)
63+
(assoc :kd-tree (create-tree db))))
5664

5765
(m/=> nearest-delta [:-> App Vec2D])
5866
(defn nearest-delta
5967
[db]
60-
(let [{:keys [point base-point]} (:nearest-neighbor db)]
61-
(mat/sub point base-point)))
68+
(if (:nearest-neighbor db)
69+
(let [{:keys [point base-point]} (:nearest-neighbor db)]
70+
(mat/sub point base-point))
71+
[0 0]))
6272

6373
(defn snap-with
6474
[db f & more]
6575
(let [db (update-nearest-neighbor db)]
6676
(if (:nearest-neighbor db)
6777
(apply f db (nearest-delta db) more)
6878
db)))
79+

src/renderer/snap/subs.cljs

+1-26
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
(ns renderer.snap.subs
22
(:require
3-
[kdtree :as kdtree]
43
[re-frame.core :as rf]
54
[renderer.element.subs :as-alias element.s]
6-
[renderer.frame.subs :as-alias frame.s]
7-
[renderer.utils.element :as utils.el]))
5+
[renderer.frame.subs :as-alias frame.s]))
86

97
(rf/reg-sub
108
::snap
@@ -23,26 +21,3 @@
2321
(rf/reg-sub
2422
::nearest-neighbor
2523
:-> :nearest-neighbor)
26-
27-
(rf/reg-sub
28-
::points
29-
:<- [::element.s/non-selected-visible]
30-
:<- [::active]
31-
:<- [::options]
32-
(fn [[non-selected-visible-elements active options] _]
33-
(when active
34-
(reduce (fn [points el] (into points (utils.el/snapping-points el options)))
35-
[] non-selected-visible-elements))))
36-
37-
(rf/reg-sub
38-
::tree
39-
:<- [::points]
40-
kdtree/build-tree)
41-
42-
(rf/reg-sub
43-
::in-viewport-tree
44-
:<- [::frame.s/viewbox]
45-
:<- [::tree]
46-
(fn [[[x y width height] tree] _]
47-
(kdtree/build-tree (kdtree/interval-search tree [[x (+ x width)]
48-
[y (+ y height)]]))))

src/renderer/tool/handlers.cljs

+15-14
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,6 @@
2828
[db explanation]
2929
(assoc db :explanation explanation))
3030

31-
(m/=> activate [:-> App Tool App])
32-
(defn activate
33-
[db tool]
34-
(-> db
35-
(hierarchy/deactivate)
36-
(assoc :tool tool)
37-
(hierarchy/activate)))
38-
3931
(m/=> set-state [:-> App State App])
4032
(defn set-state
4133
[db state]
@@ -46,6 +38,17 @@
4638
[db cursor]
4739
(assoc db :cursor cursor))
4840

41+
(m/=> activate [:-> App Tool App])
42+
(defn activate
43+
[db tool]
44+
(-> db
45+
(hierarchy/deactivate)
46+
(assoc :tool tool)
47+
(set-state :idle)
48+
(set-cursor "default")
49+
(dissoc :drag :pointer-offset :clicked-element)
50+
(hierarchy/activate)))
51+
4952
(m/=> pointer-delta [:-> App Vec2D])
5053
(defn pointer-delta
5154
[db]
@@ -86,7 +89,8 @@
8689
[db e now]
8790
(let [{:keys [pointer-offset tool dom-rect drag primary-tool drag-threshold nearest-neighbor]} db
8891
{:keys [button buttons pointer-pos]} e
89-
adjusted-pointer-pos (frame.h/adjust-pointer-pos db pointer-pos)]
92+
adjusted-pointer-pos (frame.h/adjust-pointer-pos db pointer-pos)
93+
db (snap.h/update-nearest-neighbor db)]
9094
(case (:type e)
9195
"pointermove"
9296
(-> (if pointer-offset
@@ -104,7 +108,6 @@
104108
(hierarchy/drag e))
105109
db)
106110
(hierarchy/pointer-move db e))
107-
(snap.h/update-nearest-neighbor)
108111
(assoc :pointer-pos pointer-pos
109112
:adjusted-pointer-pos adjusted-pointer-pos))
110113

@@ -138,8 +141,7 @@
138141
(dissoc :primary-tool))
139142

140143
:always
141-
(-> (dissoc :pointer-offset :drag)
142-
(update :snap dissoc :nearest-neighbor)))
144+
(dissoc :pointer-offset :drag :nearest-neighbor))
143145

144146
db)))
145147

@@ -185,8 +187,7 @@
185187
[db]
186188
(cond-> db
187189
:always
188-
(-> (dissoc :drag :pointer-offset :clicked-element)
189-
(hierarchy/activate (:tool db))
190+
(-> (activate (:tool db))
190191
(dissoc-temp)
191192
(history.h/swap))
192193

src/renderer/tool/hierarchy.cljs

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
(defmulti key-up (fn [db _e] (:tool db)))
1111
(defmulti key-down (fn [db _e] (:tool db)))
1212
(defmulti snapping-bases (fn [db _e] (:tool db)))
13+
(defmulti snapping-points (fn [db _e] (:tool db)))
1314
(defmulti activate :tool)
1415
(defmulti deactivate :tool)
15-
(defmulti properties "Returns the properties of the tool." keyword)
16+
(defmulti properties "Returns the properties of the tool." identity)
1617
(defmulti help "Returns the status bar help text." (fn [tool state] [tool state]))
1718

18-
(defmulti right-panel keyword)
19+
(defmulti right-panel identity)
1920

2021
(defmethod pointer-down :default [db _e] db)
2122
(defmethod pointer-up :default [db _e] db)
@@ -26,10 +27,11 @@
2627
(defmethod key-down :default [db _e] db)
2728
(defmethod drag :default [db e] (pointer-move db e))
2829
(defmethod drag-end :default [db e] (pointer-up db e))
29-
(defmethod activate :default [db] (assoc db :cursor "default"))
30-
(defmethod deactivate :default [db] (assoc db :cursor "default"))
30+
(defmethod activate :default [db] db)
31+
(defmethod deactivate :default [db] db)
3132
(defmethod properties :default [])
3233
(defmethod snapping-bases :default [])
34+
(defmethod snapping-points :default [])
3335
(defmethod help :default [_tool _state] "")
3436

3537

0 commit comments

Comments
 (0)