Skip to main content

gpui_component/table/
column.rs

1use gpui::{
2    Bounds, Context, Edges, Empty, EntityId, IntoElement, ParentElement as _, Pixels, Render,
3    SharedString, Styled as _, TextAlign, Window, div, prelude::FluentBuilder, px,
4};
5
6use crate::ActiveTheme as _;
7
8/// Represents a column in a table, used for initializing table columns.
9#[derive(Debug, Clone)]
10pub struct Column {
11    /// The unique key of the column.
12    ///
13    /// This is used to identify the column in the table and your data source.
14    ///
15    /// In most cases, it should match the field name in your data source.
16    pub key: SharedString,
17    /// The display name of the column.
18    pub name: SharedString,
19    /// The text alignment of the column.
20    pub align: TextAlign,
21    /// The sorting behavior of the column, if any.
22    ///
23    /// If `None`, the column is not sortable.
24    pub sort: Option<ColumnSort>,
25    /// The padding of the column.
26    pub paddings: Option<Edges<Pixels>>,
27    /// The width of the column.
28    pub width: Pixels,
29    /// Whether the column is fixed, the fixed column stays pinned to its side
30    /// when scrolling horizontally.
31    ///
32    /// Left-fixed columns must come first and right-fixed columns last.
33    pub fixed: Option<ColumnFixed>,
34    /// Whether the column is resizable.
35    pub resizable: bool,
36    /// Whether the column is movable.
37    pub movable: bool,
38    /// Whether the column is selectable.
39    ///
40    /// When `true`:
41    /// - In column selection mode: The entire column can be selected
42    /// - In cell selection mode: Individual cells in this column can be selected
43    ///
44    /// When `false`:
45    /// - The column and its cells cannot be selected
46    /// - Useful for action columns (e.g., buttons, checkboxes) that shouldn't participate in selection
47    pub selectable: bool,
48    /// The minimum width of the column.
49    pub min_width: Pixels,
50    /// The maximum width of the column.
51    pub max_width: Pixels,
52}
53
54/// A column group can be used to group multiple columns under a single header.
55#[derive(Debug, Clone)]
56pub struct ColumnGroup {
57    pub label: SharedString,
58    pub span: usize,
59}
60
61impl ColumnGroup {
62    pub fn new(label: impl Into<SharedString>, span: usize) -> Self {
63        Self {
64            label: label.into(),
65            span,
66        }
67    }
68}
69
70impl Default for Column {
71    fn default() -> Self {
72        Self {
73            key: SharedString::new(""),
74            name: SharedString::new(""),
75            align: TextAlign::Left,
76            sort: None,
77            paddings: None,
78            width: px(100.),
79            fixed: None,
80            resizable: true,
81            movable: true,
82            selectable: true,
83            min_width: px(20.0),
84            max_width: px(1200.),
85        }
86    }
87}
88
89impl Column {
90    /// Create a new column with the given key and name.
91    pub fn new(key: impl Into<SharedString>, name: impl Into<SharedString>) -> Self {
92        Self {
93            key: key.into(),
94            name: name.into(),
95            ..Default::default()
96        }
97    }
98
99    /// Set the column to be sortable with custom sort function, default is None (not sortable).
100    ///
101    /// See also [`Column::sortable`] to enable sorting with default.
102    pub fn sort(mut self, sort: ColumnSort) -> Self {
103        self.sort = Some(sort);
104        self
105    }
106
107    /// Set whether the column is sortable, default is true.
108    ///
109    /// See also [`Column::sort`].
110    pub fn sortable(mut self) -> Self {
111        self.sort = Some(ColumnSort::Default);
112        self
113    }
114
115    /// Set whether the column is sort with ascending order.
116    pub fn ascending(mut self) -> Self {
117        self.sort = Some(ColumnSort::Ascending);
118        self
119    }
120
121    /// Set whether the column is sort with descending order.
122    pub fn descending(mut self) -> Self {
123        self.sort = Some(ColumnSort::Descending);
124        self
125    }
126
127    /// Center the column's text, in its header and in its cells.
128    ///
129    /// The table aligns text; a cell that renders its own layout, such as a
130    /// flex row, aligns that layout itself.
131    pub fn text_center(mut self) -> Self {
132        self.align = TextAlign::Center;
133        self
134    }
135
136    /// Right-align the column's text, in its header and in its cells. The
137    /// default is left.
138    ///
139    /// The table aligns text; a cell that renders its own layout, such as a
140    /// flex row, aligns that layout itself.
141    pub fn text_right(mut self) -> Self {
142        self.align = TextAlign::Right;
143        self
144    }
145
146    /// Set the padding of the column, default is None.
147    pub fn paddings(mut self, paddings: impl Into<Edges<Pixels>>) -> Self {
148        self.paddings = Some(paddings.into());
149        self
150    }
151
152    /// Set the padding of the column to 0px.
153    pub fn p_0(mut self) -> Self {
154        self.paddings = Some(Edges::all(px(0.)));
155        self
156    }
157
158    /// Set the width of the column, default is 100px.
159    pub fn width(mut self, width: impl Into<Pixels>) -> Self {
160        self.width = width.into();
161        self
162    }
163
164    /// Set whether the column is fixed, default is false.
165    pub fn fixed(mut self, fixed: impl Into<ColumnFixed>) -> Self {
166        self.fixed = Some(fixed.into());
167        self
168    }
169
170    /// Set whether the column is fixed on left side, default is false.
171    pub fn fixed_left(mut self) -> Self {
172        self.fixed = Some(ColumnFixed::Left);
173        self
174    }
175
176    /// Set whether the column is fixed on right side, default is false.
177    ///
178    /// Right-fixed columns must be the last columns of the table.
179    pub fn fixed_right(mut self) -> Self {
180        self.fixed = Some(ColumnFixed::Right);
181        self
182    }
183
184    /// Set whether the column is resizable, default is true.
185    pub fn resizable(mut self, resizable: bool) -> Self {
186        self.resizable = resizable;
187        self
188    }
189
190    /// Set whether the column is movable, default is true.
191    pub fn movable(mut self, movable: bool) -> Self {
192        self.movable = movable;
193        self
194    }
195
196    /// Set whether the column is selectable, default is true.
197    ///
198    /// When `false`, this column and its cells will not participate in selection:
199    /// - In column selection mode: The column header cannot be clicked to select
200    /// - In cell selection mode: Cells in this column cannot be selected
201    ///
202    /// This is useful for action columns (e.g., with buttons or checkboxes) that
203    /// should not be part of the selection system.
204    ///
205    /// # Example
206    ///
207    /// ```rust,ignore
208    /// Column::new("actions", "Actions")
209    ///     .width(px(100.))
210    ///     .selectable(false)  // Prevent selection of action buttons
211    /// ```
212    pub fn selectable(mut self, selectable: bool) -> Self {
213        self.selectable = selectable;
214        self
215    }
216
217    /// Set the minimum width of the column, default is 20px
218    pub fn min_width(mut self, min_width: impl Into<Pixels>) -> Self {
219        let min_width = min_width.into();
220        self.min_width = min_width;
221
222        // If the current width is smaller than the new minimum,
223        // bump the width up to match the minimum.
224        if self.width < min_width {
225            self.width = min_width;
226        }
227        self
228    }
229
230    /// Set the maximum width when resizing the column, default is 1200px.
231    ///
232    /// The default protects against oversized columns during resizing. Set a
233    /// larger maximum explicitly when wider columns are needed. It does not
234    /// limit an initial width set afterwards with [`Self::width`].
235    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
236        let max_width = max_width.into();
237        self.max_width = max_width;
238
239        // If the current width is larger than the new maximum,
240        // pull the width down to match the maximum.
241        if self.width > max_width {
242            self.width = max_width;
243        }
244        self
245    }
246}
247
248impl FluentBuilder for Column {}
249
250/// The side a fixed column is pinned to when the table scrolls horizontally.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum ColumnFixed {
253    /// Pin to the left side, the column must be one of the first columns.
254    Left,
255    /// Pin to the right side, the column must be one of the last columns.
256    Right,
257}
258
259/// Used to sort the column runtime info in Table internal.
260#[derive(Debug, Clone)]
261pub(crate) struct ColGroup {
262    pub(crate) column: Column,
263    /// This is the runtime width of the column, we may update it when the column is resized.
264    ///
265    /// Including the width with next columns by col_span.
266    pub(crate) width: Pixels,
267    /// The bounds of the column in the table after it renders.
268    pub(crate) bounds: Bounds<Pixels>,
269}
270
271impl ColGroup {
272    pub(crate) fn is_resizable(&self) -> bool {
273        self.column.resizable
274    }
275}
276
277#[derive(Clone)]
278pub(crate) struct DragColumn {
279    pub(crate) entity_id: EntityId,
280    pub(crate) name: SharedString,
281    pub(crate) width: Pixels,
282    pub(crate) col_ix: usize,
283}
284
285/// The sorting behavior of a column.
286#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
287pub enum ColumnSort {
288    /// No sorting.
289    #[default]
290    Default,
291    /// Sort in ascending order.
292    Ascending,
293    /// Sort in descending order.
294    Descending,
295}
296
297impl Render for DragColumn {
298    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
299        div()
300            .px_4()
301            .py_1()
302            .bg(cx.theme().tokens.table_head)
303            .text_color(cx.theme().muted_foreground)
304            .opacity(0.9)
305            .border_1()
306            .border_color(cx.theme().border)
307            .shadow_md()
308            .w(self.width)
309            .min_w(px(100.))
310            .max_w(px(450.))
311            .child(self.name.clone())
312    }
313}
314
315#[derive(Clone)]
316pub(crate) struct ResizeColumn(pub (EntityId, usize));
317impl Render for ResizeColumn {
318    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
319        Empty
320    }
321}