# tui-overlay

[![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/)
[![Crates.io](https://img.shields.io/crates/v/tui-overlay.svg)](https://crates.io/crates/tui-overlay)
[![docs.rs](https://img.shields.io/docsrs/tui-overlay)](https://docs.rs/tui-overlay)
[![CI](https://github.com/jharsono/tui-overlay/actions/workflows/ci.yml/badge.svg)](https://github.com/jharsono/tui-overlay/actions/workflows/ci.yml)
[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/tui-overlay.svg)](LICENSE-MIT)

A composable overlay widget for [Ratatui](https://ratatui.rs): drawers, modals, popovers, and toasts from a single configurable primitive.

## Features

- **9-point anchor grid** — position overlays at any corner, edge, or center
- **Directional slide animation** — enter from any edge with configurable easing
- **Backdrop dimming** — flatten underlying UI to a ghost while preserving structure
- **Independent width/height** — partial drawers, compact notifications, full-screen modals
- **Block integration** — wrap the overlay in a ratatui `Block` for titled borders
- **Hit testing** — `overlay_rect()` enables click-outside-to-dismiss and mouse interaction
- **No content ownership** — the overlay dims, draws chrome, and exposes inner area for you to render into

## Installation

```sh
cargo add tui-overlay
```

## Usage

```rust
use std::time::Duration;
use ratatui_core::layout::Constraint;
use ratatui_core::style::Color;
use tui_overlay::{Anchor, Backdrop, Easing, Overlay, OverlayState, Slide};

let overlay = Overlay::new()
    .anchor(Anchor::Right)
    .slide(Slide::Right)
    .width(Constraint::Percentage(30))
    .backdrop(Backdrop::new(Color::Rgb(0, 0, 0)));

let mut state = OverlayState::new()
    .with_duration(Duration::from_millis(150))
    .with_easing(Easing::EaseOut);

state.open();
```

In your event loop, call `state.tick(elapsed)` and render:

```rust
// 1. Main UI
frame.render_widget(main_view, area);

// 2. Overlay (backdrop + chrome)
frame.render_stateful_widget(overlay, area, &mut state);

// 3. Body content into the overlay
if let Some(inner) = state.inner_area() {
    frame.render_widget(detail_panel, inner);
}
```

## Configuration Recipes

| Pattern | Anchor | Slide | Width | Height | Backdrop |
|---|---|---|---|---|---|
| Side drawer | `Right` | `Right` | `30%` | `100%` | yes |
| Bottom drawer | `Bottom` | `Bottom` | `100%` | `40%` | yes |
| Partial bottom drawer | `BottomLeft` | `Bottom` | `60%` | `40%` | yes |
| Modal | `Center` | — | `60%` | `50%` | yes |
| Popover | `BottomRight` | `Bottom` | `Length(40)` | `Length(20)` | no |
| Notification | `TopRight` | `Top` | `Length(40)` | `Length(3)` | no |
| Toast | `BottomRight` | `Bottom` | `Length(40)` | `Length(3)` | no |

## Animation

Slide animations support four easing curves:

| Curve | Effect |
|---|---|
| `Linear` | Constant speed |
| `EaseIn` | Slow start, fast end |
| `EaseOut` | Fast start, slow end (default) |
| `EaseInOut` | Slow start, slow end |

Interrupting an animation (e.g. closing while opening) reverses from the current position — no snap.

## Examples

### Interactive demo

```sh
cargo run --example demo
```

Keys: `[space]` toggle · `[p]` preset · `[d]` speed · `[e]` easing · `[a]` anchor · `[s]` slide · `[b]` backdrop · `[←→]` width · `[↑↓]` height · `[q]` quit

## License

Dual licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE).

## Contributing

Contributions are welcome. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you shall be dual licensed as above, without any additional terms or conditions.
