gardesk/gardisplay / 29381c4

Browse files

improve snapping: edge alignment + enforced adjacency

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
29381c4c3fd82199b6eb7dda29b509be7fc5c90e
Parents
c037b91
Tree
30b2da9

1 changed file

StatusFile+-
M gardisplay/src/ui/monitor_view.rs 123 44
gardisplay/src/ui/monitor_view.rsmodified
@@ -370,85 +370,164 @@ impl MonitorView {
370370
     }
371371
 
372372
     /// Apply snapping to a monitor after drag ends.
373
-    /// Snaps edges to nearby edges while preserving the user's intended position.
373
+    /// Snaps edges to nearby edges while ensuring adjacency (no gaps).
374374
     fn apply_snap(&mut self, dragged_idx: usize) {
375375
         if self.monitors.len() < 2 {
376376
             return;
377377
         }
378378
 
379379
         let mut dragged = self.monitors[dragged_idx].scaled_rect;
380
+
381
+        // Step 1: Find best edge snaps within threshold
382
+        let (snapped_x, snapped_y) = self.find_edge_snaps(dragged_idx, dragged);
383
+
384
+        if let Some(x) = snapped_x {
385
+            dragged.x = x;
386
+        }
387
+        if let Some(y) = snapped_y {
388
+            dragged.y = y;
389
+        }
390
+
391
+        // Check for overlap
392
+        if self.would_overlap(dragged, dragged_idx) {
393
+            return;
394
+        }
395
+
396
+        // Step 2: Check if result is adjacent to any monitor
397
+        if self.is_adjacent_to_any(dragged, dragged_idx) {
398
+            self.monitors[dragged_idx].scaled_rect = dragged;
399
+            return;
400
+        }
401
+
402
+        // Step 3: Not adjacent - snap to nearest adjacency position
403
+        // Preserve as much of the user's position as possible
404
+        if let Some(adj_pos) = self.find_nearest_adjacent_position(dragged_idx, dragged) {
405
+            self.monitors[dragged_idx].scaled_rect = adj_pos;
406
+        }
407
+    }
408
+
409
+    /// Find edge snaps within threshold for each axis.
410
+    fn find_edge_snaps(&self, dragged_idx: usize, dragged: Rect) -> (Option<i32>, Option<i32>) {
380411
         let dragged_right = dragged.x + dragged.width as i32;
381412
         let dragged_bottom = dragged.y + dragged.height as i32;
382413
 
383
-        // Find best edge snaps independently for each axis
384
-        let mut best_x_snap: Option<(i32, i32)> = None; // (new_x, distance)
385
-        let mut best_y_snap: Option<(i32, i32)> = None; // (new_y, distance)
414
+        let mut best_x: Option<(i32, i32)> = None;
415
+        let mut best_y: Option<(i32, i32)> = None;
386416
 
387417
         for (i, other) in self.monitors.iter().enumerate() {
388418
             if i == dragged_idx {
389419
                 continue;
390420
             }
391421
 
392
-            let other_rect = other.scaled_rect;
393
-            let other_right = other_rect.x + other_rect.width as i32;
394
-            let other_bottom = other_rect.y + other_rect.height as i32;
422
+            let r = other.scaled_rect;
423
+            let r_right = r.x + r.width as i32;
424
+            let r_bottom = r.y + r.height as i32;
395425
 
396
-            // X-axis edge snaps (for adjacency - touching edges)
426
+            // X snaps: adjacency (left-to-right, right-to-left) and alignment (left-to-left, right-to-right)
397427
             let x_snaps = [
398
-                // Dragged left edge to other right edge (place right of other)
399
-                (other_right, (dragged.x - other_right).abs()),
400
-                // Dragged right edge to other left edge (place left of other)
401
-                (other_rect.x - dragged.width as i32, (dragged_right - other_rect.x).abs()),
402
-                // Dragged left edge to other left edge (align left edges)
403
-                (other_rect.x, (dragged.x - other_rect.x).abs()),
404
-                // Dragged right edge to other right edge (align right edges)
405
-                (other_right - dragged.width as i32, (dragged_right - other_right).abs()),
428
+                (r_right, (dragged.x - r_right).abs()),                           // left edge to right edge
429
+                (r.x - dragged.width as i32, (dragged_right - r.x).abs()),        // right edge to left edge
430
+                (r.x, (dragged.x - r.x).abs()),                                   // left to left
431
+                (r_right - dragged.width as i32, (dragged_right - r_right).abs()), // right to right
406432
             ];
407433
 
408434
             for (new_x, dist) in x_snaps {
409
-                if dist < SNAP_THRESHOLD {
410
-                    if best_x_snap.map_or(true, |(_, best_dist)| dist < best_dist) {
411
-                        best_x_snap = Some((new_x, dist));
412
-                    }
435
+                if dist < SNAP_THRESHOLD && best_x.map_or(true, |(_, d)| dist < d) {
436
+                    best_x = Some((new_x, dist));
413437
                 }
414438
             }
415439
 
416
-            // Y-axis edge snaps
440
+            // Y snaps
417441
             let y_snaps = [
418
-                // Dragged top edge to other bottom edge (place below other)
419
-                (other_bottom, (dragged.y - other_bottom).abs()),
420
-                // Dragged bottom edge to other top edge (place above other)
421
-                (other_rect.y - dragged.height as i32, (dragged_bottom - other_rect.y).abs()),
422
-                // Dragged top edge to other top edge (align top edges)
423
-                (other_rect.y, (dragged.y - other_rect.y).abs()),
424
-                // Dragged bottom edge to other bottom edge (align bottom edges)
425
-                (other_bottom - dragged.height as i32, (dragged_bottom - other_bottom).abs()),
442
+                (r_bottom, (dragged.y - r_bottom).abs()),                            // top to bottom
443
+                (r.y - dragged.height as i32, (dragged_bottom - r.y).abs()),         // bottom to top
444
+                (r.y, (dragged.y - r.y).abs()),                                      // top to top
445
+                (r_bottom - dragged.height as i32, (dragged_bottom - r_bottom).abs()), // bottom to bottom
426446
             ];
427447
 
428448
             for (new_y, dist) in y_snaps {
429
-                if dist < SNAP_THRESHOLD {
430
-                    if best_y_snap.map_or(true, |(_, best_dist)| dist < best_dist) {
431
-                        best_y_snap = Some((new_y, dist));
432
-                    }
449
+                if dist < SNAP_THRESHOLD && best_y.map_or(true, |(_, d)| dist < d) {
450
+                    best_y = Some((new_y, dist));
433451
                 }
434452
             }
435453
         }
436454
 
437
-        // Apply snaps
438
-        if let Some((new_x, _)) = best_x_snap {
439
-            dragged.x = new_x;
440
-        }
441
-        if let Some((new_y, _)) = best_y_snap {
442
-            dragged.y = new_y;
455
+        (best_x.map(|(x, _)| x), best_y.map(|(y, _)| y))
456
+    }
457
+
458
+    /// Check if rect is adjacent to any other monitor.
459
+    fn is_adjacent_to_any(&self, rect: Rect, exclude_idx: usize) -> bool {
460
+        for (i, other) in self.monitors.iter().enumerate() {
461
+            if i == exclude_idx {
462
+                continue;
463
+            }
464
+            if Self::rects_adjacent(rect, other.scaled_rect) {
465
+                return true;
466
+            }
443467
         }
468
+        false
469
+    }
444470
 
445
-        // Check for overlap and resolve if needed
446
-        if self.would_overlap(dragged, dragged_idx) {
447
-            // Revert to original position if we'd overlap
448
-            return;
471
+    /// Check if two rects are adjacent (touching edges with overlap on perpendicular axis).
472
+    fn rects_adjacent(a: Rect, b: Rect) -> bool {
473
+        let a_right = a.x + a.width as i32;
474
+        let a_bottom = a.y + a.height as i32;
475
+        let b_right = b.x + b.width as i32;
476
+        let b_bottom = b.y + b.height as i32;
477
+
478
+        // Horizontal adjacency: right edge touches left edge (or vice versa) with vertical overlap
479
+        let h_adjacent = (a_right == b.x || a.x == b_right)
480
+            && a.y < b_bottom && a_bottom > b.y;
481
+
482
+        // Vertical adjacency: bottom edge touches top edge (or vice versa) with horizontal overlap
483
+        let v_adjacent = (a_bottom == b.y || a.y == b_bottom)
484
+            && a.x < b_right && a_right > b.x;
485
+
486
+        h_adjacent || v_adjacent
487
+    }
488
+
489
+    /// Find the nearest position that makes the rect adjacent to another monitor.
490
+    fn find_nearest_adjacent_position(&self, dragged_idx: usize, dragged: Rect) -> Option<Rect> {
491
+        let mut best: Option<(Rect, i32)> = None;
492
+
493
+        for (i, other) in self.monitors.iter().enumerate() {
494
+            if i == dragged_idx {
495
+                continue;
496
+            }
497
+
498
+            let r = other.scaled_rect;
499
+            let r_right = r.x + r.width as i32;
500
+            let r_bottom = r.y + r.height as i32;
501
+
502
+            // Try 4 adjacent positions, preserving the perpendicular coordinate
503
+            let candidates = [
504
+                // Right of other (preserve y)
505
+                Rect::new(r_right, dragged.y, dragged.width, dragged.height),
506
+                // Left of other (preserve y)
507
+                Rect::new(r.x - dragged.width as i32, dragged.y, dragged.width, dragged.height),
508
+                // Below other (preserve x)
509
+                Rect::new(dragged.x, r_bottom, dragged.width, dragged.height),
510
+                // Above other (preserve x)
511
+                Rect::new(dragged.x, r.y - dragged.height as i32, dragged.width, dragged.height),
512
+            ];
513
+
514
+            for candidate in candidates {
515
+                // Must be adjacent and not overlap
516
+                if !Self::rects_adjacent(candidate, r) {
517
+                    continue;
518
+                }
519
+                if self.would_overlap(candidate, dragged_idx) {
520
+                    continue;
521
+                }
522
+
523
+                let dist = (candidate.x - dragged.x).abs() + (candidate.y - dragged.y).abs();
524
+                if best.map_or(true, |(_, d)| dist < d) {
525
+                    best = Some((candidate, dist));
526
+                }
527
+            }
449528
         }
450529
 
451
-        self.monitors[dragged_idx].scaled_rect = dragged;
530
+        best.map(|(rect, _)| rect)
452531
     }
453532
 
454533
     /// Check if a rect overlaps with any monitor except the specified one.