Window management
Every laufey application is built around one or more native windows. A Window
controls its title, size, position, resizable and always-on-top flags, opacity,
click passthrough, visibility, and focus. The type is a builder, so you can
configure a window fluently when you create it, and each property also has a
plain setter you can call later while the window is open.
#![allow(unused)]
fn main() {
use laufey::Window;
let win = Window::new(800, 600)
.title("My App")
.position(100, 100)
.resizable(true)
.opacity(0.95)
.load("index.html"); // or .navigate("https://example.com")
win.set_size(1024, 768);
let (width, height) = win.get_size();
win.focus();
win.hide();
}
A few properties can only be chosen when the operating system creates the window
and cannot be changed afterwards: whether the window is frameless (drawn without
operating-system chrome), whether it is a non-activating panel that does not
steal keyboard focus, and whether it has a transparent background. You set those
through Window::new_with_options. Everything else is a live setter. All
positions and sizes are expressed in density-independent pixels with the origin
at the top-left of the screen. The Winit backend can create and manage windows,
but because it has no web engine it cannot navigate to a URL or execute
JavaScript.
Opacity and transparency
These are two distinct things:
-
Opacity (
Window::opacity/set_opacity/get_opacity) fades the entire window — web content and native chrome alike — by a uniform factor in0.0..=1.0, where1.0is fully opaque (the default), like CSSopacityon the whole window. It is a live setter you can animate at runtime. The web backends implement it on every desktop platform (macOSNSWindow.alphaValue, Windows layered-window alpha, Linuxgtk_widget_set_opacity). The Winit backend has no opacity API, so the call is a no-op there andget_opacityreturns1.0.#![allow(unused)] fn main() { win.set_opacity(0.8); // 80% opaque } -
Transparency (
WindowOptions::transparent) gives the window a transparent background so the web content’s own alpha composites against whatever is behind the window. Any region the page leaves transparent (e.g. atransparentroot background) shows the desktop through it. This must be chosen at creation time and is commonly paired withframeless.#![allow(unused)] fn main() { use laufey::{Window, WindowOptions}; let win = Window::new_with_options( 400, 300, WindowOptions { frameless: true, transparent: true, ..Default::default() }, ) .load("index.html"); }Transparency is supported by the system-WebView backend on macOS and on Linux (WebKitGTK, on a compositing window manager), and by the Winit backend. It is not supported by the Windows WebView2 backend or the CEF backend, which paint an opaque window background; the flag is ignored there.
Click passthrough
Window::click_passthrough / set_click_passthrough / get_click_passthrough
makes the window ignore all mouse input — clicks, moves, and wheel events fall
through to whatever window is beneath it, like Electron’s
setIgnoreMouseEvents(true). Keyboard input is unaffected. It is a live setter
you can toggle at any time, intended for frameless/transparent overlay windows:
HUDs, notification toasts, screen annotations.
#![allow(unused)]
fn main() {
use laufey::{Window, WindowOptions};
let overlay = Window::new_with_options(
400,
300,
WindowOptions { frameless: true, transparent: true, ..Default::default() },
)
.always_on_top(true)
.click_passthrough(true)
.load("overlay.html");
// Later, to start accepting input again:
overlay.set_click_passthrough(false);
}
Platform notes:
- macOS —
NSWindow.ignoresMouseEvents; works with every backend. - Windows — the top-level window gets the
WS_EX_TRANSPARENTandWS_EX_LAYEREDextended styles, which exclude it (children included) from mouse hit-testing. Composes withset_opacity, which shares the layered style. - Linux — the window’s X11/Wayland input shape region is cleared. Best-effort under a reparenting X11 window manager, where the WM’s frame may still catch clicks — pair it with a frameless window (the intended overlay use case) for reliable behavior.
- The Winit backend uses winit’s
set_cursor_hittest, with the same platform behavior as above.
Forwarding: passthrough, but still observing events
Window::click_passthrough_forward / set_click_passthrough_forward /
get_click_passthrough_forward keeps the window’s mouse events flowing to your
registered on_mouse_move / on_mouse_click / on_wheel handlers while
passthrough is active — like Electron’s
setIgnoreMouseEvents(true, { forward: true }). The OS still delivers every
event to the window beneath; forwarding is observation only, sourced from a
global input observer that hit-tests the overlay’s frame. While passthrough is
disabled the flag has no effect, because normal per-window delivery already
fires the handlers.
You cannot selectively consume a forwarded event — hit-testing is decided by the OS before your handler runs. The standard interactive-overlay pattern instead toggles passthrough just-in-time: watch forwarded mouse moves and disable passthrough when the cursor enters an interactive region, re-enable it on leave.
#![allow(unused)]
fn main() {
let overlay = Window::new_with_options(
400,
300,
WindowOptions { frameless: true, transparent: true, ..Default::default() },
)
.always_on_top(true)
.click_passthrough(true)
.click_passthrough_forward(true)
.on_mouse_move(move |ev| {
// e.g. flip set_click_passthrough(false) when (ev.x, ev.y) is over a button
})
.load("overlay.html");
}
Platform support: implemented on macOS (an NSEvent global monitor —
observing mouse events needs no extra permission) for the WebView and CEF
backends. Windows (a WH_MOUSE_LL hook), Linux/X11 (XInput2 raw
events), and the Winit backend are not implemented yet and ignore the flag (the
getter reports false); Linux/Wayland cannot support it — the compositor
does not expose global input. One macOS caveat: global monitors never see events
delivered to your own application, so if the event lands on another
(non-passthrough) window of the same app that overlaps the overlay, the
overlay’s handlers do not fire for it.