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}