tui-pages 0.7.2

Core for TUI apps with multiple pages
Documentation
# Dialogs

A built-in modal dialog, behind the `dialog` feature. It's the one piece of
rendering the crate ships, because a yes/no confirmation is the same everywhere
and not worth rewriting per app. Everything here is the `minimal_dialog` example.

```toml
tui-pages = { version = "0.7", features = ["dialog"] }
```

## How it fits

The dialog rides on the runtime's modal payload slot, `M`. You set `M` to
`DialogData<YourPurpose>`, where `YourPurpose` is your own "which dialog is
this" type:

```rust
// O = () (no named overlays), M = DialogData<Purpose>
pub type App = TuiApp<View, Action, AppState, Handler, (), DialogData<Purpose>>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Purpose {
    ConfirmDelete,
}
```

The focus manager stores the dialog content and tracks which button is active.
Your handler opens the dialog; a one-line helper drives it; you act on the
result.

## Opening a dialog

Build a `DialogData` and turn it into a focus effect with `show_intent()`:

```rust
Action::Select => match ctx.focus {
    Some(FocusTarget::Button(0)) => {
        let dialog = DialogData::new(
            "Delete an item?",                          // title
            format!("Delete \"{first}\"?"),             // message (may be multi-line)
            ["Delete", "Cancel"],                       // buttons, left to right
            Purpose::ConfirmDelete,                     // your purpose payload
        );
        ActionOutcome::effect(TuiEffect::Focus(dialog.show_intent()))
    }
    _ => ActionOutcome::none(),
}
```

There's also `DialogData::loading(title, message)` for a buttonless "please
wait" modal — swap it for a real `DialogData::new` (via another `show_intent`)
when the work finishes.

## Driving it in the event loop

`dialog::handle_key` does the conventional bindings for you and closes the dialog
when answered. It returns a `DialogKey<D>` — **no `Result`**, no `?`:

```rust
match dialog::handle_key(&mut tui.focus, key) {
    // No dialog open — pass the key through to normal handling.
    DialogKey::Ignored => {
        if tui.handle_key(key, state)?.quit_requested {
            return Ok(());
        }
    }
    // Dialog absorbed the key (e.g. moved between buttons). Nothing to do.
    DialogKey::Consumed => {}
    // Dialog finished — act on the answer.
    DialogKey::Resolved(result) => apply_dialog(result, state),
}
```

The conventional bindings it applies:

| Key | Effect |
|-----|--------|
| `Tab` / `` | next button |
| `Shift+Tab` / `` | previous button |
| `Enter` | choose the active button |
| `Esc` | dismiss |

(Want different bindings? Skip the helper and drive it yourself with
`dialog::current_dialog`, `dialog::active_button`, `dialog::selection`, and
`FocusIntent`. The helper is just the common path.)

## The result

```rust
pub enum DialogResult<D> {
    Selected { purpose: Option<D>, index: usize }, // which button (0-based)
    Dismissed,                                      // Esc, or a loading dialog
}

fn apply_dialog(result: DialogResult<Purpose>, state: &mut AppState) {
    match result {
        DialogResult::Selected { purpose: Some(Purpose::ConfirmDelete), index: 0 } => {
            state.items.remove(0); // "Delete" chosen
        }
        DialogResult::Selected { .. } => {} // "Cancel" or some other button
        DialogResult::Dismissed       => {} // Esc
    }
}
```

## Rendering it

Draw your normal UI, then draw the dialog on top if one is open. The renderer
needs the data and the active button index, both read from the focus manager:

```rust
use tui_pages::{render_dialog, DialogTheme};

// ...draw the rest of the screen first...

if let Some(data) = dialog::current_dialog(&tui.focus) {
    let active = dialog::active_button(&tui.focus).unwrap_or(0);
    render_dialog(frame, frame.area(), data, active, &DialogTheme::default());
}
```

`render_dialog` centers the modal in the area you pass and draws it. To restyle
it, build a `DialogTheme` (all fields are `ratatui::style::Color`):

```rust
pub struct DialogTheme {
    pub background: Color,
    pub border: Color,
    pub border_active: Color,
    pub title: Color,
    pub text: Color,
    pub button: Color,
    pub button_active: Color,
}
```