mirror of
https://github.com/penpot/penpot.git
synced 2026-06-16 04:12:03 +00:00
WIP
This commit is contained in:
parent
7fd4e35203
commit
b282bd753a
@ -29,10 +29,12 @@
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.data.workspace.viewport-wasm :as dwvw]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.mouse :as mse]
|
||||
[beicon.v2.core :as rx]
|
||||
[beicon.v2.operators :as rxo]
|
||||
@ -159,7 +161,13 @@
|
||||
::dwsp/interrupt
|
||||
(if (some #(= % uuid/zero) frame-ids)
|
||||
(rt/nav :workspace params-without-board {::rt/replace true})
|
||||
(rt/nav :workspace params-board {::rt/replace true}))))))))
|
||||
(rt/nav :workspace params-board {::rt/replace true})))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(when (features/active-feature? state "render-wasm/v1")
|
||||
(js/console.log "select-shape" id)
|
||||
(wasm.api/set-selected id))))))
|
||||
|
||||
(defn select-prev-shape
|
||||
([]
|
||||
@ -276,7 +284,14 @@
|
||||
expand-s (->> (rx/of (dwc/expand-all-parents ids objects))
|
||||
(rx/observe-on :async))
|
||||
interrupt-s (rx/of ::dwsp/interrupt)]
|
||||
(rx/merge expand-s interrupt-s)))))
|
||||
(rx/merge expand-s interrupt-s)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(js/console.log "select-shapes" (clj->js ids))
|
||||
;; TODO: Que set-selected permita tanto un único identificador
|
||||
;; como una lista de identificadores.
|
||||
#_(wasm.api/set-selected ids))))
|
||||
|
||||
(defn select-all
|
||||
[]
|
||||
|
||||
@ -1614,6 +1614,13 @@
|
||||
(keep #(get objects %))
|
||||
all-ordered-ids))))
|
||||
|
||||
(defn set-selected
|
||||
[id]
|
||||
(let [c1 (uuid/get-u32 id)]
|
||||
(h/call wasm/internal-module "_set_selected" (aget c1 0) (aget c1 1) (aget c1 2) (aget c1 3))))
|
||||
|
||||
(unchecked-set js/globalThis "setSelected" set-selected)
|
||||
|
||||
(defn set-objects
|
||||
"Set all shape objects for rendering.
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ description = "Wasm-based canvas renderer for Penpot"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["profile"]
|
||||
stats = []
|
||||
profile = ["profile-macros", "profile-raf"]
|
||||
profile-macros = []
|
||||
|
||||
@ -5,12 +5,12 @@ use crate::emscripten::init_gl;
|
||||
|
||||
use crate::mem;
|
||||
use crate::render::{gpu_state::GpuState, RenderState};
|
||||
use crate::state::{State, TextEditorState};
|
||||
use crate::state::{DesignState, TextEditorState};
|
||||
|
||||
static mut DESIGN_STATE: *mut State = std::ptr::null_mut();
|
||||
static mut DESIGN_STATE: *mut DesignState = std::ptr::null_mut();
|
||||
|
||||
/// Design State.
|
||||
pub(crate) fn get_design_state() -> &'static mut State {
|
||||
pub(crate) fn get_design_state() -> &'static mut DesignState {
|
||||
unsafe {
|
||||
debug_assert!(!DESIGN_STATE.is_null(), "Design State is null");
|
||||
&mut *DESIGN_STATE
|
||||
@ -105,7 +105,7 @@ fn render_init(width: i32, height: i32) {
|
||||
/// Initializes DesignState.
|
||||
fn design_init() {
|
||||
unsafe {
|
||||
let design_state = State::new();
|
||||
let design_state = DesignState::new();
|
||||
DESIGN_STATE = Box::into_raw(Box::new(design_state));
|
||||
}
|
||||
}
|
||||
|
||||
@ -932,6 +932,21 @@ pub extern "C" fn get_shape_extrect(a: u32, b: u32, c: u32, d: u32) -> Result<*m
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn set_selected(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
|
||||
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||
with_state!(state, {
|
||||
println!("set_selected {}", id);
|
||||
// TODO: Right now we're working only with one shape.
|
||||
state.selected.clear();
|
||||
state.selected.insert(id);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Add `add_selected, delete_selected, clear_selected`
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn render_shape_pixels(
|
||||
|
||||
@ -22,6 +22,7 @@ use options::RenderOptions;
|
||||
pub use surfaces::{SurfaceId, Surfaces};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::globals::get_design_state;
|
||||
use crate::math;
|
||||
use crate::shapes::{
|
||||
all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor,
|
||||
@ -400,21 +401,61 @@ pub(crate) struct RenderState {
|
||||
pub interactive_target_seeded: bool,
|
||||
/// GPU crops from `Backbuffer` or tile atlas keyed by shape id. Filled on full-frame completion; during
|
||||
/// drag, entries for the moved top-level selection are ensured here
|
||||
pub backbuffer_crop_cache: HashMap<Uuid, InteractiveDragCrop>,
|
||||
pub shape_cache: ShapeCacheMap,
|
||||
}
|
||||
|
||||
pub struct InteractiveDragCrop {
|
||||
#[derive(Debug)]
|
||||
pub struct ShapeCache {
|
||||
pub src_doc_bounds: Rect,
|
||||
pub src_selrect: Rect,
|
||||
|
||||
/// Viewbox origin (doc-space) at capture time.
|
||||
pub capture_vb_left: f32,
|
||||
pub capture_vb_top: f32,
|
||||
|
||||
/// Backbuffer pixel origin used for `snapshot_rect` (so we can do 1:1 blits).
|
||||
pub capture_src_left: i32,
|
||||
pub capture_src_top: i32,
|
||||
|
||||
pub image: skia::Image,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PotentiallyCacheableShape {
|
||||
pub id: Uuid,
|
||||
bounds: Rect,
|
||||
selrect: Rect,
|
||||
}
|
||||
|
||||
impl PartialEq for PotentiallyCacheableShape {
|
||||
fn eq(&self, other: &PotentiallyCacheableShape) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ShapeCacheMap {
|
||||
potentially_cacheable: HashMap<Uuid, PotentiallyCacheableShape>,
|
||||
pub map: HashMap<Uuid, ShapeCache>,
|
||||
}
|
||||
|
||||
impl ShapeCacheMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
potentially_cacheable: HashMap::new(),
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_potentially_cacheable(&mut self, shape: &Shape, bounds: Rect, selrect: Rect) {
|
||||
self.potentially_cacheable.insert(shape.id, PotentiallyCacheableShape {
|
||||
id: shape.id,
|
||||
bounds,
|
||||
selrect
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Chooses a window inside the full workspace-pixel crop `[0, out_w) × [0, out_h)` with each side
|
||||
/// at most `max_side_px` (**without scaling**): centered on the projection of
|
||||
/// `viewport_doc ∩ src_doc_bounds`, or on the full crop if that intersection is empty.
|
||||
@ -475,7 +516,7 @@ impl RenderState {
|
||||
moved_ids: &[Uuid],
|
||||
moved_bounds: Option<Rect>,
|
||||
) -> bool {
|
||||
if !self.backbuffer_crop_cache.contains_key(&node_id) {
|
||||
if !self.shape_cache.map.contains_key(&node_id) {
|
||||
return false;
|
||||
}
|
||||
let Some(raw) = tree.get_raw(&node_id) else {
|
||||
@ -499,7 +540,7 @@ impl RenderState {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.backbuffer_crop_cache.contains_key(&node_id) {
|
||||
if !self.shape_cache.map.contains_key(&node_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -516,7 +557,8 @@ impl RenderState {
|
||||
// becomes valid again (stationary shape unchanged).
|
||||
if let Some(moved) = moved_bounds {
|
||||
let intersects = self
|
||||
.backbuffer_crop_cache
|
||||
.shape_cache
|
||||
.map
|
||||
.get(&node_id)
|
||||
.is_some_and(|crop| moved.intersects(crop.src_doc_bounds));
|
||||
|
||||
@ -581,7 +623,7 @@ impl RenderState {
|
||||
cache_cleared_this_render: false,
|
||||
current_tile_had_shapes: false,
|
||||
interactive_target_seeded: false,
|
||||
backbuffer_crop_cache: HashMap::default(),
|
||||
shape_cache: ShapeCacheMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -1787,15 +1829,16 @@ impl RenderState {
|
||||
self.surfaces.update_render_context(self.render_area, scale);
|
||||
}
|
||||
|
||||
fn rebuild_backbuffer_crop_cache(&mut self, tree: ShapesPoolRef) {
|
||||
self.backbuffer_crop_cache.clear();
|
||||
fn rebuild_shape_cache(&mut self, tree: ShapesPoolRef) {
|
||||
performance::begin_measure!("rebuild_backbuffer_crop_cache");
|
||||
self.shape_cache.map.clear();
|
||||
|
||||
// Collect candidate shapes that are "recortable" and visible in the current viewport.
|
||||
|
||||
// This is intentionally conservative; we only cache shapes that do not overlap with
|
||||
// ANY other candidate to guarantee the pixels under their bounds belong exclusively
|
||||
// to that shape in Backbuffer.
|
||||
let viewport = self.viewbox.area;
|
||||
let viewport = self.viewbox.area();
|
||||
let scale = self.get_scale();
|
||||
let mut candidates: Vec<(Uuid, Rect, Rect)> = Vec::new(); // (id, doc_bounds, selrect)
|
||||
|
||||
@ -1863,8 +1906,8 @@ impl RenderState {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let vb_left = self.viewbox.area.left;
|
||||
let vb_top = self.viewbox.area.top;
|
||||
let vb_left = self.viewbox.area().left;
|
||||
let vb_top = self.viewbox.area().top;
|
||||
let (bb_w, bb_h) = self.surfaces.surface_size(SurfaceId::Backbuffer);
|
||||
let max_snap_px = get_gpu_state().max_texture_size();
|
||||
|
||||
@ -1971,9 +2014,9 @@ impl RenderState {
|
||||
img
|
||||
};
|
||||
|
||||
self.backbuffer_crop_cache.insert(
|
||||
self.shape_cache.map.insert(
|
||||
id,
|
||||
InteractiveDragCrop {
|
||||
ShapeCache {
|
||||
src_doc_bounds: src_doc_window,
|
||||
src_selrect: selrect,
|
||||
capture_vb_left: vb_left,
|
||||
@ -1984,6 +2027,8 @@ impl RenderState {
|
||||
},
|
||||
);
|
||||
}
|
||||
println!("rebuild_backbuffer_crop_cache {:?}", self.shape_cache.map);
|
||||
performance::end_measure!("rebuild_backbuffer_crop_cache");
|
||||
}
|
||||
|
||||
pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) {
|
||||
@ -2005,21 +2050,21 @@ impl RenderState {
|
||||
}
|
||||
|
||||
// Check if we have a valid cached viewbox (non-zero dimensions indicate valid cache)
|
||||
if self.cached_viewbox.area.width() > 0.0 {
|
||||
if self.cached_viewbox.area().width() > 0.0 {
|
||||
// Scale and translate the target according to the cached data
|
||||
let navigate_zoom = self.viewbox.zoom / self.cached_viewbox.zoom;
|
||||
let navigate_zoom = self.viewbox.zoom() / self.cached_viewbox.zoom();
|
||||
|
||||
let interest = self.options.dpr_viewport_interest_area_threshold;
|
||||
let TileRect(start_tile_x, start_tile_y, _, _) =
|
||||
tiles::get_tiles_for_viewbox_with_interest(&self.cached_viewbox, interest);
|
||||
let offset_x = self.viewbox.area.left * self.cached_viewbox.zoom * self.options.dpr;
|
||||
let offset_y = self.viewbox.area.top * self.cached_viewbox.zoom * self.options.dpr;
|
||||
let offset_x = self.viewbox.area().left * self.cached_viewbox.zoom() * self.options.dpr;
|
||||
let offset_y = self.viewbox.area().top * self.cached_viewbox.zoom() * self.options.dpr;
|
||||
let translate_x = (start_tile_x as f32 * tiles::TILE_SIZE) - offset_x;
|
||||
let translate_y = (start_tile_y as f32 * tiles::TILE_SIZE) - offset_y;
|
||||
|
||||
// For zoom-out, prefer cache only if it fully covers the viewport.
|
||||
// Otherwise, atlas will provide a more correct full-viewport preview.
|
||||
let zooming_out = self.viewbox.zoom < self.cached_viewbox.zoom;
|
||||
let zooming_out = self.viewbox.zoom() < self.cached_viewbox.zoom();
|
||||
if zooming_out {
|
||||
let cache_dim = self.surfaces.cache_dimensions();
|
||||
let cache_w = cache_dim.width as f32;
|
||||
@ -2079,14 +2124,11 @@ impl RenderState {
|
||||
// render after set_view_end handle it.
|
||||
if !self.zoom_changed() {
|
||||
let visible_rect = tiles::get_tiles_for_viewbox(&self.viewbox);
|
||||
let offset = self.viewbox.get_offset();
|
||||
for tx in visible_rect.x1()..=visible_rect.x2() {
|
||||
for ty in visible_rect.y1()..=visible_rect.y2() {
|
||||
let tile = tiles::Tile::from(tx, ty);
|
||||
if self.surfaces.has_cached_tile_surface(tile) {
|
||||
let rect = tile.get_rect_with_offset(&offset);
|
||||
self.surfaces.draw_cached_tile_into_backbuffer(tile, &rect);
|
||||
}
|
||||
let offset = self.viewbox.offset();
|
||||
for tile in visible_rect.iter(true) {
|
||||
if self.surfaces.has_cached_tile_surface(tile) {
|
||||
let rect = tile.get_rect_with_offset(&offset);
|
||||
self.surfaces.draw_cached_tile_into_backbuffer(tile, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2317,7 +2359,7 @@ impl RenderState {
|
||||
// cache from the clean Backbuffer (no UI overlay yet) so that
|
||||
// interactive drag backgrounds don't include the grid overlay.
|
||||
if !self.options.is_fast_mode() && !self.options.is_interactive_transform() {
|
||||
self.rebuild_backbuffer_crop_cache(tree);
|
||||
self.rebuild_shape_cache(tree);
|
||||
}
|
||||
// present_frame: copy clean Backbuffer → Target, draw UI/debug
|
||||
// overlays on Target only, then flush. Backbuffer stays overlay-free.
|
||||
@ -2717,6 +2759,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
self.focus_mode.exit(&element.id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2724,17 +2767,16 @@ impl RenderState {
|
||||
let tile = self
|
||||
.current_tile
|
||||
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||
let offset = self.viewbox.get_offset();
|
||||
let offset = self.viewbox.offset();
|
||||
Ok(tile.get_rect_with_offset(&offset))
|
||||
}
|
||||
|
||||
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
||||
let scale = self.get_scale();
|
||||
let offset_x = self.viewbox.area.left * scale;
|
||||
let offset_y = self.viewbox.area.top * scale;
|
||||
let offset = self.viewbox.offset();
|
||||
Rect::from_xywh(
|
||||
(rect.left * scale) - offset_x,
|
||||
(rect.top * scale) - offset_y,
|
||||
(rect.left * scale) - offset.x,
|
||||
(rect.top * scale) - offset.y,
|
||||
rect.width() * scale,
|
||||
rect.height() * scale,
|
||||
)
|
||||
@ -2752,11 +2794,11 @@ impl RenderState {
|
||||
}
|
||||
|
||||
pub fn get_aligned_tile_bounds(&mut self, tile: tiles::Tile) -> Rect {
|
||||
let scale = self.get_scale();
|
||||
let offset = self.viewbox.offset();
|
||||
let start_tile_x =
|
||||
(self.viewbox.area.left * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
(offset.x / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let start_tile_y =
|
||||
(self.viewbox.area.top * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
(offset.y / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
Rect::from_xywh(
|
||||
(tile.x() as f32 * tiles::TILE_SIZE) - start_tile_x,
|
||||
(tile.y() as f32 * tiles::TILE_SIZE) - start_tile_y,
|
||||
@ -3266,7 +3308,7 @@ impl RenderState {
|
||||
|
||||
let has_effects = transformed_element.has_effects_that_extend_bounds();
|
||||
|
||||
let is_visible = export
|
||||
let is_renderable = export
|
||||
|| mask
|
||||
|| if is_container || has_effects {
|
||||
let element_extrect =
|
||||
@ -3278,13 +3320,12 @@ impl RenderState {
|
||||
selrect.intersects(self.render_area_with_margins)
|
||||
&& !transformed_element.visually_insignificant(scale, tree)
|
||||
};
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
let shape_extrect_bounds = self.get_shape_extrect_bounds(element, tree);
|
||||
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
|
||||
}
|
||||
|
||||
if !is_visible {
|
||||
if !is_renderable {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -3301,7 +3342,7 @@ impl RenderState {
|
||||
);
|
||||
|
||||
if use_cached {
|
||||
if let Some(crop) = self.backbuffer_crop_cache.get(&node_id) {
|
||||
if let Some(crop) = self.shape_cache.map.get(&node_id) {
|
||||
let crop_image = &crop.image;
|
||||
let crop_src_selrect = crop.src_selrect;
|
||||
|
||||
@ -4030,7 +4071,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
pub fn zoom_changed(&self) -> bool {
|
||||
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
|
||||
(self.viewbox.zoom() - self.cached_viewbox.zoom()).abs() > f32::EPSILON
|
||||
}
|
||||
|
||||
pub fn mark_touched(&mut self, uuid: Uuid) {
|
||||
@ -4056,7 +4097,7 @@ impl RenderState {
|
||||
|
||||
pub fn prepare_context_loss_cleanup(&mut self) {
|
||||
// Drop cached GPU-backed snapshots before dropping the render state.
|
||||
self.backbuffer_crop_cache.clear();
|
||||
self.shape_cache.map.clear();
|
||||
self.surfaces.invalidate_tile_cache();
|
||||
// Mark context as abandoned so resource destructors avoid issuing
|
||||
// GL commands when the browser has already lost/restored the context.
|
||||
|
||||
@ -29,7 +29,7 @@ fn render_debug_view(render_state: &mut RenderState) {
|
||||
paint.set_color(skia::Color::GREEN);
|
||||
paint.set_stroke_width(1.);
|
||||
|
||||
let rect = get_debug_rect(render_state.viewbox.area);
|
||||
let rect = get_debug_rect(render_state.viewbox.area());
|
||||
render_state
|
||||
.surfaces
|
||||
.canvas(SurfaceId::Debug)
|
||||
@ -99,7 +99,7 @@ pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
||||
paint.set_stroke_width(1.);
|
||||
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
let tile_rect = tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
|
||||
let tile_rect = tiles::get_tiles_for_rect(render_state.viewbox.area(), tile_size);
|
||||
let tiles::TileRect(sx, sy, ex, ey) = tile_rect;
|
||||
|
||||
let str_rect = format!("{} {} {} {}", sx, sy, ex, ey);
|
||||
|
||||
@ -563,8 +563,8 @@ impl Surfaces {
|
||||
let s = viewbox.get_scale();
|
||||
let scale = self.atlas.scale.max(0.01);
|
||||
canvas.translate((
|
||||
(self.atlas.origin.x + viewbox.pan.x) * s,
|
||||
(self.atlas.origin.y + viewbox.pan.y) * s,
|
||||
(self.atlas.origin.x + viewbox.pan().x) * s,
|
||||
(self.atlas.origin.y + viewbox.pan().y) * s,
|
||||
));
|
||||
canvas.scale((s / scale, s / scale));
|
||||
|
||||
@ -1589,7 +1589,7 @@ impl TileTextureCache {
|
||||
texture.set_empty();
|
||||
}
|
||||
|
||||
let offset = viewbox.get_offset();
|
||||
let offset = viewbox.offset();
|
||||
let mut index = 0;
|
||||
for y in tile_viewbox.visible_rect.top()..=tile_viewbox.visible_rect.bottom() {
|
||||
for x in tile_viewbox.visible_rect.left()..=tile_viewbox.visible_rect.right() {
|
||||
|
||||
@ -21,16 +21,15 @@ pub fn render_overlay(
|
||||
};
|
||||
|
||||
canvas.save();
|
||||
let zoom = viewbox.zoom * options.dpr;
|
||||
canvas.scale((zoom, zoom));
|
||||
canvas.translate((-viewbox.area.left, -viewbox.area.top));
|
||||
canvas.scale(viewbox.scale());
|
||||
canvas.translate(viewbox.pan());
|
||||
|
||||
if editor_state.selection.is_selection() {
|
||||
render_selection(canvas, editor_state, text_content, shape);
|
||||
}
|
||||
|
||||
if editor_state.cursor_visible {
|
||||
render_cursor(canvas, zoom, editor_state, text_content, shape);
|
||||
render_cursor(canvas, viewbox.get_scale(), editor_state, text_content, shape);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
@ -7,18 +7,17 @@ use crate::shapes::{Layout, Type};
|
||||
pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::UI);
|
||||
let viewbox = render_state.viewbox;
|
||||
let zoom = viewbox.zoom * render_state.options.dpr;
|
||||
let show_grid_id = render_state.show_grid;
|
||||
|
||||
canvas.clear(Color4f::new(0.0, 0.0, 0.0, 0.0));
|
||||
canvas.save();
|
||||
canvas.scale((zoom, zoom));
|
||||
canvas.translate((-viewbox.area.left, -viewbox.area.top));
|
||||
canvas.scale(viewbox.scale());
|
||||
canvas.translate(viewbox.pan());
|
||||
|
||||
if let Some(id) = show_grid_id {
|
||||
if let Some(shape) = shapes.get(&id) {
|
||||
grid_layout::render_overlay(
|
||||
zoom,
|
||||
viewbox.get_scale(),
|
||||
render_state.options.antialias_threshold,
|
||||
canvas,
|
||||
shape,
|
||||
@ -51,7 +50,7 @@ pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
||||
|
||||
if let Some(shape) = shapes.get(&shape.id) {
|
||||
grid_layout::render_overlay(
|
||||
zoom,
|
||||
viewbox.get_scale(),
|
||||
render_state.options.antialias_threshold,
|
||||
canvas,
|
||||
shape,
|
||||
|
||||
@ -335,6 +335,23 @@ impl Shape {
|
||||
self.shape_type = shape_type;
|
||||
}
|
||||
|
||||
pub fn is_potentially_cacheable_as_image(&self) -> bool {
|
||||
// It is a container.
|
||||
let is_container = self.is_recursive();
|
||||
|
||||
// It is a costly to draw shape.
|
||||
let is_costly = matches!(self.shape_type, Type::Path(_) | Type::Bool(_) | Type::Text(_))
|
||||
|| self.has_layout();
|
||||
|
||||
// It is prolific.
|
||||
let is_prolific = self.children.len() > 2;
|
||||
|
||||
// We know how big the shape is.
|
||||
let is_extrect_cached = !self.extrect_cache.borrow().is_none();
|
||||
|
||||
(is_container || is_costly || is_prolific) && is_extrect_cached
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_frame(&self) -> bool {
|
||||
matches!(self.shape_type, Type::Frame(_))
|
||||
@ -345,7 +362,7 @@ impl Shape {
|
||||
}
|
||||
|
||||
pub fn is_group_like(&self) -> bool {
|
||||
matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_))
|
||||
matches!(self.shape_type, Type::Group(_) | Type::Bool(_))
|
||||
}
|
||||
|
||||
pub fn has_layout(&self) -> bool {
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::shapes::{
|
||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry,
|
||||
TransformEntrySource, Type,
|
||||
};
|
||||
use crate::state::{ShapesPoolRef, State};
|
||||
use crate::state::{ShapesPoolRef, DesignState};
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -176,7 +176,7 @@ fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
|
||||
fn propagate_transform(
|
||||
entry: TransformEntry,
|
||||
pixel_precision: bool,
|
||||
state: &State,
|
||||
state: &DesignState,
|
||||
entries: &mut VecDeque<Modifier>,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
modifiers: &mut HashMap<Uuid, Matrix>,
|
||||
@ -324,7 +324,7 @@ fn propagate_transform(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn propagate_reflow(
|
||||
id: &Uuid,
|
||||
state: &State,
|
||||
state: &DesignState,
|
||||
entries: &mut VecDeque<Modifier>,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
layout_reflows: &mut HashSet<Uuid>,
|
||||
@ -380,7 +380,7 @@ fn propagate_reflow(
|
||||
|
||||
fn reflow_shape(
|
||||
id: &Uuid,
|
||||
state: &State,
|
||||
state: &DesignState,
|
||||
reflown: &mut HashSet<Uuid>,
|
||||
entries: &mut VecDeque<Modifier>,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
@ -409,7 +409,7 @@ fn reflow_shape(
|
||||
}
|
||||
|
||||
pub fn propagate_modifiers(
|
||||
state: &State,
|
||||
state: &DesignState,
|
||||
modifiers: &[TransformEntry],
|
||||
pixel_precision: bool,
|
||||
) -> Result<Vec<TransformEntry>> {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use skia_safe::{self as skia, textlayout::FontCollection, Path, Point};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
mod rulers;
|
||||
mod shapes_pool;
|
||||
@ -19,7 +19,8 @@ use crate::{get_render_state, tiles};
|
||||
/// It is created by [init] and passed to the other exported functions.
|
||||
/// Note that rust-skia data structures are not thread safe, so a state
|
||||
/// must not be shared between different Web Workers.
|
||||
pub(crate) struct State {
|
||||
pub(crate) struct DesignState {
|
||||
pub selected: HashSet<Uuid>,
|
||||
pub current_id: Option<Uuid>,
|
||||
pub current_browser: u8,
|
||||
pub shapes: ShapesPool,
|
||||
@ -28,9 +29,10 @@ pub(crate) struct State {
|
||||
pub loading: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl DesignState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
selected: HashSet::new(),
|
||||
current_id: None,
|
||||
current_browser: 0,
|
||||
shapes: ShapesPool::new(),
|
||||
@ -137,6 +139,10 @@ impl State {
|
||||
self.current_id = Some(id);
|
||||
}
|
||||
|
||||
pub fn is_potentially_cacheable_as_image(&self, id: Uuid) -> bool {
|
||||
return self.selected.contains(&id)
|
||||
}
|
||||
|
||||
pub fn has_shape(&mut self, id: Uuid) -> bool {
|
||||
self.shapes.has(&id)
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> TileRect {
|
||||
|
||||
pub fn get_tiles_for_viewbox(viewbox: &Viewbox) -> TileRect {
|
||||
let tile_size = get_tile_size(viewbox.get_scale());
|
||||
get_tiles_for_rect(viewbox.area, tile_size)
|
||||
get_tiles_for_rect(viewbox.area(), tile_size)
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_viewbox_with_interest(viewbox: &Viewbox, interest: i32) -> TileRect {
|
||||
|
||||
@ -3,11 +3,11 @@ use std::ops::Mul;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct Viewbox {
|
||||
pub pan: Point,
|
||||
pub size: Size,
|
||||
pub zoom: f32,
|
||||
pub dpr: f32,
|
||||
pub area: Rect,
|
||||
pan: Point,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
dpr: f32,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl Default for Viewbox {
|
||||
@ -67,18 +67,28 @@ impl Viewbox {
|
||||
.set_wh(self.size.width / self.zoom, self.size.height / self.zoom);
|
||||
}
|
||||
|
||||
pub fn set_dpr(&mut self, dpr: f32) {
|
||||
pub fn set_dpr(&mut self, dpr: f32) -> f32 {
|
||||
self.dpr = dpr;
|
||||
self.dpr
|
||||
}
|
||||
|
||||
pub fn set_zoom(&mut self, new_zoom: f32) -> f32 {
|
||||
self.zoom = new_zoom;
|
||||
self.zoom
|
||||
}
|
||||
|
||||
pub fn get_scale(&self) -> f32 {
|
||||
self.zoom * self.dpr
|
||||
}
|
||||
|
||||
pub fn get_offset(&self) -> Point {
|
||||
pub fn offset(&self) -> Point {
|
||||
self.area.tl().mul(self.get_scale())
|
||||
}
|
||||
|
||||
pub fn area(&self) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
pub fn pan(&self) -> Point {
|
||||
self.pan
|
||||
}
|
||||
@ -87,6 +97,10 @@ impl Viewbox {
|
||||
self.zoom
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> (f32, f32) {
|
||||
(self.get_scale(), self.get_scale())
|
||||
}
|
||||
|
||||
pub fn get_matrix(&self) -> Matrix {
|
||||
let mut matrix = Matrix::new_identity();
|
||||
matrix.post_translate(self.pan());
|
||||
|
||||
@ -2,13 +2,13 @@ use crate::error::{Error, Result};
|
||||
use crate::get_render_state;
|
||||
use crate::mem;
|
||||
use crate::shapes::Fill;
|
||||
use crate::state::State;
|
||||
use crate::state::DesignState;
|
||||
use crate::uuid::Uuid;
|
||||
use crate::with_state;
|
||||
use crate::{shapes::ImageFill, utils::uuid_from_u32_quartet};
|
||||
use macros::wasm_error;
|
||||
|
||||
fn touch_shapes_with_image(state: &mut State, image_id: Uuid) {
|
||||
fn touch_shapes_with_image(state: &mut DesignState, image_id: Uuid) {
|
||||
let ids: Vec<Uuid> = state
|
||||
.shapes
|
||||
.iter()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user