diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index ac111c588..59a072c5e 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -92,14 +92,29 @@ [fills] (h/call internal-module "_clear_shape_fills") (run! (fn [fill] - (let [opacity (:fill-opacity fill) - color (:fill-color fill)] + (let [opacity (or (:fill-opacity fill) 1.0) + color (:fill-color fill) + gradient (:fill-color-gradient fill)] (when ^boolean color (let [rgb (js/parseInt (subs color 1) 16) r (bit-shift-right rgb 16) g (bit-and (bit-shift-right rgb 8) 255) b (bit-and rgb 255)] - (h/call internal-module "_add_shape_solid_fill" r g b opacity))))) + (h/call internal-module "_add_shape_solid_fill" r g b opacity))) + (when (and (some? gradient) (= (:type gradient) :linear)) + (h/call internal-module "_add_shape_linear_fill" + (:start-x gradient) + (:start-y gradient) + (:end-x gradient) + (:end-y gradient) + opacity) + (run! (fn [stop] + (let [rgb (js/parseInt (subs (:color stop) 1) 16) + r (bit-shift-right rgb 16) + g (bit-and (bit-shift-right rgb 8) 255) + b (bit-and rgb 255) + a (:opacity stop)] + (h/call internal-module "_add_shape_fill_stop" r g b a (:offset stop)))) (:stops gradient))))) fills)) (defn- translate-blend-mode diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 76bd2bc74..43d21992e 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -159,6 +159,36 @@ pub extern "C" fn add_shape_solid_fill(r: u8, g: u8, b: u8, a: f32) { } } +#[no_mangle] +pub extern "C" fn add_shape_linear_fill( + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, + opacity: f32, +) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.add_fill(shapes::Fill::new_linear_gradient( + (start_x, start_y), + (end_x, end_y), + opacity, + )) + } +} + +#[no_mangle] +pub extern "C" fn add_shape_fill_stop(r: u8, g: u8, b: u8, a: f32, offset: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + let alpha: u8 = (a * 0xff as f32).floor() as u8; + let color = skia::Color::from_argb(alpha, r, g, b); + shape + .add_gradient_stop(color, offset) + .expect("got no fill or an invalid one"); + } +} + #[no_mangle] pub extern "C" fn clear_shape_fills() { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 3573190df..499922ae3 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -212,7 +212,7 @@ impl RenderState { for fill in shape.fills().rev() { self.drawing_surface .canvas() - .draw_rect(shape.selrect, &fill.to_paint()); + .draw_rect(shape.selrect, &fill.to_paint(&shape.selrect)); } let mut paint = skia::Paint::default(); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index b095c2fa6..8860d2411 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -4,16 +4,7 @@ use uuid::Uuid; #[derive(Debug, Clone, Copy)] pub enum Kind { - None, - Text, - Path, - SVGRaw, - Image, - Circle, Rect, - Bool, - Group, - Frame, } type Color = skia::Color; @@ -41,9 +32,42 @@ impl Matrix { } } +#[derive(Debug, Clone, PartialEq)] +pub struct Gradient { + colors: Vec, + offsets: Vec, + opacity: f32, + start: (f32, f32), + end: (f32, f32), +} + +impl Gradient { + fn to_shader(&self, rect: &math::Rect) -> skia::Shader { + let start = ( + rect.left + self.start.0 * rect.width(), + rect.top + self.start.1 * rect.height(), + ); + let end = ( + rect.left + self.end.0 * rect.width(), + rect.top + self.end.1 * rect.height(), + ); + let shader = skia::shader::Shader::linear_gradient( + (start, end), + self.colors.as_slice(), + self.offsets.as_slice(), + skia::TileMode::Clamp, + None, + None, + ) + .unwrap(); + shader + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Fill { - Solid(Color), // TODO: add more fills here + Solid(Color), + LinearGradient(Gradient), } impl From for Fill { @@ -53,7 +77,17 @@ impl From for Fill { } impl Fill { - pub fn to_paint(&self) -> skia::Paint { + pub fn new_linear_gradient(start: (f32, f32), end: (f32, f32), opacity: f32) -> Self { + Self::LinearGradient(Gradient { + start, + end, + opacity, + colors: vec![], + offsets: vec![], + }) + } + + pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint { match self { Self::Solid(color) => { let mut p = skia::Paint::default(); @@ -63,6 +97,14 @@ impl Fill { p.set_blend_mode(skia::BlendMode::SrcOver); p } + Self::LinearGradient(gradient) => { + let mut p = skia::Paint::default(); + p.set_shader(gradient.to_shader(&rect)); + p.set_alpha((gradient.opacity * 255.) as u8); + p.set_style(skia::PaintStyle::Fill); + p.set_blend_mode(skia::BlendMode::SrcOver); + p + } } } } @@ -95,6 +137,7 @@ impl Into for BlendMode { } #[derive(Debug, Clone)] +#[allow(dead_code)] pub struct Shape { pub id: Uuid, pub children: Vec, @@ -146,6 +189,19 @@ impl Shape { self.fills.clear(); } + pub fn add_gradient_stop(&mut self, color: skia::Color, offset: f32) -> Result<(), String> { + let fill = self.fills.last_mut().ok_or("Shape has no fills")?; + let gradient = match fill { + Fill::LinearGradient(g) => Ok(g), + _ => Err("Active fill is not a gradient"), + }?; + + gradient.colors.push(color); + gradient.offsets.push(offset); + + Ok(()) + } + pub fn set_blend_mode(&mut self, mode: BlendMode) { self.blend_mode = mode; }