Skip to content

Cells

Most cells are painted by the grid’s body directly: text, numbers, times, icons, images and links, with no element per cell. A column that needs a real gpui-kit element (a progress bar, a checkbox, a button) asks for one, and gets it only in visible rows.

GridRow::value returns a CellValue that borrows from the row, so it never allocates. orders uses most kinds:

fn value(&self, column: OrderColumn) -> CellValue<'_> {
match column {
OrderColumn::Id => CellValue::Int(self.id as i64),
OrderColumn::Account => CellValue::Text(self.account),
OrderColumn::Instrument => CellValue::Text(self.instrument),
OrderColumn::Side => CellValue::Text(self.side),
OrderColumn::Quantity => CellValue::Int(self.quantity),
OrderColumn::Limit => CellValue::Float(self.limit),
OrderColumn::Last => CellValue::Float(self.last),
OrderColumn::Filled => CellValue::Float(self.filled),
OrderColumn::Status => CellValue::Icon(self.status.icon()),
OrderColumn::Tif => CellValue::Text(self.tif),
OrderColumn::Expiry => CellValue::Timestamp(self.expiry),
OrderColumn::Active => CellValue::Bool(self.active),
OrderColumn::Note => CellValue::Text(&self.note),
OrderColumn::Placed => CellValue::Timestamp(self.placed),
OrderColumn::Cancel => CellValue::Empty,
}
}
Variant Shows Notes
Empty Nothing
Bool TRUE / FALSE
Int, Float A number, aligned right A Float shows two decimals unless its column says otherwise
Decimal A number with its own scale With the decimal feature; sums exactly
Text Text
Timestamp UTC as 2026-11-04 12:34:56, aligned left Another zone or format is a Custom value with its own text
Icon(IconValue::new(path, text)) A one-colour SVG asset in the text colour, centred 14 px in rows up to 30 px, 16 px above
Image(ImageValue::new(id, text)) A raster image or a colour SVG Found by the grid’s image source
Link(LinkValue::new(text, url)) The text as a link A click on the text or Enter on the cell opens it
Custom(&dyn GridValue) A value of your own type Sorts, writes and exports by its own rules

An icon, an image or a link comes with its text, and sorting, the quick filter, copy, export, the tooltip and a screen reader use the text.

GridColumn::with_number_format(NumberFormat::new().with_decimals(1)) shows a column’s floats, and its aggregates, with one decimal. A copy, a CSV file, the quick filter and an export’s NumberHint follow the column’s format, so they give what the grid shows (-0.49, not -0.4899999999999949). Decimal keeps its own scale.

The default image source, AssetImageSource, loads through GPUI’s asset loader (the application’s assets, files, http(s):// addresses) and keeps every image it loaded. AssetImageSource::default().with_capacity(n) keeps the n most recently used, and with_image_source takes your own CellImageSource. GridColumn::with_image_fit and with_image_corner_radius set how an image sits in its cell.

A Link shows its text in the theme’s link colour, underlined, with the pointer cursor over the text (the one place the grid shows it). It sorts, filters and copies as its text; an export format finds the address in ExportCell::value, and a screen reader hears “link” after it. with_link_opener replaces OpenInBrowser (App::open_url) with your own LinkOpener.

Custom(&dyn GridValue) takes any type that implements GridValue. ops ranks alert severities worst first, and reads them as their label:

/// A severity is a cell value of its own (`CellValue::Custom`): it sorts by
/// rank, worst first, and reads as its label ("Critical") in a copy, the
/// search box and a screen reader.
impl GridValue for Severity {
fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
match other.downcast_ref::<Severity>() {
Some(other) => self.cmp(other),
None => TypeId::of::<Self>().cmp(&other.as_any().type_id()),
}
}
fn write_text(&self, out: &mut String) {
out.push_str(self.label());
}
}
  • cmp_value orders it, for sorting, grouping and Min/Max aggregates.
  • write_text writes what the cell shows, and what a copy, the quick filter and a screen reader read.
  • export_value writes what an export writes.
  • to_owned_value returning OwnedValue::custom(self.clone()) keeps the value itself as a group’s key.

They run in prepaint and in background sorts, so they must not allocate or lock.

LiveGridDelegate::cell_style adjusts each visible cell’s style after the change painter, through CellStyle: set_background, set_foreground, set_font_weight, set_trend (an arrow) and set_error_mark. The quick start colours sell trades. CellStyleContext gives the row, the column, the value, the change and the edit state of the cell.

aggregate_style does the same for aggregate cells on captions, footers and totals, with AggregateLine to tell them apart. Colours come from the gpui-kit theme’s semantic tokens (table_*, success, warning, danger and the rest), so a grid follows the application’s theme.

A cell whose text is truncated shows its full text as a tooltip, and so does a truncated header. LiveGridDelegate::tooltip returns your own for a cell, header_tooltip for a header.

A column with CellContent::Element (a progress bar, a tag) or CellContent::Control (a checkbox, a button) gets a real gpui-kit element in each visible cell from LiveGridDelegate::render_cell:

/// 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,
})
})
}

In orders, the Filled column is a progress bar:

OrderColumn::Filled => {
let filled = match cell.value() {
CellValue::Float(filled) => filled as f32,
_ => 0.,
};
Progress::new("filled")
.value(filled * 100.)
.accessibility_label(format!("{:.0}% filled", filled * 100.))
.w_full()
.into_any_element()
}

and Active is a checkbox that commits like an editor, by click or by Space on the focused cell:

OrderColumn::Active => {
let checked = matches!(cell.value(), CellValue::Bool(true));
let edit = cell.edit();
if let Some(edit) = edit.clone() {
cell.on_activate(move |window, cx| {
edit.commit(OwnedValue::Bool(!checked), window, cx);
});
}
Checkbox::new("active")
.checked(checked)
.with_size(cell.size())
.tab_stop(false)
.disabled(order.status.is_final())
.accessibility_label("Active")
.on_click(move |_, window, cx| {
if let Some(edit) = &edit {
edit.commit(OwnedValue::Bool(!checked), window, cx);
}
})
.into_any_element()
}
  • render_cell runs in prepaint, for visible cells only. It may allocate (an element is an allocation) but must not read or update the grid entity; the element’s own handlers run later and may write to the store.
  • Return None for a column that should show its value as text.
  • The element lives inside its row’s element, keyed by row key and column id, so its state follows the row through a re-sort.
  • A press reaches the element first. Unless the element stops it, it is also a press on the cell: selection, the menu, a double click and Click work as on any cell.
  • The keyboard focus stays with the grid, which is one Tab stop: give focusable elements tab_stop(false). Space on the focused cell of a Control column calls the handler registered with CellElementContext::on_activate; Enter stays the primary command.
  • A fading row keeps its elements until the fade ends: cell.change() shows RowChange::Removed, and change.intensity() how far it has faded. They take no press, Space or edit.
  • Group captions, footers and totals show text, not elements. An element sits inside the cell’s padding.

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