# Overlays

An overlay is an element in your tree that floats above its anchor. To show it, render it. To
hide it, stop rendering it. While it's open, the framework guarantees what happens to input.

> **Example: Overlays.** Source in [examples/overlays](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/overlays). Its page on the docs site shows it captured on macOS, Linux and Windows.


## The three kinds

`popover(id)`, `panel(id)`, and `tooltip()` come from the prelude. Each one 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. 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, and the panel stays | Sheet-like forms, quick pickers |
| `tooltip()` | Above its anchor | None, because it takes no input | Hints |

Those are the default modalities. To change one, call `.modality(..)` with `Modal`,
`LightDismiss`, or `Passive`. `Placement` is `Below`, `Above`, `Leading`, or `Trailing`. Panels
ignore it.

A popover hangs off the element that it's 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/FormalStudio/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 39.</sub>


A panel uses the same call. Because a panel 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/FormalStudio/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 51.</sub>


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

## Closing

You decide when an overlay closes. 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 lost activation). In the example, both handlers clear a
flag and call `cx.notify()`.

An overlay also closes when its anchor leaves the tree. It doesn't close when focus moves between
your own windows.

Every overlay that isn't passive is a focus scope. Opening it saves focus, and closing it hands
focus back. A modal overlay traps Tab. An Escape press that nothing inside handles closes the
innermost overlay.

## Context menus

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

```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/FormalStudio/NativeDesktop/blob/main/examples/overlays/src/main.rs), line 64.</sub>


`item_destructive` marks an item as destructive, and `submenu` nests a menu. On macOS, the menu is
an `NSMenu`. On other platforms, it's a drawn menu in the overlay layer. Notes attaches one to each
row of its list in [note_list.rs](https://github.com/FormalStudio/NativeDesktop/blob/main/examples/notes/src/note_list.rs).

## Input never leaks

Input inside an overlay (a press, a scroll, or a hover) never reaches what's under it. That
includes native views, such as a `WKWebView`, a CEF view, or a GPU surface. The example puts a
native button and a live web page under every overlay and counts the input that each one
receives. The gate fails if a count changes.

How the framework enforces this 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.
  While any overlay is open, one event monitor applies the outside-press and Escape rules.
- Linux, Windows, and 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.

On every OS, a press outside a light-dismiss overlay closes it and is consumed, release included.
The element under the pointer sees nothing. macOS menus already behave this way.

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

Native surfaces that the OS composites above GPUI (WebView2, WPE) aren't covered yet. They need
either the overlay composition surface under `nd-gpui/fast` or a child popup window, which lands
with those engines. For details, see [webview](https://native.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's a controlled element: 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/FormalStudio/NativeDesktop/blob/main/examples/notes/src/app.rs), line 216.</sub>


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

## Toasts

`toast_overlay(id)` shows one window's toasts at the bottom, one at a time. You own the list:
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/FormalStudio/NativeDesktop/blob/main/examples/gallery/src/lib.rs), line 425.</sub>


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

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