@@ -381,8 +381,32 @@ impl MonitorView { |
| 381 | 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 | 407 | /// Snap a monitor to be adjacent to the nearest other monitor. |
| 385 | 408 | /// Uses directional awareness - snaps to the side the monitor was dropped on. |
| 409 | + /// Ensures no overlaps with other monitors. |
| 386 | 410 | fn snap_to_nearest(&mut self, dragged_idx: usize, dragged: Rect) { |
| 387 | 411 | let dragged_center_x = dragged.x + dragged.width as i32 / 2; |
| 388 | 412 | let dragged_center_y = dragged.y + dragged.height as i32 / 2; |
@@ -402,44 +426,53 @@ impl MonitorView { |
| 402 | 426 | let dx = dragged_center_x - other_center_x; |
| 403 | 427 | let dy = dragged_center_y - other_center_y; |
| 404 | 428 | |
| 405 | | - // Determine snap position based on which side we're dropping on |
| 406 | | - // Also align on the perpendicular axis to ensure actual adjacency |
| 407 | | - let (new_x, new_y) = if dx.abs() > dy.abs() { |
| 408 | | - // More horizontal - snap left or right |
| 409 | | - // Clamp y to ensure vertical overlap with the target |
| 410 | | - let clamped_y = dragged.y |
| 411 | | - .max(other_rect.y - dragged.height as i32 + 1) |
| 412 | | - .min(other_rect.y + other_rect.height as i32 - 1); |
| 413 | | - if dx > 0 { |
| 414 | | - // Dropping to the right of other - snap to right side |
| 415 | | - (other_rect.x + other_rect.width as i32, clamped_y) |
| 416 | | - } else { |
| 417 | | - // Dropping to the left of other - snap to left side |
| 418 | | - (other_rect.x - dragged.width as i32, clamped_y) |
| 429 | + // Try all 4 sides and pick the best non-overlapping position |
| 430 | + let candidates = [ |
| 431 | + // Right of other |
| 432 | + (other_rect.x + other_rect.width as i32, other_rect.y), |
| 433 | + // Left of other |
| 434 | + (other_rect.x - dragged.width as i32, other_rect.y), |
| 435 | + // Below other |
| 436 | + (other_rect.x, other_rect.y + other_rect.height as i32), |
| 437 | + // Above other |
| 438 | + (other_rect.x, other_rect.y - dragged.height as i32), |
| 439 | + ]; |
| 440 | + |
| 441 | + // Score each candidate based on direction preference |
| 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 { |
| 421 | | - // More vertical - snap above or below |
| 422 | | - // Clamp x to ensure horizontal overlap with the target |
| 423 | | - let clamped_x = dragged.x |
| 424 | | - .max(other_rect.x - dragged.width as i32 + 1) |
| 425 | | - .min(other_rect.x + other_rect.width as i32 - 1); |
| 426 | | - if dy > 0 { |
| 427 | | - // Dropping below other - snap to bottom |
| 428 | | - (clamped_x, other_rect.y + other_rect.height as i32) |
| 449 | + |
| 450 | + // Calculate distance with direction weighting |
| 451 | + let new_center_x = new_x + dragged.width as i32 / 2; |
| 452 | + let new_center_y = new_y + dragged.height as i32 / 2; |
| 453 | + |
| 454 | + // Base distance |
| 455 | + let mut dist = (dragged_center_x - new_center_x).abs() |
| 456 | + + (dragged_center_y - new_center_y).abs(); |
| 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 | 467 | } else { |
| 430 | | - // Dropping above other - snap to top |
| 431 | | - (clamped_x, other_rect.y - dragged.height as i32) |
| 468 | + if (dy > 0) != (snap_dy > 0) { |
| 469 | + dist += 1000; // Penalize wrong vertical direction |
| 470 | + } |
| 432 | 471 | } |
| 433 | | - }; |
| 434 | 472 | |
| 435 | | - // Distance from current position to this snap position |
| 436 | | - let new_center_x = new_x + dragged.width as i32 / 2; |
| 437 | | - let new_center_y = new_y + dragged.height as i32 / 2; |
| 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)); |
| 473 | + if best_snap.map_or(true, |(_, _, best_dist)| dist < best_dist) { |
| 474 | + best_snap = Some((new_x, new_y, dist)); |
| 475 | + } |
| 443 | 476 | } |
| 444 | 477 | } |
| 445 | 478 | |