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