# Overlays

An overlay is an element in your tree that floats above its anchor. You show it by rendering it
and hide it by not rendering it. The framework guarantees what happens to input while it is open.

> **Example: Overlays.** Source in [examples/overlays](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/overlays). Live on the web and captured on macOS, Linux and Windows in the docs app.


## The three kinds

`popover(id)`, `panel(id)` and `tooltip()` come from the prelude. Each is a container: add
children as content and size it with the usual style methods. An overlay takes no room in its
parent's layout, and its parent is its anchor, so it closes with the anchor's subtree.

| Element | Placed | Outside presses | Use |
| --- | --- | --- | --- |
| `popover(id)` | below its anchor (`.placement(..)` moves it) | close it, and are consumed | menus of controls, pickers |
| `panel(id)` | over the window, near the top | consumed, panel stays | sheet-like forms, quick pickers |
| `tooltip()` | above its anchor | none: it takes no input | hints |

Those are the default modalities. `.modality(..)` changes one: `Modal`, `LightDismiss` or
`Passive`. `Placement` is `Below`, `Above`, `Leading` or `Trailing`; panels ignore it.

A popover hangs off the element it is a child of:

```rust
/// Anchored to the element it is a child of; `on_dismiss` is where the app closes it.
fn popover_view(&self, cx: &mut Context<Self>) -> impl IntoElement {
    popover("pop").w(px(300.)).h(px(200.)).p(px(12.)).child(text("Clicks in here stay in here.")).on_dismiss(
        cx.listener(|this, _: &Dismissed, _, cx| {
            this.popover = false;
            cx.notify();
        }),
    )
}
```

