diff --git a/editor/src/tool/snapping.rs b/editor/src/tool/snapping.rs index 1597361bc9..61c6512445 100644 --- a/editor/src/tool/snapping.rs +++ b/editor/src/tool/snapping.rs @@ -1,24 +1,71 @@ -use glam::DVec2; -use graphene::LayerId; +use std::f64::consts::PI; -use crate::consts::SNAP_TOLERANCE; +use glam::{DAffine2, DVec2}; +use graphene::{ + layers::style::{self, Stroke}, + LayerId, Operation, +}; + +use crate::{ + consts::{COLOR_ACCENT, SNAP_TOLERANCE}, + message_prelude::*, +}; use super::DocumentMessageHandler; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct SnapHandler { snap_targets: Option<(Vec, Vec)>, -} -impl Default for SnapHandler { - fn default() -> Self { - Self { snap_targets: None } - } + overlay_paths: Vec>, } impl SnapHandler { + fn add_overlays(&mut self, responses: &mut VecDeque, viewport_bounds: DVec2) { + fn add_overlay_line(responses: &mut VecDeque, transform: DAffine2) -> Vec { + let layer_path = vec![generate_uuid()]; + + let operation = Operation::AddOverlayLine { + path: layer_path.clone(), + transform: transform.to_cols_array(), + style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), None), + }; + responses.push_back(DocumentMessage::Overlay(operation.into()).into()); + + layer_path + } + + if let Some((x_targets, y_targets)) = &self.snap_targets { + for x_target in x_targets { + self.overlay_paths.push(add_overlay_line( + responses, + DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.y, 1.), PI / 2., DVec2::new(x_target.round(), 0.)), + )); + } + for y_target in y_targets { + self.overlay_paths.push(add_overlay_line( + responses, + DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.x, 1.), 0., DVec2::new(0., y_target.round())), + )); + } + } + } + + fn remove_overlays(&mut self, responses: &mut VecDeque) { + while let Some(layer) = self.overlay_paths.pop() { + responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into()); + } + } + /// Gets a list of snap targets for the X and Y axes in Viewport coords for the target layers (usually all layers or all non-selected layers.) /// This should be called at the start of a drag. - pub fn start_snap(&mut self, document_message_handler: &DocumentMessageHandler, target_layers: Vec>, ignore_layers: &[Vec]) { + pub fn start_snap( + &mut self, + responses: &mut VecDeque, + viewport_bounds: DVec2, + document_message_handler: &DocumentMessageHandler, + target_layers: Vec>, + ignore_layers: &[Vec], + ) { if document_message_handler.snapping_enabled { // Could be made into sorted Vec or a HashSet for more performant lookups. self.snap_targets = Some( @@ -30,6 +77,7 @@ impl SnapHandler { .map(|vec| vec.into()) .unzip(), ); + self.add_overlays(responses, viewport_bounds); } } @@ -104,7 +152,8 @@ impl SnapHandler { } } - pub fn cleanup(&mut self) { + pub fn cleanup(&mut self, responses: &mut VecDeque) { + self.remove_overlays(responses); self.snap_targets = None; } } diff --git a/editor/src/tool/tools/ellipse.rs b/editor/src/tool/tools/ellipse.rs index 1a9029801d..c90d62196e 100644 --- a/editor/src/tool/tools/ellipse.rs +++ b/editor/src/tool/tools/ellipse.rs @@ -81,7 +81,7 @@ impl Fsm for EllipseToolFsmState { if let ToolMessage::Ellipse(event) = event { match (self, event) { (Ready, DragStart) => { - shape_data.start(document, input.mouse.position); + shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position); responses.push_back(DocumentMessage::StartTransaction.into()); shape_data.path = Some(vec![generate_uuid()]); responses.push_back(DocumentMessage::DeselectAllLayers.into()); @@ -112,12 +112,12 @@ impl Fsm for EllipseToolFsmState { false => responses.push_back(DocumentMessage::CommitTransaction.into()), } - shape_data.cleanup(); + shape_data.cleanup(responses); Ready } (Drawing, Abort) => { responses.push_back(DocumentMessage::AbortTransaction.into()); - shape_data.cleanup(); + shape_data.cleanup(responses); Ready } diff --git a/editor/src/tool/tools/line.rs b/editor/src/tool/tools/line.rs index c348a8d6b6..8acb9922ec 100644 --- a/editor/src/tool/tools/line.rs +++ b/editor/src/tool/tools/line.rs @@ -86,7 +86,7 @@ impl Fsm for LineToolFsmState { if let ToolMessage::Line(event) = event { match (self, event) { (Ready, DragStart) => { - data.snap_handler.start_snap(document, document.all_layers_sorted(), &[]); + data.snap_handler.start_snap(responses, input.viewport_bounds.size(), document, document.all_layers_sorted(), &[]); data.drag_start = data.snap_handler.snap_position(document, input.mouse.position); responses.push_back(DocumentMessage::StartTransaction.into()); @@ -120,7 +120,7 @@ impl Fsm for LineToolFsmState { } (Drawing, DragStop) => { data.drag_current = data.snap_handler.snap_position(document, input.mouse.position); - data.snap_handler.cleanup(); + data.snap_handler.cleanup(responses); // TODO: introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) match data.drag_start == input.mouse.position { @@ -133,7 +133,7 @@ impl Fsm for LineToolFsmState { Ready } (Drawing, Abort) => { - data.snap_handler.cleanup(); + data.snap_handler.cleanup(responses); responses.push_back(DocumentMessage::AbortTransaction.into()); data.path = None; Ready diff --git a/editor/src/tool/tools/pen.rs b/editor/src/tool/tools/pen.rs index 67abe7f023..b70d192f44 100644 --- a/editor/src/tool/tools/pen.rs +++ b/editor/src/tool/tools/pen.rs @@ -94,7 +94,7 @@ impl Fsm for PenToolFsmState { data.path = Some(vec![generate_uuid()]); data.layer_exists = false; - data.snap_handler.start_snap(document, document.all_layers_sorted(), &[]); + data.snap_handler.start_snap(responses, input.viewport_bounds.size(), document, document.all_layers_sorted(), &[]); let snapped_position = data.snap_handler.snap_position(document, input.mouse.position); let pos = transform.inverse() * DAffine2::from_translation(snapped_position); @@ -148,7 +148,7 @@ impl Fsm for PenToolFsmState { data.path = None; data.points.clear(); - data.snap_handler.cleanup(); + data.snap_handler.cleanup(responses); Ready } diff --git a/editor/src/tool/tools/rectangle.rs b/editor/src/tool/tools/rectangle.rs index 600ab27be4..aff92dce4a 100644 --- a/editor/src/tool/tools/rectangle.rs +++ b/editor/src/tool/tools/rectangle.rs @@ -82,7 +82,7 @@ impl Fsm for RectangleToolFsmState { if let ToolMessage::Rectangle(event) = event { match (self, event) { (Ready, DragStart) => { - shape_data.start(document, input.mouse.position); + shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position); responses.push_back(DocumentMessage::StartTransaction.into()); shape_data.path = Some(vec![generate_uuid()]); responses.push_back(DocumentMessage::DeselectAllLayers.into()); @@ -113,13 +113,13 @@ impl Fsm for RectangleToolFsmState { false => responses.push_back(DocumentMessage::CommitTransaction.into()), } - shape_data.cleanup(); + shape_data.cleanup(responses); Ready } (Drawing, Abort) => { responses.push_back(DocumentMessage::AbortTransaction.into()); - shape_data.cleanup(); + shape_data.cleanup(responses); Ready } diff --git a/editor/src/tool/tools/resize.rs b/editor/src/tool/tools/resize.rs index 64f8c7a710..090b33b173 100644 --- a/editor/src/tool/tools/resize.rs +++ b/editor/src/tool/tools/resize.rs @@ -14,9 +14,9 @@ pub struct Resize { } impl Resize { /// Starts a resize, assigning the snap targets and snapping the starting position. - pub fn start(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) { + pub fn start(&mut self, responses: &mut VecDeque, viewport_bounds: DVec2, document: &DocumentMessageHandler, mouse_position: DVec2) { let layers = document.all_layers_sorted(); - self.snap_handler.start_snap(document, layers, &[]); + self.snap_handler.start_snap(responses, viewport_bounds, document, layers, &[]); self.drag_start = self.snap_handler.snap_position(document, mouse_position); } @@ -47,8 +47,8 @@ impl Resize { } } - pub fn cleanup(&mut self) { - self.snap_handler.cleanup(); + pub fn cleanup(&mut self, responses: &mut VecDeque) { + self.snap_handler.cleanup(responses); self.path = None; } } diff --git a/editor/src/tool/tools/select.rs b/editor/src/tool/tools/select.rs index be95ef73ec..761a488a1d 100644 --- a/editor/src/tool/tools/select.rs +++ b/editor/src/tool/tools/select.rs @@ -200,7 +200,8 @@ impl Fsm for SelectToolFsmState { } else { Vec::new() }; - data.snap_handler.start_snap(document, document.non_selected_layers_sorted(), &ignore_layers); + data.snap_handler + .start_snap(responses, input.viewport_bounds.size(), document, document.non_selected_layers_sorted(), &ignore_layers); state } (Dragging, MouseMove { snap_angle }) => { @@ -255,7 +256,7 @@ impl Fsm for SelectToolFsmState { true => DocumentMessage::Undo, false => DocumentMessage::CommitTransaction, }; - data.snap_handler.cleanup(); + data.snap_handler.cleanup(responses); responses.push_front(response.into()); Ready } diff --git a/editor/src/tool/tools/shape.rs b/editor/src/tool/tools/shape.rs index dc668d7e33..fb47e1a6a5 100644 --- a/editor/src/tool/tools/shape.rs +++ b/editor/src/tool/tools/shape.rs @@ -83,7 +83,7 @@ impl Fsm for ShapeToolFsmState { if let ToolMessage::Shape(event) = event { match (self, event) { (Ready, DragStart) => { - shape_data.start(document, input.mouse.position); + shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position); responses.push_back(DocumentMessage::StartTransaction.into()); shape_data.path = Some(vec![generate_uuid()]); responses.push_back(DocumentMessage::DeselectAllLayers.into()); @@ -121,12 +121,12 @@ impl Fsm for ShapeToolFsmState { false => responses.push_back(DocumentMessage::CommitTransaction.into()), } - shape_data.cleanup(); + shape_data.cleanup(responses); Ready } (Drawing, Abort) => { responses.push_back(DocumentMessage::AbortTransaction.into()); - shape_data.cleanup(); + shape_data.cleanup(responses); Ready }