♻️ Refactor to make it more ECS friendly

This commit is contained in:
AzazelN28 2025-01-02 15:49:43 +01:00
parent 79df616108
commit fb4e92d0e8
14 changed files with 835 additions and 980 deletions

View file

@ -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(),