This document describes the custom window resize functionality implemented for Ferrite's borderless window mode. Since the application uses with_decorations(false) to provide a custom title bar, native OS resize handles are not available. This module provides custom edge/corner detection and resize initiation.
- Source:
src/ui/window.rs - Integration:
src/app.rs
Tracks the current resize operation state:
pub struct WindowResizeState {
current_direction: Option<ResizeDirection>,
is_resizing: bool,
}current_direction: The detected resize direction based on mouse position (None if not near an edge)is_resizing: Whether a resize operation is currently active (mouse held down)
The main entry point called at the start of each frame:
- Early exit if maximized: Resize is disabled when window is maximized
- Get window rect and pointer position: Required for edge detection
- Continue active resize: If a resize is in progress, maintain state until mouse is released
- Detect resize direction: Check if pointer is near any edge/corner
- Set cursor icon: Change cursor to indicate resize capability
- Initiate resize: On mouse press, send
ViewportCommand::BeginResize(direction)to egui
The implementation defines two key constants:
RESIZE_BORDER_WIDTH(5px): The width of edge resize zonesCORNER_GRAB_SIZE(10px): The size of corner grab areas (larger for easier targeting)
The detect_resize_direction() function:
- Checks proximity to each edge (left, right, top, bottom)
- Prioritizes corners over edges (corners are checked first)
- Returns the appropriate
ResizeDirectionvariant
Supported directions:
North,South,East,West(edges)NorthEast,NorthWest,SouthEast,SouthWest(corners)
Each direction maps to a corresponding cursor icon:
| Direction | Cursor Icon |
|---|---|
| North | ResizeNorth (↑) |
| South | ResizeSouth (↓) |
| East | ResizeEast (→) |
| West | ResizeWest (←) |
| NorthEast | ResizeNorthEast (↗) |
| NorthWest | ResizeNorthWest (↖) |
| SouthEast | ResizeSouthEast (↘) |
| SouthWest | ResizeSouthWest (↙) |
The title bar drag-to-move functionality is updated to defer to resize handling:
// Handle drag to move window (but not if we're in a resize zone)
let is_in_resize = self.window_resize_state.current_direction().is_some()
|| self.window_resize_state.is_resizing();
if drag_response.dragged() && !is_in_resize {
ctx.send_viewport_cmd(egui::ViewportCommand::StartDrag);
}This prevents conflicts between window move and resize operations when the mouse is near edges.
The resize handling must be called early in the frame, before any UI rendering:
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Handle window resize for borderless window (must be early, before UI)
handle_window_resize(ctx, &mut self.window_resize_state);
// ... rest of UI rendering
// Block edge widgets from stealing resize clicks (end of frame, Foreground order)
consume_clicks_in_resize_zones(ctx, &self.window_resize_state);
self.window_resize_state.apply_cursor(ctx);
}This ensures:
- Resize state is updated before UI checks it
- Cursor icon changes are applied immediately
- Resize commands are sent before any UI interaction
- Foreground click guards (wider than the 5px cursor zone) win hit-testing over edge UI such as the side-panel toggle strip and Help button
Do not fix resize/click conflicts by moving buttons away from edges. Use:
WindowResizeState::blocks_widget_clicks()— true while the resize cursor is shown, during resize, or for one frame after release over a grab zoneconsume_clicks_in_resize_zones()— paints invisible Foreground hit targets usingget_click_block_zone_rect()(24px on east/west vs. 5px detection)
- Windows: Fully supported via winit/eframe integration
- macOS: Should work similarly (winit handles platform specifics)
- Linux: Supported on X11 and Wayland (winit handles platform specifics)
Note: Platform-specific behavior may vary slightly depending on the window manager.
- Cursor changes to resize icons when hovering edges
- Cursor changes to diagonal icons when hovering corners
- All 8 resize directions work correctly
- Minimum window size is enforced (400x300)
- Resize is disabled when window is maximized
- Title bar drag-to-move works in center area
- Title bar does not initiate move when dragging from edges
- Double-click on title bar still toggles maximize
- Smooth 60fps performance during resize
/// Handle window resize for borderless windows.
/// Returns `true` if a resize operation was initiated.
pub fn handle_window_resize(ctx: &egui::Context, state: &mut WindowResizeState) -> bool
/// Check if a pointer position is within the resize border zone.
pub fn is_in_resize_zone(window_rect: Rect, pointer_pos: Pos2) -> bool
/// Get the resize zone rectangle for a given edge.
pub fn get_resize_zone_rect(window_rect: Rect, edge: ResizeDirection) -> Rect/// Create a new resize state.
pub fn new() -> Self
/// Check if currently resizing.
pub fn is_resizing(&self) -> bool
/// Get current resize direction.
pub fn current_direction(&self) -> Option<ResizeDirection>eframe::egui- UI framework with viewport commandsegui::ResizeDirection- Direction enum for resize operationsegui::ViewportCommand::BeginResize- Command to initiate native resize