From 8c03cdb126d426de3a0857d70d85b7202d11cbe5 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Sat, 2 Aug 2025 21:58:00 -0600 Subject: [PATCH 01/26] added buttons to UI with preliminary icons --- crates/rnote-engine/src/engine/mod.rs | 8 +++++ .../selection-mirror-horizontal-symbolic.svg | 4 +++ .../selection-mirror-vertical-symbolic.svg | 4 +++ crates/rnote-ui/data/resources.gresource.xml | 2 ++ .../data/ui/penssidebar/selectorpage.ui | 22 ++++++++++++++ crates/rnote-ui/src/appwindow/actions.rs | 30 +++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg create mode 100644 crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 936c293fc2..6db30c6bea 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -753,6 +753,14 @@ impl Engine { | self.update_rendering_current_viewport() } + pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags { + todo!("horizontal mirror not implemented yet!"); + } + + pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { + todo!("vertical mirror not implemented yet!"); + } + pub fn select_with_bounds( &mut self, bounds: Aabb, diff --git a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg new file mode 100644 index 0000000000..09cbff8597 --- /dev/null +++ b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg new file mode 100644 index 0000000000..a3a021bda5 --- /dev/null +++ b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/crates/rnote-ui/data/resources.gresource.xml b/crates/rnote-ui/data/resources.gresource.xml index 5532364b4b..284b229293 100644 --- a/crates/rnote-ui/data/resources.gresource.xml +++ b/crates/rnote-ui/data/resources.gresource.xml @@ -115,6 +115,8 @@ icons/scalable/actions/return-origin-page-symbolic.svg icons/scalable/actions/save-symbolic.svg icons/scalable/actions/selection-deselect-all-symbolic.svg + icons/scalable/actions/selection-mirror-horizontal-symbolic.svg + icons/scalable/actions/selection-mirror-vertical-symbolic.svg icons/scalable/actions/selection-duplicate-symbolic.svg icons/scalable/actions/selection-invert-color-symbolic.svg icons/scalable/actions/selection-resize-lock-aspectratio-symbolic.svg diff --git a/crates/rnote-ui/data/ui/penssidebar/selectorpage.ui b/crates/rnote-ui/data/ui/penssidebar/selectorpage.ui index 705810264d..024d68aaae 100644 --- a/crates/rnote-ui/data/ui/penssidebar/selectorpage.ui +++ b/crates/rnote-ui/data/ui/penssidebar/selectorpage.ui @@ -100,6 +100,28 @@ + + + Mirror Selection Horizontally + win.selection-mirror-horizontal + selection-mirror-horizontal-symbolic + + + + + + Mirror Selection Vertically + win.selection-mirror-vertical + selection-mirror-vertical-symbolic + + + Invert Color Brightness of All Selected Strokes diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 740ca2245e..22e866e08d 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -87,6 +87,10 @@ impl RnAppWindow { self.add_action(&action_selection_select_all); let action_selection_deselect_all = gio::SimpleAction::new("selection-deselect-all", None); self.add_action(&action_selection_deselect_all); + let action_selection_mirror_horizontal = gio::SimpleAction::new("selection-mirror-horizontal", None); + self.add_action(&action_selection_mirror_horizontal); + let action_selection_mirror_vertical = gio::SimpleAction::new("selection-mirror-vertical", None); + self.add_action(&action_selection_mirror_vertical); let action_clear_doc = gio::SimpleAction::new("clear-doc", None); self.add_action(&action_clear_doc); let action_new_doc = gio::SimpleAction::new("new-doc", None); @@ -464,6 +468,32 @@ impl RnAppWindow { } )); + // Mirror selection horizontally + action_selection_mirror_horizontal.connect_activate(clone!( + #[weak(rename_to=appwindow)] + self, + move |_, _| { + let Some(canvas) = appwindow.active_tab_canvas() else { + return; + }; + let widget_flags = canvas.engine_mut().mirror_horizontal_selection(); + appwindow.handle_widget_flags(widget_flags, &canvas); + } + )); + + // Mirror selection vertically + action_selection_mirror_vertical.connect_activate(clone!( + #[weak(rename_to=appwindow)] + self, + move |_, _| { + let Some(canvas) = appwindow.active_tab_canvas() else { + return; + }; + let widget_flags = canvas.engine_mut().mirror_vertical_selection(); + appwindow.handle_widget_flags(widget_flags, &canvas); + } + )); + // Clear doc action_clear_doc.connect_activate(clone!( #[weak(rename_to=appwindow)] From d543167fe301b5ff2841c6b970b6b559e3ff8212 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Sun, 3 Aug 2025 07:10:17 -0600 Subject: [PATCH 02/26] horizontal mirroring is functional for BrushStrokes --- crates/rnote-engine/src/engine/mod.rs | 5 ++- crates/rnote-engine/src/store/stroke_comp.rs | 28 ++++++++++++++ crates/rnote-engine/src/strokes/stroke.rs | 39 ++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 6db30c6bea..e5e161100b 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -754,7 +754,10 @@ impl Engine { } pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags { - todo!("horizontal mirror not implemented yet!"); + self.store + .mirror_stroke_horizontal(&self.store.selection_keys_as_rendered()) + | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() } pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 7acfa08093..cb9e34c084 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -298,6 +298,34 @@ impl StrokeStore { widget_flags } + // Mirror stroke horizontally for given set of keys + // + // The strokes need to update rendering after mirror + pub(crate) fn mirror_stroke_horizontal(&mut self, keys: &[StrokeKey]) -> WidgetFlags { + let mut widget_flags = WidgetFlags::default(); + + if keys.is_empty() { + return widget_flags; + } + + keys.iter().for_each(|&key| { + if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) + .get_mut(key) + .map(Arc::make_mut) + { + { + stroke.horizontal_mirror(); + self.set_rendering_dirty(key); + } + } + }); + + widget_flags.redraw = true; + widget_flags.store_modified = true; + + widget_flags + } + /// Invert the stroke, text and fill color of the given keys. /// /// Strokes then need to update their rendering. diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index d426421554..329e91703e 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -672,4 +672,43 @@ impl Stroke { } } } + + pub fn horizontal_mirror(&mut self) { + let selection_centerline_x = self.bounds().center().x; + + match self { + Stroke::BrushStroke(brushstroke) => { + let current_penpath_elements = brushstroke.path.clone().into_elements(); + + let mut coords = current_penpath_elements + .iter() + .map(|element| { + element.pos + }).collect::>>(); + + // actually mirroring all the points + for coord in coords.iter_mut() { + coord.x -= selection_centerline_x; + coord.x *= -1.0; + coord.x += selection_centerline_x; + } + + let new_penpath_elements: Vec = current_penpath_elements + .iter() + .zip(coords.iter()) + .map(|(current_penpath_element, new_position)| Element { + pos: *new_position, + ..*current_penpath_element}).collect(); + + if let Some(new_penpath) = PenPath::try_from_elements(new_penpath_elements) { + brushstroke.path = new_penpath; + } + + } + Stroke::ShapeStroke(shape_stroke) => todo!("no shapes!"), + Stroke::TextStroke(text_stroke) => todo!(), + Stroke::VectorImage(vector_image) => todo!(), + Stroke::BitmapImage(bitmap_image) => todo!(), + } + } } From 039277ed9b58a2ba6b6b10148da3ee91605cae38 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Sun, 3 Aug 2025 08:57:01 -0600 Subject: [PATCH 03/26] center horizontal mirrors on selection area, not individual strokes --- crates/rnote-engine/src/store/stroke_comp.rs | 25 ++++++++++++++++---- crates/rnote-engine/src/strokes/stroke.rs | 4 +--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index cb9e34c084..d5d156006a 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -308,15 +308,32 @@ impl StrokeStore { return widget_flags; } + let all_stroke_bounds = self.strokes_bounds(keys); + + let min_x = all_stroke_bounds.iter() + .map(|aabb_element| aabb_element.mins.coords.x) + .reduce(|a, b| { + a.min(b) + }); + let max_x = all_stroke_bounds.iter() + .map(|aabb_element| aabb_element.maxs.coords.x) + .reduce(|a, b| { + a.max(b) + }); + + let selection_centerline_x = if let (Some(min_x), Some(max_x)) = (min_x, max_x) { + (min_x + max_x)/2.0 + } else { + return widget_flags; + }; + keys.iter().for_each(|&key| { if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) .get_mut(key) .map(Arc::make_mut) { - { - stroke.horizontal_mirror(); - self.set_rendering_dirty(key); - } + stroke.horizontal_mirror(selection_centerline_x); + self.set_rendering_dirty(key); } }); diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 329e91703e..c013bfe2d5 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -673,9 +673,7 @@ impl Stroke { } } - pub fn horizontal_mirror(&mut self) { - let selection_centerline_x = self.bounds().center().x; - + pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { let current_penpath_elements = brushstroke.path.clone().into_elements(); From 761ede062915653aa0b4a1dfc48471f1fffb4448 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Sun, 3 Aug 2025 21:09:36 -0600 Subject: [PATCH 04/26] horizontal mirroring of ShapeStrokes is fully functional --- crates/rnote-engine/src/strokes/stroke.rs | 81 +++++++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index c013bfe2d5..8a0f5743b9 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -673,6 +673,13 @@ impl Stroke { } } + // horizontally mirrors point around line 'x = selection_centerline_x' + fn mirror_point_x(point: &mut na::Vector2, selection_centerline_x: f64) { + point.x -= selection_centerline_x; + point.x *= -1.0; + point.x += selection_centerline_x; + } + pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { @@ -684,11 +691,8 @@ impl Stroke { element.pos }).collect::>>(); - // actually mirroring all the points for coord in coords.iter_mut() { - coord.x -= selection_centerline_x; - coord.x *= -1.0; - coord.x += selection_centerline_x; + Self::mirror_point_x(coord, selection_centerline_x); } let new_penpath_elements: Vec = current_penpath_elements @@ -703,10 +707,75 @@ impl Stroke { } } - Stroke::ShapeStroke(shape_stroke) => todo!("no shapes!"), - Stroke::TextStroke(text_stroke) => todo!(), + Stroke::ShapeStroke(shape_stroke) => { + // affine transformation matrix performing a reflection across line 'x = selection_centerline_x' + let mirror_transformation_x = na::Matrix3::new( + -1.0, 0.0, 2.0 * selection_centerline_x, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + ); + + match &mut shape_stroke.shape { + rnote_compose::Shape::Line(line) => { + Self::mirror_point_x(&mut line.start, selection_centerline_x); + Self::mirror_point_x(&mut line.end, selection_centerline_x); + } + rnote_compose::Shape::Arrow(arrow) => { + Self::mirror_point_x(&mut arrow.start, selection_centerline_x); + Self::mirror_point_x(&mut arrow.tip, selection_centerline_x); + } + rnote_compose::Shape::Rectangle(rectangle) => { + let transformed_affine_matrix = + mirror_transformation_x * rectangle.transform.affine.matrix(); + + rectangle.transform.affine = + na::Affine2::from_matrix_unchecked(transformed_affine_matrix); + } + rnote_compose::Shape::Ellipse(ellipse) => { + let transformed_affine_matrix = + mirror_transformation_x * ellipse.transform.affine.matrix(); + + ellipse.transform.affine = + na::Affine2::from_matrix_unchecked(transformed_affine_matrix); + } + rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { + for point in [ + &mut quadratic_bezier.start, + &mut quadratic_bezier.end, + &mut quadratic_bezier.cp, + ] { + Self::mirror_point_x(point, selection_centerline_x); + } + } + rnote_compose::Shape::CubicBezier(cubic_bezier) => { + for point in [ + &mut cubic_bezier.start, + &mut cubic_bezier.end, + &mut cubic_bezier.cp1, + &mut cubic_bezier.cp2, + ] { + Self::mirror_point_x(point, selection_centerline_x); + } + } + rnote_compose::Shape::Polyline(polyline) => { + Self::mirror_point_x(&mut polyline.start, selection_centerline_x); + + for point in polyline.path.iter_mut() { + Self::mirror_point_x(point, selection_centerline_x); + } + } + rnote_compose::Shape::Polygon(polygon) => { + Self::mirror_point_x(&mut polygon.start, selection_centerline_x); + + for point in polygon.path.iter_mut() { + Self::mirror_point_x(point, selection_centerline_x); + } + } + } + } Stroke::VectorImage(vector_image) => todo!(), Stroke::BitmapImage(bitmap_image) => todo!(), + Stroke::TextStroke(_) => {} } } } From 8555e6d49bcdfc92777076b2c14df4df19f0f1de Mon Sep 17 00:00:00 2001 From: podzolelements Date: Sun, 3 Aug 2025 21:41:26 -0600 Subject: [PATCH 05/26] apply code formatting rules, no functional changes --- crates/rnote-engine/src/store/stroke_comp.rs | 16 +++++++------- crates/rnote-engine/src/strokes/stroke.rs | 22 ++++++++++---------- crates/rnote-ui/src/appwindow/actions.rs | 6 ++++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index d5d156006a..5db0f40f74 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -310,19 +310,17 @@ impl StrokeStore { let all_stroke_bounds = self.strokes_bounds(keys); - let min_x = all_stroke_bounds.iter() + let min_x = all_stroke_bounds + .iter() .map(|aabb_element| aabb_element.mins.coords.x) - .reduce(|a, b| { - a.min(b) - }); - let max_x = all_stroke_bounds.iter() + .reduce(|a, b| a.min(b)); + let max_x = all_stroke_bounds + .iter() .map(|aabb_element| aabb_element.maxs.coords.x) - .reduce(|a, b| { - a.max(b) - }); + .reduce(|a, b| a.max(b)); let selection_centerline_x = if let (Some(min_x), Some(max_x)) = (min_x, max_x) { - (min_x + max_x)/2.0 + (min_x + max_x) / 2.0 } else { return widget_flags; }; diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 8a0f5743b9..bdd5790d0b 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -687,10 +687,9 @@ impl Stroke { let mut coords = current_penpath_elements .iter() - .map(|element| { - element.pos - }).collect::>>(); - + .map(|element| element.pos) + .collect::>>(); + for coord in coords.iter_mut() { Self::mirror_point_x(coord, selection_centerline_x); } @@ -700,20 +699,21 @@ impl Stroke { .zip(coords.iter()) .map(|(current_penpath_element, new_position)| Element { pos: *new_position, - ..*current_penpath_element}).collect(); + ..*current_penpath_element + }) + .collect(); if let Some(new_penpath) = PenPath::try_from_elements(new_penpath_elements) { brushstroke.path = new_penpath; } - } Stroke::ShapeStroke(shape_stroke) => { // affine transformation matrix performing a reflection across line 'x = selection_centerline_x' - let mirror_transformation_x = na::Matrix3::new( - -1.0, 0.0, 2.0 * selection_centerline_x, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - ); + let mirror_transformation_x = na::matrix![ + -1.0, 0.0, 2.0 * selection_centerline_x; + 0.0, 1.0, 0.0; + 0.0, 0.0, 1.0; + ]; match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 22e866e08d..1f7d603f7f 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -87,9 +87,11 @@ impl RnAppWindow { self.add_action(&action_selection_select_all); let action_selection_deselect_all = gio::SimpleAction::new("selection-deselect-all", None); self.add_action(&action_selection_deselect_all); - let action_selection_mirror_horizontal = gio::SimpleAction::new("selection-mirror-horizontal", None); + let action_selection_mirror_horizontal = + gio::SimpleAction::new("selection-mirror-horizontal", None); self.add_action(&action_selection_mirror_horizontal); - let action_selection_mirror_vertical = gio::SimpleAction::new("selection-mirror-vertical", None); + let action_selection_mirror_vertical = + gio::SimpleAction::new("selection-mirror-vertical", None); self.add_action(&action_selection_mirror_vertical); let action_clear_doc = gio::SimpleAction::new("clear-doc", None); self.add_action(&action_clear_doc); From a5c3d41e833dddc4cbbc2e38c35d92705cabe023 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Mon, 4 Aug 2025 19:11:42 -0600 Subject: [PATCH 06/26] horizontal mirroring functional on both bitmapped and vector images --- crates/rnote-engine/src/strokes/stroke.rs | 126 +++++++++++----------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index bdd5790d0b..e4d1c75dff 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -680,6 +680,19 @@ impl Stroke { point.x += selection_centerline_x; } + // performs a matrix transform on 2d affine matrix causing a reflection across line 'x = selection_centerline_x' + fn mirror_affine_x(affine: &mut na::Affine2, selection_centerline_x: f64) { + let mirror_transformation_x = na::matrix![ + -1.0, 0.0, 2.0 * selection_centerline_x; + 0.0, 1.0, 0.0; + 0.0, 0.0, 1.0; + ]; + + let transformed_affine = mirror_transformation_x * affine.matrix(); + + *affine = na::Affine2::from_matrix_unchecked(transformed_affine); + } + pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { @@ -707,74 +720,67 @@ impl Stroke { brushstroke.path = new_penpath; } } - Stroke::ShapeStroke(shape_stroke) => { - // affine transformation matrix performing a reflection across line 'x = selection_centerline_x' - let mirror_transformation_x = na::matrix![ - -1.0, 0.0, 2.0 * selection_centerline_x; - 0.0, 1.0, 0.0; - 0.0, 0.0, 1.0; - ]; - - match &mut shape_stroke.shape { - rnote_compose::Shape::Line(line) => { - Self::mirror_point_x(&mut line.start, selection_centerline_x); - Self::mirror_point_x(&mut line.end, selection_centerline_x); - } - rnote_compose::Shape::Arrow(arrow) => { - Self::mirror_point_x(&mut arrow.start, selection_centerline_x); - Self::mirror_point_x(&mut arrow.tip, selection_centerline_x); - } - rnote_compose::Shape::Rectangle(rectangle) => { - let transformed_affine_matrix = - mirror_transformation_x * rectangle.transform.affine.matrix(); - - rectangle.transform.affine = - na::Affine2::from_matrix_unchecked(transformed_affine_matrix); - } - rnote_compose::Shape::Ellipse(ellipse) => { - let transformed_affine_matrix = - mirror_transformation_x * ellipse.transform.affine.matrix(); - - ellipse.transform.affine = - na::Affine2::from_matrix_unchecked(transformed_affine_matrix); - } - rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { - for point in [ - &mut quadratic_bezier.start, - &mut quadratic_bezier.end, - &mut quadratic_bezier.cp, - ] { - Self::mirror_point_x(point, selection_centerline_x); - } + Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { + rnote_compose::Shape::Line(line) => { + Self::mirror_point_x(&mut line.start, selection_centerline_x); + Self::mirror_point_x(&mut line.end, selection_centerline_x); + } + rnote_compose::Shape::Arrow(arrow) => { + Self::mirror_point_x(&mut arrow.start, selection_centerline_x); + Self::mirror_point_x(&mut arrow.tip, selection_centerline_x); + } + rnote_compose::Shape::Rectangle(rectangle) => { + Self::mirror_affine_x(&mut rectangle.transform.affine, selection_centerline_x); + } + rnote_compose::Shape::Ellipse(ellipse) => { + Self::mirror_affine_x(&mut ellipse.transform.affine, selection_centerline_x); + } + rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { + for point in [ + &mut quadratic_bezier.start, + &mut quadratic_bezier.end, + &mut quadratic_bezier.cp, + ] { + Self::mirror_point_x(point, selection_centerline_x); } - rnote_compose::Shape::CubicBezier(cubic_bezier) => { - for point in [ - &mut cubic_bezier.start, - &mut cubic_bezier.end, - &mut cubic_bezier.cp1, - &mut cubic_bezier.cp2, - ] { - Self::mirror_point_x(point, selection_centerline_x); - } + } + rnote_compose::Shape::CubicBezier(cubic_bezier) => { + for point in [ + &mut cubic_bezier.start, + &mut cubic_bezier.end, + &mut cubic_bezier.cp1, + &mut cubic_bezier.cp2, + ] { + Self::mirror_point_x(point, selection_centerline_x); } - rnote_compose::Shape::Polyline(polyline) => { - Self::mirror_point_x(&mut polyline.start, selection_centerline_x); + } + rnote_compose::Shape::Polyline(polyline) => { + Self::mirror_point_x(&mut polyline.start, selection_centerline_x); - for point in polyline.path.iter_mut() { - Self::mirror_point_x(point, selection_centerline_x); - } + for point in polyline.path.iter_mut() { + Self::mirror_point_x(point, selection_centerline_x); } - rnote_compose::Shape::Polygon(polygon) => { - Self::mirror_point_x(&mut polygon.start, selection_centerline_x); + } + rnote_compose::Shape::Polygon(polygon) => { + Self::mirror_point_x(&mut polygon.start, selection_centerline_x); - for point in polygon.path.iter_mut() { - Self::mirror_point_x(point, selection_centerline_x); - } + for point in polygon.path.iter_mut() { + Self::mirror_point_x(point, selection_centerline_x); } } + }, + Stroke::VectorImage(vector_image) => { + Self::mirror_affine_x( + &mut vector_image.rectangle.transform.affine, + selection_centerline_x, + ); + } + Stroke::BitmapImage(bitmap_image) => { + Self::mirror_affine_x( + &mut bitmap_image.rectangle.transform.affine, + selection_centerline_x, + ); } - Stroke::VectorImage(vector_image) => todo!(), - Stroke::BitmapImage(bitmap_image) => todo!(), Stroke::TextStroke(_) => {} } } From 1eaaff62308beadbf455d6d0086858db5122fb69 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Tue, 5 Aug 2025 19:30:12 -0600 Subject: [PATCH 07/26] vertical mirroring fully implemented --- crates/rnote-engine/src/engine/mod.rs | 5 +- crates/rnote-engine/src/store/stroke_comp.rs | 43 +++++++ crates/rnote-engine/src/strokes/stroke.rs | 112 +++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index e5e161100b..28dcc7e464 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -761,7 +761,10 @@ impl Engine { } pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { - todo!("vertical mirror not implemented yet!"); + self.store + .mirror_stroke_vertical(&self.store.selection_keys_as_rendered()) + | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() } pub fn select_with_bounds( diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 5db0f40f74..4cc9088837 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -341,6 +341,49 @@ impl StrokeStore { widget_flags } + /// Mirror stroke vertically for given set of keys + /// + /// The strokes must update rendering after a mirror + pub(crate) fn mirror_stroke_vertical(&mut self, keys: &[StrokeKey]) -> WidgetFlags { + let mut widget_flags = WidgetFlags::default(); + + if keys.is_empty() { + return widget_flags; + } + + let all_stroke_bounds = self.strokes_bounds(keys); + + let min_y = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.mins.coords.y) + .reduce(|a, b| a.min(b)); + let max_y = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.maxs.coords.y) + .reduce(|a, b| a.max(b)); + + let selection_centerline_y = if let (Some(min_y), Some(max_y)) = (min_y, max_y) { + (min_y + max_y) / 2.0 + } else { + return widget_flags; + }; + + keys.iter().for_each(|&key| { + if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) + .get_mut(key) + .map(Arc::make_mut) + { + stroke.vertical_mirror(selection_centerline_y); + self.set_rendering_dirty(key); + } + }); + + widget_flags.redraw = true; + widget_flags.store_modified = true; + + widget_flags + } + /// Invert the stroke, text and fill color of the given keys. /// /// Strokes then need to update their rendering. diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index e4d1c75dff..94fecc7791 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -784,4 +784,116 @@ impl Stroke { Stroke::TextStroke(_) => {} } } + + /// vertically mirrors point around line 'y = selection_centerline_y' + fn mirror_point_y(point: &mut na::Vector2, selection_centerline_y: f64) { + point.y -= selection_centerline_y; + point.y *= -1.0; + point.y += selection_centerline_y; + } + + /// performs a matrix transform on 2d affine matrix causing a reflection across line 'y = selection_centerline_y' + fn mirror_affine_y(affine: &mut na::Affine2, selection_centerline_y: f64) { + let mirror_transformation_y = na::matrix![ + 1.0, 0.0, 0.0; + 0.0, -1.0, 2.0 * selection_centerline_y; + 0.0, 0.0, 1.0; + ]; + + let transformed_affine = mirror_transformation_y * affine.matrix(); + + *affine = na::Affine2::from_matrix_unchecked(transformed_affine); + } + + pub fn vertical_mirror(&mut self, selection_centerline_y: f64) { + match self { + Stroke::BrushStroke(brushstroke) => { + let current_penpath_elements = brushstroke.path.clone().into_elements(); + + let mut coords = current_penpath_elements + .iter() + .map(|element| element.pos) + .collect::>>(); + + for coord in coords.iter_mut() { + Self::mirror_point_y(coord, selection_centerline_y); + } + + let new_penpath_elements: Vec = current_penpath_elements + .iter() + .zip(coords.iter()) + .map(|(current_penpath_element, new_position)| Element { + pos: *new_position, + ..*current_penpath_element + }) + .collect(); + + if let Some(new_penpath) = PenPath::try_from_elements(new_penpath_elements) { + brushstroke.path = new_penpath; + } + } + Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { + rnote_compose::Shape::Line(line) => { + Self::mirror_point_y(&mut line.start, selection_centerline_y); + Self::mirror_point_y(&mut line.end, selection_centerline_y); + } + rnote_compose::Shape::Arrow(arrow) => { + Self::mirror_point_y(&mut arrow.start, selection_centerline_y); + Self::mirror_point_y(&mut arrow.tip, selection_centerline_y); + } + rnote_compose::Shape::Rectangle(rectangle) => { + Self::mirror_affine_y(&mut rectangle.transform.affine, selection_centerline_y); + } + rnote_compose::Shape::Ellipse(ellipse) => { + Self::mirror_affine_y(&mut ellipse.transform.affine, selection_centerline_y); + } + rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { + for point in [ + &mut quadratic_bezier.start, + &mut quadratic_bezier.end, + &mut quadratic_bezier.cp, + ] { + Self::mirror_point_y(point, selection_centerline_y); + } + } + rnote_compose::Shape::CubicBezier(cubic_bezier) => { + for point in [ + &mut cubic_bezier.start, + &mut cubic_bezier.end, + &mut cubic_bezier.cp1, + &mut cubic_bezier.cp2, + ] { + Self::mirror_point_y(point, selection_centerline_y); + } + } + rnote_compose::Shape::Polyline(polyline) => { + Self::mirror_point_y(&mut polyline.start, selection_centerline_y); + + for point in polyline.path.iter_mut() { + Self::mirror_point_y(point, selection_centerline_y); + } + } + rnote_compose::Shape::Polygon(polygon) => { + Self::mirror_point_y(&mut polygon.start, selection_centerline_y); + + for point in polygon.path.iter_mut() { + Self::mirror_point_y(point, selection_centerline_y); + } + } + }, + Stroke::VectorImage(vector_image) => { + Self::mirror_affine_y( + &mut vector_image.rectangle.transform.affine, + selection_centerline_y, + ); + } + Stroke::BitmapImage(bitmap_image) => { + Self::mirror_affine_y( + &mut bitmap_image.rectangle.transform.affine, + selection_centerline_y, + ); + } + Stroke::TextStroke(_) => {} + } + } } From a40d12abd1676d02e80226f79627df4f30ab4275 Mon Sep 17 00:00:00 2001 From: podzolelements Date: Thu, 7 Aug 2025 09:24:54 -0600 Subject: [PATCH 08/26] update icons --- .../selection-mirror-horizontal-symbolic.svg | 151 +++++++++++++++++- .../selection-mirror-vertical-symbolic.svg | 151 +++++++++++++++++- 2 files changed, 294 insertions(+), 8 deletions(-) diff --git a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg index 09cbff8597..13202cdd0e 100644 --- a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg +++ b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-horizontal-symbolic.svg @@ -1,4 +1,147 @@ - - - - \ No newline at end of file + +b'Body'b'Body' diff --git a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg index a3a021bda5..b93d496862 100644 --- a/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg +++ b/crates/rnote-ui/data/icons/scalable/actions/selection-mirror-vertical-symbolic.svg @@ -1,4 +1,147 @@ - - - - \ No newline at end of file + +b'Body'b'Body' From bd036d3336385d3ba99c61d289d057e62b43d6b1 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Thu, 21 Aug 2025 11:16:03 -0600 Subject: [PATCH 09/26] condense mirror_stroke_* functions with orientation parameter --- crates/rnote-engine/src/engine/mod.rs | 15 +-- crates/rnote-engine/src/store/stroke_comp.rs | 106 ++++++++----------- 2 files changed, 56 insertions(+), 65 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 28dcc7e464..5f77888510 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -23,6 +23,7 @@ use crate::pens::PenMode; use crate::pens::{Pen, PenStyle}; use crate::store::StrokeKey; use crate::store::render_comp::{self, RenderCompState}; +use crate::store::stroke_comp::MirrorOrientation; use crate::strokes::content::GeneratedContentImages; use crate::strokes::textstroke::{TextAttribute, TextStyle}; use crate::{AudioPlayer, SelectionCollision, WidgetFlags, render}; @@ -754,16 +755,18 @@ impl Engine { } pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags { - self.store - .mirror_stroke_horizontal(&self.store.selection_keys_as_rendered()) - | self.record(Instant::now()) + self.store.mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Horizontal, + ) | self.record(Instant::now()) | self.update_content_rendering_current_viewport() } pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { - self.store - .mirror_stroke_vertical(&self.store.selection_keys_as_rendered()) - | self.record(Instant::now()) + self.store.mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Vertical, + ) | self.record(Instant::now()) | self.update_content_rendering_current_viewport() } diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 4cc9088837..f702c31ec6 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -15,6 +15,12 @@ use std::sync::Arc; #[cfg(feature = "ui")] use tracing::error; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MirrorOrientation { + Horizontal, + Vertical, +} + /// Systems that are related to the stroke components. impl StrokeStore { /// Gets a immutable reference to a stroke. @@ -298,10 +304,14 @@ impl StrokeStore { widget_flags } - // Mirror stroke horizontally for given set of keys - // - // The strokes need to update rendering after mirror - pub(crate) fn mirror_stroke_horizontal(&mut self, keys: &[StrokeKey]) -> WidgetFlags { + /// Mirror stroke either horizontally or vertically for given set of keys + /// + /// The strokes need to update rendering after mirror + pub(crate) fn mirror_stroke( + &mut self, + keys: &[StrokeKey], + orientation: MirrorOrientation, + ) -> WidgetFlags { let mut widget_flags = WidgetFlags::default(); if keys.is_empty() { @@ -310,70 +320,48 @@ impl StrokeStore { let all_stroke_bounds = self.strokes_bounds(keys); - let min_x = all_stroke_bounds - .iter() - .map(|aabb_element| aabb_element.mins.coords.x) - .reduce(|a, b| a.min(b)); - let max_x = all_stroke_bounds - .iter() - .map(|aabb_element| aabb_element.maxs.coords.x) - .reduce(|a, b| a.max(b)); - - let selection_centerline_x = if let (Some(min_x), Some(max_x)) = (min_x, max_x) { - (min_x + max_x) / 2.0 - } else { - return widget_flags; - }; - - keys.iter().for_each(|&key| { - if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) - .get_mut(key) - .map(Arc::make_mut) - { - stroke.horizontal_mirror(selection_centerline_x); - self.set_rendering_dirty(key); + let min_component; + let max_component; + + match orientation { + MirrorOrientation::Horizontal => { + min_component = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.mins.coords.x) + .reduce(|a, b| a.min(b)); + max_component = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.maxs.coords.x) + .reduce(|a, b| a.max(b)); + } + MirrorOrientation::Vertical => { + min_component = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.mins.coords.y) + .reduce(|a, b| a.min(b)); + max_component = all_stroke_bounds + .iter() + .map(|aabb_element| aabb_element.maxs.coords.y) + .reduce(|a, b| a.max(b)); } - }); - - widget_flags.redraw = true; - widget_flags.store_modified = true; - - widget_flags - } - - /// Mirror stroke vertically for given set of keys - /// - /// The strokes must update rendering after a mirror - pub(crate) fn mirror_stroke_vertical(&mut self, keys: &[StrokeKey]) -> WidgetFlags { - let mut widget_flags = WidgetFlags::default(); - - if keys.is_empty() { - return widget_flags; } - let all_stroke_bounds = self.strokes_bounds(keys); - - let min_y = all_stroke_bounds - .iter() - .map(|aabb_element| aabb_element.mins.coords.y) - .reduce(|a, b| a.min(b)); - let max_y = all_stroke_bounds - .iter() - .map(|aabb_element| aabb_element.maxs.coords.y) - .reduce(|a, b| a.max(b)); - - let selection_centerline_y = if let (Some(min_y), Some(max_y)) = (min_y, max_y) { - (min_y + max_y) / 2.0 - } else { - return widget_flags; - }; + let selection_centerline = + if let (Some(min_component), Some(max_component)) = (min_component, max_component) { + (min_component + max_component) / 2.0 + } else { + return widget_flags; + }; keys.iter().for_each(|&key| { if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) .get_mut(key) .map(Arc::make_mut) { - stroke.vertical_mirror(selection_centerline_y); + match orientation { + MirrorOrientation::Horizontal => stroke.horizontal_mirror(selection_centerline), + MirrorOrientation::Vertical => stroke.vertical_mirror(selection_centerline), + } self.set_rendering_dirty(key); } }); From 7fe6cc3dc8350cf80bb88d5648e86d377a254fce Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Thu, 21 Aug 2025 11:50:08 -0600 Subject: [PATCH 10/26] move point mirroring functions into rnote-compose --- crates/rnote-compose/src/lib.rs | 2 + crates/rnote-compose/src/point_utils.rs | 13 +++++ crates/rnote-engine/src/strokes/stroke.rs | 59 +++++++++-------------- 3 files changed, 38 insertions(+), 36 deletions(-) create mode 100644 crates/rnote-compose/src/point_utils.rs diff --git a/crates/rnote-compose/src/lib.rs b/crates/rnote-compose/src/lib.rs index 5810cb0a7d..875dee2a62 100644 --- a/crates/rnote-compose/src/lib.rs +++ b/crates/rnote-compose/src/lib.rs @@ -19,6 +19,8 @@ pub mod ext; pub mod penevent; /// module for pen paths pub mod penpath; +/// module for misc operations on points +pub mod point_utils; /// utilities for serializing / deserializing pub mod serialize; /// module for shapes diff --git a/crates/rnote-compose/src/point_utils.rs b/crates/rnote-compose/src/point_utils.rs new file mode 100644 index 0000000000..7c120375e5 --- /dev/null +++ b/crates/rnote-compose/src/point_utils.rs @@ -0,0 +1,13 @@ +/// horizontally mirrors point around line 'x = centerline_x' +pub fn mirror_point_x(point: &mut na::Vector2, centerline_x: f64) { + point.x -= centerline_x; + point.x *= -1.0; + point.x += centerline_x; +} + +/// vertically mirrors point around line 'y = centerline_y' +pub fn mirror_point_y(point: &mut na::Vector2, centerline_y: f64) { + point.y -= centerline_y; + point.y *= -1.0; + point.y += centerline_y; +} \ No newline at end of file diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 94fecc7791..2bdd802b7e 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -12,6 +12,7 @@ use crate::{Engine, render}; use p2d::bounding_volume::Aabb; use rnote_compose::ext::AabbExt; use rnote_compose::penpath::Element; +use rnote_compose::point_utils; use rnote_compose::shapes::{Rectangle, Shapeable}; use rnote_compose::style::smooth::SmoothOptions; use rnote_compose::transform::Transform; @@ -673,13 +674,6 @@ impl Stroke { } } - // horizontally mirrors point around line 'x = selection_centerline_x' - fn mirror_point_x(point: &mut na::Vector2, selection_centerline_x: f64) { - point.x -= selection_centerline_x; - point.x *= -1.0; - point.x += selection_centerline_x; - } - // performs a matrix transform on 2d affine matrix causing a reflection across line 'x = selection_centerline_x' fn mirror_affine_x(affine: &mut na::Affine2, selection_centerline_x: f64) { let mirror_transformation_x = na::matrix![ @@ -704,7 +698,7 @@ impl Stroke { .collect::>>(); for coord in coords.iter_mut() { - Self::mirror_point_x(coord, selection_centerline_x); + point_utils::mirror_point_x(coord, selection_centerline_x); } let new_penpath_elements: Vec = current_penpath_elements @@ -722,12 +716,12 @@ impl Stroke { } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { - Self::mirror_point_x(&mut line.start, selection_centerline_x); - Self::mirror_point_x(&mut line.end, selection_centerline_x); + point_utils::mirror_point_x(&mut line.start, selection_centerline_x); + point_utils::mirror_point_x(&mut line.end, selection_centerline_x); } rnote_compose::Shape::Arrow(arrow) => { - Self::mirror_point_x(&mut arrow.start, selection_centerline_x); - Self::mirror_point_x(&mut arrow.tip, selection_centerline_x); + point_utils::mirror_point_x(&mut arrow.start, selection_centerline_x); + point_utils::mirror_point_x(&mut arrow.tip, selection_centerline_x); } rnote_compose::Shape::Rectangle(rectangle) => { Self::mirror_affine_x(&mut rectangle.transform.affine, selection_centerline_x); @@ -741,7 +735,7 @@ impl Stroke { &mut quadratic_bezier.end, &mut quadratic_bezier.cp, ] { - Self::mirror_point_x(point, selection_centerline_x); + point_utils::mirror_point_x(point, selection_centerline_x); } } rnote_compose::Shape::CubicBezier(cubic_bezier) => { @@ -751,21 +745,21 @@ impl Stroke { &mut cubic_bezier.cp1, &mut cubic_bezier.cp2, ] { - Self::mirror_point_x(point, selection_centerline_x); + point_utils::mirror_point_x(point, selection_centerline_x); } } rnote_compose::Shape::Polyline(polyline) => { - Self::mirror_point_x(&mut polyline.start, selection_centerline_x); + point_utils::mirror_point_x(&mut polyline.start, selection_centerline_x); for point in polyline.path.iter_mut() { - Self::mirror_point_x(point, selection_centerline_x); + point_utils::mirror_point_x(point, selection_centerline_x); } } rnote_compose::Shape::Polygon(polygon) => { - Self::mirror_point_x(&mut polygon.start, selection_centerline_x); + point_utils::mirror_point_x(&mut polygon.start, selection_centerline_x); for point in polygon.path.iter_mut() { - Self::mirror_point_x(point, selection_centerline_x); + point_utils::mirror_point_x(point, selection_centerline_x); } } }, @@ -785,13 +779,6 @@ impl Stroke { } } - /// vertically mirrors point around line 'y = selection_centerline_y' - fn mirror_point_y(point: &mut na::Vector2, selection_centerline_y: f64) { - point.y -= selection_centerline_y; - point.y *= -1.0; - point.y += selection_centerline_y; - } - /// performs a matrix transform on 2d affine matrix causing a reflection across line 'y = selection_centerline_y' fn mirror_affine_y(affine: &mut na::Affine2, selection_centerline_y: f64) { let mirror_transformation_y = na::matrix![ @@ -816,7 +803,7 @@ impl Stroke { .collect::>>(); for coord in coords.iter_mut() { - Self::mirror_point_y(coord, selection_centerline_y); + point_utils::mirror_point_y(coord, selection_centerline_y); } let new_penpath_elements: Vec = current_penpath_elements @@ -834,12 +821,12 @@ impl Stroke { } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { - Self::mirror_point_y(&mut line.start, selection_centerline_y); - Self::mirror_point_y(&mut line.end, selection_centerline_y); + point_utils::mirror_point_y(&mut line.start, selection_centerline_y); + point_utils::mirror_point_y(&mut line.end, selection_centerline_y); } rnote_compose::Shape::Arrow(arrow) => { - Self::mirror_point_y(&mut arrow.start, selection_centerline_y); - Self::mirror_point_y(&mut arrow.tip, selection_centerline_y); + point_utils::mirror_point_y(&mut arrow.start, selection_centerline_y); + point_utils::mirror_point_y(&mut arrow.tip, selection_centerline_y); } rnote_compose::Shape::Rectangle(rectangle) => { Self::mirror_affine_y(&mut rectangle.transform.affine, selection_centerline_y); @@ -853,7 +840,7 @@ impl Stroke { &mut quadratic_bezier.end, &mut quadratic_bezier.cp, ] { - Self::mirror_point_y(point, selection_centerline_y); + point_utils::mirror_point_y(point, selection_centerline_y); } } rnote_compose::Shape::CubicBezier(cubic_bezier) => { @@ -863,21 +850,21 @@ impl Stroke { &mut cubic_bezier.cp1, &mut cubic_bezier.cp2, ] { - Self::mirror_point_y(point, selection_centerline_y); + point_utils::mirror_point_y(point, selection_centerline_y); } } rnote_compose::Shape::Polyline(polyline) => { - Self::mirror_point_y(&mut polyline.start, selection_centerline_y); + point_utils::mirror_point_y(&mut polyline.start, selection_centerline_y); for point in polyline.path.iter_mut() { - Self::mirror_point_y(point, selection_centerline_y); + point_utils::mirror_point_y(point, selection_centerline_y); } } rnote_compose::Shape::Polygon(polygon) => { - Self::mirror_point_y(&mut polygon.start, selection_centerline_y); + point_utils::mirror_point_y(&mut polygon.start, selection_centerline_y); for point in polygon.path.iter_mut() { - Self::mirror_point_y(point, selection_centerline_y); + point_utils::mirror_point_y(point, selection_centerline_y); } } }, From 0f6465922b77985a1f9b4b3a4e71eb4d5df46025 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 12:09:48 -0600 Subject: [PATCH 11/26] move affine matrix transformations into Transform --- crates/rnote-compose/src/point_utils.rs | 2 +- crates/rnote-compose/src/transform/mod.rs | 26 ++++++++ crates/rnote-engine/src/strokes/stroke.rs | 74 +++++++++-------------- 3 files changed, 55 insertions(+), 47 deletions(-) diff --git a/crates/rnote-compose/src/point_utils.rs b/crates/rnote-compose/src/point_utils.rs index 7c120375e5..6ddb86519a 100644 --- a/crates/rnote-compose/src/point_utils.rs +++ b/crates/rnote-compose/src/point_utils.rs @@ -10,4 +10,4 @@ pub fn mirror_point_y(point: &mut na::Vector2, centerline_y: f64) { point.y -= centerline_y; point.y *= -1.0; point.y += centerline_y; -} \ No newline at end of file +} diff --git a/crates/rnote-compose/src/transform/mod.rs b/crates/rnote-compose/src/transform/mod.rs index d6be392f15..dae3231733 100644 --- a/crates/rnote-compose/src/transform/mod.rs +++ b/crates/rnote-compose/src/transform/mod.rs @@ -120,6 +120,32 @@ impl Transform { .unwrap(); } + /// Apply a reflection across line 'x = centerline_x' to the affine matrix + pub fn append_mirror_x_mut(&mut self, centerline_x: f64) { + let mirror_transformation_x = na::matrix![ + -1.0, 0.0, 2.0 * centerline_x; + 0.0, 1.0, 0.0; + 0.0, 0.0, 1.0; + ]; + + let transformed_affine = mirror_transformation_x * self.affine.matrix(); + + self.affine = na::Affine2::from_matrix_unchecked(transformed_affine); + } + + /// Apply a reflection across line 'y = centerline_y' to the affine matrix + pub fn append_mirror_y_mut(&mut self, centerline_y: f64) { + let mirror_transformation_y = na::matrix![ + 1.0, 0.0, 0.0; + 0.0, -1.0, 2.0 * centerline_y; + 0.0, 0.0, 1.0; + ]; + + let transformed_affine = mirror_transformation_y * self.affine.matrix(); + + self.affine = na::Affine2::from_matrix_unchecked(transformed_affine); + } + /// Convert the transform to a Svg attribute string, insertable into svg elements. pub fn to_svg_transform_attr_str(&self) -> String { let matrix = self.affine; diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 2bdd802b7e..a782aac580 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -674,19 +674,6 @@ impl Stroke { } } - // performs a matrix transform on 2d affine matrix causing a reflection across line 'x = selection_centerline_x' - fn mirror_affine_x(affine: &mut na::Affine2, selection_centerline_x: f64) { - let mirror_transformation_x = na::matrix![ - -1.0, 0.0, 2.0 * selection_centerline_x; - 0.0, 1.0, 0.0; - 0.0, 0.0, 1.0; - ]; - - let transformed_affine = mirror_transformation_x * affine.matrix(); - - *affine = na::Affine2::from_matrix_unchecked(transformed_affine); - } - pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { @@ -724,10 +711,14 @@ impl Stroke { point_utils::mirror_point_x(&mut arrow.tip, selection_centerline_x); } rnote_compose::Shape::Rectangle(rectangle) => { - Self::mirror_affine_x(&mut rectangle.transform.affine, selection_centerline_x); + rectangle + .transform + .append_mirror_x_mut(selection_centerline_x); } rnote_compose::Shape::Ellipse(ellipse) => { - Self::mirror_affine_x(&mut ellipse.transform.affine, selection_centerline_x); + ellipse + .transform + .append_mirror_x_mut(selection_centerline_x); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { for point in [ @@ -764,34 +755,21 @@ impl Stroke { } }, Stroke::VectorImage(vector_image) => { - Self::mirror_affine_x( - &mut vector_image.rectangle.transform.affine, - selection_centerline_x, - ); + vector_image + .rectangle + .transform + .append_mirror_x_mut(selection_centerline_x); } Stroke::BitmapImage(bitmap_image) => { - Self::mirror_affine_x( - &mut bitmap_image.rectangle.transform.affine, - selection_centerline_x, - ); + bitmap_image + .rectangle + .transform + .append_mirror_x_mut(selection_centerline_x); } Stroke::TextStroke(_) => {} } } - /// performs a matrix transform on 2d affine matrix causing a reflection across line 'y = selection_centerline_y' - fn mirror_affine_y(affine: &mut na::Affine2, selection_centerline_y: f64) { - let mirror_transformation_y = na::matrix![ - 1.0, 0.0, 0.0; - 0.0, -1.0, 2.0 * selection_centerline_y; - 0.0, 0.0, 1.0; - ]; - - let transformed_affine = mirror_transformation_y * affine.matrix(); - - *affine = na::Affine2::from_matrix_unchecked(transformed_affine); - } - pub fn vertical_mirror(&mut self, selection_centerline_y: f64) { match self { Stroke::BrushStroke(brushstroke) => { @@ -829,10 +807,14 @@ impl Stroke { point_utils::mirror_point_y(&mut arrow.tip, selection_centerline_y); } rnote_compose::Shape::Rectangle(rectangle) => { - Self::mirror_affine_y(&mut rectangle.transform.affine, selection_centerline_y); + rectangle + .transform + .append_mirror_y_mut(selection_centerline_y); } rnote_compose::Shape::Ellipse(ellipse) => { - Self::mirror_affine_y(&mut ellipse.transform.affine, selection_centerline_y); + ellipse + .transform + .append_mirror_y_mut(selection_centerline_y); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { for point in [ @@ -869,16 +851,16 @@ impl Stroke { } }, Stroke::VectorImage(vector_image) => { - Self::mirror_affine_y( - &mut vector_image.rectangle.transform.affine, - selection_centerline_y, - ); + vector_image + .rectangle + .transform + .append_mirror_y_mut(selection_centerline_y); } Stroke::BitmapImage(bitmap_image) => { - Self::mirror_affine_y( - &mut bitmap_image.rectangle.transform.affine, - selection_centerline_y, - ); + bitmap_image + .rectangle + .transform + .append_mirror_y_mut(selection_centerline_y); } Stroke::TextStroke(_) => {} } From d95cbb499661e598236a12613454335472b52c68 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 13:49:26 -0600 Subject: [PATCH 12/26] add mirroring to Segments and Elements; internalize PenPath mirroring --- crates/rnote-compose/src/penpath/element.rs | 12 +++++- crates/rnote-compose/src/penpath/mod.rs | 18 ++++++++ crates/rnote-compose/src/penpath/segment.rs | 38 +++++++++++++++- crates/rnote-engine/src/strokes/stroke.rs | 48 +-------------------- 4 files changed, 68 insertions(+), 48 deletions(-) diff --git a/crates/rnote-compose/src/penpath/element.rs b/crates/rnote-compose/src/penpath/element.rs index a7f8696813..8142c8fddb 100644 --- a/crates/rnote-compose/src/penpath/element.rs +++ b/crates/rnote-compose/src/penpath/element.rs @@ -1,5 +1,5 @@ // Imports -use crate::transform::Transformable; +use crate::{point_utils, transform::Transformable}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -65,4 +65,14 @@ impl Element { pub fn transform_by(&mut self, transform: na::Affine2) { self.pos = transform.transform_point(&self.pos.into()).coords; } + + /// Mirrors position of element around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.pos, centerline_x); + } + + /// Mirrors position of element around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.pos, centerline_y); + } } diff --git a/crates/rnote-compose/src/penpath/mod.rs b/crates/rnote-compose/src/penpath/mod.rs index 0bbb1d1d7c..a2fe7887d4 100644 --- a/crates/rnote-compose/src/penpath/mod.rs +++ b/crates/rnote-compose/src/penpath/mod.rs @@ -142,6 +142,24 @@ impl PenPath { Some(Self { start, segments }) } + /// Mirrors the path around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.start.mirror_x(centerline_x); + + for element in &mut self.segments { + element.mirror_x(centerline_x); + } + } + + /// Mirrors the path around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.start.mirror_y(centerline_y); + + for element in &mut self.segments { + element.mirror_y(centerline_y); + } + } + /// Checks whether bounds collide with the path. If it does, it returns the indices of the colliding segments /// /// `loosened` loosens the segments hitboxes by the value diff --git a/crates/rnote-compose/src/penpath/segment.rs b/crates/rnote-compose/src/penpath/segment.rs index b8e4cae6c1..c5858cdfb7 100644 --- a/crates/rnote-compose/src/penpath/segment.rs +++ b/crates/rnote-compose/src/penpath/segment.rs @@ -1,6 +1,6 @@ // Imports use super::Element; -use crate::transform::Transformable; +use crate::{point_utils, transform::Transformable}; use serde::{Deserialize, Serialize}; /// A single segment, usually of a pen path. @@ -106,4 +106,40 @@ impl Segment { Segment::CubBezTo { end, .. } => *end, } } + + /// Mirrors position of segment around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + match self { + Segment::LineTo { end } => { + end.mirror_x(centerline_x); + } + Segment::QuadBezTo { cp, end } => { + point_utils::mirror_point_x(cp, centerline_x); + end.mirror_x(centerline_x); + } + Segment::CubBezTo { cp1, cp2, end } => { + point_utils::mirror_point_x(cp1, centerline_x); + point_utils::mirror_point_x(cp2, centerline_x); + end.mirror_x(centerline_x); + } + } + } + + /// Mirrors position of segment around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + match self { + Segment::LineTo { end } => { + end.mirror_y(centerline_y); + } + Segment::QuadBezTo { cp, end } => { + point_utils::mirror_point_y(cp, centerline_y); + end.mirror_y(centerline_y); + } + Segment::CubBezTo { cp1, cp2, end } => { + point_utils::mirror_point_y(cp1, centerline_y); + point_utils::mirror_point_y(cp2, centerline_y); + end.mirror_y(centerline_y); + } + } + } } diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index a782aac580..b4455bf476 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -677,29 +677,7 @@ impl Stroke { pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { - let current_penpath_elements = brushstroke.path.clone().into_elements(); - - let mut coords = current_penpath_elements - .iter() - .map(|element| element.pos) - .collect::>>(); - - for coord in coords.iter_mut() { - point_utils::mirror_point_x(coord, selection_centerline_x); - } - - let new_penpath_elements: Vec = current_penpath_elements - .iter() - .zip(coords.iter()) - .map(|(current_penpath_element, new_position)| Element { - pos: *new_position, - ..*current_penpath_element - }) - .collect(); - - if let Some(new_penpath) = PenPath::try_from_elements(new_penpath_elements) { - brushstroke.path = new_penpath; - } + brushstroke.path.mirror_x(selection_centerline_x); } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { @@ -773,29 +751,7 @@ impl Stroke { pub fn vertical_mirror(&mut self, selection_centerline_y: f64) { match self { Stroke::BrushStroke(brushstroke) => { - let current_penpath_elements = brushstroke.path.clone().into_elements(); - - let mut coords = current_penpath_elements - .iter() - .map(|element| element.pos) - .collect::>>(); - - for coord in coords.iter_mut() { - point_utils::mirror_point_y(coord, selection_centerline_y); - } - - let new_penpath_elements: Vec = current_penpath_elements - .iter() - .zip(coords.iter()) - .map(|(current_penpath_element, new_position)| Element { - pos: *new_position, - ..*current_penpath_element - }) - .collect(); - - if let Some(new_penpath) = PenPath::try_from_elements(new_penpath_elements) { - brushstroke.path = new_penpath; - } + brushstroke.path.mirror_y(selection_centerline_y); } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { From ee8fd4dbd88d0e00ff72559c6a3dec0346df2f70 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 15:10:00 -0600 Subject: [PATCH 13/26] move mirroring logic out of Stroke for point-based shapes --- crates/rnote-compose/src/shapes/arrow.rs | 14 +++- crates/rnote-compose/src/shapes/cubbez.rs | 15 +++++ crates/rnote-compose/src/shapes/line.rs | 14 +++- crates/rnote-compose/src/shapes/polygon.rs | 19 ++++++ crates/rnote-compose/src/shapes/polyline.rs | 20 +++++- crates/rnote-compose/src/shapes/quadbez.rs | 15 +++++ crates/rnote-engine/src/strokes/stroke.rs | 71 ++++----------------- 7 files changed, 106 insertions(+), 62 deletions(-) diff --git a/crates/rnote-compose/src/shapes/arrow.rs b/crates/rnote-compose/src/shapes/arrow.rs index 66a567866d..cd0c2cb0ab 100644 --- a/crates/rnote-compose/src/shapes/arrow.rs +++ b/crates/rnote-compose/src/shapes/arrow.rs @@ -1,8 +1,8 @@ // Imports use super::Line; -use crate::ext::Vector2Ext; use crate::shapes::Shapeable; use crate::transform::Transformable; +use crate::{ext::Vector2Ext, point_utils}; use kurbo::{PathEl, Shape}; use na::Rotation2; use p2d::bounding_volume::Aabb; @@ -113,6 +113,18 @@ impl Arrow { .collect::>() } + /// Mirrors arrow around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + point_utils::mirror_point_x(&mut self.tip, centerline_x); + } + + /// Mirrors arrow around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + point_utils::mirror_point_y(&mut self.tip, centerline_y); + } + /// Convert to kurbo shape. pub fn to_kurbo(&self, stroke_width: Option) -> kurbo::BezPath { let mut bez_path = diff --git a/crates/rnote-compose/src/shapes/cubbez.rs b/crates/rnote-compose/src/shapes/cubbez.rs index 3d8e0fc82a..1b7289b6be 100644 --- a/crates/rnote-compose/src/shapes/cubbez.rs +++ b/crates/rnote-compose/src/shapes/cubbez.rs @@ -2,6 +2,7 @@ use super::line::Line; use super::quadbez::QuadraticBezier; use crate::ext::{KurboShapeExt, Vector2Ext}; +use crate::point_utils; use crate::shapes::Shapeable; use crate::transform::Transformable; use kurbo::Shape; @@ -142,6 +143,20 @@ impl CubicBezier { ) } + /// Mirrors cubic bezier around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_x(point, centerline_x); + } + } + + /// Mirrors cubic bezier around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_y(point, centerline_y); + } + } + /// Approximate a cubic with a quadratic bezier curve. pub fn approx_with_quadbez(&self) -> QuadraticBezier { let start = self.start; diff --git a/crates/rnote-compose/src/shapes/line.rs b/crates/rnote-compose/src/shapes/line.rs index 0451690418..3e451e823a 100644 --- a/crates/rnote-compose/src/shapes/line.rs +++ b/crates/rnote-compose/src/shapes/line.rs @@ -1,9 +1,9 @@ // Imports -use crate::Transform; use crate::ext::{AabbExt, Vector2Ext}; use crate::shapes::Rectangle; use crate::shapes::Shapeable; use crate::transform::Transformable; +use crate::{Transform, point_utils}; use kurbo::Shape; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -95,4 +95,16 @@ impl Line { }) .collect::>() } + + /// Mirrors Line around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + point_utils::mirror_point_x(&mut self.end, centerline_x); + } + + /// Mirrors Line around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + point_utils::mirror_point_y(&mut self.end, centerline_y); + } } diff --git a/crates/rnote-compose/src/shapes/polygon.rs b/crates/rnote-compose/src/shapes/polygon.rs index 6ba8619512..092387ebd2 100644 --- a/crates/rnote-compose/src/shapes/polygon.rs +++ b/crates/rnote-compose/src/shapes/polygon.rs @@ -1,6 +1,7 @@ // Imports use super::{Line, Shapeable}; use crate::ext::{AabbExt, Vector2Ext}; +use crate::point_utils; use crate::transform::Transformable; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -90,6 +91,24 @@ impl Polygon { path: Vec::new(), } } + + /// Mirrors polygon around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline_x); + } + } + + /// Mirrors polygon around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Extend> for Polygon { diff --git a/crates/rnote-compose/src/shapes/polyline.rs b/crates/rnote-compose/src/shapes/polyline.rs index 9e4bb4d92e..c5e50009be 100644 --- a/crates/rnote-compose/src/shapes/polyline.rs +++ b/crates/rnote-compose/src/shapes/polyline.rs @@ -1,7 +1,7 @@ // Imports use super::{Line, Shapeable}; -use crate::ext::Vector2Ext; use crate::transform::Transformable; +use crate::{ext::Vector2Ext, point_utils}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -87,6 +87,24 @@ impl Polyline { path: Vec::new(), } } + + /// Mirrors polyline around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline_x); + } + } + + /// Mirrors polyline around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Extend> for Polyline { diff --git a/crates/rnote-compose/src/shapes/quadbez.rs b/crates/rnote-compose/src/shapes/quadbez.rs index 0d7d4997e0..5d414bb511 100644 --- a/crates/rnote-compose/src/shapes/quadbez.rs +++ b/crates/rnote-compose/src/shapes/quadbez.rs @@ -2,6 +2,7 @@ use super::CubicBezier; use super::line::Line; use crate::ext::{KurboShapeExt, Vector2Ext}; +use crate::point_utils; use crate::shapes::Shapeable; use crate::transform::Transformable; use kurbo::Shape; @@ -92,6 +93,20 @@ impl QuadraticBezier { (first_split, second_split) } + /// Mirrors quadratic bezier around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_x(point, centerline_x); + } + } + + /// Mirrors quadratic bezier around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_y(point, centerline_y); + } + } + /// Convert to a cubic bezier (raising the order of a bezier curve is without losses). pub fn to_cubic_bezier(&self) -> CubicBezier { CubicBezier { diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index b4455bf476..7715731843 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -12,7 +12,6 @@ use crate::{Engine, render}; use p2d::bounding_volume::Aabb; use rnote_compose::ext::AabbExt; use rnote_compose::penpath::Element; -use rnote_compose::point_utils; use rnote_compose::shapes::{Rectangle, Shapeable}; use rnote_compose::style::smooth::SmoothOptions; use rnote_compose::transform::Transform; @@ -681,12 +680,10 @@ impl Stroke { } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { - point_utils::mirror_point_x(&mut line.start, selection_centerline_x); - point_utils::mirror_point_x(&mut line.end, selection_centerline_x); + line.mirror_x(selection_centerline_x); } rnote_compose::Shape::Arrow(arrow) => { - point_utils::mirror_point_x(&mut arrow.start, selection_centerline_x); - point_utils::mirror_point_x(&mut arrow.tip, selection_centerline_x); + arrow.mirror_x(selection_centerline_x); } rnote_compose::Shape::Rectangle(rectangle) => { rectangle @@ -699,37 +696,16 @@ impl Stroke { .append_mirror_x_mut(selection_centerline_x); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { - for point in [ - &mut quadratic_bezier.start, - &mut quadratic_bezier.end, - &mut quadratic_bezier.cp, - ] { - point_utils::mirror_point_x(point, selection_centerline_x); - } + quadratic_bezier.mirror_x(selection_centerline_x); } rnote_compose::Shape::CubicBezier(cubic_bezier) => { - for point in [ - &mut cubic_bezier.start, - &mut cubic_bezier.end, - &mut cubic_bezier.cp1, - &mut cubic_bezier.cp2, - ] { - point_utils::mirror_point_x(point, selection_centerline_x); - } + cubic_bezier.mirror_x(selection_centerline_x); } rnote_compose::Shape::Polyline(polyline) => { - point_utils::mirror_point_x(&mut polyline.start, selection_centerline_x); - - for point in polyline.path.iter_mut() { - point_utils::mirror_point_x(point, selection_centerline_x); - } + polyline.mirror_x(selection_centerline_x); } rnote_compose::Shape::Polygon(polygon) => { - point_utils::mirror_point_x(&mut polygon.start, selection_centerline_x); - - for point in polygon.path.iter_mut() { - point_utils::mirror_point_x(point, selection_centerline_x); - } + polygon.mirror_x(selection_centerline_x); } }, Stroke::VectorImage(vector_image) => { @@ -755,12 +731,10 @@ impl Stroke { } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { - point_utils::mirror_point_y(&mut line.start, selection_centerline_y); - point_utils::mirror_point_y(&mut line.end, selection_centerline_y); + line.mirror_y(selection_centerline_y); } rnote_compose::Shape::Arrow(arrow) => { - point_utils::mirror_point_y(&mut arrow.start, selection_centerline_y); - point_utils::mirror_point_y(&mut arrow.tip, selection_centerline_y); + arrow.mirror_y(selection_centerline_y); } rnote_compose::Shape::Rectangle(rectangle) => { rectangle @@ -773,37 +747,16 @@ impl Stroke { .append_mirror_y_mut(selection_centerline_y); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { - for point in [ - &mut quadratic_bezier.start, - &mut quadratic_bezier.end, - &mut quadratic_bezier.cp, - ] { - point_utils::mirror_point_y(point, selection_centerline_y); - } + quadratic_bezier.mirror_y(selection_centerline_y); } rnote_compose::Shape::CubicBezier(cubic_bezier) => { - for point in [ - &mut cubic_bezier.start, - &mut cubic_bezier.end, - &mut cubic_bezier.cp1, - &mut cubic_bezier.cp2, - ] { - point_utils::mirror_point_y(point, selection_centerline_y); - } + cubic_bezier.mirror_y(selection_centerline_y); } rnote_compose::Shape::Polyline(polyline) => { - point_utils::mirror_point_y(&mut polyline.start, selection_centerline_y); - - for point in polyline.path.iter_mut() { - point_utils::mirror_point_y(point, selection_centerline_y); - } + polyline.mirror_y(selection_centerline_y); } rnote_compose::Shape::Polygon(polygon) => { - point_utils::mirror_point_y(&mut polygon.start, selection_centerline_y); - - for point in polygon.path.iter_mut() { - point_utils::mirror_point_y(point, selection_centerline_y); - } + polygon.mirror_y(selection_centerline_y); } }, Stroke::VectorImage(vector_image) => { From 9036e09dae2b8de7b82405255a9abfeb320763a9 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 15:39:21 -0600 Subject: [PATCH 14/26] add mirroring for transform-based shapes --- crates/rnote-compose/src/shapes/ellipse.rs | 10 ++++++ crates/rnote-compose/src/shapes/rectangle.rs | 10 ++++++ .../rnote-engine/src/strokes/bitmapimage.rs | 10 ++++++ crates/rnote-engine/src/strokes/stroke.rs | 36 +++++-------------- .../rnote-engine/src/strokes/vectorimage.rs | 10 ++++++ 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/crates/rnote-compose/src/shapes/ellipse.rs b/crates/rnote-compose/src/shapes/ellipse.rs index 287c0256af..74b7eef5a5 100644 --- a/crates/rnote-compose/src/shapes/ellipse.rs +++ b/crates/rnote-compose/src/shapes/ellipse.rs @@ -112,4 +112,14 @@ impl Ellipse { lines } + + /// Mirrors ellipse around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.transform.append_mirror_x_mut(centerline_x); + } + + /// Mirrors ellipse around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.transform.append_mirror_y_mut(centerline_y); + } } diff --git a/crates/rnote-compose/src/shapes/rectangle.rs b/crates/rnote-compose/src/shapes/rectangle.rs index c0cc3f5e54..20659dae02 100644 --- a/crates/rnote-compose/src/shapes/rectangle.rs +++ b/crates/rnote-compose/src/shapes/rectangle.rs @@ -150,4 +150,14 @@ impl Rectangle { }, ] } + + /// Mirrors rectangle around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.transform.append_mirror_x_mut(centerline_x); + } + + /// Mirrors rectangle around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.transform.append_mirror_y_mut(centerline_y); + } } diff --git a/crates/rnote-engine/src/strokes/bitmapimage.rs b/crates/rnote-engine/src/strokes/bitmapimage.rs index 74f24ab898..0eb46355ee 100644 --- a/crates/rnote-engine/src/strokes/bitmapimage.rs +++ b/crates/rnote-engine/src/strokes/bitmapimage.rs @@ -231,4 +231,14 @@ impl BitmapImage { }) .collect() } + + /// Mirrors bitmapped image around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.rectangle.mirror_x(centerline_x); + } + + /// Mirrors bitmapped image around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.rectangle.mirror_y(centerline_y); + } } diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 7715731843..8b4dd6a4d2 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -686,14 +686,10 @@ impl Stroke { arrow.mirror_x(selection_centerline_x); } rnote_compose::Shape::Rectangle(rectangle) => { - rectangle - .transform - .append_mirror_x_mut(selection_centerline_x); + rectangle.mirror_x(selection_centerline_x); } rnote_compose::Shape::Ellipse(ellipse) => { - ellipse - .transform - .append_mirror_x_mut(selection_centerline_x); + ellipse.mirror_x(selection_centerline_x); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { quadratic_bezier.mirror_x(selection_centerline_x); @@ -709,16 +705,10 @@ impl Stroke { } }, Stroke::VectorImage(vector_image) => { - vector_image - .rectangle - .transform - .append_mirror_x_mut(selection_centerline_x); + vector_image.mirror_x(selection_centerline_x); } Stroke::BitmapImage(bitmap_image) => { - bitmap_image - .rectangle - .transform - .append_mirror_x_mut(selection_centerline_x); + bitmap_image.mirror_x(selection_centerline_x); } Stroke::TextStroke(_) => {} } @@ -737,14 +727,10 @@ impl Stroke { arrow.mirror_y(selection_centerline_y); } rnote_compose::Shape::Rectangle(rectangle) => { - rectangle - .transform - .append_mirror_y_mut(selection_centerline_y); + rectangle.mirror_y(selection_centerline_y); } rnote_compose::Shape::Ellipse(ellipse) => { - ellipse - .transform - .append_mirror_y_mut(selection_centerline_y); + ellipse.mirror_y(selection_centerline_y); } rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { quadratic_bezier.mirror_y(selection_centerline_y); @@ -760,16 +746,10 @@ impl Stroke { } }, Stroke::VectorImage(vector_image) => { - vector_image - .rectangle - .transform - .append_mirror_y_mut(selection_centerline_y); + vector_image.mirror_y(selection_centerline_y); } Stroke::BitmapImage(bitmap_image) => { - bitmap_image - .rectangle - .transform - .append_mirror_y_mut(selection_centerline_y); + bitmap_image.mirror_y(selection_centerline_y); } Stroke::TextStroke(_) => {} } diff --git a/crates/rnote-engine/src/strokes/vectorimage.rs b/crates/rnote-engine/src/strokes/vectorimage.rs index 611b2874a9..d44a728625 100644 --- a/crates/rnote-engine/src/strokes/vectorimage.rs +++ b/crates/rnote-engine/src/strokes/vectorimage.rs @@ -336,4 +336,14 @@ impl VectorImage { }) .collect() } + + /// Mirrors vector image around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.rectangle.mirror_x(centerline_x); + } + + /// Mirrors vector image around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.rectangle.mirror_y(centerline_y); + } } From 01d37ce49fbd37a4bde34cdea57f8e98bbbae6a4 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 15:51:01 -0600 Subject: [PATCH 15/26] add direct mirroring to BrushStrokes --- crates/rnote-engine/src/strokes/brushstroke.rs | 10 ++++++++++ crates/rnote-engine/src/strokes/stroke.rs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/rnote-engine/src/strokes/brushstroke.rs b/crates/rnote-engine/src/strokes/brushstroke.rs index b3d344135c..8387c1cb69 100644 --- a/crates/rnote-engine/src/strokes/brushstroke.rs +++ b/crates/rnote-engine/src/strokes/brushstroke.rs @@ -290,6 +290,16 @@ impl BrushStroke { self.update_geometry(); } + /// Mirrors brushstroke around line 'x = centerline_x' + pub fn mirror_x(&mut self, centerline_x: f64) { + self.path.mirror_x(centerline_x); + } + + /// Mirrors brushstroke around line 'y = centerline_y' + pub fn mirror_y(&mut self, centerline_y: f64) { + self.path.mirror_y(centerline_y); + } + // internal method generating the current hitboxes. fn gen_hitboxes_int(&self) -> Vec { let stroke_width = self.style.stroke_width(); diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 8b4dd6a4d2..c6796917ff 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -676,7 +676,7 @@ impl Stroke { pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { match self { Stroke::BrushStroke(brushstroke) => { - brushstroke.path.mirror_x(selection_centerline_x); + brushstroke.mirror_x(selection_centerline_x); } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { @@ -717,7 +717,7 @@ impl Stroke { pub fn vertical_mirror(&mut self, selection_centerline_y: f64) { match self { Stroke::BrushStroke(brushstroke) => { - brushstroke.path.mirror_y(selection_centerline_y); + brushstroke.mirror_y(selection_centerline_y); } Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { rnote_compose::Shape::Line(line) => { From fa56d6fbc65e96d636a57c6f72717fdd4bb4aaf9 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 17:19:19 -0600 Subject: [PATCH 16/26] add mirroring as an operation to the Transformable trait --- crates/rnote-compose/src/penpath/element.rs | 18 ++- crates/rnote-compose/src/penpath/mod.rs | 34 +++-- crates/rnote-compose/src/penpath/segment.rs | 32 +++-- crates/rnote-compose/src/shapes/arrow.rs | 22 ++-- crates/rnote-compose/src/shapes/cubbez.rs | 26 ++-- crates/rnote-compose/src/shapes/ellipse.rs | 18 ++- crates/rnote-compose/src/shapes/line.rs | 22 ++-- crates/rnote-compose/src/shapes/polygon.rs | 34 +++-- crates/rnote-compose/src/shapes/polyline.rs | 34 +++-- crates/rnote-compose/src/shapes/quadbez.rs | 26 ++-- crates/rnote-compose/src/shapes/rectangle.rs | 18 ++- crates/rnote-compose/src/shapes/shape.rs | 58 +++++++++ crates/rnote-compose/src/transform/mod.rs | 8 ++ .../src/transform/transformable.rs | 4 + crates/rnote-engine/src/render.rs | 8 ++ crates/rnote-engine/src/store/stroke_comp.rs | 4 +- .../rnote-engine/src/strokes/bitmapimage.rs | 18 ++- .../rnote-engine/src/strokes/brushstroke.rs | 18 ++- .../rnote-engine/src/strokes/shapestroke.rs | 8 ++ crates/rnote-engine/src/strokes/stroke.rs | 122 ++++++------------ crates/rnote-engine/src/strokes/textstroke.rs | 5 + .../rnote-engine/src/strokes/vectorimage.rs | 18 ++- 22 files changed, 288 insertions(+), 267 deletions(-) diff --git a/crates/rnote-compose/src/penpath/element.rs b/crates/rnote-compose/src/penpath/element.rs index 8142c8fddb..c5994ee943 100644 --- a/crates/rnote-compose/src/penpath/element.rs +++ b/crates/rnote-compose/src/penpath/element.rs @@ -35,6 +35,14 @@ impl Transformable for Element { fn scale(&mut self, scale: na::Vector2) { self.pos = self.pos.component_mul(&scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.pos, centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.pos, centerline_y); + } } impl Element { @@ -65,14 +73,4 @@ impl Element { pub fn transform_by(&mut self, transform: na::Affine2) { self.pos = transform.transform_point(&self.pos.into()).coords; } - - /// Mirrors position of element around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.pos, centerline_x); - } - - /// Mirrors position of element around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.pos, centerline_y); - } } diff --git a/crates/rnote-compose/src/penpath/mod.rs b/crates/rnote-compose/src/penpath/mod.rs index a2fe7887d4..3774cb11b4 100644 --- a/crates/rnote-compose/src/penpath/mod.rs +++ b/crates/rnote-compose/src/penpath/mod.rs @@ -98,6 +98,22 @@ impl Transformable for PenPath { segment.scale(scale); }); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.start.mirror_x(centerline_x); + + for element in &mut self.segments { + element.mirror_x(centerline_x); + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.start.mirror_y(centerline_y); + + for element in &mut self.segments { + element.mirror_y(centerline_y); + } + } } impl PenPath { @@ -142,24 +158,6 @@ impl PenPath { Some(Self { start, segments }) } - /// Mirrors the path around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.start.mirror_x(centerline_x); - - for element in &mut self.segments { - element.mirror_x(centerline_x); - } - } - - /// Mirrors the path around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.start.mirror_y(centerline_y); - - for element in &mut self.segments { - element.mirror_y(centerline_y); - } - } - /// Checks whether bounds collide with the path. If it does, it returns the indices of the colliding segments /// /// `loosened` loosens the segments hitboxes by the value diff --git a/crates/rnote-compose/src/penpath/segment.rs b/crates/rnote-compose/src/penpath/segment.rs index c5858cdfb7..d678149451 100644 --- a/crates/rnote-compose/src/penpath/segment.rs +++ b/crates/rnote-compose/src/penpath/segment.rs @@ -93,22 +93,8 @@ impl Transformable for Segment { } } } -} - -impl Segment { - /// The end element of a segment. - /// - /// All segment variants have an end element. - pub fn end(&self) -> Element { - match self { - Segment::LineTo { end, .. } => *end, - Segment::QuadBezTo { end, .. } => *end, - Segment::CubBezTo { end, .. } => *end, - } - } - /// Mirrors position of segment around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { + fn mirror_x(&mut self, centerline_x: f64) { match self { Segment::LineTo { end } => { end.mirror_x(centerline_x); @@ -125,8 +111,7 @@ impl Segment { } } - /// Mirrors position of segment around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { + fn mirror_y(&mut self, centerline_y: f64) { match self { Segment::LineTo { end } => { end.mirror_y(centerline_y); @@ -143,3 +128,16 @@ impl Segment { } } } + +impl Segment { + /// The end element of a segment. + /// + /// All segment variants have an end element. + pub fn end(&self) -> Element { + match self { + Segment::LineTo { end, .. } => *end, + Segment::QuadBezTo { end, .. } => *end, + Segment::CubBezTo { end, .. } => *end, + } + } +} diff --git a/crates/rnote-compose/src/shapes/arrow.rs b/crates/rnote-compose/src/shapes/arrow.rs index cd0c2cb0ab..55176aab05 100644 --- a/crates/rnote-compose/src/shapes/arrow.rs +++ b/crates/rnote-compose/src/shapes/arrow.rs @@ -56,6 +56,16 @@ impl Transformable for Arrow { self.start = self.start.component_mul(&scale); self.tip = self.tip.component_mul(&scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + point_utils::mirror_point_x(&mut self.tip, centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + point_utils::mirror_point_y(&mut self.tip, centerline_y); + } } impl Shapeable for Arrow { @@ -113,18 +123,6 @@ impl Arrow { .collect::>() } - /// Mirrors arrow around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - point_utils::mirror_point_x(&mut self.tip, centerline_x); - } - - /// Mirrors arrow around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - point_utils::mirror_point_y(&mut self.tip, centerline_y); - } - /// Convert to kurbo shape. pub fn to_kurbo(&self, stroke_width: Option) -> kurbo::BezPath { let mut bez_path = diff --git a/crates/rnote-compose/src/shapes/cubbez.rs b/crates/rnote-compose/src/shapes/cubbez.rs index 1b7289b6be..38ddd7fe2b 100644 --- a/crates/rnote-compose/src/shapes/cubbez.rs +++ b/crates/rnote-compose/src/shapes/cubbez.rs @@ -51,6 +51,18 @@ impl Transformable for CubicBezier { self.cp2 = self.cp2.component_mul(&scale); self.end = self.end.component_mul(&scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_x(point, centerline_x); + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Shapeable for CubicBezier { @@ -143,20 +155,6 @@ impl CubicBezier { ) } - /// Mirrors cubic bezier around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_x(point, centerline_x); - } - } - - /// Mirrors cubic bezier around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_y(point, centerline_y); - } - } - /// Approximate a cubic with a quadratic bezier curve. pub fn approx_with_quadbez(&self) -> QuadraticBezier { let start = self.start; diff --git a/crates/rnote-compose/src/shapes/ellipse.rs b/crates/rnote-compose/src/shapes/ellipse.rs index 74b7eef5a5..35c780830c 100644 --- a/crates/rnote-compose/src/shapes/ellipse.rs +++ b/crates/rnote-compose/src/shapes/ellipse.rs @@ -41,6 +41,14 @@ impl Transformable for Ellipse { fn scale(&mut self, scale: na::Vector2) { self.transform.append_scale_mut(scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.transform.append_mirror_x_mut(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.transform.append_mirror_y_mut(centerline_y); + } } impl Shapeable for Ellipse { @@ -112,14 +120,4 @@ impl Ellipse { lines } - - /// Mirrors ellipse around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.transform.append_mirror_x_mut(centerline_x); - } - - /// Mirrors ellipse around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.transform.append_mirror_y_mut(centerline_y); - } } diff --git a/crates/rnote-compose/src/shapes/line.rs b/crates/rnote-compose/src/shapes/line.rs index 3e451e823a..491c459079 100644 --- a/crates/rnote-compose/src/shapes/line.rs +++ b/crates/rnote-compose/src/shapes/line.rs @@ -38,6 +38,16 @@ impl Transformable for Line { self.start = self.start.component_mul(&scale); self.end = self.end.component_mul(&scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + point_utils::mirror_point_x(&mut self.end, centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + point_utils::mirror_point_y(&mut self.end, centerline_y); + } } impl Shapeable for Line { @@ -95,16 +105,4 @@ impl Line { }) .collect::>() } - - /// Mirrors Line around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - point_utils::mirror_point_x(&mut self.end, centerline_x); - } - - /// Mirrors Line around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - point_utils::mirror_point_y(&mut self.end, centerline_y); - } } diff --git a/crates/rnote-compose/src/shapes/polygon.rs b/crates/rnote-compose/src/shapes/polygon.rs index 092387ebd2..2c56ded535 100644 --- a/crates/rnote-compose/src/shapes/polygon.rs +++ b/crates/rnote-compose/src/shapes/polygon.rs @@ -42,6 +42,22 @@ impl Transformable for Polygon { *p = p.component_mul(&scale); } } + + fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline_x); + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Shapeable for Polygon { @@ -91,24 +107,6 @@ impl Polygon { path: Vec::new(), } } - - /// Mirrors polygon around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline_x); - } - } - - /// Mirrors polygon around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline_y); - } - } } impl Extend> for Polygon { diff --git a/crates/rnote-compose/src/shapes/polyline.rs b/crates/rnote-compose/src/shapes/polyline.rs index c5e50009be..402c04adad 100644 --- a/crates/rnote-compose/src/shapes/polyline.rs +++ b/crates/rnote-compose/src/shapes/polyline.rs @@ -41,6 +41,22 @@ impl Transformable for Polyline { *p = p.component_mul(&scale); } } + + fn mirror_x(&mut self, centerline_x: f64) { + point_utils::mirror_point_x(&mut self.start, centerline_x); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline_x); + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + point_utils::mirror_point_y(&mut self.start, centerline_y); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Shapeable for Polyline { @@ -87,24 +103,6 @@ impl Polyline { path: Vec::new(), } } - - /// Mirrors polyline around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline_x); - } - } - - /// Mirrors polyline around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline_y); - } - } } impl Extend> for Polyline { diff --git a/crates/rnote-compose/src/shapes/quadbez.rs b/crates/rnote-compose/src/shapes/quadbez.rs index 5d414bb511..4185c773fa 100644 --- a/crates/rnote-compose/src/shapes/quadbez.rs +++ b/crates/rnote-compose/src/shapes/quadbez.rs @@ -45,6 +45,18 @@ impl Transformable for QuadraticBezier { self.cp = self.cp.component_mul(&scale); self.end = self.end.component_mul(&scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_x(point, centerline_x); + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_y(point, centerline_y); + } + } } impl Shapeable for QuadraticBezier { @@ -93,20 +105,6 @@ impl QuadraticBezier { (first_split, second_split) } - /// Mirrors quadratic bezier around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_x(point, centerline_x); - } - } - - /// Mirrors quadratic bezier around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_y(point, centerline_y); - } - } - /// Convert to a cubic bezier (raising the order of a bezier curve is without losses). pub fn to_cubic_bezier(&self) -> CubicBezier { CubicBezier { diff --git a/crates/rnote-compose/src/shapes/rectangle.rs b/crates/rnote-compose/src/shapes/rectangle.rs index 20659dae02..b8e1c99b8c 100644 --- a/crates/rnote-compose/src/shapes/rectangle.rs +++ b/crates/rnote-compose/src/shapes/rectangle.rs @@ -81,6 +81,14 @@ impl Transformable for Rectangle { fn scale(&mut self, scale: na::Vector2) { self.transform.append_scale_mut(scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.transform.append_mirror_x_mut(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.transform.append_mirror_y_mut(centerline_y); + } } impl Rectangle { @@ -150,14 +158,4 @@ impl Rectangle { }, ] } - - /// Mirrors rectangle around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.transform.append_mirror_x_mut(centerline_x); - } - - /// Mirrors rectangle around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.transform.append_mirror_y_mut(centerline_y); - } } diff --git a/crates/rnote-compose/src/shapes/shape.rs b/crates/rnote-compose/src/shapes/shape.rs index ad30f8298c..d08dc53bfe 100644 --- a/crates/rnote-compose/src/shapes/shape.rs +++ b/crates/rnote-compose/src/shapes/shape.rs @@ -129,6 +129,64 @@ impl Transformable for Shape { } } } + + fn mirror_x(&mut self, centerline_x: f64) { + match self { + Self::Line(line) => { + line.mirror_x(centerline_x); + } + Self::Arrow(arrow) => { + arrow.mirror_x(centerline_x); + } + Self::Rectangle(rectangle) => { + rectangle.mirror_x(centerline_x); + } + Self::Ellipse(ellipse) => { + ellipse.mirror_x(centerline_x); + } + Self::QuadraticBezier(quadratic_bezier) => { + quadratic_bezier.mirror_x(centerline_x); + } + Self::CubicBezier(cubic_bezier) => { + cubic_bezier.mirror_x(centerline_x); + } + Self::Polyline(polyline) => { + polyline.mirror_x(centerline_x); + } + Self::Polygon(polygon) => { + polygon.mirror_x(centerline_x); + } + } + } + + fn mirror_y(&mut self, centerline_y: f64) { + match self { + Self::Line(line) => { + line.mirror_y(centerline_y); + } + Self::Arrow(arrow) => { + arrow.mirror_y(centerline_y); + } + Self::Rectangle(rectangle) => { + rectangle.mirror_y(centerline_y); + } + Self::Ellipse(ellipse) => { + ellipse.mirror_y(centerline_y); + } + Self::QuadraticBezier(quadratic_bezier) => { + quadratic_bezier.mirror_y(centerline_y); + } + Self::CubicBezier(cubic_bezier) => { + cubic_bezier.mirror_y(centerline_y); + } + Self::Polyline(polyline) => { + polyline.mirror_y(centerline_y); + } + Self::Polygon(polygon) => { + polygon.mirror_y(centerline_y); + } + } + } } impl Shapeable for Shape { diff --git a/crates/rnote-compose/src/transform/mod.rs b/crates/rnote-compose/src/transform/mod.rs index dae3231733..961745f123 100644 --- a/crates/rnote-compose/src/transform/mod.rs +++ b/crates/rnote-compose/src/transform/mod.rs @@ -53,6 +53,14 @@ impl Transformable for Transform { fn scale(&mut self, scale: na::Vector2) { self.append_scale_mut(scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.append_mirror_x_mut(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.append_mirror_y_mut(centerline_y); + } } impl Transform { diff --git a/crates/rnote-compose/src/transform/transformable.rs b/crates/rnote-compose/src/transform/transformable.rs index 27c9d37585..05d739a4db 100644 --- a/crates/rnote-compose/src/transform/transformable.rs +++ b/crates/rnote-compose/src/transform/transformable.rs @@ -6,4 +6,8 @@ pub trait Transformable { fn rotate(&mut self, angle: f64, center: na::Point2); /// Scale by the given scale-factor. fn scale(&mut self, scale: na::Vector2); + /// Mirror around line 'x = centerline_x' + fn mirror_x(&mut self, centerline_x: f64); + /// Mirror around line 'y = centerline_y' + fn mirror_y(&mut self, centerline_y: f64); } diff --git a/crates/rnote-engine/src/render.rs b/crates/rnote-engine/src/render.rs index e70e865acd..a99045a4a0 100644 --- a/crates/rnote-engine/src/render.rs +++ b/crates/rnote-engine/src/render.rs @@ -186,6 +186,14 @@ impl Transformable for Image { fn scale(&mut self, scale: na::Vector2) { self.rect.scale(scale) } + + fn mirror_x(&mut self, centerline_x: f64) { + self.rect.mirror_x(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.rect.mirror_y(centerline_y); + } } impl Image { diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index f702c31ec6..0b2809e59e 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -359,8 +359,8 @@ impl StrokeStore { .map(Arc::make_mut) { match orientation { - MirrorOrientation::Horizontal => stroke.horizontal_mirror(selection_centerline), - MirrorOrientation::Vertical => stroke.vertical_mirror(selection_centerline), + MirrorOrientation::Horizontal => stroke.mirror_x(selection_centerline), + MirrorOrientation::Vertical => stroke.mirror_y(selection_centerline), } self.set_rendering_dirty(key); } diff --git a/crates/rnote-engine/src/strokes/bitmapimage.rs b/crates/rnote-engine/src/strokes/bitmapimage.rs index 0eb46355ee..4b93b3e91f 100644 --- a/crates/rnote-engine/src/strokes/bitmapimage.rs +++ b/crates/rnote-engine/src/strokes/bitmapimage.rs @@ -95,6 +95,14 @@ impl Transformable for BitmapImage { fn scale(&mut self, scale: na::Vector2) { self.rectangle.scale(scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.rectangle.mirror_x(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.rectangle.mirror_y(centerline_y); + } } impl BitmapImage { @@ -231,14 +239,4 @@ impl BitmapImage { }) .collect() } - - /// Mirrors bitmapped image around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.rectangle.mirror_x(centerline_x); - } - - /// Mirrors bitmapped image around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.rectangle.mirror_y(centerline_y); - } } diff --git a/crates/rnote-engine/src/strokes/brushstroke.rs b/crates/rnote-engine/src/strokes/brushstroke.rs index 8387c1cb69..f55216c8f2 100644 --- a/crates/rnote-engine/src/strokes/brushstroke.rs +++ b/crates/rnote-engine/src/strokes/brushstroke.rs @@ -255,6 +255,14 @@ impl Transformable for BrushStroke { self.style .set_stroke_width(self.style.stroke_width() * scale_scalar); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.path.mirror_x(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.path.mirror_y(centerline_y); + } } impl BrushStroke { @@ -290,16 +298,6 @@ impl BrushStroke { self.update_geometry(); } - /// Mirrors brushstroke around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.path.mirror_x(centerline_x); - } - - /// Mirrors brushstroke around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.path.mirror_y(centerline_y); - } - // internal method generating the current hitboxes. fn gen_hitboxes_int(&self) -> Vec { let stroke_width = self.style.stroke_width(); diff --git a/crates/rnote-engine/src/strokes/shapestroke.rs b/crates/rnote-engine/src/strokes/shapestroke.rs index 9fa590180e..e963830cdc 100644 --- a/crates/rnote-engine/src/strokes/shapestroke.rs +++ b/crates/rnote-engine/src/strokes/shapestroke.rs @@ -98,6 +98,14 @@ impl Transformable for ShapeStroke { self.style .set_stroke_width(self.style.stroke_width() * scale_scalar); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.shape.mirror_x(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.shape.mirror_y(centerline_y); + } } impl ShapeStroke { diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index c6796917ff..18362c07b0 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -199,6 +199,46 @@ impl Transformable for Stroke { } } } + + fn mirror_x(&mut self, selection_centerline_x: f64) { + match self { + Self::BrushStroke(brushstroke) => { + brushstroke.mirror_x(selection_centerline_x); + } + Self::ShapeStroke(shape_stroke) => { + shape_stroke.mirror_x(selection_centerline_x); + } + Self::VectorImage(vector_image) => { + vector_image.mirror_x(selection_centerline_x); + } + Self::BitmapImage(bitmap_image) => { + bitmap_image.mirror_x(selection_centerline_x); + } + Self::TextStroke(text_stroke) => { + text_stroke.mirror_x(selection_centerline_x); + } + } + } + + fn mirror_y(&mut self, selection_centerline_y: f64) { + match self { + Self::BrushStroke(brushstroke) => { + brushstroke.mirror_y(selection_centerline_y); + } + Self::ShapeStroke(shape_stroke) => { + shape_stroke.mirror_y(selection_centerline_y); + } + Self::VectorImage(vector_image) => { + vector_image.mirror_y(selection_centerline_y); + } + Self::BitmapImage(bitmap_image) => { + bitmap_image.mirror_y(selection_centerline_y); + } + Self::TextStroke(text_stroke) => { + text_stroke.mirror_y(selection_centerline_y); + } + } + } } impl Stroke { @@ -672,86 +712,4 @@ impl Stroke { } } } - - pub fn horizontal_mirror(&mut self, selection_centerline_x: f64) { - match self { - Stroke::BrushStroke(brushstroke) => { - brushstroke.mirror_x(selection_centerline_x); - } - Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { - rnote_compose::Shape::Line(line) => { - line.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::Arrow(arrow) => { - arrow.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::Rectangle(rectangle) => { - rectangle.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::Ellipse(ellipse) => { - ellipse.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { - quadratic_bezier.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::CubicBezier(cubic_bezier) => { - cubic_bezier.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::Polyline(polyline) => { - polyline.mirror_x(selection_centerline_x); - } - rnote_compose::Shape::Polygon(polygon) => { - polygon.mirror_x(selection_centerline_x); - } - }, - Stroke::VectorImage(vector_image) => { - vector_image.mirror_x(selection_centerline_x); - } - Stroke::BitmapImage(bitmap_image) => { - bitmap_image.mirror_x(selection_centerline_x); - } - Stroke::TextStroke(_) => {} - } - } - - pub fn vertical_mirror(&mut self, selection_centerline_y: f64) { - match self { - Stroke::BrushStroke(brushstroke) => { - brushstroke.mirror_y(selection_centerline_y); - } - Stroke::ShapeStroke(shape_stroke) => match &mut shape_stroke.shape { - rnote_compose::Shape::Line(line) => { - line.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::Arrow(arrow) => { - arrow.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::Rectangle(rectangle) => { - rectangle.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::Ellipse(ellipse) => { - ellipse.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::QuadraticBezier(quadratic_bezier) => { - quadratic_bezier.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::CubicBezier(cubic_bezier) => { - cubic_bezier.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::Polyline(polyline) => { - polyline.mirror_y(selection_centerline_y); - } - rnote_compose::Shape::Polygon(polygon) => { - polygon.mirror_y(selection_centerline_y); - } - }, - Stroke::VectorImage(vector_image) => { - vector_image.mirror_y(selection_centerline_y); - } - Stroke::BitmapImage(bitmap_image) => { - bitmap_image.mirror_y(selection_centerline_y); - } - Stroke::TextStroke(_) => {} - } - } } diff --git a/crates/rnote-engine/src/strokes/textstroke.rs b/crates/rnote-engine/src/strokes/textstroke.rs index 2ca3b3e28d..374016b893 100644 --- a/crates/rnote-engine/src/strokes/textstroke.rs +++ b/crates/rnote-engine/src/strokes/textstroke.rs @@ -465,6 +465,11 @@ impl Transformable for TextStroke { fn scale(&mut self, scale: na::Vector2) { self.transform.append_scale_mut(scale); } + + // no mirroring for text as of now + fn mirror_x(&mut self, _centerline_x: f64) {} + + fn mirror_y(&mut self, _centerline_y: f64) {} } impl Shapeable for TextStroke { diff --git a/crates/rnote-engine/src/strokes/vectorimage.rs b/crates/rnote-engine/src/strokes/vectorimage.rs index d44a728625..6b1621d866 100644 --- a/crates/rnote-engine/src/strokes/vectorimage.rs +++ b/crates/rnote-engine/src/strokes/vectorimage.rs @@ -134,6 +134,14 @@ impl Transformable for VectorImage { fn scale(&mut self, scale: na::Vector2) { self.rectangle.scale(scale); } + + fn mirror_x(&mut self, centerline_x: f64) { + self.rectangle.mirror_x(centerline_x); + } + + fn mirror_y(&mut self, centerline_y: f64) { + self.rectangle.mirror_y(centerline_y); + } } impl VectorImage { @@ -336,14 +344,4 @@ impl VectorImage { }) .collect() } - - /// Mirrors vector image around line 'x = centerline_x' - pub fn mirror_x(&mut self, centerline_x: f64) { - self.rectangle.mirror_x(centerline_x); - } - - /// Mirrors vector image around line 'y = centerline_y' - pub fn mirror_y(&mut self, centerline_y: f64) { - self.rectangle.mirror_y(centerline_y); - } } From 5061f26da22faecbee0664fa214e990475abd5ed Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 19:10:36 -0600 Subject: [PATCH 17/26] condense Transformable mirror functions with orientation paremeter --- crates/rnote-compose/src/penpath/element.rs | 20 ++++-- crates/rnote-compose/src/penpath/mod.rs | 16 ++--- crates/rnote-compose/src/penpath/segment.rs | 66 ++++++++++--------- crates/rnote-compose/src/shapes/arrow.rs | 21 +++--- crates/rnote-compose/src/shapes/cubbez.rs | 23 ++++--- crates/rnote-compose/src/shapes/ellipse.rs | 10 +-- crates/rnote-compose/src/shapes/line.rs | 21 +++--- crates/rnote-compose/src/shapes/polygon.rs | 31 +++++---- crates/rnote-compose/src/shapes/polyline.rs | 31 +++++---- crates/rnote-compose/src/shapes/quadbez.rs | 23 ++++--- crates/rnote-compose/src/shapes/rectangle.rs | 10 +-- crates/rnote-compose/src/shapes/shape.rs | 49 +++----------- crates/rnote-compose/src/transform/mod.rs | 60 +++++++++-------- .../src/transform/transformable.rs | 8 +-- crates/rnote-engine/src/engine/mod.rs | 2 +- crates/rnote-engine/src/render.rs | 10 +-- crates/rnote-engine/src/store/stroke_comp.rs | 13 +--- .../rnote-engine/src/strokes/bitmapimage.rs | 10 +-- .../rnote-engine/src/strokes/brushstroke.rs | 10 +-- .../rnote-engine/src/strokes/shapestroke.rs | 9 +-- crates/rnote-engine/src/strokes/stroke.rs | 34 ++-------- crates/rnote-engine/src/strokes/textstroke.rs | 6 +- .../rnote-engine/src/strokes/vectorimage.rs | 10 +-- 23 files changed, 215 insertions(+), 278 deletions(-) diff --git a/crates/rnote-compose/src/penpath/element.rs b/crates/rnote-compose/src/penpath/element.rs index c5994ee943..ecb028fcf5 100644 --- a/crates/rnote-compose/src/penpath/element.rs +++ b/crates/rnote-compose/src/penpath/element.rs @@ -1,5 +1,8 @@ // Imports -use crate::{point_utils, transform::Transformable}; +use crate::{ + point_utils, + transform::{MirrorOrientation, Transformable}, +}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -36,12 +39,15 @@ impl Transformable for Element { self.pos = self.pos.component_mul(&scale); } - fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.pos, centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.pos, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + point_utils::mirror_point_x(&mut self.pos, centerline); + } + MirrorOrientation::Vertical => { + point_utils::mirror_point_y(&mut self.pos, centerline); + } + } } } diff --git a/crates/rnote-compose/src/penpath/mod.rs b/crates/rnote-compose/src/penpath/mod.rs index 3774cb11b4..3af3e7e7a6 100644 --- a/crates/rnote-compose/src/penpath/mod.rs +++ b/crates/rnote-compose/src/penpath/mod.rs @@ -9,7 +9,7 @@ pub use segment::Segment; // Imports use crate::ext::{KurboShapeExt, Vector2Ext}; use crate::shapes::{CubicBezier, Line, QuadraticBezier, Shapeable}; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use kurbo::Shape; use p2d::bounding_volume::{Aabb, BoundingVolume}; use serde::{Deserialize, Serialize}; @@ -99,19 +99,11 @@ impl Transformable for PenPath { }); } - fn mirror_x(&mut self, centerline_x: f64) { - self.start.mirror_x(centerline_x); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.start.mirror(centerline, orientation); for element in &mut self.segments { - element.mirror_x(centerline_x); - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.start.mirror_y(centerline_y); - - for element in &mut self.segments { - element.mirror_y(centerline_y); + element.mirror(centerline, orientation); } } } diff --git a/crates/rnote-compose/src/penpath/segment.rs b/crates/rnote-compose/src/penpath/segment.rs index d678149451..582a3fce25 100644 --- a/crates/rnote-compose/src/penpath/segment.rs +++ b/crates/rnote-compose/src/penpath/segment.rs @@ -1,6 +1,9 @@ // Imports use super::Element; -use crate::{point_utils, transform::Transformable}; +use crate::{ + point_utils, + transform::{MirrorOrientation, Transformable}, +}; use serde::{Deserialize, Serialize}; /// A single segment, usually of a pen path. @@ -94,37 +97,36 @@ impl Transformable for Segment { } } - fn mirror_x(&mut self, centerline_x: f64) { - match self { - Segment::LineTo { end } => { - end.mirror_x(centerline_x); - } - Segment::QuadBezTo { cp, end } => { - point_utils::mirror_point_x(cp, centerline_x); - end.mirror_x(centerline_x); - } - Segment::CubBezTo { cp1, cp2, end } => { - point_utils::mirror_point_x(cp1, centerline_x); - point_utils::mirror_point_x(cp2, centerline_x); - end.mirror_x(centerline_x); - } - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - match self { - Segment::LineTo { end } => { - end.mirror_y(centerline_y); - } - Segment::QuadBezTo { cp, end } => { - point_utils::mirror_point_y(cp, centerline_y); - end.mirror_y(centerline_y); - } - Segment::CubBezTo { cp1, cp2, end } => { - point_utils::mirror_point_y(cp1, centerline_y); - point_utils::mirror_point_y(cp2, centerline_y); - end.mirror_y(centerline_y); - } + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => match self { + Segment::LineTo { end } => { + end.mirror(centerline, orientation); + } + Segment::QuadBezTo { cp, end } => { + point_utils::mirror_point_x(cp, centerline); + end.mirror(centerline, orientation); + } + Segment::CubBezTo { cp1, cp2, end } => { + point_utils::mirror_point_x(cp1, centerline); + point_utils::mirror_point_x(cp2, centerline); + end.mirror(centerline, orientation); + } + }, + MirrorOrientation::Vertical => match self { + Segment::LineTo { end } => { + end.mirror(centerline, orientation); + } + Segment::QuadBezTo { cp, end } => { + point_utils::mirror_point_y(cp, centerline); + end.mirror(centerline, orientation); + } + Segment::CubBezTo { cp1, cp2, end } => { + point_utils::mirror_point_y(cp1, centerline); + point_utils::mirror_point_y(cp2, centerline); + end.mirror(centerline, orientation); + } + }, } } } diff --git a/crates/rnote-compose/src/shapes/arrow.rs b/crates/rnote-compose/src/shapes/arrow.rs index 55176aab05..d5c2467c81 100644 --- a/crates/rnote-compose/src/shapes/arrow.rs +++ b/crates/rnote-compose/src/shapes/arrow.rs @@ -1,7 +1,7 @@ // Imports use super::Line; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use crate::{ext::Vector2Ext, point_utils}; use kurbo::{PathEl, Shape}; use na::Rotation2; @@ -57,14 +57,17 @@ impl Transformable for Arrow { self.tip = self.tip.component_mul(&scale); } - fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - point_utils::mirror_point_x(&mut self.tip, centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - point_utils::mirror_point_y(&mut self.tip, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + point_utils::mirror_point_x(&mut self.start, centerline); + point_utils::mirror_point_x(&mut self.tip, centerline); + } + MirrorOrientation::Vertical => { + point_utils::mirror_point_y(&mut self.start, centerline); + point_utils::mirror_point_y(&mut self.tip, centerline); + } + } } } diff --git a/crates/rnote-compose/src/shapes/cubbez.rs b/crates/rnote-compose/src/shapes/cubbez.rs index 38ddd7fe2b..0df88db773 100644 --- a/crates/rnote-compose/src/shapes/cubbez.rs +++ b/crates/rnote-compose/src/shapes/cubbez.rs @@ -4,7 +4,7 @@ use super::quadbez::QuadraticBezier; use crate::ext::{KurboShapeExt, Vector2Ext}; use crate::point_utils; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use kurbo::Shape; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -52,15 +52,18 @@ impl Transformable for CubicBezier { self.end = self.end.component_mul(&scale); } - fn mirror_x(&mut self, centerline_x: f64) { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_x(point, centerline_x); - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_y(point, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_x(point, centerline); + } + } + MirrorOrientation::Vertical => { + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point_y(point, centerline); + } + } } } } diff --git a/crates/rnote-compose/src/shapes/ellipse.rs b/crates/rnote-compose/src/shapes/ellipse.rs index 35c780830c..c9e09a55e9 100644 --- a/crates/rnote-compose/src/shapes/ellipse.rs +++ b/crates/rnote-compose/src/shapes/ellipse.rs @@ -3,7 +3,7 @@ use super::Line; use crate::Transform; use crate::ext::{Affine2Ext, Vector2Ext}; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use kurbo::Shape; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -42,12 +42,8 @@ impl Transformable for Ellipse { self.transform.append_scale_mut(scale); } - fn mirror_x(&mut self, centerline_x: f64) { - self.transform.append_mirror_x_mut(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.transform.append_mirror_y_mut(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.transform.append_mirror_mut(centerline, orientation); } } diff --git a/crates/rnote-compose/src/shapes/line.rs b/crates/rnote-compose/src/shapes/line.rs index 491c459079..0d078b0eea 100644 --- a/crates/rnote-compose/src/shapes/line.rs +++ b/crates/rnote-compose/src/shapes/line.rs @@ -2,7 +2,7 @@ use crate::ext::{AabbExt, Vector2Ext}; use crate::shapes::Rectangle; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use crate::{Transform, point_utils}; use kurbo::Shape; use p2d::bounding_volume::Aabb; @@ -39,14 +39,17 @@ impl Transformable for Line { self.end = self.end.component_mul(&scale); } - fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - point_utils::mirror_point_x(&mut self.end, centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - point_utils::mirror_point_y(&mut self.end, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + point_utils::mirror_point_x(&mut self.start, centerline); + point_utils::mirror_point_x(&mut self.end, centerline); + } + MirrorOrientation::Vertical => { + point_utils::mirror_point_y(&mut self.start, centerline); + point_utils::mirror_point_y(&mut self.end, centerline); + } + } } } diff --git a/crates/rnote-compose/src/shapes/polygon.rs b/crates/rnote-compose/src/shapes/polygon.rs index 2c56ded535..3c11d7f17b 100644 --- a/crates/rnote-compose/src/shapes/polygon.rs +++ b/crates/rnote-compose/src/shapes/polygon.rs @@ -2,7 +2,7 @@ use super::{Line, Shapeable}; use crate::ext::{AabbExt, Vector2Ext}; use crate::point_utils; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -43,19 +43,22 @@ impl Transformable for Polygon { } } - fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline_x); - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + point_utils::mirror_point_x(&mut self.start, centerline); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline); + } + } + MirrorOrientation::Vertical => { + point_utils::mirror_point_y(&mut self.start, centerline); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline); + } + } } } } diff --git a/crates/rnote-compose/src/shapes/polyline.rs b/crates/rnote-compose/src/shapes/polyline.rs index 402c04adad..49247d4fa9 100644 --- a/crates/rnote-compose/src/shapes/polyline.rs +++ b/crates/rnote-compose/src/shapes/polyline.rs @@ -1,6 +1,6 @@ // Imports use super::{Line, Shapeable}; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use crate::{ext::Vector2Ext, point_utils}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -42,19 +42,22 @@ impl Transformable for Polyline { } } - fn mirror_x(&mut self, centerline_x: f64) { - point_utils::mirror_point_x(&mut self.start, centerline_x); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline_x); - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - point_utils::mirror_point_y(&mut self.start, centerline_y); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + point_utils::mirror_point_x(&mut self.start, centerline); + + for point in self.path.iter_mut() { + point_utils::mirror_point_x(point, centerline); + } + } + MirrorOrientation::Vertical => { + point_utils::mirror_point_y(&mut self.start, centerline); + + for point in self.path.iter_mut() { + point_utils::mirror_point_y(point, centerline); + } + } } } } diff --git a/crates/rnote-compose/src/shapes/quadbez.rs b/crates/rnote-compose/src/shapes/quadbez.rs index 4185c773fa..d5df6e3929 100644 --- a/crates/rnote-compose/src/shapes/quadbez.rs +++ b/crates/rnote-compose/src/shapes/quadbez.rs @@ -4,7 +4,7 @@ use super::line::Line; use crate::ext::{KurboShapeExt, Vector2Ext}; use crate::point_utils; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use kurbo::Shape; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -46,15 +46,18 @@ impl Transformable for QuadraticBezier { self.end = self.end.component_mul(&scale); } - fn mirror_x(&mut self, centerline_x: f64) { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_x(point, centerline_x); - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_y(point, centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_x(point, centerline); + } + } + MirrorOrientation::Vertical => { + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point_y(point, centerline); + } + } } } } diff --git a/crates/rnote-compose/src/shapes/rectangle.rs b/crates/rnote-compose/src/shapes/rectangle.rs index b8e1c99b8c..afa769b153 100644 --- a/crates/rnote-compose/src/shapes/rectangle.rs +++ b/crates/rnote-compose/src/shapes/rectangle.rs @@ -3,7 +3,7 @@ use super::Line; use crate::Transform; use crate::ext::{AabbExt, Vector2Ext}; use crate::shapes::Shapeable; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -82,12 +82,8 @@ impl Transformable for Rectangle { self.transform.append_scale_mut(scale); } - fn mirror_x(&mut self, centerline_x: f64) { - self.transform.append_mirror_x_mut(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.transform.append_mirror_y_mut(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.transform.mirror(centerline, orientation); } } diff --git a/crates/rnote-compose/src/shapes/shape.rs b/crates/rnote-compose/src/shapes/shape.rs index d08dc53bfe..624ff1a3c6 100644 --- a/crates/rnote-compose/src/shapes/shape.rs +++ b/crates/rnote-compose/src/shapes/shape.rs @@ -2,7 +2,7 @@ use super::{ Arrow, CubicBezier, Ellipse, Line, Polygon, Polyline, QuadraticBezier, Rectangle, Shapeable, }; -use crate::transform::Transformable; +use crate::transform::{MirrorOrientation, Transformable}; use p2d::bounding_volume::Aabb; use serde::{Deserialize, Serialize}; @@ -130,60 +130,31 @@ impl Transformable for Shape { } } - fn mirror_x(&mut self, centerline_x: f64) { + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { match self { Self::Line(line) => { - line.mirror_x(centerline_x); + line.mirror(centerline, orientation); } Self::Arrow(arrow) => { - arrow.mirror_x(centerline_x); + arrow.mirror(centerline, orientation); } Self::Rectangle(rectangle) => { - rectangle.mirror_x(centerline_x); + rectangle.mirror(centerline, orientation); } Self::Ellipse(ellipse) => { - ellipse.mirror_x(centerline_x); + ellipse.mirror(centerline, orientation); } Self::QuadraticBezier(quadratic_bezier) => { - quadratic_bezier.mirror_x(centerline_x); + quadratic_bezier.mirror(centerline, orientation); } Self::CubicBezier(cubic_bezier) => { - cubic_bezier.mirror_x(centerline_x); + cubic_bezier.mirror(centerline, orientation); } Self::Polyline(polyline) => { - polyline.mirror_x(centerline_x); + polyline.mirror(centerline, orientation); } Self::Polygon(polygon) => { - polygon.mirror_x(centerline_x); - } - } - } - - fn mirror_y(&mut self, centerline_y: f64) { - match self { - Self::Line(line) => { - line.mirror_y(centerline_y); - } - Self::Arrow(arrow) => { - arrow.mirror_y(centerline_y); - } - Self::Rectangle(rectangle) => { - rectangle.mirror_y(centerline_y); - } - Self::Ellipse(ellipse) => { - ellipse.mirror_y(centerline_y); - } - Self::QuadraticBezier(quadratic_bezier) => { - quadratic_bezier.mirror_y(centerline_y); - } - Self::CubicBezier(cubic_bezier) => { - cubic_bezier.mirror_y(centerline_y); - } - Self::Polyline(polyline) => { - polyline.mirror_y(centerline_y); - } - Self::Polygon(polygon) => { - polygon.mirror_y(centerline_y); + polygon.mirror(centerline, orientation); } } } diff --git a/crates/rnote-compose/src/transform/mod.rs b/crates/rnote-compose/src/transform/mod.rs index 961745f123..b33a67d738 100644 --- a/crates/rnote-compose/src/transform/mod.rs +++ b/crates/rnote-compose/src/transform/mod.rs @@ -18,6 +18,15 @@ pub struct Transform { pub affine: na::Affine2, } +/// Selection of which direction a mirror should use +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MirrorOrientation { + /// Mirror is applied accross the line 'x = centerline' + Horizontal, + /// Mirror is applied accross the line 'y = centerline' + Vertical, +} + impl Default for Transform { fn default() -> Self { Self { @@ -54,12 +63,8 @@ impl Transformable for Transform { self.append_scale_mut(scale); } - fn mirror_x(&mut self, centerline_x: f64) { - self.append_mirror_x_mut(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.append_mirror_y_mut(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.append_mirror_mut(centerline, orientation); } } @@ -128,28 +133,29 @@ impl Transform { .unwrap(); } - /// Apply a reflection across line 'x = centerline_x' to the affine matrix - pub fn append_mirror_x_mut(&mut self, centerline_x: f64) { - let mirror_transformation_x = na::matrix![ - -1.0, 0.0, 2.0 * centerline_x; - 0.0, 1.0, 0.0; - 0.0, 0.0, 1.0; - ]; - - let transformed_affine = mirror_transformation_x * self.affine.matrix(); - - self.affine = na::Affine2::from_matrix_unchecked(transformed_affine); - } - - /// Apply a reflection across line 'y = centerline_y' to the affine matrix - pub fn append_mirror_y_mut(&mut self, centerline_y: f64) { - let mirror_transformation_y = na::matrix![ - 1.0, 0.0, 0.0; - 0.0, -1.0, 2.0 * centerline_y; - 0.0, 0.0, 1.0; - ]; + /// Apply a reflection across either Horizontal: 'x = centerline' or Vertical: 'y = centerline' to the + /// affine matrix based on the orientation + pub fn append_mirror_mut(&mut self, centerline: f64, orientation: MirrorOrientation) { + let mirror_transformation; + + match orientation { + MirrorOrientation::Horizontal => { + mirror_transformation = na::matrix![ + -1.0, 0.0, 2.0 * centerline; + 0.0, 1.0, 0.0; + 0.0, 0.0, 1.0; + ]; + } + MirrorOrientation::Vertical => { + mirror_transformation = na::matrix![ + 1.0, 0.0, 0.0; + 0.0, -1.0, 2.0 * centerline; + 0.0, 0.0, 1.0; + ]; + } + } - let transformed_affine = mirror_transformation_y * self.affine.matrix(); + let transformed_affine = mirror_transformation * self.affine.matrix(); self.affine = na::Affine2::from_matrix_unchecked(transformed_affine); } diff --git a/crates/rnote-compose/src/transform/transformable.rs b/crates/rnote-compose/src/transform/transformable.rs index 05d739a4db..8ebe25d03d 100644 --- a/crates/rnote-compose/src/transform/transformable.rs +++ b/crates/rnote-compose/src/transform/transformable.rs @@ -1,3 +1,5 @@ +use crate::transform::MirrorOrientation; + /// Trait for types that can be (geometrically) transformed. pub trait Transformable { /// Translate (as in moves) by the given offset. @@ -6,8 +8,6 @@ pub trait Transformable { fn rotate(&mut self, angle: f64, center: na::Point2); /// Scale by the given scale-factor. fn scale(&mut self, scale: na::Vector2); - /// Mirror around line 'x = centerline_x' - fn mirror_x(&mut self, centerline_x: f64); - /// Mirror around line 'y = centerline_y' - fn mirror_y(&mut self, centerline_y: f64); + /// Mirror around either Horizontal: 'x = centerline' or Vertical: 'y = centerline' + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation); } diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 5f77888510..4a753bb4a4 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -23,7 +23,6 @@ use crate::pens::PenMode; use crate::pens::{Pen, PenStyle}; use crate::store::StrokeKey; use crate::store::render_comp::{self, RenderCompState}; -use crate::store::stroke_comp::MirrorOrientation; use crate::strokes::content::GeneratedContentImages; use crate::strokes::textstroke::{TextAttribute, TextStyle}; use crate::{AudioPlayer, SelectionCollision, WidgetFlags, render}; @@ -35,6 +34,7 @@ use p2d::bounding_volume::{Aabb, BoundingVolume}; use rnote_compose::eventresult::EventPropagation; use rnote_compose::ext::AabbExt; use rnote_compose::penevent::{PenEvent, ShortcutKey}; +use rnote_compose::transform::MirrorOrientation; use rnote_compose::{Color, SplitOrder}; use serde::{Deserialize, Serialize}; use snapshot::Snapshotable; diff --git a/crates/rnote-engine/src/render.rs b/crates/rnote-engine/src/render.rs index a99045a4a0..a1db9eb864 100644 --- a/crates/rnote-engine/src/render.rs +++ b/crates/rnote-engine/src/render.rs @@ -8,7 +8,7 @@ use p2d::bounding_volume::{Aabb, BoundingVolume}; use piet::RenderContext; use rnote_compose::ext::AabbExt; use rnote_compose::shapes::{Rectangle, Shapeable}; -use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transformable}; use serde::{Deserialize, Serialize}; use std::io::{self, Cursor}; use std::sync::Arc; @@ -187,12 +187,8 @@ impl Transformable for Image { self.rect.scale(scale) } - fn mirror_x(&mut self, centerline_x: f64) { - self.rect.mirror_x(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.rect.mirror_y(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.rect.mirror(centerline, orientation); } } diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 0b2809e59e..a217ff3ab9 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -10,17 +10,11 @@ use p2d::bounding_volume::{Aabb, BoundingVolume}; use rnote_compose::Color; use rnote_compose::penpath::Element; use rnote_compose::shapes::Shapeable; -use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transformable}; use std::sync::Arc; #[cfg(feature = "ui")] use tracing::error; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum MirrorOrientation { - Horizontal, - Vertical, -} - /// Systems that are related to the stroke components. impl StrokeStore { /// Gets a immutable reference to a stroke. @@ -358,10 +352,7 @@ impl StrokeStore { .get_mut(key) .map(Arc::make_mut) { - match orientation { - MirrorOrientation::Horizontal => stroke.mirror_x(selection_centerline), - MirrorOrientation::Vertical => stroke.mirror_y(selection_centerline), - } + stroke.mirror(selection_centerline, orientation); self.set_rendering_dirty(key); } }); diff --git a/crates/rnote-engine/src/strokes/bitmapimage.rs b/crates/rnote-engine/src/strokes/bitmapimage.rs index 4b93b3e91f..facf1e1fd5 100644 --- a/crates/rnote-engine/src/strokes/bitmapimage.rs +++ b/crates/rnote-engine/src/strokes/bitmapimage.rs @@ -13,8 +13,8 @@ use rnote_compose::color; use rnote_compose::ext::{AabbExt, Affine2Ext}; use rnote_compose::shapes::Rectangle; use rnote_compose::shapes::Shapeable; -use rnote_compose::transform::Transform; use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transform}; use serde::{Deserialize, Serialize}; use std::ops::Range; @@ -96,12 +96,8 @@ impl Transformable for BitmapImage { self.rectangle.scale(scale); } - fn mirror_x(&mut self, centerline_x: f64) { - self.rectangle.mirror_x(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.rectangle.mirror_y(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.rectangle.mirror(centerline, orientation); } } diff --git a/crates/rnote-engine/src/strokes/brushstroke.rs b/crates/rnote-engine/src/strokes/brushstroke.rs index f55216c8f2..898e7359bb 100644 --- a/crates/rnote-engine/src/strokes/brushstroke.rs +++ b/crates/rnote-engine/src/strokes/brushstroke.rs @@ -11,7 +11,7 @@ use rnote_compose::ext::AabbExt; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::shapes::Shapeable; use rnote_compose::style::Composer; -use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transformable}; use rnote_compose::{PenPath, Style}; use serde::{Deserialize, Serialize}; use tracing::error; @@ -256,12 +256,8 @@ impl Transformable for BrushStroke { .set_stroke_width(self.style.stroke_width() * scale_scalar); } - fn mirror_x(&mut self, centerline_x: f64) { - self.path.mirror_x(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.path.mirror_y(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.path.mirror(centerline, orientation); } } diff --git a/crates/rnote-engine/src/strokes/shapestroke.rs b/crates/rnote-engine/src/strokes/shapestroke.rs index e963830cdc..2d5a2133e5 100644 --- a/crates/rnote-engine/src/strokes/shapestroke.rs +++ b/crates/rnote-engine/src/strokes/shapestroke.rs @@ -7,6 +7,7 @@ use rnote_compose::ext::AabbExt; use rnote_compose::shapes::Shape; use rnote_compose::shapes::Shapeable; use rnote_compose::style::Composer; +use rnote_compose::transform::MirrorOrientation; use rnote_compose::transform::Transformable; use serde::{Deserialize, Serialize}; @@ -99,12 +100,8 @@ impl Transformable for ShapeStroke { .set_stroke_width(self.style.stroke_width() * scale_scalar); } - fn mirror_x(&mut self, centerline_x: f64) { - self.shape.mirror_x(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.shape.mirror_y(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.shape.mirror(centerline, orientation); } } diff --git a/crates/rnote-engine/src/strokes/stroke.rs b/crates/rnote-engine/src/strokes/stroke.rs index 18362c07b0..b64e962c15 100644 --- a/crates/rnote-engine/src/strokes/stroke.rs +++ b/crates/rnote-engine/src/strokes/stroke.rs @@ -14,8 +14,8 @@ use rnote_compose::ext::AabbExt; use rnote_compose::penpath::Element; use rnote_compose::shapes::{Rectangle, Shapeable}; use rnote_compose::style::smooth::SmoothOptions; -use rnote_compose::transform::Transform; use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transform}; use rnote_compose::{Color, PenPath, Style}; use serde::{Deserialize, Serialize}; use tracing::error; @@ -200,42 +200,22 @@ impl Transformable for Stroke { } } - fn mirror_x(&mut self, selection_centerline_x: f64) { + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { match self { Self::BrushStroke(brushstroke) => { - brushstroke.mirror_x(selection_centerline_x); + brushstroke.mirror(centerline, orientation); } Self::ShapeStroke(shape_stroke) => { - shape_stroke.mirror_x(selection_centerline_x); + shape_stroke.mirror(centerline, orientation); } Self::VectorImage(vector_image) => { - vector_image.mirror_x(selection_centerline_x); + vector_image.mirror(centerline, orientation); } Self::BitmapImage(bitmap_image) => { - bitmap_image.mirror_x(selection_centerline_x); + bitmap_image.mirror(centerline, orientation); } Self::TextStroke(text_stroke) => { - text_stroke.mirror_x(selection_centerline_x); - } - } - } - - fn mirror_y(&mut self, selection_centerline_y: f64) { - match self { - Self::BrushStroke(brushstroke) => { - brushstroke.mirror_y(selection_centerline_y); - } - Self::ShapeStroke(shape_stroke) => { - shape_stroke.mirror_y(selection_centerline_y); - } - Self::VectorImage(vector_image) => { - vector_image.mirror_y(selection_centerline_y); - } - Self::BitmapImage(bitmap_image) => { - bitmap_image.mirror_y(selection_centerline_y); - } - Self::TextStroke(text_stroke) => { - text_stroke.mirror_y(selection_centerline_y); + text_stroke.mirror(centerline, orientation); } } } diff --git a/crates/rnote-engine/src/strokes/textstroke.rs b/crates/rnote-engine/src/strokes/textstroke.rs index 374016b893..e4239a2dbb 100644 --- a/crates/rnote-engine/src/strokes/textstroke.rs +++ b/crates/rnote-engine/src/strokes/textstroke.rs @@ -7,7 +7,7 @@ use p2d::bounding_volume::Aabb; use piet::{RenderContext, TextLayout, TextLayoutBuilder}; use rnote_compose::ext::{AabbExt, Affine2Ext, Vector2Ext}; use rnote_compose::shapes::Shapeable; -use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transformable}; use rnote_compose::{Color, Transform, color}; use serde::{Deserialize, Serialize}; use std::ops::Range; @@ -467,9 +467,7 @@ impl Transformable for TextStroke { } // no mirroring for text as of now - fn mirror_x(&mut self, _centerline_x: f64) {} - - fn mirror_y(&mut self, _centerline_y: f64) {} + fn mirror(&mut self, _centerline: f64, _orientation: MirrorOrientation) {} } impl Shapeable for TextStroke { diff --git a/crates/rnote-engine/src/strokes/vectorimage.rs b/crates/rnote-engine/src/strokes/vectorimage.rs index 6b1621d866..c1749e6c00 100644 --- a/crates/rnote-engine/src/strokes/vectorimage.rs +++ b/crates/rnote-engine/src/strokes/vectorimage.rs @@ -12,8 +12,8 @@ use rnote_compose::color; use rnote_compose::ext::AabbExt; use rnote_compose::shapes::Rectangle; use rnote_compose::shapes::Shapeable; -use rnote_compose::transform::Transform; use rnote_compose::transform::Transformable; +use rnote_compose::transform::{MirrorOrientation, Transform}; use serde::{Deserialize, Serialize}; use std::ops::Range; use std::sync::Arc; @@ -135,12 +135,8 @@ impl Transformable for VectorImage { self.rectangle.scale(scale); } - fn mirror_x(&mut self, centerline_x: f64) { - self.rectangle.mirror_x(centerline_x); - } - - fn mirror_y(&mut self, centerline_y: f64) { - self.rectangle.mirror_y(centerline_y); + fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { + self.rectangle.mirror(centerline, orientation); } } From 27bb53ab9d393f2512e50bbc49c4a1d7a4177b37 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Fri, 22 Aug 2025 20:07:04 -0600 Subject: [PATCH 18/26] add orientation based mirror_point --- crates/rnote-compose/src/penpath/element.rs | 9 +---- crates/rnote-compose/src/penpath/segment.rs | 42 +++++++-------------- crates/rnote-compose/src/point_utils.rs | 14 +++++++ crates/rnote-compose/src/shapes/arrow.rs | 12 +----- crates/rnote-compose/src/shapes/cubbez.rs | 13 +------ crates/rnote-compose/src/shapes/line.rs | 12 +----- crates/rnote-compose/src/shapes/polygon.rs | 19 ++-------- crates/rnote-compose/src/shapes/polyline.rs | 19 ++-------- crates/rnote-compose/src/shapes/quadbez.rs | 13 +------ 9 files changed, 44 insertions(+), 109 deletions(-) diff --git a/crates/rnote-compose/src/penpath/element.rs b/crates/rnote-compose/src/penpath/element.rs index ecb028fcf5..b0b7dad15a 100644 --- a/crates/rnote-compose/src/penpath/element.rs +++ b/crates/rnote-compose/src/penpath/element.rs @@ -40,14 +40,7 @@ impl Transformable for Element { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - point_utils::mirror_point_x(&mut self.pos, centerline); - } - MirrorOrientation::Vertical => { - point_utils::mirror_point_y(&mut self.pos, centerline); - } - } + point_utils::mirror_point(&mut self.pos, centerline, orientation); } } diff --git a/crates/rnote-compose/src/penpath/segment.rs b/crates/rnote-compose/src/penpath/segment.rs index 582a3fce25..510e076e41 100644 --- a/crates/rnote-compose/src/penpath/segment.rs +++ b/crates/rnote-compose/src/penpath/segment.rs @@ -98,35 +98,19 @@ impl Transformable for Segment { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => match self { - Segment::LineTo { end } => { - end.mirror(centerline, orientation); - } - Segment::QuadBezTo { cp, end } => { - point_utils::mirror_point_x(cp, centerline); - end.mirror(centerline, orientation); - } - Segment::CubBezTo { cp1, cp2, end } => { - point_utils::mirror_point_x(cp1, centerline); - point_utils::mirror_point_x(cp2, centerline); - end.mirror(centerline, orientation); - } - }, - MirrorOrientation::Vertical => match self { - Segment::LineTo { end } => { - end.mirror(centerline, orientation); - } - Segment::QuadBezTo { cp, end } => { - point_utils::mirror_point_y(cp, centerline); - end.mirror(centerline, orientation); - } - Segment::CubBezTo { cp1, cp2, end } => { - point_utils::mirror_point_y(cp1, centerline); - point_utils::mirror_point_y(cp2, centerline); - end.mirror(centerline, orientation); - } - }, + match self { + Segment::LineTo { end } => { + end.mirror(centerline, orientation); + } + Segment::QuadBezTo { cp, end } => { + point_utils::mirror_point(cp, centerline, orientation); + end.mirror(centerline, orientation); + } + Segment::CubBezTo { cp1, cp2, end } => { + point_utils::mirror_point(cp1, centerline, orientation); + point_utils::mirror_point(cp2, centerline, orientation); + end.mirror(centerline, orientation); + } } } } diff --git a/crates/rnote-compose/src/point_utils.rs b/crates/rnote-compose/src/point_utils.rs index 6ddb86519a..13d0001924 100644 --- a/crates/rnote-compose/src/point_utils.rs +++ b/crates/rnote-compose/src/point_utils.rs @@ -1,3 +1,5 @@ +use crate::transform::MirrorOrientation; + /// horizontally mirrors point around line 'x = centerline_x' pub fn mirror_point_x(point: &mut na::Vector2, centerline_x: f64) { point.x -= centerline_x; @@ -11,3 +13,15 @@ pub fn mirror_point_y(point: &mut na::Vector2, centerline_y: f64) { point.y *= -1.0; point.y += centerline_y; } + +/// mirror point around either Horizontal: 'x = centerline' or Vertical: 'y = centerline' line +pub fn mirror_point(point: &mut na::Vector2, centerline: f64, orientation: MirrorOrientation) { + match orientation { + MirrorOrientation::Horizontal => { + mirror_point_x(point, centerline); + } + MirrorOrientation::Vertical => { + mirror_point_y(point, centerline); + } + } +} diff --git a/crates/rnote-compose/src/shapes/arrow.rs b/crates/rnote-compose/src/shapes/arrow.rs index d5c2467c81..a8e5e48c83 100644 --- a/crates/rnote-compose/src/shapes/arrow.rs +++ b/crates/rnote-compose/src/shapes/arrow.rs @@ -58,16 +58,8 @@ impl Transformable for Arrow { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - point_utils::mirror_point_x(&mut self.start, centerline); - point_utils::mirror_point_x(&mut self.tip, centerline); - } - MirrorOrientation::Vertical => { - point_utils::mirror_point_y(&mut self.start, centerline); - point_utils::mirror_point_y(&mut self.tip, centerline); - } - } + point_utils::mirror_point(&mut self.start, centerline, orientation); + point_utils::mirror_point(&mut self.tip, centerline, orientation); } } diff --git a/crates/rnote-compose/src/shapes/cubbez.rs b/crates/rnote-compose/src/shapes/cubbez.rs index 0df88db773..e498a72808 100644 --- a/crates/rnote-compose/src/shapes/cubbez.rs +++ b/crates/rnote-compose/src/shapes/cubbez.rs @@ -53,17 +53,8 @@ impl Transformable for CubicBezier { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_x(point, centerline); - } - } - MirrorOrientation::Vertical => { - for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { - point_utils::mirror_point_y(point, centerline); - } - } + for point in [&mut self.start, &mut self.cp1, &mut self.cp2, &mut self.end] { + point_utils::mirror_point(point, centerline, orientation); } } } diff --git a/crates/rnote-compose/src/shapes/line.rs b/crates/rnote-compose/src/shapes/line.rs index 0d078b0eea..96a7307f83 100644 --- a/crates/rnote-compose/src/shapes/line.rs +++ b/crates/rnote-compose/src/shapes/line.rs @@ -40,16 +40,8 @@ impl Transformable for Line { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - point_utils::mirror_point_x(&mut self.start, centerline); - point_utils::mirror_point_x(&mut self.end, centerline); - } - MirrorOrientation::Vertical => { - point_utils::mirror_point_y(&mut self.start, centerline); - point_utils::mirror_point_y(&mut self.end, centerline); - } - } + point_utils::mirror_point(&mut self.start, centerline, orientation); + point_utils::mirror_point(&mut self.end, centerline, orientation); } } diff --git a/crates/rnote-compose/src/shapes/polygon.rs b/crates/rnote-compose/src/shapes/polygon.rs index 3c11d7f17b..f714e855a0 100644 --- a/crates/rnote-compose/src/shapes/polygon.rs +++ b/crates/rnote-compose/src/shapes/polygon.rs @@ -44,21 +44,10 @@ impl Transformable for Polygon { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - point_utils::mirror_point_x(&mut self.start, centerline); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline); - } - } - MirrorOrientation::Vertical => { - point_utils::mirror_point_y(&mut self.start, centerline); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline); - } - } + point_utils::mirror_point(&mut self.start, centerline, orientation); + + for point in self.path.iter_mut() { + point_utils::mirror_point(point, centerline, orientation); } } } diff --git a/crates/rnote-compose/src/shapes/polyline.rs b/crates/rnote-compose/src/shapes/polyline.rs index 49247d4fa9..76a78cc983 100644 --- a/crates/rnote-compose/src/shapes/polyline.rs +++ b/crates/rnote-compose/src/shapes/polyline.rs @@ -43,21 +43,10 @@ impl Transformable for Polyline { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - point_utils::mirror_point_x(&mut self.start, centerline); - - for point in self.path.iter_mut() { - point_utils::mirror_point_x(point, centerline); - } - } - MirrorOrientation::Vertical => { - point_utils::mirror_point_y(&mut self.start, centerline); - - for point in self.path.iter_mut() { - point_utils::mirror_point_y(point, centerline); - } - } + point_utils::mirror_point(&mut self.start, centerline, orientation); + + for point in self.path.iter_mut() { + point_utils::mirror_point(point, centerline, orientation); } } } diff --git a/crates/rnote-compose/src/shapes/quadbez.rs b/crates/rnote-compose/src/shapes/quadbez.rs index d5df6e3929..02350d0160 100644 --- a/crates/rnote-compose/src/shapes/quadbez.rs +++ b/crates/rnote-compose/src/shapes/quadbez.rs @@ -47,17 +47,8 @@ impl Transformable for QuadraticBezier { } fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { - match orientation { - MirrorOrientation::Horizontal => { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_x(point, centerline); - } - } - MirrorOrientation::Vertical => { - for point in [&mut self.start, &mut self.cp, &mut self.end] { - point_utils::mirror_point_y(point, centerline); - } - } + for point in [&mut self.start, &mut self.cp, &mut self.end] { + point_utils::mirror_point(point, centerline, orientation); } } } From bff148218ffffc5ef632a0ffc140ac0f58ce179d Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Sun, 24 Aug 2025 14:45:52 -0600 Subject: [PATCH 19/26] fix code style, no functional changes --- crates/rnote-compose/src/penpath/mod.rs | 4 ++-- crates/rnote-compose/src/shapes/polygon.rs | 4 ++-- crates/rnote-compose/src/shapes/polyline.rs | 4 ++-- crates/rnote-compose/src/transform/mod.rs | 14 ++++++-------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/rnote-compose/src/penpath/mod.rs b/crates/rnote-compose/src/penpath/mod.rs index 3af3e7e7a6..fbf2569684 100644 --- a/crates/rnote-compose/src/penpath/mod.rs +++ b/crates/rnote-compose/src/penpath/mod.rs @@ -102,9 +102,9 @@ impl Transformable for PenPath { fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { self.start.mirror(centerline, orientation); - for element in &mut self.segments { + self.segments.iter_mut().for_each(|element| { element.mirror(centerline, orientation); - } + }); } } diff --git a/crates/rnote-compose/src/shapes/polygon.rs b/crates/rnote-compose/src/shapes/polygon.rs index f714e855a0..e598ed93b7 100644 --- a/crates/rnote-compose/src/shapes/polygon.rs +++ b/crates/rnote-compose/src/shapes/polygon.rs @@ -46,9 +46,9 @@ impl Transformable for Polygon { fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { point_utils::mirror_point(&mut self.start, centerline, orientation); - for point in self.path.iter_mut() { + self.path.iter_mut().for_each(|point| { point_utils::mirror_point(point, centerline, orientation); - } + }); } } diff --git a/crates/rnote-compose/src/shapes/polyline.rs b/crates/rnote-compose/src/shapes/polyline.rs index 76a78cc983..c9e9bcc227 100644 --- a/crates/rnote-compose/src/shapes/polyline.rs +++ b/crates/rnote-compose/src/shapes/polyline.rs @@ -45,9 +45,9 @@ impl Transformable for Polyline { fn mirror(&mut self, centerline: f64, orientation: MirrorOrientation) { point_utils::mirror_point(&mut self.start, centerline, orientation); - for point in self.path.iter_mut() { + self.path.iter_mut().for_each(|point| { point_utils::mirror_point(point, centerline, orientation); - } + }); } } diff --git a/crates/rnote-compose/src/transform/mod.rs b/crates/rnote-compose/src/transform/mod.rs index b33a67d738..816ca4beb1 100644 --- a/crates/rnote-compose/src/transform/mod.rs +++ b/crates/rnote-compose/src/transform/mod.rs @@ -136,24 +136,22 @@ impl Transform { /// Apply a reflection across either Horizontal: 'x = centerline' or Vertical: 'y = centerline' to the /// affine matrix based on the orientation pub fn append_mirror_mut(&mut self, centerline: f64, orientation: MirrorOrientation) { - let mirror_transformation; - - match orientation { + let mirror_transformation = match orientation { MirrorOrientation::Horizontal => { - mirror_transformation = na::matrix![ + na::matrix![ -1.0, 0.0, 2.0 * centerline; 0.0, 1.0, 0.0; 0.0, 0.0, 1.0; - ]; + ] } MirrorOrientation::Vertical => { - mirror_transformation = na::matrix![ + na::matrix![ 1.0, 0.0, 0.0; 0.0, -1.0, 2.0 * centerline; 0.0, 0.0, 1.0; - ]; + ] } - } + }; let transformed_affine = mirror_transformation * self.affine.matrix(); From a1b167f4325f00a899d050599efc9f0a8248dec3 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Mon, 25 Aug 2025 09:13:32 -0600 Subject: [PATCH 20/26] prevent mirror operations with TextStrokes selected --- crates/rnote-engine/src/engine/mod.rs | 34 +++++++++++++------- crates/rnote-engine/src/store/stroke_comp.rs | 27 +++++++++++++--- crates/rnote-ui/src/appwindow/actions.rs | 22 ++++++++++--- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 4a753bb4a4..9ca2ff2161 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -754,20 +754,30 @@ impl Engine { | self.update_rendering_current_viewport() } - pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags { - self.store.mirror_stroke( - &self.store.selection_keys_as_rendered(), - MirrorOrientation::Horizontal, - ) | self.record(Instant::now()) - | self.update_content_rendering_current_viewport() + pub fn mirror_horizontal_selection(&mut self) -> Option { + self.store + .mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Horizontal, + ) + .map(|widget_flags| { + widget_flags + | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() + }) } - pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { - self.store.mirror_stroke( - &self.store.selection_keys_as_rendered(), - MirrorOrientation::Vertical, - ) | self.record(Instant::now()) - | self.update_content_rendering_current_viewport() + pub fn mirror_vertical_selection(&mut self) -> Option { + self.store + .mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Vertical, + ) + .map(|widget_flags| { + widget_flags + | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() + }) } pub fn select_with_bounds( diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index a217ff3ab9..9f0de41883 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -305,11 +305,30 @@ impl StrokeStore { &mut self, keys: &[StrokeKey], orientation: MirrorOrientation, - ) -> WidgetFlags { + ) -> Option { let mut widget_flags = WidgetFlags::default(); if keys.is_empty() { - return widget_flags; + return Some(widget_flags); + } + + let mut stroke_contains_text = false; + keys.iter().for_each(|&key| { + if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) + .get_mut(key) + .map(Arc::make_mut) + { + match stroke { + Stroke::TextStroke(_text_stroke) => { + stroke_contains_text = true; + } + _ => {} + } + } + }); + + if stroke_contains_text { + return None; } let all_stroke_bounds = self.strokes_bounds(keys); @@ -344,7 +363,7 @@ impl StrokeStore { if let (Some(min_component), Some(max_component)) = (min_component, max_component) { (min_component + max_component) / 2.0 } else { - return widget_flags; + return Some(widget_flags); }; keys.iter().for_each(|&key| { @@ -360,7 +379,7 @@ impl StrokeStore { widget_flags.redraw = true; widget_flags.store_modified = true; - widget_flags + Some(widget_flags) } /// Invert the stroke, text and fill color of the given keys. diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 1f7d603f7f..dfdabffe13 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -1,5 +1,5 @@ // Imports -use crate::{RnAppWindow, RnCanvas, config, dialogs}; +use crate::{RnAppWindow, RnCanvas, config, dialogs, overlays}; use gettextrs::gettext; use gtk4::gio::InputStream; use gtk4::graphene; @@ -478,8 +478,14 @@ impl RnAppWindow { let Some(canvas) = appwindow.active_tab_canvas() else { return; }; - let widget_flags = canvas.engine_mut().mirror_horizontal_selection(); - appwindow.handle_widget_flags(widget_flags, &canvas); + if let Some(widget_flags) = canvas.engine_mut().mirror_horizontal_selection() { + appwindow.handle_widget_flags(widget_flags, &canvas); + } else { + appwindow.overlays().dispatch_toast_text( + &gettext("Mirroring selections containing text is not supported"), + overlays::TEXT_TOAST_TIMEOUT_DEFAULT, + ); + } } )); @@ -491,8 +497,14 @@ impl RnAppWindow { let Some(canvas) = appwindow.active_tab_canvas() else { return; }; - let widget_flags = canvas.engine_mut().mirror_vertical_selection(); - appwindow.handle_widget_flags(widget_flags, &canvas); + if let Some(widget_flags) = canvas.engine_mut().mirror_vertical_selection() { + appwindow.handle_widget_flags(widget_flags, &canvas); + } else { + appwindow.overlays().dispatch_toast_text( + &gettext("Mirroring selections containing text is not supported"), + overlays::TEXT_TOAST_TIMEOUT_DEFAULT, + ); + } } )); From 019285a27a7edac201dbef454354121be8c80c3b Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Tue, 26 Aug 2025 09:29:34 -0600 Subject: [PATCH 21/26] add keyboard shortcuts --- crates/rnote-engine/src/pens/selector/mod.rs | 24 ++++++++++++ .../src/pens/selector/penevents.rs | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/crates/rnote-engine/src/pens/selector/mod.rs b/crates/rnote-engine/src/pens/selector/mod.rs index b1035b86f6..9eba855a7d 100644 --- a/crates/rnote-engine/src/pens/selector/mod.rs +++ b/crates/rnote-engine/src/pens/selector/mod.rs @@ -21,6 +21,7 @@ use rnote_compose::ext::{AabbExt, Vector2Ext}; use rnote_compose::penevent::{PenEvent, PenProgress, PenState}; use rnote_compose::penpath::Element; use rnote_compose::style::indicators; +use rnote_compose::transform::MirrorOrientation; use rnote_compose::{Color, color}; use std::time::Instant; use tracing::error; @@ -802,6 +803,29 @@ impl Selector { *widget_flags |= self.update_state(engine_view); } + + fn mirror( + &mut self, + engine_view: &mut EngineViewMut, + orientation: MirrorOrientation, + ) -> Option { + let strokes = engine_view.store.selection_keys_as_rendered(); + + let widget_flags = engine_view + .store + .mirror_stroke(&strokes, orientation) + .map(|wf| wf | engine_view.store.record(Instant::now())); + + engine_view.store.update_geometry_for_strokes(&strokes); + engine_view.store.regenerate_rendering_in_viewport_threaded( + engine_view.tasks_tx.clone(), + false, + engine_view.camera.viewport(), + engine_view.camera.image_scale(), + ); + + widget_flags + } } fn cancel_selection(selection: &[StrokeKey], engine_view: &mut EngineViewMut) -> WidgetFlags { diff --git a/crates/rnote-engine/src/pens/selector/penevents.rs b/crates/rnote-engine/src/pens/selector/penevents.rs index d4880c8212..f584df9276 100644 --- a/crates/rnote-engine/src/pens/selector/penevents.rs +++ b/crates/rnote-engine/src/pens/selector/penevents.rs @@ -12,6 +12,7 @@ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::ext::{AabbExt, Vector2Ext}; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenProgress}; use rnote_compose::penpath::Element; +use rnote_compose::transform::MirrorOrientation; use std::collections::HashSet; use std::time::Instant; @@ -685,6 +686,44 @@ impl Selector { } } } + KeyboardKey::Unicode('x') => { + if let Some(mirror_widget_flags) = + self.mirror(engine_view, MirrorOrientation::Horizontal) + { + widget_flags = mirror_widget_flags; + + EventResult { + handled: true, + propagate: EventPropagation::Stop, + progress: PenProgress::InProgress, + } + } else { + EventResult { + handled: false, + propagate: EventPropagation::Proceed, + progress: PenProgress::InProgress, + } + } + } + KeyboardKey::Unicode('y') => { + if let Some(mirror_widget_flags) = + self.mirror(engine_view, MirrorOrientation::Vertical) + { + widget_flags = mirror_widget_flags; + + EventResult { + handled: true, + propagate: EventPropagation::Stop, + progress: PenProgress::InProgress, + } + } else { + EventResult { + handled: false, + propagate: EventPropagation::Proceed, + progress: PenProgress::InProgress, + } + } + } KeyboardKey::Delete | KeyboardKey::BackSpace => { engine_view.store.set_trashed_keys(selection, true); widget_flags |= super::cancel_selection(selection, engine_view); From af9d5d0fa01350aa166bceeafc8af17bb718d3d7 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Tue, 26 Aug 2025 15:54:33 -0600 Subject: [PATCH 22/26] change keyboard shortcuts and bind them to gtk accels --- crates/rnote-engine/src/pens/selector/mod.rs | 24 ------------ .../src/pens/selector/penevents.rs | 39 ------------------- crates/rnote-ui/data/ui/shortcuts.ui | 12 ++++++ crates/rnote-ui/src/appwindow/actions.rs | 2 + 4 files changed, 14 insertions(+), 63 deletions(-) diff --git a/crates/rnote-engine/src/pens/selector/mod.rs b/crates/rnote-engine/src/pens/selector/mod.rs index 9eba855a7d..b1035b86f6 100644 --- a/crates/rnote-engine/src/pens/selector/mod.rs +++ b/crates/rnote-engine/src/pens/selector/mod.rs @@ -21,7 +21,6 @@ use rnote_compose::ext::{AabbExt, Vector2Ext}; use rnote_compose::penevent::{PenEvent, PenProgress, PenState}; use rnote_compose::penpath::Element; use rnote_compose::style::indicators; -use rnote_compose::transform::MirrorOrientation; use rnote_compose::{Color, color}; use std::time::Instant; use tracing::error; @@ -803,29 +802,6 @@ impl Selector { *widget_flags |= self.update_state(engine_view); } - - fn mirror( - &mut self, - engine_view: &mut EngineViewMut, - orientation: MirrorOrientation, - ) -> Option { - let strokes = engine_view.store.selection_keys_as_rendered(); - - let widget_flags = engine_view - .store - .mirror_stroke(&strokes, orientation) - .map(|wf| wf | engine_view.store.record(Instant::now())); - - engine_view.store.update_geometry_for_strokes(&strokes); - engine_view.store.regenerate_rendering_in_viewport_threaded( - engine_view.tasks_tx.clone(), - false, - engine_view.camera.viewport(), - engine_view.camera.image_scale(), - ); - - widget_flags - } } fn cancel_selection(selection: &[StrokeKey], engine_view: &mut EngineViewMut) -> WidgetFlags { diff --git a/crates/rnote-engine/src/pens/selector/penevents.rs b/crates/rnote-engine/src/pens/selector/penevents.rs index f584df9276..d4880c8212 100644 --- a/crates/rnote-engine/src/pens/selector/penevents.rs +++ b/crates/rnote-engine/src/pens/selector/penevents.rs @@ -12,7 +12,6 @@ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::ext::{AabbExt, Vector2Ext}; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenProgress}; use rnote_compose::penpath::Element; -use rnote_compose::transform::MirrorOrientation; use std::collections::HashSet; use std::time::Instant; @@ -686,44 +685,6 @@ impl Selector { } } } - KeyboardKey::Unicode('x') => { - if let Some(mirror_widget_flags) = - self.mirror(engine_view, MirrorOrientation::Horizontal) - { - widget_flags = mirror_widget_flags; - - EventResult { - handled: true, - propagate: EventPropagation::Stop, - progress: PenProgress::InProgress, - } - } else { - EventResult { - handled: false, - propagate: EventPropagation::Proceed, - progress: PenProgress::InProgress, - } - } - } - KeyboardKey::Unicode('y') => { - if let Some(mirror_widget_flags) = - self.mirror(engine_view, MirrorOrientation::Vertical) - { - widget_flags = mirror_widget_flags; - - EventResult { - handled: true, - propagate: EventPropagation::Stop, - progress: PenProgress::InProgress, - } - } else { - EventResult { - handled: false, - propagate: EventPropagation::Proceed, - progress: PenProgress::InProgress, - } - } - } KeyboardKey::Delete | KeyboardKey::BackSpace => { engine_view.store.set_trashed_keys(selection, true); widget_flags |= super::cancel_selection(selection, engine_view); diff --git a/crates/rnote-ui/data/ui/shortcuts.ui b/crates/rnote-ui/data/ui/shortcuts.ui index 2b7e2ac840..6a83d20dcd 100644 --- a/crates/rnote-ui/data/ui/shortcuts.ui +++ b/crates/rnote-ui/data/ui/shortcuts.ui @@ -255,6 +255,18 @@ <ctrl><shift>z + + + Mirror Selection Horizontally + <ctrl>m + + + + + Mirror Selection Vertically + <ctrl><shift>m + + diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index dfdabffe13..82bba265af 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -1072,6 +1072,8 @@ impl RnAppWindow { app.set_accels_for_action("win.pen-style::eraser", &["4", "KP_4"]); app.set_accels_for_action("win.pen-style::selector", &["5", "KP_5"]); app.set_accels_for_action("win.pen-style::tools", &["6", "KP_6"]); + app.set_accels_for_action("win.selection-mirror-horizontal", &["m"]); + app.set_accels_for_action("win.selection-mirror-vertical", &["m"]); // shortcuts for devel build if config::PROFILE.to_lowercase().as_str() == "devel" { From 9a63a4847906d944e8a4b74f58f3685e8f22c911 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Tue, 26 Aug 2025 16:55:15 -0600 Subject: [PATCH 23/26] modify WidgetFlags to send a popup message to the user --- crates/rnote-engine/src/engine/mod.rs | 34 +++++++------------- crates/rnote-engine/src/store/stroke_comp.rs | 32 ++++++++---------- crates/rnote-engine/src/widgetflags.rs | 8 +++++ crates/rnote-ui/src/appwindow/actions.rs | 22 +++---------- crates/rnote-ui/src/appwindow/mod.rs | 8 ++++- 5 files changed, 46 insertions(+), 58 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 9ca2ff2161..4a753bb4a4 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -754,30 +754,20 @@ impl Engine { | self.update_rendering_current_viewport() } - pub fn mirror_horizontal_selection(&mut self) -> Option { - self.store - .mirror_stroke( - &self.store.selection_keys_as_rendered(), - MirrorOrientation::Horizontal, - ) - .map(|widget_flags| { - widget_flags - | self.record(Instant::now()) - | self.update_content_rendering_current_viewport() - }) + pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags { + self.store.mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Horizontal, + ) | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() } - pub fn mirror_vertical_selection(&mut self) -> Option { - self.store - .mirror_stroke( - &self.store.selection_keys_as_rendered(), - MirrorOrientation::Vertical, - ) - .map(|widget_flags| { - widget_flags - | self.record(Instant::now()) - | self.update_content_rendering_current_viewport() - }) + pub fn mirror_vertical_selection(&mut self) -> WidgetFlags { + self.store.mirror_stroke( + &self.store.selection_keys_as_rendered(), + MirrorOrientation::Vertical, + ) | self.record(Instant::now()) + | self.update_content_rendering_current_viewport() } pub fn select_with_bounds( diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 9f0de41883..cfe4de9785 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -305,30 +305,26 @@ impl StrokeStore { &mut self, keys: &[StrokeKey], orientation: MirrorOrientation, - ) -> Option { + ) -> WidgetFlags { let mut widget_flags = WidgetFlags::default(); if keys.is_empty() { - return Some(widget_flags); + return widget_flags; } - let mut stroke_contains_text = false; - keys.iter().for_each(|&key| { - if let Some(stroke) = Arc::make_mut(&mut self.stroke_components) - .get_mut(key) - .map(Arc::make_mut) - { - match stroke { - Stroke::TextStroke(_text_stroke) => { - stroke_contains_text = true; - } - _ => {} - } - } + let stroke_contains_text = keys.iter().any(|&key| { + matches!( + Arc::make_mut(&mut self.stroke_components) + .get_mut(key) + .map(Arc::make_mut), + Some(Stroke::TextStroke(_)) + ) }); if stroke_contains_text { - return None; + widget_flags.popup_message = + Some("Mirroring selections containing text is not supported".to_string()); + return widget_flags; } let all_stroke_bounds = self.strokes_bounds(keys); @@ -363,7 +359,7 @@ impl StrokeStore { if let (Some(min_component), Some(max_component)) = (min_component, max_component) { (min_component + max_component) / 2.0 } else { - return Some(widget_flags); + return widget_flags; }; keys.iter().for_each(|&key| { @@ -379,7 +375,7 @@ impl StrokeStore { widget_flags.redraw = true; widget_flags.store_modified = true; - Some(widget_flags) + widget_flags } /// Invert the stroke, text and fill color of the given keys. diff --git a/crates/rnote-engine/src/widgetflags.rs b/crates/rnote-engine/src/widgetflags.rs index d348063a9c..123b78c5d4 100644 --- a/crates/rnote-engine/src/widgetflags.rs +++ b/crates/rnote-engine/src/widgetflags.rs @@ -26,6 +26,10 @@ pub struct WidgetFlags { /// Meaning, when enabled instead of key events, text events are then emitted /// for regular unicode text. Used when writing text with the typewriter. pub enable_text_preprocessing: Option, + /// If Some, a popup message is sent to the user, through a dispatch_toast_text message. + /// Intended to notify the user that an operation they performed could not be completed + /// or is not possible + pub popup_message: Option, } impl Default for WidgetFlags { @@ -42,6 +46,7 @@ impl Default for WidgetFlags { hide_undo: None, hide_redo: None, enable_text_preprocessing: None, + popup_message: None, } } } @@ -74,5 +79,8 @@ impl std::ops::BitOrAssign for WidgetFlags { if rhs.enable_text_preprocessing.is_some() { self.enable_text_preprocessing = rhs.enable_text_preprocessing; } + if rhs.popup_message.is_some() { + self.popup_message = rhs.popup_message.clone(); + } } } diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 82bba265af..3d862d971c 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -1,5 +1,5 @@ // Imports -use crate::{RnAppWindow, RnCanvas, config, dialogs, overlays}; +use crate::{RnAppWindow, RnCanvas, config, dialogs}; use gettextrs::gettext; use gtk4::gio::InputStream; use gtk4::graphene; @@ -478,14 +478,8 @@ impl RnAppWindow { let Some(canvas) = appwindow.active_tab_canvas() else { return; }; - if let Some(widget_flags) = canvas.engine_mut().mirror_horizontal_selection() { - appwindow.handle_widget_flags(widget_flags, &canvas); - } else { - appwindow.overlays().dispatch_toast_text( - &gettext("Mirroring selections containing text is not supported"), - overlays::TEXT_TOAST_TIMEOUT_DEFAULT, - ); - } + let widget_flags = canvas.engine_mut().mirror_horizontal_selection(); + appwindow.handle_widget_flags(widget_flags, &canvas); } )); @@ -497,14 +491,8 @@ impl RnAppWindow { let Some(canvas) = appwindow.active_tab_canvas() else { return; }; - if let Some(widget_flags) = canvas.engine_mut().mirror_vertical_selection() { - appwindow.handle_widget_flags(widget_flags, &canvas); - } else { - appwindow.overlays().dispatch_toast_text( - &gettext("Mirroring selections containing text is not supported"), - overlays::TEXT_TOAST_TIMEOUT_DEFAULT, - ); - } + let widget_flags = canvas.engine_mut().mirror_vertical_selection(); + appwindow.handle_widget_flags(widget_flags, &canvas); } )); diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index d8b7e23bb3..421c38692b 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -6,7 +6,7 @@ mod imp; // Imports use crate::{ FileType, RnApp, RnCanvas, RnCanvasWrapper, RnMainHeader, RnOverlays, RnSidebar, config, - dialogs, env, + dialogs, env, overlays, }; use adw::{prelude::*, subclass::prelude::*}; use core::cell::{Ref, RefMut}; @@ -335,6 +335,12 @@ impl RnAppWindow { if let Some(enable_text_preprocessing) = widget_flags.enable_text_preprocessing { canvas.set_text_preprocessing(enable_text_preprocessing); } + if let Some(popup_message) = widget_flags.popup_message { + self.overlays().dispatch_toast_text( + &gettext(popup_message), + overlays::TEXT_TOAST_TIMEOUT_DEFAULT, + ); + } } /// Get the active (selected) tab page. From e7322e031a8923a52267afb55251c9f4c7dda811 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Tue, 26 Aug 2025 23:49:32 -0600 Subject: [PATCH 24/26] relocate gettext call --- Cargo.lock | 1 + crates/rnote-engine/Cargo.toml | 1 + crates/rnote-engine/src/store/stroke_comp.rs | 6 ++++-- crates/rnote-ui/src/appwindow/mod.rs | 6 ++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6306a5afc9..f7a4fc35c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3587,6 +3587,7 @@ dependencies = [ "flate2", "futures", "geo", + "gettext-rs", "gio", "glib", "gtk4", diff --git a/crates/rnote-engine/Cargo.toml b/crates/rnote-engine/Cargo.toml index c8add5331c..c2c05918c2 100644 --- a/crates/rnote-engine/Cargo.toml +++ b/crates/rnote-engine/Cargo.toml @@ -20,6 +20,7 @@ clap = { workspace = true, optional = true } flate2 = { workspace = true } futures = { workspace = true } geo = { workspace = true } +gettext-rs = { workspace = true } gio = { workspace = true } glib = { workspace = true } ijson = { workspace = true } diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index cfe4de9785..22d7ba1793 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -6,6 +6,7 @@ use crate::strokes::{Content, Stroke}; use crate::{StrokeStore, WidgetFlags}; use geo::intersects::Intersects; use geo::prelude::Contains; +use gettextrs::gettext; use p2d::bounding_volume::{Aabb, BoundingVolume}; use rnote_compose::Color; use rnote_compose::penpath::Element; @@ -322,8 +323,9 @@ impl StrokeStore { }); if stroke_contains_text { - widget_flags.popup_message = - Some("Mirroring selections containing text is not supported".to_string()); + widget_flags.popup_message = Some(gettext( + "Mirroring selections containing text is not supported", + )); return widget_flags; } diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index 421c38692b..f6781a0a87 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -336,10 +336,8 @@ impl RnAppWindow { canvas.set_text_preprocessing(enable_text_preprocessing); } if let Some(popup_message) = widget_flags.popup_message { - self.overlays().dispatch_toast_text( - &gettext(popup_message), - overlays::TEXT_TOAST_TIMEOUT_DEFAULT, - ); + self.overlays() + .dispatch_toast_text(&popup_message, overlays::TEXT_TOAST_TIMEOUT_DEFAULT); } } From 4159527e4212eda4c9b23fa8e2009b119dcaddb8 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Wed, 27 Aug 2025 12:31:45 -0600 Subject: [PATCH 25/26] properly update stroke geometry --- crates/rnote-engine/src/store/stroke_comp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 22d7ba1793..1917a38fb1 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -373,6 +373,7 @@ impl StrokeStore { self.set_rendering_dirty(key); } }); + self.update_geometry_for_strokes(keys); widget_flags.redraw = true; widget_flags.store_modified = true; From 53da69d48ff2278d050afb33e285595e74893217 Mon Sep 17 00:00:00 2001 From: "podzolelements@mailbox.org" Date: Thu, 28 Aug 2025 16:28:32 -0600 Subject: [PATCH 26/26] move WidgetFlags text popup proccessing into rnote-ui --- Cargo.lock | 1 - crates/rnote-engine/Cargo.toml | 1 - crates/rnote-engine/src/store/stroke_comp.rs | 6 ++---- crates/rnote-engine/src/widgetflags.rs | 8 +++++++- crates/rnote-ui/src/appwindow/mod.rs | 9 ++++++++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7a4fc35c4..6306a5afc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3587,7 +3587,6 @@ dependencies = [ "flate2", "futures", "geo", - "gettext-rs", "gio", "glib", "gtk4", diff --git a/crates/rnote-engine/Cargo.toml b/crates/rnote-engine/Cargo.toml index c2c05918c2..c8add5331c 100644 --- a/crates/rnote-engine/Cargo.toml +++ b/crates/rnote-engine/Cargo.toml @@ -20,7 +20,6 @@ clap = { workspace = true, optional = true } flate2 = { workspace = true } futures = { workspace = true } geo = { workspace = true } -gettext-rs = { workspace = true } gio = { workspace = true } glib = { workspace = true } ijson = { workspace = true } diff --git a/crates/rnote-engine/src/store/stroke_comp.rs b/crates/rnote-engine/src/store/stroke_comp.rs index 1917a38fb1..edb089a007 100644 --- a/crates/rnote-engine/src/store/stroke_comp.rs +++ b/crates/rnote-engine/src/store/stroke_comp.rs @@ -3,10 +3,10 @@ use super::StrokeKey; use super::render_comp::RenderCompState; use crate::engine::StrokeContent; use crate::strokes::{Content, Stroke}; +use crate::widgetflags::PopupMessage; use crate::{StrokeStore, WidgetFlags}; use geo::intersects::Intersects; use geo::prelude::Contains; -use gettextrs::gettext; use p2d::bounding_volume::{Aabb, BoundingVolume}; use rnote_compose::Color; use rnote_compose::penpath::Element; @@ -323,9 +323,7 @@ impl StrokeStore { }); if stroke_contains_text { - widget_flags.popup_message = Some(gettext( - "Mirroring selections containing text is not supported", - )); + widget_flags.popup_message = Some(PopupMessage::MirrorText); return widget_flags; } diff --git a/crates/rnote-engine/src/widgetflags.rs b/crates/rnote-engine/src/widgetflags.rs index 123b78c5d4..ed6f2733e6 100644 --- a/crates/rnote-engine/src/widgetflags.rs +++ b/crates/rnote-engine/src/widgetflags.rs @@ -1,3 +1,9 @@ +/// Types of messages that can be sent to the user via a dispatch_toast_text +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PopupMessage { + MirrorText, +} + /// Flags returned to the UI widget that holds the engine. #[must_use] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -29,7 +35,7 @@ pub struct WidgetFlags { /// If Some, a popup message is sent to the user, through a dispatch_toast_text message. /// Intended to notify the user that an operation they performed could not be completed /// or is not possible - pub popup_message: Option, + pub popup_message: Option, } impl Default for WidgetFlags { diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index f6781a0a87..049a4c1597 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -19,6 +19,7 @@ use rnote_engine::ext::GdkRGBAExt; use rnote_engine::pens::PenStyle; use rnote_engine::pens::pensconfig::brushconfig::BrushStyle; use rnote_engine::pens::pensconfig::shaperconfig::ShaperStyle; +use rnote_engine::widgetflags::PopupMessage; use rnote_engine::{WidgetFlags, engine::EngineTask}; use std::path::Path; use tracing::{debug, error}; @@ -336,8 +337,14 @@ impl RnAppWindow { canvas.set_text_preprocessing(enable_text_preprocessing); } if let Some(popup_message) = widget_flags.popup_message { + let popup_text = match popup_message { + PopupMessage::MirrorText => { + gettext("Mirroring selections containing text is not supported") + } + }; + self.overlays() - .dispatch_toast_text(&popup_message, overlays::TEXT_TOAST_TIMEOUT_DEFAULT); + .dispatch_toast_text(&popup_text, overlays::TEXT_TOAST_TIMEOUT_DEFAULT); } }