diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4d224baa63..28123c294f 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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) diff --git a/frontend/src/app/main/data/workspace/viewport.cljs b/frontend/src/app/main/data/workspace/viewport.cljs index bf203c10a7..67fb95eb7f 100644 --- a/frontend/src/app/main/data/workspace/viewport.cljs +++ b/frontend/src/app/main/data/workspace/viewport.cljs @@ -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)))) diff --git a/frontend/src/app/main/data/workspace/viewport_wasm.cljs b/frontend/src/app/main/data/workspace/viewport_wasm.cljs new file mode 100644 index 0000000000..4115589ab0 --- /dev/null +++ b/frontend/src/app/main/data/workspace/viewport_wasm.cljs @@ -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!))) \ No newline at end of file diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 2984024803..91aa48ad89 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -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)))) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 0ef0936d22..9bcf142358 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index d23ac367e7..ce90438c45 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -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))) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index edb1a4f0bf..f3b6cdc244 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -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