limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/Data

    Table

    Rows of cells that line up under a header of column labels, for a list the user sorts, filters and reshapes. The host owns the rows and every choice; the table lays them out and reports what was asked.

    Native componentGuide source

    Preview

    CodeSource
    fn view(state: &State, width: f32, palette: ComponentPalette) -> Element {
        let text = |value: &str| Text::new(value, palette).truncate().render();
        let columns = vec![
            TableColumn::flexible("title", "Title", 160.0).sortable(),
            TableColumn::fixed("length", "Length", 56.0).sortable().align_end(),
        ];
        let rows = state
            .tracks
            .iter()
            .map(|track| {
                TableRow::new(format!("track:{}", track.id), vec![text(&track.title), text(&track.length)])
                    .selected(track.id == state.playing)
            })
            .collect();
        Table::new("tracks", columns, width, palette).rows(rows).sort(state.sort.clone()).render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if let Some(key) = id.strip_prefix("tracks:sort:") {
                state.sort = TableSort::toggled(state.sort.take(), key);
                state.sort_tracks();
            } else if let Some(track) = id.strip_prefix("track:") {
                state.play(track);
            }
        }
    }

    Examples

    Each one is the library drawing it. Press, type and hover.

    Track list2 / 2
    CodeSource

    Read the source of this example.

    Narrow
    CodeSource
    card(narrow, tracks(cx, NARROW, narrow - 16.0, "aster", palette))
    Compact
    CodeSource
    card(width.min(560.0), documents(cx, width.min(560.0) - 16.0, palette))
    No rows
    CodeSource
    card(width, tracks(cx, "demo-table-empty", width - 16.0, "not found", palette))

    Table

    Rows of cells that line up under a header of column labels, for a list the user sorts, filters and reshapes. The host owns the rows and every choice; the table lays them out and reports what was asked.

    Import

    use limn::{Component, Icon, RadiusRole, Table, TableColumn, TableRow, TableSizing, TableSort};

    Usage

    A table takes an ID, its columns, the width it is given and the palette. Each row takes an ID and one cell per column.

    fn view(state: &State, width: f32, palette: ComponentPalette) -> Element {
        let text = |value: &str| Text::new(value, palette).truncate().render();
        let columns = vec![
            TableColumn::flexible("title", "Title", 160.0).sortable(),
            TableColumn::fixed("length", "Length", 56.0).sortable().align_end(),
        ];
        let rows = state
            .tracks
            .iter()
            .map(|track| {
                TableRow::new(format!("track:{}", track.id), vec![text(&track.title), text(&track.length)])
                    .selected(track.id == state.playing)
            })
            .collect();
        Table::new("tracks", columns, width, palette).rows(rows).sort(state.sort.clone()).render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if let Some(key) = id.strip_prefix("tracks:sort:") {
                state.sort = TableSort::toggled(state.sort.take(), key);
                state.sort_tracks();
            } else if let Some(track) = id.strip_prefix("track:") {
                state.play(track);
            }
        }
    }

    The width decides which columns fit and how wide the flexible ones are, so pass the width the table really gets. The host filters and sorts the rows; the table shows them in the order given. crates/limn-gallery/src/pages/table.rs is a whole host. Components has the full reference.

    Columns

    TableColumn::fixed("index", "#", 24.0).align_end()
    TableColumn::flexible("title", "Title", 160.0)
    TableColumn::fixed("length", "Length", 56.0).icon(Icon::Clock)

    fixed(key, label, width) is a column of that width. flexible(key, label, min_width) shares the width the fixed ones leave, never under its minimum. key names the column in the table’s actions. align_end sets the label and the cells against the end of the column, for figures. icon shows a glyph in the header in place of the label, which becomes its hint.

    Sorting

    TableColumn::fixed("album", "Album", 180.0).sortable()
    Table::new("tracks", columns, width, palette).sort(state.sort.clone())

    sortable makes the header a button that reports {id}:sort:{key}. sort takes the TableSort the host keeps: the sorted column’s label takes the text color and an arrow, up for ascending and down for descending. TableSort::toggled(current, key) gives what a click leaves: ascending, then descending, then no sort; another column starts over.

    Resizing

    TableColumn::fixed("album", "Album", state.album_width).resizable(80.0)

    resizable(min_width) lets the boundary beside a fixed column be dragged. The drag is reported on {id}:resize:{key}; the host stores the width and passes it back to fixed. The column is never drawn under min_width. A flexible column ignores it.

    A column before the first flexible one grows as its handle goes right, one after it as its handle goes left, and the flexible column takes up the change.

    Optional columns

    TableColumn::fixed("album", "Album", 180.0).optional(state.album_shown).hide_below(460.0)
    Table::new("tracks", columns, width, palette).columns_menu(state.columns_open, "Columns")

    optional(shown) lists the column in the columns menu, checked while shown; a column that is not shown is left out of the table. hide_below(width) leaves a column out while the table is narrower than that.

    columns_menu(open, title) adds a button at the end of the header that opens the list of optional columns, with title as its hint. Without it the same list is still the header’s context menu. menu_palette colors both menus.

    Rows

    TableRow::new("track:7", cells).selected(true).controls(["track:7:more"])

    A row takes one cell per column in the order the columns were given, those not shown included. selected fills the row with the palette’s selected. controls names the controls inside its cells: they keep their own targets, and the row stays lit while the pointer is over one of them.

    A cell hugs its content at the start or the end of its column. A cell the host made .flex(1.0) fills its column.

    let table = Table::new("tracks", columns, width, palette).sticky_header().render();
    ScrollArea::new("tracks-scroll", table).render()

    sticky_header keeps the header at the top of the scroll area around the table while its rows scroll under it, filled with the palette’s surface.

    Sizing and radius

    Table::new("tracks", columns, width, palette)
        .sizing(TableSizing::ROOMY)
        .radius(RadiusRole::Surface)

    TableSizing::COMPACT, the default, is rows 28 high under a header of 28. ROOMY is 56 under 36, for rows that hold artwork and two lines of copy. radius takes the role of the corners of a row’s fill; RadiusRole::Control unless set.

    Wrapping each row

    let (header, rows) = Table::new("tracks", columns, width, palette).rows(rows).into_parts();

    into_parts, in place of render, gives the header and one element per row, for a host that wraps each row in something of its own, such as a context menu. Stack them in one column, the first row header_gap under the header and the rows row_gap apart.

    Keyboard

    KeyEffect
    TabMoves through the sortable headers, the handles and the columns button, then the rows.
    EnterActivates the header, the button or the row in focus.

    Motion

    A row’s fill answers the pointer over motion.hover_ms.

    Actions

    ActionWhen
    Clicked on {id}:sort:{key}The header of a sortable column is activated.
    DragStarted, DragMoved, then Dropped or DragCancelled, on {id}:resize:{key}The boundary of a resizable column is dragged.
    Clicked on {id}:column:{key}An optional column is toggled, in the columns menu or in the header’s context menu.
    Clicked on {id}:columns, and on {id}:columns:dismissThe columns button is activated; its menu is dismissed.
    Clicked on a row’s IDThe row is activated.

    Accessibility

    The header of a sortable column is a button named by the column’s label, also when it shows only an icon, and says whether it is the selected one, the column the rows are sorted by. The columns button is named by its title and says whether its menu is expanded. A row is an item of a list named by the text of its cells, and says whether it is selected. The platform is not told the direction of the sort, nor that the rows and columns form a table.

    API reference

    The guide covers composition, options and actions. Generate the complete Rust type and builder reference from the checkout:

    cargo doc -p limn --open

    Component API overview · State and actions

    The example is drawn by crates/limn-gallery/src/pages/table.rs.