@@ -31,12 +31,17 @@ pub enum DragState { |
| 31 | 31 | }, |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | | -#[derive(Debug, Clone, Copy)] |
| 34 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 35 | 35 | pub enum ResizeEdge { |
| 36 | 36 | TopLeft, |
| 37 | + Top, |
| 37 | 38 | TopRight, |
| 39 | + Left, |
| 40 | + Right, |
| 38 | 41 | BottomLeft, |
| 42 | + Bottom, |
| 39 | 43 | BottomRight, |
| 44 | + None, |
| 40 | 45 | } |
| 41 | 46 | |
| 42 | 47 | impl WindowManager { |
@@ -124,22 +129,26 @@ impl WindowManager { |
| 124 | 129 | continue; |
| 125 | 130 | } |
| 126 | 131 | |
| 132 | + // Check window rules and EWMH hints |
| 133 | + let rule_actions = self.check_rules(window); |
| 134 | + let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |
| 135 | + |
| 127 | 136 | // Subscribe to events on the window |
| 128 | | - self.conn.select_input( |
| 129 | | - window, |
| 130 | | - EventMask::ENTER_WINDOW |
| 131 | | - | EventMask::FOCUS_CHANGE |
| 132 | | - | EventMask::PROPERTY_CHANGE |
| 133 | | - | EventMask::STRUCTURE_NOTIFY, |
| 134 | | - )?; |
| 137 | + // Floating windows get POINTER_MOTION for edge resize cursors |
| 138 | + let base_events = EventMask::ENTER_WINDOW |
| 139 | + | EventMask::FOCUS_CHANGE |
| 140 | + | EventMask::PROPERTY_CHANGE |
| 141 | + | EventMask::STRUCTURE_NOTIFY; |
| 142 | + let events = if should_float { |
| 143 | + base_events | EventMask::POINTER_MOTION |
| 144 | + } else { |
| 145 | + base_events |
| 146 | + }; |
| 147 | + self.conn.select_input(window, events)?; |
| 135 | 148 | |
| 136 | 149 | // Grab button for click-to-focus |
| 137 | 150 | self.conn.grab_button(window)?; |
| 138 | 151 | |
| 139 | | - // Check window rules and EWMH hints |
| 140 | | - let rule_actions = self.check_rules(window); |
| 141 | | - let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |
| 142 | | - |
| 143 | 152 | // Manage the window |
| 144 | 153 | if should_float { |
| 145 | 154 | self.manage_window_floating(window); |
@@ -154,7 +163,10 @@ impl WindowManager { |
| 154 | 163 | // Focus the first window |
| 155 | 164 | if let Some(window) = self.focused_window { |
| 156 | 165 | self.set_focus(window, true)?; |
| 157 | | - self.conn.ungrab_button(window)?; |
| 166 | + // Ungrab button for click-through (unless floating - keep for edge resize) |
| 167 | + if !self.is_floating(window) { |
| 168 | + self.conn.ungrab_button(window)?; |
| 169 | + } |
| 158 | 170 | } |
| 159 | 171 | tracing::info!("Adopted {} existing windows", adopted); |
| 160 | 172 | } |
@@ -233,18 +245,6 @@ impl WindowManager { |
| 233 | 245 | return Ok(()); |
| 234 | 246 | } |
| 235 | 247 | |
| 236 | | - // Subscribe to events on the window |
| 237 | | - self.conn.select_input( |
| 238 | | - window, |
| 239 | | - EventMask::ENTER_WINDOW |
| 240 | | - | EventMask::FOCUS_CHANGE |
| 241 | | - | EventMask::PROPERTY_CHANGE |
| 242 | | - | EventMask::STRUCTURE_NOTIFY, |
| 243 | | - )?; |
| 244 | | - |
| 245 | | - // Grab button for click-to-focus |
| 246 | | - self.conn.grab_button(window)?; |
| 247 | | - |
| 248 | 248 | // Check window rules first |
| 249 | 249 | let rule_actions = self.check_rules(window); |
| 250 | 250 | |
@@ -255,6 +255,22 @@ impl WindowManager { |
| 255 | 255 | // Determine if window should float (rule > ICCCM/EWMH hints) |
| 256 | 256 | let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |
| 257 | 257 | |
| 258 | + // Subscribe to events on the window |
| 259 | + // Floating windows get POINTER_MOTION for edge resize cursors |
| 260 | + let base_events = EventMask::ENTER_WINDOW |
| 261 | + | EventMask::FOCUS_CHANGE |
| 262 | + | EventMask::PROPERTY_CHANGE |
| 263 | + | EventMask::STRUCTURE_NOTIFY; |
| 264 | + let events = if should_float { |
| 265 | + base_events | EventMask::POINTER_MOTION |
| 266 | + } else { |
| 267 | + base_events |
| 268 | + }; |
| 269 | + self.conn.select_input(window, events)?; |
| 270 | + |
| 271 | + // Grab button for click-to-focus |
| 272 | + self.conn.grab_button(window)?; |
| 273 | + |
| 258 | 274 | // Manage window on target workspace |
| 259 | 275 | if target_idx != self.focused_workspace { |
| 260 | 276 | // Window goes to a different workspace |
@@ -403,8 +419,16 @@ impl WindowManager { |
| 403 | 419 | let child = event.child; |
| 404 | 420 | tracing::debug!("ButtonPress on window {}, child {}, button {}", window, child, event.detail); |
| 405 | 421 | |
| 422 | + // If we're already in a drag, ignore additional button presses |
| 423 | + if self.drag_state.is_some() { |
| 424 | + tracing::debug!("Already in drag state, ignoring ButtonPress"); |
| 425 | + return Ok(()); |
| 426 | + } |
| 427 | + |
| 406 | 428 | // Check for mod+click on floating windows (move/resize) |
| 407 | | - let has_mod = event.state.contains(x11rb::protocol::xproto::KeyButMask::MOD1); |
| 429 | + // Support both Alt (MOD1) and Super (MOD4) as the modifier |
| 430 | + let has_mod = event.state.contains(x11rb::protocol::xproto::KeyButMask::MOD1) |
| 431 | + || event.state.contains(x11rb::protocol::xproto::KeyButMask::MOD4); |
| 408 | 432 | |
| 409 | 433 | // For Alt+click from root grab, use child window (the window under cursor) |
| 410 | 434 | let target = if window == self.conn.root && child != 0 { |
@@ -429,8 +453,8 @@ impl WindowManager { |
| 429 | 453 | self.conn.grab_pointer(target)?; |
| 430 | 454 | return Ok(()); |
| 431 | 455 | } else if event.detail == 3 { |
| 432 | | - // Mod+Button3 = Resize |
| 433 | | - let edge = determine_resize_edge(&geometry, event.root_x, event.root_y); |
| 456 | + // Mod+Button3 = Resize (quadrant-based edge detection) |
| 457 | + let edge = determine_resize_edge_quadrant(&geometry, event.root_x, event.root_y); |
| 434 | 458 | tracing::debug!("Starting resize for floating window {}, edge {:?}", target, edge); |
| 435 | 459 | self.drag_state = Some(DragState::Resize { |
| 436 | 460 | window: target, |
@@ -444,6 +468,45 @@ impl WindowManager { |
| 444 | 468 | } |
| 445 | 469 | } |
| 446 | 470 | |
| 471 | + // Check for edge resize on floating windows (click on edge without mod key) |
| 472 | + let is_floating_win = self.is_floating(window); |
| 473 | + tracing::debug!( |
| 474 | + "Edge resize check: window={}, is_floating={}, button={}, pos=({},{})", |
| 475 | + window, is_floating_win, event.detail, event.root_x, event.root_y |
| 476 | + ); |
| 477 | + |
| 478 | + if !has_mod && is_floating_win && event.detail == 1 { |
| 479 | + let geometry = self.get_floating_geometry(window); |
| 480 | + let edge = determine_resize_edge(&geometry, event.root_x, event.root_y); |
| 481 | + tracing::debug!( |
| 482 | + "Edge detection: geometry=({},{} {}x{}), edge={:?}", |
| 483 | + geometry.x, geometry.y, geometry.width, geometry.height, edge |
| 484 | + ); |
| 485 | + if edge != ResizeEdge::None { |
| 486 | + tracing::info!("Starting edge resize for floating window {}, edge {:?}", window, edge); |
| 487 | + self.drag_state = Some(DragState::Resize { |
| 488 | + window, |
| 489 | + start_x: event.root_x, |
| 490 | + start_y: event.root_y, |
| 491 | + start_geometry: geometry.clone(), |
| 492 | + edge, |
| 493 | + }); |
| 494 | + tracing::debug!("Grabbing pointer for resize, geometry={:?}", geometry); |
| 495 | + self.conn.grab_pointer(window)?; |
| 496 | + self.conn.flush()?; |
| 497 | + return Ok(()); |
| 498 | + } |
| 499 | + // Not on edge - if this is a focused floating window, replay the click |
| 500 | + if self.focused_window == Some(window) { |
| 501 | + self.conn.conn.allow_events( |
| 502 | + x11rb::protocol::xproto::Allow::REPLAY_POINTER, |
| 503 | + x11rb::CURRENT_TIME, |
| 504 | + )?; |
| 505 | + self.conn.flush()?; |
| 506 | + return Ok(()); |
| 507 | + } |
| 508 | + } |
| 509 | + |
| 447 | 510 | // Only handle if we manage this window |
| 448 | 511 | if !self.windows.contains_key(&window) { |
| 449 | 512 | return Ok(()); |
@@ -451,14 +514,21 @@ impl WindowManager { |
| 451 | 514 | |
| 452 | 515 | // Focus the clicked window |
| 453 | 516 | if self.focused_window != Some(window) { |
| 454 | | - // Regrab button on old focused window |
| 517 | + // Regrab button on old focused window (unless it's floating - keep grab for edge resize) |
| 455 | 518 | if let Some(old) = self.focused_window { |
| 456 | | - self.conn.grab_button(old)?; |
| 519 | + if !self.is_floating(old) { |
| 520 | + self.conn.grab_button(old)?; |
| 521 | + } |
| 457 | 522 | } |
| 458 | 523 | |
| 459 | | - // Set focus and ungrab button on new focused window (no warp - mouse click) |
| 524 | + // Set focus |
| 460 | 525 | self.set_focus(window, false)?; |
| 461 | | - self.conn.ungrab_button(window)?; |
| 526 | + |
| 527 | + // For non-floating windows, ungrab button for click-through |
| 528 | + // For floating windows, keep grab for edge resize detection |
| 529 | + if !self.is_floating(window) { |
| 530 | + self.conn.ungrab_button(window)?; |
| 531 | + } |
| 462 | 532 | |
| 463 | 533 | // Raise floating windows on focus |
| 464 | 534 | if self.is_floating(window) { |
@@ -476,7 +546,9 @@ impl WindowManager { |
| 476 | 546 | Ok(()) |
| 477 | 547 | } |
| 478 | 548 | |
| 479 | | - fn handle_button_release(&mut self, _event: ButtonReleaseEvent) -> Result<()> { |
| 549 | + fn handle_button_release(&mut self, event: ButtonReleaseEvent) -> Result<()> { |
| 550 | + tracing::debug!("ButtonRelease: button={}, window={}, in_drag={}", |
| 551 | + event.detail, event.event, self.drag_state.is_some()); |
| 480 | 552 | if self.drag_state.is_some() { |
| 481 | 553 | tracing::debug!("Ending drag operation"); |
| 482 | 554 | self.drag_state = None; |
@@ -487,10 +559,24 @@ impl WindowManager { |
| 487 | 559 | } |
| 488 | 560 | |
| 489 | 561 | fn handle_motion_notify(&mut self, event: MotionNotifyEvent) -> Result<()> { |
| 490 | | - let Some(ref drag) = self.drag_state else { |
| 562 | + // If not in a drag, check for edge cursor changes on floating windows |
| 563 | + if self.drag_state.is_none() { |
| 564 | + let window = event.event; |
| 565 | + let is_managed = self.windows.contains_key(&window); |
| 566 | + let is_float = is_managed && self.is_floating(window); |
| 567 | + |
| 568 | + if is_float { |
| 569 | + tracing::trace!( |
| 570 | + "Motion on floating window {}: root({},{}) event({},{})", |
| 571 | + window, event.root_x, event.root_y, event.event_x, event.event_y |
| 572 | + ); |
| 573 | + self.update_edge_cursor(window, event.root_x, event.root_y)?; |
| 574 | + } |
| 491 | 575 | return Ok(()); |
| 492 | | - }; |
| 576 | + } |
| 493 | 577 | |
| 578 | + let drag = self.drag_state.as_ref().unwrap(); |
| 579 | + tracing::debug!("Motion during drag: root({},{}), drag_state={:?}", event.root_x, event.root_y, drag); |
| 494 | 580 | match drag { |
| 495 | 581 | DragState::Move { |
| 496 | 582 | window, |
@@ -531,6 +617,53 @@ impl WindowManager { |
| 531 | 617 | Ok(()) |
| 532 | 618 | } |
| 533 | 619 | |
| 620 | + /// Handle pointer motion for edge cursor changes on floating windows. |
| 621 | + fn update_edge_cursor(&mut self, window: u32, root_x: i16, root_y: i16) -> Result<()> { |
| 622 | + if !self.is_floating(window) { |
| 623 | + // Not a floating window, ensure cursor is normal |
| 624 | + if self.current_edge_cursor.is_some() { |
| 625 | + self.conn.set_window_cursor(window, self.conn.cursor_normal)?; |
| 626 | + self.current_edge_cursor = None; |
| 627 | + } |
| 628 | + return Ok(()); |
| 629 | + } |
| 630 | + |
| 631 | + let geometry = self.get_floating_geometry(window); |
| 632 | + let edge = determine_resize_edge(&geometry, root_x, root_y); |
| 633 | + |
| 634 | + // Check if we need to update the cursor |
| 635 | + let current = self.current_edge_cursor; |
| 636 | + if current.map(|(w, e)| (w, e)) == Some((window, edge)) { |
| 637 | + return Ok(()); // No change needed |
| 638 | + } |
| 639 | + |
| 640 | + // Get the appropriate cursor for this edge |
| 641 | + let cursor = match edge { |
| 642 | + ResizeEdge::TopLeft => self.conn.cursor_top_left, |
| 643 | + ResizeEdge::Top => self.conn.cursor_top, |
| 644 | + ResizeEdge::TopRight => self.conn.cursor_top_right, |
| 645 | + ResizeEdge::Left => self.conn.cursor_left, |
| 646 | + ResizeEdge::Right => self.conn.cursor_right, |
| 647 | + ResizeEdge::BottomLeft => self.conn.cursor_bottom_left, |
| 648 | + ResizeEdge::Bottom => self.conn.cursor_bottom, |
| 649 | + ResizeEdge::BottomRight => self.conn.cursor_bottom_right, |
| 650 | + ResizeEdge::None => self.conn.cursor_normal, |
| 651 | + }; |
| 652 | + |
| 653 | + // Set cursor on the window |
| 654 | + self.conn.set_window_cursor(window, cursor)?; |
| 655 | + self.conn.flush()?; |
| 656 | + |
| 657 | + if edge != ResizeEdge::None { |
| 658 | + tracing::debug!("Set resize cursor {:?} for floating window {} edge", edge, window); |
| 659 | + self.current_edge_cursor = Some((window, edge)); |
| 660 | + } else { |
| 661 | + self.current_edge_cursor = None; |
| 662 | + } |
| 663 | + |
| 664 | + Ok(()) |
| 665 | + } |
| 666 | + |
| 534 | 667 | fn handle_enter_notify(&mut self, event: EnterNotifyEvent) -> Result<()> { |
| 535 | 668 | let window = event.event; |
| 536 | 669 | |
@@ -563,14 +696,20 @@ impl WindowManager { |
| 563 | 696 | |
| 564 | 697 | tracing::debug!("Focus follows mouse: focusing window {}", window); |
| 565 | 698 | |
| 566 | | - // Regrab button on old focused window |
| 699 | + // Regrab button on old focused window (unless floating - keep for edge resize) |
| 567 | 700 | if let Some(old) = self.focused_window { |
| 568 | | - self.conn.grab_button(old)?; |
| 701 | + if !self.is_floating(old) { |
| 702 | + self.conn.grab_button(old)?; |
| 703 | + } |
| 569 | 704 | } |
| 570 | 705 | |
| 571 | 706 | // Focus the new window (no warp - mouse enter) |
| 572 | 707 | self.set_focus(window, false)?; |
| 573 | | - self.conn.ungrab_button(window)?; |
| 708 | + |
| 709 | + // Ungrab button for click-through (unless floating - keep for edge resize) |
| 710 | + if !self.is_floating(window) { |
| 711 | + self.conn.ungrab_button(window)?; |
| 712 | + } |
| 574 | 713 | |
| 575 | 714 | // Raise floating windows on focus |
| 576 | 715 | if self.is_floating(window) { |
@@ -968,11 +1107,17 @@ impl WindowManager { |
| 968 | 1107 | .or_else(|| self.workspaces[workspace_idx].floating.last().copied()) |
| 969 | 1108 | .or_else(|| self.workspaces[workspace_idx].tree.first_window()) |
| 970 | 1109 | { |
| 1110 | + // Regrab on old window (unless floating) |
| 971 | 1111 | if let Some(old) = self.focused_window { |
| 972 | | - self.conn.grab_button(old)?; |
| 1112 | + if !self.is_floating(old) { |
| 1113 | + self.conn.grab_button(old)?; |
| 1114 | + } |
| 973 | 1115 | } |
| 974 | 1116 | self.set_focus(window, true)?; |
| 975 | | - self.conn.ungrab_button(window)?; |
| 1117 | + // Ungrab on new window (unless floating - keep for edge resize) |
| 1118 | + if !self.is_floating(window) { |
| 1119 | + self.conn.ungrab_button(window)?; |
| 1120 | + } |
| 976 | 1121 | } else { |
| 977 | 1122 | // No windows on target monitor - clear focus and warp to monitor center |
| 978 | 1123 | self.focused_window = None; |
@@ -1646,11 +1791,17 @@ impl WindowManager { |
| 1646 | 1791 | .or_else(|| self.workspaces[workspace_idx].floating.last().copied()) |
| 1647 | 1792 | .or_else(|| self.workspaces[workspace_idx].tree.first_window()) |
| 1648 | 1793 | { |
| 1794 | + // Regrab on old window (unless floating) |
| 1649 | 1795 | if let Some(old) = self.focused_window { |
| 1650 | | - self.conn.grab_button(old)?; |
| 1796 | + if !self.is_floating(old) { |
| 1797 | + self.conn.grab_button(old)?; |
| 1798 | + } |
| 1651 | 1799 | } |
| 1652 | 1800 | self.set_focus(window, true)?; |
| 1653 | | - self.conn.ungrab_button(window)?; |
| 1801 | + // Ungrab on new window (unless floating - keep for edge resize) |
| 1802 | + if !self.is_floating(window) { |
| 1803 | + self.conn.ungrab_button(window)?; |
| 1804 | + } |
| 1654 | 1805 | } else { |
| 1655 | 1806 | // No windows - warp to monitor center |
| 1656 | 1807 | self.focused_window = None; |
@@ -1761,14 +1912,16 @@ impl WindowManager { |
| 1761 | 1912 | |
| 1762 | 1913 | let next_window = floating[next_idx]; |
| 1763 | 1914 | |
| 1764 | | - // Regrab button on old focused window |
| 1915 | + // Regrab button on old focused window (unless it's floating) |
| 1765 | 1916 | if let Some(old) = self.focused_window { |
| 1766 | | - self.conn.grab_button(old)?; |
| 1917 | + if !self.is_floating(old) { |
| 1918 | + self.conn.grab_button(old)?; |
| 1919 | + } |
| 1767 | 1920 | } |
| 1768 | 1921 | |
| 1769 | 1922 | // Focus and raise the next floating window (keyboard action, warp pointer) |
| 1923 | + // Keep button grabbed on floating windows for edge resize |
| 1770 | 1924 | self.set_focus(next_window, true)?; |
| 1771 | | - self.conn.ungrab_button(next_window)?; |
| 1772 | 1925 | self.raise_window(next_window)?; |
| 1773 | 1926 | |
| 1774 | 1927 | tracing::debug!("Cycled to floating window {} (idx {})", next_window, next_idx); |
@@ -1800,6 +1953,21 @@ impl WindowManager { |
| 1800 | 1953 | win.floating = false; |
| 1801 | 1954 | } |
| 1802 | 1955 | |
| 1956 | + // Remove POINTER_MOTION event mask (no longer need edge detection) |
| 1957 | + self.conn.select_input( |
| 1958 | + window, |
| 1959 | + EventMask::ENTER_WINDOW |
| 1960 | + | EventMask::FOCUS_CHANGE |
| 1961 | + | EventMask::PROPERTY_CHANGE |
| 1962 | + | EventMask::STRUCTURE_NOTIFY, |
| 1963 | + )?; |
| 1964 | + |
| 1965 | + // Clear edge cursor state if this window had one |
| 1966 | + if self.current_edge_cursor.map(|(w, _)| w) == Some(window) { |
| 1967 | + self.conn.set_window_cursor(window, self.conn.cursor_normal)?; |
| 1968 | + self.current_edge_cursor = None; |
| 1969 | + } |
| 1970 | + |
| 1803 | 1971 | // Remove from floating list |
| 1804 | 1972 | self.current_workspace_mut().remove_floating(window); |
| 1805 | 1973 | |
@@ -1842,6 +2010,20 @@ impl WindowManager { |
| 1842 | 2010 | win.floating_geometry = geometry; |
| 1843 | 2011 | } |
| 1844 | 2012 | |
| 2013 | + // Add POINTER_MOTION event mask for edge detection |
| 2014 | + self.conn.select_input( |
| 2015 | + window, |
| 2016 | + EventMask::ENTER_WINDOW |
| 2017 | + | EventMask::FOCUS_CHANGE |
| 2018 | + | EventMask::PROPERTY_CHANGE |
| 2019 | + | EventMask::STRUCTURE_NOTIFY |
| 2020 | + | EventMask::POINTER_MOTION, |
| 2021 | + )?; |
| 2022 | + |
| 2023 | + // Re-establish button grabs for edge resize detection |
| 2024 | + // (buttons may have been ungrabbed when the window was focused as tiled) |
| 2025 | + self.conn.grab_button(window)?; |
| 2026 | + |
| 1845 | 2027 | // Add to floating list (on top) |
| 1846 | 2028 | self.current_workspace_mut().add_floating(window); |
| 1847 | 2029 | |
@@ -2053,7 +2235,37 @@ fn parse_direction(s: &str) -> Option<Direction> { |
| 2053 | 2235 | } |
| 2054 | 2236 | } |
| 2055 | 2237 | |
| 2238 | +/// Threshold in pixels for detecting edge proximity |
| 2239 | +const EDGE_THRESHOLD: i16 = 12; |
| 2240 | + |
| 2241 | +/// Determine which edge/corner of a window a point is near. |
| 2242 | +/// Returns ResizeEdge::None if not near any edge. |
| 2056 | 2243 | fn determine_resize_edge(geometry: &Rect, click_x: i16, click_y: i16) -> ResizeEdge { |
| 2244 | + let left = geometry.x; |
| 2245 | + let right = geometry.x + geometry.width as i16; |
| 2246 | + let top = geometry.y; |
| 2247 | + let bottom = geometry.y + geometry.height as i16; |
| 2248 | + |
| 2249 | + let near_left = click_x >= left && click_x < left + EDGE_THRESHOLD; |
| 2250 | + let near_right = click_x > right - EDGE_THRESHOLD && click_x <= right; |
| 2251 | + let near_top = click_y >= top && click_y < top + EDGE_THRESHOLD; |
| 2252 | + let near_bottom = click_y > bottom - EDGE_THRESHOLD && click_y <= bottom; |
| 2253 | + |
| 2254 | + match (near_left, near_right, near_top, near_bottom) { |
| 2255 | + (true, _, true, _) => ResizeEdge::TopLeft, |
| 2256 | + (_, true, true, _) => ResizeEdge::TopRight, |
| 2257 | + (true, _, _, true) => ResizeEdge::BottomLeft, |
| 2258 | + (_, true, _, true) => ResizeEdge::BottomRight, |
| 2259 | + (true, _, _, _) => ResizeEdge::Left, |
| 2260 | + (_, true, _, _) => ResizeEdge::Right, |
| 2261 | + (_, _, true, _) => ResizeEdge::Top, |
| 2262 | + (_, _, _, true) => ResizeEdge::Bottom, |
| 2263 | + _ => ResizeEdge::None, |
| 2264 | + } |
| 2265 | +} |
| 2266 | + |
| 2267 | +/// Determine resize edge for mod+click (quadrant-based, always picks a corner) |
| 2268 | +fn determine_resize_edge_quadrant(geometry: &Rect, click_x: i16, click_y: i16) -> ResizeEdge { |
| 2057 | 2269 | let center_x = geometry.x + geometry.width as i16 / 2; |
| 2058 | 2270 | let center_y = geometry.y + geometry.height as i16 / 2; |
| 2059 | 2271 | |
@@ -2082,20 +2294,37 @@ fn calculate_resize(geometry: &Rect, edge: ResizeEdge, dx: i16, dy: i16) -> (i16 |
| 2082 | 2294 | w = (w as i16 - dx).max(MIN_SIZE as i16) as u16; |
| 2083 | 2295 | h = (h as i16 - dy).max(MIN_SIZE as i16) as u16; |
| 2084 | 2296 | } |
| 2297 | + ResizeEdge::Top => { |
| 2298 | + y += dy; |
| 2299 | + h = (h as i16 - dy).max(MIN_SIZE as i16) as u16; |
| 2300 | + } |
| 2085 | 2301 | ResizeEdge::TopRight => { |
| 2086 | 2302 | y += dy; |
| 2087 | 2303 | w = (w as i16 + dx).max(MIN_SIZE as i16) as u16; |
| 2088 | 2304 | h = (h as i16 - dy).max(MIN_SIZE as i16) as u16; |
| 2089 | 2305 | } |
| 2306 | + ResizeEdge::Left => { |
| 2307 | + x += dx; |
| 2308 | + w = (w as i16 - dx).max(MIN_SIZE as i16) as u16; |
| 2309 | + } |
| 2310 | + ResizeEdge::Right => { |
| 2311 | + w = (w as i16 + dx).max(MIN_SIZE as i16) as u16; |
| 2312 | + } |
| 2090 | 2313 | ResizeEdge::BottomLeft => { |
| 2091 | 2314 | x += dx; |
| 2092 | 2315 | w = (w as i16 - dx).max(MIN_SIZE as i16) as u16; |
| 2093 | 2316 | h = (h as i16 + dy).max(MIN_SIZE as i16) as u16; |
| 2094 | 2317 | } |
| 2318 | + ResizeEdge::Bottom => { |
| 2319 | + h = (h as i16 + dy).max(MIN_SIZE as i16) as u16; |
| 2320 | + } |
| 2095 | 2321 | ResizeEdge::BottomRight => { |
| 2096 | 2322 | w = (w as i16 + dx).max(MIN_SIZE as i16) as u16; |
| 2097 | 2323 | h = (h as i16 + dy).max(MIN_SIZE as i16) as u16; |
| 2098 | 2324 | } |
| 2325 | + ResizeEdge::None => { |
| 2326 | + // No resize |
| 2327 | + } |
| 2099 | 2328 | } |
| 2100 | 2329 | |
| 2101 | 2330 | (x, y, w, h) |