# densa > densa is an open-source Rust platform for applications that show a lot of live data: live Arrow tables kept sorted, filtered and aggregated as they change, a GPU interface with a data grid, charts, docking and screen reader support, and a runtime that hands data between threads without waiting. Version 0.2.5 on crates.io; Rust 1.90 or later. # AGENTS.md (from the densa repository) Notes for AI coding agents (and the people working with them) on densa: first for building an application with it, then for changing densa itself. The API documentation on [docs.rs](https://docs.rs/densa) is the reference; [densa.dev/llms.txt](https://densa.dev/llms.txt) lists the same material as plain text. ## Reading the docs and the source - Fetch these files as they are (`curl https://densa.dev/llms-full.txt`): a tool that summarizes a page drops the item names and signatures you need. [llms-full.txt](https://densa.dev/llms-full.txt) holds this file and every crate's front page, with an example each. - docs.rs shows the last release. Code newer than it (on the main branch, listed under "Unreleased" in the CHANGELOG) is documented in the source. - The source of the densa you build is on your disk, with every doc comment and example. From crates.io: `~/.cargo/registry/src/*/densa-data-0.2.*/` (one folder per crate). From git: `~/.cargo/git/checkouts/densa-//` (`densa-` and 16 hex digits, not `densa-connectors-...`), where `` is the first 7 characters of the hash after `#` in the `densa` entries of your `Cargo.lock`. Older commits sit beside it: read the one your build uses. - The examples in `crates/densa-widgets/examples/` and the tests in each crate's `tests/` folder show every widget in use. ## Versions: check these before writing code densa is younger than most training data. Use what the installed crates say, not memory: - densa 0.2 from crates.io, all `densa-*` crates at the same version. Rust 1.90 or later, edition 2021. - Arrow 60 (`arrow-array`, `arrow-schema`). Build record batches with the versions densa re-exports, `densa_data::arrow_array` and `densa_data::arrow_schema` (also `densa::data::arrow_array`): a different Arrow version gives errors such as "expected `RecordBatch`, found `RecordBatch`". - wgpu 30 and winit 0.30, used only inside `densa-render` and `densa-window`. Applications built on `densa_window::run` never touch them. - No async runtime. Data arrives on threads of your own; the window thread owns the interface. ## Building an application Start from the `densa` crate (features `window` and `data`, both on by default) or depend on the crates you use. Its front page on docs.rs maps tasks to modules and has a complete program: a live table shown in a grid, in a window. Complete programs to copy from, in `crates/densa-widgets/examples/`: | Example | Shows | |---|---| | `monitor.rs` | a live table in a grid, sorted, and a chart of the selected row's history, fed by a worker thread that wakes the window (the pattern to copy for live data) | | `linked.rs` | a grid and a chart over two live tables, the chart filtered by the selected row | | `live_table.rs` | 100,000 rows, 1,000 updated every frame (made inside `frame`, a stress test: copy `monitor.rs` for real data); click a title to sort, type to filter | | `paged.rs` | a billion rows, none held: a `PagedModel` fetches the pages on screen from a `PageSource` (the pattern for a query engine or a database); click a title to sort at the source | | `dock.rs` | panels in tabs and splits, dragged around; with `--layout FILE` the layout is saved and restored | | `gallery.rs` | the standard widgets: menus, a toolbar, a list, a splitter, text input | | `chart.rs` | a chart of your own data with panes, zoom and pan | Run one with `cargo run --release -p densa-widgets --example monitor`. How the pieces fit: - `densa_window::run(WindowOptions, app)` opens the window and calls your `App`: `build` once (add widgets under `ui.root()`), `action` for each action a widget reports (a click, an edit, a selected row), `frame` before each paint (take in new data; return `true` to keep painting continuously). - Widgets live in the `Ui` and are reached by `WidgetId`. Change one with `ui.update::(id, |widget, cx| ...)`; read one with `ui.widget::(id)`. Do not keep references to widgets. - Widgets are in `densa::widgets` (`Label`, `Button`, `TextInput`, `Panel`, `ScrollView`, `Splitter`, `DockArea`, `MenuBar`, `DataGrid`, `Chart`). Add one with `ui.add(parent, widget, style)`: `parent` is `ui.root()` or a container such as `Panel`, and `style` a `densa::ui::Style`, usually `Style::row()` or `Style::column()` with `.gap(..)`, `.padding(..)` and `.grow(1.0)` (widgets that fill space, such as `DataGrid`, give theirs: `DataGrid::::style()`). The root is a column the size of the window. - Layout is flexbox in logical pixels; painting is in physical pixels. - A widget reports what the user did as an `Action { source, kind }` (`ActionKind::Clicked`, `Changed`, `Submitted`, `Selected(row)`, ...), passed to `App::action`; read the details from the widget (`ui.widget::(id)?.text()`). - Live data: a `densa_data::LiveTable` keyed by an integer or text column (the key column may be declared nullable; a row with a null key is refused). `upsert` replaces rows by key; views (`ViewSpec`: sort and filter) and grouped aggregates (`GroupSpec`) follow every change; `Window` keeps only recent rows. Every batch must have the table's columns: the same names, order and data types (`LiveTable::schema`). Delete by key with an Arrow array of the key column's type (`table.delete(&StringArray::from(vec!["h1"]))`). Read a view's rows through `table.view(id)`: each `view.row(i)` is a `RowRef` naming one of `table.batches()` and a row in it; read grouped results with `table.groups(id).unwrap().to_batch()` (keys first, then the aggregates in the order you added them). The rustdoc of `View` and `Groups` shows both. Show a table with `DataGrid`, and change it through `grid.model_mut(cx).table_mut()`. - A table too large to hold (a query engine's result, a database table): `DataGrid` with your `densa_arrow::PageSource`, which counts the rows and gives a page (`offset`, `limit`, in the order of a `PageQuery`) on a thread of the model's own. The grid fetches only the pages it shows; see the recipe below. - Other threads never touch the `Ui`. They hand data over (a channel, or `densa_sync::triple_buffer` for snapshots) and call `Waker::wake` (from `host.waker()`) so a frame comes. - A frame must not allocate once its text is cached. Format numbers with `densa_fmt` into stack buffers, not `format!`. Errors are typed (`DataError`, `UiError`, `ParagraphError`, `WindowError`, ...) and their messages say what to change: read the message before guessing. ### Testing without a window A `Ui` runs without a window or GPU, so widgets and whole forms can be tested with `cargo test`: ```rust use densa::core::{Point, Size}; use densa::paragraph::TextSystem; use densa::ui::{ActionKind, PointerButton, PointerEvent, PointerKind, Style, Ui}; use densa::widgets::Button; let mut ui = Ui::new(TextSystem::bundled()?); // the bundled font: the same on every machine ui.resize(Size::new(800.0, 600.0)); let save = ui.add(ui.root(), Button::new("Save"), Style::row())?; ui.layout(); let r = ui.rect(save).unwrap(); let at = Point::new((r.left() + r.right()) / 2.0, (r.top() + r.bottom()) / 2.0); for kind in [PointerKind::Down(PointerButton::Primary), PointerKind::Up(PointerButton::Primary)] { ui.pointer(PointerEvent::new(kind, at)); } let clicked = ui.actions().any(|a| a.source == save && a.kind == ActionKind::Clicked); assert!(clicked); ``` `ui.paint(&mut canvas)` draws the frame into a `densa::draw::Canvas`, for tests that count what was drawn. ### Recipes Each compiles as written (with `use` lines for the names it takes from `densa::`). A menu bar whose commands reach the app: ```rust use densa::ui::ActionKind; use densa::widgets::{MenuBar, MenuItem}; const OPEN: u32 = 1; const QUIT: u32 = 2; let file = [ MenuItem::new("Open…", OPEN).with_shortcut("Ctrl+O"), // the shortcut is shown; keys are yours MenuItem::separator(), MenuItem::new("Quit", QUIT), ]; let bar = ui.add(ui.root(), MenuBar::new().with_menu("File", file), MenuBar::style())?; // In App::action: a chosen item arrives as ActionKind::Command(OPEN) from a menu. let chosen = |kind: ActionKind| match kind { ActionKind::Command(command) => Some(command), _ => None, }; ``` A grid over data of your own: implement `densa::grid::GridModel` and give it to `DataGrid::new`. The types it needs are all in `densa::grid`: ```rust use densa::grid::{Align, CellStyle, CellText, Column, GridModel, Palette, SortOrder}; struct Orders { columns: Vec, // Column::new("Item", 16, Align::Left), ... rows: Vec<(u64, String)>, // (id, item) } impl GridModel for Orders { fn columns(&self) -> &[Column] { &self.columns } fn rows(&self) -> usize { self.rows.len() } fn cell(&self, row: usize, _column: usize, text: &mut CellText, palette: &Palette) -> CellStyle { // CellText is a 64-byte stack buffer: push_str, or densa::fmt's writers for numbers. // Text that does not fit is refused: write a placeholder such as "#" instead. if text.push_str(&self.rows[row].1).is_err() { let _ = text.push_str("#"); } CellStyle::new(palette.text) } // Optional: sorting on a title click, and keys so a selected row follows its record. fn sort(&mut self, _column: usize, order: SortOrder) -> bool { self.rows.sort_by(|a, b| a.1.cmp(&b.1)); if order == SortOrder::Descending { self.rows.reverse(); } true } fn row_key(&self, row: usize) -> Option { self.rows.get(row).map(|r| r.0) } fn row_of_key(&self, key: u64) -> Option { self.rows.iter().position(|r| r.0 == key) } } ``` A live grid filtered as the user types: on `ActionKind::Changed` from a `TextInput`, give the grid's `LiveModel` a filter (`Filter::contains` is case-sensitive): ```rust use densa::arrow::LiveModel; use densa::data::Filter; use densa::ui::{Action, ActionKind, Ui, WidgetId}; use densa::widgets::{DataGrid, TextInput}; fn on_action(ui: &mut Ui, input: WidgetId, grid: WidgetId, action: &Action) { if action.source != input || action.kind != ActionKind::Changed { return; } let text = ui.widget::(input).map_or(String::new(), |t| t.text().to_owned()); let filter = (!text.is_empty()).then(|| Filter::contains("symbol", text)); ui.update::, _>(grid, |grid, cx| { let _ = grid.model_mut(cx).set_filter(filter); // Err: no such column, or not text }); } ``` Rows enriched from another table (each trade with its instrument's columns), kept up to date as either side changes: a `densa::data::LookupJoin`. Its output is a table of your own, so it can sit in a grid: ```rust use densa::arrow::LiveModel; use densa::data::arrow_array::RecordBatch; use densa::data::LookupJoin; use densa::widgets::DataGrid; fn enriched( ui: &mut densa::ui::Ui, trades: RecordBatch, // id (key), symbol, price, ... instruments: RecordBatch, // symbol (key), name, ... ) -> Result<(LookupJoin, densa::ui::WidgetId), Box> { let mut join = LookupJoin::new(trades.schema(), "id", "symbol", instruments.schema(), "symbol")?; let mut table = join.table()?; join.upsert_right(&mut table, instruments)?; join.upsert_left(&mut table, trades)?; let grid = DataGrid::new(LiveModel::new(table)?); let grid = ui.add(ui.root(), grid, DataGrid::::style())?; // Later, on the window thread: hand the grid's table to the join. // ui.update::, _>(grid, |g, cx| join.upsert_left(g.model_mut(cx).table_mut(), more)); Ok((join, grid)) } ``` A grid over a table too large to hold: implement `densa::arrow::PageSource` (its calls run on a thread of the model's own, so they may wait on a database or a query engine; return the batches as they come, the model joins them) and give it to a `PagedModel`, with a wake that brings a frame when pages arrive. A copy of selected rows holds only the rows fetched. `model.error()` says why rows are missing (a `FetchError`); `retry` asks again for what failed, `refresh` counts and fetches again after the data changed: ```rust use densa::arrow::{PageError, PageQuery, PageSource, PagedModel}; use densa::data::arrow_array::RecordBatch; use densa::data::arrow_schema::SchemaRef; use densa::widgets::DataGrid; struct Trips { schema: SchemaRef, // every page must have these columns' data types } impl PageSource for Trips { fn row_count(&mut self, _query: &PageQuery) -> Result { Ok(2_000_000_000) // SELECT count(*) FROM trips } fn page( &mut self, query: &PageQuery, offset: u64, limit: usize, ) -> Result, PageError> { // `trip_id` tells rows apart: where sort keys tie, pages keep one order. let order = query.order_by_sql(&["trip_id"]); let sql = format!("SELECT * FROM trips {order} LIMIT {limit} OFFSET {offset}"); let _ = sql; // run it on your engine Ok(vec![RecordBatch::new_empty(self.schema.clone())]) // the batches it gave } } // In App::build (`host` is its `&mut Host`): let waker = host.waker(); let source = Trips { schema: schema.clone() }; let model = PagedModel::new(schema, source, move || { waker.wake(); })?; let grid = ui.add(ui.root(), DataGrid::new(model), DataGrid::::style())?; ``` A chart that follows a live table: keep the table, a view sorted by time, and a `densa::arrow::LiveSeries` per line in your `ChartModel`; call `series.sync(&table)` after every change to the table, or the chart shows nothing. `monitor.rs` (its `Trail` model) and `linked.rs` are complete examples. Typing in a test without a window: give the input focus, then send keys. ```rust use densa::ui::{Key, KeyEvent, NamedKey}; ui.set_focus(Some(input)); ui.key(&KeyEvent::down(Key::Character("A")).with_text("A")); ui.key(&KeyEvent::down(Key::Named(NamedKey::Backspace))); let edits = ui.actions().filter(|a| a.source == input).count(); // a Changed per edit ``` ## Changing densa [CONTRIBUTING.md](CONTRIBUTING.md) is the full guide: the workspace layout, what the code must guarantee, and every check a pull request must pass. In short: - Libraries, not app code: no core crate knows about a domain (markets, hosts, sensors); applications bring theirs through traits such as `GridSource`, `GridModel` and `ChartModel`. - Dependencies point downward only (app, window, widgets, ui, draw and paragraph, text, core). Only `densa-render` and `densa-window` touch the GPU or a window. - Frames do not allocate. Prove it with the counting allocator in the crate's `tests/zero_alloc.rs`, which must `assert!(is_active())` first. - Every `unsafe` block has a `// SAFETY:` comment, and every atomic ordering a comment saying why it is enough. - Tests are named as sentences and check behaviour, including model-based tests that compare against a simple reference. A change is checked by planting bugs in it: a test that still passes with a deliberate bug is missing something. - Public docs, error messages and examples are plain English, give a runnable example, and say how to fix what went wrong. Before calling a change done: ```sh cargo fmt --all cargo test --workspace cargo clippy --workspace --all-targets -- -D warnings RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps cargo +1.90 check --workspace --all-targets ``` Set `DENSA_SKIP_GPU_TESTS=1` where there is no GPU or desktop session. ## In your own project Add this to your project's `AGENTS.md` (or `CLAUDE.md`) so an agent finds densa's docs: ```md ## densa This project uses densa 0.2 (https://densa.dev). Before writing densa code, read https://densa.dev/llms.txt and the `densa` crate's front page on docs.rs; copy from densa-widgets' examples. Use the Arrow types densa re-exports (`densa_data::arrow_array`). Widgets change only through `ui.update::(id, ...)` on the window thread; other threads send data and call `Waker::wake`. Frames must not allocate: format with `densa_fmt`. ``` --- # Crate densa (https://docs.rs/densa) A native Rust platform for data-dense, real-time applications: live Arrow tables, a GPU data grid and charts, widgets with docking and screen reader support, in one stack densa: a native Rust platform for data-dense, real-time applications. This crate gathers densa's libraries under one name, each as a module. Every module is also its own crate (`densa-ui` is [`ui`], and so on): depend on those directly to take only what you use. - Interface: [`ui`] (the widget tree, layout, events, focus, accessibility), [`widgets`] (labels, buttons, text inputs, scroll views, docking, the data grid and charts as widgets), `window` (a native window; feature `window`). - Drawing: [`draw`] (the GPU-free display list), `render` (wgpu; feature `window`), [`grid`], [`chart`], [`text`] (fonts and the glyph atlas), [`paragraph`] (shaped, wrapped text, selection and editing). - Data: `data` (live Arrow tables with incremental views and aggregates) and `arrow` (Arrow in the grid and charts), both behind the `data` feature. - Runtime: [`sync`] (a wait-free triple buffer), [`platform`] (CPU placement and thread priority), [`fmt`] (allocation-free number formatting), [`alloc`] (a counting allocator for proving code allocates nothing), and [`core`] (the shared primitives). Data sources (CSV and Parquet files, WebSocket streams, Arrow Flight) live in [densa-connectors](https://github.com/densa-rs/densa-connectors). See [densa.dev](https://densa.dev) for guides and the roadmap, and [densa.dev/llms.txt](https://densa.dev/llms.txt) for these docs in plain text. # Which module for which task | To | Use | |---|---| | open a window and run an app | `window::run` with your `window::App` | | lay widgets out | [`ui::Ui::add`] with a [`ui::Style`] (flexbox, logical pixels) | | show rows of live data | [`widgets::DataGrid`] over `arrow::LiveModel` (a live table) | | show rows of your own data | [`widgets::DataGrid`] over your [`grid::GridModel`] | | show a table too large to hold (a query engine's result, a database) | [`widgets::DataGrid`] over `arrow::PagedModel`, with your `arrow::PageSource` giving pages | | keep data sorted, filtered and aggregated as it changes | `data::LiveTable` with `data::ViewSpec` and `data::GroupSpec` | | keep only recent rows, or count per minute | `data::Window`, `data::GroupSpec::group_by_time` | | give each row the columns of the row it names in another table | `data::LookupJoin` | | read a view's rows, or grouped results | `data::View::row` (a `data::RowRef` into `data::LiveTable::batches`), `data::Groups::to_batch` | | chart data | [`widgets::Chart`] with your [`chart::ChartModel`]; `arrow::LiveSeries` reads a live table | | arrange panels in tabs and splits | [`widgets::DockArea`] | | add a menu bar | [`widgets::MenuBar`] with [`widgets::MenuItem`]s; choices arrive as [`ui::ActionKind::Command`] | | update the screen from another thread | `window::Host::waker`, and [`sync::triple_buffer()`] for snapshots | | format numbers in a frame without allocating | [`fmt`] | | test widgets without a window | [`ui::Ui::new`] with [`paragraph::TextSystem::bundled`], then [`ui::Ui::pointer`] and [`ui::Ui::actions`] | Complete programs to start from are in [densa-widgets' examples](https://github.com/densa-rs/densa/tree/main/crates/densa-widgets/examples): `monitor` (a live table, a grid and a chart of the selected row), `linked`, `live_table`, `dock`, `gallery` and `chart`. # A live table in a window ```rust use std::error::Error; use std::sync::mpsc::{self, Receiver}; use std::sync::Arc; use std::time::Duration; use densa::arrow::LiveModel; use densa::data::arrow_array::{ArrayRef, Float64Array, RecordBatch, StringArray}; use densa::data::LiveTable; use densa::ui::{Ui, WidgetId}; use densa::widgets::DataGrid; use densa::window::{App, Host, WindowOptions}; type Grid = DataGrid; #[derive(Default)] struct Monitor { grid: Option, /// Rows from the thread that reads them (a socket, a file, a feed). arrivals: Option>, } /// Hosts and their load, keyed by host: a row with a known host replaces it. fn loads(hosts: Vec<&str>, load: Vec) -> Result> { Ok(RecordBatch::try_from_iter([ ("host", Arc::new(StringArray::from(hosts)) as ArrayRef), ("load", Arc::new(Float64Array::from(load)) as ArrayRef), ])?) } impl App for Monitor { fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box> { let rows = loads(vec!["web-1", "db-1"], vec![0.42, 3.5])?; let mut table = LiveTable::new(rows.schema(), "host")?; table.upsert(rows)?; let grid = Grid::new(LiveModel::new(table)?); self.grid = Some(ui.add(ui.root(), grid, Grid::style())?); // Another thread reads the data and wakes the window when rows arrive. let (send, arrivals) = mpsc::channel(); let waker = host.waker(); std::thread::spawn(move || loop { std::thread::sleep(Duration::from_secs(1)); let Ok(rows) = loads(vec!["web-1"], vec![0.5]) else { return }; if send.send(rows).is_err() { return; // the window closed } waker.wake(); }); self.arrivals = Some(arrivals); Ok(()) } /// Before each frame: take in what arrived. The grid repaints what changed. fn frame(&mut self, ui: &mut Ui, _: &mut Host<'_>) -> bool { let (Some(grid), Some(arrivals)) = (self.grid, &self.arrivals) else { return false; }; for rows in arrivals.try_iter() { ui.update::(grid, |grid, cx| { if let Err(error) = grid.model_mut(cx).table_mut().upsert(rows) { eprintln!("rows refused: {error}"); } }); } false // nothing animates: the next frame comes when the thread wakes the window } } densa::window::run(WindowOptions::new("Monitor"), Monitor::default())?; ``` --- # Crate densa-data (https://docs.rs/densa-data) Live Arrow tables for densa: keyed upserts, sorted and filtered views, grouped aggregates (per key or per time bucket), time windows and lookup joins, kept up to date incrementally Live Arrow tables: rows identified by a key, updated by record batches, and views that stay sorted and filtered as the rows change. [`LiveTable`] takes Arrow [`RecordBatch`](arrow_array::RecordBatch)es from a feed, a query engine or a file, and upserts them by a key column; [`ViewSpec`] describes a view (a [`Filter`] and sort columns), which the table keeps up to date incrementally instead of recomputing it; [`GroupSpec`] does the same for grouped aggregates (count, sum, min, max, mean), grouped by key columns or by buckets of one (counts per minute, [`GroupSpec::group_by_time`]). A view's rows are [`RowRef`]s into [`LiveTable::batches`], so a display layer (such as `densa-arrow`'s grid) reads values straight from the Arrow arrays. Each key's row is a [`RecordId`] while the key lives: a selection can follow it through updates, compactions and re-sorted views ([`View::position`]). [`LiveTable::snapshot`] hands the live rows to anything else that reads Arrow, such as a SQL engine. A [`Window`] keeps a table to its recent rows, by a time column. A [`LookupJoin`] gives each row of one table the columns of the row of another that it names (a trade its instrument), kept up to date as either changes. This crate has no user-interface dependency. ```rust use std::sync::Arc; use arrow_array::{ArrayRef, Float64Array, RecordBatch, StringArray}; use densa_data::{CompareOp, Filter, LiveTable, Order, Scalar, ViewSpec}; let quotes = |symbols: Vec<&str>, prices: Vec| { RecordBatch::try_from_iter([ ("symbol", Arc::new(StringArray::from(symbols)) as ArrayRef), ("price", Arc::new(Float64Array::from(prices)) as ArrayRef), ]) }; let mut table = LiveTable::new(quotes(vec![], vec![])?.schema(), "symbol")?; let expensive = table.add_view( ViewSpec::new() .filter(Filter::compare("price", CompareOp::Gt, Scalar::Float(100.0))) .sort_by("price", Order::Descending), )?; table.upsert(quotes(vec!["AAA", "BBB", "CCC"], vec![150.0, 90.0, 120.0])?)?; table.upsert(quotes(vec!["BBB"], vec![200.0])?)?; // BBB rises: it enters the view let view = table.view(expensive).unwrap(); assert_eq!(view.len(), 3); // BBB 200, AAA 150, CCC 120 ``` --- # Crate densa-arrow (https://docs.rs/densa-arrow) Apache Arrow in densa's grid and charts: live tables as grid models and chart series, with typed, allocation-free cell formatting Apache Arrow tables in densa's data grid. [`ArrowTable`] holds Arrow [`RecordBatch`](arrow_array::RecordBatch)es (from Parquet, DataFusion, DuckDB, a Flight stream or your own code) and formats their values into grid cells; [`ArrowTable::view`] turns it into a `densa_grid::GridSource` scrolled to a row, which `GridPainter` draws. Arrays are shared with the batches, never copied, so a table of millions of rows costs no more to show than its visible rows, and drawing a frame allocates nothing. For live data, [`LiveModel`] makes a `densa_data::LiveTable` the model of the grid widget (`densa_widgets::DataGrid`): sorted by a click, filtered by the app, its selection kept on a row as the row moves. [`LiveGrid`] paints a view of one with `GridPainter` directly, and [`LiveSeries`] reads two of its numeric columns as a `densa_chart` series (a price over time), so a chart follows the same live data. For tables too large to hold (a query engine's result over billions of rows), a [`PagedModel`] is the grid widget's model: your [`PageSource`] gives rows a page at a time, on a thread of the model's own, and the grid fetches only the pages it shows, in the order a click on a column title asks for. ```rust use std::sync::Arc; use arrow_array::{ArrayRef, Float64Array, RecordBatch, StringArray}; use densa_arrow::ArrowTable; use densa_grid::{CellText, GridSource, Palette}; let batch = RecordBatch::try_from_iter([ ("sensor", Arc::new(StringArray::from(vec!["a-1", "b-2"])) as ArrayRef), ("celsius", Arc::new(Float64Array::from(vec![21.5, 80.25])) as ArrayRef), ])?; let mut table = ArrowTable::from_batches([batch])?; table.set_decimals(1, Some(1))?; let columns = table.columns(); // for `GridPainter::new` let view = table.view(0, &Palette::new()); // each frame: `painter.paint(&view, &mut canvas)` let mut text = CellText::new(); view.cell(1, 1, &mut text); assert_eq!(text.as_str(), "80.2"); ``` --- # Crate densa-ui (https://docs.rs/densa-ui) The widget layer of densa: a retained widget tree with flexbox layout, pointer and keyboard events, focus and themes, independent of any window or GPU API The widget layer of densa: a retained tree of widgets, laid out with flexbox, painted onto a `densa_draw::Canvas`, and driven by pointer, keyboard and input method events. A [`Ui`] owns the widgets. The application adds them under the [root](Ui::root) with a [`Style`], then a window layer (such as `densa-window`) calls [`Ui::paint`] each frame and hands it input with [`Ui::pointer`], [`Ui::key`] and [`Ui::ime`]. Widgets report what the user did as [`Action`]s, read with [`Ui::actions`]. Nothing here depends on a window system or a GPU, so an interface runs, and is tested, headless. Layout runs only when a style, a widget's content or the viewport changes; painting, hit-testing and delivering events run every frame and do not allocate. Popups (menus, completion lists) open above every other widget with [`Ui::open_popup`], placed next to an [`Anchor`] and kept inside the viewport; a press outside closes them. A secondary press or the context menu key reports [`ActionKind::ContextMenu`], where an application opens its context menu. Widgets copy and paste through the Ui's [`Clipboard`] ([`EventCx::clipboard`]): a [`MemoryClipboard`] until the window gives it the system's. Widgets also describe themselves to assistive technology ([`Widget::accessibility`]): [`Ui::access_tree`] is what a screen reader reads, and [`Ui::access_action`] does what it asks. Both are platform-neutral; `densa-window` connects them to the operating system. ```rust use densa_core::{Point, Size}; use densa_paragraph::TextSystem; use densa_ui::{ ActionKind, Event, EventCx, Handled, PointerButton, PointerEvent, PointerKind, Style, Ui, Widget, }; /// Reports a click when pressed. struct Clickable; impl Widget for Clickable { fn event(&mut self, cx: &mut EventCx<'_>, event: &Event<'_>) -> Handled { match event { Event::Pointer(p) if matches!(p.kind, PointerKind::Down(_)) => { cx.emit(ActionKind::Clicked); Handled::Yes } _ => Handled::No, } } } let mut ui = Ui::new(TextSystem::new()?); ui.resize(Size::new(400.0, 300.0)); let button = ui.add(ui.root(), Clickable, Style::row().size(120.0, 32.0))?; let press = PointerKind::Down(PointerButton::Primary); ui.pointer(PointerEvent::new(press, Point::new(10.0, 10.0))); let clicked: Vec<_> = ui.actions().map(|action| action.source).collect(); assert_eq!(clicked, [button]); ``` --- # Crate densa-widgets (https://docs.rs/densa-widgets) Standard widgets for densa: labels, buttons, text inputs, menus, scroll views, splitters, docking, and the data grid and charts as widgets Standard widgets for `densa_ui`: [`Label`], [`Button`], [`TextInput`] (with undo and the clipboard), [`ScrollView`], [`Splitter`], [`Panel`], [`DockArea`] (panels in tab groups the user rearranges, the arrangement saved and restored as a [`DockLayout`]), [`DataGrid`] (rows of a model, scrolled, selected, copied and sorted), [`Chart`] (time series, candles and volume, panned and zoomed), and [`Menu`] and [`MenuBar`] (commands in menus that pop up, with submenus, check marks and keyboard navigation). Each is a [`Widget`](densa_ui::Widget): add it to a [`Ui`](densa_ui::Ui) with a [`Style`](densa_ui::Style), and read what the user did from the actions it reports. They take their colors and text size from the [`Theme`](densa_ui::Theme), and paint and handle input without allocating (editing text re-lays it out, which does). ```rust use densa_core::Size; use densa_paragraph::TextSystem; use densa_ui::{Align, Style, Ui}; use densa_widgets::{Button, Label, Panel, TextInput}; let mut ui = Ui::new(TextSystem::new()?); ui.resize(Size::new(640.0, 480.0)); let bar = ui.add( ui.root(), Panel::new(), Style::row().gap(6.0).padding(8.0).align_items(Align::Center), )?; ui.add(bar, Label::new("Symbol"), Style::row())?; let filter = ui.add(bar, TextInput::new("").with_placeholder("Filter"), Style::row())?; let run = ui.add(bar, Button::new("Run").primary(), Style::row())?; ui.layout(); assert!(ui.rect(run).unwrap().left() > ui.rect(filter).unwrap().right()); ``` --- # Crate densa-window (https://docs.rs/densa-window) Runs a densa interface in a native window: winit input in, one wgpu draw call per frame out Runs a densa interface in a native window. [`run`] opens a window, creates a [`Ui`] for it and hands it to an [`App`] to fill with widgets. From then on it translates the window system's input (winit) into `densa_ui` events, passes the actions widgets report to the app, and paints the interface with `densa_render` (wgpu) in one draw call per frame, presented with V-Sync. Frames are painted when something changed, when another thread asks for one through a [`Waker`] (new data arrived), or continuously while the app asks for it (animation). Widgets copy and paste through the system clipboard (on Linux, X11's; XWayland's under Wayland). Files dragged onto the window reach [`App::file_drop`]. Screen readers (Narrator, VoiceOver, Orca) read the interface through AccessKit: the window sends the [`Ui`]'s accessibility tree after each frame, only while one is listening, and carries out what it asks. The [`Ui`]'s text uses `densa_paragraph::TextSystem::new`: the bundled font, and the system's fonts for characters it lacks (Korean, Japanese, Chinese, ...). Turning off the default `system-fonts` feature leaves the bundled font alone. ```rust use std::error::Error; use densa_ui::{Ui, Style}; use densa_window::{App, Host, WindowOptions}; struct Hello; impl App for Hello { fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box> { // Add widgets under ui.root() here; hand host.waker() to threads that feed them. let _ = (ui.root(), host.waker()); Ok(()) } } densa_window::run(WindowOptions::new("Hello"), Hello)?; ``` --- # Crate densa-chart (https://docs.rs/densa-chart) Charts for densa: time series, candlesticks and volume over your own data, drawn without allocating Charts over your own data, drawn onto a `densa_draw::Canvas`: lines, candlesticks and bars, with gridlines and tick labels. Domain-agnostic: an application implements [`Points`] (a line or bars) or [`Candles`] over its data in ascending `x`, picks the visible part with an [`Axis`] for each direction, and draws onto a [`Plot`] (an area of the canvas and its two axes). Time axes count seconds since the epoch and label ticks as times of day or dates (UTC); category axes place named things (a bar chart's bars) at `0, 1, 2, ...` and label them by name. A chart widget asks its data through [`ChartModel`]: panes on one time axis, each giving its vertical axis for what shows and drawing its series. A line is drawn as one vertical span per pixel column, shaded at its ends, so a million points cost about as much as the plot is wide and every high and low still shows (M4 aggregation). No GPU dependency: hand the canvas's instances to `densa-render` (or any other backend) to show them. Painting allocates nothing once the label characters are cached. ```rust use densa_chart::{paint_line, points_within, y_extent, Axis, LineStyle, Plot, Rect}; use densa_draw::{Canvas, InstanceBuffer}; use densa_text::{AtlasConfig, GlyphCache}; // A day of readings, one a second: (seconds since the epoch, value). let readings: Vec<(f64, f64)> = (0..86_400) .map(|s| (1_792_368_000.0 + f64::from(s), (f64::from(s) / 3_600.0).sin())) .collect(); // The last hour, scaled to what it holds. let x = Axis::time(1_792_450_800.0, 1_792_454_400.0); let (low, high) = y_extent(&readings[..], points_within(&readings[..], &x)).unwrap(); let plot = Plot::new(Rect::new(0.0, 0.0, 800.0, 300.0), x, Axis::number(low, high).padded(0.05)); let mut cache = GlyphCache::new(AtlasConfig::DEFAULT); let mut out = InstanceBuffer::with_capacity(4_096); let mut canvas = Canvas::begin(&mut cache, &mut out); paint_line(&mut canvas, &plot, &readings[..], LineStyle::new(densa_chart::Color::WHITE)); assert!(out.len() <= 3 * 800, "at most a few rectangles per pixel column"); ``` --- # Crate densa-grid (https://docs.rs/densa-grid) Data grid for densa: columns, cell styles and an allocation-free painter over your own data A data grid over your own data, drawn onto a `densa_draw::Canvas`. Domain-agnostic: an application describes its [`Column`]s and implements [`GridSource`] (one call per visible cell, formatting into a stack buffer); [`GridPainter`] lays the columns out from font metrics, aligns text, fills overflowing numbers with `#` like a spreadsheet, and draws cell styles and glyphs. No GPU dependency: hand the canvas's instances to `densa-render` (or any other backend) to show them. A widget showing a grid asks its data through [`GridModel`] instead: the same cells, plus the rows to scroll through and sorting by column. Painting allocates nothing once the frame's characters are cached. ```rust use densa_draw::{Canvas, Color, InstanceBuffer}; use densa_grid::{Align, CellStyle, CellText, Column, GridPainter, GridSource, Palette}; use densa_text::{AtlasConfig, Font, GlyphCache}; /// Your data, as the grid asks for it: a cell at a time, into a stack buffer. struct Hosts(Vec<(&'static str, f64)>); impl GridSource for Hosts { fn rows(&self) -> usize { self.0.len() } fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle { let (name, load) = self.0[row]; // A cell that does not fit its buffer stays empty rather than failing the frame. let _ = match column { 0 => text.push_str(name), _ => densa_fmt::write_fixed(text, load, 1), }; CellStyle::new(Color::WHITE) } } let columns = vec![Column::new("host", 12, Align::Left), Column::new("load", 6, Align::Right)]; let mut painter = GridPainter::new(columns, 20, Font::bundled_mono()?, 13.0, Palette::new()); let mut cache = GlyphCache::new(AtlasConfig::default()); let mut instances = InstanceBuffer::with_capacity(4096); let mut canvas = Canvas::begin(&mut cache, &mut instances); painter.paint(&Hosts(vec![("web-1", 0.42), ("db-1", 3.5)]), &mut canvas); assert!(instances.len() > 0, "titles, cells and their glyphs"); ``` --- # Crate densa-draw (https://docs.rs/densa-draw) GPU-independent display list for densa: a Canvas that batches rectangles and glyphs into instances A frame's display list, independent of any GPU API. Painters draw through a [`Canvas`]: filled rectangles and glyphs (from a shared `densa_text::GlyphCache`) become [`Instance`]s in an [`InstanceBuffer`], one quad each, in the vertex layout `densa-render` uploads and draws in a single instanced draw call. Keeping the list GPU-free makes every painter testable without a GPU, and lets other backends draw the same list. Drawing allocates nothing once the frame's glyphs are cached. ```rust use densa_draw::{Canvas, Color, InstanceBuffer, Rect}; use densa_text::{AtlasConfig, GlyphCache}; // One glyph cache and one instance buffer per frame loop, reused every frame. let mut cache = GlyphCache::new(AtlasConfig::default()); let mut instances = InstanceBuffer::with_capacity(1024); let mut canvas = Canvas::begin(&mut cache, &mut instances); canvas.fill_rect(Rect::new(0.0, 0.0, 200.0, 20.0), Color::srgb(0x202020)); let saved = canvas.state(); canvas.clip(Rect::new(0.0, 0.0, 100.0, 100.0)); // Cut to the clip on the CPU: the frame stays one draw call. canvas.fill_rect(Rect::new(50.0, 0.0, 200.0, 20.0), Color::WHITE); canvas.restore(saved); assert_eq!(instances.len(), 2); let cut = instances.as_slice()[1]; assert_eq!(cut.rect, [50.0, 0.0, 50.0, 20.0]); // x, y, width, height ``` --- # Crate densa-render (https://docs.rs/densa-render) wgpu renderer for densa: draws a frame's instances in one instanced draw call, with a growing glyph atlas wgpu renderer for densa. [`GpuRenderer`] draws a frame's `densa_draw::Instance`s, as built by a `Canvas` from any painter (grids, paragraphs, your own), with one instanced draw call: each quad is a solid rectangle or a glyph sampled from the atlas. It keeps the atlas texture in step with the glyph cache's updates, growing it on the GPU as the cache grows. Applications built on `densa-window` never call it. A frame loop of your own does: ```rust use densa_draw::{Canvas, InstanceBuffer, Rect}; use densa_render::{AtlasError, Color, GpuRenderer}; use densa_text::{AtlasConfig, GlyphCache}; /// Once per device: the renderer, and a glyph cache kept within the device's texture limit. fn setup(device: &wgpu::Device, format: wgpu::TextureFormat) -> (GpuRenderer, GlyphCache) { let renderer = GpuRenderer::new(device, format, 65_536); let mut cache = GlyphCache::new(AtlasConfig::default()); let _ = cache.limit_side(GpuRenderer::max_atlas_side(device)); (renderer, cache) } /// Each frame: paint through a canvas, follow the glyph atlas, draw in one call. fn frame( device: &wgpu::Device, queue: &wgpu::Queue, (renderer, cache): (&mut GpuRenderer, &mut GlyphCache), instances: &mut InstanceBuffer, target: &wgpu::TextureView, size: (u32, u32), ) -> Result<(), AtlasError> { let mut canvas = Canvas::begin(cache, instances); canvas.fill_rect(Rect::new(10.0, 10.0, 100.0, 20.0), Color::WHITE); // Grids, paragraphs and charts paint onto the same canvas here. renderer.apply_atlas_updates(device, queue, cache.drain_updates())?; let count = renderer.prepare(queue, size, instances.as_slice()); let mut encoder = device.create_command_encoder(&Default::default()); renderer.render(&mut encoder, target, Color::BLACK, count); queue.submit([encoder.finish()]); Ok(()) } ``` --- # Crate densa-paragraph (https://docs.rs/densa-paragraph) Paragraph layout for densa on parley: shaping, wrapping, bidi, alignment, hit-testing and selection Paragraph layout for densa: shaping, line breaking, bidi and alignment. [`TextSystem`] (fonts plus layout scratch space, one per application) turns text and a [`ParagraphStyle`], with optional [`Span`]s, into a [`Paragraph`]: shaped by parley, broken into lines at a maximum width and aligned. A paragraph hands its glyphs out as `densa_text::GlyphRun`s, which a renderer draws (densa-draw's `Canvas::draw_run`), so layout and rendering stay independent of each other. [`TextSelection`] adds what editing needs on top: hit-testing a point to a text position, caret and selection rectangles, and keyboard motions (by character, word and line, in visual order for mixed-direction text). [`TextEditor`] makes text editable: typing, deletion by character, word or line, input-method (IME) composition, and undo and redo, keeping a selection and a laid-out paragraph in step. Laying out allocates and happens when text, style or width change; drawing a laid-out paragraph, hit-testing and moving a selection or a caret do not allocate. # Fonts and scripts [`TextSystem::new`] draws the default `monospace` family with the bundled JetBrains Mono and every character it lacks (Korean, Japanese, Chinese, symbols, ...) with the operating system's fonts; [`TextSystem::bundled`] uses the bundled font alone, the same on every machine. Features: - `system-fonts` (default): find the operating system's fonts. On Linux, fontconfig is loaded at run time; without it, text falls back to the bundled font. - `complex-scripts`: dictionaries for word breaks in Chinese and Japanese and line breaks in Thai, Lao, Khmer and Burmese (several MB). Without it, a double-click in Japanese selects one character. ```rust use densa_paragraph::{ParagraphStyle, TextSystem}; // `TextSystem::new()` also finds the system's fonts; `bundled()` is the same everywhere. let mut text = TextSystem::bundled()?; let style = ParagraphStyle::new(16.0).with_max_width(Some(120.0)); let mut paragraph = text.paragraph("A paragraph wrapped at a width.", &style, &[])?; let wrapped = paragraph.lines().len(); assert!(wrapped > 1); // A new width re-breaks the lines without shaping the text again. paragraph.reflow(None)?; assert_eq!(paragraph.lines().len(), 1); ``` --- # Crate densa-text (https://docs.rs/densa-text) Text for densa: font loading, glyph rasterization and a growable, allocation-free glyph atlas cache Real text for densa: fonts, glyph rasterization and a glyph atlas cache. Domain-agnostic: any widget that draws text goes through these three pieces. - [`Font`] wraps an OpenType/TrueType font (skrifa): character mapping, advances, line metrics. [`Font::bundled_mono`] is JetBrains Mono, shipped under the SIL OFL 1.1. - [`rasterize`] turns a glyph outline into an 8-bit coverage bitmap (zeno), optionally shifted by a fraction of a pixel. - [`GlyphCache`] packs rasterized glyphs into a paged texture atlas (etagere) that grows a page at a time and, once at its limit, evicts the least recently used glyphs (never the current frame's); it queues the [`AtlasUpdate`]s the GPU texture must follow. A warm cache answers lookups without allocating, so per-frame text drawing stays allocation-free; rasterizing a glyph seen for the first time allocates, once. ```rust use densa_text::{quantize_x, AtlasConfig, Font, GlyphCache, GlyphMap}; let mut chars = GlyphMap::new(Font::bundled_mono()?, 14.0); let mut cache = GlyphCache::new(AtlasConfig::default()); cache.begin_frame(); let a = chars.get('A').expect("the bundled font has A"); let (_, subpixel) = quantize_x(10.25); // a pen at x = 10.25 px let placed = cache.get(chars.font(), a.id, chars.px(), subpixel)?; assert!(placed.size[0] > 0.0, "rasterized into the atlas"); assert_eq!(cache.cached_glyphs(), 1); // Asked again (next frame, say): found, not rasterized again. cache.begin_frame(); cache.get(chars.font(), a.id, chars.px(), subpixel)?; assert_eq!(cache.cached_glyphs(), 1); ``` --- # Crate densa-core (https://docs.rs/densa-core) Primitive types shared by every densa crate: Color, Point, Rect Primitive types shared by every densa crate. Dependency-free on purpose: text layout, rendering and application code all agree on these types without depending on each other. ```rust use densa_core::{Color, Point, Rect}; // x, y, width, height. Stored as edges: neighbours built from the same edge value // (`Rect::from_edges`, or `translate`) tile exactly. let cell = Rect::new(10.0, 20.0, 100.0, 18.0); assert!(cell.contains(Point::new(10.0, 20.0))); assert!(!cell.contains(Point::new(110.0, 20.0)), "the right edge is outside"); assert_eq!(cell.intersect(Rect::new(0.0, 0.0, 50.0, 50.0)).width(), 40.0); // Colors are linear RGB; `srgb` reads the hex code a design tool gives. let accent = Color::srgb(0x3B82F6); assert_eq!(accent.to_srgb_hex(), 0x3B82F6); let faint = accent.with_alpha(0.25); assert_eq!(faint.a, 0.25); ``` --- # Crate densa-fmt (https://docs.rs/densa-fmt) Allocation-free number formatting into fixed-capacity stack buffers Allocation-free number formatting into fixed-capacity stack buffers. Floats go through `core::fmt` (exact, allocation-free, identical output to `format!`); only digit grouping, which `core::fmt` lacks, is hand-written. All writes append all-or-nothing: on error the buffer is left exactly as it was. [`civil_from_days`] turns days since the epoch into a calendar date, for writers of dates. ```rust use densa_fmt::{write_fixed, write_grouped, FmtBuf}; // On the stack: nothing here allocates, so it can run in every frame. let mut text = FmtBuf::<32>::new(); write_grouped(&mut text, 1_234_567, ',')?; text.push_str(" at ")?; write_fixed(&mut text, 101.5, 2)?; assert_eq!(text.as_str(), "1,234,567 at 101.50"); // A write that does not fit leaves the buffer as it was. let mut small = FmtBuf::<4>::new(); assert!(write_grouped(&mut small, 1_234_567, ',').is_err()); assert!(small.is_empty()); // Several writes are undone together by truncating back to where they began. let mark = text.len(); let written = text.push_str(" and ").and_then(|()| write_fixed(&mut text, f64::NAN, 2)); if written.is_err() { text.truncate(mark); } assert_eq!(text.as_str(), "1,234,567 at 101.50"); ``` --- # Crate densa-sync (https://docs.rs/densa-sync) Wait-free SPSC triple buffer for low-latency data and UI pipelines, loom-verified Lock-free synchronization primitives for low-latency data → UI pipelines. ```rust use densa_sync::triple_buffer; // A worker thread publishes whole snapshots; the UI thread reads the newest one without // waiting for the worker, and the worker never waits for the UI. let (mut writer, mut reader) = triple_buffer(vec![0.0_f64; 4]); let worker = std::thread::spawn(move || { let mut latest = vec![0.0_f64; 4]; // the worker's own, always current latest[2] = 101.5; // The slot handed out holds an older snapshot: overwrite all of it, never a part. writer.write(|prices| prices.copy_from_slice(&latest)); writer.publish(); }); worker.join().unwrap(); assert!(reader.update(), "a snapshot was published since the last update"); assert_eq!(reader.read(|prices| prices[2]), 101.5); assert!(!reader.update(), "nothing newer"); ``` --- # Crate densa-platform (https://docs.rs/densa-platform) OS services for low-latency threads: CPU topology, thread pinning and priority OS services for low-latency threads: CPU topology, thread pinning, current CPU. On hybrid CPUs (Intel P-cores + E-cores) the scheduler may move a busy-spinning worker between core types, changing its throughput by 2x or more. These helpers let the engine pick a performance core, pin to it, and detect migrations. Implemented for Windows. Elsewhere, queries return [`CpuError::Unsupported`] and [`current_cpu`] returns `None`, so callers degrade to "not pinned". ```rust use densa_platform::{logical_cpus, pick_performance_cpu, pin_current_thread, CpuError}; match logical_cpus() { Ok(cpus) => { // A performance core on a hybrid CPU (any core on another). if let Some(cpu) = pick_performance_cpu(&cpus) { let worker = std::thread::spawn(move || pin_current_thread(cpu)); worker.join().unwrap()?; } } // Not implemented on this system: the thread runs where the scheduler puts it. Err(CpuError::Unsupported) => {} Err(error) => return Err(error.into()), } ``` --- # Crate densa-alloc (https://docs.rs/densa-alloc) Per-thread counting global allocator for proving that hot paths allocate nothing Per-thread allocation counting, to *prove* hot paths never touch the heap. Install [`CountingAllocator`] as the global allocator of a binary or test crate: ```rust #[global_allocator] static ALLOCATOR: densa_alloc::CountingAllocator = densa_alloc::CountingAllocator::system(); ``` then wrap the code under test in [`measure`]. Counters are per thread, so other threads (test harness, workers) never pollute a measurement. Without the allocator installed all counters stay at zero; always check [`is_active`] first so a zero-allocation assertion cannot pass vacuously.