<sub>From [examples/overlays/src/main.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 39.</sub>


A panel is the same call. Because it is modal, the rest of the window stays out of reach until
the app drops it:

```rust
/// Modal: presses outside are consumed and the panel stays until Esc or the app closes it.
fn panel_view(&self, cx: &mut Context<Self>) -> impl IntoElement {
    panel("panel").w(px(260.)).h(px(120.)).p(px(12.)).child(text("A modal panel. Esc closes it.")).on_dismiss(
        cx.listener(|this, _: &Dismissed, _, cx| {
            this.panel = false;
            cx.notify();
        }),
    )
}
```

<sub>From [examples/overlays/src/main.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 51.</sub>


Most controls have a shortcut for tooltips, `.tooltip("Command palette")` on a button or icon
button. Use the `tooltip()` element when the hint needs content of its own.

## Closing

Dismissal is yours. An overlay never removes itself; it reports `Dismissed { reason }` to
`on_dismiss` and you stop rendering it. The reason is `Escape`, `OutsidePress` or
`Deactivated`, the app losing activation. In the example both handlers clear a flag and call
`cx.notify()`.

An overlay also closes when its anchor leaves the tree. It does not close when focus moves
between your own windows.

Every non-passive overlay is a focus scope. Opening saves focus, closing hands it back, a modal
one traps Tab, and an Escape that nothing inside took closes the innermost overlay.

## Context menus

`.context_menu(build)` on any element adds a native menu on right click, control-click or the
menu key. The closure runs when the menu opens, so the items can depend on current state. Each
item dispatches an action, which reaches the focused view like a key binding would.

```rust
/// Built when the menu opens; each item dispatches an action.
fn menu_items(menu: ContextMenu, _window: &mut Window, _cx: &mut App) -> ContextMenu {
    menu.item("Choose", Choose).separator().item("Again", Choose)
}
```

<sub>From [examples/overlays/src/main.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 64.</sub>


`item_destructive` marks an item as destructive and `submenu` nests a menu. The menu is an
`NSMenu` on macOS and a drawn menu in the overlay layer elsewhere. Notes attaches one to each
row of its list in [note_list.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/notes/src/note_list.rs).

## Input never leaks

Input inside an overlay, a press, a scroll or hover, never reaches what lies under it. That
includes native views: a `WKWebView`, a CEF view, a GPU surface. The example puts a native
button and a live web page under every overlay and counts what each received, and the gate
fails if a count moves.

How it holds depends on the OS:

- macOS: each overlay is its own window above the owner, so the window server routes input and
  nothing relies on hit-testing inside one window. This matters for hover. Tracking areas fire
  for covered views, so an overlay drawn inside the window would still make a web view's
  `:hover` flicker. Popovers are `NSPopover`, panels and tooltips are borderless child panels
  that never become key, so the owner keeps its key chrome and keys keep reaching nd's dispatch.
  One event monitor applies the outside-press and Escape rules while any overlay is open.
- Linux, Windows, web: overlays draw in GPUI's deferred layer, above their parents, with a
  transparent backdrop that consumes presses. A light-dismiss backdrop lets scrolls through.
  Gesture hit testing starts inside the topmost overlay or hits nothing.

A press outside a light-dismiss overlay closes it and is consumed, release included, on every
OS. The element under the pointer sees nothing. This matches what macOS menus already do.

Declared accelerators run through the main menu before the window's views, so an embedded page
cannot swallow a shortcut while an overlay is up.

Not done: native surfaces that the OS composites above GPUI (WebView2, WPE) need either the
overlay composition surface under `nd-gpui/fast` or a child popup window. That lands with those
engines, see [webview](https://nativedesktop.formalsnake.dev/guides/webview.md). The leak gate runs on macOS and on Hyprland.

## Command palette

`command_palette(id)` is a glass card near the top of the window with a text field, ranked
rows and keyboard navigation. It is controlled: you pass the open flag, the query and the rows,
and you react to the events. Notes puts it in the scaffold's overlay slot:

```rust
.overlay(
    command_palette("palette")
        .open(self.palette_open)
        .placeholder("Search notes and commands")
        .query(self.palette_query.clone())
        .items(palette_items)
        .on_query_change(cx.listener(|this, e: &PaletteQueryChanged, _window, cx| {
            this.palette_query = e.text.clone();
            cx.notify();
        }))
        .on_activate(cx.listener(Self::palette_activate))
        .on_submit(cx.listener(|this, e: &PaletteSubmitted, window, cx| {
            // Nothing matched: typed text becomes a new note.
            this.palette_open = false;
            cx.notify();
            let id = this.library.update(cx, |library, cx| {
                let id = library.create(cx);
                library.set_body(id, e.text.to_string(), cx);
                id
            });
            this.select(id, window, cx);
        }))
        .on_cancel(cx.listener(|this, _: &PaletteCancelled, _window, cx| {
            this.palette_open = false;
            cx.notify();
        })),
)
```

<sub>From [examples/notes/src/app.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/notes/src/app.rs), line 224.</sub>


Rows are `palette_item(id, title)` with `.subtitle`, `.icon`, `.hint` (a shortcut label) and
`.completion`, the text the field completes inline when the query is a prefix. Notes ranks
notes first, then commands, so a title that starts with the query offers itself. The palette
shows exactly the rows you give it, in order; filtering is yours, and
[`fuzzy.rs`](https://github.com/FormalSnake/NativeDesktop/blob/main/crates/nd-components/src/fuzzy.rs) has the ranker notes uses.
`on_submit` fires when nothing matched, with the typed text.

## Toasts

`toast_overlay(id)` shows one window's toasts, one at a time, at the bottom. The list is yours:
append a `toast(id, title)` to show one and remove it when `on_dismiss` fires.

```rust
toast_overlay("toasts")
    .toasts(self.toasts.clone())
    .on_dismiss(cx.listener(|this, e: &ToastDismissed, _, cx| {
        this.toasts.retain(|t| t.id != e.id);
        cx.notify();
    }))
    .on_action(cx.listener(|this, _: &ToastActionClicked, _, cx| {
        this.last_event = "Undo clicked".into();
        cx.notify();
    })),
```

<sub>From [examples/gallery/src/lib.rs](https://github.com/FormalSnake/NativeDesktop/blob/main/examples/gallery/src/lib.rs), line 432.</sub>


A toast goes away after 5 seconds. `.persistent()` keeps it until dismissed, `.timeout(..)`
changes the time, and `.action("Undo")` adds one button whose click arrives at `on_action`
before the dismissal. A `.high_priority()` toast interrupts the one on screen, which is
dismissed with reason `Interrupted`; normal ones queue in order.

The gallery has a page each for the palette and for toasts.
