mirror of
https://github.com/penpot/penpot.git
synced 2025-07-12 18:47:16 +02:00
🐛 Fix focus mode for wasm render
This commit is contained in:
parent
01311225c7
commit
1c7aea4b84
5 changed files with 145 additions and 5 deletions
|
@ -320,6 +320,12 @@
|
||||||
(wasm.api/initialize base-objects zoom vbox background)
|
(wasm.api/initialize base-objects zoom vbox background)
|
||||||
(reset! initialized? true)))
|
(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]
|
(mf/with-effect [vbox zoom]
|
||||||
(when (and @canvas-init? initialized?)
|
(when (and @canvas-init? initialized?)
|
||||||
(wasm.api/set-view-box zoom vbox)))
|
(wasm.api/set-view-box zoom vbox)))
|
||||||
|
|
|
@ -802,6 +802,28 @@
|
||||||
(request-render "set-objects")
|
(request-render "set-objects")
|
||||||
(process-pending pending)))
|
(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
|
(defn set-structure-modifiers
|
||||||
[entries]
|
[entries]
|
||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
|
|
|
@ -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]
|
#[no_mangle]
|
||||||
pub extern "C" fn init_shapes_pool(capacity: usize) {
|
pub extern "C" fn init_shapes_pool(capacity: usize) {
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
|
|
|
@ -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 {
|
pub(crate) struct RenderState {
|
||||||
gpu_state: GpuState,
|
gpu_state: GpuState,
|
||||||
pub options: RenderOptions,
|
pub options: RenderOptions,
|
||||||
|
@ -109,6 +176,7 @@ pub(crate) struct RenderState {
|
||||||
// migration to remove group-level fills is completed, this code should be removed.
|
// migration to remove group-level fills is completed, this code should be removed.
|
||||||
pub nested_fills: Vec<Vec<Fill>>,
|
pub nested_fills: Vec<Vec<Fill>>,
|
||||||
pub show_grid: Option<Uuid>,
|
pub show_grid: Option<Uuid>,
|
||||||
|
pub focus_mode: FocusMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
||||||
|
@ -176,6 +244,7 @@ impl RenderState {
|
||||||
pending_tiles: PendingTiles::new_empty(),
|
pending_tiles: PendingTiles::new_empty(),
|
||||||
nested_fills: vec![],
|
nested_fills: vec![],
|
||||||
show_grid: None,
|
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(
|
pub fn render_shape(
|
||||||
&mut self,
|
&mut self,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
|
@ -569,6 +646,8 @@ impl RenderState {
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
self.tile_viewbox.update(self.viewbox, scale);
|
self.tile_viewbox.update(self.viewbox, scale);
|
||||||
|
|
||||||
|
self.focus_mode.reset();
|
||||||
|
|
||||||
performance::begin_measure!("render");
|
performance::begin_measure!("render");
|
||||||
performance::begin_measure!("start_render_loop");
|
performance::begin_measure!("start_render_loop");
|
||||||
|
|
||||||
|
@ -683,6 +762,8 @@ impl RenderState {
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::Current)
|
.canvas(SurfaceId::Current)
|
||||||
.save_layer(&layer_rec);
|
.save_layer(&layer_rec);
|
||||||
|
|
||||||
|
self.focus_mode.enter(&element.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -728,6 +809,8 @@ impl RenderState {
|
||||||
self.nested_fills.pop();
|
self.nested_fills.pop();
|
||||||
}
|
}
|
||||||
self.surfaces.canvas(SurfaceId::Current).restore();
|
self.surfaces.canvas(SurfaceId::Current).restore();
|
||||||
|
|
||||||
|
self.focus_mode.exit(&element.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_tile_bounds(&mut self) -> Rect {
|
pub fn get_current_tile_bounds(&mut self) -> Rect {
|
||||||
|
@ -823,7 +906,7 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.render_shape_enter(element, mask);
|
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(
|
self.render_shape(
|
||||||
element,
|
element,
|
||||||
modifiers.get(&element.id),
|
modifiers.get(&element.id),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
use std::iter;
|
use std::{iter, vec};
|
||||||
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
|
@ -135,6 +135,14 @@ impl<'a> State<'a> {
|
||||||
Ok(())
|
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) {
|
pub fn init_shapes_pool(&mut self, capacity: usize) {
|
||||||
self.shapes_pool.initialize(capacity);
|
self.shapes_pool.initialize(capacity);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue