mirror of
https://github.com/penpot/penpot.git
synced 2026-07-03 21:05:25 +00:00
WIP
This commit is contained in:
parent
37add2c6d4
commit
2c42d4fb06
@ -9,7 +9,7 @@ description = "Wasm-based canvas renderer for Penpot"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["profile"]
|
||||
stats = []
|
||||
|
||||
profile = ["profile-macros", "profile-raf"]
|
||||
|
||||
@ -5,12 +5,13 @@ use crate::emscripten::init_gl;
|
||||
|
||||
use crate::mem;
|
||||
use crate::render::{gpu_state::GpuState, RenderState};
|
||||
use crate::state::{State, TextEditorState, UIState};
|
||||
use crate::state::{DesignState, TextEditorState, UIState};
|
||||
use crate::tiles::TileRenderState;
|
||||
|
||||
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
|
||||
@ -39,6 +40,16 @@ pub(crate) fn get_render_state() -> &'static mut RenderState {
|
||||
}
|
||||
}
|
||||
|
||||
static mut TILE_RENDER_STATE: *mut TileRenderState = std::ptr::null_mut();
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_tile_render_state() -> &'static mut TileRenderState {
|
||||
unsafe {
|
||||
debug_assert!(!TILE_RENDER_STATE.is_null(), "Tile Render State is null");
|
||||
&mut *TILE_RENDER_STATE
|
||||
}
|
||||
}
|
||||
|
||||
/// Text Editor State
|
||||
static mut TEXT_EDITOR_STATE: *mut TextEditorState = std::ptr::null_mut();
|
||||
|
||||
@ -113,10 +124,16 @@ fn render_init(width: i32, height: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_render_init() {
|
||||
unsafe {
|
||||
TILE_RENDER_STATE = Box::into_raw(Box::new(TileRenderState::new()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
@ -144,6 +161,7 @@ pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
|
||||
init_gl!();
|
||||
gpu_init();
|
||||
render_init(width, height);
|
||||
tile_render_init();
|
||||
text_editor_init();
|
||||
design_init();
|
||||
ui_init();
|
||||
|
||||
@ -20,7 +20,10 @@ use std::collections::HashMap;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{render::{FrameType, RenderFlag}, shapes::Frame};
|
||||
use crate::{
|
||||
render::{FrameType, RenderFlag},
|
||||
shapes::Frame,
|
||||
};
|
||||
|
||||
use globals::{get_design_state, get_gpu_state, get_render_state};
|
||||
|
||||
@ -115,22 +118,17 @@ pub extern "C" fn render2(timestamp: i32, flags: u8) -> Result<FrameType> {
|
||||
// si el render es sync o no y que esto no permita
|
||||
let allow_stop = true;
|
||||
render_state
|
||||
.continue_render_loop(
|
||||
timestamp,
|
||||
allow_stop
|
||||
)
|
||||
.continue_render_loop(timestamp, allow_stop)
|
||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?
|
||||
} else {
|
||||
let sync_render = false;
|
||||
render_state.start_render_loop(
|
||||
timestamp,
|
||||
sync_render
|
||||
).map_err(|_| Error::RecoverableError("Error rendering".to_string()))?
|
||||
render_state
|
||||
.start_render_loop(timestamp, sync_render)
|
||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?
|
||||
};
|
||||
render_state.end_render_loop(&frame_type);
|
||||
return Ok(frame_type);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ use macros::wasm_error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::get_render_state;
|
||||
use crate::globals::get_tile_render_state;
|
||||
|
||||
use skia_safe::{self as skia, Rect};
|
||||
|
||||
@ -69,7 +70,8 @@ pub fn render_wasm_label(render_state: &mut RenderState) {
|
||||
}
|
||||
|
||||
pub fn render_debug_tiles_for_viewbox(render_state: &mut RenderState) {
|
||||
let tiles::TileRect(sx, sy, ex, ey) = render_state.tile.viewbox.interest_rect;
|
||||
let tile_render_state = get_tile_render_state();
|
||||
let tiles::TileRect(sx, sy, ex, ey) = tile_render_state.viewbox.interest_rect;
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_color(skia::Color::RED);
|
||||
|
||||
@ -335,7 +335,8 @@ pub fn render_element_drop_shadows_and_composite(
|
||||
}
|
||||
|
||||
if let Some(clips) = clip_bounds.as_ref() {
|
||||
let antialias = element.should_use_antialias(scale, render_state.options.antialias_threshold);
|
||||
let antialias =
|
||||
element.should_use_antialias(scale, render_state.options.antialias_threshold);
|
||||
render_state.surfaces.canvas(target_surface).save();
|
||||
render_state.clip_target_surface_to_stack(clips, target_surface, scale, antialias);
|
||||
render_state
|
||||
|
||||
@ -1,193 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use skia_safe as skia;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::shapes::{radius_to_sigma, Shape, StrokeKind, Type};
|
||||
|
||||
use super::layer_blur;
|
||||
use super::{ClipStack, NodeRenderState, RenderState, SurfaceId};
|
||||
|
||||
pub fn render_shape_enter(
|
||||
render_state: &mut RenderState,
|
||||
element: &Shape,
|
||||
mask: bool,
|
||||
clip_bounds: Option<&ClipStack>,
|
||||
target_surface: SurfaceId,
|
||||
) {
|
||||
if let Type::Group(group) = element.shape_type {
|
||||
let fills = &element.fills;
|
||||
let shadows = &element.shadows;
|
||||
render_state.nested_fills.push(fills.to_vec());
|
||||
render_state.nested_shadows.push(shadows.to_vec());
|
||||
|
||||
if group.masked {
|
||||
let mask_group_blur = element.masked_group_layer_blur().is_some();
|
||||
if mask_group_blur {
|
||||
render_state.surfaces.canvas(target_surface).save();
|
||||
if let Some(clips) = clip_bounds {
|
||||
let scale = render_state.get_scale();
|
||||
let antialias = element
|
||||
.should_use_antialias(scale, render_state.options.antialias_threshold);
|
||||
render_state.clip_target_surface_to_stack(
|
||||
clips,
|
||||
target_surface,
|
||||
scale,
|
||||
antialias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut paint = skia::Paint::default();
|
||||
if let Some(blur) = element.masked_group_layer_blur() {
|
||||
let scale = render_state.get_scale();
|
||||
let sigma = radius_to_sigma(blur.value * scale);
|
||||
if let Some(filter) =
|
||||
skia::image_filters::blur((sigma, sigma), None, None, None)
|
||||
{
|
||||
paint.set_image_filter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
render_state
|
||||
.surfaces
|
||||
.canvas(target_surface)
|
||||
.save_layer(&layer_rec);
|
||||
}
|
||||
}
|
||||
|
||||
if let Type::Frame(_) = element.shape_type {
|
||||
render_state.nested_fills.push(Vec::new());
|
||||
}
|
||||
|
||||
if mask {
|
||||
let mut mask_paint = skia::Paint::default();
|
||||
mask_paint.set_blend_mode(skia::BlendMode::DstIn);
|
||||
let mask_rec = skia::canvas::SaveLayerRec::default().paint(&mask_paint);
|
||||
render_state
|
||||
.surfaces
|
||||
.canvas(target_surface)
|
||||
.save_layer(&mask_rec);
|
||||
}
|
||||
|
||||
let needs_layer = element.needs_layer();
|
||||
|
||||
if needs_layer {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_blend_mode(element.blend_mode().into());
|
||||
paint.set_alpha_f(element.opacity());
|
||||
|
||||
if let Some(frame_blur) = layer_blur::frame_clip_layer_blur(element) {
|
||||
let scale = render_state.get_scale();
|
||||
let sigma = radius_to_sigma(frame_blur.value * scale);
|
||||
if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) {
|
||||
paint.set_image_filter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
render_state
|
||||
.surfaces
|
||||
.canvas(target_surface)
|
||||
.save_layer(&layer_rec);
|
||||
}
|
||||
|
||||
render_state.focus_mode.enter(&element.id);
|
||||
}
|
||||
|
||||
pub fn render_shape_exit(
|
||||
render_state: &mut RenderState,
|
||||
element: &Shape,
|
||||
visited_mask: bool,
|
||||
clip_bounds: Option<ClipStack>,
|
||||
target_surface: SurfaceId,
|
||||
) -> Result<()> {
|
||||
if visited_mask {
|
||||
if let Type::Group(group) = element.shape_type {
|
||||
if group.masked {
|
||||
render_state.surfaces.canvas(target_surface).restore();
|
||||
}
|
||||
}
|
||||
} else if let Type::Group(group) = element.shape_type {
|
||||
if group.masked {
|
||||
render_state.pending_nodes.push(NodeRenderState {
|
||||
id: element.id,
|
||||
visited_children: true,
|
||||
clip_bounds: None,
|
||||
visited_mask: true,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
});
|
||||
if let Some(&mask_id) = element.mask_id() {
|
||||
render_state.pending_nodes.push(NodeRenderState {
|
||||
id: mask_id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: true,
|
||||
flattened: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match element.shape_type {
|
||||
Type::Frame(_) | Type::Group(_) => {
|
||||
render_state.nested_fills.pop();
|
||||
render_state.nested_blurs.pop();
|
||||
render_state.cached_layer_blur = None;
|
||||
render_state.nested_shadows.pop();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let needs_exit_strokes = render_state.focus_mode.is_active()
|
||||
&& (element.clip()
|
||||
|| (matches!(element.shape_type, Type::Frame(_)) && element.has_inner_stroke()));
|
||||
|
||||
if needs_exit_strokes {
|
||||
let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
|
||||
element_strokes.to_mut().clear_fills();
|
||||
element_strokes.to_mut().clear_shadows();
|
||||
element_strokes.to_mut().clip_content = false;
|
||||
|
||||
if !element.clip() {
|
||||
let is_open = element.is_open();
|
||||
element_strokes
|
||||
.to_mut()
|
||||
.strokes
|
||||
.retain(|s| s.render_kind(is_open) == StrokeKind::Inner);
|
||||
}
|
||||
|
||||
if layer_blur::frame_clip_layer_blur(element).is_some() {
|
||||
element_strokes.to_mut().set_blur(None);
|
||||
}
|
||||
render_state.render_shape(
|
||||
&element_strokes,
|
||||
clip_bounds,
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Strokes,
|
||||
SurfaceId::InnerShadows,
|
||||
SurfaceId::TextDropShadows,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
target_surface,
|
||||
)?;
|
||||
}
|
||||
|
||||
let needs_layer = element.needs_layer();
|
||||
|
||||
if needs_layer {
|
||||
render_state.surfaces.canvas(target_surface).restore();
|
||||
}
|
||||
|
||||
if visited_mask && element.masked_group_layer_blur().is_some() {
|
||||
render_state.surfaces.canvas(target_surface).restore();
|
||||
}
|
||||
|
||||
render_state.focus_mode.exit(&element.id);
|
||||
Ok(())
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
use crate::error::Result;
|
||||
use crate::get_gpu_state;
|
||||
use crate::state::ShapesPoolRef;
|
||||
use crate::uuid::Uuid;
|
||||
use skia_safe as skia;
|
||||
|
||||
use super::{NodeRenderState, RenderState, SurfaceId};
|
||||
|
||||
pub fn render_shape_pixels(
|
||||
render_state: &mut RenderState,
|
||||
id: &Uuid,
|
||||
tree: ShapesPoolRef,
|
||||
scale: f32,
|
||||
timestamp: i32,
|
||||
) -> Result<(Vec<u8>, i32, i32)> {
|
||||
let target_surface = SurfaceId::Export;
|
||||
|
||||
let saved_focus_mode = render_state.focus_mode.clone();
|
||||
let saved_export_context = render_state.export_context;
|
||||
let saved_render_area = render_state.render_area;
|
||||
let saved_render_area_with_margins = render_state.render_area_with_margins;
|
||||
let saved_current_tile = render_state.tile.current;
|
||||
let saved_pending_nodes = std::mem::take(&mut render_state.pending_nodes);
|
||||
let saved_nested_fills = std::mem::take(&mut render_state.nested_fills);
|
||||
let saved_nested_blurs = std::mem::take(&mut render_state.nested_blurs);
|
||||
let saved_nested_shadows = std::mem::take(&mut render_state.nested_shadows);
|
||||
let saved_ignore_nested_blurs = render_state.ignore_nested_blurs;
|
||||
let saved_preview_mode = render_state.preview_mode;
|
||||
|
||||
render_state.focus_mode.clear();
|
||||
|
||||
render_state
|
||||
.surfaces
|
||||
.canvas(target_surface)
|
||||
.clear(skia::Color::TRANSPARENT);
|
||||
|
||||
if tree.len() != 0 {
|
||||
let Some(shape) = tree.get(id) else {
|
||||
return Ok((Vec::new(), 0, 0));
|
||||
};
|
||||
let mut extrect = shape.extrect(tree, scale);
|
||||
render_state.export_context = Some((extrect, scale));
|
||||
let margins = render_state.surfaces.margins;
|
||||
extrect.offset((margins.width as f32 / scale, margins.height as f32 / scale));
|
||||
|
||||
render_state.surfaces.resize_export_surface(scale, extrect);
|
||||
render_state.render_area = extrect;
|
||||
render_state.render_area_with_margins = extrect;
|
||||
render_state.surfaces.update_render_context(extrect, scale);
|
||||
|
||||
render_state.pending_nodes.push(NodeRenderState {
|
||||
id: *id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
});
|
||||
render_state.render_shape_tree_tile( timestamp, false, true)?;
|
||||
}
|
||||
|
||||
render_state.export_context = None;
|
||||
|
||||
render_state.surfaces.flush_and_submit(target_surface);
|
||||
|
||||
let image = render_state.surfaces.snapshot(target_surface);
|
||||
let data = image
|
||||
.encode(
|
||||
Some(&mut get_gpu_state().context),
|
||||
skia::EncodedImageFormat::PNG,
|
||||
100,
|
||||
)
|
||||
.expect("PNG encode failed");
|
||||
let skia::ISize { width, height } = image.dimensions();
|
||||
|
||||
render_state.focus_mode = saved_focus_mode;
|
||||
render_state.export_context = saved_export_context;
|
||||
render_state.render_area = saved_render_area;
|
||||
render_state.render_area_with_margins = saved_render_area_with_margins;
|
||||
render_state.tile.current = saved_current_tile;
|
||||
render_state.pending_nodes = saved_pending_nodes;
|
||||
render_state.nested_fills = saved_nested_fills;
|
||||
render_state.nested_blurs = saved_nested_blurs;
|
||||
render_state.nested_shadows = saved_nested_shadows;
|
||||
render_state.ignore_nested_blurs = saved_ignore_nested_blurs;
|
||||
render_state.preview_mode = saved_preview_mode;
|
||||
|
||||
let workspace_scale = render_state.get_scale();
|
||||
if let Some(tile) = render_state.tile.current {
|
||||
render_state.update_render_context(tile);
|
||||
} else if !render_state.render_area.is_empty() {
|
||||
render_state
|
||||
.surfaces
|
||||
.update_render_context(render_state.render_area, workspace_scale);
|
||||
}
|
||||
|
||||
Ok((data.as_bytes().to_vec(), width, height))
|
||||
}
|
||||
@ -162,11 +162,7 @@ impl Surfaces {
|
||||
self.dpr = dpr;
|
||||
}
|
||||
|
||||
pub fn draw_tile_atlas_to_backbuffer(
|
||||
&mut self,
|
||||
viewbox: &Viewbox,
|
||||
tile_viewbox: &TileViewbox,
|
||||
) {
|
||||
pub fn draw_tile_atlas_to_backbuffer(&mut self, viewbox: &Viewbox, tile_viewbox: &TileViewbox) {
|
||||
self.tiles.update(viewbox, tile_viewbox);
|
||||
if self.tiles.needs_snapshot() || self.tile_atlas_image.is_none() {
|
||||
self.tile_atlas_image = Some(self.tile_atlas.image_snapshot());
|
||||
@ -218,11 +214,7 @@ impl Surfaces {
|
||||
Ok(general_purpose::STANDARD.encode(encoded_image.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn base64_snapshot_rect(
|
||||
&mut self,
|
||||
id: SurfaceId,
|
||||
irect: IRect,
|
||||
) -> Result<Option<String>> {
|
||||
pub fn base64_snapshot_rect(&mut self, id: SurfaceId, irect: IRect) -> Result<Option<String>> {
|
||||
let surface = self.get_mut(id);
|
||||
if let Some(image) = surface.image_snapshot_with_bounds(irect) {
|
||||
let mut context = surface.direct_context();
|
||||
@ -356,11 +348,7 @@ impl Surfaces {
|
||||
f(self.get_mut(surface_id));
|
||||
}
|
||||
|
||||
pub fn get_render_context_translation(
|
||||
&mut self,
|
||||
render_area: Rect,
|
||||
scale: f32,
|
||||
) -> (f32, f32) {
|
||||
pub fn get_render_context_translation(&mut self, render_area: Rect, scale: f32) -> (f32, f32) {
|
||||
(
|
||||
-render_area.left() + self.margins.width as f32 / scale,
|
||||
-render_area.top() + self.margins.height as f32 / scale,
|
||||
@ -438,12 +426,8 @@ impl Surfaces {
|
||||
|
||||
pub fn copy_backbuffer_to_target(&mut self) {
|
||||
let sampling_options = self.sampling_options;
|
||||
self.backbuffer.draw(
|
||||
self.target.canvas(),
|
||||
(0.0, 0.0),
|
||||
sampling_options,
|
||||
None,
|
||||
);
|
||||
self.backbuffer
|
||||
.draw(self.target.canvas(), (0.0, 0.0), sampling_options, None);
|
||||
}
|
||||
|
||||
pub fn clear_target(&mut self, color: skia::Color) {
|
||||
@ -652,10 +636,7 @@ impl Surfaces {
|
||||
self.clear_all_dirty();
|
||||
}
|
||||
|
||||
pub fn draw_current_tile_into_tile_atlas(
|
||||
&mut self,
|
||||
tile: &Tile,
|
||||
) {
|
||||
pub fn draw_current_tile_into_tile_atlas(&mut self, tile: &Tile) {
|
||||
let rect = TILE_DRAWABLE_RECT;
|
||||
|
||||
let tile_image_opt = self.current.image_snapshot_with_bounds(rect);
|
||||
@ -688,7 +669,6 @@ impl Surfaces {
|
||||
/// so that `render_from_cache` can still show a scaled preview of the old
|
||||
/// content while new tiles are being rendered.
|
||||
pub fn invalidate_tile_cache(&mut self) {
|
||||
println!("invalidate_tile_cache");
|
||||
self.tiles.clear();
|
||||
self.tile_atlas_image = None;
|
||||
}
|
||||
@ -761,9 +741,7 @@ impl TileAtlasTextureProvider {
|
||||
let bottom = top + tile_size as f32;
|
||||
rects.push(Rect::new(left, top, right, bottom));
|
||||
}
|
||||
Self {
|
||||
rects,
|
||||
}
|
||||
Self { rects }
|
||||
}
|
||||
|
||||
pub fn available(&self) -> usize {
|
||||
@ -776,7 +754,10 @@ impl TileAtlasTextureProvider {
|
||||
|
||||
pub fn deallocate(&mut self, rect: Rect) -> bool {
|
||||
println!("Deallocating {:?}", rect);
|
||||
debug_assert!(!self.rects.contains(&rect), "Deallocating an already deallocated rect");
|
||||
debug_assert!(
|
||||
!self.rects.contains(&rect),
|
||||
"Deallocating an already deallocated rect"
|
||||
);
|
||||
self.rects.push(rect);
|
||||
true
|
||||
}
|
||||
@ -831,10 +812,8 @@ impl TileTextureCache {
|
||||
}
|
||||
|
||||
if self.textures.len() != tile_viewbox.visible_rect.len() as usize {
|
||||
self.textures.resize(
|
||||
tile_viewbox.visible_rect.len() as usize,
|
||||
Rect::new_empty(),
|
||||
);
|
||||
self.textures
|
||||
.resize(tile_viewbox.visible_rect.len() as usize, Rect::new_empty());
|
||||
}
|
||||
|
||||
for texture in self.textures.iter_mut() {
|
||||
@ -892,15 +871,12 @@ impl TileTextureCache {
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, tile: Tile) {
|
||||
println!("remove {:?}", tile);
|
||||
self.is_updated = true;
|
||||
self.removed.insert(tile);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
println!("clear");
|
||||
for k in self.grid.keys() {
|
||||
println!("{:?}", k);
|
||||
self.removed.insert(*k);
|
||||
}
|
||||
self.is_updated = true;
|
||||
|
||||
@ -53,7 +53,7 @@ pub use svgraw::*;
|
||||
pub use text::*;
|
||||
pub use transform::*;
|
||||
|
||||
use crate::math::{self, Bounds, Matrix, Point, IRect};
|
||||
use crate::math::{self, Bounds, IRect, Matrix, Point};
|
||||
|
||||
use crate::state::ShapesPoolRef;
|
||||
|
||||
@ -202,7 +202,6 @@ pub struct Shape {
|
||||
pub extrect_cache: RefCell<Option<math::Rect>>,
|
||||
pub svg_transform: Option<Matrix>,
|
||||
pub ignore_constraints: bool,
|
||||
pub tile_rect: TileRect,
|
||||
deleted: bool,
|
||||
}
|
||||
|
||||
@ -306,7 +305,6 @@ impl Shape {
|
||||
extrect_cache: RefCell::new(None),
|
||||
svg_transform: None,
|
||||
ignore_constraints: false,
|
||||
tile_rect: TileRect::new_empty(),
|
||||
deleted: false,
|
||||
}
|
||||
}
|
||||
@ -406,7 +404,6 @@ impl Shape {
|
||||
text.update_layout(self.selrect);
|
||||
text.set_xywh(left, top, self.selrect.width(), self.selrect.height());
|
||||
}
|
||||
self.tile_rect.set_from_tile_bounds(left, top, right, bottom, tiles::TILE_SIZE);
|
||||
}
|
||||
|
||||
pub fn set_masked(&mut self, masked: bool) {
|
||||
@ -431,7 +428,7 @@ impl Shape {
|
||||
pub fn set_transform(&mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) {
|
||||
self.transform = Matrix::new_all(a, c, e, b, d, f, 0.0, 0.0, 1.0);
|
||||
if self.transform_centered.is_none() && self.is_rotated() {
|
||||
let center= self.center();
|
||||
let center = self.center();
|
||||
let mut matrix = self.transform;
|
||||
matrix.post_translate(center);
|
||||
matrix.pre_translate(-center);
|
||||
@ -447,10 +444,6 @@ impl Shape {
|
||||
transform
|
||||
}
|
||||
|
||||
pub fn get_tile_rect(&self, viewbox: &Viewbox) -> TileRect {
|
||||
TileRect::from_scaled(&self.tile_rect, viewbox.get_scale())
|
||||
}
|
||||
|
||||
pub fn set_opacity(&mut self, opacity: f32) {
|
||||
self.opacity = opacity;
|
||||
}
|
||||
|
||||
@ -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::{DesignState, ShapesPoolRef};
|
||||
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>> {
|
||||
|
||||
@ -11,6 +11,7 @@ pub use text_editor::*;
|
||||
pub use ui::UIState;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::globals::get_tile_render_state;
|
||||
use crate::render::FrameType;
|
||||
use crate::shapes::{grid_layout::grid_cell_data, Shape};
|
||||
use crate::uuid::Uuid;
|
||||
@ -21,7 +22,7 @@ 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 current_id: Option<Uuid>,
|
||||
pub current_browser: u8,
|
||||
pub shapes: ShapesPool,
|
||||
@ -30,7 +31,7 @@ pub(crate) struct State {
|
||||
pub loading: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl DesignState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_id: None,
|
||||
@ -171,16 +172,17 @@ impl State {
|
||||
//
|
||||
// Instead, remove the shape from *all* tiles where it was indexed, and
|
||||
// drop cached tiles for those entries.
|
||||
let indexed_tiles: Vec<tiles::Tile> = render_state
|
||||
.tile
|
||||
let tile_render_state = get_tile_render_state();
|
||||
let indexed_tiles: Vec<tiles::Tile> = tile_render_state
|
||||
.tiles
|
||||
.get_tiles_of(shape.id)
|
||||
.map(|t| t.iter().copied().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let tile_render_state = get_tile_render_state();
|
||||
for tile in indexed_tiles {
|
||||
render_state.remove_cached_tile(tile);
|
||||
render_state.tile.tiles.remove_shape_at(tile, shape.id);
|
||||
tile_render_state.tiles.remove_shape_at(tile, shape.id);
|
||||
}
|
||||
|
||||
if let Some(shape_to_delete) = self.shapes.get(&id) {
|
||||
|
||||
@ -1,14 +1,134 @@
|
||||
use crate::render::Surfaces;
|
||||
use crate::globals::{get_design_state, get_render_state, get_tile_render_state};
|
||||
use crate::shapes::Shape;
|
||||
use crate::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
use crate::{render::Surfaces, state::ShapesPoolRef};
|
||||
use skia_safe as skia;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum TileDisplayPhase {
|
||||
Enter = 0,
|
||||
Exit = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TileDisplayItem {
|
||||
pub id: Uuid,
|
||||
pub phase: TileDisplayPhase,
|
||||
}
|
||||
|
||||
impl TileDisplayItem {
|
||||
pub fn enter(id: Uuid) -> Self {
|
||||
Self {
|
||||
id,
|
||||
phase: TileDisplayPhase::Enter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit(id: Uuid) -> Self {
|
||||
Self {
|
||||
id,
|
||||
phase: TileDisplayPhase::Exit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TileDisplayList {
|
||||
output: HashMap<Tile, Vec<TileDisplayItem>>,
|
||||
}
|
||||
|
||||
impl TileDisplayList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
output: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_from(&mut self, root_id: Uuid) {
|
||||
self.output.clear();
|
||||
self.dfs(root_id);
|
||||
}
|
||||
|
||||
pub fn get(&self, tile: Tile) -> Option<&Vec<TileDisplayItem>> {
|
||||
self.output.get(&tile)
|
||||
}
|
||||
|
||||
// Recursive helper that pushes to the output vector
|
||||
fn dfs(
|
||||
&mut self,
|
||||
id: Uuid,
|
||||
) {
|
||||
let design_state = get_design_state();
|
||||
let render_state = get_render_state();
|
||||
let tile_render_state= get_tile_render_state();
|
||||
|
||||
let shapes = &design_state.shapes;
|
||||
if id.is_nil() {
|
||||
let shape = shapes.get(&id).unwrap();
|
||||
for shape_id in shape.children_ids(false) {
|
||||
self.dfs(shape_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let shape = shapes.get(&id).unwrap();
|
||||
let tile_rect = TileRect::from_shape_and_viewbox(shape, render_state.viewbox);
|
||||
let intersection_rect = tile_render_state.viewbox.interest_rect.intersection(&tile_rect);
|
||||
if intersection_rect.is_degenerate() {
|
||||
return;
|
||||
}
|
||||
|
||||
for tile in intersection_rect.iter(true) {
|
||||
let _ = self.output.entry(tile).or_insert_with(Vec::new);
|
||||
self.output.get_mut(&tile).unwrap().push(TileDisplayItem::enter(shape.id));
|
||||
}
|
||||
|
||||
for shape_id in shape.children_ids(false) {
|
||||
self.dfs(shape_id);
|
||||
}
|
||||
|
||||
for tile in intersection_rect.iter(true) {
|
||||
self.output.get_mut(&tile).unwrap().push(TileDisplayItem::exit(shape.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TileRenderState {
|
||||
pub current: Option<Tile>,
|
||||
pub current_had_shapes: bool,
|
||||
pub viewbox: TileViewbox,
|
||||
pub tiles: TileHashMap,
|
||||
pub pending: PendingTiles,
|
||||
pub display_list: TileDisplayList,
|
||||
}
|
||||
|
||||
impl TileRenderState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: None,
|
||||
current_had_shapes: false,
|
||||
tiles: TileHashMap::new(),
|
||||
viewbox: TileViewbox::new(),
|
||||
pending: PendingTiles::new(),
|
||||
display_list: TileDisplayList::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||
pub struct Tile(pub i32, pub i32);
|
||||
|
||||
impl Tile {
|
||||
pub fn from(x: i32, y: i32) -> Self {
|
||||
Tile(x, y)
|
||||
Self(x, y)
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self(0, 0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -32,6 +152,14 @@ impl Tile {
|
||||
}
|
||||
}
|
||||
|
||||
fn itrunc(x: f32) -> f32 {
|
||||
if x < 0.0 {
|
||||
x.floor()
|
||||
} else {
|
||||
x.ceil()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||
pub struct TileRect(pub i32, pub i32, pub i32, pub i32);
|
||||
|
||||
@ -42,11 +170,30 @@ impl TileRect {
|
||||
}
|
||||
|
||||
pub fn from_scaled(other: &TileRect, scale: f32) -> Self {
|
||||
Self(
|
||||
itrunc(other.0 as f32 * scale) as i32,
|
||||
itrunc(other.1 as f32 * scale) as i32,
|
||||
itrunc(other.2 as f32 * scale) as i32,
|
||||
itrunc(other.3 as f32 * scale) as i32,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_shape(shape: &Shape, scale: f32) -> Self {
|
||||
let tile_rect = get_tiles_for_rect(shape.selrect, TILE_SIZE);
|
||||
Self::from_scaled(&tile_rect, scale)
|
||||
}
|
||||
|
||||
pub fn from_shape_and_viewbox(shape: &Shape, viewbox: Viewbox) -> Self {
|
||||
Self::from_shape(shape, viewbox.get_scale())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersection(&self, other: &Self) -> Self {
|
||||
Self (
|
||||
(other.0 as f32 * scale).trunc() as i32,
|
||||
(other.1 as f32 * scale).trunc() as i32,
|
||||
(other.2 as f32 * scale).trunc() as i32,
|
||||
(other.3 as f32 * scale).trunc() as i32,
|
||||
self.left().max(other.left()),
|
||||
self.top().max(other.top()),
|
||||
self.right().min(other.right()),
|
||||
self.bottom().min(other.bottom()),
|
||||
)
|
||||
}
|
||||
|
||||
@ -181,7 +328,7 @@ impl Iterator for TileRectIter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TileViewbox {
|
||||
pub visible_rect: TileRect,
|
||||
pub interest_rect: TileRect,
|
||||
@ -190,6 +337,15 @@ pub struct TileViewbox {
|
||||
}
|
||||
|
||||
impl TileViewbox {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
visible_rect: TileRect::new_empty(),
|
||||
interest_rect: TileRect::new_empty(),
|
||||
interest: 0,
|
||||
center: Tile::new_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_interest(viewbox: &Viewbox, interest: i32) -> Self {
|
||||
Self {
|
||||
visible_rect: get_tiles_for_viewbox(viewbox),
|
||||
@ -265,6 +421,7 @@ pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
||||
}
|
||||
|
||||
// This structure is useful to keep all the shape uuids by shape id.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TileHashMap {
|
||||
grid: HashMap<Tile, HashSet<Uuid>>,
|
||||
index: HashMap<Uuid, HashSet<Tile>>,
|
||||
@ -330,7 +487,7 @@ const VIEWPORT_SPIRAL_DEFAULT_CAPACITY: usize = VIEWPORT_DEFAULT_CAPACITY;
|
||||
/// Cached spiral of tile offsets for a given grid size.
|
||||
///
|
||||
/// Offsets are centered at (0,0) and must be translated by the desired origin/center tile.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TileSpiral {
|
||||
offsets: Vec<Tile>,
|
||||
columns: usize,
|
||||
@ -417,6 +574,7 @@ impl TileSpiral {
|
||||
|
||||
// This structure keeps the list of tiles that are in the pending list, the
|
||||
// ones that are going to be rendered.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingTiles {
|
||||
pub list: Vec<Tile>,
|
||||
pub spiral: TileSpiral,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -108,7 +108,7 @@ pub extern "C" fn set_guides() -> Result<()> {
|
||||
// Guides are drawn on the UI overlay composited onto `Target`. Refresh the
|
||||
// presented frame immediately so removed guides do not linger as stale pixels.
|
||||
with_state!(state, {
|
||||
get_render_state().present_frame(&state.shapes);
|
||||
get_render_state().present_frame();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user