From 51ce51ea8c5c8bc319d91cb42cfa7a1a05a617d8 Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Thu, 21 Nov 2024 06:27:49 +0000 Subject: [PATCH] Refactor collection of snap targets (#2114) * Collect snap targets cleanup * Make Clippy happy --------- Co-authored-by: Keavon Chambers --- editor/src/consts.rs | 3 ++ .../tool/common_functionality/resize.rs | 18 ++++---- .../tool/common_functionality/shape_editor.rs | 3 +- .../tool/common_functionality/snapping.rs | 43 +++++++++++------- .../snapping/alignment_snapper.rs | 12 ++--- .../snapping/distribution_snapper.rs | 13 +++--- .../snapping/layer_snapper.rs | 38 +++++++++++----- .../transformation_cage.rs | 44 ++++++++++++------- .../tool/tool_messages/artboard_tool.rs | 6 ++- .../messages/tool/tool_messages/line_tool.rs | 17 +++---- .../messages/tool/tool_messages/pen_tool.rs | 19 ++++---- 11 files changed, 133 insertions(+), 83 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 3f4f3c5c..69e54f87 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -30,6 +30,9 @@ pub const DRAG_BEYOND_VIEWPORT_SPEED_FACTOR: f64 = 20.; // Snapping point pub const SNAP_POINT_TOLERANCE: f64 = 5.; +pub const MAX_ALIGNMENT_CANDIDATES: usize = 100; // These are layers whose bounding boxes are used for alignment. +pub const MAX_SNAP_CANDIDATES: usize = 10; // These are layers that are used for the layer snapper +pub const MAX_LAYER_SNAP_POINTS: usize = 100; // These are points (anchors and bounding box corners etc.) in the layer snapper pub const DRAG_THRESHOLD: f64 = 1.; diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index c01931ad..fe4c2218 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -5,7 +5,7 @@ use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::{input_mapper::utility_types::input_keyboard::Key, portfolio::document::graph_operation::utility_types::TransformIn}; use glam::{DAffine2, DVec2, Vec2Swizzles}; -use super::snapping::{SnapCandidatePoint, SnapConstraint, SnapData}; +use super::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration}; #[derive(Clone, Debug, Default)] pub struct Resize { @@ -19,7 +19,7 @@ impl Resize { pub fn start(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { let root_transform = document.metadata().document_to_viewport; let point = SnapCandidatePoint::handle(root_transform.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); self.drag_start = snapped.snapped_point_document; } @@ -51,6 +51,7 @@ impl Resize { let ratio = input.keyboard.get(lock_ratio as usize); let center = input.keyboard.get(center as usize); let snap_data = SnapData::ignore(document, input, &ignore); + let config = SnapTypeConfiguration::default(); if ratio { let size = points_viewport[1] - points_viewport[0]; let size = size.abs().max(size.abs().yx()) * size.signum(); @@ -61,27 +62,28 @@ impl Resize { direction: end_document - self.drag_start, }; if center { - let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, None); + let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config); let far = SnapCandidatePoint::handle(2. * self.drag_start - end_document); - let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, None); + let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, config); let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document); points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document); self.snap_manager.update_indicator(best); } else { - let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, None); + let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config); points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document); self.snap_manager.update_indicator(snapped); } } else if center { - let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false); - let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(2. * self.drag_start - document_mouse), None, false); + let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config); + let opposite = 2. * self.drag_start - document_mouse; + let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(opposite), config); let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document); points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document); self.snap_manager.update_indicator(best); } else { - let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false); + let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config); points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document); self.snap_manager.update_indicator(snapped); } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 76d51f4f..446c48e3 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource}; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration; use crate::messages::tool::tool_messages::path_tool::PointSelectState; use bezier_rs::{Bezier, BezierHandles, TValue}; @@ -210,7 +211,7 @@ impl ShapeState { } } - let snapped = snap_manager.free_snap(&snap_data, &point, None, false); + let snapped = snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()); if best_snapped.other_snap_better(&snapped) { offset = snapped.snapped_point_document - point.document_point + mouse_delta; best_snapped = snapped; diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index f09f4c2c..1d9bffc1 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -20,6 +20,15 @@ use glam::{DAffine2, DVec2}; use graphene_std::vector::NoHashBuilder; use std::cmp::Ordering; +/// Configuration for the relevant snap type +#[derive(Debug, Clone, Copy, Default)] +pub struct SnapTypeConfiguration { + pub only_geometry: bool, + pub use_existing_candidates: bool, + pub accept_distribution: bool, + pub bbox: Option, +} + /// Handles snapping and snap overlays #[derive(Debug, Clone, Default)] pub struct SnapManager { @@ -242,7 +251,7 @@ impl SnapManager { } pub fn preview_draw(&mut self, snap_data: &SnapData, mouse: DVec2) { let point = SnapCandidatePoint::handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse)); - let snapped = self.free_snap(snap_data, &point, None, false); + let snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default()); self.update_indicator(snapped); } @@ -342,64 +351,64 @@ impl SnapManager { self.add_candidates(layer, snap_data, quad); } - if self.alignment_candidates.as_ref().is_some_and(|candidates| candidates.len() > 100) { + if self.alignment_candidates.as_ref().is_some_and(|candidates| candidates.len() > crate::consts::MAX_ALIGNMENT_CANDIDATES) { warn!("Alignment candidate overflow"); } - if self.candidates.as_ref().is_some_and(|candidates| candidates.len() > 10) { + if self.candidates.as_ref().is_some_and(|candidates| candidates.len() > crate::consts::MAX_SNAP_CANDIDATES) { warn!("Snap candidate overflow"); } } - pub fn free_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, bbox: Option, to_paths: bool) -> SnappedPoint { + pub fn free_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, config: SnapTypeConfiguration) -> SnappedPoint { if !point.document_point.is_finite() { warn!("Snapping non-finite position"); return SnappedPoint::infinite_snap(DVec2::ZERO); } let mut snap_results = SnapResults::default(); - if point.source_index == 0 { + if !config.use_existing_candidates { self.candidates = None; } let mut snap_data = snap_data.clone(); if snap_data.candidates.is_none() { - self.find_candidates(&snap_data, point, bbox); + self.find_candidates(&snap_data, point, config.bbox); } snap_data.candidates = self.candidates.as_ref(); snap_data.alignment_candidates = self.alignment_candidates.as_ref(); - self.layer_snapper.free_snap(&mut snap_data, point, &mut snap_results); + self.layer_snapper.free_snap(&mut snap_data, point, &mut snap_results, config); self.grid_snapper.free_snap(&mut snap_data, point, &mut snap_results); - self.alignment_snapper.free_snap(&mut snap_data, point, &mut snap_results); - self.distribution_snapper.free_snap(&mut snap_data, point, &mut snap_results, bbox); + self.alignment_snapper.free_snap(&mut snap_data, point, &mut snap_results, config); + self.distribution_snapper.free_snap(&mut snap_data, point, &mut snap_results, config); - Self::find_best_snap(&mut snap_data, point, snap_results, false, false, to_paths) + Self::find_best_snap(&mut snap_data, point, snap_results, false, false, config.only_geometry) } - pub fn constrained_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, constraint: SnapConstraint, bbox: Option) -> SnappedPoint { + pub fn constrained_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, constraint: SnapConstraint, config: SnapTypeConfiguration) -> SnappedPoint { if !point.document_point.is_finite() { warn!("Snapping non-finite position"); return SnappedPoint::infinite_snap(DVec2::ZERO); } let mut snap_results = SnapResults::default(); - if point.source_index == 0 { + if !config.use_existing_candidates { self.candidates = None; } let mut snap_data = snap_data.clone(); if snap_data.candidates.is_none() { - self.find_candidates(&snap_data, point, bbox); + self.find_candidates(&snap_data, point, config.bbox); } snap_data.candidates = self.candidates.as_ref(); snap_data.alignment_candidates = self.alignment_candidates.as_ref(); - self.layer_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint); + self.layer_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config); self.grid_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint); - self.alignment_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint); - self.distribution_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, bbox); + self.alignment_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config); + self.distribution_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config); - Self::find_best_snap(&mut snap_data, point, snap_results, true, false, false) + Self::find_best_snap(&mut snap_data, point, snap_results, true, false, config.only_geometry) } fn alignment_x_overlay(boxes: &VecDeque, transform: DAffine2, overlay_context: &mut OverlayContext) { diff --git a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs index 2967adbc..53d7e6aa 100644 --- a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs @@ -50,8 +50,8 @@ impl AlignmentSnapper { } } - pub fn snap_bbox_points(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) { - self.collect_bounding_box_points(snap_data, point.source_index == 0); + pub fn snap_bbox_points(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) { + self.collect_bounding_box_points(snap_data, !config.use_existing_candidates); let unselected_geometry = if snap_data.document.snapping_state.target_enabled(SnapTarget::Alignment(AlignmentSnapTarget::Handle)) { snap_data.node_snap_cache.map(|cache| cache.unselected.as_slice()).unwrap_or(&[]) } else { @@ -154,23 +154,23 @@ impl AlignmentSnapper { _ => {} } } - pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults) { + pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) { let is_bbox = matches!(point.source, SnapSource::BoundingBox(_)); let is_geometry = matches!(point.source, SnapSource::Geometry(_)); let geometry_selected = snap_data.has_manipulators(); if is_bbox || (is_geometry && geometry_selected) || (is_geometry && point.alignment) { - self.snap_bbox_points(snap_data, point, snap_results, SnapConstraint::None); + self.snap_bbox_points(snap_data, point, snap_results, SnapConstraint::None, config); } } - pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) { + pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) { let is_bbox = matches!(point.source, SnapSource::BoundingBox(_)); let is_geometry = matches!(point.source, SnapSource::Geometry(_)); let geometry_selected = snap_data.has_manipulators(); if is_bbox || (is_geometry && geometry_selected) || (is_geometry && point.alignment) { - self.snap_bbox_points(snap_data, point, snap_results, constraint); + self.snap_bbox_points(snap_data, point, snap_results, constraint, config); } } } diff --git a/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs index 247be776..f0b39b77 100644 --- a/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs @@ -323,22 +323,23 @@ impl DistributionSnapper { } } - pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, bounds: Option) { - let Some(bounds) = bounds else { return }; + pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) { + let Some(bounds) = config.bbox else { return }; if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::Center) || !snap_data.document.snapping_state.bounds.distribute { return; } - self.collect_bounding_box_points(snap_data, point.source_index == 0, bounds); + self.collect_bounding_box_points(snap_data, config.accept_distribution, bounds); self.snap_bbox_points(snap_tolerance(snap_data.document), point, snap_results, SnapConstraint::None, bounds); } - pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, bounds: Option) { - let Some(bounds) = bounds else { return }; + pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) { + let Some(bounds) = config.bbox else { return }; if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::Center) || !snap_data.document.snapping_state.bounds.distribute { return; } - self.collect_bounding_box_points(snap_data, point.source_index == 0, bounds); + + self.collect_bounding_box_points(snap_data, config.accept_distribution, bounds); self.snap_bbox_points(snap_tolerance(snap_data.document), point, snap_results, constraint, bounds); } } diff --git a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs index 8f81ef66..ebcbad27 100644 --- a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs @@ -92,8 +92,8 @@ impl LayerSnapper { } } } - pub fn free_snap_paths(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults) { - self.collect_paths(snap_data, point.source_index == 0); + pub fn free_snap_paths(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) { + self.collect_paths(snap_data, !config.use_existing_candidates); let document = snap_data.document; let normals = document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Normal)); @@ -131,9 +131,9 @@ impl LayerSnapper { } } - pub fn snap_paths_constrained(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) { + pub fn snap_paths_constrained(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) { let document = snap_data.document; - self.collect_paths(snap_data, point.source_index == 0); + self.collect_paths(snap_data, !config.use_existing_candidates); let tolerance = snap_tolerance(document); let constraint_path = if let SnapConstraint::Circle { center, radius } = constraint { @@ -182,6 +182,10 @@ impl LayerSnapper { if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) { continue; } + if self.points_to_snap.len() >= crate::consts::MAX_LAYER_SNAP_POINTS { + warn!("Snap point overflow; skipping."); + return; + } if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::Corner)) { let Some(bounds) = document @@ -201,6 +205,10 @@ impl LayerSnapper { if snap_data.ignore_bounds(layer) { continue; } + if self.points_to_snap.len() >= crate::consts::MAX_LAYER_SNAP_POINTS { + warn!("Snap point overflow; skipping."); + return; + } let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else { continue; }; @@ -210,7 +218,6 @@ impl LayerSnapper { } } pub fn snap_anchors(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, c: SnapConstraint, constrained_point: DVec2) { - self.collect_anchors(snap_data, point.source_index == 0); let mut best = None; for candidate in &self.points_to_snap { // Candidate is not on constraint @@ -244,14 +251,16 @@ impl LayerSnapper { snap_results.points.push(result); } } - pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults) { + pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) { + self.collect_anchors(snap_data, !config.use_existing_candidates); self.snap_anchors(snap_data, point, snap_results, SnapConstraint::None, point.document_point); - self.free_snap_paths(snap_data, point, snap_results); + self.free_snap_paths(snap_data, point, snap_results, config); } - pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) { + pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) { + self.collect_anchors(snap_data, !config.use_existing_candidates); self.snap_anchors(snap_data, point, snap_results, constraint, constraint.projection(point.document_point)); - self.snap_paths_constrained(snap_data, point, snap_results, constraint); + self.snap_paths_constrained(snap_data, point, snap_results, constraint, config); } } @@ -313,7 +322,6 @@ pub struct SnapCandidatePoint { pub document_point: DVec2, pub source: SnapSource, pub target: SnapTarget, - pub source_index: usize, pub quad: Option, pub neighbors: Vec, pub alignment: bool, @@ -417,6 +425,9 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath= crate::consts::MAX_LAYER_SNAP_POINTS { + return; + } let in_handle = curve.handle_start().map(|handle| handle - curve.start).filter(handle_not_under(to_document)); let out_handle = curve.handle_end().map(|handle| handle - curve.end).filter(handle_not_under(to_document)); @@ -435,6 +446,10 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath= crate::consts::MAX_LAYER_SNAP_POINTS { + return; + } + let colinear = are_manipulator_handles_colinear(group, to_document, subpath, index); if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles)) { @@ -471,6 +486,9 @@ pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, p if document.network_interface.is_artboard(&layer.to_node(), &[]) { return; } + if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS { + return; + } if layer.has_children(document.metadata()) { for child in layer.descendants(document.metadata()) { diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 647799c5..e1fcb6dd 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -3,6 +3,7 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::transformation::OriginalTransforms; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration; use graphene_core::renderer::Quad; @@ -124,7 +125,15 @@ impl SelectedEdges { let mut best_snap = SnappedPoint::infinite_snap(pivot); let mut best_scale_factor = DVec2::ONE; let tolerance = snapping::snap_tolerance(snap_data.document); - for point in points { + + let bbox = Some(Rect::from_box((bounds_to_doc * Quad::from_box([min, max])).bounding_box())); + for (index, point) in points.iter_mut().enumerate() { + let config = SnapTypeConfiguration { + bbox, + use_existing_candidates: index != 0, + ..Default::default() + }; + let old_position = point.document_point; let bounds_space = bounds_to_doc.inverse().transform_point2(point.document_point); let normalized = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]); @@ -135,16 +144,16 @@ impl SelectedEdges { origin: point.document_point, direction: (point.document_point - bounds_to_doc.transform_point2(pivot)).normalize_or_zero(), }; - manager.constrained_snap(&snap_data, point, constraint, None) + manager.constrained_snap(&snap_data, point, constraint, config) } else if !(self.top || self.bottom) || !(self.left || self.right) { let axis = if !(self.top || self.bottom) { DVec2::X } else { DVec2::Y }; let constraint = SnapConstraint::Line { origin: point.document_point, direction: bounds_to_doc.transform_vector2(axis), }; - manager.constrained_snap(&snap_data, point, constraint, None) + manager.constrained_snap(&snap_data, point, constraint, config) } else { - manager.free_snap(&snap_data, point, None, false) + manager.free_snap(&snap_data, point, config) }; point.document_point = old_position; @@ -218,7 +227,7 @@ pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2 } /// Snaps a dragging event from the artboard or select tool -pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &Vec) -> DVec2 { +pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &[SnapCandidatePoint]) -> DVec2 { let mouse_position = axis_align_drag(axis_align, snap_data.input.mouse.position, start); let document = snap_data.document; let total_mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - start); @@ -228,22 +237,25 @@ pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: Snap let bbox = Rect::point_iter(candidates.iter().map(|candidate| candidate.document_point + total_mouse_delta_document)); - for point in candidates { + for (index, point) in candidates.iter().enumerate() { + let config = SnapTypeConfiguration { + bbox, + accept_distribution: true, + use_existing_candidates: index != 0, + ..Default::default() + }; + let mut point = point.clone(); point.document_point += total_mouse_delta_document; let snapped = if axis_align { - snap_manager.constrained_snap( - &snap_data, - &point, - SnapConstraint::Line { - origin: point.document_point, - direction: total_mouse_delta_document.try_normalize().unwrap_or(DVec2::X), - }, - bbox, - ) + let constraint = SnapConstraint::Line { + origin: point.document_point, + direction: total_mouse_delta_document.try_normalize().unwrap_or(DVec2::X), + }; + snap_manager.constrained_snap(&snap_data, &point, constraint, config) } else { - snap_manager.free_snap(&snap_data, &point, bbox, false) + snap_manager.free_snap(&snap_data, &point, config) }; if best_snap.other_snap_better(&snapped) { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 8536aac0..1d89e4f3 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -6,6 +6,7 @@ use crate::messages::tool::common_functionality::snapping; use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint; use crate::messages::tool::common_functionality::snapping::SnapData; use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration; use crate::messages::tool::common_functionality::transformation_cage::*; use graph_craft::document::NodeId; @@ -260,7 +261,7 @@ impl Fsm for ArtboardToolFsmState { let point = SnapCandidatePoint::handle(to_document.transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); tool_data.drag_start = snapped.snapped_point_document; tool_data.drag_current = snapped.snapped_point_document; @@ -330,7 +331,8 @@ impl Fsm for ArtboardToolFsmState { let document_mouse = to_viewport.inverse().transform_point2(input.mouse.position); - let snapped = tool_data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false); + let config = SnapTypeConfiguration::default(); + let snapped = tool_data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config); let snapped_mouse_position = to_viewport.transform_point2(snapped.snapped_point_document); tool_data.snap_manager.update_indicator(snapped); diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index a386f259..70a777e8 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; use graphene_core::Color; @@ -173,7 +173,7 @@ impl Fsm for LineToolFsmState { } (LineToolFsmState::Ready, LineToolMessage::DragStart) => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); tool_data.drag_start = snapped.snapped_point_document; responses.add(DocumentMessage::StartTransaction); @@ -314,6 +314,7 @@ fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_an let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.drag_start]); let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.drag_start]); + let config = SnapTypeConfiguration::default(); if constrained { let constraint = SnapConstraint::Line { @@ -321,26 +322,26 @@ fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_an direction: document_points[1] - document_points[0], }; if center { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None); - let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None); + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); + let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; document_points[1] = document_points[0] * 2. - best.snapped_point_document; document_points[0] = best.snapped_point_document; snap.update_indicator(best); } else { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None); + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); document_points[1] = snapped.snapped_point_document; snap.update_indicator(snapped); } } else if center { - let snapped = snap.free_snap(&snap_data, &near_point, None, false); - let snapped_far = snap.free_snap(&snap_data, &far_point, None, false); + let snapped = snap.free_snap(&snap_data, &near_point, config); + let snapped_far = snap.free_snap(&snap_data, &far_point, config); let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; document_points[1] = document_points[0] * 2. - best.snapped_point_document; document_points[0] = best.snapped_point_document; snap.update_indicator(best); } else { - let snapped = snap.free_snap(&snap_data, &near_point, None, false); + let snapped = snap.free_snap(&snap_data, &near_point, config); document_points[1] = snapped.snapped_point_document; snap.update_indicator(snapped); } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 484b3c29..eb58504d 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -8,7 +8,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::utility_functions::should_extend; use bezier_rs::{Bezier, BezierHandles}; @@ -395,6 +395,7 @@ impl PenToolData { let neighbors = relative.filter(|_| neighbor).map_or(Vec::new(), |neighbor| vec![neighbor]); + let config = SnapTypeConfiguration::default(); if let Some(relative) = relative .map(|layer| transform.transform_point2(layer)) .filter(|&relative| (snap_angle || lock_angle) && (relative - document_pos).length_squared() > f64::EPSILON * 100.) @@ -417,8 +418,8 @@ impl PenToolData { let near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()); let far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors); if colinear { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None); - let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None); + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); + let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); document_pos = if snapped_far.other_snap_better(&snapped) { snapped.snapped_point_document } else { @@ -426,13 +427,13 @@ impl PenToolData { }; snap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }); } else { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None); + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); document_pos = snapped.snapped_point_document; snap.update_indicator(snapped); } } else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| colinear) { - let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), None, false); - let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), None, false); + let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), config); + let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), config); document_pos = if snapped_far.other_snap_better(&snapped) { snapped.snapped_point_document } else { @@ -440,7 +441,7 @@ impl PenToolData { }; snap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }); } else { - let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors), None, false); + let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors), config); document_pos = snapped.snapped_point_document; snap.update_indicator(snapped); } @@ -456,7 +457,7 @@ impl PenToolData { fn create_initial_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, tool_options: &PenOptions, append: bool) { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); @@ -632,7 +633,7 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart { append_to_selected }) => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); // Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector) if tool_data.buffering_merged_vector {