🎉 Events enhancements (#9310)

* 🎉 Coalesce viewport pointermove into one PointerEvent

* 🎉 Skip worker hover selection query during transform

* 🎉 Coalesce snap X/Y into one worker query-snap-xy
This commit is contained in:
Alejandro Alonso 2026-05-04 17:57:00 +02:00 committed by GitHub
parent e948020886
commit c794e0ed73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 66 additions and 53 deletions

View File

@ -177,12 +177,12 @@
(rx/of #(-> % (assoc-in [:workspace-local :panning] true))) (rx/of #(-> % (assoc-in [:workspace-local :panning] true)))
(->> stream (->> stream
(rx/filter mse/pointer-event?) (rx/filter mse/pointer-event?)
(rx/filter #(= :delta (:source %))) (rx/filter #(some? (mse/get-pointer-movement %)))
(rx/take-until stopper) (rx/take-until stopper)
;; Some events are executed in synchronous way like panning with backspace pressed ;; Some events are executed in synchronous way like panning with backspace pressed
(rx/observe-on :af) (rx/observe-on :af)
(rx/map (fn [event] (rx/map (fn [event]
(let [delta (dm/get-prop event :pt)] (let [delta (mse/get-pointer-movement event)]
(update-viewport-position {:x #(- % (/ (:x delta) zoom)) (update-viewport-position {:x #(- % (/ (:x delta) zoom))
:y #(- % (/ (:y delta) zoom))}))))))))))) :y #(- % (/ (:y delta) zoom))})))))))))))

View File

@ -160,8 +160,8 @@
(rx/of #(-> % (assoc-in [:workspace-local :zooming] true))) (rx/of #(-> % (assoc-in [:workspace-local :zooming] true)))
(->> stream (->> stream
(rx/filter mse/pointer-event?) (rx/filter mse/pointer-event?)
(rx/filter #(= :delta (:source %))) (rx/filter #(some? (mse/get-pointer-movement %)))
(rx/map :pt) (rx/map mse/get-pointer-movement)
(rx/take-until stopper) (rx/take-until stopper)
(rx/map (fn [delta] (rx/map (fn [delta]
(let [scale (+ 1 (/ (:y delta) 100))] ;; this number may be adjusted after user testing (let [scale (+ 1 (/ (:y delta) 100))] ;; this number may be adjusted after user testing

View File

@ -93,23 +93,12 @@
(rx/take 1) (rx/take 1)
(rx/map (remove-from-snap-points remove-snap?))))) (rx/map (remove-from-snap-points remove-snap?)))))
(defn- search-snap (defn- snap-accuracy-ranges
[page-id frame-id points coord remove-snap? zoom] [points coord zoom]
(let [snap-accuracy (/ snap-accuracy zoom) (let [acc (/ snap-accuracy zoom)]
ranges (->> points (->> points
(map coord) (map coord)
(mapv #(vector (- % snap-accuracy) (mapv #(vector (- % acc) (+ % acc))))))
(+ % snap-accuracy))))
vbox @refs/vbox]
(->> (mw/ask! {:cmd :index/query-snap
:page-id page-id
:frame-id frame-id
:axis coord
:bounds vbox
:ranges ranges})
(rx/take 1)
(rx/map (remove-from-snap-points remove-snap?))
(rx/map (get-min-distance-snap points coord)))))
(defn snap->vector [[[from-x to-x] [from-y to-y]]] (defn snap->vector [[[from-x to-x] [from-y to-y]]]
(when (or from-x to-x from-y to-y) (when (or from-x to-x from-y to-y)
@ -119,10 +108,21 @@
(defn- closest-snap (defn- closest-snap
[page-id frame-id points remove-snap? zoom] [page-id frame-id points remove-snap? zoom]
(let [snap-x (search-snap page-id frame-id points :x remove-snap? zoom) (let [vbox @refs/vbox
snap-y (search-snap page-id frame-id points :y remove-snap? zoom)] x-ranges (snap-accuracy-ranges points :x zoom)
(->> (rx/combine-latest snap-x snap-y) y-ranges (snap-accuracy-ranges points :y zoom)
(rx/map snap->vector)))) rm (remove-from-snap-points remove-snap?)
gmx (get-min-distance-snap points :x)
gmy (get-min-distance-snap points :y)]
(->> (mw/ask! {:cmd :index/query-snap-xy
:page-id page-id
:frame-id frame-id
:bounds vbox
:x-ranges x-ranges
:y-ranges y-ranges})
(rx/take 1)
(rx/map (fn [{:keys [x y]}]
(snap->vector [(gmx (rm x)) (gmy (rm y))]))))))
(defn sr-distance [coord sr1 sr2] (defn sr-distance [coord sr1 sr2]
(let [c1 (if (= coord :x) :x1 :y1) (let [c1 (if (= coord :x) :x1 :y1)

View File

@ -360,16 +360,14 @@
(rx/push! move-stream pt) (rx/push! move-stream pt)
(reset! last-position raw-pt) (reset! last-position raw-pt)
(st/emit! (mse/->PointerEvent :delta delta ;; Single store emit per move: viewport `pt` + `movement` (old :delta `pt`) avoids
(kbd/ctrl? event) ;; doubling Potok + `st/stream` work on every pointermove.
(kbd/shift? event)
(kbd/alt? event)
(kbd/meta? event)))
(st/emit! (mse/->PointerEvent :viewport pt (st/emit! (mse/->PointerEvent :viewport pt
(kbd/ctrl? event) (kbd/ctrl? event)
(kbd/shift? event) (kbd/shift? event)
(kbd/alt? event) (kbd/alt? event)
(kbd/meta? event)))))))) (kbd/meta? event)
delta)))))))
(defn- schedule-zoom! (defn- schedule-zoom!
"Accumulate a compound zoom scale and a cursor point into `state`, scheduling "Accumulate a compound zoom scale and a cursor point into `state`, scheduling

View File

@ -181,7 +181,6 @@
(let [;; We use ref so we don't recreate the stream on a change (let [;; We use ref so we don't recreate the stream on a change
zoom-ref (mf/use-ref zoom) zoom-ref (mf/use-ref zoom)
mod-ref (mf/use-ref @mod?) mod-ref (mf/use-ref @mod?)
transform-ref (mf/use-ref nil)
selected-ref (mf/use-ref selected) selected-ref (mf/use-ref selected)
hover-disabled-ref (mf/use-ref hover-disabled?) hover-disabled-ref (mf/use-ref hover-disabled?)
focus-ref (mf/use-ref focus) focus-ref (mf/use-ref focus)
@ -191,13 +190,15 @@
query-point query-point
(mf/use-callback (mf/use-callback
(mf/deps page-id) (mf/deps page-id transform)
(fn [point] (fn [point]
(let [zoom (mf/ref-val zoom-ref) (let [zoom (mf/ref-val zoom-ref)
rect (grc/center->rect point (/ 5 zoom))] rect (grc/center->rect point (/ 5 zoom))]
(if (mf/ref-val hover-disabled-ref) (if (or (mf/ref-val hover-disabled-ref)
(rx/of nil) (some? transform))
;; No index query while dragging/transforming: snap already hits the worker.
(rx/of [])
(->> (mw/ask-buffered! (->> (mw/ask-buffered!
{:cmd :index/query-selection {:cmd :index/query-selection
:page-id page-id :page-id page-id
@ -222,7 +223,6 @@
(->> move-stream (->> move-stream
(rx/tap #(reset! last-point-ref %)) (rx/tap #(reset! last-point-ref %))
;; When transforming shapes we stop querying the worker
(rx/merge-map query-point))) (rx/merge-map query-point)))
(rx/share))) (rx/share)))
@ -231,10 +231,6 @@
(->> over-shapes-stream (rx/debounce 50))] (->> over-shapes-stream (rx/debounce 50))]
;; Refresh the refs on a value change ;; Refresh the refs on a value change
(mf/use-effect
(mf/deps transform)
#(mf/set-ref-val! transform-ref transform))
(mf/use-effect (mf/use-effect
(mf/deps zoom) (mf/deps zoom)
#(mf/set-ref-val! zoom-ref zoom)) #(mf/set-ref-val! zoom-ref zoom))

View File

@ -9,7 +9,10 @@
[beicon.v2.core :as rx])) [beicon.v2.core :as rx]))
(defrecord MouseEvent [type ctrl shift alt meta]) (defrecord MouseEvent [type ctrl shift alt meta])
(defrecord PointerEvent [source pt ctrl shift alt meta]) ;; `movement` — optional delta (client/window space) for the same tick as `pt`.
;; Used so viewport `pointermove` can emit once (`source` :viewport) while pan/zoom
;; still observe displacement without a second Potok emit.
(defrecord PointerEvent [source pt ctrl shift alt meta movement])
(defrecord ScrollEvent [point]) (defrecord ScrollEvent [point])
(defrecord BlurEvent []) (defrecord BlurEvent [])
@ -53,6 +56,10 @@
[^PointerEvent ev] [^PointerEvent ev]
(.-pt ev)) (.-pt ev))
(defn get-pointer-movement
[^PointerEvent ev]
(.-movement ev))
(defn get-pointer-ctrl-mod (defn get-pointer-ctrl-mod
[^PointerEvent ev] [^PointerEvent ev]
(.-ctrl ev)) (.-ctrl ev))

View File

@ -63,23 +63,35 @@
(log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true)))) (log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true))))
nil)) nil))
(defn- run-query-snap
[index page-id frame-id axis ranges bounds]
(let [match-bounds?
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))
xform
(comp (mapcat #(snap/query index page-id frame-id axis %))
(distinct)
(filter match-bounds?))]
(into [] xform ranges)))
;; FIXME: schema ;; FIXME: schema
(defmethod impl/handler :index/query-snap (defmethod impl/handler :index/query-snap
[{:keys [page-id frame-id axis ranges bounds] :as message}] [{:keys [page-id frame-id axis ranges bounds]}]
(if-let [index (get @state ::snap)] (if-let [index (get @state ::snap)]
(let [match-bounds? (run-query-snap index page-id frame-id axis ranges bounds)
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))
xform
(comp (mapcat #(snap/query index page-id frame-id axis %))
(distinct)
(filter match-bounds?))]
(into [] xform ranges))
[])) []))
;; Single round-trip for X+Y snap used by `app.main.snap/closest-snap` (e.g. shape drag).
(defmethod impl/handler :index/query-snap-xy
[{:keys [page-id frame-id bounds x-ranges y-ranges]}]
(if-let [index (get @state ::snap)]
{:x (run-query-snap index page-id frame-id :x x-ranges bounds)
:y (run-query-snap index page-id frame-id :y y-ranges bounds)}
{:x [] :y []}))
;; FIXME: schema ;; FIXME: schema
(defmethod impl/handler :index/query-selection (defmethod impl/handler :index/query-selection