Skip to content

Latest commit

 

History

History
159 lines (120 loc) · 8.6 KB

File metadata and controls

159 lines (120 loc) · 8.6 KB

windows-window

Minimal window creation and message-loop support for content hosted by a raw HWND.

When to use it

Use windows-window when a Windows desktop application needs a small top-level window and message loop to host a swap chain, WebView2 controller, Direct2D or Direct3D renderer, or another API that accepts an HWND. It avoids depending on the full windows crate or generating application-specific bindings for basic windowing.

The crate is not a general UI toolkit. Menus, controls, input policy, multi-window coordination, and specialized message handling remain the application's responsibility.

Prerequisites

The crate targets Windows desktop applications. Create and drive a window on the thread that owns its message queue. Interop code receiving Window::hwnd() must not retain the handle beyond the Window lifetime.

Window creation attempts to set process DPI awareness to per-monitor v2. Set any different process DPI policy before creating a Window.

The README contains dependency setup and the minimal create-and-run example.

First workflow: host resize-sensitive content

Most integrations need the following sequence:

  1. Put shared renderer or controller state behind Rc<RefCell<_>> or another UI-thread owner.
  2. Build the window with on_resize and on_close closures that update and release the hosted content.
  3. Call create, then use client_size for the initial content size.
  4. Pass hwnd to the hosting API.
  5. Choose run for an event-driven host or run_with for a render loop.
  6. Drop hosted resources before the Window when their API requires the parent handle to remain valid.

on_resize receives client-area width and height in physical pixels. The callback also handles the initial resize messages that arrive after the builder installs its state.

Window creation and ownership

Window::new(title) returns a WindowBuilder. size sets the initial outer window size, while client_size sets the size excluding non-client borders. style and ex_style replace the defaults with raw WS_* and WS_EX_* values. The defaults are WS_OVERLAPPEDWINDOW and no extended style. no_redirection_bitmap adds WS_EX_NOREDIRECTIONBITMAP for content supplied by composition. visible(false) creates a hidden top-level window that can receive messages for integrations such as notification-area icons.

on_message receives (hwnd, message, wparam, lparam) and returns Option<isize>. Return Some(result) only when the application fully handled the message. Return None to use the crate's built-in handling and DefWindowProcW. on_resize and on_move are focused alternatives for WM_SIZE and WM_MOVE. If on_message handles either message, its focused callback does not run.

on_close runs before default WM_CLOSE processing destroys the window. Use it to close or drop hosted resources whose APIs require a live parent HWND. If on_message handles WM_CLOSE, on_close does not run.

quit_on_close controls whether closing the window posts WM_QUIT. It defaults to true for single-window applications. Dropping a window does not post WM_QUIT, so cleanup and failed creation cannot terminate an application-owned loop. Set the option to false when window lifetime and application lifetime differ, then call quit explicitly when the application should exit.

create registers the shared window class, creates the window, shows it when configured as visible, and returns an error if creation fails. After native destruction, Window::hwnd returns null and Window::client_size returns (0, 0). Window::close sends WM_CLOSE through the configured close and quit behavior. Dropping a live Window calls DestroyWindow directly without posting WM_QUIT.

Choosing a message loop

API Use it when Behavior
run() Event-driven updates. Blocks in GetMessageW until quit.
run_with(render) Consecutive frames may be needed. Drains messages, then calls render.
pump() An external operation owns the wait. Dispatches pending messages; never blocks.
quit() Application state requires loop termination. Posts WM_QUIT to the current thread.

The run_with closure returns Result<bool>. Return Ok(true) to request another immediate frame, or Ok(false) to block until a message arrives. Propagating an error exits the loop. This lets a renderer switch between animation and an idle or occluded state without busy-waiting.

pump returns false after consuming WM_QUIT; the caller should then stop its outer loop. Repeatedly calling pump without another wait mechanism spins the CPU.

Messages, reentrancy, and panics

Message dispatch is reentrant: a handler can call a Win32 API that sends another message before the first callback returns. The crate temporarily removes user handlers while one runs. Nested messages therefore use default processing instead of re-entering a closure or borrowing its captured RefCell again. A nested WM_CLOSE is deferred until the active handler returns so hosted resources can shut down before the native window is destroyed.

This also means a nested WM_SIZE triggered inside a handler does not invoke on_resize. Apply any state update needed by that synchronous operation directly.

Handlers run across an extern "system" window-procedure boundary without catch_unwind. A panic that escapes a handler aborts the process rather than unwinding through Win32. Return errors through captured application state or catch a panic inside the closure if recovery is required.

Do not perform long blocking work in a message handler. It prevents painting, input, timers, and other components on the same UI thread from progressing.

Interop and common options

  • Use hwnd() only with APIs that accept a borrowed parent or target handle.
  • Use client_size() after creation to size the initial swap chain or child content.
  • Forward resize callbacks to Controller::set_bounds for windows-webview, or resize the relevant swap-chain buffers.
  • A composition host can use no_redirection_bitmap() to avoid allocating an unused DWM redirection surface.
  • Raw input, paint, keyboard, mouse, DPI, and position behavior can be implemented through on_message. This crate intentionally does not project message-specific argument types.

Samples

Sample What to study
window-message Creation and message handling.
standalone Swap-chain hosting and resize flow.
direct2d Rendering only while visible.
direct3d12 Binding a swap chain to the handle.
dcomp Composition, custom style, and DPI.
webview-minimal Controller lifetime and resize flow.

Internal documentation

This section is for contributors to windows-window.

src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/window.txt. It contains the minimal flat Win32 surface needed for class registration, creation, DPI setup, destruction, and message dispatch. The hand-written window.rs depends only on windows-core.

One class is registered lazily for the process. A boxed state containing optional message, resize, move, and close handlers and a shared liveness bit is stored in GWLP_USERDATA after CreateWindowExW. wndproc clears the bit and removes the state on WM_NCDESTROY. Window::drop destroys only the original native window while it remains live. This prevents a late drop from acting on an HWND value that Windows has recycled for another window.

Before invoking a callback, wndproc takes all handlers out of state. After the callback it reads GWLP_USERDATA again because synchronous handling may have destroyed the window and freed the state. It restores the handlers only when the state still exists. Keep this ordering when changing dispatch behavior.

The crate does not catch panics in wndproc, add message-specific wrappers, or coordinate several top-level windows. Those boundaries keep the crate small and its ownership rules explicit.