gardesk/gardisplay / 3158fd0

Browse files

prevent gaps by snapping to nearest monitor on release

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
3158fd030cd0b51aaf0e41a4577db2fb4b89c0c7
Parents
08a81ff
Tree
2090f2e

1 changed file

StatusFile+-
M gardisplay/src/ui/monitor_view.rs 108 1
gardisplay/src/ui/monitor_view.rsmodified
@@ -370,12 +370,18 @@ impl MonitorView {
370370
     }
371371
 
372372
     /// Apply snapping to a monitor after drag ends.
373
+    /// Ensures no gaps - monitors must always be adjacent to at least one other.
373374
     fn apply_snap(&mut self, dragged_idx: usize) {
375
+        if self.monitors.len() < 2 {
376
+            return;
377
+        }
378
+
374379
         let mut snap_x: Option<i32> = None;
375380
         let mut snap_y: Option<i32> = None;
376381
 
377382
         let dragged = self.monitors[dragged_idx].scaled_rect;
378383
 
384
+        // First pass: threshold-based snapping for fine alignment
379385
         for (i, other) in self.monitors.iter().enumerate() {
380386
             if i == dragged_idx {
381387
                 continue;
@@ -422,13 +428,114 @@ impl MonitorView {
422428
             }
423429
         }
424430
 
425
-        // Apply snaps
431
+        // Apply threshold snaps
426432
         if let Some(x) = snap_x {
427433
             self.monitors[dragged_idx].scaled_rect.x = x;
428434
         }
429435
         if let Some(y) = snap_y {
430436
             self.monitors[dragged_idx].scaled_rect.y = y;
431437
         }
438
+
439
+        // Second pass: close gaps - ensure monitor is adjacent to at least one other
440
+        let dragged = self.monitors[dragged_idx].scaled_rect;
441
+        if !self.is_adjacent_to_any(dragged_idx) {
442
+            self.snap_to_nearest(dragged_idx, dragged);
443
+        }
444
+    }
445
+
446
+    /// Check if a monitor is adjacent (touching) any other monitor.
447
+    fn is_adjacent_to_any(&self, monitor_idx: usize) -> bool {
448
+        let rect = self.monitors[monitor_idx].scaled_rect;
449
+
450
+        for (i, other) in self.monitors.iter().enumerate() {
451
+            if i == monitor_idx {
452
+                continue;
453
+            }
454
+
455
+            if self.rects_adjacent(rect, other.scaled_rect) {
456
+                return true;
457
+            }
458
+        }
459
+        false
460
+    }
461
+
462
+    /// Check if two rects are adjacent (touching edges, with possible overlap on perpendicular axis).
463
+    fn rects_adjacent(&self, a: Rect, b: Rect) -> bool {
464
+        // Check if they overlap on one axis and touch on the other
465
+        let a_right = a.x + a.width as i32;
466
+        let a_bottom = a.y + a.height as i32;
467
+        let b_right = b.x + b.width as i32;
468
+        let b_bottom = b.y + b.height as i32;
469
+
470
+        // Horizontal adjacency: a's right touches b's left OR a's left touches b's right
471
+        // AND they overlap vertically
472
+        let horiz_touch = a_right == b.x || a.x == b_right;
473
+        let vert_overlap = a.y < b_bottom && a_bottom > b.y;
474
+
475
+        // Vertical adjacency: a's bottom touches b's top OR a's top touches b's bottom
476
+        // AND they overlap horizontally
477
+        let vert_touch = a_bottom == b.y || a.y == b_bottom;
478
+        let horiz_overlap = a.x < b_right && a_right > b.x;
479
+
480
+        (horiz_touch && vert_overlap) || (vert_touch && horiz_overlap)
481
+    }
482
+
483
+    /// Snap a monitor to be adjacent to the nearest other monitor.
484
+    fn snap_to_nearest(&mut self, dragged_idx: usize, dragged: Rect) {
485
+        let dragged_center_x = dragged.x + dragged.width as i32 / 2;
486
+        let dragged_center_y = dragged.y + dragged.height as i32 / 2;
487
+
488
+        let mut best_snap: Option<(i32, i32, i32)> = None; // (new_x, new_y, distance)
489
+
490
+        for (i, other) in self.monitors.iter().enumerate() {
491
+            if i == dragged_idx {
492
+                continue;
493
+            }
494
+
495
+            let other_rect = other.scaled_rect;
496
+
497
+            // Calculate potential snap positions (adjacent to this monitor)
498
+            let snaps = [
499
+                // Snap to right of other
500
+                (
501
+                    other_rect.x + other_rect.width as i32,
502
+                    other_rect.y,
503
+                ),
504
+                // Snap to left of other
505
+                (
506
+                    other_rect.x - dragged.width as i32,
507
+                    other_rect.y,
508
+                ),
509
+                // Snap to bottom of other
510
+                (
511
+                    other_rect.x,
512
+                    other_rect.y + other_rect.height as i32,
513
+                ),
514
+                // Snap to top of other
515
+                (
516
+                    other_rect.x,
517
+                    other_rect.y - dragged.height as i32,
518
+                ),
519
+            ];
520
+
521
+            for (new_x, new_y) in snaps {
522
+                let new_center_x = new_x + dragged.width as i32 / 2;
523
+                let new_center_y = new_y + dragged.height as i32 / 2;
524
+
525
+                // Distance from current position to this snap position
526
+                let dist = (dragged_center_x - new_center_x).abs()
527
+                    + (dragged_center_y - new_center_y).abs();
528
+
529
+                if best_snap.map_or(true, |(_, _, best_dist)| dist < best_dist) {
530
+                    best_snap = Some((new_x, new_y, dist));
531
+                }
532
+            }
533
+        }
534
+
535
+        if let Some((new_x, new_y, _)) = best_snap {
536
+            self.monitors[dragged_idx].scaled_rect.x = new_x;
537
+            self.monitors[dragged_idx].scaled_rect.y = new_y;
538
+        }
432539
     }
433540
 
434541
     /// Render the monitor view.