Skip to main content

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}