11use super :: * ;
22use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
33use crate :: messages:: portfolio:: document:: utility_types:: misc:: * ;
4- use crate :: messages:: prelude:: * ;
54use glam:: DVec2 ;
65use graphene_core:: renderer:: Quad ;
6+ use std:: collections:: VecDeque ;
77
88#[ derive( Clone , Debug , Default ) ]
99pub 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