diff --git a/frontend/src/app/main/data/workspace/viewport.cljs b/frontend/src/app/main/data/workspace/viewport.cljs index 2687bb9113..4383807a32 100644 --- a/frontend/src/app/main/data/workspace/viewport.cljs +++ b/frontend/src/app/main/data/workspace/viewport.cljs @@ -177,12 +177,12 @@ (rx/of #(-> % (assoc-in [:workspace-local :panning] true))) (->> stream (rx/filter mse/pointer-event?) - (rx/filter #(= :delta (:source %))) + (rx/filter #(some? (mse/get-pointer-movement %))) (rx/take-until stopper) ;; Some events are executed in synchronous way like panning with backspace pressed (rx/observe-on :af) (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)) :y #(- % (/ (:y delta) zoom))}))))))))))) diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 33f1846407..e1cb6ec716 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -160,8 +160,8 @@ (rx/of #(-> % (assoc-in [:workspace-local :zooming] true))) (->> stream (rx/filter mse/pointer-event?) - (rx/filter #(= :delta (:source %))) - (rx/map :pt) + (rx/filter #(some? (mse/get-pointer-movement %))) + (rx/map mse/get-pointer-movement) (rx/take-until stopper) (rx/map (fn [delta] (let [scale (+ 1 (/ (:y delta) 100))] ;; this number may be adjusted after user testing diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index f485880536..5831a1fa1b 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -93,23 +93,12 @@ (rx/take 1) (rx/map (remove-from-snap-points remove-snap?))))) -(defn- search-snap - [page-id frame-id points coord remove-snap? zoom] - (let [snap-accuracy (/ snap-accuracy zoom) - ranges (->> points - (map coord) - (mapv #(vector (- % snap-accuracy) - (+ % 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-accuracy-ranges + [points coord zoom] + (let [acc (/ snap-accuracy zoom)] + (->> points + (map coord) + (mapv #(vector (- % acc) (+ % acc)))))) (defn snap->vector [[[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 [page-id frame-id points remove-snap? zoom] - (let [snap-x (search-snap page-id frame-id points :x remove-snap? zoom) - snap-y (search-snap page-id frame-id points :y remove-snap? zoom)] - (->> (rx/combine-latest snap-x snap-y) - (rx/map snap->vector)))) + (let [vbox @refs/vbox + x-ranges (snap-accuracy-ranges points :x zoom) + y-ranges (snap-accuracy-ranges points :y zoom) + 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] (let [c1 (if (= coord :x) :x1 :y1) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index c3c26ab6a5..0eac02f9a3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -360,16 +360,14 @@ (rx/push! move-stream pt) (reset! last-position raw-pt) - (st/emit! (mse/->PointerEvent :delta delta - (kbd/ctrl? event) - (kbd/shift? event) - (kbd/alt? event) - (kbd/meta? event))) + ;; Single store emit per move: viewport `pt` + `movement` (old :delta `pt`) avoids + ;; doubling Potok + `st/stream` work on every pointermove. (st/emit! (mse/->PointerEvent :viewport pt (kbd/ctrl? event) (kbd/shift? event) (kbd/alt? event) - (kbd/meta? event)))))))) + (kbd/meta? event) + delta))))))) (defn- schedule-zoom! "Accumulate a compound zoom scale and a cursor point into `state`, scheduling diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 225dde4fd9..8774234543 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -181,7 +181,6 @@ (let [;; We use ref so we don't recreate the stream on a change zoom-ref (mf/use-ref zoom) mod-ref (mf/use-ref @mod?) - transform-ref (mf/use-ref nil) selected-ref (mf/use-ref selected) hover-disabled-ref (mf/use-ref hover-disabled?) focus-ref (mf/use-ref focus) @@ -191,13 +190,15 @@ query-point (mf/use-callback - (mf/deps page-id) + (mf/deps page-id transform) (fn [point] (let [zoom (mf/ref-val zoom-ref) rect (grc/center->rect point (/ 5 zoom))] - (if (mf/ref-val hover-disabled-ref) - (rx/of nil) + (if (or (mf/ref-val hover-disabled-ref) + (some? transform)) + ;; No index query while dragging/transforming: snap already hits the worker. + (rx/of []) (->> (mw/ask-buffered! {:cmd :index/query-selection :page-id page-id @@ -222,7 +223,6 @@ (->> move-stream (rx/tap #(reset! last-point-ref %)) - ;; When transforming shapes we stop querying the worker (rx/merge-map query-point))) (rx/share))) @@ -231,10 +231,6 @@ (->> over-shapes-stream (rx/debounce 50))] ;; Refresh the refs on a value change - (mf/use-effect - (mf/deps transform) - #(mf/set-ref-val! transform-ref transform)) - (mf/use-effect (mf/deps zoom) #(mf/set-ref-val! zoom-ref zoom)) diff --git a/frontend/src/app/util/mouse.cljs b/frontend/src/app/util/mouse.cljs index 8f3e7652a0..01065e98c5 100644 --- a/frontend/src/app/util/mouse.cljs +++ b/frontend/src/app/util/mouse.cljs @@ -9,7 +9,10 @@ [beicon.v2.core :as rx])) (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 BlurEvent []) @@ -53,6 +56,10 @@ [^PointerEvent ev] (.-pt ev)) +(defn get-pointer-movement + [^PointerEvent ev] + (.-movement ev)) + (defn get-pointer-ctrl-mod [^PointerEvent ev] (.-ctrl ev)) diff --git a/frontend/src/app/worker/index.cljs b/frontend/src/app/worker/index.cljs index 3ff1f37d19..20abaf00e7 100644 --- a/frontend/src/app/worker/index.cljs +++ b/frontend/src/app/worker/index.cljs @@ -63,23 +63,35 @@ (log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true)))) 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 (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)] - (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)) + (run-query-snap index page-id frame-id axis ranges bounds) [])) +;; 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 (defmethod impl/handler :index/query-selection