diff --git a/common/src/app/common/types/shape/impl.cljc b/common/src/app/common/types/shape/impl.cljc index 45a0f9e13..ed798d1d5 100644 --- a/common/src/app/common/types/shape/impl.cljc +++ b/common/src/app/common/types/shape/impl.cljc @@ -19,6 +19,7 @@ (defonce wasm-set-shape-selrect (constantly nil)) (defonce wasm-set-shape-transform (constantly nil)) (defonce wasm-set-shape-rotation (constantly nil)) +(defonce wasm-set-shape-fills (constantly nil)) (defonce wasm-set-shapes (constantly nil)) (cr/defrecord Shape [id name type x y width height rotation selrect points @@ -118,6 +119,7 @@ :rotation (wasm-set-shape-rotation v) :transform (wasm-set-shape-transform v) :shapes (wasm-set-shapes v) + :fills (wasm-set-shape-fills v) nil)) (let [delegate (.-delegate ^ShapeProxy coll) delegate' (assoc delegate k v)] diff --git a/frontend/src/app/render_wasm.cljs b/frontend/src/app/render_wasm.cljs index f954140ff..27231af09 100644 --- a/frontend/src/app/render_wasm.cljs +++ b/frontend/src/app/render_wasm.cljs @@ -7,6 +7,7 @@ (ns app.render-wasm "A WASM based render API" (:require + [app.common.colors :as cc] [app.common.data.macros :as dm] [app.common.types.shape.impl :as ctsi] [app.common.uuid :as uuid] @@ -65,6 +66,14 @@ (let [buffer (uuid/uuid->u32 id)] (._add_child_shape ^js internal-module (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))) +(defn set-shape-fills + [fills] + (._clear_shape_fills ^js internal-module) + (doseq [fill (filter #(contains? % :fill-color) fills)] + (let [a (:fill-opacity fill) + [r g b] (cc/hex->rgb (:fill-color fill))] + (._add_shape_solid_fill ^js internal-module r g b a)))) + (defn set-objects [objects] (let [shapes (into [] xform (vals objects)) @@ -76,11 +85,13 @@ selrect (dm/get-prop shape :selrect) rotation (dm/get-prop shape :rotation) transform (dm/get-prop shape :transform) + fills (dm/get-prop shape :fills) children (dm/get-prop shape :shapes)] (use-shape id) (set-shape-selrect selrect) (set-shape-rotation rotation) (set-shape-transform transform) + (set-shape-fills fills) (set-shapes children) (recur (inc index))))))) @@ -143,4 +154,5 @@ (set! app.common.types.shape.impl/wasm-set-shape-selrect set-shape-selrect) (set! app.common.types.shape.impl/wasm-set-shape-transform set-shape-transform) (set! app.common.types.shape.impl/wasm-set-shape-rotation set-shape-rotation) +(set! app.common.types.shape.impl/wasm-set-shape-fills set-shape-fills) (set! app.common.types.shape.impl/wasm-set-shapes set-shapes) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 5a427f05f..2d6271aa4 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -32,15 +32,6 @@ pub unsafe extern "C" fn resize_surface(width: i32, height: i32) { state.set_surface(surface); } -/// Draws a rect at the specified coordinates with the give ncolor -/// # Safety -#[no_mangle] -pub unsafe extern "C" fn draw_rect(x1: f32, y1: f32, x2: f32, y2: f32) { - let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); - let r = skia::Rect::new(x1, y1, x2, y2); - render::render_rect(&mut state.render_state.surface, r, skia::Color::RED); -} - #[no_mangle] pub unsafe extern "C" fn draw_all_shapes(zoom: f32, pan_x: f32, pan_y: f32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); @@ -49,7 +40,7 @@ pub unsafe extern "C" fn draw_all_shapes(zoom: f32, pan_x: f32, pan_y: f32) { scale(zoom, zoom); translate(pan_x, pan_y); - render::render_shape(state, Uuid::nil()); + render::render_shape_tree(state, Uuid::nil()); flush(); } @@ -143,16 +134,34 @@ pub extern "C" fn add_child_shape(a: u32, b: u32, c: u32, d: u32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); let id = uuid_from_u32_quartet(a, b, c, d); if let Some(shape) = state.current_shape.as_deref_mut() { - shape.shapes.push(id); - } + shape.shapes.push(id); + } } #[no_mangle] pub extern "C" fn clear_child_shapes() { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); if let Some(shape) = state.current_shape.as_deref_mut() { - shape.shapes.clear(); - } + shape.shapes.clear(); + } +} + +#[no_mangle] +pub extern "C" fn add_shape_solid_fill(r: u8, g: u8, b: u8, a: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape.as_deref_mut() { + let alpha: u8 = (a * 0xff as f32).floor() as u8; + let color = skia::Color::from_argb(alpha, r, g, b); + shape.add_fill(shapes::Fill::from(color)); + } +} + +#[no_mangle] +pub extern "C" fn clear_shape_fills() { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape.as_deref_mut() { + shape.clear_fills(); + } } fn main() { diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 833b05c42..db98861c2 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -2,6 +2,7 @@ use skia_safe as skia; use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext}; use uuid::Uuid; +use crate::shapes::Shape; use crate::state::State; extern "C" { @@ -74,16 +75,24 @@ pub(crate) fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) .unwrap() } -pub(crate) fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color: skia::Color) { - let mut paint = skia::Paint::default(); - paint.set_style(skia::PaintStyle::Fill); - paint.set_color(color); - paint.set_anti_alias(true); - surface.canvas().draw_rect(rect, &paint); +pub(crate) fn render_shape_tree(state: &mut State, id: Uuid) { + let shape = state.shapes.get(&id).unwrap(); + + // This is needed so the next non-children shape does not carry this shape's transform + state.render_state.surface.canvas().save(); + + render_single_shape(&mut state.render_state.surface, shape); + + // draw all the children shapes + let shape_ids = shape.shapes.clone(); + for shape_id in shape_ids { + render_shape_tree(state, shape_id); + } + + state.render_state.surface.canvas().restore(); } -pub(crate) fn render_shape(state: &mut State, id: Uuid) { - let shape = state.shapes.get(&id).unwrap(); +fn render_single_shape(surface: &mut skia::Surface, shape: &Shape) { let r = skia::Rect::new( shape.selrect.x1, shape.selrect.y1, @@ -91,35 +100,32 @@ pub(crate) fn render_shape(state: &mut State, id: Uuid) { shape.selrect.y2, ); - // TODO: check if this save and restore are really neaded or not - // state.render_state.surface.canvas().save(); - // Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc let mut matrix = skia::Matrix::new_identity(); let (translate_x, translate_y) = shape.translation(); - let (scale_x, scale_y) = shape.scale(); + let (scale_x, scale_y) = shape.scale(); let (skew_x, skew_y) = shape.skew(); - matrix.set_all(scale_x, skew_x, translate_x, skew_y, scale_y, translate_y, 0., 0., 1.); + matrix.set_all( + scale_x, + skew_x, + translate_x, + skew_y, + scale_y, + translate_y, + 0., + 0., + 1., + ); let mut center = r.center(); matrix.post_translate(center); center.negate(); matrix.pre_translate(center); - state.render_state.surface.canvas().concat(&matrix); + surface.canvas().concat(&matrix); - let mut color = skia::Color::RED;; - if skew_x != 0. { - color = skia::Color::BLACK; - } - render_rect(&mut state.render_state.surface, r, color); - - // state.render_state.surface.canvas().restore(); - - let shape_ids = shape.shapes.clone(); - - for shape_id in shape_ids { - render_shape(state, shape_id); + for fill in shape.fills() { + surface.canvas().draw_rect(r, &fill.to_paint()); } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 38746d491..0e6f49e19 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1,3 +1,4 @@ +use skia_safe as skia; use uuid::Uuid; #[derive(Debug, Clone, Copy)] @@ -27,6 +28,8 @@ pub struct Rect { pub y2: f32, } +type Color = skia::Color; + #[derive(Debug, Clone, Copy)] pub struct Matrix { pub a: f32, @@ -50,25 +53,54 @@ impl Matrix { } } +#[derive(Debug, Clone, PartialEq)] +pub enum Fill { + Solid(Color), // TODO: add more fills here +} + +impl From for Fill { + fn from(value: Color) -> Self { + Self::Solid(value) + } +} + +impl Fill { + pub fn to_paint(&self) -> skia::Paint { + match self { + Self::Solid(color) => { + let mut p = skia::Paint::default(); + p.set_color(*color); + p.set_style(skia::PaintStyle::Fill); + p.set_anti_alias(true); + // TODO: get proper blend mode. See https://tree.taiga.io/project/penpot/task/9275 + p.set_blend_mode(skia::BlendMode::DstOver); + p + } + } + } +} + #[derive(Debug, Clone)] pub struct Shape { pub id: Uuid, - pub shapes: Vec::, + pub shapes: Vec, pub kind: Kind, pub selrect: Rect, pub transform: Matrix, pub rotation: f32, + fills: Vec, } impl Shape { pub fn new(id: Uuid) -> Self { Self { id, - shapes: Vec::::new(), + shapes: vec![], kind: Kind::Rect, selrect: Rect::default(), transform: Matrix::identity(), rotation: 0., + fills: vec![], } } @@ -83,4 +115,16 @@ impl Shape { pub fn skew(&self) -> (f32, f32) { (self.transform.c, self.transform.b) } + + pub fn fills(&self) -> std::slice::Iter { + self.fills.iter() + } + + pub fn add_fill(&mut self, f: Fill) { + self.fills.push(f) + } + + pub fn clear_fills(&mut self) { + self.fills.clear(); + } } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 3fdb427d7..b52387f00 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -1,6 +1,5 @@ use skia_safe as skia; use std::collections::HashMap; -use std::vec::Vec; use uuid::Uuid; use crate::render::RenderState; @@ -16,7 +15,6 @@ pub(crate) struct State<'a> { pub current_id: Option, pub current_shape: Option<&'a mut Shape>, pub shapes: HashMap, - pub display_list: Vec, } impl<'a> State<'a> { @@ -26,7 +24,6 @@ impl<'a> State<'a> { current_id: None, current_shape: None, shapes: HashMap::with_capacity(capacity), - display_list: Vec::with_capacity(capacity), } }