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(&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                                .iter()
900                                .filter(|col| col.column.fixed == Some(ColumnFixed::Left))
901                                .enumerate()
902                                .map(|(col_ix, _)| self.render_th(col_ix, window, cx)),
903                        )
904                        .child(
905                            // Fixed columns border
906                            div()
907                                .absolute()
908                                .top_0()
909                                .right_0()
910                                .bottom_0()
911                                .w_0()
912                                .flex_shrink_0()
913                                .border_r_1()
914                                .border_color(cx.theme().border),
915                        )
916                        .child(
917                            canvas(
918                                move |bounds, _, cx| {
919                                    view.update(cx, |r, _| r.fixed_head_cols_bounds = bounds)
920                                },
921                                |_, _, _, _| {},
922                            )
923                            .absolute()
924                            .size_full(),
925                        ),
926                )
927            })
928            .child(
929                // Columns
930                h_flex()
931                    .id("table-head")
932                    .size_full()
933                    .overflow_scroll()
934                    .relative()
935                    .track_scroll(&horizontal_scroll_handle)
936                    .bg(cx.theme().table_head)
937                    .child(
938                        h_flex()
939                            .relative()
940                            .children(
941                                self.col_groups
942                                    .iter()
943                                    .skip(left_columns_count)
944                                    .enumerate()
945                                    .map(|(col_ix, _)| {
946                                        self.render_th(left_columns_count + col_ix, window, cx)
947                                    }),
948                            )
949                            .child(self.delegate.render_last_empty_col(window, cx)),
950                    ),
951            )
952    }
953
954    #[allow(clippy::too_many_arguments)]
955    fn render_table_row(
956        &mut self,
957        row_ix: usize,
958        rows_count: usize,
959        left_columns_count: usize,
960        col_sizes: Rc<Vec<gpui::Size<Pixels>>>,
961        columns_count: usize,
962        is_filled: bool,
963        window: &mut Window,
964        cx: &mut Context<Self>,
965    ) -> Stateful<Div> {
966        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
967        let is_stripe_row = self.options.stripe && row_ix % 2 != 0;
968        let is_selected = self.selected_row == Some(row_ix);
969        let view = cx.entity().clone();
970        let row_height = self.options.size.table_row_height();
971
972        if row_ix < rows_count {
973            let is_last_row = row_ix + 1 == rows_count;
974            let need_render_border = is_selected || !is_last_row || !is_filled;
975
976            let mut tr = self.delegate.render_tr(row_ix, window, cx);
977            let style = tr.style().clone();
978
979            tr.h_flex()
980                .w_full()
981                .h(row_height)
982                .when(need_render_border, |this| {
983                    this.border_b_1().border_color(cx.theme().table_row_border)
984                })
985                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))
986                .refine_style(&style)
987                .hover(|this| {
988                    if is_selected || self.right_clicked_row == Some(row_ix) {
989                        this
990                    } else {
991                        this.bg(cx.theme().table_hover)
992                    }
993                })
994                .when(left_columns_count > 0, |this| {
995                    // Left fixed columns
996                    this.child(
997                        h_flex()
998                            .relative()
999                            .h_full()
1000                            .children({
1001                                let mut items = Vec::with_capacity(left_columns_count);
1002
1003                                (0..left_columns_count).for_each(|col_ix| {
1004                                    items.push(self.render_col_wrap(col_ix, window, cx).child(
1005                                        self.render_cell(col_ix, window, cx).child(
1006                                            self.measure_render_td(row_ix, col_ix, window, cx),
1007                                        ),
1008                                    ));
1009                                });
1010
1011                                items
1012                            })
1013                            .child(
1014                                // Fixed columns border
1015                                div()
1016                                    .absolute()
1017                                    .top_0()
1018                                    .right_0()
1019                                    .bottom_0()
1020                                    .w_0()
1021                                    .flex_shrink_0()
1022                                    .border_r_1()
1023                                    .border_color(cx.theme().border),
1024                            ),
1025                    )
1026                })
1027                .child(
1028                    h_flex()
1029                        .flex_1()
1030                        .h_full()
1031                        .overflow_hidden()
1032                        .relative()
1033                        .child(
1034                            crate::virtual_list::virtual_list(
1035                                view,
1036                                row_ix,
1037                                Axis::Horizontal,
1038                                col_sizes,
1039                                {
1040                                    move |table, visible_range: Range<usize>, window, cx| {
1041                                        table.update_visible_range_if_need(
1042                                            visible_range.clone(),
1043                                            Axis::Horizontal,
1044                                            window,
1045                                            cx,
1046                                        );
1047
1048                                        let mut items = Vec::with_capacity(
1049                                            visible_range.end - visible_range.start,
1050                                        );
1051
1052                                        visible_range.for_each(|col_ix| {
1053                                            let col_ix = col_ix + left_columns_count;
1054                                            let el =
1055                                                table.render_col_wrap(col_ix, window, cx).child(
1056                                                    table.render_cell(col_ix, window, cx).child(
1057                                                        table.measure_render_td(
1058                                                            row_ix, col_ix, window, cx,
1059                                                        ),
1060                                                    ),
1061                                                );
1062
1063                                            items.push(el);
1064                                        });
1065
1066                                        items
1067                                    }
1068                                },
1069                            )
1070                            .with_scroll_handle(&self.horizontal_scroll_handle),
1071                        )
1072                        .child(self.delegate.render_last_empty_col(window, cx)),
1073                )
1074                // Row selected style
1075                .when_some(self.selected_row, |this, _| {
1076                    this.when(
1077                        is_selected && self.selection_state == SelectionState::Row,
1078                        |this| {
1079                            this.border_color(gpui::transparent_white()).child(
1080                                div()
1081                                    .top(if row_ix == 0 { px(0.) } else { px(-1.) })
1082                                    .left(px(0.))
1083                                    .right(px(0.))
1084                                    .bottom(px(-1.))
1085                                    .absolute()
1086                                    .bg(cx.theme().table_active)
1087                                    .border_1()
1088                                    .border_color(cx.theme().table_active_border),
1089                            )
1090                        },
1091                    )
1092                })
1093                // Row right click row style
1094                .when(self.right_clicked_row == Some(row_ix), |this| {
1095                    this.border_color(gpui::transparent_white()).child(
1096                        div()
1097                            .top(if row_ix == 0 { px(0.) } else { px(-1.) })
1098                            .left(px(0.))
1099                            .right(px(0.))
1100                            .bottom(px(-1.))
1101                            .absolute()
1102                            .border_1()
1103                            .border_color(cx.theme().selection),
1104                    )
1105                })
1106                .on_mouse_down(
1107                    MouseButton::Right,
1108                    cx.listener(move |this, e, window, cx| {
1109                        this.on_row_right_click(e, row_ix, window, cx);
1110                    }),
1111                )
1112                .on_click(cx.listener(move |this, e, window, cx| {
1113                    this.on_row_left_click(e, row_ix, window, cx);
1114                }))
1115        } else {
1116            // Render fake rows to fill the rest table space
1117            self.delegate
1118                .render_tr(row_ix, window, cx)
1119                .h_flex()
1120                .w_full()
1121                .h(row_height)
1122                .border_b_1()
1123                .border_color(cx.theme().table_row_border)
1124                .when(is_stripe_row, |this| this.bg(cx.theme().table_even))
1125                .children((0..columns_count).map(|col_ix| {
1126                    h_flex()
1127                        .left(horizontal_scroll_handle.offset().x)
1128                        .child(self.render_cell(col_ix, window, cx))
1129                }))
1130                .child(self.delegate.render_last_empty_col(window, cx))
1131        }
1132    }
1133
1134    /// Calculate the extra rows needed to fill the table empty space when `stripe` is true.
1135    fn calculate_extra_rows_needed(
1136        &self,
1137        total_height: Pixels,
1138        actual_height: Pixels,
1139        row_height: Pixels,
1140    ) -> usize {
1141        let mut extra_rows_needed = 0;
1142
1143        let remaining_height = total_height - actual_height;
1144        if remaining_height > px(0.) {
1145            extra_rows_needed = (remaining_height / row_height).floor() as usize;
1146        }
1147
1148        extra_rows_needed
1149    }
1150
1151    #[inline]
1152    fn measure_render_td(
1153        &mut self,
1154        row_ix: usize,
1155        col_ix: usize,
1156        window: &mut Window,
1157        cx: &mut Context<Self>,
1158    ) -> impl IntoElement {
1159        if !crate::measure_enable() {
1160            return self
1161                .delegate
1162                .render_td(row_ix, col_ix, window, cx)
1163                .into_any_element();
1164        }
1165
1166        let start = std::time::Instant::now();
1167        let el = self.delegate.render_td(row_ix, col_ix, window, cx);
1168        self._measure.push(start.elapsed());
1169        el.into_any_element()
1170    }
1171
1172    fn measure(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1173        if !crate::measure_enable() {
1174            return;
1175        }
1176
1177        // Print avg measure time of each td
1178        if self._measure.len() > 0 {
1179            let total = self
1180                ._measure
1181                .iter()
1182                .fold(Duration::default(), |acc, d| acc + *d);
1183            let avg = total / self._measure.len() as u32;
1184            eprintln!(
1185                "last render {} cells total: {:?}, avg: {:?}",
1186                self._measure.len(),
1187                total,
1188                avg,
1189            );
1190        }
1191        self._measure.clear();
1192    }
1193
1194    fn render_vertical_scrollbar(
1195        &mut self,
1196
1197        _: &mut Window,
1198        _: &mut Context<Self>,
1199    ) -> Option<impl IntoElement> {
1200        Some(
1201            div()
1202                .occlude()
1203                .absolute()
1204                .top(self.options.size.table_row_height())
1205                .right_0()
1206                .bottom_0()
1207                .w(Scrollbar::width())
1208                .child(Scrollbar::vertical(&self.vertical_scroll_handle).max_fps(60)),
1209        )
1210    }
1211
1212    fn render_horizontal_scrollbar(
1213        &mut self,
1214        _: &mut Window,
1215        _: &mut Context<Self>,
1216    ) -> impl IntoElement {
1217        div()
1218            .occlude()
1219            .absolute()
1220            .left(self.fixed_head_cols_bounds.size.width)
1221            .right_0()
1222            .bottom_0()
1223            .h(Scrollbar::width())
1224            .child(Scrollbar::horizontal(&self.horizontal_scroll_handle))
1225    }
1226}
1227
1228impl<D> Focusable for TableState<D>
1229where
1230    D: TableDelegate,
1231{
1232    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
1233        self.focus_handle.clone()
1234    }
1235}
1236impl<D> EventEmitter<TableEvent> for TableState<D> where D: TableDelegate {}
1237
1238impl<D> Render for TableState<D>
1239where
1240    D: TableDelegate,
1241{
1242    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1243        self.measure(window, cx);
1244
1245        let columns_count = self.delegate.columns_count(cx);
1246        let left_columns_count = self
1247            .col_groups
1248            .iter()
1249            .filter(|col| self.col_fixed && col.column.fixed == Some(ColumnFixed::Left))
1250            .count();
1251        let rows_count = self.delegate.rows_count(cx);
1252        let loading = self.delegate.loading(cx);
1253
1254        let row_height = self.options.size.table_row_height();
1255        let total_height = self
1256            .vertical_scroll_handle
1257            .0
1258            .borrow()
1259            .base_handle
1260            .bounds()
1261            .size
1262            .height;
1263        let actual_height = row_height * rows_count as f32;
1264        let extra_rows_count =
1265            self.calculate_extra_rows_needed(total_height, actual_height, row_height);
1266        let render_rows_count = if self.options.stripe {
1267            rows_count + extra_rows_count
1268        } else {
1269            rows_count
1270        };
1271        let right_clicked_row = self.right_clicked_row;
1272        let is_filled = total_height > Pixels::ZERO && total_height <= actual_height;
1273
1274        let loading_view = if loading {
1275            Some(
1276                self.delegate
1277                    .render_loading(self.options.size, window, cx)
1278                    .into_any_element(),
1279            )
1280        } else {
1281            None
1282        };
1283
1284        let empty_view = if rows_count == 0 {
1285            Some(
1286                div()
1287                    .size_full()
1288                    .child(self.delegate.render_empty(window, cx))
1289                    .into_any_element(),
1290            )
1291        } else {
1292            None
1293        };
1294
1295        let inner_table = v_flex()
1296            .id("table-inner")
1297            .size_full()
1298            .overflow_hidden()
1299            .child(self.render_table_head(left_columns_count, window, cx))
1300            .context_menu({
1301                let view = cx.entity().clone();
1302                move |this, window: &mut Window, cx: &mut Context<PopupMenu>| {
1303                    if let Some(row_ix) = view.read(cx).right_clicked_row {
1304                        view.update(cx, |menu, cx| {
1305                            menu.delegate().context_menu(row_ix, this, window, cx)
1306                        })
1307                    } else {
1308                        this
1309                    }
1310                }
1311            })
1312            .map(|this| {
1313                if rows_count == 0 {
1314                    this.children(empty_view)
1315                } else {
1316                    this.child(
1317                        h_flex().id("table-body").flex_grow().size_full().child(
1318                            uniform_list(
1319                                "table-uniform-list",
1320                                render_rows_count,
1321                                cx.processor(
1322                                    move |table, visible_range: Range<usize>, window, cx| {
1323                                        // We must calculate the col sizes here, because the col sizes
1324                                        // need render_th first, then that method will set the bounds of each col.
1325                                        let col_sizes: Rc<Vec<gpui::Size<Pixels>>> = Rc::new(
1326                                            table
1327                                                .col_groups
1328                                                .iter()
1329                                                .skip(left_columns_count)
1330                                                .map(|col| col.bounds.size)
1331                                                .collect(),
1332                                        );
1333
1334                                        table.load_more_if_need(
1335                                            rows_count,
1336                                            visible_range.end,
1337                                            window,
1338                                            cx,
1339                                        );
1340                                        table.update_visible_range_if_need(
1341                                            visible_range.clone(),
1342                                            Axis::Vertical,
1343                                            window,
1344                                            cx,
1345                                        );
1346
1347                                        if visible_range.end > rows_count {
1348                                            table.scroll_to_row(
1349                                                std::cmp::min(
1350                                                    visible_range.start,
1351                                                    rows_count.saturating_sub(1),
1352                                                ),
1353                                                cx,
1354                                            );
1355                                        }
1356
1357                                        let mut items = Vec::with_capacity(
1358                                            visible_range.end.saturating_sub(visible_range.start),
1359                                        );
1360
1361                                        // Render fake rows to fill the table
1362                                        visible_range.for_each(|row_ix| {
1363                                            // Render real rows for available data
1364                                            items.push(table.render_table_row(
1365                                                row_ix,
1366                                                rows_count,
1367                                                left_columns_count,
1368                                                col_sizes.clone(),
1369                                                columns_count,
1370                                                is_filled,
1371                                                window,
1372                                                cx,
1373                                            ));
1374                                        });
1375
1376                                        items
1377                                    },
1378                                ),
1379                            )
1380                            .flex_grow()
1381                            .size_full()
1382                            .with_sizing_behavior(ListSizingBehavior::Auto)
1383                            .track_scroll(self.vertical_scroll_handle.clone())
1384                            .into_any_element(),
1385                        ),
1386                    )
1387                }
1388            });
1389
1390        div()
1391            .size_full()
1392            .children(loading_view)
1393            .when(!loading, |this| {
1394                this.child(inner_table)
1395                    .child(ScrollableMask::new(
1396                        Axis::Horizontal,
1397                        &self.horizontal_scroll_handle,
1398                    ))
1399                    .when(right_clicked_row.is_some(), |this| {
1400                        this.on_mouse_down_out(cx.listener(|this, _, _, cx| {
1401                            this.right_clicked_row = None;
1402                            cx.notify();
1403                        }))
1404                    })
1405            })
1406            .child(canvas(
1407                {
1408                    let state = cx.entity();
1409                    move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds)
1410                },
1411                |_, _, _, _| {},
1412            ))
1413            .when(!window.is_inspector_picking(cx), |this| {
1414                this.child(
1415                    div()
1416                        .absolute()
1417                        .top_0()
1418                        .size_full()
1419                        .when(self.options.scrollbar_visible.bottom, |this| {
1420                            this.child(self.render_horizontal_scrollbar(window, cx))
1421                        })
1422                        .when(
1423                            self.options.scrollbar_visible.right && rows_count > 0,
1424                            |this| this.children(self.render_vertical_scrollbar(window, cx)),
1425                        ),
1426                )
1427            })
1428    }
1429}