Skip to content

Commit d6a4aad

Browse files
committed
Fix bug where center point and corner point have to be enabled for distribution_snapper to work
1 parent 4e09fd6 commit d6a4aad

File tree

2 files changed

+76
-39
lines changed

2 files changed

+76
-39
lines changed

editor/src/messages/portfolio/document/utility_types/misc.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,7 @@ pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 5] = [
404404
(
405405
"Distribute Evenly",
406406
(|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState,
407-
// TODO: Fix the bug/limitation that requires 'Center Points' and 'Corner Points' to be enabled
408-
"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)",
407+
"Snaps to a consistent distance offset established by the bounding boxes of nearby layers",
409408
),
410409
];
411410
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [

editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs

+75-37
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use super::*;
22
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
33
use crate::messages::portfolio::document::utility_types::misc::*;
4-
use crate::messages::prelude::*;
54
use glam::DVec2;
65
use graphene_core::renderer::Quad;
6+
use std::collections::VecDeque;
77

88
#[derive(Clone, Debug, Default)]
99
pub struct DistributionSnapper {
@@ -79,18 +79,21 @@ impl DistributionSnapper {
7979
let screen_bounds = (document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()])).bounding_box();
8080
let max_extent = (screen_bounds[1] - screen_bounds[0]).abs().max_element();
8181

82+
// Collect artboard bounds
8283
for layer in document.metadata().all_layers() {
8384
if document.network_interface.is_artboard(&layer.to_node(), &[]) && !snap_data.ignore.contains(&layer) {
8485
self.add_bounds(layer, snap_data, bbox_to_snap, max_extent);
8586
}
8687
}
8788

89+
// Collect alignment candidate bounds
8890
for &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) {
8991
if !snap_data.ignore_bounds(layer) {
9092
self.add_bounds(layer, snap_data, bbox_to_snap, max_extent);
9193
}
9294
}
9395

96+
// Sort and merge intersecting rectangles
9497
self.right.sort_unstable_by(|a, b| a.center().x.total_cmp(&b.center().x));
9598
self.left.sort_unstable_by(|a, b| b.center().x.total_cmp(&a.center().x));
9699
self.down.sort_unstable_by(|a, b| a.center().y.total_cmp(&b.center().y));
@@ -184,13 +187,11 @@ impl DistributionSnapper {
184187
fn snap_bbox_points(&self, tolerance: f64, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, bounds: Rect) {
185188
let mut consider_x = true;
186189
let mut consider_y = true;
190+
187191
if let SnapConstraint::Line { direction, .. } = constraint {
188192
let direction = direction.normalize_or_zero();
189-
if direction.x == 0. {
190-
consider_x = false;
191-
} else if direction.y == 0. {
192-
consider_y = false;
193-
}
193+
consider_x = direction.x != 0.;
194+
consider_y = direction.y != 0.;
194195
}
195196

196197
let mut snap_x: Option<SnappedPoint> = None;
@@ -221,40 +222,54 @@ impl DistributionSnapper {
221222
}
222223

223224
fn horizontal_snap(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
224-
// Right
225-
if consider_x && !self.right.is_empty() {
225+
if !consider_x {
226+
return;
227+
}
228+
229+
// Try right distribution first
230+
if !self.right.is_empty() {
226231
let (equal_dist, mut vec_right) = Self::top_level_matches(bounds, &self.right, tolerance, dist_right);
227232
if let Some(distances) = equal_dist {
228233
let translation = DVec2::X * (distances.first - distances.equal);
229234
vec_right.push_front(bounds.translate(translation));
235+
236+
// Find matching left distribution
230237
for &left in Self::exact_further_matches(bounds.translate(translation), &self.left, dist_left, distances.equal, 2).iter().skip(1) {
231238
vec_right.push_front(left);
232239
}
233-
vec_right[0][0].y = vec_right[0][0].y.min(vec_right[1][1].y);
234-
vec_right[0][1].y = vec_right[0][1].y.min(vec_right[1][1].y);
235-
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Right, vec_right, distances, bounds, translation, tolerance))
240+
241+
// Adjust bounds to maintain alignment
242+
if vec_right.len() > 1 {
243+
vec_right[0][0].y = vec_right[0][0].y.min(vec_right[1][1].y);
244+
vec_right[0][1].y = vec_right[0][1].y.min(vec_right[1][1].y);
245+
}
246+
247+
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Right, vec_right, distances, bounds, translation, tolerance));
248+
return;
236249
}
237250
}
238251

239-
// Left
240-
if consider_x && !self.left.is_empty() && snap_x.is_none() {
252+
// Try left distribution if right didn't work
253+
if !self.left.is_empty() {
241254
let (equal_dist, mut vec_left) = Self::top_level_matches(bounds, &self.left, tolerance, dist_left);
242255
if let Some(distances) = equal_dist {
243256
let translation = -DVec2::X * (distances.first - distances.equal);
244257
vec_left.make_contiguous().reverse();
245258
vec_left.push_back(bounds.translate(translation));
246259

260+
// Find matching right distribution
247261
for &right in Self::exact_further_matches(bounds.translate(translation), &self.right, dist_right, distances.equal, 2).iter().skip(1) {
248262
vec_left.push_back(right);
249263
}
250-
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Left, vec_left, distances, bounds, translation, tolerance))
264+
265+
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Left, vec_left, distances, bounds, translation, tolerance));
266+
return;
251267
}
252268
}
253269

