iced_borderless_window_resize_handle_widget 0.1.1

A lightweight iced overlay widget that restores edge and corner resizing for borderless windows using native OS resize calls.
docs.rs failed to build iced_borderless_window_resize_handle_widget-0.1.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

iced-borderless-window-resize-handle-widget

Invisible edge & corner resize handles for borderless iced windows.

Setting decorations: false in iced removes the OS window frame — including its native resize borders, which makes frameless windows impossible to resize with the mouse. This crate restores them with a single lightweight overlay widget.

Pressing a handle hands the drag to the operating system's own resize machinery via drag_resize, so resizing looks, feels, and behaves natively:

Platform Mechanism
Windows Native size loop (WM_NCLBUTTONDOWN + HT* hit-test codes). No WM_NCHITTEST/WS_THICKFRAME hacks, so window snapping is untouched. The resize cursor is also kept visible for the whole drag (a Windows modal-loop quirk — see the crate docs).
Linux (X11) _NET_WM_MOVERESIZE — the window manager drives the resize.
Linux (Wayland) xdg_toplevel.resize — the compositor drives the resize.
macOS Inert. winit borderless windows already keep NSWindowStyleMask::Resizable, so macOS edge-resizes them natively.

The widget draws nothing and keeps no state — it only watches the borders of its layout bounds, shows the native resize cursors on hover, and captures clicks within its zones.

Installation

[dependencies]
iced = "0.14"  # any feature set works — the widget is renderer-agnostic
iced_borderless_window_resize_handle_widget = "0.1"

The widget only needs iced's advanced feature (which many apps already enable) and forces no renderer or executor choices on you. Your app's binary keeps exactly the feature footprint you configured.

Usage

Layer the handles on top of your UI in a stack and forward the message to this crate's drag_resize (a drop-in replacement for iced::window::drag_resize that also keeps the native resize cursor visible during the whole drag on Windows):

use iced::widget::stack;
use iced::window;
use iced_borderless_window_resize_handle_widget::{drag_resize, ResizeHandles};

#[derive(Debug, Clone)]
enum Message {
    WindowOpened(Option<window::Id>),
    Resized(window::Direction),
}

fn view(state: &App) -> iced::Element<'_, Message> {
    stack![
        my_ui(state),
        ResizeHandles::new(Message::Resized),
    ]
    .width(iced::Fill)
    .height(iced::Fill)
    .into()
}

fn update(app: &mut App, message: Message) -> iced::Task<Message> {
    match message {
        Message::WindowOpened(id) => {
            app.window_id = id;
            iced::Task::none()
        }
        Message::Resized(direction) => app
            .window_id
            .map(|id| drag_resize(id, direction))
            .unwrap_or_else(iced::Task::none),
    }
}

iced has no window::Id::MAIN; capture the id of your main window once at boot:

fn boot() -> (App, iced::Task<Message>) {
    (App::default(), window::latest().map(Message::WindowOpened))
}

If your message enum implements From<window::Direction>, you can skip the closure:

impl From<window::Direction> for Message {
    fn from(direction: window::Direction) -> Self {
        Message::Resized(direction)
    }
}

// later, in `view`:
stack![my_ui(state), ResizeHandles::from_direction()]

Or wrap any element in one call:

with_resize_handles(my_ui(state), Message::Resized)

Options

ResizeHandles::new(Message::Resized)
    .gutter(6.0)   // edge zone thickness (logical px, default 6)
    .corner(14.0)  // corner zone size — corners take precedence (default 14)
    .enabled(true) // e.g. `.enabled(!is_maximized)` to disable while maximized

Notes

  • The grab zones consume clicks in the outermost gutter pixels of the window — just like the invisible border of a native frame. Don't put interactive widgets flush against the window edges.
  • Resizing a maximized window is ignored by the OS; gate the handles with .enabled(...) if you prefer.
  • In multi-window apps, pass each window's window::Id into the message closure.

Run the demo with:

cargo run --example basic