1
1
use super :: * ;
2
2
use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
3
3
use crate :: messages:: portfolio:: document:: utility_types:: misc:: * ;
4
- use crate :: messages:: prelude:: * ;
5
4
use glam:: DVec2 ;
6
5
use graphene_core:: renderer:: Quad ;
6
+ use std:: collections:: VecDeque ;
7
7
8
8
#[ derive( Clone , Debug , Default ) ]
9
9
pub struct DistributionSnapper {
@@ -79,18 +79,21 @@ impl DistributionSnapper {
79
79
let screen_bounds = ( document. metadata ( ) . document_to_viewport . inverse ( ) * Quad :: from_box ( [ DVec2 :: ZERO , snap_data. input . viewport_bounds . size ( ) ] ) ) . bounding_box ( ) ;
80
80
let max_extent = ( screen_bounds[ 1 ] - screen_bounds[ 0 ] ) . abs ( ) . max_element ( ) ;
81
81
82
+ // Collect artboard bounds
82
83
for layer in document. metadata ( ) . all_layers ( ) {
83
84
if document. network_interface . is_artboard ( & layer. to_node ( ) , & [ ] ) && !snap_data. ignore . contains ( & layer) {
84
85
self . add_bounds ( layer, snap_data, bbox_to_snap, max_extent) ;
85
86
}
86
87
}
87
88
89
+ // Collect alignment candidate bounds
88
90
for & layer in snap_data. alignment_candidates . map_or ( [ ] . as_slice ( ) , |candidates| candidates. as_slice ( ) ) {
89
91
if !snap_data. ignore_bounds ( layer) {
90
92
self . add_bounds ( layer, snap_data, bbox_to_snap, max_extent) ;
91
93
}
92
94
}
93
95
96
+ // Sort and merge intersecting rectangles
94
97
self . right . sort_unstable_by ( |a, b| a. center ( ) . x . total_cmp ( & b. center ( ) . x ) ) ;
95
98
self . left . sort_unstable_by ( |a, b| b. center ( ) . x . total_cmp ( & a. center ( ) . x ) ) ;
96
99
self . down . sort_unstable_by ( |a, b| a. center ( ) . y . total_cmp ( & b. center ( ) . y ) ) ;
@@ -184,13 +187,11 @@ impl DistributionSnapper {
184
187
fn snap_bbox_points ( & self , tolerance : f64 , point : & SnapCandidatePoint , snap_results : & mut SnapResults , constraint : SnapConstraint , bounds : Rect ) {
185
188
let mut consider_x = true ;
186
189
let mut consider_y = true ;
190
+
187
191
if let SnapConstraint :: Line { direction, .. } = constraint {
188
192
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. ;
194
195
}
195
196
196
197
let mut snap_x: Option < SnappedPoint > = None ;
@@ -221,40 +222,54 @@ impl DistributionSnapper {
221
222
}
222
223
223
224
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 ( ) {
226
231
let ( equal_dist, mut vec_right) = Self :: top_level_matches ( bounds, & self . right , tolerance, dist_right) ;
227
232
if let Some ( distances) = equal_dist {
228
233
let translation = DVec2 :: X * ( distances. first - distances. equal ) ;
229
234
vec_right. push_front ( bounds. translate ( translation) ) ;
235
+
236
+ // Find matching left distribution
230
237
for & left in Self :: exact_further_matches ( bounds. translate ( translation) , & self . left , dist_left, distances. equal , 2 ) . iter ( ) . skip ( 1 ) {
231
238
vec_right. push_front ( left) ;
232
239
}
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 ;
236
249
}
237
250
}
238
251
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 ( ) {
241
254
let ( equal_dist, mut vec_left) = Self :: top_level_matches ( bounds, & self . left , tolerance, dist_left) ;
242
255
if let Some ( distances) = equal_dist {
243
256
let translation = -DVec2 :: X * ( distances. first - distances. equal ) ;
244
257
vec_left. make_contiguous ( ) . reverse ( ) ;
245
258
vec_left. push_back ( bounds. translate ( translation) ) ;
246
259
260
+ // Find matching right distribution
247
261
for & right in Self :: exact_further_matches ( bounds. translate ( translation) , & self . right , dist_right, distances. equal , 2 ) . iter ( ) . skip ( 1 ) {
248
262
vec_left. push_back ( right) ;
249
263
}
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 ;
251
267
}
252
268
}
253
269
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 ( ) {
256
272
let target_x = ( self . right [ 0 ] . min ( ) + self . left [ 0 ] . max ( ) ) . x / 2. ;
257
-
258
273
let offset = target_x - bounds. center ( ) . x ;
259
274
260
275
if offset. abs ( ) < tolerance {
@@ -264,68 +279,91 @@ impl DistributionSnapper {
264
279
let distances = DistributionMatch { first, equal } ;
265
280
266
281
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) ) ;
270
290
}
271
291
}
272
292
}
273
293
274
294
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 ( ) {
277
301
let ( equal_dist, mut vec_down) = Self :: top_level_matches ( bounds, & self . down , tolerance, dist_down) ;
278
302
if let Some ( distances) = equal_dist {
279
303
let translation = DVec2 :: Y * ( distances. first - distances. equal ) ;
280
304
vec_down. push_front ( bounds. translate ( translation) ) ;
281
305
306
+ // Find matching up distribution
282
307
for & up in Self :: exact_further_matches ( bounds. translate ( translation) , & self . up , dist_up, distances. equal , 2 ) . iter ( ) . skip ( 1 ) {
283
308
vec_down. push_front ( up) ;
284
309
}
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 ;
288
319
}
289
320
}
290
321
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 ( ) {
293
324
let ( equal_dist, mut vec_up) = Self :: top_level_matches ( bounds, & self . up , tolerance, dist_up) ;
294
325
if let Some ( distances) = equal_dist {
295
326
let translation = -DVec2 :: Y * ( distances. first - distances. equal ) ;
296
327
vec_up. make_contiguous ( ) . reverse ( ) ;
297
328
vec_up. push_back ( bounds. translate ( translation) ) ;
298
329
330
+ // Find matching down distribution
299
331
for & down in Self :: exact_further_matches ( bounds. translate ( translation) , & self . down , dist_down, distances. equal , 2 ) . iter ( ) . skip ( 1 ) {
300
332
vec_up. push_back ( down) ;
301
333
}
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 ;
303
337
}
304
338
}
305
339
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 ( ) {
308
342
let target_y = ( self . down [ 0 ] . min ( ) + self . up [ 0 ] . max ( ) ) . y / 2. ;
309
-
310
343
let offset = target_y - bounds. center ( ) . y ;
311
344
312
345
if offset. abs ( ) < tolerance {
313
346
let translation = DVec2 :: Y * offset;
314
-
315
347
let equal = bounds. translate ( translation) . min ( ) . y - self . up [ 0 ] . max ( ) . y ;
316
348
let first = equal + offset;
317
349
let distances = DistributionMatch { first, equal } ;
350
+
318
351
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) ) ;
322
360
}
323
361
}
324
362
}
325
363
326
364
pub fn free_snap ( & mut self , snap_data : & mut SnapData , point : & SnapCandidatePoint , snap_results : & mut SnapResults , config : SnapTypeConfiguration ) {
327
365
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 {
329
367
return ;
330
368
}
331
369
@@ -335,7 +373,7 @@ impl DistributionSnapper {
335
373
336
374
pub fn constrained_snap ( & mut self , snap_data : & mut SnapData , point : & SnapCandidatePoint , snap_results : & mut SnapResults , constraint : SnapConstraint , config : SnapTypeConfiguration ) {
337
375
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 {
339
377
return ;
340
378
}
341
379
0 commit comments