254-
// Center X
255-
if consider_x && !self.left.is_empty() && !self.right.is_empty() && snap_x.is_none() {
270+
// Try center distribution if both sides exist
271+
if !self.left.is_empty() && !self.right.is_empty() {
256272
let target_x = (self.right[0].min() + self.left[0].max()).x / 2.;
257-
258273
let offset = target_x - bounds.center().x;
259274

260275
if offset.abs() < tolerance {
@@ -264,68 +279,91 @@ impl DistributionSnapper {
264279
let distances = DistributionMatch { first, equal };
265280

266281
let mut boxes = VecDeque::from([self.left[0], bounds.translate(translation), self.right[0]]);
267-
boxes[1][0].y = boxes[1][0].y.min(boxes[0][1].y);
268-
boxes[1][1].y = boxes[1][1].y.min(boxes[0][1].y);
269-
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::X, boxes, distances, bounds, translation, tolerance))
282+
283+
// Adjust bounds to maintain alignment
284+
if boxes.len() > 1 {
285+
boxes[1][0].y = boxes[1][0].y.min(boxes[0][1].y);
286+
boxes[1][1].y = boxes[1][1].y.min(boxes[0][1].y);
287+
}
288+
289+
*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::X, boxes, distances, bounds, translation, tolerance));
270290
}
271291
}
272292
}
273293

274294
fn vertical_snap(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {
275-
// Down
276-
if consider_y && !self.down.is_empty() {
295+
if !consider_y {
296+
return;
297+
}
298+
299+
// Try down distribution first
300+
if !self.down.is_empty() {
277301
let (equal_dist, mut vec_down) = Self::top_level_matches(bounds, &self.down, tolerance, dist_down);
278302
if let Some(distances) = equal_dist {
279303
let translation = DVec2::Y * (distances.first - distances.equal);
280304
vec_down.push_front(bounds.translate(translation));
281305

306+
// Find matching up distribution
282307
for &up in Self::exact_further_matches(bounds.translate(translation), &self.up, dist_up, distances.equal, 2).iter().skip(1) {
283308
vec_down.push_front(up);
284309
}
285-
vec_down[0][0].x = vec_down[0][0].x.min(vec_down[1][1].x);
286-
vec_down[0][1].x = vec_down[0][1].x.min(vec_down[1][1].x);
287-
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Down, vec_down, distances, bounds, translation, tolerance))
310+
311+
// Adjust bounds to maintain alignment
312+
if vec_down.len() > 1 {
313+
vec_down[0][0].x = vec_down[0][0].x.min(vec_down[1][1].x);
314+
vec_down[0][1].x = vec_down[0][1].x.min(vec_down[1][1].x);
315+
}
316+
317+
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Down, vec_down, distances, bounds, translation, tolerance));
318+
return;
288319
}
289320
}
290321

291-
// Up
292-
if consider_y && !self.up.is_empty() && snap_y.is_none() {
322+
// Try up distribution if down didn't work
323+
if !self.up.is_empty() {
293324
let (equal_dist, mut vec_up) = Self::top_level_matches(bounds, &self.up, tolerance, dist_up);
294325
if let Some(distances) = equal_dist {
295326
let translation = -DVec2::Y * (distances.first - distances.equal);
296327
vec_up.make_contiguous().reverse();
297328
vec_up.push_back(bounds.translate(translation));
298329

330+
// Find matching down distribution
299331
for &down in Self::exact_further_matches(bounds.translate(translation), &self.down, dist_down, distances.equal, 2).iter().skip(1) {
300332
vec_up.push_back(down);
301333
}
302-
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Up, vec_up, distances, bounds, translation, tolerance))
334+
335+
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Up, vec_up, distances, bounds, translation, tolerance));
336+
return;
303337
}
304338
}
305339

306-
// Center Y
307-
if consider_y && !self.up.is_empty() && !self.down.is_empty() && snap_y.is_none() {
340+
// Try center distribution if both sides exist
341+
if !self.up.is_empty() && !self.down.is_empty() {
308342
let target_y = (self.down[0].min() + self.up[0].max()).y / 2.;
309-
310343
let offset = target_y - bounds.center().y;
311344

312345
if offset.abs() < tolerance {
313346
let translation = DVec2::Y * offset;
314-
315347
let equal = bounds.translate(translation).min().y - self.up[0].max().y;
316348
let first = equal + offset;
317349
let distances = DistributionMatch { first, equal };
350+
318351
let mut boxes = VecDeque::from([self.up[0], bounds.translate(translation), self.down[0]]);
319-
boxes[1][0].x = boxes[1][0].x.min(boxes[0][1].x);
320-
boxes[1][1].x = boxes[1][1].x.min(boxes[0][1].x);
321-
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance))
352+
353+
// Adjust bounds to maintain alignment
354+
if boxes.len() > 1 {
355+
boxes[1][0].x = boxes[1][0].x.min(boxes[0][1].x);
356+
boxes[1][1].x = boxes[1][1].x.min(boxes[0][1].x);
357+
}
358+
359+
*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance));
322360
}
323361
}
324362
}
325363

326364
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
327365
let Some(bounds) = config.bbox else { return };
328-
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly {
366+
if !snap_data.document.snapping_state.bounding_box.distribute_evenly {
329367
return;
330368
}
331369

@@ -335,7 +373,7 @@ impl DistributionSnapper {
335373

336374
pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {
337375
let Some(bounds) = config.bbox else { return };
338-
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly {
376+
if !snap_data.document.snapping_state.bounding_box.distribute_evenly {
339377
return;
340378
}
341379

0 commit comments

Comments
 (0)