mirror of
https://github.com/penpot/penpot.git
synced 2026-05-14 20:43:55 +00:00
Merge pull request #9523 from penpot/superalex-test-firefox
🐛 Fix WASM viewport interaction lifecycle for pan/zoom
This commit is contained in:
commit
63ff5c87c2
@ -1578,6 +1578,7 @@
|
||||
(dm/export dwv/initialize-viewport)
|
||||
(dm/export dwv/update-viewport-position)
|
||||
(dm/export dwv/update-viewport-size)
|
||||
(dm/export dwv/sync-wasm-workspace-viewport)
|
||||
(dm/export dwv/start-panning)
|
||||
(dm/export dwv/finish-panning)
|
||||
|
||||
|
||||
@ -16,13 +16,19 @@
|
||||
[app.common.math :as mth]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.viewport-wasm :as dwvw]
|
||||
[app.util.mouse :as mse]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn- render-context-lost?
|
||||
[state]
|
||||
(true? (get-in state [:render-state :lost])))
|
||||
(defn sync-wasm-workspace-viewport
|
||||
"Effect-only: pushes the current workspace zoom/view box to WASM after other
|
||||
events (e.g. `update-viewport-size`) have updated the store."
|
||||
[]
|
||||
(ptk/reify ::sync-wasm-workspace-viewport
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(defn initialize-viewport
|
||||
[{:keys [width height] :as size}]
|
||||
@ -86,7 +92,11 @@
|
||||
(update [_ state]
|
||||
(update state :workspace-local
|
||||
(fn [local]
|
||||
(setup state local)))))))
|
||||
(setup state local))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state)))))
|
||||
|
||||
(defn calculate-centered-viewbox
|
||||
"Updates the viewbox coordinates for a given center position"
|
||||
@ -105,9 +115,13 @@
|
||||
(ptk/reify ::update-viewport-position-center
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(update state :workspace-local calculate-centered-viewbox position)))))
|
||||
(update state :workspace-local calculate-centered-viewbox position)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(defn update-viewport-position
|
||||
[{:keys [x y] :or {x identity y identity}}]
|
||||
@ -124,13 +138,17 @@
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(update-in state [:workspace-local :vbox]
|
||||
(fn [vbox]
|
||||
(-> vbox
|
||||
(update :x x)
|
||||
(update :y y))))))))
|
||||
(update :y y))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(defn update-viewport-size
|
||||
[resize-type {:keys [width height] :as size}]
|
||||
@ -174,16 +192,27 @@
|
||||
(assoc-in [:vbox :width] vbox-width')
|
||||
(assoc-in [:vbox :height] vbox-height')))))))))
|
||||
|
||||
(defn- activate-panning []
|
||||
(ptk/reify ::activate-panning
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :panning] true)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-view-interaction-start! state))))
|
||||
|
||||
(defn start-panning []
|
||||
(ptk/reify ::start-panning
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning)))
|
||||
zoom (get-in state [:workspace-local :zoom])]
|
||||
(when (and (not (render-context-lost? state))
|
||||
(when (and (not (dwvw/render-context-lost? state))
|
||||
(not (get-in state [:workspace-local :panning])))
|
||||
(rx/concat
|
||||
(rx/of #(-> % (assoc-in [:workspace-local :panning] true)))
|
||||
(rx/of (activate-panning))
|
||||
(->> stream
|
||||
(rx/filter mse/pointer-event?)
|
||||
(rx/filter #(some? (mse/get-pointer-movement %)))
|
||||
@ -200,4 +229,8 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :panning)))))
|
||||
(update :workspace-local dissoc :panning)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-view-interaction-end! state))))
|
||||
|
||||
30
frontend/src/app/main/data/workspace/viewport_wasm.cljs
Normal file
30
frontend/src/app/main/data/workspace/viewport_wasm.cljs
Normal file
@ -0,0 +1,30 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.workspace.viewport-wasm
|
||||
(:require
|
||||
[app.main.features :as features]
|
||||
[app.render-wasm.api :as wasm.api]))
|
||||
|
||||
(defn render-context-lost?
|
||||
[state]
|
||||
(true? (get-in state [:render-state :lost])))
|
||||
|
||||
(defn maybe-sync-workspace-local-viewport!
|
||||
"When `render-wasm/v1` is active, pushes workspace zoom and vbox into WASM."
|
||||
[state]
|
||||
(when (and (features/active-feature? state "render-wasm/v1") (not (render-context-lost? state)))
|
||||
(wasm.api/sync-workspace-local-viewport! state)))
|
||||
|
||||
(defn maybe-view-interaction-start!
|
||||
[state]
|
||||
(when (and (features/active-feature? state "render-wasm/v1") (not (render-context-lost? state)))
|
||||
(wasm.api/view-interaction-start!)))
|
||||
|
||||
(defn maybe-view-interaction-end!
|
||||
[state]
|
||||
(when (and (features/active-feature? state "render-wasm/v1") (not (render-context-lost? state)))
|
||||
(wasm.api/view-interaction-end!)))
|
||||
@ -16,15 +16,12 @@
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.viewport-wasm :as dwvw]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.mouse :as mse]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn- render-context-lost?
|
||||
[state]
|
||||
(true? (get-in state [:render-state :lost])))
|
||||
|
||||
(defn impl-update-zoom
|
||||
[{:keys [vbox] :as local} center zoom]
|
||||
(let [new-zoom (if (fn? zoom) (zoom (:zoom local)) zoom)
|
||||
@ -47,11 +44,15 @@
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(let [center (if (= center ::auto) @ms/mouse-position center)]
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % center (fn [z] (min (* z 1.3) 200))))))))))
|
||||
#(impl-update-zoom % center (fn [z] (min (* z 1.3) 200)))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state)))))
|
||||
|
||||
(defn decrease-zoom
|
||||
([]
|
||||
@ -62,11 +63,15 @@
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(let [center (if (= center ::auto) @ms/mouse-position center)]
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01))))))))))
|
||||
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state)))))
|
||||
|
||||
(defn set-zoom
|
||||
([scale]
|
||||
@ -77,7 +82,7 @@
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(let [vp (dm/get-in state [:workspace-local :vbox])
|
||||
x (+ (:x vp) (/ (:width vp) 2))
|
||||
@ -86,22 +91,30 @@
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % center (fn [z] (-> (* z scale)
|
||||
(max 0.01)
|
||||
(min 200)))))))))))
|
||||
(min 200))))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state)))))
|
||||
|
||||
(def reset-zoom
|
||||
(ptk/reify ::reset-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % nil 1))))))
|
||||
#(impl-update-zoom % nil 1))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(def zoom-to-fit-all
|
||||
(ptk/reify ::zoom-to-fit-all
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
@ -116,13 +129,17 @@
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
(assoc :zoom-inverse (/ 1 zoom))
|
||||
(update :vbox merge srect)))))))))))
|
||||
(update :vbox merge srect)))))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(def zoom-to-selected-shape
|
||||
(ptk/reify ::zoom-to-selected-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (render-context-lost? state)
|
||||
(if (dwvw/render-context-lost? state)
|
||||
state
|
||||
(let [selected (dsh/lookup-selected state)]
|
||||
(if (empty? selected)
|
||||
@ -139,14 +156,18 @@
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
(assoc :zoom-inverse (/ 1 zoom))
|
||||
(update :vbox merge srect))))))))))))
|
||||
(update :vbox merge srect))))))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(defn fit-to-shapes
|
||||
[ids]
|
||||
(ptk/reify ::fit-to-shapes
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (or (render-context-lost? state) (empty? ids))
|
||||
(if (or (dwvw/render-context-lost? state) (empty? ids))
|
||||
state
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
@ -164,16 +185,21 @@
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
(assoc :zoom-inverse (/ 1 zoom))
|
||||
(update :vbox merge srect))))))))))
|
||||
(update :vbox merge srect))))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-sync-workspace-local-viewport! state))))
|
||||
|
||||
(defn start-zooming [pt]
|
||||
(ptk/reify ::start-zooming
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (->> stream (rx/filter (ptk/type? ::finish-zooming)))]
|
||||
(when (and (not (render-context-lost? state))
|
||||
(when (and (not (dwvw/render-context-lost? state))
|
||||
(not (get-in state [:workspace-local :zooming])))
|
||||
(rx/concat
|
||||
(rx/of (fn [s] (dwvw/maybe-view-interaction-start! s) s))
|
||||
(rx/of #(-> % (assoc-in [:workspace-local :zooming] true)))
|
||||
(->> stream
|
||||
(rx/filter mse/pointer-event?)
|
||||
@ -189,4 +215,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :zooming)))))
|
||||
(update :workspace-local dissoc :zooming)))
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(dwvw/maybe-view-interaction-end! state))))
|
||||
|
||||
@ -77,7 +77,8 @@
|
||||
(mf/deps vport)
|
||||
(fn [resize-type size]
|
||||
(when (and vport (not= size vport))
|
||||
(st/emit! (dw/update-viewport-size resize-type size)))))
|
||||
(st/emit! (dw/update-viewport-size resize-type size)
|
||||
(dw/sync-wasm-workspace-viewport)))))
|
||||
|
||||
on-resize-palette
|
||||
(mf/use-fn
|
||||
|
||||
@ -436,7 +436,8 @@
|
||||
|
||||
(mf/with-effect [vport]
|
||||
(when (and @canvas-init? @initialized?)
|
||||
(wasm.api/resize-viewbox (:width vport) (:height vport))))
|
||||
(wasm.api/resize-viewbox (:width vport) (:height vport))
|
||||
(wasm.api/set-view-box zoom vbox)))
|
||||
|
||||
(mf/with-effect [@canvas-init? preview-blend]
|
||||
(when (and @canvas-init? preview-blend)
|
||||
@ -467,10 +468,6 @@
|
||||
(wasm.api/clear-focus-mode)
|
||||
(wasm.api/set-focus-mode focus)))))
|
||||
|
||||
(mf/with-effect [vbox zoom]
|
||||
(when (and @canvas-init? @initialized?)
|
||||
(wasm.api/set-view-box zoom vbox)))
|
||||
|
||||
(mf/with-effect [background]
|
||||
(when (and @canvas-init? @initialized?)
|
||||
(wasm.api/set-canvas-background background)))
|
||||
|
||||
@ -208,6 +208,8 @@
|
||||
|
||||
(def ^:const DEBOUNCE_DELAY_MS 100)
|
||||
|
||||
(defonce ^:private view-interaction-active? (atom false))
|
||||
|
||||
;; Time budget (ms) per chunk of shape processing before yielding to browser
|
||||
(def ^:private ^:const CHUNK_TIME_BUDGET_MS 8)
|
||||
;; Threshold below which we use synchronous processing (no chunking overhead)
|
||||
@ -1146,14 +1148,26 @@
|
||||
(= result 1))
|
||||
false))
|
||||
|
||||
(defn view-interaction-start!
|
||||
[]
|
||||
(when-not @view-interaction-active?
|
||||
(h/call wasm/internal-module "_set_view_start")
|
||||
(reset! view-interaction-active? true)))
|
||||
|
||||
(defn view-interaction-end!
|
||||
[]
|
||||
(when @view-interaction-active?
|
||||
(perf/begin-measure "render-finish")
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(perf/end-measure "render-finish")
|
||||
(reset! view-interaction-active? false)))
|
||||
|
||||
(def render-finish
|
||||
(letfn [(do-render []
|
||||
;; Check if context is still initialized before executing
|
||||
;; to prevent errors when navigating quickly
|
||||
(when (and wasm/context-initialized? (not @wasm/context-lost?))
|
||||
(perf/begin-measure "render-finish")
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(perf/end-measure "render-finish")
|
||||
(view-interaction-end!)
|
||||
;; Use async _render: visible tiles render synchronously
|
||||
;; (no yield), interest-area tiles render progressively
|
||||
;; via rAF. _set_view_end already rebuilt the tile
|
||||
@ -1167,7 +1181,7 @@
|
||||
(defn set-view-box
|
||||
[zoom vbox]
|
||||
(perf/begin-measure "set-view-box")
|
||||
(h/call wasm/internal-module "_set_view_start")
|
||||
(view-interaction-start!)
|
||||
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
|
||||
(perf/end-measure "set-view-box")
|
||||
|
||||
@ -1176,6 +1190,16 @@
|
||||
(render-finish)
|
||||
(perf/end-measure "render-from-cache"))
|
||||
|
||||
(defn sync-workspace-local-viewport!
|
||||
"Pushes `[:workspace-local :zoom]` and `:vbox` into WASM."
|
||||
[state]
|
||||
(when (and wasm/context-initialized?
|
||||
(not @wasm/context-lost?))
|
||||
(let [zoom (get-in state [:workspace-local :zoom])
|
||||
vbox (get-in state [:workspace-local :vbox])]
|
||||
(when (and zoom vbox)
|
||||
(set-view-box zoom vbox)))))
|
||||
|
||||
(defn- ensure-text-content
|
||||
"Guarantee that the shape always sends a valid text tree to WASM. When the
|
||||
content is nil (freshly created text) we fall back to
|
||||
@ -1344,6 +1368,7 @@
|
||||
;; Rebuild the tile index so _render knows which shapes
|
||||
;; map to which tiles after a page switch.
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(reset! view-interaction-active? false)
|
||||
|
||||
;; Text layouts must run after _end_loading (they
|
||||
;; depend on state that is only correct when loading
|
||||
@ -1402,6 +1427,7 @@
|
||||
;; Rebuild the tile index so _render knows which shapes
|
||||
;; map to which tiles after a page switch.
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(reset! view-interaction-active? false)
|
||||
(process-pending shapes thumbnails full
|
||||
(fn []
|
||||
(if render-callback
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user