diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 64633f197d..b8e1bbe5dc 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -404,8 +404,7 @@ pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 5] = [ ( "Distribute Evenly", (|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState, - // TODO: Fix the bug/limitation that requires 'Center Points' and 'Corner Points' to be enabled - "Snaps to a consistent distance offset established by the bounding boxes of nearby layers\n(due to a bug, 'Center Points' and 'Corner Points' must be enabled)", + "Snaps to a consistent distance offset established by the bounding boxes of nearby layers", ), ]; pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [ diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 15fab9cb2c..3c5d455de6 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -114,13 +114,13 @@ fn get_closest_point(points: Vec) -> Option { (Some(result), None) | (None, Some(result)) => Some(result), (Some(mut result), Some(align)) => { let SnapTarget::DistributeEvenly(distribution) = result.target else { return Some(result) }; - if distribution.is_x() && align.alignment_target_x.is_some() { + if distribution.is_x() && align.alignment_target_horizontal.is_some() { result.snapped_point_document.y = align.snapped_point_document.y; - result.alignment_target_x = align.alignment_target_x; + result.alignment_target_horizontal = align.alignment_target_horizontal; } - if distribution.is_y() && align.alignment_target_y.is_some() { + if distribution.is_y() && align.alignment_target_vertical.is_some() { result.snapped_point_document.x = align.snapped_point_document.x; - result.alignment_target_y = align.alignment_target_y; + result.alignment_target_vertical = align.alignment_target_vertical; } Some(result) @@ -456,10 +456,10 @@ impl SnapManager { } let viewport = to_viewport.transform_point2(ind.snapped_point_document); - Self::alignment_x_overlay(&ind.distribution_boxes_x, to_viewport, overlay_context); - Self::alignment_y_overlay(&ind.distribution_boxes_y, to_viewport, overlay_context); + Self::alignment_x_overlay(&ind.distribution_boxes_horizontal, to_viewport, overlay_context); + Self::alignment_y_overlay(&ind.distribution_boxes_vertical, to_viewport, overlay_context); - let align = [ind.alignment_target_x, ind.alignment_target_y].map(|target| target.map(|target| to_viewport.transform_point2(target))); + let align = [ind.alignment_target_horizontal, ind.alignment_target_vertical].map(|target| target.map(|target| to_viewport.transform_point2(target))); let any_align = align.iter().flatten().next().is_some(); for &target in align.iter().flatten() { overlay_context.line(viewport, target, None, None); @@ -471,7 +471,7 @@ impl SnapManager { overlay_context.manipulator_handle(viewport, false, None); } - if !any_align && ind.distribution_equal_distance_x.is_none() && ind.distribution_equal_distance_y.is_none() { + if !any_align && ind.distribution_equal_distance_horizontal.is_none() && ind.distribution_equal_distance_vertical.is_none() { let text = format!("[{}] from [{}]", ind.target, ind.source); let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.)); overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_LABEL_BACKGROUND), transform, 4., [Pivot::Start, Pivot::End]); 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 48a42351b2..47e07d46ec 100644 --- a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs @@ -90,7 +90,7 @@ impl AlignmentSnapper { distance_to_align_target, fully_constrained: false, at_intersection: true, - alignment_target_x: Some(endpoint), + alignment_target_horizontal: Some(endpoint), ..Default::default() }; snap_results.points.push(snap_point); @@ -129,7 +129,7 @@ impl AlignmentSnapper { distance: distance_to_snapped, tolerance, distance_to_align_target, - alignment_target_x: Some(target_position), + alignment_target_horizontal: Some(target_position), fully_constrained: true, at_intersection: matches!(constraint, SnapConstraint::Line { .. }), ..Default::default() @@ -148,7 +148,7 @@ impl AlignmentSnapper { distance: distance_to_snapped, tolerance, distance_to_align_target, - alignment_target_y: Some(target_position), + alignment_target_vertical: Some(target_position), fully_constrained: true, at_intersection: matches!(constraint, SnapConstraint::Line { .. }), ..Default::default() @@ -174,8 +174,8 @@ impl AlignmentSnapper { target_bounds: snap_x.target_bounds, distance, tolerance, - alignment_target_x: snap_x.alignment_target_x, - alignment_target_y: snap_y.alignment_target_y, + alignment_target_horizontal: snap_x.alignment_target_horizontal, + alignment_target_vertical: snap_y.alignment_target_vertical, constrained: true, at_intersection: true, ..Default::default() 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 9207e958e4..cdd3b28d24 100644 --- a/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs @@ -1,9 +1,9 @@ use super::*; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::*; -use crate::messages::prelude::*; use glam::DVec2; use graphene_core::renderer::Quad; +use std::collections::VecDeque; #[derive(Clone, Debug, Default)] pub struct DistributionSnapper { @@ -79,18 +79,21 @@ impl DistributionSnapper { let screen_bounds = (document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()])).bounding_box(); let max_extent = (screen_bounds[1] - screen_bounds[0]).abs().max_element(); + // Collect artboard bounds for layer in document.metadata().all_layers() { if document.network_interface.is_artboard(&layer.to_node(), &[]) && !snap_data.ignore.contains(&layer) { self.add_bounds(layer, snap_data, bbox_to_snap, max_extent); } } + // Collect alignment candidate bounds for &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) { if !snap_data.ignore_bounds(layer) { self.add_bounds(layer, snap_data, bbox_to_snap, max_extent); } } + // Sort and merge intersecting rectangles self.right.sort_unstable_by(|a, b| a.center().x.total_cmp(&b.center().x)); self.left.sort_unstable_by(|a, b| b.center().x.total_cmp(&a.center().x)); self.down.sort_unstable_by(|a, b| a.center().y.total_cmp(&b.center().y)); @@ -184,20 +187,18 @@ impl DistributionSnapper { fn snap_bbox_points(&self, tolerance: f64, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, bounds: Rect) { let mut consider_x = true; let mut consider_y = true; + if let SnapConstraint::Line { direction, .. } = constraint { let direction = direction.normalize_or_zero(); - if direction.x == 0. { - consider_x = false; - } else if direction.y == 0. { - consider_y = false; - } + consider_x = direction.x != 0.; + consider_y = direction.y != 0.; } let mut snap_x: Option = None; let mut snap_y: Option = None; - self.x(consider_x, bounds, tolerance, &mut snap_x, point); - self.y(consider_y, bounds, tolerance, &mut snap_y, point); + self.horizontal_snap(consider_x, bounds, tolerance, &mut snap_x, point); + self.vertical_snap(consider_y, bounds, tolerance, &mut snap_y, point); match (snap_x, snap_y) { (Some(x), Some(y)) => { @@ -209,8 +210,8 @@ impl DistributionSnapper { final_point.snapped_point_document += y.snapped_point_document - point.document_point; final_point.source_bounds = Some(final_bounds.into()); final_point.target = SnapTarget::DistributeEvenly(DistributionSnapTarget::XY); - final_point.distribution_boxes_y = y.distribution_boxes_y; - final_point.distribution_equal_distance_y = y.distribution_equal_distance_y; + final_point.distribution_boxes_vertical = y.distribution_boxes_vertical; + final_point.distribution_equal_distance_vertical = y.distribution_equal_distance_vertical; final_point.distance = (final_point.distance * final_point.distance + y.distance * y.distance).sqrt(); snap_results.points.push(final_point); } @@ -220,42 +221,55 @@ impl DistributionSnapper { } } - fn x(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option, point: &SnapCandidatePoint) { - // Right - if consider_x && !self.right.is_empty() { + fn horizontal_snap(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option, point: &SnapCandidatePoint) { + if !consider_x { + return; + } + + // Try right distribution first + if !self.right.is_empty() { let (equal_dist, mut vec_right) = Self::top_level_matches(bounds, &self.right, tolerance, dist_right); if let Some(distances) = equal_dist { let translation = DVec2::X * (distances.first - distances.equal); vec_right.push_front(bounds.translate(translation)); + // Find matching left distribution for &left in Self::exact_further_matches(bounds.translate(translation), &self.left, dist_left, distances.equal, 2).iter().skip(1) { vec_right.push_front(left); } - *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Right, vec_right, distances, bounds, translation, tolerance)) + // Adjust bounds to maintain alignment + if vec_right.len() > 1 { + vec_right[0][0].y = vec_right[0][0].y.min(vec_right[1][1].y); + vec_right[0][1].y = vec_right[0][1].y.min(vec_right[1][1].y); + } + + *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Right, vec_right, distances, bounds, translation, tolerance)); + return; } } - // Left - if consider_x && !self.left.is_empty() && snap_x.is_none() { + // Try left distribution if right didn't work + if !self.left.is_empty() { let (equal_dist, mut vec_left) = Self::top_level_matches(bounds, &self.left, tolerance, dist_left); if let Some(distances) = equal_dist { let translation = -DVec2::X * (distances.first - distances.equal); vec_left.make_contiguous().reverse(); vec_left.push_back(bounds.translate(translation)); + // Find matching right distribution for &right in Self::exact_further_matches(bounds.translate(translation), &self.right, dist_right, distances.equal, 2).iter().skip(1) { vec_left.push_back(right); } - *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Left, vec_left, distances, bounds, translation, tolerance)) + *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Left, vec_left, distances, bounds, translation, tolerance)); + return; } } - // Center X - if consider_x && !self.left.is_empty() && !self.right.is_empty() && snap_x.is_none() { + // Try center distribution if both sides exist + if !self.left.is_empty() && !self.right.is_empty() { let target_x = (self.right[0].min() + self.left[0].max()).x / 2.; - let offset = target_x - bounds.center().x; if offset.abs() < tolerance { @@ -263,67 +277,93 @@ impl DistributionSnapper { let equal = bounds.translate(translation).min().x - self.left[0].max().x; let first = equal + offset; let distances = DistributionMatch { first, equal }; - let boxes = VecDeque::from([self.left[0], bounds.translate(translation), self.right[0]]); - *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::X, boxes, distances, bounds, translation, tolerance)) + + let mut boxes = VecDeque::from([self.left[0], bounds.translate(translation), self.right[0]]); + + // Adjust bounds to maintain alignment + if boxes.len() > 1 { + boxes[1][0].y = boxes[1][0].y.min(boxes[0][1].y); + boxes[1][1].y = boxes[1][1].y.min(boxes[0][1].y); + } + + *snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::X, boxes, distances, bounds, translation, tolerance)); } } } - fn y(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option, point: &SnapCandidatePoint) { - // Down - if consider_y && !self.down.is_empty() { + fn vertical_snap(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option, point: &SnapCandidatePoint) { + if !consider_y { + return; + } + + // Try down distribution first + if !self.down.is_empty() { let (equal_dist, mut vec_down) = Self::top_level_matches(bounds, &self.down, tolerance, dist_down); if let Some(distances) = equal_dist { let translation = DVec2::Y * (distances.first - distances.equal); vec_down.push_front(bounds.translate(translation)); + // Find matching up distribution for &up in Self::exact_further_matches(bounds.translate(translation), &self.up, dist_up, distances.equal, 2).iter().skip(1) { vec_down.push_front(up); } - *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Down, vec_down, distances, bounds, translation, tolerance)) + // Adjust bounds to maintain alignment + if vec_down.len() > 1 { + vec_down[0][0].x = vec_down[0][0].x.min(vec_down[1][1].x); + vec_down[0][1].x = vec_down[0][1].x.min(vec_down[1][1].x); + } + + *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Down, vec_down, distances, bounds, translation, tolerance)); + return; } } - // Up - if consider_y && !self.up.is_empty() && snap_y.is_none() { + // Try up distribution if down didn't work + if !self.up.is_empty() { let (equal_dist, mut vec_up) = Self::top_level_matches(bounds, &self.up, tolerance, dist_up); if let Some(distances) = equal_dist { let translation = -DVec2::Y * (distances.first - distances.equal); vec_up.make_contiguous().reverse(); vec_up.push_back(bounds.translate(translation)); + // Find matching down distribution for &down in Self::exact_further_matches(bounds.translate(translation), &self.down, dist_down, distances.equal, 2).iter().skip(1) { vec_up.push_back(down); } - *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Up, vec_up, distances, bounds, translation, tolerance)) + *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Up, vec_up, distances, bounds, translation, tolerance)); + return; } } - // Center Y - if consider_y && !self.up.is_empty() && !self.down.is_empty() && snap_y.is_none() { + // Try center distribution if both sides exist + if !self.up.is_empty() && !self.down.is_empty() { let target_y = (self.down[0].min() + self.up[0].max()).y / 2.; - let offset = target_y - bounds.center().y; if offset.abs() < tolerance { let translation = DVec2::Y * offset; - let equal = bounds.translate(translation).min().y - self.up[0].max().y; let first = equal + offset; let distances = DistributionMatch { first, equal }; - let boxes = VecDeque::from([self.up[0], bounds.translate(translation), self.down[0]]); + let mut boxes = VecDeque::from([self.up[0], bounds.translate(translation), self.down[0]]); + + // Adjust bounds to maintain alignment + if boxes.len() > 1 { + boxes[1][0].x = boxes[1][0].x.min(boxes[0][1].x); + boxes[1][1].x = boxes[1][1].x.min(boxes[0][1].x); + } - *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance)) + *snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance)); } } } 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::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly { + if !snap_data.document.snapping_state.bounding_box.distribute_evenly { return; } @@ -333,7 +373,7 @@ impl DistributionSnapper { 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::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly { + if !snap_data.document.snapping_state.bounding_box.distribute_evenly { return; } @@ -415,10 +455,14 @@ fn dist_snap_point_right() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_x[0], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.)); + let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.); + expected_box[0].y = expected_box[0].y.min(dist_snapper.left[0][1].y); + expected_box[1].y = expected_box[1].y.min(dist_snapper.left[0][1].y); + + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[0], expected_box); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); } #[test] @@ -428,16 +472,28 @@ fn dist_snap_point_right_left() { left: [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(), ..Default::default() }; + let source = Rect::from_square(DVec2::new(0.5, 0.), 2.); let snap_results = &mut SnapResults::default(); dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); + assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 5); - assert_eq!(snap_results.points[0].distribution_boxes_x[1], Rect::from_square(DVec2::new(-10., 0.), 2.)); - assert_eq!(snap_results.points[0].distribution_boxes_x[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 5); + + let mut expected_left1 = dist_snapper.left[1]; + let mut expected_center = Rect::from_square(DVec2::new(0., 0.), 2.); + + expected_center[0].y = expected_center[0].y.min(dist_snapper.left[1][1].y).min(dist_snapper.right[0][1].y); + expected_center[1].y = expected_center[1].y.min(dist_snapper.left[1][1].y).min(dist_snapper.right[0][1].y); + + expected_left1[0].y = expected_left1[0].y.min(dist_snapper.left[0][1].y).min(expected_center[1].y); + expected_left1[1].y = expected_left1[1].y.min(dist_snapper.left[0][1].y).min(expected_center[1].y); + + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[1], expected_left1); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], expected_center); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); } #[test] @@ -451,10 +507,10 @@ fn dist_snap_point_left() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_x[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], Rect::from_square(DVec2::new(0., 0.), 2.)); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); } #[test] @@ -469,10 +525,10 @@ fn dist_snap_point_left_right() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 4); - assert_eq!(snap_results.points[0].distribution_boxes_x[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 4); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], Rect::from_square(DVec2::new(0., 0.), 2.)); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); } #[test] @@ -487,10 +543,15 @@ fn dist_snap_point_center_x() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_x[1], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.)); + + let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.); + expected_box[0].y = expected_box[0].y.min(dist_snapper.left[0][1].y); + expected_box[1].y = expected_box[1].y.min(dist_snapper.left[0][1].y); + + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal[1], expected_box); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); } // ---------------------------------- @@ -507,10 +568,15 @@ fn dist_snap_point_down() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_y[0], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + + let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.); + expected_box[0].x = expected_box[0].x.min(dist_snapper.down[0][1].x); + expected_box[1].x = expected_box[1].x.min(dist_snapper.down[0][1].x); + + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_vertical[0], expected_box); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } #[test] @@ -523,13 +589,23 @@ fn dist_snap_point_down_up() { let source = Rect::from_square(DVec2::new(0., 0.5), 2.); let snap_results = &mut SnapResults::default(); dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); + assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 5); - assert_eq!(snap_results.points[0].distribution_boxes_y[1], Rect::from_square(DVec2::new(0., -10.), 2.)); - assert_eq!(snap_results.points[0].distribution_boxes_y[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 5); + + let mut expected_center = Rect::from_square(DVec2::new(0., 0.), 2.); + expected_center[0].x = expected_center[0].x.min(dist_snapper.up[1][1].x).min(dist_snapper.down[0][1].x); + expected_center[1].x = expected_center[1].x.min(dist_snapper.up[1][1].x).min(dist_snapper.down[0][1].x); + + let mut expected_up = Rect::from_square(DVec2::new(0., -10.), 2.); + expected_up[0].x = expected_up[0].x.min(dist_snapper.up[0][1].x).min(expected_center[0].x); + expected_up[1].x = expected_up[1].x.min(dist_snapper.up[0][1].x).min(expected_center[1].x); + + assert_eq!(snap_results.points[0].distribution_boxes_vertical[1], expected_up); + assert_eq!(snap_results.points[0].distribution_boxes_vertical[2], expected_center); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } #[test] @@ -543,10 +619,10 @@ fn dist_snap_point_up() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_y[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_vertical[2], Rect::from_square(DVec2::new(0., 0.), 2.)); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } #[test] @@ -561,10 +637,10 @@ fn dist_snap_point_up_down() { dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 4); - assert_eq!(snap_results.points[0].distribution_boxes_y[2], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 4); + assert_eq!(snap_results.points[0].distribution_boxes_vertical[2], Rect::from_square(DVec2::new(0., 0.), 2.)); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } #[test] @@ -577,12 +653,18 @@ fn dist_snap_point_center_y() { let source = Rect::from_square(DVec2::new(0., 0.5), 2.); let snap_results = &mut SnapResults::default(); dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); + assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_y[1], Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3); + + let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.); + expected_box[0].x = expected_box[0].x.min(dist_snapper.up[0][1].x).min(dist_snapper.down[0][1].x); + expected_box[1].x = expected_box[1].x.min(dist_snapper.up[0][1].x).min(dist_snapper.down[0][1].x); + + assert_eq!(snap_results.points[0].distribution_boxes_vertical[1], expected_box); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } #[test] @@ -592,17 +674,25 @@ fn dist_snap_point_center_xy() { down: [10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(), left: [-12., -15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(), right: [12., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(), + ..Default::default() }; let source = Rect::from_square(DVec2::new(0.3, 0.4), 2.); let snap_results = &mut SnapResults::default(); dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source); + assert_eq!(snap_results.points.len(), 1); assert_eq!(snap_results.points[0].distance, 0.5000000000000001); - assert_eq!(snap_results.points[0].distribution_equal_distance_x, Some(8.)); - assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.)); - assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3); - assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3); + assert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(8.)); + assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.)); + assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3); + assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3); + + assert!(snap_results.points[0].distribution_boxes_horizontal[1][0].y <= dist_snapper.left[0][1].y); + assert!(snap_results.points[0].distribution_boxes_horizontal[1][1].y <= dist_snapper.left[0][1].y); + assert!(snap_results.points[0].distribution_boxes_vertical[1][0].x <= dist_snapper.up[0][1].x); + assert!(snap_results.points[0].distribution_boxes_vertical[1][1].x <= dist_snapper.up[0][1].x); + assert_eq!(Rect::from_box(snap_results.points[0].source_bounds.unwrap().bounding_box()), Rect::from_square(DVec2::new(0., 0.), 2.)); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0); - assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0); + assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1); } diff --git a/editor/src/messages/tool/common_functionality/snapping/snap_results.rs b/editor/src/messages/tool/common_functionality/snapping/snap_results.rs index 16d6a65ce5..a115ccf2e1 100644 --- a/editor/src/messages/tool/common_functionality/snapping/snap_results.rs +++ b/editor/src/messages/tool/common_functionality/snapping/snap_results.rs @@ -29,17 +29,17 @@ pub struct SnappedPoint { pub outline_layers: [Option; 2], pub distance: f64, pub tolerance: f64, - pub distribution_boxes_x: VecDeque, - pub distribution_equal_distance_x: Option, - pub distribution_boxes_y: VecDeque, - pub distribution_equal_distance_y: Option, + pub distribution_boxes_horizontal: VecDeque, + pub distribution_equal_distance_horizontal: Option, + pub distribution_boxes_vertical: VecDeque, + pub distribution_equal_distance_vertical: Option, pub distance_to_align_target: f64, // If aligning so that the top is aligned but the X pos is 200 from the target, this is 200. - pub alignment_target_x: Option, - pub alignment_target_y: Option, + pub alignment_target_horizontal: Option, + pub alignment_target_vertical: Option, } impl SnappedPoint { pub fn align(&self) -> bool { - self.alignment_target_x.is_some() || self.alignment_target_y.is_some() + self.alignment_target_horizontal.is_some() || self.alignment_target_vertical.is_some() } pub fn infinite_snap(snapped_point_document: DVec2) -> Self { Self { @@ -63,10 +63,10 @@ impl SnappedPoint { snapped_point_document: point.document_point + translation, source: point.source, target: SnapTarget::DistributeEvenly(target), - distribution_boxes_x, - distribution_equal_distance_x: is_x.then_some(distances.equal), - distribution_boxes_y, - distribution_equal_distance_y: (!is_x).then_some(distances.equal), + distribution_boxes_horizontal: distribution_boxes_x, + distribution_equal_distance_horizontal: is_x.then_some(distances.equal), + distribution_boxes_vertical: distribution_boxes_y, + distribution_equal_distance_vertical: (!is_x).then_some(distances.equal), distance: (distances.first - distances.equal).abs(), constrained: true, source_bounds: Some(bounds.translate(translation).into()), diff --git a/node-graph/gcore/src/graphic_element/renderer/rect.rs b/node-graph/gcore/src/graphic_element/renderer/rect.rs index a2e9bcf5c6..ace03dbfb9 100644 --- a/node-graph/gcore/src/graphic_element/renderer/rect.rs +++ b/node-graph/gcore/src/graphic_element/renderer/rect.rs @@ -67,7 +67,7 @@ impl Rect { Self::from_box([self[0] - delta, self[1] + delta]) } - /// Expand a rect by a certain amount on top/bottom and on left/right + /// Checks if two rects intersect #[must_use] pub fn intersects(&self, other: Self) -> bool { let [mina, maxa] = [self[0].min(self[1]), self[0].max(self[1])];