diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index d574aa5ba..5ee973fe0 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -297,11 +297,15 @@ (->> resize-events-stream (rx/take-until stopper) (rx/last) - (rx/map #(dwm/set-modifiers (dwm/create-modif-tree ids %) (contains? layout :scale-text)))) + (rx/map #(dwm/apply-modifiers {:modifiers (dwm/create-modif-tree ids %) + :ignore-constraints (contains? layout :scale-text)}))) (rx/empty))) - (rx/of (dwm/apply-modifiers) - (finish-transform)))))))) + (rx/of + (if (features/active-feature? state "render-wasm/v1") + (dwm/clear-local-transform) + (dwm/apply-modifiers)) + (finish-transform)))))))) (defn trigger-bounding-box-cloaking "Trigger the bounding box cloaking (with default timer of 1sec) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 51815eb4a..c80fd1d01 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -249,7 +249,7 @@ offset (:offset stop)] [r g b a (* 100 offset)])) stops))))) - (h/call internal-module "_add_shape_fill_stops" stops-ptr n-stops)) + (h/call internal-module "_add_shape_fill_stops")) (some? image) (let [id (dm/get-prop image :id) @@ -423,6 +423,34 @@ [opacity] (h/call internal-module "_set_shape_opacity" (or opacity 1))) +(defn- translate-constraint-h + [type] + (case type + :left 0 + :right 1 + :leftright 2 + :center 3 + :scale 4)) + +(defn set-constraints-h + [constraint] + (when constraint + (h/call internal-module "_set_shape_constraint_h" (translate-constraint-h constraint)))) + +(defn- translate-constraint-v + [type] + (case type + :top 0 + :bottom 1 + :topbottom 2 + :center 3 + :scale 4)) + +(defn set-constraints-v + [constraint] + (when constraint + (h/call internal-module "_set_shape_constraint_v" (translate-constraint-v constraint)))) + (defn set-shape-hidden [hidden] (h/call internal-module "_set_shape_hidden" hidden)) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 843bec2b4..f44ca2390 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -129,6 +129,8 @@ :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) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index fdc05e9ac..d6c84a2be 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,11 +1,7 @@ -use std::collections::HashSet; -use uuid::Uuid; - use skia_safe as skia; mod debug; mod math; -mod matrix; mod mem; mod render; mod shapes; @@ -14,7 +10,7 @@ mod utils; mod view; use crate::mem::SerializableResult; -use crate::shapes::{BoolType, Kind, Path, TransformEntry}; +use crate::shapes::{BoolType, ConstraintH, ConstraintV, Kind, Path, TransformEntry}; use crate::state::State; use crate::utils::uuid_from_u32_quartet; @@ -268,20 +264,22 @@ pub extern "C" fn add_shape_radial_fill( } #[no_mangle] -pub extern "C" fn add_shape_fill_stops(ptr: *mut shapes::RawStopData, n_stops: u32) { +pub extern "C" fn add_shape_fill_stops() { + let bytes = mem::bytes(); + + let entries: Vec<_> = bytes + .chunks(size_of::()) + .map(|data| shapes::RawStopData::from_bytes(data.try_into().unwrap())) + .collect(); + let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); if let Some(shape) = state.current_shape() { - let len = n_stops as usize; - - unsafe { - let buffer = Vec::::from_raw_parts(ptr, len, len); - shape - .add_fill_gradient_stops(buffer) - .expect("could not add gradient stops"); - mem::free_bytes(); - } + shape + .add_fill_gradient_stops(entries) + .expect("could not add gradient stops"); } + mem::free_bytes(); } #[no_mangle] @@ -390,6 +388,22 @@ pub extern "C" fn set_shape_opacity(opacity: f32) { } } +#[no_mangle] +pub extern "C" fn set_shape_constraint_h(constraint: u8) { + let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_constraint_h(ConstraintH::from(constraint)) + } +} + +#[no_mangle] +pub extern "C" fn set_shape_constraint_v(constraint: u8) { + let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_constraint_v(ConstraintV::from(constraint)) + } +} + #[no_mangle] pub extern "C" fn set_shape_hidden(hidden: bool) { let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); @@ -598,37 +612,14 @@ pub extern "C" fn set_shape_path_attrs(num_attrs: u32) { pub extern "C" fn propagate_modifiers() -> *mut u8 { let bytes = mem::bytes(); - let mut entries: Vec<_> = bytes - .chunks(size_of::()) + let entries: Vec<_> = bytes + .chunks(size_of::<::BytesType>()) .map(|data| TransformEntry::from_bytes(data.try_into().unwrap())) .collect(); let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + let result = shapes::propagate_modifiers(state, entries); - let mut processed = HashSet::::new(); - - let mut result = Vec::::new(); - - // Propagate the transform to children - while let Some(entry) = entries.pop() { - if !processed.contains(&entry.id) { - if let Some(shape) = state.shapes.get(&entry.id) { - let mut children: Vec = shape - .children - .iter() - .map(|id| TransformEntry { - id: id.clone(), - transform: entry.transform, - }) - .collect(); - - entries.append(&mut children); - - processed.insert(entry.id); - result.push(entry.clone()); - } - } - } mem::write_vec(result) } @@ -644,7 +635,7 @@ pub extern "C" fn set_modifiers() { let bytes = mem::bytes(); let entries: Vec<_> = bytes - .chunks(size_of::()) + .chunks(size_of::<::BytesType>()) .map(|data| TransformEntry::from_bytes(data.try_into().unwrap())) .collect(); diff --git a/render-wasm/src/matrix.rs b/render-wasm/src/matrix.rs deleted file mode 100644 index 2692e0718..000000000 --- a/render-wasm/src/matrix.rs +++ /dev/null @@ -1,121 +0,0 @@ -// allowing dead code so we can have some API's that are not used yet without warnings -#![allow(dead_code)] -use skia_safe as skia; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Matrix { - pub a: f32, - pub b: f32, - pub c: f32, - pub d: f32, - pub e: f32, - pub f: f32, -} - -impl Matrix { - pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self { - Self { a, b, c, d, e, f } - } - - pub fn translate(x: f32, y: f32) -> Self { - Self::new(0.0, 0.0, 0.0, 0.0, x, y) - } - - pub fn identity() -> Self { - Self { - a: 1., - b: 0., - c: 0., - d: 1., - e: 0., - f: 0., - } - } - - pub fn to_skia_matrix(&self) -> skia::Matrix { - let mut res = skia::Matrix::new_identity(); - - let (translate_x, translate_y) = self.translation(); - let (scale_x, scale_y) = self.scale(); - let (skew_x, skew_y) = self.skew(); - res.set_all( - scale_x, - skew_x, - translate_x, - skew_y, - scale_y, - translate_y, - 0., - 0., - 1., - ); - - res - } - - pub fn no_translation(&self) -> Self { - let mut res = Self::identity(); - res.c = self.c; - res.b = self.b; - res.a = self.a; - res.d = self.d; - res - } - - fn translation(&self) -> (f32, f32) { - (self.e, self.f) - } - - fn scale(&self) -> (f32, f32) { - (self.a, self.d) - } - - fn skew(&self) -> (f32, f32) { - (self.c, self.b) - } - - pub fn product(&self, other: &Matrix) -> Matrix { - let a = self.a * other.a + self.c * other.b; - let b = self.b * other.a + self.d * other.b; - let c = self.a * other.c + self.c * other.d; - let d = self.b * other.c + self.d * other.d; - let e = self.a * other.e + self.c * other.f + self.e; - let f = self.b * other.e + self.d * other.f + self.f; - Matrix::new(a, b, c, d, e, f) - } - - pub fn as_bytes(&self) -> [u8; 24] { - let mut result = [0; 24]; - result[0..4].clone_from_slice(&self.a.to_le_bytes()); - result[4..8].clone_from_slice(&self.b.to_le_bytes()); - result[8..12].clone_from_slice(&self.c.to_le_bytes()); - result[12..16].clone_from_slice(&self.d.to_le_bytes()); - result[16..20].clone_from_slice(&self.e.to_le_bytes()); - result[20..24].clone_from_slice(&self.f.to_le_bytes()); - result - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_product() { - let a = Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); - let b = Matrix::new(6.0, 5.0, 4.0, 3.0, 2.0, 1.0); - - assert_eq!( - a.product(&b), - Matrix::new(21.0, 32.0, 13.0, 20.0, 10.0, 14.0) - ); - - let a = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0); - let b = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0); - - assert_eq!( - a.product(&b), - Matrix::new(81.0, 40.0, 80.0, 41.0, 112.0, 56.0) - ); - } -} diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index 9670f4574..20e44ef3e 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -57,7 +57,7 @@ pub trait SerializableResult { by the implementation of SerializableResult trait */ pub fn write_vec(result: Vec) -> *mut u8 { - let elem_size = size_of::(); + let elem_size = size_of::(); let bytes_len = 4 + result.len() * elem_size; let mut result_bytes = Vec::::with_capacity(bytes_len); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 33d3e1025..2cf7c3c30 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use uuid::Uuid; use crate::math; -use crate::matrix::Matrix; use crate::view::Viewbox; +use skia::Matrix; mod blend; mod cache; @@ -240,18 +240,14 @@ impl RenderState { clip_bounds: Option, ) { if let Some(modifiers) = modifiers { - self.drawing_surface - .canvas() - .concat(&modifiers.to_skia_matrix()); + self.drawing_surface.canvas().concat(&modifiers); } - 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); + + // Transform the shape in the center + let mut matrix = shape.transform.clone(); + matrix.post_translate(center); matrix.pre_translate(-center); self.drawing_surface.canvas().concat(&matrix); diff --git a/render-wasm/src/render/fills.rs b/render-wasm/src/render/fills.rs index 9beb474d7..6061b85c5 100644 --- a/render-wasm/src/render/fills.rs +++ b/render-wasm/src/render/fills.rs @@ -67,11 +67,13 @@ fn draw_image_fill_in_container( canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true); } Kind::Path(path) | Kind::Bool(_, path) => { - canvas.clip_path( - &path.to_skia_path().transform(&path_transform.unwrap()), - skia::ClipOp::Intersect, - true, - ); + if let Some(path_transform) = path_transform { + canvas.clip_path( + &path.to_skia_path().transform(&path_transform), + skia::ClipOp::Intersect, + true, + ); + } } Kind::SVGRaw(_) => { canvas.clip_rect(container, skia::ClipOp::Intersect, true); @@ -79,7 +81,9 @@ fn draw_image_fill_in_container( } // Draw the image with the calculated destination rectangle - canvas.draw_image_rect(image.unwrap(), None, dest_rect, &paint); + if let Some(image) = image { + canvas.draw_image_rect(image, None, dest_rect, &paint); + } // Restore the canvas to remove the clipping canvas.restore(); @@ -110,11 +114,14 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) { (_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => { let svg_attrs = &shape.svg_attrs; let mut skia_path = &mut path.to_skia_path(); - skia_path = skia_path.transform(&path_transform.unwrap()); - 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)); } (_, _) => todo!(), } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 68ca43bb4..ce1f32bc9 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -3,12 +3,13 @@ use skia_safe as skia; use std::collections::HashMap; use uuid::Uuid; -use crate::matrix::Matrix; use crate::render::BlendMode; +use skia::Matrix; mod blurs; mod bools; mod fills; +mod modifiers; mod paths; mod shadows; mod strokes; @@ -18,6 +19,7 @@ mod transform; pub use blurs::*; pub use bools::*; pub use fills::*; +pub use modifiers::*; pub use paths::*; pub use shadows::*; pub use strokes::*; @@ -36,6 +38,50 @@ pub enum Kind { SVGRaw(SVGRaw), } +#[derive(Debug, Clone, PartialEq)] +pub enum ConstraintH { + Left, + Right, + LeftRight, + Center, + Scale, +} + +impl ConstraintH { + pub fn from(value: u8) -> Option { + match value { + 0 => Some(Self::Left), + 1 => Some(Self::Right), + 2 => Some(Self::LeftRight), + 3 => Some(Self::Center), + 4 => Some(Self::Scale), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ConstraintV { + Top, + Bottom, + TopBottom, + Center, + Scale, +} + +impl ConstraintV { + pub fn from(value: u8) -> Option { + match value { + 0 => Some(Self::Top), + 1 => Some(Self::Bottom), + 2 => Some(Self::TopBottom), + 3 => Some(Self::Center), + 4 => Some(Self::Scale), + _ => None, + } + } +} + pub type Color = skia::Color; #[derive(Debug, Clone)] @@ -47,6 +93,8 @@ pub struct Shape { pub selrect: math::Rect, pub transform: Matrix, pub rotation: f32, + pub constraint_h: Option, + pub constraint_v: Option, pub clip_content: bool, pub fills: Vec, pub strokes: Vec, @@ -66,8 +114,10 @@ impl Shape { children: Vec::::new(), kind: Kind::Rect(math::Rect::new_empty(), None), selrect: math::Rect::new_empty(), - transform: Matrix::identity(), + transform: Matrix::default(), rotation: 0., + constraint_h: None, + constraint_v: None, clip_content: true, fills: vec![], strokes: vec![], @@ -111,13 +161,21 @@ impl Shape { } pub fn set_transform(&mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) { - self.transform = Matrix::new(a, b, c, d, e, f); + self.transform = Matrix::new_all(a, c, e, b, d, f, 0.0, 0.0, 1.0); } pub fn set_opacity(&mut self, opacity: f32) { self.opacity = opacity; } + pub fn set_constraint_h(&mut self, constraint: Option) { + self.constraint_h = constraint; + } + + pub fn set_constraint_v(&mut self, constraint: Option) { + self.constraint_v = constraint; + } + pub fn set_hidden(&mut self, value: bool) { self.hidden = value; } @@ -330,7 +388,7 @@ impl Shape { let center = self.bounds().center(); let mut matrix = skia::Matrix::new_identity(); matrix.pre_translate(center); - matrix.pre_concat(&self.transform.no_translation().to_skia_matrix().invert()?); + matrix.pre_concat(&self.transform.invert()?); matrix.pre_translate(-center); Some(matrix) diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 2e7645266..cad8d4c96 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -19,6 +19,13 @@ impl RawStopData { pub fn offset(&self) -> f32 { self.offset as f32 / 100.0 } + + pub fn from_bytes(bytes: [u8; 5]) -> Self { + Self { + color: [bytes[0], bytes[1], bytes[2], bytes[3]], + offset: bytes[4], + } + } } #[derive(Debug, Clone, PartialEq)] @@ -37,7 +44,7 @@ impl Gradient { self.offsets.push(offset); } - fn to_linear_shader(&self, rect: &math::Rect) -> skia::Shader { + fn to_linear_shader(&self, rect: &math::Rect) -> Option { let start = ( rect.left + self.start.0 * rect.width(), rect.top + self.start.1 * rect.height(), @@ -46,7 +53,7 @@ impl Gradient { rect.left + self.end.0 * rect.width(), rect.top + self.end.1 * rect.height(), ); - let shader = skia::shader::Shader::linear_gradient( + skia::shader::Shader::linear_gradient( (start, end), self.colors.as_slice(), self.offsets.as_slice(), @@ -54,11 +61,9 @@ impl Gradient { None, None, ) - .unwrap(); - shader } - fn to_radial_shader(&self, rect: &math::Rect) -> skia::Shader { + fn to_radial_shader(&self, rect: &math::Rect) -> Option { let center = skia::Point::new( rect.left + self.start.0 * rect.width(), rect.top + self.start.1 * rect.height(), @@ -80,7 +85,7 @@ impl Gradient { transform.pre_scale((self.width * rect.width() / rect.height(), 1.), None); transform.pre_translate((-center.x, -center.y)); - let shader = skia::shader::Shader::radial_gradient( + skia::shader::Shader::radial_gradient( center, distance, self.colors.as_slice(), @@ -89,8 +94,6 @@ impl Gradient { None, Some(&transform), ) - .unwrap(); - shader } } diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs new file mode 100644 index 000000000..2bfa7d76b --- /dev/null +++ b/render-wasm/src/shapes/modifiers.rs @@ -0,0 +1,40 @@ +use skia_safe as skia; + +use std::collections::HashSet; +use uuid::Uuid; + +use crate::shapes::{Shape, TransformEntry}; +use crate::state::State; + +fn propagate_shape(_state: &State, shape: &Shape, transform: skia::Matrix) -> Vec { + let children: Vec = shape + .children + .iter() + .map(|id| TransformEntry { + id: id.clone(), + transform, + }) + .collect(); + + children +} + +pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec { + let mut entries = modifiers.clone(); + let mut processed = HashSet::::new(); + let mut result = Vec::::new(); + + // Propagate the transform to children + while let Some(entry) = entries.pop() { + if !processed.contains(&entry.id) { + if let Some(shape) = state.shapes.get(&entry.id) { + let mut children = propagate_shape(state, shape, entry.transform); + entries.append(&mut children); + processed.insert(entry.id); + result.push(entry.clone()); + } + } + } + + result +} diff --git a/render-wasm/src/shapes/transform.rs b/render-wasm/src/shapes/transform.rs index 769a7ad38..b80b08e26 100644 --- a/render-wasm/src/shapes/transform.rs +++ b/render-wasm/src/shapes/transform.rs @@ -1,8 +1,9 @@ +use skia_safe as skia; use uuid::Uuid; -use crate::matrix::Matrix; use crate::mem::SerializableResult; use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet}; +use skia::Matrix; #[derive(PartialEq, Debug, Clone)] #[repr(C)] @@ -12,7 +13,7 @@ pub struct TransformEntry { } impl SerializableResult for TransformEntry { - type BytesType = [u8; size_of::()]; + type BytesType = [u8; 40]; fn from_bytes(bytes: Self::BytesType) -> Self { let id = uuid_from_u32_quartet( @@ -22,26 +23,35 @@ impl SerializableResult for TransformEntry { u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]), ); - let transform = Matrix::new( + let transform = Matrix::new_all( f32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]), - f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), f32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]), - f32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]), f32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]), + f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), + f32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]), f32::from_le_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]), + 0.0, + 0.0, + 1.0, ); - TransformEntry { id, transform } } fn as_bytes(&self) -> Self::BytesType { - let mut result: [u8; 40] = [0; 40]; + let mut result: Self::BytesType = [0; 40]; let (a, b, c, d) = uuid_to_u32_quartet(&self.id); result[0..4].clone_from_slice(&a.to_le_bytes()); result[4..8].clone_from_slice(&b.to_le_bytes()); result[8..12].clone_from_slice(&c.to_le_bytes()); result[12..16].clone_from_slice(&d.to_le_bytes()); - result[16..40].clone_from_slice(&self.transform.as_bytes()); + + result[16..20].clone_from_slice(&self.transform[0].to_le_bytes()); + result[20..24].clone_from_slice(&self.transform[3].to_le_bytes()); + result[24..28].clone_from_slice(&self.transform[1].to_le_bytes()); + result[28..32].clone_from_slice(&self.transform[4].to_le_bytes()); + result[32..36].clone_from_slice(&self.transform[2].to_le_bytes()); + result[36..40].clone_from_slice(&self.transform[5].to_le_bytes()); + result } @@ -61,7 +71,7 @@ mod tests { fn test_serialization() { let entry = TransformEntry { id: uuid!("550e8400-e29b-41d4-a716-446655440000"), - transform: Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), + transform: Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0), }; let bytes = entry.as_bytes(); diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index bd689aaef..aa760caea 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use crate::matrix; use skia_safe as skia; use uuid::Uuid; @@ -17,7 +16,7 @@ pub(crate) struct State<'a> { pub current_id: Option, pub current_shape: Option<&'a mut Shape>, pub shapes: HashMap, - pub modifiers: HashMap, + pub modifiers: HashMap, } impl<'a> State<'a> {