Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom titlebars

An undecorated Webview has no native titlebar and no native frame, and gets two things in their place: drag regions, which move the window, and resize edges, which resize it.

wv = Webview(app_id='com.example.myapp', decorations=False, html=HTML)

Drag regions

An element marked data-drag-region moves the window when dragged.

<div data-drag-region>
    <h1>My Application</h1>
    <button data-no-drag-region onclick="window.dry.minimize()">–</button>
    <button data-no-drag-region onclick="window.dry.toggleMaximize()">□</button>
    <button data-no-drag-region onclick="window.dry.close()">×</button>
</div>

The whole subtree drags. The heading above moves the window exactly as the bare margin around it does. An element marked data-no-drag-region opts itself and its own subtree back out, which is what the buttons inside a titlebar want: a click reaches them instead of moving the window.

The nearest marked ancestor wins, so an opt-out nested inside a drag region is honoured, and a drag region nested inside an opt-out drags again.

A double click inside a drag region toggles maximize, as a native titlebar does. A drag only starts once the pointer has actually moved, so a click inside a drag region stays a click.

Only the primary mouse button drags.

Resize edges

An undecorated Webview draws eight thin strips over its own border — 3px along each side, 7px at each corner — each with the cursor the direction implies. A grab on one resizes the window from that edge or corner.

They are ordinary fixed-position elements at z-index: 9999, and each carries data-no-drag-region, so a grab on the top edge resizes rather than moving the window even when the titlebar sits right behind it.

The mechanism differs by platform: Windows hands the grab to the operating system, which takes it over with a modal loop of its own, while macOS has no such path — tao answers NotSupported — so the frontend runs the drag itself and reports the pointer to Rust on every move. The behaviour is the same either way. The reasoning is in ADR-0004.

Window controls

<button onclick="window.dry.minimize()">Minimize</button>
<button onclick="window.dry.toggleMaximize()">Maximize</button>
<button onclick="window.dry.close()">Close</button>

These work with or without decorations. window.dry.close() goes through the close hook exactly as the native titlebar button does.

There is also window.dry.drag(), which starts a window drag from a handler of your own, and window.dry.resize(direction) for the eight directions — 'north', 'north-east', 'east', 'south-east', 'south', 'south-west', 'west', 'north-west' — if you would rather draw your own edges.

Keeping the titlebar in sync

A titlebar that can only command the window keeps its own guess at whether the window is maximized, and that guess is wrong the first time the user double-clicks the bar or reaches for an OS shortcut. Listen instead:

<script>
    window.dry.on('window:maximized', () => icon.src = RESTORE);
    window.dry.on('window:unmaximized', () => icon.src = MAXIMIZE);
</script>

A page that has just loaded has heard no Event yet, so ask once on the way in:

const { maximized } = await window.dry.state();
icon.src = maximized ? RESTORE : MAXIMIZE;

See Window Events and Runtime window control. A full example is examples/titlebar.py.