diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 662838e08..cdb1aeeac 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -12,6 +12,7 @@ [app.common.geom.matrix :as gmt] [app.common.math :as mth] [app.common.svg.path :as path] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cf] [app.main.refs :as refs] @@ -120,23 +121,12 @@ :image 8)) (defn set-shape-type - [type {:keys [masked]}] - (h/call internal-module "_set_shape_type" (translate-shape-type type)) - (cond - (= type :circle) - (h/call internal-module "_set_shape_kind_circle") + [type] + (h/call internal-module "_set_shape_type" (translate-shape-type type))) - (= type :path) - (h/call internal-module "_set_shape_kind_path") - - (= type :bool) - (h/call internal-module "_set_shape_kind_bool") - - (= type :group) - (h/call internal-module "_set_shape_kind_group" masked) - - :else - (h/call internal-module "_set_shape_kind_rect"))) +(defn set-masked + [masked] + (h/call internal-module "_set_shape_masked_group" masked)) (defn set-shape-selrect [selrect] @@ -509,6 +499,139 @@ (h/call internal-module "_set_shape_corners" r1 r2 r3 r4))) +(defn translate-layout-flex-dir + [flex-dir] + (case flex-dir + :row 0 + :column 1)) + +(defn translate-layout-align-items + [align-items] + (case align-items + :start 0 + :end 1 + :center 2 + :stretch 3)) + +(defn translate-layout-align-content + [align-content] + (case align-content + :start 0 + :end 1 + :center 2 + :space-between 3 + :space-around 4 + :space-evenly 5 + :stretch 6)) + +(defn translate-layout-justify-items + [justify-items] + (case justify-items + :start 0 + :end 1 + :center 2 + :stretch 3)) + +(defn translate-layout-justify-content + [justify-content] + (case justify-content + :start 0 + :end 1 + :center 2 + :space-between 3 + :space-around 4 + :space-evenly 5 + :stretch 6)) + +(defn translate-layout-wrap-type + [wrap-type] + (case wrap-type + :wrap 0 + :nowrap 1)) + +(defn set-flex-layout + [shape] + (let [dir (-> (or (dm/get-prop shape :layout-flex-dir) :row) translate-layout-flex-dir) + gap (dm/get-prop shape :layout-gap) + row-gap (or (dm/get-prop gap :row-gap) 0) + column-gap (or (dm/get-prop gap :column-gap) 0) + + align-items (-> (or (dm/get-prop shape :layout-align-items) :start) translate-layout-align-items) + align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) translate-layout-align-content) + justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) translate-layout-justify-items) + justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) translate-layout-justify-content) + wrap-type (-> (or (dm/get-prop shape :layout-wrap-type) :nowrap) translate-layout-wrap-type) + + padding (dm/get-prop shape :layout-padding) + padding-top (or (dm/get-prop padding :p1) 0) + padding-right (or (dm/get-prop padding :p2) 0) + padding-bottom (or (dm/get-prop padding :p3) 0) + padding-left (or (dm/get-prop padding :p4) 0)] + (h/call internal-module + "_set_flex_layout_data" + dir + row-gap + column-gap + align-items + align-content + justify-items + justify-content + wrap-type + padding-top + padding-right + padding-bottom + padding-left))) + +(defn set-grid-layout + [_shape]) + +(defn translate-layout-sizing + [value] + (case value + :fill 0 + :fix 1 + :auto 2)) + +(defn set-layout-child + [shape] + (let [margins (dm/get-prop shape :layout-item-margin) + margin-top (or (dm/get-prop margins :m1) 0) + margin-right (or (dm/get-prop margins :m2) 0) + margin-bottom (or (dm/get-prop margins :m3) 0) + margin-left (or (dm/get-prop margins :m4) 0) + + h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :auto) translate-layout-sizing) + v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :auto) translate-layout-sizing) + + max-h (dm/get-prop shape :layout-item-max-h) + has-max-h (some? max-h) + min-h (dm/get-prop shape :layout-item-min-h) + has-min-h (some? min-h) + max-w (dm/get-prop shape :layout-item-max-w) + has-max-w (some? max-w) + min-w (dm/get-prop shape :layout-item-min-w) + has-min-w (some? min-w) + is-absolute (boolean (dm/get-prop shape :layout-item-absolute)) + z-index (-> (dm/get-prop shape :layout-item-z-index) (or 0))] + (h/call internal-module + "_set_layout_child_data" + margin-top + margin-right + margin-bottom + margin-left + h-sizing + v-sizing + has-max-h + (or max-h 0) + has-min-h + (or min-h 0) + has-max-w + (or max-w 0) + has-min-w + (or min-w 0) + is-absolute + z-index))) + (defn- translate-shadow-style [style] (case style @@ -581,7 +704,7 @@ shadows (dm/get-prop shape :shadow)] (use-shape id) - (set-shape-type type {:masked masked}) + (set-shape-type type) (set-shape-clip-content clip-content) (set-shape-selrect selrect) (set-constraints-h constraint-h) @@ -592,6 +715,8 @@ (set-shape-opacity opacity) (set-shape-hidden hidden) (set-shape-children children) + (when (and (= type :group) masked) + (set-masked masked)) (when (some? blur) (set-shape-blur blur)) (when (and (some? content) (= type :path)) @@ -602,6 +727,16 @@ (when (some? bool-content) (set-shape-bool-content bool-content)) (when (some? corners) (set-shape-corners corners)) (when (some? shadows) (set-shape-shadows shadows)) + + (when (ctl/any-layout-immediate-child? objects shape) + (set-layout-child shape)) + + (when (ctl/flex-layout? shape) + (set-flex-layout shape)) + + (when (ctl/grid-layout? shape) + (set-grid-layout shape)) + (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] (recur (inc index) (into pending pending')))) pending))] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index f5debea17..dd16951d2 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -110,35 +110,37 @@ [self k v] (when ^boolean shape/*wasm-sync* (api/use-shape (:id self)) - (let [masked (:masked-group self)] - (case k - :type (api/set-shape-type v {:masked masked}) - :bool-type (api/set-shape-bool-type v) - :bool-content (api/set-shape-bool-content v) - :selrect (api/set-shape-selrect v) - :show-content (if (= (:type self) :frame) - (api/set-shape-clip-content (not v)) - (api/set-shape-clip-content false)) - :rotation (api/set-shape-rotation v) - :transform (api/set-shape-transform v) - :fills (api/set-shape-fills v) - :strokes (api/set-shape-strokes v) - :blend-mode (api/set-shape-blend-mode v) - :opacity (api/set-shape-opacity v) - :hidden (api/set-shape-hidden v) - :shapes (api/set-shape-children v) - :blur (api/set-shape-blur v) - :svg-attrs (when (= (:type self) :path) - (api/set-shape-path-attrs v)) - :constraints-h (api/set-constraints-h v) - :constraints-v (api/set-constraints-v v) - :content (cond - (= (:type self) :path) - (api/set-shape-path-content v) + (case k + :type (api/set-shape-type v) + :bool-type (api/set-shape-bool-type v) + :bool-content (api/set-shape-bool-content v) + :selrect (api/set-shape-selrect v) + :show-content (if (= (:type self) :frame) + (api/set-shape-clip-content (not v)) + (api/set-shape-clip-content false)) + :rotation (api/set-shape-rotation v) + :transform (api/set-shape-transform v) + :fills (api/set-shape-fills v) + :strokes (api/set-shape-strokes v) + :blend-mode (api/set-shape-blend-mode v) + :opacity (api/set-shape-opacity v) + :hidden (api/set-shape-hidden v) + :shapes (api/set-shape-children v) + :blur (api/set-shape-blur v) + :constraints-h (api/set-constraints-h v) + :constraints-v (api/set-constraints-v v) - (= (:type self) :svg-raw) - (api/set-shape-svg-raw-content (api/get-static-markup self))) - nil)) + :svg-attrs (when (= (:type self) :path) + (api/set-shape-path-attrs v)) + :masked-group (when (and (= (:type self) :group) (:masked-group self)) + (api/set-masked (:masked-group self))) + :content (cond + (= (:type self) :path) + (api/set-shape-path-content v) + + (= (:type self) :svg-raw) + (api/set-shape-svg-raw-content (api/get-static-markup self))) + nil) ;; when something synced with wasm ;; is modified, we need to request ;; a new render. diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 7d3e463ba..b9999245b 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -142,3 +142,75 @@ Shadow styles are serialized as `u8`: | 0 | Drop Shadow | | 1 | Inner Shadow | | \_ | Drop Shadow | + +## Layout - Direction + +| Value | Field | +| ----- | -------| +| 0 | Row | +| 1 | Column | +| \_ | error | + +## Layout - Align Items + +| Value | Field | +| ----- | --------| +| 0 | Start | +| 1 | End | +| 2 | Center | +| 3 | Stretch | +| \_ | error | + +## Layout - Align Content + +| Value | Field | +| ----- | ------------- | +| 0 | Start | +| 1 | End | +| 2 | Center | +| 3 | Space between | +| 4 | Space around | +| 5 | Space evenly | +| 6 | Stretch | +| \_ | error | + +## Layout - Justify items + +| Value | Field | +| ----- | --------| +| 0 | Start | +| 1 | End | +| 2 | Center | +| 3 | Stretch | +| \_ | error | + +## Layout - Justify content + + +| Value | Field | +| ----- | ------------- | +| 0 | Start | +| 1 | End | +| 2 | Center | +| 3 | Space between | +| 4 | Space around | +| 5 | Space evenly | +| 6 | Stretch | +| \_ | error | + +## Layout - Wrap type + +| Value | Field | +| ----- | ------- | +| 0 | Wrap | +| 1 | No Wrap | +| \_ | error | + +## Layout - Sizing + +| Value | Field | +| ----- | ------| +| 0 | Fill | +| 1 | Fix | +| 2 | Auto | +| \_ | error | diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 8a0799cce..a40c867d6 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,4 +1,3 @@ -use skia::Rect; use skia_safe as skia; mod debug; @@ -11,7 +10,7 @@ mod utils; mod view; use crate::mem::SerializableResult; -use crate::shapes::{BoolType, ConstraintH, ConstraintV, Group, Kind, Path, TransformEntry, Type}; +use crate::shapes::{BoolType, ConstraintH, ConstraintV, TransformEntry, Type}; use crate::state::State; use crate::utils::uuid_from_u32_quartet; @@ -131,50 +130,10 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) { } #[no_mangle] -pub extern "C" fn set_shape_kind_group(masked: bool) { +pub extern "C" fn set_shape_masked_group(masked: bool) { let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); if let Some(shape) = state.current_shape() { - shape.set_kind(Kind::Group(Group::new(masked))); - } -} - -#[no_mangle] -pub extern "C" fn set_shape_kind_circle() { - let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); - - if let Some(shape) = state.current_shape() { - shape.set_kind(Kind::Circle(Rect::new_empty())); - } -} - -#[no_mangle] -pub 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() { - match shape.kind() { - Kind::Rect(_, _) => {} - _ => shape.set_kind(Kind::Rect(Rect::new_empty(), None)), - } - } -} - -#[no_mangle] -pub extern "C" fn set_shape_kind_path() { - let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); - if let Some(shape) = state.current_shape() { - shape.set_kind(Kind::Path(Path::default())); - } -} - -#[no_mangle] -pub extern "C" fn set_shape_kind_bool() { - let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); - if let Some(shape) = state.current_shape() { - match shape.kind() { - Kind::Bool(_, _) => {} - _ => shape.set_kind(Kind::Bool(BoolType::default(), Path::default())), - } + shape.set_masked(masked); } } @@ -702,6 +661,101 @@ pub extern "C" fn clear_shape_shadows() { } } +#[no_mangle] +pub extern "C" fn set_flex_layout_data( + dir: u8, + row_gap: f32, + column_gap: f32, + align_items: u8, + align_content: u8, + justify_items: u8, + justify_content: u8, + wrap_type: u8, + padding_top: f32, + padding_right: f32, + padding_bottom: f32, + padding_left: f32, +) { + let dir = shapes::Direction::from_u8(dir); + let align_items = shapes::AlignItems::from_u8(align_items); + let align_content = shapes::AlignContent::from_u8(align_content); + let justify_items = shapes::JustifyItems::from_u8(justify_items); + let justify_content = shapes::JustifyContent::from_u8(justify_content); + let wrap_type = shapes::WrapType::from_u8(wrap_type); + + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_flex_layout_data( + dir, + row_gap, + column_gap, + align_items, + align_content, + justify_items, + justify_content, + wrap_type, + padding_top, + padding_right, + padding_bottom, + padding_left, + ); + } +} + +#[no_mangle] +pub extern "C" fn set_layout_child_data( + margin_top: f32, + margin_right: f32, + margin_bottom: f32, + margin_left: f32, + h_sizing: u8, + v_sizing: u8, + has_max_h: bool, + max_h: f32, + has_min_h: bool, + min_h: f32, + has_max_w: bool, + max_w: f32, + has_min_w: bool, + min_w: f32, + is_absolute: bool, + z_index: i32, +) { + let h_sizing = shapes::Sizing::from_u8(h_sizing); + let v_sizing = shapes::Sizing::from_u8(v_sizing); + let max_h = if has_max_h { Some(max_h) } else { None }; + let min_h = if has_min_h { Some(min_h) } else { None }; + let max_w = if has_max_w { Some(max_w) } else { None }; + let min_w = if has_min_w { Some(min_w) } else { None }; + + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_flex_layout_child_data( + margin_top, + margin_right, + margin_bottom, + margin_left, + h_sizing, + v_sizing, + max_h, + min_h, + max_w, + min_w, + is_absolute, + z_index, + ); + } +} + +#[no_mangle] +pub extern "C" fn set_grid_layout_data() {} + +#[no_mangle] +pub extern "C" fn add_grid_track() {} + +#[no_mangle] +pub extern "C" fn set_grid_cell() {} + fn main() { init_gl(); } diff --git a/render-wasm/src/math.rs b/render-wasm/src/math.rs index dc2e8914e..4b5f30ca8 100644 --- a/render-wasm/src/math.rs +++ b/render-wasm/src/math.rs @@ -1,4 +1,9 @@ -use skia_safe::{Matrix, Point, Vector}; +use skia_safe as skia; + +pub type Rect = skia::Rect; +pub type Matrix = skia::Matrix; +pub type Vector = skia::Vector; +pub type Point = skia::Point; pub trait VectorExt { fn new_points(a: &Point, b: &Point) -> Vector; diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 9f68cb191..51a73c860 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -15,7 +15,7 @@ mod shadows; mod strokes; mod surfaces; -use crate::shapes::{Corners, Kind, Shape}; +use crate::shapes::{Corners, Shape, Type}; use cache::CachedSurfaceImage; use gpu_state::GpuState; use options::RenderOptions; @@ -289,8 +289,8 @@ impl RenderState { matrix.post_translate(center); matrix.pre_translate(-center); - match &shape.kind { - Kind::SVGRaw(sr) => { + match &shape.shape_type { + Type::SVGRaw(sr) => { if let Some(modifiers) = modifiers { self.surfaces.shape.canvas().concat(&modifiers); } @@ -490,8 +490,8 @@ impl RenderState { // the content and the second one rendering the mask so we need to do // an extra save_layer to keep all the masked group separate from other // already drawn elements. - match element.kind { - Kind::Group(group) => { + match element.shape_type { + Type::Group(group) => { if group.masked { let paint = skia::Paint::default(); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); @@ -528,8 +528,8 @@ impl RenderState { // Because masked groups needs two rendering passes (first drawing // the content and then drawing the mask), we need to do an // extra restore. - match element.kind { - Kind::Group(group) => { + match element.shape_type { + Type::Group(group) => { if group.masked { self.surfaces.current.canvas().restore(); } @@ -567,8 +567,8 @@ impl RenderState { let render_complete = self.viewbox.area.contains(element.selrect()); if visited_children { if !visited_mask { - match element.kind { - Kind::Group(group) => { + match element.shape_type { + Type::Group(group) => { // When we're dealing with masked groups we need to // do a separate extra step to draw the mask (the last // element of a masked group) and blend (using @@ -637,8 +637,9 @@ impl RenderState { if let Some(modifiers) = modifiers.get(&element.id) { transform.post_concat(&modifiers); } - let corners = match element.kind { - Kind::Rect(_, corners) => corners, + let corners = match &element.shape_type { + Type::Rect(data) => data.corners, + Type::Frame(data) => data.corners, _ => None, }; (bounds, corners, transform) diff --git a/render-wasm/src/render/fills.rs b/render-wasm/src/render/fills.rs index b6b3caf84..82aa8837c 100644 --- a/render-wasm/src/render/fills.rs +++ b/render-wasm/src/render/fills.rs @@ -1,7 +1,8 @@ -use crate::shapes::{Fill, ImageFill, Kind, Shape}; -use skia_safe::{self as skia, RRect, Rect}; +use crate::shapes::{Fill, ImageFill, Shape, Type}; +use skia_safe::{self as skia, RRect}; use super::RenderState; +use crate::math::Rect; fn draw_image_fill_in_container( render_state: &mut RenderState, @@ -16,7 +17,6 @@ fn draw_image_fill_in_container( let size = image_fill.size(); let canvas = render_state.surfaces.shape.canvas(); - let kind = &shape.kind; let container = &shape.selrect; let path_transform = shape.to_path_transform(); let paint = fill.to_paint(container); @@ -54,28 +54,33 @@ fn draw_image_fill_in_container( canvas.save(); // Set the clipping rectangle to the container bounds - match kind { - Kind::Rect(_, _) => { + match &shape.shape_type { + Type::Rect(_) | Type::Frame(_) => { canvas.clip_rect(container, skia::ClipOp::Intersect, true); } - Kind::Circle(_) => { + Type::Circle => { let mut oval_path = skia::Path::new(); oval_path.add_oval(container, None); canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true); } - Kind::Path(path) | Kind::Bool(_, path) => { - if let Some(path_transform) = path_transform { - canvas.clip_path( - &path.to_skia_path().transform(&path_transform), - skia::ClipOp::Intersect, - true, - ); + shape_type @ (Type::Path(_) | Type::Bool(_)) => { + if let Some(path) = shape_type.path() { + if let Some(path_transform) = path_transform { + canvas.clip_path( + &path.to_skia_path().transform(&path_transform), + skia::ClipOp::Intersect, + true, + ); + } } } - Kind::SVGRaw(_) => { + Type::SVGRaw(_) => { canvas.clip_rect(container, skia::ClipOp::Intersect, true); } - Kind::Group(_) => unreachable!("A group should not have fills"), + Type::Text => { + // TODO: Text fill + } + Type::Group(_) => unreachable!("A group should not have fills"), } // Draw the image with the calculated destination rectangle @@ -94,31 +99,34 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) { let canvas = render_state.surfaces.shape.canvas(); let selrect = shape.selrect; let path_transform = shape.to_path_transform(); - let kind = &shape.kind; - match (fill, kind) { + + match (fill, &shape.shape_type) { (Fill::Image(image_fill), _) => { draw_image_fill_in_container(render_state, shape, fill, image_fill); } - (_, Kind::Rect(rect, None)) => { - canvas.draw_rect(rect, &fill.to_paint(&selrect)); + (_, Type::Rect(_) | Type::Frame(_)) => { + if let Some(corners) = shape.shape_type.corners() { + let rrect = RRect::new_rect_radii(selrect, &corners); + canvas.draw_rrect(rrect, &fill.to_paint(&selrect)); + } else { + canvas.draw_rect(selrect, &fill.to_paint(&selrect)); + } } - (_, Kind::Rect(rect, Some(corners))) => { - let rrect = RRect::new_rect_radii(rect, &corners); - canvas.draw_rrect(rrect, &fill.to_paint(&selrect)); + (_, Type::Circle) => { + canvas.draw_oval(selrect, &fill.to_paint(&selrect)); } - (_, Kind::Circle(rect)) => { - canvas.draw_oval(rect, &fill.to_paint(&selrect)); - } - (_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => { - let svg_attrs = &shape.svg_attrs; - let mut skia_path = &mut path.to_skia_path(); + (_, Type::Path(_)) | (_, Type::Bool(_)) => { + if let Some(path) = &shape.shape_type.path() { + let svg_attrs = &shape.svg_attrs; + let mut skia_path = &mut path.to_skia_path(); - if let Some(path_transform) = path_transform { - skia_path = skia_path.transform(&path_transform); - if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) { - skia_path.set_fill_type(skia::PathFillType::EvenOdd); + if let Some(path_transform) = path_transform { + skia_path = skia_path.transform(&path_transform); + if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) { + skia_path.set_fill_type(skia::PathFillType::EvenOdd); + } + canvas.draw_path(&skia_path, &fill.to_paint(&selrect)); } - canvas.draw_path(&skia_path, &fill.to_paint(&selrect)); } } (_, _) => unreachable!("This shape should not have fills"), diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 337177844..6d37b6d4d 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; -use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; -use skia::Rect; +use crate::math::{Matrix, Point, Rect}; + +use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type}; use skia_safe::{self as skia, RRect}; use super::RenderState; @@ -55,7 +56,7 @@ fn draw_stroke_on_path( stroke: &Stroke, path: &Path, selrect: &Rect, - path_transform: Option<&skia::Matrix>, + path_transform: Option<&Matrix>, svg_attrs: &HashMap, scale: f32, ) { @@ -110,8 +111,8 @@ fn handle_stroke_cap( cap: StrokeCap, width: f32, paint: &mut skia::Paint, - p1: &skia::Point, - p2: &skia::Point, + p1: &Point, + p2: &Point, ) { paint.set_style(skia::PaintStyle::Fill); paint.set_blend_mode(skia::BlendMode::Src); @@ -154,7 +155,7 @@ fn handle_stroke_caps( dpr_scale: f32, ) { let points_count = path.count_points(); - let mut points = vec![skia::Point::default(); points_count]; + let mut points = vec![Point::default(); points_count]; let c_points = path.get_points(&mut points); // Closed shapes don't have caps @@ -186,8 +187,8 @@ fn handle_stroke_caps( fn draw_square_cap( canvas: &skia::Canvas, paint: &skia::Paint, - center: &skia::Point, - direction: &skia::Point, + center: &Point, + direction: &Point, size: f32, extra_rotation: f32, ) { @@ -195,27 +196,27 @@ fn draw_square_cap( let dy = direction.y - center.y; let angle = dy.atan2(dx); - let mut matrix = skia::Matrix::new_identity(); + let mut matrix = Matrix::new_identity(); matrix.pre_rotate( angle.to_degrees() + extra_rotation, - skia::Point::new(center.x, center.y), + Point::new(center.x, center.y), ); let half_size = size / 2.0; - let rect = skia::Rect::from_xywh(center.x - half_size, center.y - half_size, size, size); + let rect = Rect::from_xywh(center.x - half_size, center.y - half_size, size, size); let points = [ - skia::Point::new(rect.left(), rect.top()), - skia::Point::new(rect.right(), rect.top()), - skia::Point::new(rect.right(), rect.bottom()), - skia::Point::new(rect.left(), rect.bottom()), + Point::new(rect.left(), rect.top()), + Point::new(rect.right(), rect.top()), + Point::new(rect.right(), rect.bottom()), + Point::new(rect.left(), rect.bottom()), ]; let mut transformed_points = points.clone(); matrix.map_points(&mut transformed_points, &points); let mut path = skia::Path::new(); - path.move_to(skia::Point::new(center.x, center.y)); + path.move_to(Point::new(center.x, center.y)); path.move_to(transformed_points[0]); path.line_to(transformed_points[1]); path.line_to(transformed_points[2]); @@ -227,25 +228,22 @@ fn draw_square_cap( fn draw_arrow_cap( canvas: &skia::Canvas, paint: &skia::Paint, - center: &skia::Point, - direction: &skia::Point, + center: &Point, + direction: &Point, size: f32, ) { let dx = direction.x - center.x; let dy = direction.y - center.y; let angle = dy.atan2(dx); - let mut matrix = skia::Matrix::new_identity(); - matrix.pre_rotate( - angle.to_degrees() - 90., - skia::Point::new(center.x, center.y), - ); + let mut matrix = Matrix::new_identity(); + matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y)); let half_height = size / 2.; let points = [ - skia::Point::new(center.x, center.y - half_height), - skia::Point::new(center.x - size, center.y + half_height), - skia::Point::new(center.x + size, center.y + half_height), + Point::new(center.x, center.y - half_height), + Point::new(center.x - size, center.y + half_height), + Point::new(center.x + size, center.y + half_height), ]; let mut transformed_points = points.clone(); @@ -255,7 +253,7 @@ fn draw_arrow_cap( path.move_to(transformed_points[1]); path.line_to(transformed_points[0]); path.line_to(transformed_points[2]); - path.move_to(skia::Point::new(center.x, center.y)); + path.move_to(Point::new(center.x, center.y)); path.line_to(transformed_points[0]); canvas.draw_path(&path, paint); @@ -264,25 +262,22 @@ fn draw_arrow_cap( fn draw_triangle_cap( canvas: &skia::Canvas, paint: &skia::Paint, - center: &skia::Point, - direction: &skia::Point, + center: &Point, + direction: &Point, size: f32, ) { let dx = direction.x - center.x; let dy = direction.y - center.y; let angle = dy.atan2(dx); - let mut matrix = skia::Matrix::new_identity(); - matrix.pre_rotate( - angle.to_degrees() - 90., - skia::Point::new(center.x, center.y), - ); + let mut matrix = Matrix::new_identity(); + matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y)); let half_height = size / 2.; let points = [ - skia::Point::new(center.x, center.y - half_height), - skia::Point::new(center.x - size, center.y + half_height), - skia::Point::new(center.x + size, center.y + half_height), + Point::new(center.x, center.y - half_height), + Point::new(center.x - size, center.y + half_height), + Point::new(center.x + size, center.y + half_height), ]; let mut transformed_points = points.clone(); @@ -336,7 +331,6 @@ fn draw_image_stroke_in_container( let size = image_fill.size(); let canvas = render_state.surfaces.shape.canvas(); - let kind = &shape.kind; let container = &shape.selrect; let path_transform = shape.to_path_transform(); let svg_attrs = &shape.svg_attrs; @@ -349,56 +343,65 @@ fn draw_image_stroke_in_container( let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb); canvas.save_layer(&layer_rec); - // Draw the stroke based on the kind, we are using this stroke as a "selector" of the area of the image we want to show. + // Draw the stroke based on the shape type, we are using this stroke as + // a "selector" of the area of the image we want to show. let outer_rect = stroke.outer_rect(container); - match kind { - Kind::Rect(rect, corners) => draw_stroke_on_rect( - canvas, - stroke, - rect, - &outer_rect, - corners, - svg_attrs, - dpr_scale, - ), - Kind::Circle(rect) => { - draw_stroke_on_circle(canvas, stroke, rect, &outer_rect, svg_attrs, dpr_scale) - } - Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"), - Kind::Path(p) | Kind::Bool(_, p) => { - canvas.save(); - let mut path = p.to_skia_path(); - path.transform(&path_transform.unwrap()); - let stroke_kind = stroke.render_kind(p.is_open()); - match stroke_kind { - StrokeKind::InnerStroke => { - canvas.clip_path(&path, skia::ClipOp::Intersect, true); - } - StrokeKind::CenterStroke => {} - StrokeKind::OuterStroke => { - canvas.clip_path(&path, skia::ClipOp::Difference, true); - } - } - let is_open = p.is_open(); - let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale); - canvas.draw_path(&path, &paint); - canvas.restore(); - if stroke.render_kind(is_open) == StrokeKind::OuterStroke { - // Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts - paint.set_stroke_width(1. / dpr_scale); - canvas.draw_path(&path, &paint); - } - handle_stroke_caps( - &mut path, - stroke, - &outer_rect, + + match &shape.shape_type { + shape_type @ (Type::Rect(_) | Type::Frame(_)) => { + draw_stroke_on_rect( canvas, - is_open, + stroke, + container, + &outer_rect, + &shape_type.corners(), svg_attrs, dpr_scale, ); } + Type::Circle => { + draw_stroke_on_circle(canvas, stroke, container, &outer_rect, svg_attrs, dpr_scale) + } + + shape_type @ (Type::Path(_) | Type::Bool(_)) => { + if let Some(p) = shape_type.path() { + canvas.save(); + let mut path = p.to_skia_path(); + path.transform(&path_transform.unwrap()); + let stroke_kind = stroke.render_kind(p.is_open()); + match stroke_kind { + StrokeKind::InnerStroke => { + canvas.clip_path(&path, skia::ClipOp::Intersect, true); + } + StrokeKind::CenterStroke => {} + StrokeKind::OuterStroke => { + canvas.clip_path(&path, skia::ClipOp::Difference, true); + } + } + let is_open = p.is_open(); + let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale); + canvas.draw_path(&path, &paint); + canvas.restore(); + if stroke.render_kind(is_open) == StrokeKind::OuterStroke { + // Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts + paint.set_stroke_width(1. / dpr_scale); + canvas.draw_path(&path, &paint); + } + handle_stroke_caps( + &mut path, + stroke, + &outer_rect, + canvas, + is_open, + svg_attrs, + dpr_scale, + ); + } + } + + _ => unreachable!("This shape should not have strokes"), } + // Draw the image. We are using now the SrcIn blend mode, so the rendered piece of image will the area of the stroke over the image. let mut image_paint = skia::Paint::default(); image_paint.set_blend_mode(skia::BlendMode::SrcIn); @@ -410,7 +413,7 @@ fn draw_image_stroke_in_container( canvas.draw_image_rect(image.unwrap(), None, dest_rect, &image_paint); // Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area. - if let Kind::Path(p) = kind { + if let Type::Path(p) = &shape.shape_type { if stroke.render_kind(p.is_open()) == StrokeKind::OuterStroke { let mut path = p.to_skia_path(); path.transform(&path_transform.unwrap()); @@ -433,31 +436,41 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr(); let selrect = shape.selrect; let path_transform = shape.to_path_transform(); - let kind = &shape.kind; let svg_attrs = &shape.svg_attrs; + if let Fill::Image(image_fill) = &stroke.fill { draw_image_stroke_in_container(render_state, shape, stroke, image_fill); } else { - match kind { - Kind::Rect(rect, corners) => draw_stroke_on_rect( - canvas, stroke, rect, &selrect, corners, svg_attrs, dpr_scale, - ), - Kind::Circle(rect) => { - draw_stroke_on_circle(canvas, stroke, rect, &selrect, &svg_attrs, dpr_scale) - } - Kind::Path(path) | Kind::Bool(_, path) => { - let svg_attrs = &shape.svg_attrs; - draw_stroke_on_path( + match &shape.shape_type { + shape_type @ (Type::Rect(_) | Type::Frame(_)) => { + draw_stroke_on_rect( canvas, stroke, - path, &selrect, - path_transform.as_ref(), + &selrect, + &shape_type.corners(), svg_attrs, dpr_scale, ); } - Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"), + Type::Circle => { + draw_stroke_on_circle(canvas, stroke, &selrect, &selrect, &svg_attrs, dpr_scale) + } + shape_type @ (Type::Path(_) | Type::Bool(_)) => { + if let Some(path) = shape_type.path() { + let svg_attrs = &shape.svg_attrs; + draw_stroke_on_path( + canvas, + stroke, + path, + &selrect, + path_transform.as_ref(), + svg_attrs, + dpr_scale, + ); + } + } + _ => unreachable!("This shape should not have strokes"), } } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 1114b182f..40420a975 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1,4 +1,4 @@ -use skia_safe::{self as skia, Matrix, Point, Rect}; +use skia_safe::{self as skia}; use std::collections::HashMap; use uuid::Uuid; @@ -7,10 +7,14 @@ use crate::render::BlendMode; mod blurs; mod bools; +mod corners; mod fills; +mod frames; mod groups; +mod layouts; mod modifiers; mod paths; +mod rects; mod shadows; mod strokes; mod svgraw; @@ -18,58 +22,84 @@ mod transform; pub use blurs::*; pub use bools::*; +pub use corners::*; pub use fills::*; +pub use frames::*; pub use groups::*; +pub use layouts::*; pub use modifiers::*; pub use paths::*; +pub use rects::*; pub use shadows::*; pub use strokes::*; pub use svgraw::*; pub use transform::*; -use crate::math::Bounds; - -pub type CornerRadius = Point; -pub type Corners = [CornerRadius; 4]; +use crate::math; +use crate::math::{Bounds, Matrix, Point}; #[derive(Debug, Clone, PartialEq)] pub enum Type { - Frame, - Group, - Bool, - Rect, - Path, + Frame(Frame), + Group(Group), + Bool(Bool), + Rect(Rect), + Path(Path), Text, Circle, - SvgRaw, - Image, + SVGRaw(SVGRaw), } impl Type { pub fn from(value: u8) -> Self { match value { - 0 => Type::Frame, - 1 => Type::Group, - 2 => Type::Bool, - 3 => Type::Rect, - 4 => Type::Path, + 0 => Type::Frame(Frame::default()), + 1 => Type::Group(Group::default()), + 2 => Type::Bool(Bool::default()), + 3 => Type::Rect(Rect::default()), + 4 => Type::Path(Path::default()), 5 => Type::Text, 6 => Type::Circle, - 7 => Type::SvgRaw, - 8 => Type::Image, - _ => Type::Rect, + 7 => Type::SVGRaw(SVGRaw::default()), + _ => Type::Rect(Rect::default()), } } -} -#[derive(Debug, Clone, PartialEq)] -pub enum Kind { - Rect(Rect, Option), - Circle(Rect), - Path(Path), - Bool(BoolType, Path), - SVGRaw(SVGRaw), - Group(Group), + pub fn corners(&self) -> Option { + match self { + Type::Rect(Rect { corners, .. }) => *corners, + Type::Frame(Frame { corners, .. }) => *corners, + _ => None, + } + } + + pub fn set_corners(&mut self, corners: Corners) { + match self { + Type::Rect(data) => { + data.corners = Some(corners); + } + Type::Frame(data) => { + data.corners = Some(corners); + } + _ => {} + } + } + + pub fn path(&self) -> Option<&Path> { + match self { + Type::Path(path) => Some(path), + Type::Bool(Bool { path, .. }) => Some(path), + _ => None, + } + } + + pub fn path_mut(&mut self) -> Option<&mut Path> { + match self { + Type::Path(path) => Some(path), + Type::Bool(Bool { path, .. }) => Some(path), + _ => None, + } + } } #[derive(Debug, Clone, PartialEq, Copy)] @@ -124,8 +154,7 @@ pub struct Shape { pub id: Uuid, pub shape_type: Type, pub children: Vec, - pub kind: Kind, - pub selrect: Rect, + pub selrect: math::Rect, pub transform: Matrix, pub rotation: f32, pub constraint_h: Option, @@ -139,17 +168,17 @@ pub struct Shape { pub hidden: bool, pub svg: Option, pub svg_attrs: HashMap, - shadows: Vec, + pub shadows: Vec, + pub layout_item: Option, } impl Shape { pub fn new(id: Uuid) -> Self { Self { id, - shape_type: Type::Rect, + shape_type: Type::Rect(Rect::default()), children: Vec::::new(), - kind: Kind::Rect(Rect::new_empty(), None), - selrect: Rect::new_empty(), + selrect: math::Rect::new_empty(), transform: Matrix::default(), rotation: 0., constraint_h: None, @@ -164,6 +193,7 @@ impl Shape { svg: None, svg_attrs: HashMap::new(), shadows: vec![], + layout_item: None, } } @@ -172,28 +202,20 @@ impl Shape { } pub fn is_frame(&self) -> bool { - self.shape_type == Type::Frame + matches!(self.shape_type, Type::Frame(_)) } 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(_, corners) => { - self.kind = Kind::Rect(self.selrect.to_owned(), corners); - } - Kind::Circle(_) => { - self.kind = Kind::Circle(self.selrect.to_owned()); + } + + pub fn set_masked(&mut self, masked: bool) { + match &mut self.shape_type { + Type::Group(data) => { + data.masked = masked; } _ => {} - }; - } - - pub fn set_kind(&mut self, kind: Kind) { - self.kind = kind; - } - - pub fn kind(&self) -> Kind { - self.kind.clone() + } } pub fn set_clip(&mut self, value: bool) { @@ -232,6 +254,78 @@ impl Shape { self.hidden = value; } + pub fn set_flex_layout_child_data( + &mut self, + margin_top: f32, + margin_right: f32, + margin_bottom: f32, + margin_left: f32, + h_sizing: Sizing, + v_sizing: Sizing, + max_h: Option, + min_h: Option, + max_w: Option, + min_w: Option, + is_absolute: bool, + z_index: i32, + ) { + self.layout_item = Some(LayoutItem { + margin_top, + margin_right, + margin_bottom, + margin_left, + h_sizing, + v_sizing, + max_h, + min_h, + max_w, + min_w, + is_absolute, + z_index, + }); + } + + pub fn set_flex_layout_data( + &mut self, + direction: Direction, + row_gap: f32, + column_gap: f32, + align_items: AlignItems, + align_content: AlignContent, + justify_items: JustifyItems, + justify_content: JustifyContent, + wrap_type: WrapType, + padding_top: f32, + padding_right: f32, + padding_bottom: f32, + padding_left: f32, + ) { + match &mut self.shape_type { + Type::Frame(data) => { + let layout_data = LayoutData { + direction, + align_items, + align_content, + justify_items, + justify_content, + padding_top, + padding_right, + padding_bottom, + padding_left, + }; + + let flex_data = FlexData { + row_gap, + column_gap, + wrap_type, + }; + + data.layout = Some(Layout::FlexLayout(layout_data, flex_data)); + } + _ => {} + } + } + pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) { self.blur = Blur::new(blur_type, hidden, value); } @@ -306,31 +400,34 @@ impl Shape { } pub fn set_path_segments(&mut self, buffer: Vec) -> Result<(), String> { - let p = Path::try_from(buffer)?; - let kind = match &self.kind { - Kind::Bool(bool_type, _) => Kind::Bool(*bool_type, p), - _ => Kind::Path(p), - }; - self.kind = kind; + let path = Path::try_from(buffer)?; + match &mut self.shape_type { + Type::Bool(Bool { bool_type, .. }) => { + self.shape_type = Type::Bool(Bool { + bool_type: *bool_type, + path, + }); + } + Type::Path(_) => { + self.shape_type = Type::Path(path); + } + _ => {} + }; Ok(()) } pub fn set_path_attr(&mut self, name: String, value: String) { - match &mut self.kind { - Kind::Path(_) => { + match self.shape_type { + Type::Path(_) => { self.set_svg_attr(name, value); } - Kind::Rect(_, _) - | Kind::Circle(_) - | Kind::SVGRaw(_) - | Kind::Bool(_, _) - | Kind::Group(_) => unreachable!("This shape should have path attrs"), + _ => unreachable!("This shape should have path attrs"), }; } pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> { - self.kind = Kind::SVGRaw(SVGRaw::from_content(content)); + self.shape_type = Type::SVGRaw(SVGRaw::from_content(content)); Ok(()) } @@ -339,37 +436,21 @@ impl Shape { } pub fn set_bool_type(&mut self, bool_type: BoolType) { - let kind = match &self.kind { - Kind::Bool(_, path) => Kind::Bool(bool_type, path.clone()), - _ => Kind::Bool(bool_type, Path::default()), + self.shape_type = match &self.shape_type { + Type::Bool(Bool { path, .. }) => Type::Bool(Bool { + bool_type, + path: path.clone(), + }), + _ => Type::Bool(Bool { + bool_type, + path: Path::default(), + }), }; - - self.kind = kind; } pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) { - match self.kind { - Kind::Rect(_, _) => { - 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); - } - _ => {} + if let Some(corners) = make_corners(raw_corners) { + self.shape_type.set_corners(corners); } } @@ -415,7 +496,7 @@ impl Shape { bounds } - pub fn selrect(&self) -> Rect { + pub fn selrect(&self) -> math::Rect { self.selrect } @@ -432,9 +513,9 @@ impl Shape { } pub fn children_ids(&self) -> Vec { - if let Kind::Bool(_, _) = self.kind { + if let Type::Bool(_) = self.shape_type { vec![] - } else if let Kind::Group(group) = self.kind { + } else if let Type::Group(group) = self.shape_type { if group.masked { self.children[1..self.children.len()].to_vec() } else { @@ -462,7 +543,7 @@ impl Shape { } pub fn is_recursive(&self) -> bool { - !matches!(self.kind, Kind::SVGRaw(_)) + !matches!(self.shape_type, Type::SVGRaw(_)) } pub fn add_shadow(&mut self, shadow: Shadow) { @@ -486,14 +567,13 @@ impl Shape { } pub fn to_path_transform(&self) -> Option { - match self.kind { - Kind::Path(_) | Kind::Bool(_, _) => { + match self.shape_type { + Type::Path(_) | Type::Bool(_) => { let center = self.center(); let mut matrix = Matrix::new_identity(); matrix.pre_translate(center); matrix.pre_concat(&self.transform.invert()?); matrix.pre_translate(-center); - Some(matrix) } _ => None, @@ -510,7 +590,7 @@ impl Shape { let width = bounds.width(); let height = bounds.height(); - self.selrect = Rect::from_xywh( + self.selrect = math::Rect::from_xywh( center.x - width / 2.0, center.y - height / 2.0, width, @@ -519,28 +599,12 @@ impl Shape { } pub fn apply_transform(&mut self, transform: &Matrix) { - match &self.kind { - Kind::Rect(_, c) => { - let c = c.clone(); - self.transform_selrect(&transform); - self.kind = Kind::Rect(self.selrect, c); - } - Kind::Circle(_) => { - self.transform_selrect(&transform); - self.kind = Kind::Circle(self.selrect); - } - Kind::Path(path) => { - let mut path = path.clone(); - self.transform_selrect(&transform); - path.transform(&transform); - self.kind = Kind::Path(path); - } - Kind::Bool(bool_type, path) => { - let bool_type = *bool_type; - let mut path = path.clone(); - self.transform_selrect(&transform); - path.transform(&transform); - self.kind = Kind::Bool(bool_type, path); + self.transform_selrect(&transform); + match &mut self.shape_type { + shape_type @ (Type::Path(_) | Type::Bool(_)) => { + if let Some(path) = shape_type.path_mut() { + path.transform(&transform); + } } _ => {} } @@ -565,19 +629,45 @@ mod tests { } #[test] - fn test_apply_transform() { - let mut shape = Shape::new(Uuid::new_v4()); - shape.set_shape_type(Type::Rect); - shape.set_selrect(0.0, 10.0, 10.0, 0.0); - shape.apply_transform(Matrix::scale((2.0, 2.0))); - - match shape.kind { - Kind::Rect(r, _) => { - //println!(">>>{r:?}"); - assert_eq!(r.width(), 20.0); - assert_eq!(r.height(), 20.0); - } - _ => assert!(false), + fn test_set_corners() { + let mut shape = any_shape(); + shape.set_corners((10.0, 20.0, 30.0, 40.0)); + if let Type::Rect(Rect { corners, .. }) = shape.shape_type { + assert_eq!( + corners, + Some([ + Point { x: 10.0, y: 10.0 }, + Point { x: 20.0, y: 20.0 }, + Point { x: 30.0, y: 30.0 }, + Point { x: 40.0, y: 40.0 } + ]) + ); + } else { + assert!(false); } } + + #[test] + fn test_set_masked() { + let mut shape = any_shape(); + shape.set_shape_type(Type::Group(Group { masked: false })); + shape.set_masked(true); + + if let Type::Group(Group { masked, .. }) = shape.shape_type { + assert_eq!(masked, true); + } else { + assert!(false); + } + } + + #[test] + fn test_apply_transform() { + let mut shape = Shape::new(Uuid::new_v4()); + shape.set_shape_type(Type::Rect(Rect::default())); + shape.set_selrect(0.0, 10.0, 10.0, 0.0); + shape.apply_transform(&Matrix::scale((2.0, 2.0))); + + assert_eq!(shape.selrect().width(), 20.0); + assert_eq!(shape.selrect().height(), 20.0); + } } diff --git a/render-wasm/src/shapes/bools.rs b/render-wasm/src/shapes/bools.rs index 3f7cdd410..1bb77b699 100644 --- a/render-wasm/src/shapes/bools.rs +++ b/render-wasm/src/shapes/bools.rs @@ -1,3 +1,11 @@ +use super::Path; + +#[derive(Debug, Clone, PartialEq)] +pub struct Bool { + pub bool_type: BoolType, + pub path: Path, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum BoolType { Union, @@ -6,6 +14,15 @@ pub enum BoolType { Exclusion, } +impl Default for Bool { + fn default() -> Self { + Bool { + bool_type: BoolType::Union, + path: Path::default(), + } + } +} + impl From for BoolType { fn from(value: u8) -> Self { match value { diff --git a/render-wasm/src/shapes/corners.rs b/render-wasm/src/shapes/corners.rs new file mode 100644 index 000000000..91f3508b3 --- /dev/null +++ b/render-wasm/src/shapes/corners.rs @@ -0,0 +1,23 @@ +use skia_safe::Point; + +pub type CornerRadius = Point; +pub type Corners = [CornerRadius; 4]; + +pub fn make_corners(raw_corners: (f32, f32, f32, f32)) -> Option { + 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; + + if are_straight_corners { + None + } else { + Some([ + (r1, r1).into(), + (r2, r2).into(), + (r3, r3).into(), + (r4, r4).into(), + ]) + } +} diff --git a/render-wasm/src/shapes/frames.rs b/render-wasm/src/shapes/frames.rs new file mode 100644 index 000000000..e9f4d718b --- /dev/null +++ b/render-wasm/src/shapes/frames.rs @@ -0,0 +1,8 @@ +use super::Corners; +use super::Layout; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Frame { + pub corners: Option, + pub layout: Option, +} diff --git a/render-wasm/src/shapes/groups.rs b/render-wasm/src/shapes/groups.rs index 4f8e4266d..e9285a7d2 100644 --- a/render-wasm/src/shapes/groups.rs +++ b/render-wasm/src/shapes/groups.rs @@ -1,10 +1,4 @@ -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Group { pub masked: bool, } - -impl Group { - pub fn new(masked: bool) -> Self { - Group { masked } - } -} diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs new file mode 100644 index 000000000..2b15a8120 --- /dev/null +++ b/render-wasm/src/shapes/layouts.rs @@ -0,0 +1,195 @@ +#![allow(dead_code)] + +#[derive(Debug, Clone, PartialEq)] +pub enum Layout { + FlexLayout(LayoutData, FlexData), + GridLayout(LayoutData, GridData), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Direction { + Row, + Column, +} + +impl Direction { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Row, + 1 => Self::Column, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AlignItems { + Start, + End, + Center, + Stretch, +} + +impl AlignItems { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Start, + 1 => Self::End, + 2 => Self::Center, + 3 => Self::Stretch, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AlignContent { + Start, + End, + Center, + SpaceBetween, + SpaceAround, + SpaceEvenly, + Stretch, +} + +impl AlignContent { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Start, + 1 => Self::End, + 2 => Self::Center, + 3 => Self::SpaceBetween, + 4 => Self::SpaceAround, + 5 => Self::SpaceEvenly, + 6 => Self::Stretch, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum JustifyItems { + Start, + End, + Center, + Stretch, +} + +impl JustifyItems { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Start, + 1 => Self::End, + 2 => Self::Center, + 3 => Self::Stretch, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum JustifyContent { + Start, + End, + Center, + SpaceBetween, + SpaceAround, + SpaceEvenly, + Stretch, +} + +impl JustifyContent { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Start, + 1 => Self::End, + 2 => Self::Center, + 3 => Self::SpaceBetween, + 4 => Self::SpaceAround, + 5 => Self::SpaceEvenly, + 6 => Self::Stretch, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum WrapType { + Wrap, + NoWrap, +} + +impl WrapType { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Wrap, + 1 => Self::NoWrap, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GridTrack {} + +#[derive(Debug, Clone, PartialEq)] +pub enum Sizing { + Fill, + Fix, + Auto, +} + +impl Sizing { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Self::Fill, + 1 => Self::Fix, + 2 => Self::Auto, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LayoutData { + pub direction: Direction, + pub align_items: AlignItems, + pub align_content: AlignContent, + pub justify_items: JustifyItems, + pub justify_content: JustifyContent, + pub padding_top: f32, + pub padding_right: f32, + pub padding_bottom: f32, + pub padding_left: f32, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FlexData { + pub row_gap: f32, + pub column_gap: f32, + pub wrap_type: WrapType, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GridData { + pub rows: Vec, + pub columns: Vec, + // layout-grid-cells ;; map of id->grid-cell +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LayoutItem { + pub margin_top: f32, + pub margin_right: f32, + pub margin_bottom: f32, + pub margin_left: f32, + pub h_sizing: Sizing, + pub v_sizing: Sizing, + pub max_h: Option, + pub min_h: Option, + pub max_w: Option, + pub min_w: Option, + pub is_absolute: bool, + pub z_index: i32, +} diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 30c104277..f11ed0214 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -221,8 +221,8 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec mod tests { use super::*; - use crate::shapes::Type; - use skia::Point; + use crate::math::{Matrix, Point}; + use crate::shapes::*; #[test] fn test_propagate_shape() { @@ -235,7 +235,7 @@ mod tests { let parent_id = Uuid::new_v4(); let mut parent = Shape::new(parent_id); - parent.set_shape_type(Type::Group); + parent.set_shape_type(Type::Group(Group::default())); parent.add_child(child_id); parent.set_selrect(1.0, 1.0, 5.0, 5.0); shapes.insert(parent_id, parent.clone()); diff --git a/render-wasm/src/shapes/rects.rs b/render-wasm/src/shapes/rects.rs new file mode 100644 index 000000000..54844a8fe --- /dev/null +++ b/render-wasm/src/shapes/rects.rs @@ -0,0 +1,6 @@ +use super::Corners; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Rect { + pub corners: Option, +} diff --git a/render-wasm/src/shapes/svgraw.rs b/render-wasm/src/shapes/svgraw.rs index 408a92d2a..c89f66819 100644 --- a/render-wasm/src/shapes/svgraw.rs +++ b/render-wasm/src/shapes/svgraw.rs @@ -8,3 +8,11 @@ impl SVGRaw { SVGRaw { content: svg } } } + +impl Default for SVGRaw { + fn default() -> Self { + Self { + content: String::from(""), + } + } +}