diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 27d8b5eda..8585e1e34 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -376,6 +376,14 @@ value (:value blur)] (h/call internal-module "_set_shape_blur" type hidden value))) +(defn set-shape-corners + [corners] + (let [r1 (or (get corners 0) 0) + r2 (or (get corners 1) 0) + r3 (or (get corners 2) 0) + r4 (or (get corners 3) 0)] + (h/call internal-module "_set_shape_corners" r1 r2 r3 r4))) + (def debounce-render-without-cache (fns/debounce render-without-cache 100)) (defn set-view @@ -407,8 +415,13 @@ opacity (dm/get-prop shape :opacity) hidden (dm/get-prop shape :hidden) content (dm/get-prop shape :content) - bool-content (dm/get-prop shape :bool-content) - blur (dm/get-prop shape :blur)] + blur (dm/get-prop shape :blur) + corners (when (some? (dm/get-prop shape :r1)) + [(dm/get-prop shape :r1) + (dm/get-prop shape :r2) + (dm/get-prop shape :r3) + (dm/get-prop shape :r4)]) + bool-content (dm/get-prop shape :bool-content)] (use-shape id) (set-shape-type type) @@ -424,6 +437,7 @@ (set-shape-blur blur)) (when (and (some? content) (= type :path)) (set-shape-path-content content)) (when (some? bool-content) (set-shape-bool-content bool-content)) + (when (some? corners) (set-shape-corners corners)) (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] (recur (inc index) (into pending pending')))) pending))] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 1578cf16f..701768ef2 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,3 +1,5 @@ +use skia_safe as skia; + mod debug; mod math; mod mem; @@ -8,7 +10,6 @@ mod utils; mod view; use crate::shapes::{BoolType, Kind, Path}; -use skia_safe as skia; use crate::state::State; use crate::utils::uuid_from_u32_quartet; @@ -125,7 +126,10 @@ pub unsafe extern "C" fn set_shape_kind_rect() { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); if let Some(shape) = state.current_shape() { - shape.set_kind(Kind::Rect(math::Rect::new_empty())); + match shape.kind() { + Kind::Rect(_, _) => {} + _ => shape.set_kind(Kind::Rect(math::Rect::new_empty(), None)), + } } } @@ -502,6 +506,14 @@ pub extern "C" fn clear_shape_strokes() { } } +#[no_mangle] +pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_corners((r1, r2, r3, r4)) + } +} + fn main() { init_gl(); } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index e9463d3e7..2f29d1f1d 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -21,9 +21,12 @@ use matrix::*; pub use paths::*; pub use strokes::*; +pub type CornerRadius = skia::Point; +pub type Corners = [CornerRadius; 4]; + #[derive(Debug, Clone, PartialEq)] pub enum Kind { - Rect(math::Rect), + Rect(math::Rect, Option), Circle(math::Rect), Path(Path), Bool(BoolType, Path), @@ -54,7 +57,7 @@ impl Shape { Self { id, children: Vec::::new(), - kind: Kind::Rect(math::Rect::new_empty()), + kind: Kind::Rect(math::Rect::new_empty(), None), selrect: math::Rect::new_empty(), transform: Matrix::identity(), rotation: 0., @@ -75,8 +78,8 @@ impl Shape { pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) { self.selrect.set_ltrb(left, top, right, bottom); match self.kind { - Kind::Rect(_) => { - self.kind = Kind::Rect(self.selrect.to_owned()); + Kind::Rect(_, corners) => { + self.kind = Kind::Rect(self.selrect.to_owned(), corners); } Kind::Circle(_) => { self.kind = Kind::Circle(self.selrect.to_owned()); @@ -206,6 +209,27 @@ impl Shape { self.kind = kind; } + pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) { + let (r1, r2, r3, r4) = raw_corners; + let are_straight_corners = r1.abs() <= f32::EPSILON + && r2.abs() <= f32::EPSILON + && r3.abs() <= f32::EPSILON + && r4.abs() <= f32::EPSILON; + + let corners = if are_straight_corners { + None + } else { + Some([ + (r1, r1).into(), + (r2, r2).into(), + (r3, r3).into(), + (r4, r4).into(), + ]) + }; + + self.kind = Kind::Rect(self.selrect, corners); + } + fn to_path_transform(&self) -> Option { match self.kind { Kind::Path(_) | Kind::Bool(_, _) => { diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index dd69102e2..2e7645266 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -171,6 +171,7 @@ impl Fill { p.set_shader(gradient.to_linear_shader(&rect)); p.set_alpha((gradient.opacity * 255.) as u8); p.set_style(skia::PaintStyle::Fill); + p.set_anti_alias(true); p.set_blend_mode(skia::BlendMode::SrcOver); p } @@ -179,6 +180,7 @@ impl Fill { p.set_shader(gradient.to_radial_shader(&rect)); p.set_alpha((gradient.opacity * 255.) as u8); p.set_style(skia::PaintStyle::Fill); + p.set_anti_alias(true); p.set_blend_mode(skia::BlendMode::SrcOver); p } diff --git a/render-wasm/src/shapes/renderable.rs b/render-wasm/src/shapes/renderable.rs index 05346bf28..c6bec4d02 100644 --- a/render-wasm/src/shapes/renderable.rs +++ b/render-wasm/src/shapes/renderable.rs @@ -1,7 +1,7 @@ -use skia_safe as skia; +use skia_safe::{self as skia, RRect}; use uuid::Uuid; -use super::{BlurType, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; +use super::{BlurType, Corners, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; use crate::math::Rect; use crate::render::{ImageStore, Renderable}; @@ -111,9 +111,13 @@ fn render_fill( ); } } - (_, Kind::Rect(rect)) => { + (_, Kind::Rect(rect, None)) => { surface.canvas().draw_rect(rect, &fill.to_paint(&selrect)); } + (_, Kind::Rect(rect, Some(corners))) => { + let rrect = RRect::new_rect_radii(rect, corners); + surface.canvas().draw_rrect(rrect, &fill.to_paint(&selrect)); + } (_, Kind::Circle(rect)) => { surface.canvas().draw_oval(rect, &fill.to_paint(&selrect)); } @@ -148,7 +152,9 @@ fn render_stroke( } } else { match kind { - Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect), + Kind::Rect(rect, corners) => { + draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect, corners) + } Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect), Kind::Path(path) | Kind::Bool(_, path) => { draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform); @@ -157,17 +163,34 @@ fn render_stroke( } } -fn draw_stroke_on_rect(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) { - // Draw the different kind of strokes for a rect is perry straightforward, we just need apply a stroke to: +fn draw_stroke_on_rect( + canvas: &skia::Canvas, + stroke: &Stroke, + rect: &Rect, + selrect: &Rect, + corners: &Option, +) { + // Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to: // - The same rect if it's a center stroke // - A bigger rect if it's an outer stroke // - A smaller rect if it's an outer stroke let stroke_rect = stroke.outer_rect(rect); - canvas.draw_rect(&stroke_rect, &stroke.to_paint(selrect)); + let paint = stroke.to_paint(selrect); + + match corners { + Some(radii) => { + let radii = stroke.outer_corners(radii); + let rrect = RRect::new_rect_radii(stroke_rect, &radii); + canvas.draw_rrect(rrect, &paint); + } + None => { + canvas.draw_rect(&stroke_rect, &paint); + } + } } fn draw_stroke_on_circle(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) { - // Draw the different kind of strokes for an oval is perry straightforward, we just need apply a stroke to: + // Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to: // - The same oval if it's a center stroke // - A bigger oval if it's an outer stroke // - A smaller oval if it's an outer stroke @@ -451,9 +474,13 @@ pub fn draw_image_fill_in_container( // Set the clipping rectangle to the container bounds match kind { - Kind::Rect(_) => { + Kind::Rect(_, None) => { canvas.clip_rect(container, skia::ClipOp::Intersect, true); } + Kind::Rect(_, Some(corners)) => { + let rrect = RRect::new_rect_radii(container, corners); + canvas.clip_rrect(rrect, skia::ClipOp::Intersect, true); + } Kind::Circle(_) => { let mut oval_path = skia::Path::new(); oval_path.add_oval(container, None); @@ -494,7 +521,9 @@ pub fn draw_image_stroke_in_container( ) { let outer_rect = stroke.outer_rect(container); match kind { - Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect), + Kind::Rect(rect, corners) => { + draw_stroke_on_rect(canvas, stroke, rect, &outer_rect, corners) + } Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect), Kind::Path(p) | Kind::Bool(_, p) => { let mut path = p.to_skia_path(); diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index d5ec04ab8..27433f50c 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -2,6 +2,8 @@ use crate::math; use crate::shapes::fills::Fill; use skia_safe as skia; +use super::Corners; + #[derive(Debug, Clone, PartialEq)] pub enum StrokeStyle { Solid, @@ -139,6 +141,20 @@ impl Stroke { } } + pub fn outer_corners(&self, corners: &Corners) -> Corners { + let offset = match self.kind { + StrokeKind::CenterStroke => 0.0, + StrokeKind::InnerStroke => -self.width / 2.0, + StrokeKind::OuterStroke => self.width / 2.0, + }; + + let mut outer = corners.clone(); + for corner in outer.iter_mut() { + corner.offset((offset, offset)) + } + outer + } + pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint { let mut paint = self.fill.to_paint(rect); paint.set_style(skia::PaintStyle::Stroke);