Skip to main content

Module tree_table_view

Module tree_table_view 

Source
Expand description

TreeTableView<T> — hierarchical multi-column data table with expand/collapse.

Sibling of TableView for tree-shaped data. Each row carries a depth level; one designated column (the tree column, defaulting to the first) shows a twist (chevron) and an indent gutter that toggles the row’s children. Backed by a SortFilterTreeModel<T> so sort, filter, and expand state compose without extra bookkeeping. Shares the header (a hosted TableHeader), column, keyboard, and selection modules with TableView.

Rows live in a TreeBodyPane — a sibling of the scrollbar — so buffer-exit / selection / expand rebuilds are never deferred mid-thumb-drag. Three row-height modes: uniform (row_height, fast path), exact per-flat-index callback (row_height_fn), and auto-measured (auto_row_height — grows to tallest cell).

§Common patterns

A checkbox column. Selection and “checked” are different things — a checkbox column wants its own state, with parent/child propagation. Build it from TreeCheckedModel over the same tree the view projects.

A cell delegate receives (&T, &CellContext) and CellContext carries no node identity — only row_index. So capture the projection and resolve the row’s NodeId through it:

ⓘ
let proxy = SortFilterTreeModel::new(tree);
let checks = TreeCheckedModel::new(proxy.tree());
let for_cells = proxy.clone();
let col = Column::new("done", lit!("Done"), move |_item, cx: &CellContext| {
    match for_cells.visible_node_id(cx.row_index) {
        Some(node) => Box::new(Checkbox::new(checks.check_state(node))) as Box<dyn Widget>,
        None => Box::new(Spacer::new()),
    }
});

For a tree whose identity is a domain key rather than a NodeId, use KeyedTreeCheckedModel instead — it survives a full re-source, which a NodeId-keyed set cannot.

Group rows that span the table, and the group kept in sight. A tree whose upper levels are groups — songs under albums under artists — draws those rows as one band across every column with full_width_row and full_width_row_delegate, and keeps the groups being scrolled through pinned under the header with pinned_ancestors:

ⓘ
let view = TreeTableView::from_projection(proxy)
    .columns(columns)
    .full_width_row(|row: &Row| !row.is_song())
    .full_width_row_delegate(|row, _cx: &CellContext| {
        Box::new(TextWidget::new(lit!(row.summary()))) as Box<dyn Widget>
    })
    .pinned_ancestors(2); // the artist, then the album

A band is still a row of the tree — it selects, drags, expands from its chevron and answers type-ahead — and one cell to the keyboard and to assistive technology. The pinned rows are copies, pushed up as the next group arrives, hidden from assistive technology, and every reveal stops below them. The two builders say the rest.

§Accessibility

Root emits Role::TreeGrid; rows carry set_level + set_expanded. ArrowLeft / ArrowRight on the tree column collapse / expand. A full-width row holds one cell, spanning every column.

ⓘ
// Column delegates capture closures — use ignore.
use teksilo_widgets::TreeTableView;
use teksilo_data::TreeModel;
let _view = TreeTableView::new(model).row_height(28.0);

§Scrolling to a row

handle returns a TreeTableViewHandle, a cheap Clone taken before the view is added and usable from any handler afterwards. Its scroll_to_row and ensure_row_visible take a flat visible row index and are applied at the next layout pass, after row measurement and scroll anchoring, so they may follow an expansion in the same turn, or be called before mount:

let view = TreeTableView::new(model);
let handle = view.handle();
// … add the columns, `ctx.add(view)`; then, from a handler, after
// expanding the row's ancestors in the same turn:
handle.ensure_row_visible(42);

The builder-value methods of the same names compute the offset when they are called, against the last layout pass.

§Pan to scroll

The view installs common::scrollable::ScrollableBehavior, which gives it the shared wheel arithmetic, a finger’s pan and the PanClaim that puts it on a pan’s claimant chain. A pan scrolls it, the release coasts, and a pan it cannot absorb hands the whole event to the container outside — never a residual. A pan that starts on a row scrolls rather than activating it or collapsing a multi-selection onto it. Both axes are claimed. Shift+wheel still scrolls the columns, and a finger’s pan is never remapped by a held Shift: the remap is a wheel convention, and turning a drag sideways is not what the hand asked for.

Structs§

TreeTableView
Hierarchical multi-column widget. See module documentation.
TreeTableViewHandle
A handle on a mounted TreeTableView, to scroll it to a row from outside the widget — a view-model revealing the node a search found, say.