@@ -381,8 +381,32 @@ impl MonitorView { |
| 381 | self.snap_to_nearest(dragged_idx, dragged); | 381 | self.snap_to_nearest(dragged_idx, dragged); |
| 382 | } | 382 | } |
| 383 | | 383 | |
| | 384 | + /// Check if a rect overlaps with any monitor except the specified one. |
| | 385 | + fn would_overlap(&self, rect: Rect, exclude_idx: usize) -> bool { |
| | 386 | + for (i, other) in self.monitors.iter().enumerate() { |
| | 387 | + if i == exclude_idx { |
| | 388 | + continue; |
| | 389 | + } |
| | 390 | + if Self::rects_overlap(rect, other.scaled_rect) { |
| | 391 | + return true; |
| | 392 | + } |
| | 393 | + } |
| | 394 | + false |
| | 395 | + } |
| | 396 | + |
| | 397 | + /// Check if two rects overlap (share any interior area). |
| | 398 | + fn rects_overlap(a: Rect, b: Rect) -> bool { |
| | 399 | + let a_right = a.x + a.width as i32; |
| | 400 | + let a_bottom = a.y + a.height as i32; |
| | 401 | + let b_right = b.x + b.width as i32; |
| | 402 | + let b_bottom = b.y + b.height as i32; |
| | 403 | + |
| | 404 | + a.x < b_right && a_right > b.x && a.y < b_bottom && a_bottom > b.y |
| | 405 | + } |
| | 406 | + |
| 384 | /// Snap a monitor to be adjacent to the nearest other monitor. | 407 | /// Snap a monitor to be adjacent to the nearest other monitor. |
| 385 | /// Uses directional awareness - snaps to the side the monitor was dropped on. | 408 | /// Uses directional awareness - snaps to the side the monitor was dropped on. |
| | 409 | + /// Ensures no overlaps with other monitors. |
| 386 | fn snap_to_nearest(&mut self, dragged_idx: usize, dragged: Rect) { | 410 | fn snap_to_nearest(&mut self, dragged_idx: usize, dragged: Rect) { |
| 387 | let dragged_center_x = dragged.x + dragged.width as i32 / 2; | 411 | let dragged_center_x = dragged.x + dragged.width as i32 / 2; |
| 388 | let dragged_center_y = dragged.y + dragged.height as i32 / 2; | 412 | let dragged_center_y = dragged.y + dragged.height as i32 / 2; |
@@ -402,44 +426,53 @@ impl MonitorView { |
| 402 | let dx = dragged_center_x - other_center_x; | 426 | let dx = dragged_center_x - other_center_x; |
| 403 | let dy = dragged_center_y - other_center_y; | 427 | let dy = dragged_center_y - other_center_y; |
| 404 | | 428 | |
| 405 | - // Determine snap position based on which side we're dropping on | 429 | + // Try all 4 sides and pick the best non-overlapping position |
| 406 | - // Also align on the perpendicular axis to ensure actual adjacency | 430 | + let candidates = [ |
| 407 | - let (new_x, new_y) = if dx.abs() > dy.abs() { | 431 | + // Right of other |
| 408 | - // More horizontal - snap left or right | 432 | + (other_rect.x + other_rect.width as i32, other_rect.y), |
| 409 | - // Clamp y to ensure vertical overlap with the target | 433 | + // Left of other |
| 410 | - let clamped_y = dragged.y | 434 | + (other_rect.x - dragged.width as i32, other_rect.y), |
| 411 | - .max(other_rect.y - dragged.height as i32 + 1) | 435 | + // Below other |
| 412 | - .min(other_rect.y + other_rect.height as i32 - 1); | 436 | + (other_rect.x, other_rect.y + other_rect.height as i32), |
| 413 | - if dx > 0 { | 437 | + // Above other |
| 414 | - // Dropping to the right of other - snap to right side | 438 | + (other_rect.x, other_rect.y - dragged.height as i32), |
| 415 | - (other_rect.x + other_rect.width as i32, clamped_y) | 439 | + ]; |
| 416 | - } else { | 440 | + |
| 417 | - // Dropping to the left of other - snap to left side | 441 | + // Score each candidate based on direction preference |
| 418 | - (other_rect.x - dragged.width as i32, clamped_y) | 442 | + for (new_x, new_y) in candidates { |
| | 443 | + let candidate_rect = Rect::new(new_x, new_y, dragged.width, dragged.height); |
| | 444 | + |
| | 445 | + // Skip if this position would overlap with another monitor |
| | 446 | + if self.would_overlap(candidate_rect, dragged_idx) { |
| | 447 | + continue; |
| 419 | } | 448 | } |
| 420 | - } else { | 449 | + |
| 421 | - // More vertical - snap above or below | 450 | + // Calculate distance with direction weighting |
| 422 | - // Clamp x to ensure horizontal overlap with the target | 451 | + let new_center_x = new_x + dragged.width as i32 / 2; |
| 423 | - let clamped_x = dragged.x | 452 | + let new_center_y = new_y + dragged.height as i32 / 2; |
| 424 | - .max(other_rect.x - dragged.width as i32 + 1) | 453 | + |
| 425 | - .min(other_rect.x + other_rect.width as i32 - 1); | 454 | + // Base distance |
| 426 | - if dy > 0 { | 455 | + let mut dist = (dragged_center_x - new_center_x).abs() |
| 427 | - // Dropping below other - snap to bottom | 456 | + + (dragged_center_y - new_center_y).abs(); |
| 428 | - (clamped_x, other_rect.y + other_rect.height as i32) | 457 | + |
| | 458 | + // Penalize positions that don't match the drag direction |
| | 459 | + let snap_dx = new_center_x - other_center_x; |
| | 460 | + let snap_dy = new_center_y - other_center_y; |
| | 461 | + |
| | 462 | + // If we're dragging more horizontally, prefer horizontal snaps |
| | 463 | + if dx.abs() > dy.abs() { |
| | 464 | + if (dx > 0) != (snap_dx > 0) { |
| | 465 | + dist += 1000; // Penalize wrong horizontal direction |
| | 466 | + } |
| 429 | } else { | 467 | } else { |
| 430 | - // Dropping above other - snap to top | 468 | + if (dy > 0) != (snap_dy > 0) { |
| 431 | - (clamped_x, other_rect.y - dragged.height as i32) | 469 | + dist += 1000; // Penalize wrong vertical direction |
| | 470 | + } |
| 432 | } | 471 | } |
| 433 | - }; | | |
| 434 | | 472 | |
| 435 | - // Distance from current position to this snap position | 473 | + if best_snap.map_or(true, |(_, _, best_dist)| dist < best_dist) { |
| 436 | - let new_center_x = new_x + dragged.width as i32 / 2; | 474 | + best_snap = Some((new_x, new_y, dist)); |
| 437 | - let new_center_y = new_y + dragged.height as i32 / 2; | 475 | + } |
| 438 | - let dist = (dragged_center_x - new_center_x).abs() | | |
| 439 | - + (dragged_center_y - new_center_y).abs(); | | |
| 440 | - | | |
| 441 | - if best_snap.map_or(true, |(_, _, best_dist)| dist < best_dist) { | | |
| 442 | - best_snap = Some((new_x, new_y, dist)); | | |
| 443 | } | 476 | } |
| 444 | } | 477 | } |
| 445 | | 478 | |