mirror of
https://github.com/penpot/penpot.git
synced 2026-01-19 20:00:07 -05:00
Compare commits
8 Commits
develop
...
superalex-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a904857a97 | ||
|
|
cb5674831a | ||
|
|
02065559da | ||
|
|
c291329fb4 | ||
|
|
89f40dcda2 | ||
|
|
ccac7bd510 | ||
|
|
43d1d127dc | ||
|
|
8bd3ef717c |
@@ -312,8 +312,8 @@
|
||||
[]
|
||||
(let [on-reload (mf/use-fn #(js/location.reload))]
|
||||
[:> error-container* {}
|
||||
[:div {:class (stl/css :main-message)} (tr "labels.webgl-context-lost.main-message")]
|
||||
[:div {:class (stl/css :desc-message)} (tr "labels.webgl-context-lost.desc-message")]
|
||||
[:div {:class (stl/css :main-message)} (tr "errors.webgl-context-lost.main-message")]
|
||||
[:div {:class (stl/css :desc-message)} (tr "errors.webgl-context-lost.desc-message")]
|
||||
[:div {:class (stl/css :buttons-container)}
|
||||
[:> button* {:variant "primary" :on-click on-reload}
|
||||
(tr "labels.reload-page")]]]))
|
||||
|
||||
@@ -275,7 +275,11 @@
|
||||
:wglobal wglobal
|
||||
:layout layout}])
|
||||
(when (or (not (and file-loaded? page-id))
|
||||
(and wasm-renderer-enabled? (not @first-frame-rendered?)))
|
||||
;; in wasm renderer, extend the pixel loader until the first frame is rendered
|
||||
;; but do not apply it when switching pages
|
||||
(and wasm-renderer-enabled?
|
||||
(not file-loaded?)
|
||||
(not @first-frame-rendered?)))
|
||||
[:> workspace-loader*])]]]]]]))
|
||||
|
||||
(mf/defc workspace-page*
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
@@ -22,9 +23,11 @@
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.notifications.badge :refer [badge-notification]]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.timers :as timers]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -52,6 +55,8 @@
|
||||
refs/workspace-data
|
||||
=))
|
||||
|
||||
|
||||
|
||||
;; --- Page Item
|
||||
|
||||
(mf/defc page-item
|
||||
@@ -63,6 +68,22 @@
|
||||
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dcm/go-to-workspace :page-id id)))
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn []
|
||||
;; when using the wasm renderer, apply a blur effect to the viewport canvas
|
||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||
(do
|
||||
(wasm.api/capture-canvas-pixels)
|
||||
(wasm.api/apply-canvas-blur)
|
||||
;; NOTE: it seems we need two RAF so the blur is actually applied and visible
|
||||
;; in the canvas :(
|
||||
(timers/raf
|
||||
(fn []
|
||||
(timers/raf navigate-fn))))
|
||||
(navigate-fn))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
@@ -155,7 +176,7 @@
|
||||
:selected selected?)
|
||||
:data-testid (dm/str "page-" id)
|
||||
:tab-index "0"
|
||||
:on-click navigate-fn
|
||||
:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-context-menu on-context-menu}
|
||||
[:div {:class (stl/css :page-icon)}
|
||||
|
||||
@@ -312,6 +312,11 @@
|
||||
(js/console.error "Error initializing canvas context:" e)
|
||||
false))]
|
||||
(reset! canvas-init? init?)
|
||||
(when init?
|
||||
;; Restore previous canvas pixels immediately after context initialization
|
||||
;; This happens before initialize-viewport is called
|
||||
(wasm.api/apply-canvas-blur)
|
||||
(wasm.api/restore-previous-canvas-pixels))
|
||||
(when-not init?
|
||||
(js/alert "WebGL not supported")
|
||||
(st/emit! (dcm/go-to-dashboard-recent))))))))
|
||||
@@ -340,6 +345,7 @@
|
||||
|
||||
(mf/with-effect [@canvas-init? zoom vbox background]
|
||||
(when (and @canvas-init? (not @initialized?))
|
||||
(wasm.api/clear-canvas-pixels)
|
||||
(wasm.api/initialize-viewport base-objects zoom vbox background)
|
||||
(reset! initialized? true)))
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.api.fonts :as f]
|
||||
[app.render-wasm.api.texts :as t]
|
||||
[app.render-wasm.api.webgl :as webgl]
|
||||
[app.render-wasm.deserializers :as dr]
|
||||
[app.render-wasm.helpers :as h]
|
||||
[app.render-wasm.mem :as mem]
|
||||
@@ -37,7 +38,6 @@
|
||||
[app.render-wasm.serializers :as sr]
|
||||
[app.render-wasm.serializers.color :as sr-clr]
|
||||
[app.render-wasm.svg-filters :as svg-filters]
|
||||
;; FIXME: rename; confunsing name
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
@@ -279,30 +279,6 @@
|
||||
[string]
|
||||
(+ (count string) 1))
|
||||
|
||||
(defn- create-webgl-texture-from-image
|
||||
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
|
||||
[gl image-element]
|
||||
(let [texture (.createTexture ^js gl)]
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
texture))
|
||||
|
||||
(defn- get-webgl-context
|
||||
"Gets the WebGL context from the WASM module"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
||||
(when gl-obj
|
||||
;; Get the current WebGL context from Emscripten
|
||||
;; The GL object has a currentContext property that contains the context handle
|
||||
(let [current-ctx (.-currentContext ^js gl-obj)]
|
||||
(when current-ctx
|
||||
(.-GLctx ^js current-ctx)))))))
|
||||
|
||||
(defn- get-texture-id-for-gl-object
|
||||
"Registers a WebGL texture with Emscripten's GL object system and returns its ID"
|
||||
@@ -332,8 +308,8 @@
|
||||
(->> (retrieve-image url)
|
||||
(rx/map
|
||||
(fn [img]
|
||||
(when-let [gl (get-webgl-context)]
|
||||
(let [texture (create-webgl-texture-from-image gl img)
|
||||
(when-let [gl (webgl/get-webgl-context)]
|
||||
(let [texture (webgl/create-webgl-texture-from-image gl img)
|
||||
texture-id (get-texture-id-for-gl-object texture)
|
||||
width (.-width ^js img)
|
||||
height (.-height ^js img)
|
||||
@@ -1448,6 +1424,12 @@
|
||||
|
||||
result)))
|
||||
|
||||
(defn apply-canvas-blur
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(dom/set-style! wasm/canvas "filter" "blur(4px)")))
|
||||
|
||||
|
||||
(defn init-wasm-module
|
||||
[module]
|
||||
(let [default-fn (unchecked-get module "default")
|
||||
@@ -1469,3 +1451,8 @@
|
||||
(js/console.error cause)
|
||||
(p/resolved false)))))
|
||||
(p/resolved false))))
|
||||
|
||||
;; Re-export public WebGL functions
|
||||
(def capture-canvas-pixels webgl/capture-canvas-pixels)
|
||||
(def restore-previous-canvas-pixels webgl/restore-previous-canvas-pixels)
|
||||
(def clear-canvas-pixels webgl/clear-canvas-pixels)
|
||||
|
||||
166
frontend/src/app/render_wasm/api/webgl.cljs
Normal file
166
frontend/src/app/render_wasm/api/webgl.cljs
Normal file
@@ -0,0 +1,166 @@
|
||||
;; 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.render-wasm.api.webgl
|
||||
"WebGL utilities for pixel capture and rendering"
|
||||
(:require
|
||||
[app.common.logging :as log]
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[app.util.dom :as dom]))
|
||||
|
||||
(defn get-webgl-context
|
||||
"Gets the WebGL context from the WASM module"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
||||
(when gl-obj
|
||||
;; Get the current WebGL context from Emscripten
|
||||
;; The GL object has a currentContext property that contains the context handle
|
||||
(let [current-ctx (.-currentContext ^js gl-obj)]
|
||||
(when current-ctx
|
||||
(.-GLctx ^js current-ctx)))))))
|
||||
|
||||
(defn create-webgl-texture-from-image
|
||||
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
|
||||
[gl image-element]
|
||||
(let [texture (.createTexture ^js gl)]
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
texture))
|
||||
|
||||
;; FIXME: temporary function until we are able to keep the same <canvas> across pages.
|
||||
(defn- draw-imagedata-to-webgl
|
||||
"Draws ImageData to a WebGL2 context by creating a texture"
|
||||
[gl image-data]
|
||||
(let [width (.-width ^js image-data)
|
||||
height (.-height ^js image-data)
|
||||
texture (.createTexture ^js gl)]
|
||||
;; Bind texture and set parameters
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
||||
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-data)
|
||||
|
||||
;; Set up viewport
|
||||
(.viewport ^js gl 0 0 width height)
|
||||
|
||||
;; Vertex & Fragment shaders
|
||||
;; Since we are only calling this function once (on page switch), we don't need
|
||||
;; to cache the compiled shaders somewhere else (cannot be reused in a differen context).
|
||||
(let [vertex-shader-source "#version 300 es
|
||||
in vec2 a_position;
|
||||
in vec2 a_texCoord;
|
||||
out vec2 v_texCoord;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_texCoord = a_texCoord;
|
||||
}"
|
||||
fragment-shader-source "#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
uniform sampler2D u_texture;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = texture(u_texture, v_texCoord);
|
||||
}"
|
||||
vertex-shader (.createShader ^js gl (.-VERTEX_SHADER ^js gl))
|
||||
fragment-shader (.createShader ^js gl (.-FRAGMENT_SHADER ^js gl))
|
||||
program (.createProgram ^js gl)]
|
||||
(.shaderSource ^js gl vertex-shader vertex-shader-source)
|
||||
(.compileShader ^js gl vertex-shader)
|
||||
(when-not (.getShaderParameter ^js gl vertex-shader (.-COMPILE_STATUS ^js gl))
|
||||
(log/error :hint "Vertex shader compilation failed"
|
||||
:log (.getShaderInfoLog ^js gl vertex-shader)))
|
||||
|
||||
(.shaderSource ^js gl fragment-shader fragment-shader-source)
|
||||
(.compileShader ^js gl fragment-shader)
|
||||
(when-not (.getShaderParameter ^js gl fragment-shader (.-COMPILE_STATUS ^js gl))
|
||||
(log/error :hint "Fragment shader compilation failed"
|
||||
:log (.getShaderInfoLog ^js gl fragment-shader)))
|
||||
|
||||
(.attachShader ^js gl program vertex-shader)
|
||||
(.attachShader ^js gl program fragment-shader)
|
||||
(.linkProgram ^js gl program)
|
||||
|
||||
(if (.getProgramParameter ^js gl program (.-LINK_STATUS ^js gl))
|
||||
(do
|
||||
(.useProgram ^js gl program)
|
||||
|
||||
;; Create full-screen quad vertices (normalized device coordinates)
|
||||
(let [position-location (.getAttribLocation ^js gl program "a_position")
|
||||
texcoord-location (.getAttribLocation ^js gl program "a_texCoord")
|
||||
position-buffer (.createBuffer ^js gl)
|
||||
texcoord-buffer (.createBuffer ^js gl)
|
||||
positions #js [-1.0 -1.0 1.0 -1.0 -1.0 1.0 -1.0 1.0 1.0 -1.0 1.0 1.0]
|
||||
texcoords #js [0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 1.0]]
|
||||
;; Set up position buffer
|
||||
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) position-buffer)
|
||||
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
|
||||
(.enableVertexAttribArray ^js gl position-location)
|
||||
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
|
||||
;; Set up texcoord buffer
|
||||
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
|
||||
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
|
||||
(.enableVertexAttribArray ^js gl texcoord-location)
|
||||
(.vertexAttribPointer ^js gl texcoord-location 2 (.-FLOAT ^js gl) false 0 0)
|
||||
;; Set texture uniform
|
||||
(.activeTexture ^js gl (.-TEXTURE0 ^js gl))
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
||||
(let [texture-location (.getUniformLocation ^js gl program "u_texture")]
|
||||
(.uniform1i ^js gl texture-location 0))
|
||||
|
||||
;; draw
|
||||
(.drawArrays ^js gl (.-TRIANGLES ^js gl) 0 6)
|
||||
|
||||
;; cleanup
|
||||
(.deleteBuffer ^js gl position-buffer)
|
||||
(.deleteBuffer ^js gl texcoord-buffer)
|
||||
(.deleteShader ^js gl vertex-shader)
|
||||
(.deleteShader ^js gl fragment-shader)
|
||||
(.deleteProgram ^js gl program)))
|
||||
(log/error :hint "Program linking failed"
|
||||
:log (.getProgramInfoLog ^js gl program)))
|
||||
|
||||
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
||||
(.deleteTexture ^js gl texture))))
|
||||
|
||||
(defn restore-previous-canvas-pixels
|
||||
"Restores previous canvas pixels into the new canvas"
|
||||
[]
|
||||
(when-let [previous-canvas-pixels wasm/canvas-pixels]
|
||||
(when-let [gl wasm/gl-context]
|
||||
(draw-imagedata-to-webgl gl previous-canvas-pixels)
|
||||
(set! wasm/canvas-pixels nil))))
|
||||
|
||||
(defn clear-canvas-pixels
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(let [context wasm/gl-context]
|
||||
(.clearColor ^js context 0 0 0 0.0)
|
||||
(.clear ^js context (.-COLOR_BUFFER_BIT ^js context))
|
||||
(.clear ^js context (.-DEPTH_BUFFER_BIT ^js context))
|
||||
(.clear ^js context (.-STENCIL_BUFFER_BIT ^js context)))
|
||||
(dom/set-style! wasm/canvas "filter" "none")
|
||||
(set! wasm/canvas-pixels nil)))
|
||||
|
||||
(defn capture-canvas-pixels
|
||||
"Captures the pixels of the viewport canvas"
|
||||
[]
|
||||
(when wasm/canvas
|
||||
(let [context wasm/gl-context
|
||||
width (.-width wasm/canvas)
|
||||
height (.-height wasm/canvas)
|
||||
buffer (js/Uint8ClampedArray. (* width height 4))
|
||||
_ (.readPixels ^js context 0 0 width height (.-RGBA ^js context) (.-UNSIGNED_BYTE ^js context) buffer)
|
||||
image-data (js/ImageData. buffer width height)]
|
||||
(set! wasm/canvas-pixels image-data))))
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
;; Reference to the HTML canvas element.
|
||||
(defonce canvas nil)
|
||||
;; Reference to the captured pixels of the canvas (for page switching effect)
|
||||
(defonce canvas-pixels nil)
|
||||
|
||||
;; Reference to the Emscripten GL context wrapper.
|
||||
(defonce gl-context-handle nil)
|
||||
@@ -56,3 +58,4 @@
|
||||
:stroke-linecap shared/RawStrokeLineCap
|
||||
:stroke-linejoin shared/RawStrokeLineJoin
|
||||
:fill-rule shared/RawFillRule})
|
||||
|
||||
|
||||
@@ -1559,6 +1559,12 @@ msgstr "Old password is incorrect"
|
||||
msgid "feedback.description"
|
||||
msgstr "Description"
|
||||
|
||||
msgid "errors.webgl-context-lost.main-message"
|
||||
msgstr "Oops! The canvas context was lost"
|
||||
|
||||
msgid "errors.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL has stopped working. Please reload the page to reset it"
|
||||
|
||||
#: src/app/main/ui/settings/feedback.cljs:122
|
||||
msgid "feedback.description-placeholder"
|
||||
msgstr "Please describe the reason of your feedback"
|
||||
@@ -8478,12 +8484,6 @@ msgstr "Recent"
|
||||
msgid "labels.deleted"
|
||||
msgstr "Deleted"
|
||||
|
||||
msgid "labels.webgl-context-lost.main-message"
|
||||
msgstr "Oops! The canvas context was lost"
|
||||
|
||||
msgid "labels.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL has stopped working. Please reload the page to reset it"
|
||||
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restore All"
|
||||
|
||||
|
||||
@@ -1552,6 +1552,12 @@ msgstr "El email o la contraseña son incorrectos."
|
||||
msgid "errors.wrong-old-password"
|
||||
msgstr "La contraseña anterior no es correcta"
|
||||
|
||||
msgid "errors.webgl-context-lost.main-message"
|
||||
msgstr "Ups! Se ha perdido el contexto del canvas"
|
||||
|
||||
msgid "errors.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL ha dejado de funcionar. Por favor, recarga la página para restaurarlo"
|
||||
|
||||
#: src/app/main/ui/settings/feedback.cljs:120
|
||||
msgid "feedback.description"
|
||||
msgstr "Descripción"
|
||||
@@ -8331,12 +8337,6 @@ msgstr "Recientes"
|
||||
msgid "labels.deleted"
|
||||
msgstr "Eliminados"
|
||||
|
||||
msgid "labels.webgl-context-lost.main-message"
|
||||
msgstr "Ups! Se ha perdido el contexto del canvas"
|
||||
|
||||
msgid "labels.webgl-context-lost.desc-message"
|
||||
msgstr "WebGL ha dejado de funcionar. Por favor, recarga la página para restaurarlo"
|
||||
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restaurar todo"
|
||||
|
||||
|
||||
@@ -258,10 +258,12 @@ pub extern "C" fn set_view_end() {
|
||||
with_state_mut!(state, {
|
||||
let _end_start = performance::begin_timed_log!("set_view_end");
|
||||
performance::begin_measure!("set_view_end");
|
||||
|
||||
state.render_state.options.set_fast_mode(false);
|
||||
state.render_state.cancel_animation_frame();
|
||||
|
||||
let zoom_changed = state.render_state.zoom_changed();
|
||||
|
||||
// Only rebuild tile indices when zoom has changed.
|
||||
// During pan-only operations, shapes stay in the same tiles
|
||||
// because tile_size = 1/scale * TILE_SIZE (depends only on zoom).
|
||||
@@ -284,6 +286,10 @@ pub extern "C" fn set_view_end() {
|
||||
performance::end_measure!("set_view_end::clear_tile_index");
|
||||
performance::end_timed_log!("clear_tile_index", _clear_start);
|
||||
}
|
||||
// Sync cached_viewbox with current viewbox to ensure accurate
|
||||
// comparison in subsequent calls, especially during rapid zoom+pan
|
||||
// interactions where multiple set_view calls occur before set_view_end.
|
||||
// state.render_state.sync_cached_viewbox();
|
||||
performance::end_measure!("set_view_end");
|
||||
performance::end_timed_log!("set_view_end", _end_start);
|
||||
#[cfg(feature = "profile-macros")]
|
||||
|
||||
@@ -1136,6 +1136,23 @@ impl RenderState {
|
||||
) -> Result<(), String> {
|
||||
let _start = performance::begin_timed_log!("start_render_loop");
|
||||
let scale = self.get_scale();
|
||||
let zoom_changed = self.zoom_changed();
|
||||
// CRITICAL FIX: If zoom changed, we MUST rebuild the tile index before using it.
|
||||
// Otherwise, the index will have tiles from the old zoom level, causing visible
|
||||
// tiles to appear empty. This can happen if start_render_loop() is called before
|
||||
// set_view_end() finishes rebuilding the index, or if set_view_end() hasn't been
|
||||
// called yet. We have access to tree here, so we can rebuild the index ourselves.
|
||||
//
|
||||
// IMPORTANT: If there's a render in progress, we must cancel it first because
|
||||
// rebuild_tiles_shallow() will invalidate the tile index, causing tiles already
|
||||
// in pending_tiles to lose their shapes.
|
||||
if zoom_changed {
|
||||
if self.render_in_progress {
|
||||
self.cancel_animation_frame();
|
||||
}
|
||||
self.rebuild_tiles_shallow(tree);
|
||||
}
|
||||
|
||||
self.tile_viewbox.update(self.viewbox, scale);
|
||||
|
||||
self.focus_mode.reset();
|
||||
@@ -1988,7 +2005,6 @@ impl RenderState {
|
||||
self.background_color,
|
||||
);
|
||||
performance::end_measure!("render_shape_tree::cached");
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_workspace_current_tile(
|
||||
self,
|
||||
@@ -2001,7 +2017,6 @@ impl RenderState {
|
||||
performance::begin_measure!("render_shape_tree::uncached");
|
||||
let (is_empty, early_return) =
|
||||
self.render_shape_tree_partial_uncached(tree, timestamp, allow_stop)?;
|
||||
|
||||
if early_return {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -2027,7 +2042,6 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::Current)
|
||||
.clear(self.background_color);
|
||||
@@ -2066,19 +2080,14 @@ impl RenderState {
|
||||
self.render_in_progress = false;
|
||||
|
||||
self.surfaces.gc();
|
||||
|
||||
// Cache target surface in a texture
|
||||
self.cached_viewbox = self.viewbox;
|
||||
|
||||
self.cached_target_snapshot = Some(self.surfaces.snapshot(SurfaceId::Cache));
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
ui::render(self, tree);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2162,13 +2171,11 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the changed tiles
|
||||
self.surfaces.remove_cached_tiles(self.background_color);
|
||||
for tile in all_tiles {
|
||||
self.remove_cached_tile(tile);
|
||||
}
|
||||
|
||||
performance::end_measure!("rebuild_tiles_shallow");
|
||||
}
|
||||
|
||||
@@ -2292,6 +2299,13 @@ impl RenderState {
|
||||
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
|
||||
}
|
||||
|
||||
/// Updates the cached viewbox to match the current viewbox.
|
||||
/// This should be called at the end of view interactions to ensure
|
||||
/// that cached_viewbox reflects the most recent state for the next comparison.
|
||||
pub fn sync_cached_viewbox(&mut self) {
|
||||
self.cached_viewbox = self.viewbox;
|
||||
}
|
||||
|
||||
pub fn mark_touched(&mut self, uuid: Uuid) {
|
||||
self.touched_ids.insert(uuid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user