gpui_component/table/
state.rs

1use std::{ops::Range, rc::Rc, time::Duration};
2
3use crate::{
4    ActiveTheme, Icon, IconName, StyleSized as _, StyledExt, VirtualListScrollHandle,
5    actions::{Cancel, SelectDown, SelectUp},
6    h_flex,
7    menu::{ContextMenuExt, PopupMenu},
8    scroll::{ScrollableMask, Scrollbar},
9    v_flex,
10};
11use gpui::{
12    AppContext, Axis, Bounds, ClickEvent, Context, Div, DragMoveEvent, EventEmitter, FocusHandle,
13    Focusable, InteractiveElement, IntoElement, ListSizingBehavior, MouseButton, MouseDownEvent,
14    ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful,
15    StatefulInteractiveElement as _, Styled, Task, UniformListScrollHandle, Window, canvas, div,
16    prelude::FluentBuilder, px, uniform_list,
17};
18
19use super::*;
20
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22enum SelectionState {
23    Column,
24    Row,
25}
26
27/// The Table event.
28#[derive(Clone)]
29pub enum TableEvent {
30    /// Single click or move to selected row.
31    SelectRow(usize),
32    /// Double click on the row.
33    DoubleClickedRow(usize),
34    /// Selected column.
35    SelectColumn(usize),
36    /// The column widths have changed.
37    ///
38    /// The `Vec<Pixels>` contains the new widths of all columns.
39    ColumnWidthsChanged(Vec<Pixels>),
40    /// A column has been moved.
41    ///
42    /// The first `usize` is the original index of the column,
43    /// and the second `usize` is the new index of the column.
44    MoveColumn(usize, usize),
45}
46
47/// The visible range of the rows and columns.
48#[derive(Debug, Default)]
49pub struct TableVisibleRange {
50    /// The visible range of the rows.
51    rows: Range<usize>,
52    /// The visible range of the columns.
53    cols: Range<usize>,
54}
55
56impl TableVisibleRange {
57    /// Returns the visible range of the rows.
58    pub fn rows(&self) -> &Range<usize> {
59        &self.rows
60    }
61
62    /// Returns the visible range of the columns.
63    pub fn cols(&self) -> &Range<usize> {
64        &self.cols
65    }
66}
67
68/// The state for [`Table`].
69pub struct TableState<D: TableDelegate> {
70    focus_handle: FocusHandle,
71    delegate: D,
72    pub(super) options: TableOptions,
73    /// The bounds of the table container.
74    bounds: Bounds<Pixels>,
75    /// The bounds of the fixed head cols.
76    fixed_head_cols_bounds: Bounds<Pixels>,
77
78    col_groups: Vec<ColGroup>,
79
80    /// Whether the table can loop selection, default is true.
81    ///
82    /// When the prev/next selection is out of the table bounds, the selection will loop to the other side.
83    pub loop_selection: bool,
84    /// Whether the table can select column.
85    pub col_selectable: bool,
86    /// Whether the table can select row.
87    pub row_selectable: bool,
88    /// Whether the table can sort.
89    pub sortable: bool,
90    /// Whether the table can resize columns.
91    pub col_resizable: bool,
92    /// Whether the table can move columns.
93    pub col_movable: bool,
94    /// Enable/disable fixed columns feature.
95    pub col_fixed: bool,
96
97    pub vertical_scroll_handle: UniformListScrollHandle,
98    pub horizontal_scroll_handle: VirtualListScrollHandle,
99
100    selected_row: Option<usize>,
101    selection_state: SelectionState,
102    right_clicked_row: Option<usize>,
103    selected_col: Option<usize>,
104
105    /// The column index that is being resized.
106    resizing_col: Option<usize>,
107
108    /// The visible range of the rows and columns.
109    visible_range: TableVisibleRange,
110
111    _measure: Vec<Duration>,
112    _load_more_task: Task<()>,
113}
114
115impl<D> TableState<D>
116where
117    D: TableDelegate,
118{
119    /// Create a new TableState with the given delegate.
120    pub fn new(delegate: D, _: &mut Window, cx: &mut Context<Self>) -> Self {
121        let mut this = Self {
122            focus_handle: cx.focus_handle(),
123            options: TableOptions::default(),
124            delegate,
125            col_groups: Vec::new(),
126            horizontal_scroll_handle: VirtualListScrollHandle::new(),
127            vertical_scroll_handle: UniformListScrollHandle::new(),
128            selection_state: SelectionState::Row,
129            selected_row: None,
130            right_clicked_row: None,
131            selected_col: None,
132            resizing_col: None,
133            bounds: Bounds::default(),
134            fixed_head_cols_bounds: Bounds::default(),
135            visible_range: TableVisibleRange::default(),
136            loop_selection: true,
137            col_selectable: true,
138            row_selectable: true,
139            sortable: true,
140            col_movable: true,
141            col_resizable: true,
142            col_fixed: true,
143            _load_more_task: Task::ready(()),
144            _measure: Vec::new(),
145        };
146
147        this.prepare_col_groups(cx);
148        this
149    }
150
151    /// Returns a reference to the delegate.
152    pub fn delegate(&self) -> &D {
153        &self.delegate
154    }
155
156    /// Returns a mutable reference to the delegate.
157    pub fn delegate_mut(&mut self) -> &mut D {
158        &mut self.delegate
159    }
160
161    /// Set to loop selection, default to true.
162    pub fn loop_selection(mut self, loop_selection: bool) -> Self {
163        self.loop_selection = loop_selection;
164        self
165    }
166
167    /// Set to enable/disable column movable, default to true.
168    pub fn col_movable(mut self, col_movable: bool) -> Self {
169        self.col_movable = col_movable;
170        self
171    }
172
173    /// Set to enable/disable column resizable, default to true.
174    pub fn col_resizable(mut self, col_resizable: bool) -> Self {
175        self.col_resizable = col_resizable;
176        self
177    }
178
179    /// Set to enable/disable column sortable, default true
180    pub fn sortable(mut self, sortable: bool) -> Self {
181        self.sortable = sortable;
182        self
183    }
184
185    /// Set to enable/disable row selectable, default true
186    pub fn row_selectable(mut self, row_selectable: bool) -> Self {
187        self.row_selectable = row_selectable;
188        self
189    }
190
191    /// Set to enable/disable column selectable, default true
192    pub fn col_selectable(mut self, col_selectable: bool) -> Self {
193        self.col_selectable = col_selectable;
194        self
195    }
196
197    /// When we update columns or rows, we need to refresh the table.
198    pub fn refresh(&mut self, cx: &mut Context<Self>) {
199        self.prepare_col_groups(cx);
200    }
201
202    /// Scroll to the row at the given index.
203    pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
204        self.vertical_scroll_handle
205            .scroll_to_item(row_ix, ScrollStrategy::Top);
206        cx.notify();
207    }
208
209    // Scroll to the column at the given index.
210    pub fn scroll_to_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
211        let col_ix = col_ix.saturating_sub(self.fixed_left_cols_count());
212
213        self.horizontal_scroll_handle
214            .scroll_to_item(col_ix, ScrollStrategy::Top);
215        cx.notify();
216    }
217
218    /// Returns the selected row index.
219    pub fn selected_row(&self) -> Option<usize> {
220        self.selected_row
221    }
222
223    /// Sets the selected row to the given index.
224    pub fn set_selected_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
225        let is_down = match self.selected_row {
226            Some(selected_row) => row_ix > selected_row,
227            None => true,
228        };
229
230        self.selection_state = SelectionState::Row;
231        self.right_clicked_row = None;
232        self.selected_row = Some(row_ix);
233        if let Some(row_ix) = self.selected_row {
234            self.vertical_scroll_handle.scroll_to_item(
235                row_ix,
236                if is_down {
237                    ScrollStrategy::Bottom
238                } else {
239                    ScrollStrategy::Top
240                },
241            );
242        }
243        cx.emit(TableEvent::SelectRow(row_ix));
244        cx.notify();
245    }
246
247    /// Returns the selected column index.
248    pub fn selected_col(&self) -> Option<usize> {
249        self.selected_col
250    }
251
252    /// Sets the selected col to the given index.
253    pub fn set_selected_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
254        self.selection_state = SelectionState::Column;
255        self.selected_col = Some(col_ix);
256        if let Some(col_ix) = self.selected_col {
257            self.scroll_to_col(col_ix, cx);
258        }
259        cx.emit(TableEvent::SelectColumn(col_ix));
260        cx.notify();
261    }
262
263    /// Clear the selection of the table.
264    pub fn clear_selection(&mut self, cx: &mut Context<Self>) {
265        self.selection_state = SelectionState::Row;
266        self.selected_row = None;
267        self.selected_col = None;
268        cx.notify();
269    }
270
271    /// Returns the visible range of the rows and columns.
272    ///
273    /// See [`TableVisibleRange`].
274    pub fn visible_range(&self) -> &TableVisibleRange {
275        &self.visible_range
276    }
277
278    fn prepare_col_groups(&mut self, cx: &mut Context<Self>) {
279        self.col_groups = (0..self.delegate.columns_count(cx))
280            .map(|col_ix| {
281                let column = self.delegate().column(col_ix, cx);
282                ColGroup {
283                    width: column.width,
284                    bounds: Bounds::default(),
285                    column: column.clone(),
286                }
287            })
288            .collect();
289        cx.notify();
290    }
291
292    fn fixed_left_cols_count(&self) -> usize {
293        if !self.col_fixed {
294            return 0;
295        }
296
297        self.col_groups
298            .iter()
299            .filter(|col| col.column.fixed == Some(ColumnFixed::Left))
300            .count()
301    }
302
303    fn on_row_right_click(
304        &mut self,
305        _: &MouseDownEvent,
306        row_ix: usize,
307        _: &mut Window,
308        _: &mut Context<Self>,
309    ) {
310        self.right_clicked_row = Some(row_ix);
311    }
312
313    fn on_row_left_click(
314        &mut self,
315        e: &ClickEvent,
316        row_ix: usize,
317        _: &mut Window,
318        cx: &mut Context<Self>,
319    ) {
320        self.set_selected_row(row_ix, cx);
321
322        if e.click_count() == 2 {
323            cx.emit(TableEvent::DoubleClickedRow(row_ix));
324        }
325    }
326
327    fn on_col_head_click(&mut self, col_ix: usize, _: &mut Window, cx: &mut Context<Self>) {
328        if !self.col_selectable {
329            return;
330        }
331
332        let Some(col_group) = self.col_groups.get(col_ix) else {
333            return;
334        };
335
336        if !col_group.column.selectable {
337            return;
338        }
339
340        self.set_selected_col(col_ix, cx)
341    }
342
343    fn has_selection(&self) -> bool {
344        self.selected_row.is_some() || self.selected_col.is_some()
345    }
346
347    pub(super) fn action_cancel(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {
348        if self.has_selection() {
349            self.clear_selection(cx);
350            return;
351        }
352        cx.propagate();
353    }
354
355    pub(super) fn action_select_prev(
356        &mut self,
357        _: &SelectUp,
358        _: &mut Window,
359        cx: &mut Context<Self>,
360    ) {
361        let rows_count = self.delegate.rows_count(cx);
362        if rows_count < 1 {
363            return;
364        }
365
366        let mut selected_row = self.selected_row.unwrap_or(0);
367        if selected_row > 0 {
368            selected_row = selected_row.saturating_sub(1);
369        } else {
370            if self.loop_selection {
371                selected_row = rows_count.saturating_sub(1);
372            }
373        }
374
375        self.set_selected_row(selected_row, cx);
376    }
377
378    pub(super) fn action_select_next(
379        &mut self,
380        _: &SelectDown,
381        _: &mut Window,
382        cx: &mut Context<Self>,
383    ) {
384        let rows_count = self.delegate.rows_count(cx);
385        if rows_count < 1 {
386            return;
387        }
388
389        let selected_row = match self.selected_row {
390            Some(selected_row) if selected_row < rows_count.saturating_sub(1) => selected_row + 1,
391            Some(selected_row) => {
392                if self.loop_selection {
393                    0
394                } else {
395                    selected_row
396                }
397            }
398            _ => 0,
399        };
400
401        self.set_selected_row(selected_row, cx);
402    }
403
404    pub(super) fn action_select_prev_col(
405        &mut self,
406        _: &SelectPrevColumn,
407        _: &mut Window,
408        cx: &mut Context<Self>,
409    ) {
410        let mut selected_col = self.selected_col.unwrap_or(0);
411        let columns_count = self.delegate.columns_count(cx);
412        if selected_col > 0 {
413            selected_col = selected_col.saturating_sub(1);
414        } else {
415            if self.loop_selection {
416                selected_col = columns_count.saturating_sub(1);
417            }
418        }
419        self.set_selected_col(selected_col, cx);
420    }
421
422    pub(super) fn action_select_next_col(
423        &mut self,
424        _: &SelectNextColumn,
425        _: &mut Window,
426        cx: &mut Context<Self>,
427    ) {
428        let mut selected_col = self.selected_col.unwrap_or(0);
429        if selected_col < self.delegate.columns_count(cx).saturating_sub(1) {
430            selected_col += 1;
431        } else {
432            if self.loop_selection {
433                selected_col = 0;
434            }
435        }
436
437        self.set_selected_col(selected_col, cx);
438    }
439
440    /// Scroll table when mouse position is near the edge of the table bounds.
441    fn scroll_table_by_col_resizing(
442        &mut self,
443        mouse_position: Point<Pixels>,
444        col_group: &ColGroup,
445    ) {
446        // Do nothing if pos out of the table bounds right for avoid scroll to the right.
447        if mouse_position.x > self.bounds.right() {
448            return;
449        }
450
451        let mut offset = self.horizontal_scroll_handle.offset();
452        let col_bounds = col_group.bounds;
453
454        if mouse_position.x < self.bounds.left()
455            && col_bounds.right() < self.bounds.left() + px(20.)
456        {
457            offset.x += px(1.);
458        } else if mouse_position.x > self.bounds.right()
459            && col_bounds.right() > self.bounds.right() - px(20.)
460        {
461            offset.x -= px(1.);
462        }
463
464        self.horizontal_scroll_handle.set_offset(offset);
465    }
466
467    /// The `ix`` is the index of the col to resize,
468    /// and the `size` is the new size for the col.
469    fn resize_cols(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
470        if !self.col_resizable {
471            return;
472        }
473
474        const MIN_WIDTH: Pixels = px(10.0);
475        const MAX_WIDTH: Pixels = px(1200.0);
476        let Some(col_group) = self.col_groups.get_mut(ix) else {
477            return;
478        };
479
480        if !col_group.is_resizable() {
481            return;
482        }
483        let size = size.floor();
484
485        let old_width = col_group.width;
486        let new_width = size;
487        if new_width < MIN_WIDTH {
488            return;
489        }
490        let changed_width = new_width - old_width;
491        // If change size is less than 1px, do nothing.
492        if changed_width > px(-1.0) && changed_width < px(1.0) {
493            return;
494        }
495        col_group.width = new_width.min(MAX_WIDTH);
496
497        cx.notify();
498    }
499
500    fn perform_sort(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) {
501        if !self.sortable {
502            return;
503        }
504
505        let sort = self.col_groups.get(col_ix).and_then(|g| g.column.sort);
506        if sort.is_none() {
507            return;
508        }
509
510        let sort = sort.unwrap();
511        let sort = match sort {
512            ColumnSort::Ascending => ColumnSort::Default,
513            ColumnSort::Descending => ColumnSort::Ascending,
514            ColumnSort::Default => ColumnSort::Descending,
515        };
516
517        for (ix, col_group) in self.col_groups.iter_mut().enumerate() {
518            if ix == col_ix {
519                col_group.column.sort = Some(sort);
520            } else {
521                if col_group.column.sort.is_some() {
522                    col_group.column.sort = Some(ColumnSort::Default);
523                }
524            }
525        }
526
527        self.delegate_mut().perform_sort(col_ix, sort, window, cx);
528
529        cx.notify();
530    }
531
532    fn move_column(
533        &mut self,
534        col_ix: usize,
535        to_ix: usize,
536        window: &mut Window,
537        cx: &mut Context<Self>,
538    ) {
539        if col_ix == to_ix {
540            return;
541        }
542
543        self.delegate.move_column(col_ix, to_ix, window, cx);
544        let col_group = self.col_groups.remove(col_ix);
545        self.col_groups.insert(to_ix, col_group);
546
547        cx.emit(TableEvent::MoveColumn(col_ix, to_ix));
548        cx.notify();
549    }
550
551    /// Dispatch delegate's `load_more` method when the visible range is near the end.
552    fn load_more_if_need(
553        &mut self,
554        rows_count: usize,
555        visible_end: usize,
556        window: &mut Window,
557        cx: &mut Context<Self>,
558    ) {
559        let threshold = self.delegate.load_more_threshold();
560        // Securely handle subtract logic to prevent attempt to subtract with overflow
561        if visible_end >= rows_count.saturating_sub(threshold) {
562            if !self.delegate.is_eof(cx) {
563                return;
564            }
565
566            self._load_more_task = cx.spawn_in(window, async move |view, window| {
567                _ = view.update_in(window, |view, window, cx| {
568                    view.delegate.load_more(window, cx);
569                });
570            });
571        }
572    }
573
574    fn update_visible_range_if_need(
575        &mut self,
576        visible_range: Range<usize>,
577        axis: Axis,
578        window: &mut Window,
579        cx: &mut Context<Self>,
580    ) {
581        // Skip when visible range is only 1 item.
582        // The visual_list will use first item to measure.
583        if visible_range.len() <= 1 {
584            return;
585        }
586
587        if axis == Axis::Vertical {
588            if self.visible_range.rows == visible_range {
589                return;
590            }
591            self.delegate_mut()
592                .visible_rows_changed(visible_range.clone(), window, cx);
593            self.visible_range.rows = visible_range;
594        } else {
595            if self.visible_range.cols == visible_range {
596                return;
597            }
598            self.delegate_mut()
599                .visible_columns_changed(visible_range.clone(), window, cx);
600            self.visible_range.cols = visible_range;
601        }
602    }
603
604    fn render_cell(&self, col_ix: usize, _window: &mut Window, _cx: &mut Context<Self>) -> Div {
605        let Some(col_group) = self.col_groups.get(col_ix) else {
606            return div();
607        };
608
609        let col_width = col_group.width;
610        let col_padding = col_group.column.paddings;
611
612        div()
613            .w(col_width)
614            .h_full()
615            .flex_shrink_0()
616            .overflow_hidden()
617            .whitespace_nowrap()
618            .table_cell_size(self.options.size)
619            .map(|this| match col_padding {
620                Some(padding) => this
621                    .pl(padding.left)
622                    .pr(padding.right)
623                    .pt(padding.top)
624                    .pb(padding.bottom),
625                None => this,
626            })
627    }
628
629    /// Show Column selection style, when the column is selected and the selection state is Column.
630    fn render_col_wrap(&self, col_ix: usize, _: &mut Window, cx: &mut Context<Self>) -> Div {
631        let el = h_flex().h_full();
632        let selectable = self.col_selectable
633            && self
634                .col_groups
635                .get(col_ix)
636                .map(|col_group| col_group.column.selectable)
637                .unwrap_or(false);
638
639        if selectable
640            && self.selected_col == Some(col_ix)
641            && self.selection_state == SelectionState::Column
642        {
643            el.bg(cx.theme().table_active)
644        } else {
645            el
646        }
647    }
648
649    fn render_resize_handle(
650        &self,
651        ix: usize,
652        _: &mut Window,
653        cx: &mut Context<Self>,
654    ) -> impl IntoElement {
655        const HANDLE_SIZE: Pixels = px(2.);
656
657        let resizable = self.col_resizable
658            && self
659                .col_groups
660                .get(ix)
661                .map(|col| col.is_resizable())
662                .unwrap_or(false);
663        if !resizable {
664            return div().into_any_element();
665        }
666
667        let group_id = SharedString::from(format!("resizable-handle:{}", ix));
668
669        h_flex()
670            .id(("resizable-handle", ix))
671            .group(group_id.clone())
672            .occlude()
673            .cursor_col_resize()
674            .h_full()
675            .w(HANDLE_SIZE)
676            .ml(-(HANDLE_SIZE))
677            .justify_end()
678            .items_center()
679            .child(
680                div()
681                    .h_full()
682                    .justify_center()
683                    .bg(cx.theme().table_row_border)
684                    .group_hover(group_id, |this| this.bg(cx.theme().border).h_full())
685                    .w(px(1.)),
686            )
687            .on_drag_move(
688                cx.listener(move |view, e: &DragMoveEvent<ResizeColumn>, window, cx| {
689                    match e.drag(cx) {
690                        ResizeColumn((entity_id, ix)) => {
691                            if cx.entity_id() != *entity_id {
692                                return;
693                            }
694
695                            // sync col widths into real widths
696                            // TODO: Consider to remove this, this may not need now.
697                            // for (_, col_group) in view.col_groups.iter_mut().enumerate() {
698                            //     col_group.width = col_group.bounds.size.width;
699                            // }
700
701                            let ix = *ix;
702                            view.resizing_col = Some(ix);
703
704                            let col_group = view
705                                .col_groups
706                                .get(ix)
707                                .expect("BUG: invalid col index")
708                                .clone();
709
710                            view.resize_cols(
711                                ix,
712                                e.event.position.x - HANDLE_SIZE - col_group.bounds.left(),
713                                window,
714                                cx,
715                            );
716
717                            // scroll the table if the drag is near the edge
718                            view.scroll_table_by_col_resizing(e.event.position, &col_group);
719                        }
720                    };
721                }),
722            )
723            .on_drag(ResizeColumn((cx.entity_id(), ix)), |drag, _, _, cx| {
724                cx.stop_propagation();
725                cx.new(|_| drag.clone())
726            })
727            .on_mouse_up_out(
728                MouseButton::Left,
729                cx.listener(|view, _, _, cx| {
730                    if view.resizing_col.is_none() {
731                        return;
732                    }
733
734                    view.resizing_col = None;
735
736                    let new_widths = view.col_groups.iter().map(|g| g.width).collect();
737                    cx.emit(TableEvent::ColumnWidthsChanged(new_widths));
738                    cx.notify();
739                }),
740            )
741            .into_any_element()
742    }
743
744    fn render_sort_icon(
745        &self,
746        col_ix: usize,
747        col_group: &ColGroup,
748        _: &mut Window,
749        cx: &mut Context<Self>,
750    ) -> Option<impl IntoElement> {
751        if !self.sortable {
752            return None;
753        }
754
755        let Some(sort) = col_group.column.sort else {
756            return None;
757        };
758
759        let (icon, is_on) = match sort {
760            ColumnSort::Ascending => (IconName::SortAscending, true),
761            ColumnSort::Descending => (IconName::SortDescending, true),
762            ColumnSort::Default => (IconName::ChevronsUpDown, false),
763        };
764
765        Some(
766            div()
767                .id(("icon-sort", col_ix))
768                .p(px(2.))
769                .rounded(cx.theme().radius / 2.)
770                .map(|this| match is_on {
771                    true => this,
772                    false => this.opacity(0.5),
773                })
774                .hover(|this| this.bg(cx.theme().secondary).opacity(7.))
775                .active(|this| this.bg(cx.theme().secondary_active).opacity(1.))
776                .on_click(
777                    cx.listener(move |table, _, window, cx| table.perform_sort(col_ix, window, cx)),
778                )
779                .child(
780                    Icon::new(icon)
781                        .size_3()
782                        .text_color(cx.theme().secondary_foreground),
783                ),
784        )
785    }
786
787    /// Render the column header.
788    /// The children must be one by one items.
789    /// Because the horizontal scroll handle will use the child_item_bounds to
790    /// calculate the item position for itself's `scroll_to_item` method.
791    fn render_th(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) -> Div {
792        let entity_id = cx.entity_id();
793        let col_group = self.col_groups.get(col_ix).expect("BUG: invalid col index");
794
795        let movable = self.col_movable && col_group.column.movable;
796        let paddings = col_group.column.paddings;
797        let name = col_group.column.name.clone();
798
799        h_flex()
800            .h_full()
801            .child(
802                self.render_cell(col_ix, window, cx)
803                    .id(("col-header", col_ix))
804                    .on_click(cx.listener(move |this, _, window, cx| {
805                        this.on_col_head_click(col_ix, window, cx);
806                    }))
807                    .child(
808                        h_flex()
809                            .size_full()
810                            .justify_between()
811                            .items_center()
812                            .child(self.delegate.render_th(col_ix, window, cx))
813                            .when_some(paddings, |this, paddings| {
814                                // Leave right space for the sort icon, if this column have custom padding
815                                let offset_pr =
816                                    self.options.size.table_cell_padding().right - paddings.right;
817                                this.pr(offset_pr.max(px(0.)))
818                            })
819                            .children(self.render_sort_icon(col_ix, &col_group, window, cx)),
820                    )
821                    .when(movable, |this| {
822                        this.on_drag(
823                            DragColumn {
824                                entity_id,
825                                col_ix,
826                                name,
827                                width: col_group.width,
828                            },
829                            |drag, _, _, cx| {
830                                cx.stop_propagation();
831                                cx.new(|_| drag.clone())
832                            },
833                        )
834                        .drag_over::<DragColumn>(|this, _, _, cx| {
835                            this.rounded_l_none()
836                                .border_l_2()
837                                .border_r_0()
838                                .border_color(cx.theme().drag_border)
839                        })
840                        .on_drop(cx.listener(
841                            move |table, drag: &DragColumn, window, cx| {
842                                // If the drag col is not the same as the drop col, then swap the cols.
843                                if drag.entity_id != cx.entity_id() {
844                                    return;
845                                }
846
847                                table.move_column(drag.col_ix, col_ix, window, cx);
848                            },
849                        ))
850                    }),
851            )
852            // resize handle
853            .child(self.render_resize_handle(col_ix, window, cx))
854            // to save the bounds of this col.
855            .child({
856                let view = cx.entity().clone();
857                canvas(
858                    move |bounds, _, cx| {
859                        view.update(cx, |r, _| r.col_groups[col_ix].bounds = bounds)
860                    },
861                    |_, _, _, _| {},
862                )
863                .absolute()
864                .size_full()
865            })
866    }
867
868    fn render_table_head(
869        &mut self,
870        left_columns_count: usize,
871        window: &mut Window,
872        cx: &mut Context<Self>,
873    ) -> impl IntoElement {
874        let view = cx.entity().clone();
875        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
876
877        // Reset fixed head columns bounds, if no fixed columns are present
878        if left_columns_count == 0 {
879            self.fixed_head_cols_bounds = Bounds::default();
880        }
881
882        h_flex()
883            .w_full()
884            .h(self.options.size.table_row_height())
885            .flex_shrink_0()
886            .border_b_1()
887            .border_color(cx.theme().border)
888            .text_color(cx.theme().table_head_foreground)
889            .when(left_columns_count > 0, |this| {
890                let view = view.clone();
891                // Render left fixed columns
892                this.child(
893                    h_flex()
894                        .relative()
895                        .h_full()
896                        .bg(cx.theme().table_head)
897                        .children(
898                            self.col_groups
899                                .clone()
900                                .into_iter()
901                                .filter(|col| col.column.fixed == Some(ColumnFixed::Left))
902                                .enumerate()
903                                .map(|(col_ix, _)| self.render_th(col_ix, window, cx)),
904                        )
905                        .child(
906                            // Fixed columns border
907                            div()
908                                .absolute()
909                                .top_0()
910                                .right_0()
911                                .bottom_0()
912                                .w_0()
913                                .flex_shrink_0()
914                                .border_r_1()
915                                .border_color(cx.theme().border),
916                        )
917                        .child(
918                            canvas(
919                                move |bounds, _, cx| {
920                                    view.update(cx, |r, _| r.fixed_head_cols_bounds = bounds)
921                                },
922                                |_, _, _, _| {},
923                            )
924                            .absolute()
925                            .size_full(),
926                        ),
927                )
928            })
929            .child(
930                // Columns
931                h_flex()
932                    .id("table-head")
933                    .size_full()
934                    .overflow_scroll()
935                    .relative()
936                    .track_scroll(&horizontal_scroll_handle)
937                    .bg(cx.theme().table_head)
938                    .child(
939                        h_flex()
940                            .relative()
941                            .children(
942                                self.col_groups
943                                    .clone()
944                                    .into_iter()
945                                    .skip(left_columns_count)
946                                    .enumerate()
947                                    .map(|(col_ix, _)| {
948                                        self.render_th(left_columns_count + col_ix, window, cx)
949                                    }),
950                            )
951                            .child(self.delegate.render_last_empty_col(window, cx)),
952                    ),
953            )
954    }
955
956    #[allow(clippy::too_many_arguments)]
957    fn render_table_row(
958        &mut self,
959        row_ix: usize,
960        rows_count: usize,
961        left_columns_count: usize,
962        col_sizes: Rc<Vec<gpui::Size<Pixels>>>,
963        columns_count: usize,
964        is_filled: bool,
965        window: &mut Window,
966        cx: &mut Context<Self>,
967    ) -> Stateful<Div> {
968        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
969        let is_stripe_row = self.options.stripe && row_ix % 2 != 0;
970        let is_selected = self.selected_row == Some(row_ix);
971        let view = cx.entity().clone();
972        let row_height = self.options.size.table_row_height();
973
974        if row_ix < rows_count {
975            let is_last_row = row_ix + 1 == rows_count;
976            let need_render_border = is_selected || !is_last_row || !is_filled;
977
978            let mut tr = self.delegate.render_tr(row_ix, window, cx);
979            let style = tr.style().clone();
980
981            tr.h_flex()
982                .w_full()
983                .h(row_height)
984                .when(need_render_border, |this| {
985                    this.border_b_1().border_color(cx.theme().table_row_border)
986                })
987                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))
988                .refine_style(&style)
989                .hover(|this| {
990                    if is_selected || self.right_clicked_row == Some(row_ix) {
991                        this
992                    } else {
993                        this.bg(cx.theme().table_hover)
994                    }
995                })
996                .when(left_columns_count > 0, |this| {
997                    // Left fixed columns
998                    this.child(
999                        h_flex()
1000                            .relative()
1001                            .h_full()
1002                            .children({
1003                                let mut items = Vec::with_capacity(left_columns_count);
1004
1005                                (0..left_columns_count).for_each(|col_ix| {
1006                                    items.push(self.render_col_wrap(col_ix, window, cx).child(
1007                                        self.render_cell(col_ix, window, cx).child(
1008                                            self.measure_render_td(row_ix, col_ix, window, cx),
1009                                        ),
1010                                    ));
1011                                });
1012
1013                                items
1014                            })
1015                            .child(
1016                                // Fixed columns border
1017                                div()
1018                                    .absolute()
1019                                    .top_0()
1020                                    .right_0()
1021                                    .bottom_0()
1022                                    .w_0()
1023                                    .flex_shrink_0()
1024                                    .border_r_1()
1025                                    .border_color(cx.theme().border),
1026                            ),
1027                    )
1028                })
1029                .child(
1030                    h_flex()
1031                        .flex_1()
1032                        .h_full()
1033                        .overflow_hidden()
1034                        .relative()
1035                        .child(
1036                            crate::virtual_list::virtual_list(
1037                                view,
1038                                row_ix,
1039                                Axis::Horizontal,
1040                                col_sizes,
1041                                {
1042                                    move |table, visible_range: Range<usize>, window, cx| {
1043                                        table.update_visible_range_if_need(
1044                                            visible_range.clone(),
1045                                            Axis::Horizontal,
1046                                            window,
1047                                            cx,
1048                                        );
1049
1050                                        let mut items = Vec::with_capacity(
1051                                            visible_range.end - visible_range.start,
1052                                        );
1053
1054                                        visible_range.for_each(|col_ix| {
1055                                            let col_ix = col_ix + left_columns_count;
1056                                            let el =
1057                                                table.render_col_wrap(col_ix, window, cx).child(
1058                                                    table.render_cell(col_ix, window, cx).child(
1059                                                        table.measure_render_td(
1060                                                            row_ix, col_ix, window, cx,
1061                                                        ),
1062                                                    ),
1063                                                );
1064
1065                                            items.push(el);
1066                                        });
1067
1068                                        items
1069                                    }
1070                                },
1071                            )
1072                            .with_scroll_handle(&self.horizontal_scroll_handle),
1073                        )
1074                        .child(self.delegate.render_last_empty_col(window, cx)),
1075                )
1076                // Row selected style
1077                .when_some(self.selected_row, |this, _| {
1078                    this.when(
1079                        is_selected && self.selection_state == SelectionState::Row,
1080                        |this| {
1081                            this.border_color(gpui::transparent_white()).child(
1082                                div()
1083                                    .top(if row_ix == 0 { px(0.) } else { px(-1.) })
1084                                    .left(px(0.))
1085                                    .right(px(0.))
1086                                    .bottom(px(-1.))
1087                                    .absolute()
1088                                    .bg(cx.theme().table_active)
1089                                    .border_1()
1090                                    .border_color(cx.theme().table_active_border),
1091                            )
1092                        },
1093                    )
1094                })
1095                // Row right click row style
1096                .when(self.right_clicked_row == Some(row_ix), |this| {
1097                    this.border_color(gpui::transparent_white()).child(
1098                        div()
1099                            .top(if row_ix == 0 { px(0.) } else { px(-1.) })
1100                            .left(px(0.))
1101                            .right(px(0.))
1102                            .bottom(px(-1.))
1103                            .absolute()
1104                            .border_1()
1105                            .border_color(cx.theme().selection),
1106                    )
1107                })
1108                .on_mouse_down(
1109                    MouseButton::Right,
1110                    cx.listener(move |this, e, window, cx| {
1111                        this.on_row_right_click(e, row_ix, window, cx);
1112                    }),
1113                )
1114                .on_click(cx.listener(move |this, e, window, cx| {
1115                    this.on_row_left_click(e, row_ix, window, cx);
1116                }))
1117        } else {
1118            // Render fake rows to fill the rest table space
1119            self.delegate
1120                .render_tr(row_ix, window, cx)
1121                .h_flex()
1122                .w_full()
1123                .h(row_height)
1124                .border_b_1()
1125                .border_color(cx.theme().table_row_border)
1126                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))
1127                .children((0..columns_count).map(|col_ix| {
1128                    h_flex()
1129                        .left(horizontal_scroll_handle.offset().x)
1130                        .child(self.render_cell(col_ix, window, cx))
1131                }))
1132                .child(self.delegate.render_last_empty_col(window, cx))
1133        }
1134    }
1135
1136    /// Calculate the extra rows needed to fill the table empty space when `stripe` is true.
1137    fn calculate_extra_rows_needed(
1138        &self,
1139        total_height: Pixels,
1140        actual_height: Pixels,
1141        row_height: Pixels,
1142    ) -> usize {
1143        let mut extra_rows_needed = 0;
1144
1145        let remaining_height = total_height - actual_height;
1146        if remaining_height > px(0.) {
1147            extra_rows_needed = (remaining_height / row_height).floor() as usize;
1148        }
1149
1150        extra_rows_needed
1151    }
1152
1153    #[inline]
1154    fn measure_render_td(
1155        &mut self,
1156        row_ix: usize,
1157        col_ix: usize,
1158        window: &mut Window,
1159        cx: &mut Context<Self>,
1160    ) -> impl IntoElement {
1161        if !crate::measure_enable() {
1162            return self
1163                .delegate
1164                .render_td(row_ix, col_ix, window, cx)
1165                .into_any_element();
1166        }
1167
1168        let start = std::time::Instant::now();
1169        let el = self.delegate.render_td(row_ix, col_ix, window, cx);
1170        self._measure.push(start.elapsed());
1171        el.into_any_element()
1172    }
1173
1174    fn measure(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1175        if !crate::measure_enable() {
1176            return;
1177        }
1178
1179        // Print avg measure time of each td
1180        if self._measure.len() > 0 {
1181            let total = self
1182                ._measure
1183                .iter()
1184                .fold(Duration::default(), |acc, d| acc + *d);
1185            let avg = total / self._measure.len() as u32;
1186            eprintln!(
1187                "last render {} cells total: {:?}, avg: {:?}",
1188                self._measure.len(),
1189                total,
1190                avg,
1191            );
1192        }
1193        self._measure.clear();
1194    }
1195
1196    fn render_vertical_scrollbar(
1197        &mut self,
1198
1199        _: &mut Window,
1200        _: &mut Context<Self>,
1201    ) -> Option<impl IntoElement> {
1202        Some(
1203            div()
1204                .occlude()
1205                .absolute()
1206                .top(self.options.size.table_row_height())
1207                .right_0()
1208                .bottom_0()
1209                .w(Scrollbar::width())
1210                .child(Scrollbar::vertical(&self.vertical_scroll_handle).max_fps(60)),
1211        )
1212    }
1213
1214    fn render_horizontal_scrollbar(
1215        &mut self,
1216        _: &mut Window,
1217        _: &mut Context<Self>,
1218    ) -> impl IntoElement {
1219        div()
1220            .occlude()
1221            .absolute()
1222            .left(self.fixed_head_cols_bounds.size.width)
1223            .right_0()
1224            .bottom_0()
1225            .h(Scrollbar::width())
1226            .child(Scrollbar::horizontal(&self.horizontal_scroll_handle))
1227    }
1228}
1229
1230impl<D> Focusable for TableState<D>
1231where
1232    D: TableDelegate,
1233{
1234    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
1235        self.focus_handle.clone()
1236    }
1237}
1238impl<D> EventEmitter<TableEvent> for TableState<D> where D: TableDelegate {}
1239
1240impl<D> Render for TableState<D>
1241where
1242    D: TableDelegate,
1243{
1244    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1245        self.measure(window, cx);
1246
1247        let columns_count = self.delegate.columns_count(cx);
1248        let left_columns_count = self
1249            .col_groups
1250            .iter()
1251            .filter(|col| self.col_fixed && col.column.fixed == Some(ColumnFixed::Left))
1252            .count();
1253        let rows_count = self.delegate.rows_count(cx);
1254        let loading = self.delegate.loading(cx);
1255
1256        let row_height = self.options.size.table_row_height();
1257        let total_height = self
1258            .vertical_scroll_handle
1259            .0
1260            .borrow()
1261            .base_handle
1262            .bounds()
1263            .size
1264            .height;
1265        let actual_height = row_height * rows_count as f32;
1266        let extra_rows_count =
1267            self.calculate_extra_rows_needed(total_height, actual_height, row_height);
1268        let render_rows_count = if self.options.stripe {
1269            rows_count + extra_rows_count
1270        } else {
1271            rows_count
1272        };
1273        let right_clicked_row = self.right_clicked_row;
1274        let is_filled = total_height > Pixels::ZERO && total_height <= actual_height;
1275
1276        let loading_view = if loading {
1277            Some(
1278                self.delegate
1279                    .render_loading(self.options.size, window, cx)
1280                    .into_any_element(),
1281            )
1282        } else {
1283            None
1284        };
1285
1286        let empty_view = if rows_count == 0 {
1287            Some(
1288                div()
1289                    .size_full()
1290                    .child(self.delegate.render_empty(window, cx))
1291                    .into_any_element(),
1292            )
1293        } else {
1294            None
1295        };
1296
1297        let inner_table = v_flex()
1298            .id("table-inner")
1299            .size_full()
1300            .overflow_hidden()
1301            .child(self.render_table_head(left_columns_count, window, cx))
1302            .context_menu({
1303                let view = cx.entity().clone();
1304                move |this, window: &mut Window, cx: &mut Context<PopupMenu>| {
1305                    if let Some(row_ix) = view.read(cx).right_clicked_row {
1306                        view.update(cx, |menu, cx| {
1307                            menu.delegate_mut().context_menu(row_ix, this, window, cx)
1308                        })
1309                    } else {
1310                        this
1311                    }
1312                }
1313            })
1314            .map(|this| {
1315                if rows_count == 0 {
1316                    this.children(empty_view)
1317                } else {
1318                    this.child(
1319                        h_flex().id("table-body").flex_grow().size_full().child(
1320                            uniform_list(
1321                                "table-uniform-list",
1322                                render_rows_count,
1323                                cx.processor(
1324                                    move |table, visible_range: Range<usize>, window, cx| {
1325                                        // We must calculate the col sizes here, because the col sizes
1326                                        // need render_th first, then that method will set the bounds of each col.
1327                                        let col_sizes: Rc<Vec<gpui::Size<Pixels>>> = Rc::new(
1328                                            table
1329                                                .col_groups
1330                                                .iter()
1331                                                .skip(left_columns_count)
1332                                                .map(|col| col.bounds.size)
1333                                                .collect(),
1334                                        );
1335
1336                                        table.load_more_if_need(
1337                                            rows_count,
1338                                            visible_range.end,
1339                                            window,
1340                                            cx,
1341                                        );
1342                                        table.update_visible_range_if_need(
1343                                            visible_range.clone(),
1344                                            Axis::Vertical,
1345                                            window,
1346                                            cx,
1347                                        );
1348
1349                                        if visible_range.end > rows_count {
1350                                            table.scroll_to_row(
1351                                                std::cmp::min(
1352                                                    visible_range.start,
1353                                                    rows_count.saturating_sub(1),
1354                                                ),
1355                                                cx,
1356                                            );
1357                                        }
1358
1359                                        let mut items = Vec::with_capacity(
1360                                            visible_range.end.saturating_sub(visible_range.start),
1361                                        );
1362
1363                                        // Render fake rows to fill the table
1364                                        visible_range.for_each(|row_ix| {
1365                                            // Render real rows for available data
1366                                            items.push(table.render_table_row(
1367                                                row_ix,
1368                                                rows_count,
1369                                                left_columns_count,
1370                                                col_sizes.clone(),
1371                                                columns_count,
1372                                                is_filled,
1373                                                window,
1374                                                cx,
1375                                            ));
1376                                        });
1377
1378                                        items
1379                                    },
1380                                ),
1381                            )
1382                            .flex_grow()
1383                            .size_full()
1384                            .with_sizing_behavior(ListSizingBehavior::Auto)
1385                            .track_scroll(self.vertical_scroll_handle.clone())
1386                            .into_any_element(),
1387                        ),
1388                    )
1389                }
1390            });
1391
1392        div()
1393            .size_full()
1394            .children(loading_view)
1395            .when(!loading, |this| {
1396                this.child(inner_table)
1397                    .child(ScrollableMask::new(
1398                        Axis::Horizontal,
1399                        &self.horizontal_scroll_handle,
1400                    ))
1401                    .when(right_clicked_row.is_some(), |this| {
1402                        this.on_mouse_down_out(cx.listener(|this, _, _, cx| {
1403                            this.right_clicked_row = None;
1404                            cx.notify();
1405                        }))
1406                    })
1407            })
1408            .child(canvas(
1409                {
1410                    let state = cx.entity();
1411                    move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds)
1412                },
1413                |_, _, _, _| {},
1414            ))
1415            .when(!window.is_inspector_picking(cx), |this| {
1416                this.child(
1417                    div()
1418                        .absolute()
1419                        .top_0()
1420                        .size_full()
1421                        .when(self.options.scrollbar_visible.bottom, |this| {
1422                            this.child(self.render_horizontal_scrollbar(window, cx))
1423                        })
1424                        .when(
1425                            self.options.scrollbar_visible.right && rows_count > 0,
1426                            |this| this.children(self.render_vertical_scrollbar(window, cx)),
1427                        ),
1428                )
1429            })
1430    }
1431}