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()
109 .size_full()
110 .child(self.column(col_ix, cx).name.clone())
111 }
112
113 /// Render the row at the given row and column.
114 ///
115 /// Not include the table head row.
116 fn render_tr(
117 &mut self,
118 row_ix: usize,
119 window: &mut Window,
120 cx: &mut Context<TableState<Self>>,
121 ) -> Stateful<Div> {
122 div().id(("row", row_ix))
123 }
124
125 /// Render the context menu for the row at the given row index.
126 fn context_menu(
127 &mut self,
128 row_ix: usize,
129 menu: PopupMenu,
130 window: &mut Window,
131 cx: &mut Context<TableState<Self>>,
132 ) -> PopupMenu {
133 menu
134 }
135
136 /// Render cell at the given row and column.
137 fn render_td(
138 &mut self,
139 row_ix: usize,
140 col_ix: usize,
141 window: &mut Window,
142 cx: &mut Context<TableState<Self>>,
143 ) -> impl IntoElement;
144
145 /// Move the column at the given `col_ix` so that it ends up at the index `to_ix`.
146 ///
147 /// e.g.: `let col = self.columns.remove(col_ix); self.columns.insert(to_ix, col);`
148 fn move_column(
149 &mut self,
150 col_ix: usize,
151 to_ix: usize,
152 window: &mut Window,
153 cx: &mut Context<TableState<Self>>,
154 ) {
155 }
156
157 /// Return a Element to show when table is empty.
158 fn render_empty(
159 &mut self,
160 window: &mut Window,
161 cx: &mut Context<TableState<Self>>,
162 ) -> impl IntoElement {
163 h_flex()
164 .size_full()
165 .justify_center()
166 .text_color(cx.theme().muted_foreground.opacity(0.6))
167 .child(Icon::new(IconName::Inbox).size_12())
168 .into_any_element()
169 }
170
171 /// Return true to show the loading view.
172 fn loading(&self, cx: &App) -> bool {
173 false
174 }
175
176 /// Return a Element to show when table is loading, default is built-in Skeleton loading view.
177 ///
178 /// The size is the size of the Table.
179 fn render_loading(
180 &mut self,
181 size: Size,
182 window: &mut Window,
183 cx: &mut Context<TableState<Self>>,
184 ) -> impl IntoElement {
185 Loading::new().size(size)
186 }
187
188 /// Return true to enable load more data when scrolling to the bottom.
189 ///
190 /// Default: false
191 fn has_more(&self, cx: &App) -> bool {
192 false
193 }
194
195 /// Returns a threshold value (n rows), of course, when scrolling to the bottom,
196 /// the remaining number of rows triggers `load_more`.
197 /// This should smaller than the total number of first load rows.
198 ///
199 /// Default: 20 rows
200 fn load_more_threshold(&self) -> usize {
201 20
202 }
203
204 /// Load more data when the table is scrolled to the bottom.
205 ///
206 /// This will performed in a background task.
207 ///
208 /// This is always called when the table is near the bottom,
209 /// so you must check if there is more data to load or lock the loading state.
210 fn load_more(&mut self, window: &mut Window, cx: &mut Context<TableState<Self>>) {}
211
212 /// Render the last empty column, default to empty.
213 fn render_last_empty_col(
214 &mut self,
215 window: &mut Window,
216 cx: &mut Context<TableState<Self>>,
217 ) -> impl IntoElement {
218 h_flex().w_3().h_full().flex_shrink_0()
219 }
220
221 /// Called when the visible range of the rows changed.
222 ///
223 /// The range never ends past `rows_count`; a table whose rows drop to none
224 /// reports `0..0`.
225 ///
226 /// NOTE: Make sure this method is fast, because it will be called frequently.
227 ///
228 /// This can used to handle some data update, to only update the visible rows.
229 /// Please ensure that the data is updated in the background task.
230 fn visible_rows_changed(
231 &mut self,
232 visible_range: Range<usize>,
233 window: &mut Window,
234 cx: &mut Context<TableState<Self>>,
235 ) {
236 }
237
238 /// Called when the visible range of the columns changed.
239 ///
240 /// NOTE: Make sure this method is fast, because it will be called frequently.
241 ///
242 /// This can used to handle some data update, to only update the visible rows.
243 /// Please ensure that the data is updated in the background task.
244 fn visible_columns_changed(
245 &mut self,
246 visible_range: Range<usize>,
247 window: &mut Window,
248 cx: &mut Context<TableState<Self>>,
249 ) {
250 }
251
252 /// Get the text representation of a cell for export purposes (e.g., CSV export).
253 ///
254 /// Returns an empty string by default. Implement this method to support export.
255 /// The text should be formatted as it should appear in the exported data.
256 fn cell_text(&self, row_ix: usize, col_ix: usize, cx: &App) -> String {
257 String::new()
258 }
259}