🐛 Fix focus mode for wasm render

This commit is contained in:
Alejandro Alonso 2025-06-10 13:32:33 +02:00
parent 01311225c7
commit 1c7aea4b84
5 changed files with 145 additions and 5 deletions

View file

@ -320,6 +320,12 @@
(wasm.api/initialize base-objects zoom vbox background)
(reset! initialized? true)))
(mf/with-effect [focus]
(when (and @canvas-init? @initialized?)
(if (empty? focus)
(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)))

View file

@ -248,7 +248,7 @@
heap (mem/get-heap-u8)
dview (js/DataView. (.-buffer heap))]
;; write fill data to heap
;; write fill data to heap
(loop [fills (seq fills)
current-offset offset]
(when-not (empty? fills)
@ -256,10 +256,10 @@
new-offset (sr-fills/write-fill! current-offset dview fill)]
(recur (rest fills) new-offset))))
;; send fills to wasm
;; send fills to wasm
(h/call wasm/internal-module "_set_shape_fills")
;; load images for image fills if not cached
;; load images for image fills if not cached
(keep (fn [fill]
(let [image (:fill-image fill)
id (dm/get-prop image :id)
@ -802,6 +802,28 @@
(request-render "set-objects")
(process-pending pending)))
(defn clear-focus-mode
[]
(h/call wasm/internal-module "_clear_focus_mode")
(clear-drawing-cache)
(request-render "clear-focus-mode"))
(defn set-focus-mode
[entries]
(let [offset (mem/alloc-bytes-32 (* (count entries) 16))
heapu32 (mem/get-heap-u32)]
(loop [entries (seq entries)
current-offset offset]
(when-not (empty? entries)
(let [id (first entries)]
(sr/heapu32-set-uuid id heapu32 current-offset)
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 16))))))
(h/call wasm/internal-module "_set_focus_mode")
(clear-drawing-cache)
(request-render "set-focus-mode")))
(defn set-structure-modifiers
[entries]
(when-not (empty? entries)

View file

@ -161,6 +161,27 @@ pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
});
}
#[no_mangle]
pub extern "C" fn clear_focus_mode() {
with_state!(state, {
state.clear_focus_mode();
});
}
#[no_mangle]
pub extern "C" fn set_focus_mode() {
let bytes = mem::bytes();
let entries: Vec<Uuid> = bytes
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
.map(|data| Uuid::from_bytes(data.try_into().unwrap()))
.collect();
with_state!(state, {
state.set_focus_mode(entries);
});
}
#[no_mangle]
pub extern "C" fn init_shapes_pool(capacity: usize) {
with_state!(state, {

View file

@ -81,6 +81,73 @@ impl NodeRenderState {
}
}
/// Represents the "focus mode" state used during rendering.
///
/// Focus mode allows selectively highlighting or isolating specific shapes (UUIDs)
/// during the render pass. It maintains a list of shapes to focus and tracks
/// whether the current rendering context is inside a focused element.
///
/// # Focus Propagation
/// If a shape is in focus, all its nested content
/// is also considered to be in focus for the duration of the render traversal. Focus
/// state propagates *downward* through the tree while rendering.
///
/// # Usage
/// - `set_shapes(...)` to activate focus mode for specific elements and their anidated content.
/// - `clear()` to disable focus mode.
/// - `reset()` should be called at the beginning of the render loop.
/// - `enter(...)` / `exit(...)` should be called when entering and leaving shape
/// render contexts.
/// - `is_active()` returns whether the current shape is being rendered in focus.
pub struct FocusMode {
shapes: Vec<Uuid>,
active: bool,
}
impl FocusMode {
pub fn new() -> Self {
FocusMode {
shapes: Vec::new(),
active: false,
}
}
pub fn clear(&mut self) {
self.shapes.clear();
self.active = false;
}
pub fn set_shapes(&mut self, shapes: Vec<Uuid>) {
self.shapes = shapes;
}
/// Returns `true` if the given shape ID should be focused.
/// If the `shapes` list is empty, focus applies to all shapes.
pub fn should_focus(&self, id: &Uuid) -> bool {
self.shapes.is_empty() || self.shapes.contains(id)
}
pub fn enter(&mut self, id: &Uuid) {
if !self.active && self.should_focus(id) {
self.active = true;
}
}
pub fn exit(&mut self, id: &Uuid) {
if self.active && self.should_focus(id) {
self.active = false;
}
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn reset(&mut self) {
self.active = false;
}
}
pub(crate) struct RenderState {
gpu_state: GpuState,
pub options: RenderOptions,
@ -109,6 +176,7 @@ pub(crate) struct RenderState {
// migration to remove group-level fills is completed, this code should be removed.
pub nested_fills: Vec<Vec<Fill>>,
pub show_grid: Option<Uuid>,
pub focus_mode: FocusMode,
}
pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
@ -176,6 +244,7 @@ impl RenderState {
pending_tiles: PendingTiles::new_empty(),
nested_fills: vec![],
show_grid: None,
focus_mode: FocusMode::new(),
}
}
@ -311,6 +380,14 @@ impl RenderState {
});
}
pub fn clear_focus_mode(&mut self) {
self.focus_mode.clear();
}
pub fn set_focus_mode(&mut self, shapes: Vec<Uuid>) {
self.focus_mode.set_shapes(shapes);
}
pub fn render_shape(
&mut self,
shape: &Shape,
@ -569,6 +646,8 @@ impl RenderState {
let scale = self.get_scale();
self.tile_viewbox.update(self.viewbox, scale);
self.focus_mode.reset();
performance::begin_measure!("render");
performance::begin_measure!("start_render_loop");
@ -683,6 +762,8 @@ impl RenderState {
self.surfaces
.canvas(SurfaceId::Current)
.save_layer(&layer_rec);
self.focus_mode.enter(&element.id);
}
#[inline]
@ -728,6 +809,8 @@ impl RenderState {
self.nested_fills.pop();
}
self.surfaces.canvas(SurfaceId::Current).restore();
self.focus_mode.exit(&element.id);
}
pub fn get_current_tile_bounds(&mut self) -> Rect {
@ -823,7 +906,7 @@ impl RenderState {
}
self.render_shape_enter(element, mask);
if !node_render_state.is_root() {
if !node_render_state.is_root() && self.focus_mode.is_active() {
self.render_shape(
element,
modifiers.get(&element.id),

View file

@ -1,5 +1,5 @@
use std::collections::{hash_map::Entry, HashMap};
use std::iter;
use std::{iter, vec};
use skia_safe as skia;
@ -135,6 +135,14 @@ impl<'a> State<'a> {
Ok(())
}
pub fn clear_focus_mode(&mut self) {
self.render_state.clear_focus_mode();
}
pub fn set_focus_mode(&mut self, shapes: Vec<Uuid>) {
self.render_state.set_focus_mode(shapes);
}
pub fn init_shapes_pool(&mut self, capacity: usize) {
self.shapes_pool.initialize(capacity);
}