A virtualized, batteries-included data grid for iced — sort, filter, live ingest, themes, and smooth scrolling over hundreds of thousands of rows.
Recorded deterministically, headless — the demo renders itself:
cargo run -p oriel-iced --example record_showcase --release
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.
- 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), 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).
[dependencies]
iced = "0.14"
oriel-core = "0.1"
oriel-iced = "0.1"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.
Two crates, one-way dependency:
oriel-core— headless, zero dependencies: virtualization math, view/interaction state, and the two seams an integrator satisfies (an asyncDataSource, an opaque-elementColumnmodel). Unit-testable with no GUI in the loop.oriel-iced— the iced integrator: theVirtualGridwidget, 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. AI agents working
on the codebase are bound to them via AGENTS.md.
| 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) |
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.
