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