gpui_component/table/
state.rs

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