mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 10:18:12 +08:00
Add snapping targets for b-box edges and multi-layer spacing distribution (#1793)
* Initial work on aligning bounding boxes * Work in progress distribution * Distribution snapping * Distribution overlays * Align points and clean up * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
0dbbabe73e
commit
cdd179cf10
@@ -0,0 +1,172 @@
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::utility_types::misc::*;
|
||||
|
||||
use graphene_core::renderer::Quad;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AlignmentSnapper {
|
||||
bounding_box_points: Vec<SnapCandidatePoint>,
|
||||
}
|
||||
|
||||
impl AlignmentSnapper {
|
||||
pub fn collect_bounding_box_points(&mut self, snap_data: &mut SnapData, first_point: bool) {
|
||||
if !first_point {
|
||||
return;
|
||||
}
|
||||
|
||||
let document = snap_data.document;
|
||||
|
||||
self.bounding_box_points.clear();
|
||||
if !document.snapping_state.bounds.align {
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in document.metadata().all_layers() {
|
||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::Corner)) {
|
||||
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
get_bbox_points(Quad::from_box(bounds), &mut self.bounding_box_points, BBoxSnapValues::ALIGN_ARTBOARD, document);
|
||||
}
|
||||
}
|
||||
for &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) {
|
||||
if snap_data.ignore_bounds(layer) {
|
||||
continue;
|
||||
}
|
||||
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);
|
||||
let values = BBoxSnapValues::ALIGN_BOUNDING_BOX;
|
||||
get_bbox_points(quad, &mut self.bounding_box_points, values, document);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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 {
|
||||
&[]
|
||||
};
|
||||
|
||||
// TODO: snap handle points
|
||||
let document = snap_data.document;
|
||||
let tolerance = snap_tolerance(document);
|
||||
|
||||
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.abs() < 1e-5 {
|
||||
consider_y = false;
|
||||
} else if direction.y.abs() < 1e-5 {
|
||||
consider_x = false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut snap_x: Option<SnappedPoint> = None;
|
||||
let mut snap_y: Option<SnappedPoint> = None;
|
||||
|
||||
for target_point in self.bounding_box_points.iter().chain(unselected_geometry) {
|
||||
let target_position = target_point.document_point;
|
||||
|
||||
let point_on_x = DVec2::new(point.document_point.x, target_position.y);
|
||||
let dist_x = (target_position.y - point.document_point.y).abs();
|
||||
|
||||
let point_on_y = DVec2::new(target_position.x, point.document_point.y);
|
||||
let dist_y = (target_position.x - point.document_point.x).abs();
|
||||
|
||||
let target_geometry = matches!(target_point.target, SnapTarget::Geometry(_));
|
||||
let updated_target = if target_geometry {
|
||||
SnapTarget::Alignment(AlignmentSnapTarget::Handle)
|
||||
} else {
|
||||
target_point.target
|
||||
};
|
||||
|
||||
if consider_x && dist_x < tolerance && snap_x.as_ref().map_or(true, |point| dist_y < point.distance_to_align_target) {
|
||||
snap_x = Some(SnappedPoint {
|
||||
snapped_point_document: point_on_x,
|
||||
source: point.source, //ToDo map source
|
||||
target: updated_target,
|
||||
target_bounds: target_point.quad,
|
||||
distance: dist_x,
|
||||
tolerance,
|
||||
distance_to_align_target: dist_y,
|
||||
alignment_target_x: Some(target_position),
|
||||
fully_constrained: true,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
if consider_y && dist_y < tolerance && snap_y.as_ref().map_or(true, |point| dist_x < point.distance_to_align_target) {
|
||||
snap_y = Some(SnappedPoint {
|
||||
snapped_point_document: point_on_y,
|
||||
source: point.source, //ToDo map source
|
||||
target: updated_target,
|
||||
target_bounds: target_point.quad,
|
||||
distance: dist_y,
|
||||
tolerance,
|
||||
distance_to_align_target: dist_x,
|
||||
alignment_target_y: Some(target_position),
|
||||
fully_constrained: true,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
match (snap_x, snap_y) {
|
||||
(Some(snap_x), Some(snap_y)) => {
|
||||
let intersection = DVec2::new(snap_y.snapped_point_document.x, snap_x.snapped_point_document.y);
|
||||
let distance = intersection.distance(point.document_point);
|
||||
|
||||
if distance >= tolerance {
|
||||
snap_results.points.push(if snap_x.distance < snap_y.distance { snap_x } else { snap_y });
|
||||
return;
|
||||
}
|
||||
|
||||
snap_results.points.push(SnappedPoint {
|
||||
snapped_point_document: intersection,
|
||||
source: point.source, // TODO: map source
|
||||
target: SnapTarget::Alignment(AlignmentSnapTarget::Intersection),
|
||||
target_bounds: snap_x.target_bounds,
|
||||
distance,
|
||||
tolerance,
|
||||
alignment_target_x: snap_x.alignment_target_x,
|
||||
alignment_target_y: snap_y.alignment_target_y,
|
||||
constrained: true,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
(Some(snap_x), None) => snap_results.points.push(snap_x),
|
||||
(None, Some(snap_y)) => snap_results.points.push(snap_y),
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
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 graphene_core::renderer::Quad;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DistributionSnapper {
|
||||
right: Vec<Rect>,
|
||||
left: Vec<Rect>,
|
||||
down: Vec<Rect>,
|
||||
up: Vec<Rect>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct DistributionMatch {
|
||||
pub equal: f64,
|
||||
pub first: f64,
|
||||
}
|
||||
|
||||
fn dist_right(a: Rect, b: Rect) -> f64 {
|
||||
-a.max().x + b.min().x
|
||||
}
|
||||
fn dist_left(a: Rect, b: Rect) -> f64 {
|
||||
a.min().x - b.max().x
|
||||
}
|
||||
fn dist_down(a: Rect, b: Rect) -> f64 {
|
||||
-a.max().y + b.min().y
|
||||
}
|
||||
fn dist_up(a: Rect, b: Rect) -> f64 {
|
||||
a.min().y - b.max().y
|
||||
}
|
||||
|
||||
impl DistributionSnapper {
|
||||
fn add_bounds(&mut self, layer: LayerNodeIdentifier, snap_data: &mut SnapData, bbox_to_snap: Rect, max_extent: f64) {
|
||||
let document = snap_data.document;
|
||||
|
||||
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {
|
||||
return;
|
||||
};
|
||||
let bounds = Rect::from_box(bounds);
|
||||
if bounds.intersects(bbox_to_snap) {
|
||||
return;
|
||||
}
|
||||
|
||||
let difference = bounds.center() - bbox_to_snap.center();
|
||||
|
||||
let x_bounds = bbox_to_snap.expand_by(max_extent, 0.);
|
||||
let y_bounds = bbox_to_snap.expand_by(0., max_extent);
|
||||
|
||||
if x_bounds.intersects(bounds) {
|
||||
if difference.x > 0. {
|
||||
self.right.push(bounds);
|
||||
} else {
|
||||
self.left.push(bounds);
|
||||
}
|
||||
} else if y_bounds.intersects(bounds) {
|
||||
if difference.y > 0. {
|
||||
self.down.push(bounds);
|
||||
} else {
|
||||
self.up.push(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_bounding_box_points(&mut self, snap_data: &mut SnapData, first_point: bool, bbox_to_snap: Rect) {
|
||||
if !first_point {
|
||||
return;
|
||||
}
|
||||
|
||||
let document = snap_data.document;
|
||||
|
||||
self.right.clear();
|
||||
self.left.clear();
|
||||
self.down.clear();
|
||||
self.up.clear();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
self.up.sort_unstable_by(|a, b| b.center().y.total_cmp(&a.center().y));
|
||||
|
||||
Self::merge_intersecting(&mut self.right);
|
||||
Self::merge_intersecting(&mut self.left);
|
||||
Self::merge_intersecting(&mut self.down);
|
||||
Self::merge_intersecting(&mut self.up);
|
||||
}
|
||||
|
||||
fn merge_intersecting(rectangles: &mut Vec<Rect>) {
|
||||
let mut index = 0;
|
||||
while index < rectangles.len() {
|
||||
let insert_index = index;
|
||||
let mut obelisk = rectangles[index];
|
||||
|
||||
while index + 1 < rectangles.len() && rectangles[index].intersects(rectangles[index + 1]) {
|
||||
index += 1;
|
||||
obelisk = Rect::combine_bounds(obelisk, rectangles[index]);
|
||||
}
|
||||
|
||||
if index > insert_index {
|
||||
rectangles.insert(insert_index, obelisk);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn exact_further_matches(source: Rect, rectangles: &[Rect], dist_fn: fn(Rect, Rect) -> f64, first_dist: f64, depth: u8) -> VecDeque<Rect> {
|
||||
if rectangles.is_empty() || depth > 10 {
|
||||
return VecDeque::from([source]);
|
||||
}
|
||||
|
||||
for (index, &rect) in rectangles.iter().enumerate() {
|
||||
let next_dist = dist_fn(source, rect);
|
||||
|
||||
if (first_dist - next_dist).abs() < 5e-5 * depth as f64 {
|
||||
let mut results = Self::exact_further_matches(rect, &rectangles[(index + 1)..], dist_fn, first_dist, depth + 1);
|
||||
results.push_front(source);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
VecDeque::from([source])
|
||||
}
|
||||
|
||||
fn matches_within_tolerance(source: Rect, rectangles: &[Rect], tolerance: f64, dist_fn: fn(Rect, Rect) -> f64, first_dist: f64) -> Option<(f64, VecDeque<Rect>)> {
|
||||
for (index, &rect) in rectangles.iter().enumerate() {
|
||||
let next_dist = dist_fn(source, rect);
|
||||
|
||||
if (first_dist - next_dist).abs() < tolerance {
|
||||
let this_dist = next_dist;
|
||||
let results = Self::exact_further_matches(rect, &rectangles[(index + 1)..], dist_fn, this_dist, 2);
|
||||
return Some((this_dist, results));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn top_level_matches(source: Rect, rectangles: &[Rect], tolerance: f64, dist_fn: fn(Rect, Rect) -> f64) -> (Option<DistributionMatch>, VecDeque<Rect>) {
|
||||
if rectangles.is_empty() {
|
||||
return (None, VecDeque::new());
|
||||
}
|
||||
|
||||
let mut best: Option<(DistributionMatch, Rect, VecDeque<Rect>)> = None;
|
||||
for (index, &rect) in rectangles.iter().enumerate() {
|
||||
let first_dist = dist_fn(source, rect);
|
||||
|
||||
let Some((equal_dist, results)) = Self::matches_within_tolerance(rect, &rectangles[(index + 1)..], tolerance, dist_fn, first_dist) else {
|
||||
continue;
|
||||
};
|
||||
if best.as_ref().is_some_and(|(_, _, best)| best.len() >= results.len()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
best = Some((DistributionMatch { first: first_dist, equal: equal_dist }, rect, results));
|
||||
}
|
||||
|
||||
if let Some((dist, rect, mut results)) = best {
|
||||
results.push_front(rect);
|
||||
(Some(dist), results)
|
||||
} else {
|
||||
(None, VecDeque::from([rectangles[0]]))
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let mut snap_x: Option<SnappedPoint> = None;
|
||||
let mut snap_y: Option<SnappedPoint> = None;
|
||||
|
||||
self.x(consider_x, bounds, tolerance, &mut snap_x, point);
|
||||
self.y(consider_y, bounds, tolerance, &mut snap_y, point);
|
||||
|
||||
match (snap_x, snap_y) {
|
||||
(Some(x), Some(y)) => {
|
||||
let x_bounds = Rect::from_box(x.source_bounds.unwrap_or_default().bounding_box());
|
||||
let y_bounds = Rect::from_box(y.source_bounds.unwrap_or_default().bounding_box());
|
||||
let final_bounds = Rect::from_box([0, 1].map(|index| DVec2::new(x_bounds[index].x, y_bounds[index].y)));
|
||||
|
||||
let mut final_point = x;
|
||||
final_point.snapped_point_document += y.snapped_point_document - point.document_point;
|
||||
final_point.source_bounds = Some(final_bounds.into());
|
||||
final_point.target = SnapTarget::Distribution(DistributionSnapTarget::Xy);
|
||||
final_point.distribution_boxes_y = y.distribution_boxes_y;
|
||||
final_point.distribution_equal_distance_y = y.distribution_equal_distance_y;
|
||||
final_point.distance = (final_point.distance * final_point.distance + y.distance * y.distance).sqrt();
|
||||
snap_results.points.push(final_point);
|
||||
}
|
||||
(Some(x), None) => snap_results.points.push(x),
|
||||
(None, Some(y)) => snap_results.points.push(y),
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn x(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
|
||||
// Right
|
||||
if consider_x && !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));
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Left
|
||||
if consider_x && !self.left.is_empty() && snap_x.is_none() {
|
||||
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));
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Center X
|
||||
if consider_x && !self.left.is_empty() && !self.right.is_empty() && snap_x.is_none() {
|
||||
let target_x = (self.right[0].min() + self.left[0].max()).x / 2.;
|
||||
|
||||
let offset = target_x - bounds.center().x;
|
||||
|
||||
if offset.abs() < tolerance {
|
||||
let translation = DVec2::X * offset;
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn y(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
|
||||
// Down
|
||||
if consider_y && !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));
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Up
|
||||
if consider_y && !self.up.is_empty() && snap_y.is_none() {
|
||||
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));
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// Center Y
|
||||
if consider_y && !self.up.is_empty() && !self.down.is_empty() && snap_y.is_none() {
|
||||
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]]);
|
||||
|
||||
*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, bounds: Option<Rect>) {
|
||||
let Some(bounds) = bounds 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.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<Rect>) {
|
||||
let Some(bounds) = bounds 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.snap_bbox_points(snap_tolerance(snap_data.document), point, snap_results, constraint, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_intersecting_test() {
|
||||
let mut rectangles = vec![Rect::from_square(DVec2::ZERO, 2.), Rect::from_square(DVec2::new(10., 0.), 2.)];
|
||||
DistributionSnapper::merge_intersecting(&mut rectangles);
|
||||
assert_eq!(rectangles.len(), 2);
|
||||
|
||||
let mut rectangles = vec![
|
||||
Rect::from_square(DVec2::ZERO, 2.),
|
||||
Rect::from_square(DVec2::new(1., 0.), 2.),
|
||||
Rect::from_square(DVec2::new(10., 0.), 2.),
|
||||
Rect::from_square(DVec2::new(11., 0.), 2.),
|
||||
];
|
||||
DistributionSnapper::merge_intersecting(&mut rectangles);
|
||||
assert_eq!(rectangles.len(), 6);
|
||||
assert_eq!(rectangles[0], Rect::from_box([DVec2::new(-2., -2.), DVec2::new(3., 2.)]));
|
||||
assert_eq!(rectangles[3], Rect::from_box([DVec2::new(8., -2.), DVec2::new(13., 2.)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_simple_2() {
|
||||
let rectangles = [10., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));
|
||||
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
||||
let (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);
|
||||
assert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));
|
||||
assert_eq!(rectangles.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_simple_3() {
|
||||
let rectangles = [10., 20., 30.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));
|
||||
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
||||
let (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);
|
||||
assert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));
|
||||
assert_eq!(rectangles.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_out_of_tolerance() {
|
||||
let rectangles = [10., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));
|
||||
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
||||
let (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 0.4, dist_right);
|
||||
assert_eq!(offset, None);
|
||||
assert_eq!(rectangles.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_with_nonsense() {
|
||||
let source = Rect::from_square(DVec2::new(0.5, 0.), 2.);
|
||||
let rectangles = [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));
|
||||
let (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);
|
||||
assert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));
|
||||
assert_eq!(rectangles.len(), 2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_boxes_in_order(rectangles: &VecDeque<Rect>, index: usize) {
|
||||
for (&first, &second) in rectangles.iter().zip(rectangles.iter().skip(1)) {
|
||||
assert!(first.max()[index] < second.min()[index], "{first:?} {second:?} {index}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_right() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.right = [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
dist_snapper.left = [-2.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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(), 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_right_left() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.right = [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
dist_snapper.left = [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_left() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.left = [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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(), 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_left_right() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.left = [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
dist_snapper.right = [2., 10., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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(), 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_center_x() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.left = [-10., -15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
dist_snapper.right = [10., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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(), 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);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_down() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.down = [2., 10., 15., 20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.up = [-2.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
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[0], Rect::from_square(DVec2::new(0., 0.), 2.));
|
||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_down_up() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.down = [2., 10., 15., 20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.up = [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_up() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.up = [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
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[2], Rect::from_square(DVec2::new(0., 0.), 2.));
|
||||
assert_boxes_in_order(&snap_results.points[0].distribution_boxes_y, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_up_down() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.up = [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.down = [2., 10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
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(), 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_center_y() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.up = [-10., -15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.down = [10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_snap_point_center_xy() {
|
||||
let mut dist_snapper = DistributionSnapper::default();
|
||||
dist_snapper.up = [-10., -15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.down = [10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec();
|
||||
dist_snapper.left = [-12., -15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
dist_snapper.right = [12., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec();
|
||||
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!(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);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ impl LayerSnapper {
|
||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
|
||||
continue;
|
||||
}
|
||||
self.add_layer_bounds(document, layer, SnapTarget::Board(BoardSnapTarget::Edge));
|
||||
self.add_layer_bounds(document, layer, SnapTarget::Artboard(ArtboardSnapTarget::Edge));
|
||||
}
|
||||
for &layer in snap_data.get_candidates() {
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
@@ -183,7 +183,7 @@ impl LayerSnapper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if document.snapping_state.target_enabled(SnapTarget::Board(BoardSnapTarget::Corner)) {
|
||||
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::Corner)) {
|
||||
let Some(bounds) = document
|
||||
.network_interface
|
||||
.document_metadata()
|
||||
@@ -316,17 +316,19 @@ pub struct SnapCandidatePoint {
|
||||
pub source_index: usize,
|
||||
pub quad: Option<Quad>,
|
||||
pub neighbors: Vec<DVec2>,
|
||||
pub alignment: bool,
|
||||
}
|
||||
impl SnapCandidatePoint {
|
||||
pub fn new(document_point: DVec2, source: SnapSource, target: SnapTarget) -> Self {
|
||||
Self::new_quad(document_point, source, target, None)
|
||||
Self::new_quad(document_point, source, target, None, true)
|
||||
}
|
||||
pub fn new_quad(document_point: DVec2, source: SnapSource, target: SnapTarget, quad: Option<Quad>) -> Self {
|
||||
pub fn new_quad(document_point: DVec2, source: SnapSource, target: SnapTarget, quad: Option<Quad>, alignment: bool) -> Self {
|
||||
Self {
|
||||
document_point,
|
||||
source,
|
||||
target,
|
||||
quad,
|
||||
alignment,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -362,12 +364,30 @@ impl BBoxSnapValues {
|
||||
};
|
||||
|
||||
pub const ARTBOARD: Self = Self {
|
||||
corner_source: SnapSource::Board(BoardSnapSource::Corner),
|
||||
corner_target: SnapTarget::Board(BoardSnapTarget::Corner),
|
||||
corner_source: SnapSource::Artboard(ArtboardSnapSource::Corner),
|
||||
corner_target: SnapTarget::Artboard(ArtboardSnapTarget::Corner),
|
||||
edge_source: SnapSource::None,
|
||||
edge_target: SnapTarget::None,
|
||||
center_source: SnapSource::Board(BoardSnapSource::Center),
|
||||
center_target: SnapTarget::Board(BoardSnapTarget::Center),
|
||||
center_source: SnapSource::Artboard(ArtboardSnapSource::Center),
|
||||
center_target: SnapTarget::Artboard(ArtboardSnapTarget::Center),
|
||||
};
|
||||
|
||||
pub const ALIGN_BOUNDING_BOX: Self = Self {
|
||||
corner_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCorner),
|
||||
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCorner),
|
||||
edge_source: SnapSource::None,
|
||||
edge_target: SnapTarget::None,
|
||||
center_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCenter),
|
||||
center_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCenter),
|
||||
};
|
||||
|
||||
pub const ALIGN_ARTBOARD: Self = Self {
|
||||
corner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCorner),
|
||||
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCorner),
|
||||
edge_source: SnapSource::None,
|
||||
edge_target: SnapTarget::None,
|
||||
center_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenter),
|
||||
center_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenter),
|
||||
};
|
||||
}
|
||||
pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values: BBoxSnapValues, document: &DocumentMessageHandler) {
|
||||
@@ -375,14 +395,14 @@ pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values:
|
||||
let start = quad.0[index];
|
||||
let end = quad.0[(index + 1) % 4];
|
||||
if document.snapping_state.target_enabled(values.corner_target) {
|
||||
points.push(SnapCandidatePoint::new_quad(start, values.corner_source, values.corner_target, Some(quad)));
|
||||
points.push(SnapCandidatePoint::new_quad(start, values.corner_source, values.corner_target, Some(quad), false));
|
||||
}
|
||||
if document.snapping_state.target_enabled(values.edge_target) {
|
||||
points.push(SnapCandidatePoint::new_quad((start + end) / 2., values.edge_source, values.edge_target, Some(quad)));
|
||||
points.push(SnapCandidatePoint::new_quad((start + end) / 2., values.edge_source, values.edge_target, Some(quad), false));
|
||||
}
|
||||
}
|
||||
if document.snapping_state.target_enabled(values.center_target) {
|
||||
points.push(SnapCandidatePoint::new_quad(quad.center(), values.center_source, values.center_target, Some(quad)));
|
||||
points.push(SnapCandidatePoint::new_quad(quad.center(), values.center_source, values.center_target, Some(quad), false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{SnapSource, SnapTarget};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
||||
use bezier_rs::Bezier;
|
||||
use glam::DVec2;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::vector::PointId;
|
||||
use graphene_std::renderer::Rect;
|
||||
|
||||
use super::DistributionMatch;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SnapResults {
|
||||
@@ -18,13 +24,24 @@ pub struct SnappedPoint {
|
||||
pub target: SnapTarget,
|
||||
pub at_intersection: bool,
|
||||
pub constrained: bool, // Found when looking for constrained
|
||||
pub fully_constrained: bool,
|
||||
pub target_bounds: Option<Quad>,
|
||||
pub source_bounds: Option<Quad>,
|
||||
pub curves: [Option<Bezier>; 2],
|
||||
pub distance: f64,
|
||||
pub tolerance: f64,
|
||||
pub distribution_boxes_x: VecDeque<Rect>,
|
||||
pub distribution_equal_distance_x: Option<f64>,
|
||||
pub distribution_boxes_y: VecDeque<Rect>,
|
||||
pub distribution_equal_distance_y: 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 alignment_target_x: Option<DVec2>,
|
||||
pub alignment_target_y: Option<DVec2>,
|
||||
}
|
||||
impl SnappedPoint {
|
||||
pub fn align(&self) -> bool {
|
||||
self.alignment_target_x.is_some() || self.alignment_target_y.is_some()
|
||||
}
|
||||
pub fn infinite_snap(snapped_point_document: DVec2) -> Self {
|
||||
Self {
|
||||
snapped_point_document,
|
||||
@@ -39,6 +56,25 @@ impl SnappedPoint {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
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 [distribution_boxes_x, distribution_boxes_y] = if is_x { [boxes, Default::default()] } else { [Default::default(), boxes] };
|
||||
Self {
|
||||
snapped_point_document: point.document_point + translation,
|
||||
source: point.source,
|
||||
target: SnapTarget::Distribution(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),
|
||||
distance: (distances.first - distances.equal).abs(),
|
||||
constrained: true,
|
||||
source_bounds: Some(bounds.translate(translation).into()),
|
||||
tolerance,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn other_snap_better(&self, other: &Self) -> bool {
|
||||
if self.distance.is_finite() && !other.distance.is_finite() {
|
||||
return false;
|
||||
@@ -60,12 +96,16 @@ impl SnappedPoint {
|
||||
let other_more_constrained = other.constrained && !self.constrained;
|
||||
let self_more_constrained = self.constrained && !other.constrained;
|
||||
|
||||
let both_align = other.align() && self.align();
|
||||
let other_better_align = !other.align() && self.align() || (both_align && !self.source.center() && other.source.center());
|
||||
let self_better_align = !self.align() && other.align() || (both_align && !other.source.center() && self.source.center());
|
||||
|
||||
// Prefer nodes to intersections if both are at the same position
|
||||
let constrained_at_same_pos = other.constrained && self.constrained && self.snapped_point_document.abs_diff_eq(other.snapped_point_document, 1.);
|
||||
let other_better_constraint = constrained_at_same_pos && self.at_intersection && !other.at_intersection;
|
||||
let self_better_constraint = constrained_at_same_pos && other.at_intersection && !self.at_intersection;
|
||||
|
||||
(other_closer || other_more_constrained || other_better_constraint) && !self_more_constrained && !self_better_constraint
|
||||
(other_closer || other_more_constrained || other_better_align || other_better_constraint) && !self_more_constrained && !self_better_align && !self_better_constraint
|
||||
}
|
||||
pub fn is_snapped(&self) -> bool {
|
||||
self.distance.is_finite()
|
||||
|
||||
Reference in New Issue
Block a user