# oriel

[![CI](https://github.com/robbym/oriel/actions/workflows/ci.yml/badge.svg)](https://github.com/robbym/oriel/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/oriel-iced)](https://crates.io/crates/oriel-iced)
[![docs.rs](https://img.shields.io/docsrs/oriel-iced)](https://docs.rs/oriel-iced)

A virtualized, batteries-included **data grid for [iced](https://iced.rs)** —
sort, filter, live ingest, themes, and smooth scrolling over hundreds of
thousands of rows.

![The oriel showcase: live ingest with tail-follow, sorting, filtering, theme switching, row animations](docs/showcase.gif)

<sub>Recorded deterministically, headless — the demo renders itself:
`cargo run -p oriel-iced --example record_showcase --release`</sub>

```
cargo run -p oriel-iced --example showcase --release
```

The showcase is the full tour in one window: a live job pipeline streaming
over ~25 000 rows of history, with every feature reachable from the toolbar
and keyboard.

## Features

- **Virtualization** — materialized widgets are a function of the viewport,
  never the row count; sub-pixel-exact to ~670 000 default-height rows.
- **Transactional ingest** — append/update/remove imperatively at 60 Hz;
  the viewport re-anchors **by row identity**, so mutations above the view
  never make visible content jump.
- **Tail-follow** with intent-aware auto-freeze (scrolling up frees you;
  content growth never does).
- **Sort & filter** as plain, persistable specs, executed by your store —
  in memory, or pushed down to a real query engine through the async seam.
- **Selection & keyboard navigation**, identity-keyed — survives re-sorting
  and churn by construction.
- **Columns**: resizable with policy bounds, grip double-click auto-size
  (headers, cells, footers, and the sort arrow all measured), pluggable
  cell renderers via an opaque-element trait.
- **Footers** with live aggregates that dock to short content.
- **Row animations** — ag-grid-style flights and enter-fades, screen-space,
  shared deadline, suppressed while tail-following.
- **Themes** — CSS-verified ports of ag-grid Quartz/Alpine/Balham,
  Material, Spreadsheet, and Bootstrap ([provenance](docs/table-themes.md)),
  plus fully palette-derived defaults; every color and length is a public
  knob with a public default.
- **Own scrollbar** with a real minimum thumb (iced's is 2 px at grid
  scale).

## Quickstart

```toml
[dependencies]
iced = "0.14"
oriel-core = "0.1"
oriel-iced = "0.1"
```

```rust
use iced::{Element, Task};
use oriel_core::columns::ColumnId;
use oriel_core::data::VecStore;
use oriel_iced::column::TextColumn;
use oriel_iced::grid::{self, BoxedColumn, VirtualGrid};

#[derive(Clone, Debug)]
struct Person { id: u64, name: String }

type Store = VecStore<Person, u64, fn(&Person) -> u64>;

struct App { grid: VirtualGrid<Store> }

#[derive(Debug, Clone)]
enum Msg { Grid(grid::Message<Person>) }

fn boot() -> (App, Task<Msg>) {
    fn key(p: &Person) -> u64 { p.id }
    let rows = (0..100_000u64).map(|id| Person { id, name: format!("Person {id}") }).collect();
    let store = VecStore::new(rows, key as fn(&Person) -> u64);
    let columns: Vec<BoxedColumn<Person>> = vec![
        Box::new(TextColumn::new(ColumnId(1), "#", 70.0, |p: &Person| p.id.to_string()).numeric()),
        Box::new(TextColumn::new(ColumnId(2), "Name", 260.0, |p: &Person| p.name.clone())),
    ];
    let (grid, init) = VirtualGrid::new(store, columns);
    (App { grid }, init.map(Msg::Grid))
}

fn update(app: &mut App, msg: Msg) -> Task<Msg> {
    match msg { Msg::Grid(m) => app.grid.update(m).map(Msg::Grid) }
}

fn view(app: &App) -> Element<'_, Msg> {
    app.grid.view().map(Msg::Grid)
}

fn main() -> iced::Result {
    iced::application(boot, update, view).run()
}
```

The crate-level rustdoc is the real manual: the wiring contract, a
"where do I…?" map, keyboard/persistence/testing recipes, and compiled doc
examples on every high-traffic API.

## Architecture

Two crates, one-way dependency:

- **`oriel-core`** — headless, **zero dependencies**: virtualization math,
  view/interaction state, and the two seams an integrator satisfies (an
  async `DataSource`, an opaque-element `Column` model). Unit-testable with
  no GUI in the loop.
- **`oriel-iced`** — the iced integrator: the `VirtualGrid` widget, provided
  column renderers, themes, and the grid's hand-rolled widgets (resize
  grips, auto-size measurer, scrollbar, row animations).

The rules this project is built under — and held to by tests and lints —
live in [`DESIGN-PHILOSOPHY.md`](DESIGN-PHILOSOPHY.md). AI agents working
on the codebase are bound to them via [`AGENTS.md`](AGENTS.md).

## Examples

| Example | What it shows |
|---|---|
| `showcase` | The flagship tour — every feature, one grid |
| `record_showcase` | Deterministic headless demo recording (renders the showcase into a video, no window) |

## Status

Early `0.x`. Built and verified against **iced 0.14** (iced is pre-1.0 and
its widget API moves between minor versions — development is lockfile-pinned,
and the upstream behaviors this crate depends on are pinned by canary tests).
Interaction behavior is regression-tested headlessly against the real iced
runtime with the tiny-skia renderer — no window or GPU needed;
`cargo test --workspace` runs everything.

## License

[MIT](LICENSE)
