gpui_component/table/delegate.rs
1use std::ops::Range;
2
3use gpui::{
4 App, Context, Div, InteractiveElement as _, IntoElement, ParentElement as _, Pixels,
5 SharedString, Stateful, Styled as _, Window, div,
6};
7
8use crate::{
9 ActiveTheme as _, Icon, IconName, Size, h_flex,
10 menu::PopupMenu,
11 table::{Column, ColumnGroup, ColumnSort, TableState, loading::Loading},
12};
13
14/// A delegate trait for providing data and rendering for a table.
15#[allow(unused)]
16pub trait TableDelegate: Sized + 'static {
17 /// Return the number of columns in the table.
18 fn columns_count(&self, cx: &App) -> usize;
19
20 /// Return the number of rows in the table.
21 fn rows_count(&self, cx: &App) -> usize;
22
23 /// Returns the table column at the given index.
24 ///
25 /// This only call on Table prepare or refresh.
26 fn column(&self, col_ix: usize, cx: &App) -> Column;
27
28 /// Perform sort on the column at the given index.
29 fn perform_sort(
30 &mut self,
31 col_ix: usize,
32 sort: ColumnSort,
33 window: &mut Window,
34 cx: &mut Context<TableState<Self>>,
35 ) {
36 }
37
38 /// Return the width the content of column `col_ix` needs to be read
39 /// whole, or `None` to leave the column as it is.
40 ///
41 /// Called when the column's resize handle is double-clicked. Measure the
42 /// widest of the column's cells and its header as drawn; the table adds
43 /// the cell padding, clamps the sum to the column's `min_width` and
44 /// `max_width`, and emits [`TableEvent::ColumnWidthsChanged`]. A sortable
45 /// column's header also holds the table's sort icon (a 0.75rem icon with
46 /// 2 px of padding on each side), which a header measurement must count.
47 ///
48 /// This is called from inside the table's own update, so an
49 /// implementation must not update the table's entity again.
50 ///
51 /// Returns `None` by default, so a double-click changes nothing.
52 ///
53 /// [`TableEvent::ColumnWidthsChanged`]: crate::table::TableEvent::ColumnWidthsChanged
54 fn fit_column(
55 &mut self,
56 col_ix: usize,
57 window: &mut Window,
58 cx: &mut Context<TableState<Self>>,
59 ) -> Option<Pixels> {
60 None
61 }
62
63 /// Render the table head row.
64 fn render_header(
65 &mut self,
66 window: &mut Window,
67 cx: &mut Context<TableState<Self>>,
68 ) -> Stateful<Div> {
69 div().id("header")
70 }
71
72 /// Return the group headers definitions (can be multi-level).
73 ///
74 /// By default, it returns None, meaning no group headers.
75 fn group_headers(&self, cx: &App) -> Option<Vec<Vec<ColumnGroup>>> {
76 None
77 }
78
79 /// Custom render for a group header cell.
80 /// Receives the group label, the logical col_span, and the pixel width.
81 fn render_group_th(
82 &mut self,
83 label: &SharedString,
84 _col_span: usize,
85 width: Pixels,
86 _window: &mut Window,
87 cx: &mut Context<TableState<Self>>,
88 ) -> impl IntoElement {
89 div()
90 .w(width)
91 .h_full()
92 .flex_shrink_0()
93 .flex()
94 .items_center()
95 .justify_center()
96 .border_r_1()
97 .border_color(cx.theme().border)
98 .child(label.clone())
99 }
100
101 /// Render the header cell at the given column index, default to the column name.
102 fn render_th(
103 &mut self,
104 col_ix: usize,
105 window: &mut Window,
106 cx: &mut Context<TableState<Self>>,
107 ) -> impl IntoElement {
108 div().w_full().child(self.column(col_ix, cx).name.clone())
109 }
110
111 /// Render the row at the given row and column.
112 ///
113 /// Not include the table head row.
114 fn render_tr(
115 &mut self,
116 row_ix: usize,
117 window: &mut Window,
118 cx: &mut Context<TableState<Self>>,
119 ) -> Stateful<Div> {
120 div().id(("row", row_ix))
121 }
122
123 /// Render the context menu for the row at the given row index.
124 fn context_menu(
125 &mut self,
126 row_ix: usize,
127 menu: PopupMenu,
128 window: &mut Window,
129 cx: &mut Context<TableState<Self>>,
130 ) -> PopupMenu {
131 menu
132 }
133
134 /// Render cell at the given row and column.
135 fn render_td(
136 &mut self,
137 row_ix: usize,
138 col_ix: usize,
139 window: &mut Window,
140 cx: &mut Context<TableState<Self>>,
141 ) -> impl IntoElement;
142
143 /// Return the text a screen reader should read for the cell at the given
144 /// row and column, usually the text [`render_td`](Self::render_td) draws.
145 ///
146 /// Plain text drawn by a cell is not exposed to assistive technology, so
147 /// without this a cell is announced with no content. Returns `None` by
148 /// default.
149 fn cell_label(&self, row_ix: usize, col_ix: usize, cx: &App) -> Option<SharedString> {
150 None
151 }
152
153 /// Return the name a screen reader should read for the row at the given
154 /// index, such as when the row is selected and the table has focus.
155 ///
156 /// Defaults to the row's [`cell_label`](Self::cell_label)s, joined with
157 /// commas, or `None` when none of its cells has a label. A name returned
158 /// here replaces an `aria_label` set in [`render_tr`](Self::render_tr).
159 fn row_label(&self, row_ix: usize, cx: &App) -> Option<SharedString> {
160 let mut label = String::new();
161 for text in (0..self.columns_count(cx))
162 .filter_map(|col_ix| self.cell_label(row_ix, col_ix, cx))
163 .filter(|text| !text.is_empty())
164 {
165 if !label.is_empty() {
166 label.push_str(", ");
167 }
168 label.push_str(&text);
169 }
170 (!label.is_empty()).then(|| label.into())
171 }
172
173 /// Move the column at the given `col_ix` so that it ends up at the index `to_ix`.
174 ///
175 /// e.g.: `let col = self.columns.remove(col_ix); self.columns.insert(to_ix, col);`
176 fn move_column(
177 &mut self,
178 col_ix: usize,
179 to_ix: usize,
180 window: &mut Window,
181 cx: &mut Context<TableState<Self>>,
182 ) {
183 }
184
185 /// Return a Element to show when table is empty.
186 fn render_empty(
187 &mut self,
188 window: &mut Window,
189 cx: &mut Context<TableState<Self>>,
190 ) -> impl IntoElement {
191 h_flex()
192 .size_full()
193 .justify_center()
194 .text_color(cx.theme().muted_foreground.opacity(0.6))
195 .child(Icon::new(IconName::Inbox).size_12())
196 .into_any_element()
197 }
198
199 /// Return true to show the loading view.
200 fn loading(&self, cx: &App) -> bool {
201 false
202 }
203
204 /// Return a Element to show when table is loading, default is built-in Skeleton loading view.
205 ///
206 /// The size is the size of the Table.
207 fn render_loading(
208 &mut self,
209 size: Size,
210 window: &mut Window,
211 cx: &mut Context<TableState<Self>>,
212 ) -> impl IntoElement {
213 Loading::new().size(size)
214 }
215
216 /// Return true to enable load more data when scrolling to the bottom.
217 ///
218 /// Default: false
219 fn has_more(&self, cx: &App) -> bool {
220 false
221 }
222
223 /// Returns a threshold value (n rows), of course, when scrolling to the bottom,
224 /// the remaining number of rows triggers `load_more`.
225 /// This should smaller than the total number of first load rows.
226 ///
227 /// Default: 20 rows
228 fn load_more_threshold(&self) -> usize {
229 20
230 }
231
232 /// Load more data when the table is scrolled to the bottom.
233 ///
234 /// This will performed in a background task.
235 ///
236 /// This is always called when the table is near the bottom,
237 /// so you must check if there is more data to load or lock the loading state.
238 fn load_more(&mut self, window: &mut Window, cx: &mut Context<TableState<Self>>) {}
239
240 /// Render the last empty column, default to empty.
241 fn render_last_empty_col(
242 &mut self,
243 window: &mut Window,
244 cx: &mut Context<TableState<Self>>,
245 ) -> impl IntoElement {
246 h_flex().w_3().h_full().flex_shrink_0()
247 }
248
249 /// Called when the visible range of the rows changed.
250 ///
251 /// The range never ends past `rows_count`; a table whose rows drop to none
252 /// reports `0..0`.
253 ///
254 /// NOTE: Make sure this method is fast, because it will be called frequently.
255 ///
256 /// This can used to handle some data update, to only update the visible rows.
257 /// Please ensure that the data is updated in the background task.
258 fn visible_rows_changed(
259 &mut self,
260 visible_range: Range<usize>,
261 window: &mut Window,
262 cx: &mut Context<TableState<Self>>,
263 ) {
264 }
265
266 /// Called when the visible range of the columns changed.
267 ///
268 /// NOTE: Make sure this method is fast, because it will be called frequently.
269 ///
270 /// This can used to handle some data update, to only update the visible rows.
271 /// Please ensure that the data is updated in the background task.
272 fn visible_columns_changed(
273 &mut self,
274 visible_range: Range<usize>,
275 window: &mut Window,
276 cx: &mut Context<TableState<Self>>,
277 ) {
278 }
279
280 /// Get the text representation of a cell for export purposes (e.g., CSV export).
281 ///
282 /// Returns an empty string by default. Implement this method to support export.
283 /// The text should be formatted as it should appear in the exported data.
284 fn cell_text(&self, row_ix: usize, col_ix: usize, cx: &App) -> String {
285 String::new()
286 }
287}