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}