zaxis 0.1.0
Components

Table

Pinned headers, shared column widths, resizing, stable row selection and virtual rows.

Table composes Grid columns with the existing ScrollArea. It owns presentation and interactions; the application owns data and sorting. It supports ordinary variable-height rows and fixed-height virtualization.

The default surface is a rounded card with an eight-pixel radius, subtle border, eight-pixel outer padding and rounded header corners. Row and scrollbar clips stay inside the frame's curved edges, including custom radii or zero requested padding. rounding, border, and padding override the inherited style.

use zaxis::{Column, Id, Table};

let output = Table::new("records")
    .columns([
        Column::fixed("id", 70.0).min_width(45.0).title("ID").sortable(true),
        Column::remainder("name").min_width(130.0).title("Name").sortable(true),
        Column::fixed("enabled", 110.0).title("Enabled"),
    ])
    .max_height(300.0)
    .show_rows(ui, 36.0, records.len(), |body, index| {
        let record = &mut records[index];
        body.row(record.id, |row| {
            row.cell(|ui| { ui.label(record.id.to_string()); });
            row.cell(|ui| { ui.label(&record.name); });
            row.cell(|ui| { ui.checkbox(&mut record.enabled, "Active"); });
        });
    });

if let Some(request) = output.sort_request {
    // Reorder records using request.column and request.direction.
}

For variable-height rows, use show(ui, |body| { ... }) and call body.row(stable_key, build) for each record. Row height is the tallest cell, subject to TableStyle::row_min_height. Any components, nested layouts or custom widgets may be placed in a cell.

Identity and virtual rows

show_rows(ui, full_row_height, total_count, build) delegates visibility and offset clamping to ScrollArea::show_rows. It builds only visible rows; the inner output is their index range. The height is the complete pitch, including cell padding. Content taller than that height is clipped. Call body.row exactly once for each visible callback, with an application key such as a database ID. Use body.row_id(Id, build) for an already constructed ID.

Never use a display index as the key for sortable/changing data. Rows and their controls are scoped by the table, persistent row key and column ID, so sorting or inserting data does not assign another record's identity to them. Virtual row layout caches are retained only for live rows. Bound values belong to the application; keep them outside the callback. Offscreen controls receive no input.

Header and resizing

The header stays above the scrolling body. Horizontal offset moves header cells and body cells together, with exactly the same resolved widths. Width policies and minima are those of Grid. Content widths include header labels and observed row content; virtual tables retain the largest observed content width so scrolling does not make columns jump. A newly encountered wider cell schedules a redraw; the header and all built rows use the same widths on both passes.

Hovering a header edge displays the native system column-resize cursor. Drag to resize its column. Pointer capture and the cursor continue outside the header until release, and min_width clamps the result. A resized column becomes a retained fixed width, keyed by column ID; other flexible columns receive the remaining space. Disable all handles with resizable(false), or a particular handle with Column::resizable(false). Widths and scroll offsets survive hiding and reordering the table. Widths are not persisted to disk by the library.

The table scrolls both axes, using nested wheel routing, middle-button autoscroll and overlay scrollbars from ScrollArea. scroll_offset(Vec2) sets an offset for one pass; subsequent passes retain user scrolling. No separate scrolling engine is involved.

Selection and sorting

Click passive row content or empty cell space to select that row. Interactive cell controls take precedence. selected_row and row_clicked return the application's row ID, never the display position. Selection is retained by default; selected_row(Option<Id>) supplies an application-controlled selection, and selectable(false) disables row activation.

Column::sortable(true) makes a header emit SortRequest { column: Id, direction: SortDirection::{Ascending, Descending} }. Successive clicks toggle the direction. The request appears only on activation. The library does not reorder data or call a comparator. sort(Option<SortRequest>) supplies the currently applied sort indicator if the application controls sorting externally.

Style and output

Style::table holds TableStyle: outer/cell padding, header and minimum row heights, component spacing, font size, surface/text/hover/selection/alternate colors, corner radii and border, separator width/color, resize handle width, striping/separator defaults and ScrollStyle chrome. Body viewport padding and row gaps are zero so header and body column edges match; table and cell padding handle the insets.

Builders include columns, column, style, rounding, border, padding, width, max_height, striped, separators, resizable, selectable, selected_row, sort, and scroll_offset. The default total height limit is 300 logical pixels, clipped to the parent size.

TableOutput contains inner, table rect, header_rect, body viewport, shared widths, final offset, selected_row, row_clicked, sort_request, and the built (row_id, rect) pairs. Virtual output stays proportional to visible rows.

Run cargo run --example grid_table. The example has settings cards, a 10,000-row table, editable checkboxes, resizing and application-side sorting. It uses Immediate presentation without Vsync.

Dragging rows

Table::drag_rows(true) makes rows draggable with RowDrag payloads and before / after targets; the move is reported as TableOutput::row_moved and the model stays yours. With show_rows, call ui.keep_drag_source(row) while the dragged row exists so scrolling it out of the virtual window does not end the drag. Autoscroll applies to the table body. See Drag and drop.

Accessibility

Role Table, named with Table::accessible_label(".."), with the row and column counts of the whole data set. The header is a Row of ColumnHeader nodes; a sortable header accepts Click and the sorted one publishes its direction. Body rows are Row nodes of Cell nodes with their indices; a selectable row publishes its selected state and accepts Click. Widgets inside cells keep their own roles. Rows that were not built have no node; the table accepts scroll requests. See Accessibility.

Edit on GitHub

On this page