Skip to content

Columns and layout

The schema lists the columns once, in their default order. Users then resize, move, freeze, hide and autofit them; the grid keeps all of that by column id in a layout that an application saves and restores.

GridSchema::new() takes GridColumns and ColumnBands in order. The dock_layout example groups three columns under an Execution band and hides Fee by default:

fn schema() -> GridSchema<TradeColumn> {
GridSchema::new()
.with_frozen(1)
.column(GridColumn::new(TradeColumn::Id, "Id").with_width(px(80.)))
.column(GridColumn::new(TradeColumn::Instrument, "Instrument").with_width(px(120.)))
.band(
ColumnBand::new("execution", "Execution")
.column(GridColumn::new(TradeColumn::Side, "Side").with_width(px(70.)))
.column(GridColumn::new(TradeColumn::Quantity, "Quantity"))
.column(GridColumn::new(TradeColumn::Price, "Price")),
)
.column(GridColumn::new(TradeColumn::Venue, "Venue").with_width(px(90.)))
.column(
GridColumn::new(TradeColumn::Fee, "Fee")
.with_width(px(90.))
.with_visible(false),
)
}
GridColumn builder Sets
with_width, with_min_width, with_max_width The width and its limits
with_zone(ColumnZone::FrozenLeft / Scrolling / FrozenRight) Where the column sits; GridSchema::with_frozen(n) freezes the first n on the left
with_align Left, right, centre or by value (Auto: numbers right, text left)
with_visible(false) Hidden at start
with_resizable, with_movable, with_sortable, with_groupable, with_hideable What the user may do with it; all on by default
with_icon(icon), with_icon_only(icon) An icon before the title, or the icon alone; the chooser, the tooltip and screen readers keep the title
with_number_format, with_image_fit, with_image_corner_radius How values show (Cells)
with_content(CellContent::Element / Control) A real element per visible cell (Cells)
with_internal(true) A helper column the view can sort, group and aggregate by but nobody sees

An internal column stays hidden whatever a layout, Show all or set_column_visible say; the chooser leaves it out, and a copy or an export of the visible columns does not write it (one that names it in ExportRequest::with_columns does).

The orders schema freezes Id on the left and a Cancel button column on the right, and shows Status as an icon-only header:

/// The columns, their widths and contents: Id frozen left; Side a tag,
/// Filled a progress bar (elements); Active a checkbox and Cancel a button
/// (controls), Cancel frozen right; Status an icon, its header the icon
/// alone.
pub fn schema() -> GridSchema<OrderColumn> {
COLUMN_WIDTHS
.into_iter()
.fold(GridSchema::new(), |schema, (column, title, width)| {
let grid_column = GridColumn::new(column, title).with_width(px(width));
schema.column(match column {
OrderColumn::Id => grid_column.with_zone(ColumnZone::FrozenLeft),
OrderColumn::Side | OrderColumn::Filled => {
grid_column.with_content(CellContent::Element)
}
OrderColumn::Active => grid_column.with_content(CellContent::Control),
// An action column: frozen right, always on screen.
OrderColumn::Cancel => grid_column
.with_content(CellContent::Control)
.with_zone(ColumnZone::FrozenRight),
OrderColumn::Status => grid_column
.with_icon_only(IconName::Info)
.with_align(ColumnAlign::Auto),
_ => grid_column,
})
})
}
  • Resize by dragging a header’s edge; a double click on the edge autofits the column to its visible rows (with_autofit_policy replaces the measure). autofit_column and autofit_columns do it from code.
  • Move by dragging a header, or with Move left and Move right in its menu. A band moves as a whole and its columns stay inside it (BandedMove); with_column_move_policy(FreeMove) lets a column leave its band.
  • Sort with a click on a header: ascending, descending, none; Shift adds a column to the sort (with_sort_cycle replaces the cycle).
  • The header menu has the sort commands, Group by this column, Freeze left, Freeze right and Unfreeze, Move left and Move right, Autofit, Hide column, Show all columns, Choose columns… and Reset columns.

From code: set_column_visible, show_all_columns, move_column, move_band, set_column_zone, set_column_width, all by column.

“Choose columns…” in the header menu opens a popover: the columns by zone, band members indented, a search field from 10 columns on, a checkbox to show or hide each, a grip and Alt+↑/↓ to reorder, Show all and Reset columns. Changes apply at once and arrive as LayoutChange, like a change made with the mouse.

show_column_chooser(window, cx) opens it from a toolbar. ColumnChooserPanel is the same panel as an entity, for an application that shows it in its own popover, dialog or sheet; with_column_chooser replaces what “Choose columns…” opens.

A toolbar or a status bar drives any grid without knowing its delegate type through LiveGridHandle::new(&grid). It reads the columns (ColumnInfo, BandInfo) and commands the grid by column id: visibility, order, zones, widths, the layout, the sort, grouping, expansion, the quick filter, counts, the held order and the reorder policy, copy and export to a path. An unknown id returns false. blotter’s status bar follows its grid through one:

handle.subscribe(cx, move |event, cx| {
let _ = this.update(cx, |root, cx| {
let total = root.handle.total_row_count(cx);
if root.status.apply(event, total) {
cx.notify();
}
});
}),

Each handle method is one read or one update of the grid: never call it from inside an update of the same grid, such as a delegate hook.

A GridLayout holds the columns by id in display order, each with its width, visibility and zone, the sort, and the grouping with the expansion of its groups. It does not hold the quick filter, the scroll position, the selection, a tree, or a grouping by a function.

Method Does
layout(), default_layout() The current layout, and the schema’s
with_layout(layout), set_layout(&layout, cx), reset_layout(cx) Apply a layout at start, later, or go back to the schema’s
export_layout(), import_layout(text, cx) The layout as text (JsonLayoutCodec, version 1) and back
with_view_id(id) Names the kind of grid: a layout saved for another view is refused
with_layout_codec, with_layout_merge Another text format; another rule for a layout that meets a changed schema

A saved layout survives schema changes: unknown columns are ignored and new ones are added (AppendNewColumns). Save it on LayoutChange, SortChange, GroupChange and ExpansionChange; the Cookbook has the code, and dock_layout keeps named layouts and each dock panel’s grid in one file.

With the derive feature, #[derive(GridColumns)] writes the column enum’s implementation:

gpui-kit-grid = { git = "https://github.com/GigLaboCom/gpui-kit-grid", tag = "v1.0.0", features = ["derive"] }
/// The columns of the `quickstart` example, derived.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, GridColumns)]
enum TradeColumn {
Id,
Instrument,
Side,
Quantity,
Price,
}

The declaration order is the schema order, and an id is the variant name in snake_case, as a hand-written implementation spells it, so saved layouts survive the switch. #[grid_columns(rename_all = "…")] changes the rule and #[grid_column(id = "…")] sets one id:

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, GridColumns)]
#[grid_columns(rename_all = "kebab-case")]
enum Column {
Id,
BidSize,
HTTPStatus,
Leg2Price,
// An explicit id wins over the rule; any text but an empty one.
#[grid_column(id = "Last price")]
LastPrice,
}
assert_eq!(
ids::<Column>(),
["id", "bid-size", "http-status", "leg2-price", "Last price"]
);

A missing variant, a shifted index, a repeated id and more than 128 columns are compile errors. The feature is off by default, and the hand-written implementation stays supported.

Apache-2.0, built on gpui-kit. Repository Sponsor More from GigLabo Privacy Licenses