diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index c5eef0d4496edea4d30c665c82dc0a9f00bb83be..3d0b86a9523f5ac05e51941c826e32379368c464 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -271,6 +271,13 @@ pub trait Styled: Sized { self } + /// Sets the element to stretch flex items to fill the available space along the container's cross axis. + /// [Docs](https://tailwindcss.com/docs/align-items#stretch) + fn items_stretch(mut self) -> Self { + self.style().align_items = Some(AlignItems::Stretch); + self + } + /// Sets the element to justify flex items against the start of the container's main axis. /// [Docs](https://tailwindcss.com/docs/justify-content#start) fn justify_start(mut self) -> Self { diff --git a/crates/ui/src/components/data_table.rs b/crates/ui/src/components/data_table.rs index 9cd2a5cb7a0d802d170fcfbe6a812027c779d942..6b852f31a503a1dd79d28b90b82eb76638157a27 100644 --- a/crates/ui/src/components/data_table.rs +++ b/crates/ui/src/components/data_table.rs @@ -2,17 +2,17 @@ use std::{ops::Range, rc::Rc}; use gpui::{ AbsoluteLength, AppContext, Context, DefiniteLength, DragMoveEvent, Entity, EntityId, - FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, Point, Stateful, - UniformListScrollHandle, WeakEntity, transparent_black, uniform_list, + FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, ListState, Point, + Stateful, UniformListScrollHandle, WeakEntity, list, transparent_black, uniform_list, }; use crate::{ ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component, ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce, - ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled, StyledExt as _, - StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex, px, - single_example, v_flex, + ScrollAxes, ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled, + StyledExt as _, StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex, + px, single_example, v_flex, }; use itertools::intersperse_with; @@ -22,14 +22,23 @@ const RESIZE_COLUMN_WIDTH: f32 = 8.0; struct DraggedColumn(usize); struct UniformListData { - render_item_fn: Box, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>, + render_list_of_rows_fn: + Box, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>, element_id: ElementId, row_count: usize, } +struct VariableRowHeightListData { + /// Unlike UniformList, this closure renders only single row, allowing each one to have its own height + render_row_fn: Box [AnyElement; COLS]>, + list_state: ListState, + row_count: usize, +} + enum TableContents { Vec(Vec<[AnyElement; COLS]>), UniformList(UniformListData), + VariableRowHeightList(VariableRowHeightListData), } impl TableContents { @@ -37,6 +46,7 @@ impl TableContents { match self { TableContents::Vec(rows) => Some(rows), TableContents::UniformList(_) => None, + TableContents::VariableRowHeightList(_) => None, } } @@ -44,6 +54,7 @@ impl TableContents { match self { TableContents::Vec(rows) => rows.len(), TableContents::UniformList(data) => data.row_count, + TableContents::VariableRowHeightList(data) => data.row_count, } } @@ -519,7 +530,30 @@ impl Table { self.rows = TableContents::UniformList(UniformListData { element_id: id.into(), row_count, - render_item_fn: Box::new(render_item_fn), + render_list_of_rows_fn: Box::new(render_item_fn), + }); + self + } + + /// Enables rendering of tables with variable row heights, allowing each row to have its own height. + /// + /// This mode is useful for displaying content such as CSV data or multiline cells, where rows may not have uniform heights. + /// It is generally slower than [`Table::uniform_list`] due to the need to measure each row individually, but it provides correct layout for non-uniform or multiline content. + /// + /// # Parameters + /// - `row_count`: The total number of rows in the table. + /// - `list_state`: The [`ListState`] used for managing scroll position and virtualization. This must be initialized and managed by the caller, and should be kept in sync with the number of rows. + /// - `render_row_fn`: A closure that renders a single row, given the row index, a mutable reference to [`Window`], and a mutable reference to [`App`]. It should return an array of [`AnyElement`]s, one for each column. + pub fn variable_row_height_list( + mut self, + row_count: usize, + list_state: ListState, + render_row_fn: impl Fn(usize, &mut Window, &mut App) -> [AnyElement; COLS] + 'static, + ) -> Self { + self.rows = TableContents::VariableRowHeightList(VariableRowHeightListData { + render_row_fn: Box::new(render_row_fn), + list_state, + row_count, }); self } @@ -647,7 +681,11 @@ pub fn render_table_row( .column_widths .map_or([None; COLS], |widths| widths.map(Some)); - let mut row = h_flex() + let mut row = div() + // NOTE: `h_flex()` sneakily applies `items_center()` which is not default behavior for div element. + // Applying `.flex().flex_row()` manually to overcome that + .flex() + .flex_row() .id(("table_row", row_index)) .size_full() .when_some(bg, |row, bg| row.bg(bg)) @@ -855,7 +893,7 @@ impl RenderOnce for Table { uniform_list_data.element_id, uniform_list_data.row_count, { - let render_item_fn = uniform_list_data.render_item_fn; + let render_item_fn = uniform_list_data.render_list_of_rows_fn; move |range: Range, window, cx| { let elements = render_item_fn(range.clone(), window, cx); elements @@ -891,6 +929,24 @@ impl RenderOnce for Table { }, ), ), + TableContents::VariableRowHeightList(variable_list_data) => parent.child( + list(variable_list_data.list_state.clone(), { + let render_item_fn = variable_list_data.render_row_fn; + move |row_index: usize, window: &mut Window, cx: &mut App| { + let row = render_item_fn(row_index, window, cx); + render_table_row( + row_index, + row, + table_context.clone(), + window, + cx, + ) + } + }) + .size_full() + .flex_grow() + .with_sizing_behavior(ListSizingBehavior::Auto), + ), }) .when_some( self.col_widths.as_ref().zip(interaction_state.as_ref()), @@ -917,7 +973,7 @@ impl RenderOnce for Table { .read(cx) .custom_scrollbar .clone() - .unwrap_or_else(|| Scrollbars::new(super::ScrollAxes::Both)); + .unwrap_or_else(|| Scrollbars::new(ScrollAxes::Both)); content .custom_scrollbars( scrollbars.tracked_scroll_handle(&state.read(cx).scroll_handle),