mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 06:18:12 +08:00
Improve the distribution part of the snapping system (#2626)
* fix distribution snapper visualization going far from layers * fix bugs * Remove traces * remove comment * Fix tests * Rename distribution snapper variables from x, y to horizontal, vertical * Fix tests * Fix bug where center point and corner point have to be enabled for distribution_snapper to work * Cleanup --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
ddb2d744d4
commit
66a297df2c
@@ -404,8 +404,7 @@ pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 5] = [
|
|||||||
(
|
(
|
||||||
"Distribute Evenly",
|
"Distribute Evenly",
|
||||||
(|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState,
|
(|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",
|
||||||
"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)",
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
|
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
|
||||||
|
|||||||
@@ -114,13 +114,13 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
|
|||||||
(Some(result), None) | (None, Some(result)) => Some(result),
|
(Some(result), None) | (None, Some(result)) => Some(result),
|
||||||
(Some(mut result), Some(align)) => {
|
(Some(mut result), Some(align)) => {
|
||||||
let SnapTarget::DistributeEvenly(distribution) = result.target else { return Some(result) };
|
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.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.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)
|
Some(result)
|
||||||
@@ -456,10 +456,10 @@ impl SnapManager {
|
|||||||
}
|
}
|
||||||
let viewport = to_viewport.transform_point2(ind.snapped_point_document);
|
let viewport = to_viewport.transform_point2(ind.snapped_point_document);
|
||||||
|
|
||||||
Self::alignment_x_overlay(&ind.distribution_boxes_x, to_viewport, overlay_context);
|
Self::alignment_x_overlay(&ind.distribution_boxes_horizontal, to_viewport, overlay_context);
|
||||||
Self::alignment_y_overlay(&ind.distribution_boxes_y, 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();
|
let any_align = align.iter().flatten().next().is_some();
|
||||||
for &target in align.iter().flatten() {
|
for &target in align.iter().flatten() {
|
||||||
overlay_context.line(viewport, target, None, None);
|
overlay_context.line(viewport, target, None, None);
|
||||||
@@ -471,7 +471,7 @@ impl SnapManager {
|
|||||||
overlay_context.manipulator_handle(viewport, false, None);
|
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 text = format!("[{}] from [{}]", ind.target, ind.source);
|
||||||
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));
|
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]);
|
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_LABEL_BACKGROUND), transform, 4., [Pivot::Start, Pivot::End]);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ impl AlignmentSnapper {
|
|||||||
distance_to_align_target,
|
distance_to_align_target,
|
||||||
fully_constrained: false,
|
fully_constrained: false,
|
||||||
at_intersection: true,
|
at_intersection: true,
|
||||||
alignment_target_x: Some(endpoint),
|
alignment_target_horizontal: Some(endpoint),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
snap_results.points.push(snap_point);
|
snap_results.points.push(snap_point);
|
||||||
@@ -129,7 +129,7 @@ impl AlignmentSnapper {
|
|||||||
distance: distance_to_snapped,
|
distance: distance_to_snapped,
|
||||||
tolerance,
|
tolerance,
|
||||||
distance_to_align_target,
|
distance_to_align_target,
|
||||||
alignment_target_x: Some(target_position),
|
alignment_target_horizontal: Some(target_position),
|
||||||
fully_constrained: true,
|
fully_constrained: true,
|
||||||
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
|
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -148,7 +148,7 @@ impl AlignmentSnapper {
|
|||||||
distance: distance_to_snapped,
|
distance: distance_to_snapped,
|
||||||
tolerance,
|
tolerance,
|
||||||
distance_to_align_target,
|
distance_to_align_target,
|
||||||
alignment_target_y: Some(target_position),
|
alignment_target_vertical: Some(target_position),
|
||||||
fully_constrained: true,
|
fully_constrained: true,
|
||||||
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
|
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -174,8 +174,8 @@ impl AlignmentSnapper {
|
|||||||
target_bounds: snap_x.target_bounds,
|
target_bounds: snap_x.target_bounds,
|
||||||
distance,
|
distance,
|
||||||
tolerance,
|
tolerance,
|
||||||
alignment_target_x: snap_x.alignment_target_x,
|
alignment_target_horizontal: snap_x.alignment_target_horizontal,
|
||||||
alignment_target_y: snap_y.alignment_target_y,
|
alignment_target_vertical: snap_y.alignment_target_vertical,
|
||||||
constrained: true,
|
constrained: true,
|
||||||
at_intersection: true,
|
at_intersection: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::*;
|
use crate::messages::portfolio::document::utility_types::misc::*;
|
||||||
use crate::messages::prelude::*;
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct DistributionSnapper {
|
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 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();
|
let max_extent = (screen_bounds[1] - screen_bounds[0]).abs().max_element();
|
||||||
|
|
||||||
|
// Collect artboard bounds
|
||||||
for layer in document.metadata().all_layers() {
|
for layer in document.metadata().all_layers() {
|
||||||
if document.network_interface.is_artboard(&layer.to_node(), &[]) && !snap_data.ignore.contains(&layer) {
|
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);
|
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()) {
|
for &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) {
|
||||||
if !snap_data.ignore_bounds(layer) {
|
if !snap_data.ignore_bounds(layer) {
|
||||||
self.add_bounds(layer, snap_data, bbox_to_snap, max_extent);
|
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.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.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));
|
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) {
|
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_x = true;
|
||||||
let mut consider_y = true;
|
let mut consider_y = true;
|
||||||
|
|
||||||
if let SnapConstraint::Line { direction, .. } = constraint {
|
if let SnapConstraint::Line { direction, .. } = constraint {
|
||||||
let direction = direction.normalize_or_zero();
|
let direction = direction.normalize_or_zero();
|
||||||
if direction.x == 0. {
|
consider_x = direction.x != 0.;
|
||||||
consider_x = false;
|
consider_y = direction.y != 0.;
|
||||||
} else if direction.y == 0. {
|
|
||||||
consider_y = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut snap_x: Option<SnappedPoint> = None;
|
let mut snap_x: Option<SnappedPoint> = None;
|
||||||
let mut snap_y: Option<SnappedPoint> = None;
|
let mut snap_y: Option<SnappedPoint> = None;
|
||||||
|
|
||||||
self.x(consider_x, bounds, tolerance, &mut snap_x, point);
|
self.horizontal_snap(consider_x, bounds, tolerance, &mut snap_x, point);
|
||||||
self.y(consider_y, bounds, tolerance, &mut snap_y, point);
|
self.vertical_snap(consider_y, bounds, tolerance, &mut snap_y, point);
|
||||||
|
|
||||||
match (snap_x, snap_y) {
|
match (snap_x, snap_y) {
|
||||||
(Some(x), Some(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.snapped_point_document += y.snapped_point_document - point.document_point;
|
||||||
final_point.source_bounds = Some(final_bounds.into());
|
final_point.source_bounds = Some(final_bounds.into());
|
||||||
final_point.target = SnapTarget::DistributeEvenly(DistributionSnapTarget::XY);
|
final_point.target = SnapTarget::DistributeEvenly(DistributionSnapTarget::XY);
|
||||||
final_point.distribution_boxes_y = y.distribution_boxes_y;
|
final_point.distribution_boxes_vertical = y.distribution_boxes_vertical;
|
||||||
final_point.distribution_equal_distance_y = y.distribution_equal_distance_y;
|
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();
|
final_point.distance = (final_point.distance * final_point.distance + y.distance * y.distance).sqrt();
|
||||||
snap_results.points.push(final_point);
|
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<SnappedPoint>, point: &SnapCandidatePoint) {
|
fn horizontal_snap(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
|
||||||
// Right
|
if !consider_x {
|
||||||
if consider_x && !self.right.is_empty() {
|
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);
|
let (equal_dist, mut vec_right) = Self::top_level_matches(bounds, &self.right, tolerance, dist_right);
|
||||||
if let Some(distances) = equal_dist {
|
if let Some(distances) = equal_dist {
|
||||||
let translation = DVec2::X * (distances.first - distances.equal);
|
let translation = DVec2::X * (distances.first - distances.equal);
|
||||||
vec_right.push_front(bounds.translate(translation));
|
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) {
|
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);
|
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
|
// Try left distribution if right didn't work
|
||||||
if consider_x && !self.left.is_empty() && snap_x.is_none() {
|
if !self.left.is_empty() {
|
||||||
let (equal_dist, mut vec_left) = Self::top_level_matches(bounds, &self.left, tolerance, dist_left);
|
let (equal_dist, mut vec_left) = Self::top_level_matches(bounds, &self.left, tolerance, dist_left);
|
||||||
if let Some(distances) = equal_dist {
|
if let Some(distances) = equal_dist {
|
||||||
let translation = -DVec2::X * (distances.first - distances.equal);
|
let translation = -DVec2::X * (distances.first - distances.equal);
|
||||||
vec_left.make_contiguous().reverse();
|
vec_left.make_contiguous().reverse();
|
||||||
vec_left.push_back(bounds.translate(translation));
|
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) {
|
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);
|
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
|
// Try center distribution if both sides exist
|
||||||
if consider_x && !self.left.is_empty() && !self.right.is_empty() && snap_x.is_none() {
|
if !self.left.is_empty() && !self.right.is_empty() {
|
||||||
let target_x = (self.right[0].min() + self.left[0].max()).x / 2.;
|
let target_x = (self.right[0].min() + self.left[0].max()).x / 2.;
|
||||||
|
|
||||||
let offset = target_x - bounds.center().x;
|
let offset = target_x - bounds.center().x;
|
||||||
|
|
||||||
if offset.abs() < tolerance {
|
if offset.abs() < tolerance {
|
||||||
@@ -263,67 +277,93 @@ impl DistributionSnapper {
|
|||||||
let equal = bounds.translate(translation).min().x - self.left[0].max().x;
|
let equal = bounds.translate(translation).min().x - self.left[0].max().x;
|
||||||
let first = equal + offset;
|
let first = equal + offset;
|
||||||
let distances = DistributionMatch { first, equal };
|
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<SnappedPoint>, point: &SnapCandidatePoint) {
|
fn vertical_snap(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
|
||||||
// Down
|
if !consider_y {
|
||||||
if consider_y && !self.down.is_empty() {
|
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);
|
let (equal_dist, mut vec_down) = Self::top_level_matches(bounds, &self.down, tolerance, dist_down);
|
||||||
if let Some(distances) = equal_dist {
|
if let Some(distances) = equal_dist {
|
||||||
let translation = DVec2::Y * (distances.first - distances.equal);
|
let translation = DVec2::Y * (distances.first - distances.equal);
|
||||||
vec_down.push_front(bounds.translate(translation));
|
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) {
|
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);
|
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
|
// Try up distribution if down didn't work
|
||||||
if consider_y && !self.up.is_empty() && snap_y.is_none() {
|
if !self.up.is_empty() {
|
||||||
let (equal_dist, mut vec_up) = Self::top_level_matches(bounds, &self.up, tolerance, dist_up);
|
let (equal_dist, mut vec_up) = Self::top_level_matches(bounds, &self.up, tolerance, dist_up);
|
||||||
if let Some(distances) = equal_dist {
|
if let Some(distances) = equal_dist {
|
||||||
let translation = -DVec2::Y * (distances.first - distances.equal);
|
let translation = -DVec2::Y * (distances.first - distances.equal);
|
||||||
vec_up.make_contiguous().reverse();
|
vec_up.make_contiguous().reverse();
|
||||||
vec_up.push_back(bounds.translate(translation));
|
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) {
|
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);
|
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
|
// Try center distribution if both sides exist
|
||||||
if consider_y && !self.up.is_empty() && !self.down.is_empty() && snap_y.is_none() {
|
if !self.up.is_empty() && !self.down.is_empty() {
|
||||||
let target_y = (self.down[0].min() + self.up[0].max()).y / 2.;
|
let target_y = (self.down[0].min() + self.up[0].max()).y / 2.;
|
||||||
|
|
||||||
let offset = target_y - bounds.center().y;
|
let offset = target_y - bounds.center().y;
|
||||||
|
|
||||||
if offset.abs() < tolerance {
|
if offset.abs() < tolerance {
|
||||||
let translation = DVec2::Y * offset;
|
let translation = DVec2::Y * offset;
|
||||||
|
|
||||||
let equal = bounds.translate(translation).min().y - self.up[0].max().y;
|
let equal = bounds.translate(translation).min().y - self.up[0].max().y;
|
||||||
let first = equal + offset;
|
let first = equal + offset;
|
||||||
let distances = DistributionMatch { first, equal };
|
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]]);
|
||||||
|
|
||||||
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance))
|
// 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
|
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 };
|
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;
|
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) {
|
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 };
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,10 +455,14 @@ fn dist_snap_point_right() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_horizontal, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3);
|
let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x[0], Rect::from_square(DVec2::new(0., 0.), 2.));
|
expected_box[0].y = expected_box[0].y.min(dist_snapper.left[0][1].y);
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0);
|
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]
|
#[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(),
|
left: [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
||||||
let snap_results = &mut SnapResults::default();
|
let snap_results = &mut SnapResults::default();
|
||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_horizontal, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 5);
|
assert_eq!(snap_results.points[0].distribution_boxes_horizontal.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.));
|
let mut expected_left1 = dist_snapper.left[1];
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0);
|
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]
|
#[test]
|
||||||
@@ -451,10 +507,10 @@ fn dist_snap_point_left() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_horizontal, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3);
|
assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x[2], Rect::from_square(DVec2::new(0., 0.), 2.));
|
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_x, 0);
|
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -469,10 +525,10 @@ fn dist_snap_point_left_right() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_horizontal, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 4);
|
assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 4);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x[2], Rect::from_square(DVec2::new(0., 0.), 2.));
|
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_x, 0);
|
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -487,10 +543,15 @@ fn dist_snap_point_center_x() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_horizontal, 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.));
|
let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_x, 0);
|
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);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_vertical, 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.));
|
let mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1);
|
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]
|
#[test]
|
||||||
@@ -523,13 +589,23 @@ fn dist_snap_point_down_up() {
|
|||||||
let source = Rect::from_square(DVec2::new(0., 0.5), 2.);
|
let source = Rect::from_square(DVec2::new(0., 0.5), 2.);
|
||||||
let snap_results = &mut SnapResults::default();
|
let snap_results = &mut SnapResults::default();
|
||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_vertical, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 5);
|
assert_eq!(snap_results.points[0].distribution_boxes_vertical.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.));
|
let mut expected_center = Rect::from_square(DVec2::new(0., 0.), 2.);
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1);
|
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]
|
#[test]
|
||||||
@@ -543,10 +619,10 @@ fn dist_snap_point_up() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_vertical, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3);
|
assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y[2], Rect::from_square(DVec2::new(0., 0.), 2.));
|
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_y, 1);
|
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -561,10 +637,10 @@ fn dist_snap_point_up_down() {
|
|||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_vertical, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 4);
|
assert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 4);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y[2], Rect::from_square(DVec2::new(0., 0.), 2.));
|
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_y, 1);
|
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -577,12 +653,18 @@ fn dist_snap_point_center_y() {
|
|||||||
let source = Rect::from_square(DVec2::new(0., 0.5), 2.);
|
let source = Rect::from_square(DVec2::new(0., 0.5), 2.);
|
||||||
let snap_results = &mut SnapResults::default();
|
let snap_results = &mut SnapResults::default();
|
||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5);
|
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_equal_distance_vertical, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y.len(), 3);
|
assert_eq!(snap_results.points[0].distribution_boxes_vertical.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);
|
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]
|
#[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(),
|
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(),
|
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(),
|
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 source = Rect::from_square(DVec2::new(0.3, 0.4), 2.);
|
||||||
let snap_results = &mut SnapResults::default();
|
let snap_results = &mut SnapResults::default();
|
||||||
dist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);
|
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.len(), 1);
|
||||||
assert_eq!(snap_results.points[0].distance, 0.5000000000000001);
|
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_horizontal, Some(8.));
|
||||||
assert_eq!(snap_results.points[0].distribution_equal_distance_y, Some(6.));
|
assert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_x.len(), 3);
|
assert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);
|
||||||
assert_eq!(snap_results.points[0].distribution_boxes_y.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_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_horizontal, 0);
|
||||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1);
|
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ pub struct SnappedPoint {
|
|||||||
pub outline_layers: [Option<LayerNodeIdentifier>; 2],
|
pub outline_layers: [Option<LayerNodeIdentifier>; 2],
|
||||||
pub distance: f64,
|
pub distance: f64,
|
||||||
pub tolerance: f64,
|
pub tolerance: f64,
|
||||||
pub distribution_boxes_x: VecDeque<Rect>,
|
pub distribution_boxes_horizontal: VecDeque<Rect>,
|
||||||
pub distribution_equal_distance_x: Option<f64>,
|
pub distribution_equal_distance_horizontal: Option<f64>,
|
||||||
pub distribution_boxes_y: VecDeque<Rect>,
|
pub distribution_boxes_vertical: VecDeque<Rect>,
|
||||||
pub distribution_equal_distance_y: Option<f64>,
|
pub distribution_equal_distance_vertical: Option<f64>,
|
||||||
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 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<DVec2>,
|
pub alignment_target_horizontal: Option<DVec2>,
|
||||||
pub alignment_target_y: Option<DVec2>,
|
pub alignment_target_vertical: Option<DVec2>,
|
||||||
}
|
}
|
||||||
impl SnappedPoint {
|
impl SnappedPoint {
|
||||||
pub fn align(&self) -> bool {
|
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 {
|
pub fn infinite_snap(snapped_point_document: DVec2) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -58,15 +58,15 @@ impl SnappedPoint {
|
|||||||
pub fn distribute(point: &SnapCandidatePoint, target: DistributionSnapTarget, boxes: VecDeque<Rect>, distances: DistributionMatch, bounds: Rect, translation: DVec2, tolerance: f64) -> Self {
|
pub fn distribute(point: &SnapCandidatePoint, target: DistributionSnapTarget, boxes: VecDeque<Rect>, distances: DistributionMatch, bounds: Rect, translation: DVec2, tolerance: f64) -> Self {
|
||||||
let is_x = target.is_x();
|
let is_x = target.is_x();
|
||||||
|
|
||||||
let [distribution_boxes_x, distribution_boxes_y] = if is_x { [boxes, Default::default()] } else { [Default::default(), boxes] };
|
let [distribution_boxes_horizontal, distribution_boxes_vertical] = if is_x { [boxes, Default::default()] } else { [Default::default(), boxes] };
|
||||||
Self {
|
Self {
|
||||||
snapped_point_document: point.document_point + translation,
|
snapped_point_document: point.document_point + translation,
|
||||||
source: point.source,
|
source: point.source,
|
||||||
target: SnapTarget::DistributeEvenly(target),
|
target: SnapTarget::DistributeEvenly(target),
|
||||||
distribution_boxes_x,
|
distribution_boxes_horizontal,
|
||||||
distribution_equal_distance_x: is_x.then_some(distances.equal),
|
distribution_equal_distance_horizontal: is_x.then_some(distances.equal),
|
||||||
distribution_boxes_y,
|
distribution_boxes_vertical,
|
||||||
distribution_equal_distance_y: (!is_x).then_some(distances.equal),
|
distribution_equal_distance_vertical: (!is_x).then_some(distances.equal),
|
||||||
distance: (distances.first - distances.equal).abs(),
|
distance: (distances.first - distances.equal).abs(),
|
||||||
constrained: true,
|
constrained: true,
|
||||||
source_bounds: Some(bounds.translate(translation).into()),
|
source_bounds: Some(bounds.translate(translation).into()),
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl Rect {
|
|||||||
Self::from_box([self[0] - delta, self[1] + delta])
|
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]
|
#[must_use]
|
||||||
pub fn intersects(&self, other: Self) -> bool {
|
pub fn intersects(&self, other: Self) -> bool {
|
||||||
let [mina, maxa] = [self[0].min(self[1]), self[0].max(self[1])];
|
let [mina, maxa] = [self[0].min(self[1]), self[0].max(self[1])];
|
||||||
|
|||||||
Reference in New Issue
Block a user