tui-pages 0.7.2

Core for TUI apps with multiple pages
Documentation
# Navigation, Buffers & Panes

Same rule as focus: you describe intent with a `TuiEffect`, the runtime owns the
state. There is no `BufferState` API you call into — you return effects, and the
runtime mutates `tui.buffer` for you. You only ever *read* `tui.buffer`, and only
to render it.

There are two layers:

- **Buffer history** — the list of open views you cycle through (like editor
  tabs or buffers).
- **Panes** — how the visible area is split into regions.

## Driving it: effects

Every navigation action in your handler maps to one effect. From the `buffers`
example:

```rust
let effect = match action {
    Action::Open(view)    => TuiEffect::Navigate(view),   // open / switch to a view
    Action::NextBuffer    => TuiEffect::NextBuffer,        // cycle history forward
    Action::PrevBuffer    => TuiEffect::PreviousBuffer,    // cycle history back
    Action::CloseBuffer   => TuiEffect::CloseBuffer,       // drop the active buffer
    Action::Split(split)  => TuiEffect::SplitPane(split),  // split the active pane
    Action::NextPane      => TuiEffect::NextPane,          // move pane focus
    Action::PrevPane      => TuiEffect::PreviousPane,
    Action::ClosePane     => TuiEffect::ClosePane,
    Action::Quit          => TuiEffect::Quit,
};
Ok(ActionOutcome::effect(effect))
```

That's the whole handler for a multi-buffer, multi-pane app. Bind keys to those
actions and you're done:

```rust
TuiPages::builder(View::Editor)
    .focus_wrap(FocusWrap::Wrap)   // NextBuffer past the end wraps to the first
    .bind(modes::GENERAL, "tab", Action::NextBuffer)
    .bind(modes::GENERAL, "w",   Action::CloseBuffer)
    .bind(modes::GENERAL, "v",   Action::Split(PaneSplit::Vertical))
    .bind(modes::GENERAL, "s",   Action::Split(PaneSplit::Horizontal))
    .bind(modes::GENERAL, "o",   Action::NextPane)
    .bind(modes::GENERAL, "x",   Action::ClosePane)
    // ...
```

`PaneSplit` is just:

```rust
pub enum PaneSplit {
    Horizontal,  // stacked top/bottom
    Vertical,    // side by side
}
```

`TuiEffect::Navigate(view)` and buffer/pane cycling honour your `FocusWrap`
policy. Buffer history is per-view: when you navigate back to a buffer, its
focus is restored. After a navigation effect the runtime re-runs your page
function, so the new view's `PageSpec` takes effect automatically.

## Reading it: rendering

Your renderer reads `tui.buffer` (a `BufferState<V>`) to lay out the screen.
These are the read accessors:

```rust
tui.buffer.get_active_view()    // Option<&V> — the focused buffer's view
tui.buffer.is_split()           // bool — is the area split into panes?
tui.buffer.split_direction()    // Option<PaneSplit>
tui.buffer.panes()              // &[PaneSession<V>] — one per pane
tui.buffer.active_pane_index()  // usize — which pane has focus
```

A render function that handles both the single and split cases (from the
`buffers` example) looks like:

```rust
pub fn render(frame: &mut Frame, buffer: &BufferState<View>) {
    if buffer.is_split() {
        let dir = match buffer.split_direction() {
            Some(PaneSplit::Vertical) => Direction::Horizontal,
            _                         => Direction::Vertical,
        };
        let areas = Layout::default().direction(dir)
            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(frame.area());

        for (i, pane) in buffer.panes().iter().enumerate() {
            let active = i == buffer.active_pane_index();
            draw_pane(frame, areas[i], pane, active);
        }
    } else {
        draw_single(frame, frame.area(), buffer.get_active_view());
    }
}
```

The shortcut `tui.current_view()` returns the active buffer's view directly —
handy when you don't care about panes.

## When do you need this?

Only if your app has tabs/buffers or split panes. A single-page app ignores all
of it: there's always exactly one buffer and one pane, and you just render
`tui.current_view()`. See the `buffers` example for the full thing.