mirror of
https://github.com/penpot/penpot.git
synced 2025-07-15 05:05:19 +02:00
♻️ Refactor to make it more ECS friendly
This commit is contained in:
parent
79df616108
commit
fb4e92d0e8
14 changed files with 835 additions and 980 deletions
|
@ -3,65 +3,40 @@ use skia_safe as skia;
|
|||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::math;
|
||||
use crate::view::Viewbox;
|
||||
|
||||
mod blend;
|
||||
mod cache;
|
||||
mod debug;
|
||||
mod fills;
|
||||
mod gpu_state;
|
||||
mod images;
|
||||
mod options;
|
||||
mod strokes;
|
||||
|
||||
use crate::shapes::{Kind, Shape};
|
||||
use cache::CachedSurfaceImage;
|
||||
use gpu_state::GpuState;
|
||||
use options::RenderOptions;
|
||||
|
||||
pub use blend::BlendMode;
|
||||
pub use images::*;
|
||||
|
||||
pub trait Renderable {
|
||||
fn render(
|
||||
&self,
|
||||
surface: &mut skia::Surface,
|
||||
images: &ImageStore,
|
||||
scale: f32,
|
||||
font_provider: &skia::textlayout::TypefaceFontProvider,
|
||||
) -> Result<(), String>;
|
||||
fn blend_mode(&self) -> BlendMode;
|
||||
fn opacity(&self) -> f32;
|
||||
fn bounds(&self) -> math::Rect;
|
||||
fn hidden(&self) -> bool;
|
||||
fn clip(&self) -> bool;
|
||||
fn children_ids(&self) -> Vec<Uuid>;
|
||||
fn image_filter(&self, scale: f32) -> Option<skia::ImageFilter>;
|
||||
fn is_recursive(&self) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) struct CachedSurfaceImage {
|
||||
pub image: Image,
|
||||
pub viewbox: Viewbox,
|
||||
has_all_shapes: bool,
|
||||
}
|
||||
|
||||
impl CachedSurfaceImage {
|
||||
fn is_dirty_for_zooming(&mut self, viewbox: &Viewbox) -> bool {
|
||||
!self.has_all_shapes && !self.viewbox.area.contains(viewbox.area)
|
||||
}
|
||||
|
||||
fn is_dirty_for_panning(&mut self, _viewbox: &Viewbox) -> bool {
|
||||
!self.has_all_shapes
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RenderState {
|
||||
gpu_state: GpuState,
|
||||
options: RenderOptions,
|
||||
|
||||
// TODO: Probably we're going to need
|
||||
// a surface stack like the one used
|
||||
// by SVG: https://www.w3.org/TR/SVG2/render.html
|
||||
pub final_surface: skia::Surface,
|
||||
pub drawing_surface: skia::Surface,
|
||||
pub debug_surface: skia::Surface,
|
||||
pub font_provider: skia::textlayout::TypefaceFontProvider,
|
||||
pub cached_surface_image: Option<CachedSurfaceImage>,
|
||||
options: RenderOptions,
|
||||
pub viewbox: Viewbox,
|
||||
images: ImageStore,
|
||||
background_color: skia::Color,
|
||||
pub images: ImageStore,
|
||||
pub background_color: skia::Color,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
|
@ -179,11 +154,46 @@ impl RenderState {
|
|||
.reset_matrix();
|
||||
}
|
||||
|
||||
pub fn render_single_element(&mut self, element: &impl Renderable) {
|
||||
let scale = self.viewbox.zoom * self.options.dpr();
|
||||
element
|
||||
.render(&mut self.drawing_surface, &self.images, scale, &self.font_provider)
|
||||
.unwrap();
|
||||
pub fn render_shape(&mut self, shape: &mut Shape) {
|
||||
let transform = shape.transform.to_skia_matrix();
|
||||
|
||||
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
||||
let center = shape.bounds().center();
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
matrix.pre_translate(center);
|
||||
matrix.pre_concat(&transform);
|
||||
matrix.pre_translate(-center);
|
||||
|
||||
self.drawing_surface.canvas().concat(&matrix);
|
||||
|
||||
match &shape.kind {
|
||||
Kind::SVGRaw(sr) => {
|
||||
if let Some(svg) = shape.svg.as_ref() {
|
||||
svg.render(self.drawing_surface.canvas())
|
||||
} else {
|
||||
let font_manager = skia::FontMgr::from(self.font_provider.clone());
|
||||
let dom_result = skia::svg::Dom::from_str(sr.content.to_string(), font_manager);
|
||||
match dom_result {
|
||||
Ok(dom) => {
|
||||
dom.render(self.drawing_surface.canvas());
|
||||
shape.set_svg(dom);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing SVG. Error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for fill in shape.fills().rev() {
|
||||
fills::render(self, shape, fill);
|
||||
}
|
||||
|
||||
for stroke in shape.strokes().rev() {
|
||||
strokes::render(self, shape, stroke);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.drawing_surface.draw(
|
||||
&mut self.final_surface.canvas(),
|
||||
|
@ -197,7 +207,7 @@ impl RenderState {
|
|||
.clear(skia::Color::TRANSPARENT);
|
||||
}
|
||||
|
||||
pub fn zoom(&mut self, tree: &HashMap<Uuid, impl Renderable>) -> Result<(), String> {
|
||||
pub fn zoom(&mut self, tree: &HashMap<Uuid, Shape>) -> Result<(), String> {
|
||||
if let Some(cached_surface_image) = self.cached_surface_image.as_mut() {
|
||||
let is_dirty = cached_surface_image.is_dirty_for_zooming(&self.viewbox);
|
||||
if is_dirty {
|
||||
|
@ -210,7 +220,7 @@ impl RenderState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pan(&mut self, tree: &HashMap<Uuid, impl Renderable>) -> Result<(), String> {
|
||||
pub fn pan(&mut self, tree: &HashMap<Uuid, Shape>) -> Result<(), String> {
|
||||
if let Some(cached_surface_image) = self.cached_surface_image.as_mut() {
|
||||
let is_dirty = cached_surface_image.is_dirty_for_panning(&self.viewbox);
|
||||
if is_dirty {
|
||||
|
@ -223,11 +233,7 @@ impl RenderState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_all(
|
||||
&mut self,
|
||||
tree: &HashMap<Uuid, impl Renderable>,
|
||||
generate_cached_surface_image: bool,
|
||||
) {
|
||||
pub fn render_all(&mut self, tree: &HashMap<Uuid, Shape>, generate_cached_surface_image: bool) {
|
||||
self.reset_canvas();
|
||||
self.scale(
|
||||
self.viewbox.zoom * self.options.dpr(),
|
||||
|
@ -288,66 +294,24 @@ impl RenderState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_debug_view(&mut self) {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_style(skia::PaintStyle::Stroke);
|
||||
paint.set_color(skia::Color::from_argb(255, 255, 0, 255));
|
||||
paint.set_stroke_width(1.);
|
||||
|
||||
let mut scaled_rect = self.viewbox.area.clone();
|
||||
let x = 100. + scaled_rect.x() * 0.2;
|
||||
let y = 100. + scaled_rect.y() * 0.2;
|
||||
let width = scaled_rect.width() * 0.2;
|
||||
let height = scaled_rect.height() * 0.2;
|
||||
scaled_rect.set_xywh(x, y, width, height);
|
||||
|
||||
self.debug_surface.canvas().draw_rect(scaled_rect, &paint);
|
||||
}
|
||||
|
||||
fn render_debug_element(&mut self, element: &impl Renderable, intersected: bool) {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_style(skia::PaintStyle::Stroke);
|
||||
paint.set_color(if intersected {
|
||||
skia::Color::from_argb(255, 255, 255, 0)
|
||||
} else {
|
||||
skia::Color::from_argb(255, 0, 255, 255)
|
||||
});
|
||||
paint.set_stroke_width(1.);
|
||||
|
||||
let mut scaled_rect = element.bounds();
|
||||
let x = 100. + scaled_rect.x() * 0.2;
|
||||
let y = 100. + scaled_rect.y() * 0.2;
|
||||
let width = scaled_rect.width() * 0.2;
|
||||
let height = scaled_rect.height() * 0.2;
|
||||
scaled_rect.set_xywh(x, y, width, height);
|
||||
|
||||
self.debug_surface.canvas().draw_rect(scaled_rect, &paint);
|
||||
}
|
||||
|
||||
fn render_debug(&mut self) {
|
||||
let paint = skia::Paint::default();
|
||||
self.render_debug_view();
|
||||
self.debug_surface.draw(
|
||||
&mut self.final_surface.canvas(),
|
||||
(0.0, 0.0),
|
||||
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest),
|
||||
Some(&paint),
|
||||
);
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
// Returns a boolean indicating if the viewbox contains the rendered shapes
|
||||
fn render_shape_tree(&mut self, root_id: &Uuid, tree: &HashMap<Uuid, impl Renderable>) -> bool {
|
||||
fn render_shape_tree(&mut self, root_id: &Uuid, tree: &HashMap<Uuid, Shape>) -> bool {
|
||||
if let Some(element) = tree.get(&root_id) {
|
||||
let mut is_complete = self.viewbox.area.contains(element.bounds());
|
||||
|
||||
if !root_id.is_nil() {
|
||||
if !element.bounds().intersects(self.viewbox.area) || element.hidden() {
|
||||
self.render_debug_element(element, false);
|
||||
// TODO: This means that not all the shapes are rendered so we
|
||||
// need to call a render_all on the zoom out.
|
||||
return is_complete; // TODO return is_complete or return false??
|
||||
} else {
|
||||
self.render_debug_element(element, true);
|
||||
if !root_id.is_nil() {
|
||||
if !element.bounds().intersects(self.viewbox.area) || element.hidden() {
|
||||
debug::render_debug_element(self, element, false);
|
||||
// TODO: This means that not all the shapes are rendered so we
|
||||
// need to call a render_all on the zoom out.
|
||||
return is_complete; // TODO return is_complete or return false??
|
||||
} else {
|
||||
debug::render_debug_element(self, element, true);
|
||||
}
|
||||
}
|
||||
|
||||
let mut paint = skia::Paint::default();
|
||||
|
@ -364,7 +328,7 @@ impl RenderState {
|
|||
self.drawing_surface.canvas().save();
|
||||
|
||||
if !root_id.is_nil() {
|
||||
self.render_single_element(element);
|
||||
self.render_shape(&mut element.clone());
|
||||
if element.clip() {
|
||||
self.drawing_surface.canvas().clip_rect(
|
||||
element.bounds(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue