Skip to main content

gpui_component/table/
state.rs

1use std::{ops::Range, rc::Rc, time::Duration};
2
3use crate::{
4    ActiveTheme, ElementExt, Icon, IconName, StyleSized as _, StyledExt, VirtualListScrollHandle,
5    actions::{
6        Cancel, SelectDown, SelectFirst, SelectLast, SelectNextColumn, SelectPageDown,
7        SelectPageUp, SelectPrevColumn, SelectUp,
8    },
9    h_flex,
10    menu::{ContextMenuExt, PopupMenu},
11    scroll::{ScrollableMask, Scrollbar},
12    v_flex,
13};
14use gpui::{
15    AppContext, Axis, Bounds, ClickEvent, Context, Div, DragMoveEvent, ElementId, EventEmitter,
16    FocusHandle, Focusable, InteractiveElement, IntoElement, ListSizingBehavior, MouseButton,
17    MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful,
18    StatefulInteractiveElement as _, Styled, Task, UniformListScrollHandle, Window, div,
19    prelude::FluentBuilder, px, uniform_list,
20};
21
22use super::*;
23
24/// How far behind the pointer a resize drag trails the column edge.
25const HANDLE_SIZE: Pixels = px(2.);
26
27/// Grab room on each side of a resize handle's hairline, so the divider can be
28/// caught without pixel-hunting. Both halves of the band are this wide, so
29/// widening the grab area stays symmetric about the boundary. `resizable`'s
30/// handle pads a hairline the same way, and by the same amount, see
31/// `crates/base/src/resizable/resize_handle.rs`.
32const HANDLE_PADDING: Pixels = px(4.);
33
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35enum SelectionMode {
36    Column,
37    Row,
38    Cell,
39}
40
41impl SelectionMode {
42    #[inline(always)]
43    fn is_row(&self) -> bool {
44        matches!(self, SelectionMode::Row)
45    }
46
47    #[inline(always)]
48    fn is_column(&self) -> bool {
49        matches!(self, SelectionMode::Column)
50    }
51
52    #[inline(always)]
53    fn is_cell(&self) -> bool {
54        matches!(self, SelectionMode::Cell)
55    }
56}
57
58/// The Table event.
59#[derive(Clone)]
60pub enum TableEvent {
61    /// Single click or move to selected row.
62    SelectRow(usize),
63    /// Double click on the row.
64    DoubleClickedRow(usize),
65    /// Selected column.
66    SelectColumn(usize),
67    /// A cell has been selected (clicked or navigated to via keyboard).
68    ///
69    /// Emitted when a cell is selected in cell selection mode.
70    /// The first `usize` is the row index, and the second `usize` is the column index.
71    ///
72    /// This event is also emitted when navigating between cells using keyboard shortcuts.
73    SelectCell(usize, usize),
74    /// A cell has been double-clicked.
75    ///
76    /// Emitted when a cell is double-clicked in cell selection mode.
77    /// The first `usize` is the row index, and the second `usize` is the column index.
78    ///
79    /// Use this event to trigger actions like opening a detail view or editing the cell content.
80    DoubleClickedCell(usize, usize),
81    /// The column widths have changed.
82    ///
83    /// The `Vec<Pixels>` contains the new widths of all columns.
84    ColumnWidthsChanged(Vec<Pixels>),
85    /// A column has been moved.
86    ///
87    /// The first `usize` is the original index of the column,
88    /// and the second `usize` is the new index of the column.
89    MoveColumn(usize, usize),
90    /// A row has been right-clicked.
91    ///
92    /// Contains the row index, or `None` if right-clicked on an empty area.
93    /// Use this event to show context menus for rows.
94    RightClickedRow(Option<usize>),
95    /// A cell has been right-clicked.
96    ///
97    /// Emitted when a cell is right-clicked in cell selection mode.
98    /// The first `usize` is the row index, and the second `usize` is the column index.
99    ///
100    /// Use this event to show context menus specific to the cell content.
101    /// The right-clicked cell is highlighted with a subtle border until another cell is clicked.
102    RightClickedCell(usize, usize),
103    /// The selection has been cleared.
104    ///
105    /// This event is emitted when the selection is cleared.
106    ClearSelection,
107}
108
109/// The visible range of the rows and columns.
110#[derive(Debug, Default)]
111pub struct TableVisibleRange {
112    /// The visible range of the rows.
113    rows: Range<usize>,
114    /// The visible range of the columns.
115    cols: Range<usize>,
116}
117
118impl TableVisibleRange {
119    /// Returns the visible range of the rows.
120    pub fn rows(&self) -> &Range<usize> {
121        &self.rows
122    }
123
124    /// Returns the visible range of the columns.
125    pub fn cols(&self) -> &Range<usize> {
126        &self.cols
127    }
128}
129
130/// The state for [`DataTable`].
131///
132/// # Selection Modes
133///
134/// The table supports three selection modes:
135/// - **Row Selection**: Select entire rows (default mode)
136/// - **Column Selection**: Select entire columns
137/// - **Cell Selection**: Select individual cells
138///
139/// ## Cell Selection
140///
141/// When `cell_selectable` is enabled, users can:
142/// - Click on cells to select them
143/// - Right-click on cells to mark them for context menus
144/// - Double-click on cells to trigger actions
145/// - Navigate between cells using keyboard (arrow keys, Home, End, PageUp, PageDown, Tab)
146///
147/// When in cell selection mode, a row header column appears on the left side,
148/// allowing users to select entire rows by clicking on it.
149///
150/// # Events
151///
152/// The table emits the following events related to cell selection:
153/// - [`TableEvent::SelectCell`]: Emitted when a cell is selected
154/// - [`TableEvent::DoubleClickedCell`]: Emitted when a cell is double-clicked
155/// - [`TableEvent::RightClickedCell`]: Emitted when a cell is right-clicked
156///
157/// # Example
158///
159/// ```rust,ignore
160/// let table_state = cx.new(|cx| {
161///     TableState::new(delegate, cx)
162///         .cell_selectable(true)
163///         .row_selectable(true)
164/// });
165///
166/// // Subscribe to cell events
167/// cx.subscribe(&table_state, |this, table, event, cx| {
168///     match event {
169///         TableEvent::SelectCell(row_ix, col_ix) => {
170///             println!("Selected cell: ({}, {})", row_ix, col_ix);
171///         }
172///         TableEvent::DoubleClickedCell(row_ix, col_ix) => {
173///             println!("Double-clicked cell: ({}, {})", row_ix, col_ix);
174///         }
175///         _ => {}
176///     }
177/// });
178/// ```
179#[derive(Clone)]
180pub(crate) struct HeaderCell {
181    pub label: SharedString,
182    pub width: Pixels,
183    col_span: usize,
184    is_leaf: bool,
185    leaf_col_ix: Option<usize>,
186    start_leaf_col_ix: usize,
187}
188
189pub struct TableState<D: TableDelegate> {
190    focus_handle: FocusHandle,
191    delegate: D,
192    pub(super) options: TableOptions,
193    /// The bounds of the table container.
194    bounds: Bounds<Pixels>,
195    /// The bounds of the fixed head cols.
196    fixed_head_cols_bounds: Bounds<Pixels>,
197
198    col_groups: Vec<ColGroup>,
199    header_layout: Vec<Vec<HeaderCell>>,
200
201    /// Whether the table can loop selection, default is true.
202    ///
203    /// When the prev/next selection is out of the table bounds, the selection will loop to the other side.
204    pub loop_selection: bool,
205    /// Whether the table can select column.
206    pub col_selectable: bool,
207    /// Whether the table can select row.
208    pub row_selectable: bool,
209    /// Whether the table can select cell, default is false.
210    ///
211    /// When enabled:
212    /// - Users can click on individual cells to select them
213    /// - A row header column appears on the left for selecting entire rows
214    ///   (can be hidden via [`Self::row_header`])
215    /// - Keyboard navigation works at the cell level (arrow keys move between cells)
216    /// - Right-click and double-click events are supported for cells
217    pub cell_selectable: bool,
218    /// Whether the row header column is visible when `cell_selectable` is enabled,
219    /// default is `true`.
220    ///
221    /// Set to `false` to hide the narrow leftmost header column while keeping cell
222    /// selection — useful when you want to put your own content (e.g. a row index
223    /// column) on the left. When hidden, clicking the already-selected cell again
224    /// escalates the selection to the whole row so users can still pick rows; row
225    /// escalation requires `row_selectable` to be enabled.
226    pub row_header: bool,
227    /// Whether the table can sort.
228    pub sortable: bool,
229    /// Whether the table can resize columns.
230    pub col_resizable: bool,
231    /// Whether the table can move columns.
232    pub col_movable: bool,
233    /// Enable/disable fixed columns feature.
234    pub col_fixed: bool,
235
236    pub vertical_scroll_handle: UniformListScrollHandle,
237    pub horizontal_scroll_handle: VirtualListScrollHandle,
238
239    selected_row: Option<usize>,
240    selection_mode: SelectionMode,
241    right_clicked_row: Option<usize>,
242    right_clicked_cell: Option<(usize, usize)>,
243    selected_col: Option<usize>,
244    selected_cell: Option<(usize, usize)>,
245
246    /// The column index that is being resized.
247    resizing_col: Option<usize>,
248
249    /// The insertion gap index (`0..=cols_count`) while dragging a column
250    /// header: the dragged column will be inserted between the columns
251    /// `gap - 1` and `gap` on drop.
252    col_drag_gap: Option<usize>,
253
254    /// The visible range of the rows and columns.
255    visible_range: TableVisibleRange,
256
257    _measure: Vec<Duration>,
258    _load_more_task: Task<()>,
259}
260
261impl<D> TableState<D>
262where
263    D: TableDelegate,
264{
265    /// Create a new TableState with the given delegate.
266    pub fn new(delegate: D, _: &mut Window, cx: &mut Context<Self>) -> Self {
267        let mut this = Self {
268            focus_handle: cx.focus_handle().tab_stop(true),
269            options: TableOptions::default(),
270            delegate,
271            col_groups: Vec::new(),
272            header_layout: Vec::new(),
273            horizontal_scroll_handle: VirtualListScrollHandle::new(),
274            vertical_scroll_handle: UniformListScrollHandle::new(),
275            selection_mode: SelectionMode::Row,
276            selected_row: None,
277            right_clicked_row: None,
278            right_clicked_cell: None,
279            selected_col: None,
280            selected_cell: None,
281            resizing_col: None,
282            col_drag_gap: None,
283            bounds: Bounds::default(),
284            fixed_head_cols_bounds: Bounds::default(),
285            visible_range: TableVisibleRange::default(),
286            loop_selection: true,
287            col_selectable: true,
288            row_selectable: true,
289            cell_selectable: false,
290            row_header: true,
291            sortable: true,
292            col_movable: true,
293            col_resizable: true,
294            col_fixed: true,
295            _load_more_task: Task::ready(()),
296            _measure: Vec::new(),
297        };
298
299        this.prepare_col_groups(cx);
300        this
301    }
302
303    /// Returns a reference to the delegate.
304    pub fn delegate(&self) -> &D {
305        &self.delegate
306    }
307
308    /// Returns a mutable reference to the delegate.
309    pub fn delegate_mut(&mut self) -> &mut D {
310        &mut self.delegate
311    }
312
313    /// Set to loop selection, default to true.
314    pub fn loop_selection(mut self, loop_selection: bool) -> Self {
315        self.loop_selection = loop_selection;
316        self
317    }
318
319    /// Set to enable/disable column movable, default to true.
320    pub fn col_movable(mut self, col_movable: bool) -> Self {
321        self.col_movable = col_movable;
322        self
323    }
324
325    /// Set to enable/disable column resizable, default to true.
326    pub fn col_resizable(mut self, col_resizable: bool) -> Self {
327        self.col_resizable = col_resizable;
328        self
329    }
330
331    /// Set to enable/disable column sortable, default true
332    pub fn sortable(mut self, sortable: bool) -> Self {
333        self.sortable = sortable;
334        self
335    }
336
337    /// Set to enable/disable row selectable, default true
338    pub fn row_selectable(mut self, row_selectable: bool) -> Self {
339        self.row_selectable = row_selectable;
340        self
341    }
342
343    /// Set to enable/disable column selectable, default true
344    pub fn col_selectable(mut self, col_selectable: bool) -> Self {
345        self.col_selectable = col_selectable;
346        self
347    }
348
349    /// Set to enable/disable cell selection, default is false.
350    ///
351    /// When enabled:
352    /// - Individual cells become selectable by clicking
353    /// - A row header column appears on the left side (can be hidden via [`Self::row_header`])
354    /// - Keyboard navigation operates at the cell level
355    /// - Cell-specific events (SelectCell, DoubleClickedCell, RightClickedCell) are emitted
356    ///
357    /// # Example
358    ///
359    /// ```rust,ignore
360    /// let table_state = cx.new(|cx| {
361    ///     TableState::new(delegate, cx)
362    ///         .cell_selectable(true)  // Enable cell selection
363    ///         .row_selectable(true)   // Also allow row selection via row header
364    /// });
365    /// ```
366    pub fn cell_selectable(mut self, cell_selectable: bool) -> Self {
367        self.cell_selectable = cell_selectable;
368        self
369    }
370
371    /// Set whether the row header column is shown, default is `true`.
372    ///
373    /// Only effective when `cell_selectable` is `true` — otherwise the row header
374    /// column is never rendered. Hide it when you want to use the leftmost column
375    /// for your own content (e.g. a row index column).
376    ///
377    /// When hidden, the first click on a cell selects the cell; clicking the
378    /// already-selected cell again escalates to selecting the whole row, so users
379    /// can still pick rows without the dedicated header column. The row escalation
380    /// requires `row_selectable` to be enabled.
381    pub fn row_header(mut self, row_header: bool) -> Self {
382        self.row_header = row_header;
383        self
384    }
385
386    /// When we update columns or rows, we need to refresh the table.
387    pub fn refresh(&mut self, cx: &mut Context<Self>) {
388        self.prepare_col_groups(cx);
389    }
390
391    /// Scroll to the row at the given index.
392    pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
393        self.vertical_scroll_handle
394            .scroll_to_item(row_ix, ScrollStrategy::Top);
395        cx.notify();
396    }
397
398    // Scroll to the column at the given index.
399    pub fn scroll_to_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
400        let col_ix = col_ix.saturating_sub(self.fixed_left_cols_count());
401
402        // Resolve the offset here instead of deferring it to the virtual list.
403        //
404        // The header and the rows share `horizontal_scroll_handle`, but only
405        // the rows are a `VirtualList`, and a deferred `scroll_to_item` is
406        // applied during that list's prepaint — which runs *after* the header
407        // has already been rendered and prepainted with the old offset. The
408        // header would therefore stay one frame behind the rows.
409        match self.horizontal_offset_for_col(col_ix) {
410            Some(offset_x) => {
411                let mut offset = self.horizontal_scroll_handle.offset();
412                offset.x = offset_x;
413                self.horizontal_scroll_handle.set_offset(offset);
414            }
415            // Before the first layout the viewport size is unknown, let the
416            // virtual list resolve the offset once it has been laid out.
417            None => self
418                .horizontal_scroll_handle
419                .scroll_to_item(col_ix, ScrollStrategy::Top),
420        }
421
422        cx.notify();
423    }
424
425    /// The horizontal scroll offset that brings the scrollable (non-fixed)
426    /// column at `col_ix` fully into view.
427    ///
428    /// This mirrors the nearest-edge behavior of [`VirtualListScrollHandle::scroll_to_item`]
429    /// with [`ScrollStrategy::Top`]: an already visible column keeps the current
430    /// offset, otherwise the column is aligned to the edge it overflows. Both
431    /// of those move the offset towards a column that exists, so the result
432    /// never runs past the content and needs no extra clamping.
433    ///
434    /// Returns `None` when the viewport size is not known yet, or when `col_ix`
435    /// is out of range.
436    fn horizontal_offset_for_col(&self, col_ix: usize) -> Option<Pixels> {
437        let viewport_width = self.horizontal_scroll_handle.bounds().size.width;
438        if viewport_width <= px(0.) {
439            return None;
440        }
441
442        let cols = self.col_groups.get(self.fixed_left_cols_count()..)?;
443        let col_left: Pixels = cols.get(..col_ix)?.iter().map(|col| col.width).sum();
444        let col_right = col_left + cols.get(col_ix)?.width;
445        let offset_x = self.horizontal_scroll_handle.offset().x;
446
447        Some(if col_left + offset_x < px(0.) {
448            -col_left
449        } else if col_right + offset_x > viewport_width {
450            viewport_width - col_right
451        } else {
452            offset_x
453        })
454    }
455
456    /// Returns the selected row index.
457    pub fn selected_row(&self) -> Option<usize> {
458        self.selected_row
459    }
460
461    /// Sets the selected row to the given index.
462    pub fn set_selected_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
463        let is_down = match self.selected_row {
464            Some(selected_row) => row_ix > selected_row,
465            None => true,
466        };
467
468        cx.stop_propagation();
469        self.selection_mode = SelectionMode::Row;
470        self.right_clicked_row = None;
471        self.selected_row = Some(row_ix);
472        if let Some(row_ix) = self.selected_row {
473            self.vertical_scroll_handle.scroll_to_item(
474                row_ix,
475                if is_down {
476                    ScrollStrategy::Bottom
477                } else {
478                    ScrollStrategy::Top
479                },
480            );
481        }
482        cx.emit(TableEvent::SelectRow(row_ix));
483        cx.emit(TableEvent::RightClickedRow(None));
484        cx.notify();
485    }
486
487    /// Returns the row that has been right clicked.
488    pub fn right_clicked_row(&self) -> Option<usize> {
489        self.right_clicked_row
490    }
491
492    /// Set or clear the right-clicked row state.
493    ///
494    /// Pass `None` to clear — useful when opening a header context menu
495    /// to prevent the row context menu from appearing simultaneously.
496    pub fn set_right_clicked_row(&mut self, row: Option<usize>, cx: &mut Context<Self>) {
497        self.right_clicked_row = row;
498        cx.notify();
499    }
500
501    /// Returns the selected column index.
502    pub fn selected_col(&self) -> Option<usize> {
503        self.selected_col
504    }
505
506    /// Sets the selected col to the given index.
507    pub fn set_selected_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
508        self.selection_mode = SelectionMode::Column;
509        self.selected_col = Some(col_ix);
510        if let Some(col_ix) = self.selected_col {
511            self.scroll_to_col(col_ix, cx);
512        }
513        cx.emit(TableEvent::SelectColumn(col_ix));
514        cx.notify();
515    }
516
517    /// Returns the selected cell as `(row_ix, col_ix)`.
518    ///
519    /// Returns `None` if no cell is currently selected or if the table is in row/column selection mode.
520    ///
521    /// # Example
522    ///
523    /// ```rust,ignore
524    /// if let Some((row_ix, col_ix)) = table_state.read(cx).selected_cell() {
525    ///     println!("Selected cell: ({}, {})", row_ix, col_ix);
526    /// }
527    /// ```
528    pub fn selected_cell(&self) -> Option<(usize, usize)> {
529        self.selected_cell
530    }
531
532    /// Sets the selected cell to the given row and column indices.
533    ///
534    /// This method:
535    /// - Switches the table to cell selection mode
536    /// - Scrolls to make the cell visible (centered vertically)
537    /// - Emits a [`TableEvent::SelectCell`] event
538    ///
539    /// # Example
540    ///
541    /// ```rust,ignore
542    /// // Select the cell at row 5, column 3
543    /// table_state.update(cx, |state, cx| {
544    ///     state.set_selected_cell(5, 3, cx);
545    /// });
546    /// ```
547    pub fn set_selected_cell(&mut self, row_ix: usize, col_ix: usize, cx: &mut Context<Self>) {
548        self.selection_mode = SelectionMode::Cell;
549        self.selected_cell = Some((row_ix, col_ix));
550
551        // Scroll to the cell
552        self.vertical_scroll_handle
553            .scroll_to_item(row_ix, ScrollStrategy::Center);
554        self.scroll_to_col(col_ix, cx);
555
556        cx.emit(TableEvent::SelectCell(row_ix, col_ix));
557        cx.notify();
558    }
559
560    /// Clear the selection of the table.
561    pub fn clear_selection(&mut self, cx: &mut Context<Self>) {
562        self.selection_mode = SelectionMode::Row;
563        self.selected_row = None;
564        self.selected_col = None;
565        self.selected_cell = None;
566        cx.emit(TableEvent::ClearSelection);
567        cx.notify();
568    }
569
570    /// Returns the visible range of the rows and columns.
571    ///
572    /// See [`TableVisibleRange`].
573    pub fn visible_range(&self) -> &TableVisibleRange {
574        &self.visible_range
575    }
576
577    /// Dump the header row of the table.
578    ///
579    /// Batched exporters can read the headers once with this, then stream the
580    /// rows with [`Self::dump_range`].
581    pub fn headers(&self, cx: &App) -> Vec<String> {
582        let columns_count = self.delegate.columns_count(cx);
583        let mut headers = Vec::with_capacity(columns_count);
584        for col_ix in 0..columns_count {
585            let column = self.delegate.column(col_ix, cx);
586            headers.push(column.name.to_string());
587        }
588
589        headers
590    }
591
592    /// Dump table data.
593    ///
594    /// Returns a tuple of (headers, rows) where each row is a vector of cell values.
595    ///
596    /// This materializes the complete table in memory. For large tables, prefer
597    /// [`Self::dump_range`] and process rows in batches.
598    pub fn dump(&self, cx: &App) -> (Vec<String>, Vec<Vec<String>>) {
599        self.dump_range(0..self.delegate.rows_count(cx), cx)
600    }
601
602    /// Dump table data for the specified row range.
603    ///
604    /// Returns the same `(headers, rows)` shape as [`Self::dump`], with only
605    /// the rows inside the clamped range.
606    ///
607    /// The requested range is clamped to the table's current row count. For
608    /// large tables, callers can invoke this repeatedly with bounded ranges.
609    pub fn dump_range(&self, range: Range<usize>, cx: &App) -> (Vec<String>, Vec<Vec<String>>) {
610        let columns_count = self.delegate.columns_count(cx);
611        let rows_count = self.delegate.rows_count(cx);
612        let start = range.start.min(rows_count);
613        let end = range.end.min(rows_count).max(start);
614
615        let mut rows = Vec::with_capacity(end - start);
616        for row_ix in start..end {
617            let mut row = Vec::with_capacity(columns_count);
618            for col_ix in 0..columns_count {
619                row.push(self.delegate.cell_text(row_ix, col_ix, cx));
620            }
621            rows.push(row);
622        }
623
624        (self.headers(cx), rows)
625    }
626
627    /// Re-compute the header layout from the current delegate.
628    ///
629    /// Call this after changing delegate state that affects `group_headers`.
630    pub fn refresh_header_layout(&mut self, cx: &mut Context<Self>) {
631        self.update_header_layout(cx);
632        cx.notify();
633    }
634
635    fn prepare_col_groups(&mut self, cx: &mut Context<Self>) {
636        self.col_groups = (0..self.delegate.columns_count(cx))
637            .map(|col_ix| {
638                let column = self.delegate().column(col_ix, cx);
639                ColGroup {
640                    width: column.width,
641                    bounds: Bounds::default(),
642                    column,
643                }
644            })
645            .collect();
646
647        self.update_header_layout(cx);
648    }
649
650    fn update_header_layout(&mut self, cx: &mut Context<Self>) {
651        let group_rows = self.delegate.group_headers(cx);
652
653        let mut layout = match group_rows.as_ref() {
654            Some(rows) => Vec::with_capacity(rows.len() + 1),
655            None => Vec::with_capacity(1),
656        };
657
658        if let Some(group_rows) = group_rows {
659            for row in group_rows {
660                let mut cell_row = Vec::with_capacity(row.len());
661                let mut current_leaf_ix = 0;
662                for group in row {
663                    let mut width = px(0.);
664                    let start_leaf_col_ix = current_leaf_ix;
665                    for i in 0..group.span {
666                        if current_leaf_ix + i < self.col_groups.len() {
667                            width += self.col_groups[current_leaf_ix + i].width;
668                        }
669                    }
670                    current_leaf_ix += group.span;
671                    cell_row.push(HeaderCell {
672                        label: group.label.clone(),
673                        width,
674                        col_span: group.span,
675                        is_leaf: false,
676                        leaf_col_ix: None,
677                        start_leaf_col_ix,
678                    });
679                }
680                layout.push(cell_row);
681            }
682        }
683
684        let mut leaf_row = Vec::with_capacity(self.col_groups.len());
685        for (ix, group) in self.col_groups.iter().enumerate() {
686            leaf_row.push(HeaderCell {
687                label: group.column.name.clone(),
688                width: group.width,
689                col_span: 1,
690                is_leaf: true,
691                leaf_col_ix: Some(ix),
692                start_leaf_col_ix: ix,
693            });
694        }
695        layout.push(leaf_row);
696
697        self.header_layout = layout;
698    }
699
700    fn fixed_left_cols_count(&self) -> usize {
701        if !self.col_fixed {
702            return 0;
703        }
704
705        self.col_groups
706            .iter()
707            .filter(|col| col.column.fixed == Some(ColumnFixed::Left))
708            .count()
709    }
710
711    fn page_item_count(&self) -> usize {
712        let row_height = self.options.size.table_row_height();
713        let height = self.bounds.size.height;
714        let count = (height / row_height).floor() as usize;
715        count.saturating_sub(1).max(1)
716    }
717
718    fn on_row_right_click(
719        &mut self,
720        _: &MouseDownEvent,
721        row_ix: Option<usize>,
722        _: &mut Window,
723        cx: &mut Context<Self>,
724    ) {
725        self.right_clicked_row = row_ix;
726        self.right_clicked_cell = None;
727        cx.emit(TableEvent::RightClickedRow(row_ix));
728    }
729
730    fn on_cell_right_click(
731        &mut self,
732        _: &MouseDownEvent,
733        row_ix: usize,
734        col_ix: usize,
735        _: &mut Window,
736        cx: &mut Context<Self>,
737    ) {
738        if !self.cell_selectable {
739            return;
740        }
741
742        cx.stop_propagation();
743        self.right_clicked_cell = Some((row_ix, col_ix));
744        self.right_clicked_row = None;
745        cx.emit(TableEvent::RightClickedCell(row_ix, col_ix));
746    }
747
748    fn on_row_left_click(
749        &mut self,
750        e: &ClickEvent,
751        row_ix: usize,
752        _: &mut Window,
753        cx: &mut Context<Self>,
754    ) {
755        if !self.row_selectable {
756            return;
757        }
758
759        self.set_selected_row(row_ix, cx);
760
761        if e.click_count() == 2 {
762            cx.emit(TableEvent::DoubleClickedRow(row_ix));
763        }
764    }
765
766    fn on_col_head_click(&mut self, col_ix: usize, _: &mut Window, cx: &mut Context<Self>) {
767        if !self.col_selectable {
768            return;
769        }
770
771        let Some(col_group) = self.col_groups.get(col_ix) else {
772            return;
773        };
774
775        if !col_group.column.selectable {
776            return;
777        }
778
779        self.set_selected_col(col_ix, cx)
780    }
781
782    fn on_cell_click(
783        &mut self,
784        e: &ClickEvent,
785        row_ix: usize,
786        col_ix: usize,
787        _: &mut Window,
788        cx: &mut Context<Self>,
789    ) {
790        if !self.cell_selectable {
791            return;
792        }
793
794        cx.stop_propagation();
795
796        let is_double_click = e.click_count() == 2;
797
798        // When the row header column is hidden, a single click on the
799        // already-selected cell escalates the selection to the entire row —
800        // giving users a way to pick rows without the dedicated header column.
801        // Double-clicks are passed through to `DoubleClickedCell` and never
802        // trigger the escalation.
803        let is_reselect =
804            self.selection_mode.is_cell() && self.selected_cell == Some((row_ix, col_ix));
805        let should_escalate_to_row =
806            !self.row_header && self.row_selectable && is_reselect && !is_double_click;
807        if should_escalate_to_row {
808            self.set_selected_row(row_ix, cx);
809            return;
810        }
811
812        self.set_selected_cell(row_ix, col_ix, cx);
813
814        if is_double_click {
815            cx.emit(TableEvent::DoubleClickedCell(row_ix, col_ix));
816        }
817    }
818
819    fn has_selection(&self) -> bool {
820        self.selected_row.is_some() || self.selected_col.is_some() || self.selected_cell.is_some()
821    }
822
823    pub(super) fn action_cancel(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {
824        if self.has_selection() {
825            self.clear_selection(cx);
826            return;
827        }
828        cx.propagate();
829    }
830
831    pub(super) fn action_select_prev(
832        &mut self,
833        _: &SelectUp,
834        _: &mut Window,
835        cx: &mut Context<Self>,
836    ) {
837        let rows_count = self.delegate.rows_count(cx);
838        if rows_count < 1 {
839            return;
840        }
841
842        // Cell selection mode: move up within the same column
843        if self.selection_mode.is_cell() {
844            if let Some((row_ix, col_ix)) = self.selected_cell {
845                let new_row = if row_ix > 0 {
846                    row_ix.saturating_sub(1)
847                } else if self.loop_selection {
848                    rows_count.saturating_sub(1)
849                } else {
850                    row_ix
851                };
852                self.set_selected_cell(new_row, col_ix, cx);
853            } else {
854                // No cell selected, select first cell
855                self.set_selected_cell(0, 0, cx);
856            }
857            return;
858        }
859
860        // Row selection mode
861        let mut selected_row = self.selected_row.unwrap_or(0);
862        if selected_row > 0 {
863            selected_row = selected_row.saturating_sub(1);
864        } else {
865            if self.loop_selection {
866                selected_row = rows_count.saturating_sub(1);
867            }
868        }
869
870        self.set_selected_row(selected_row, cx);
871    }
872
873    pub(super) fn action_select_next(
874        &mut self,
875        _: &SelectDown,
876        _: &mut Window,
877        cx: &mut Context<Self>,
878    ) {
879        let rows_count = self.delegate.rows_count(cx);
880        if rows_count < 1 {
881            return;
882        }
883
884        // Cell selection mode: move down within the same column
885        if self.selection_mode.is_cell() {
886            if let Some((row_ix, col_ix)) = self.selected_cell {
887                let new_row = if row_ix < rows_count.saturating_sub(1) {
888                    row_ix + 1
889                } else if self.loop_selection {
890                    0
891                } else {
892                    row_ix
893                };
894                self.set_selected_cell(new_row, col_ix, cx);
895            } else {
896                // No cell selected, select first cell
897                self.set_selected_cell(0, 0, cx);
898            }
899            return;
900        }
901
902        // Row selection mode
903        let selected_row = match self.selected_row {
904            Some(selected_row) if selected_row < rows_count.saturating_sub(1) => selected_row + 1,
905            Some(selected_row) => {
906                if self.loop_selection {
907                    0
908                } else {
909                    selected_row
910                }
911            }
912            _ => 0,
913        };
914
915        self.set_selected_row(selected_row, cx);
916    }
917
918    pub(super) fn action_select_first_column(
919        &mut self,
920        _: &SelectFirst,
921        _: &mut Window,
922        cx: &mut Context<Self>,
923    ) {
924        // Cell selection mode: move to first cell in current row
925        if self.selection_mode.is_cell() {
926            if let Some((row_ix, _)) = self.selected_cell {
927                self.set_selected_cell(row_ix, 0, cx);
928            } else {
929                // No cell selected, select first cell of first row
930                self.set_selected_cell(0, 0, cx);
931            }
932            return;
933        }
934
935        // Column selection mode
936        self.set_selected_col(0, cx);
937    }
938
939    pub(super) fn action_select_last_column(
940        &mut self,
941        _: &SelectLast,
942        _: &mut Window,
943        cx: &mut Context<Self>,
944    ) {
945        let columns_count = self.delegate.columns_count(cx);
946
947        // Cell selection mode: move to last cell in current row
948        if self.selection_mode.is_cell() {
949            if let Some((row_ix, _)) = self.selected_cell {
950                self.set_selected_cell(row_ix, columns_count.saturating_sub(1), cx);
951            } else {
952                // No cell selected, select last cell of first row
953                self.set_selected_cell(0, columns_count.saturating_sub(1), cx);
954            }
955            return;
956        }
957
958        // Column selection mode
959        self.set_selected_col(columns_count.saturating_sub(1), cx);
960    }
961
962    pub(super) fn action_select_page_up(
963        &mut self,
964        _: &SelectPageUp,
965        _: &mut Window,
966        cx: &mut Context<Self>,
967    ) {
968        let step = self.page_item_count();
969
970        // Cell selection mode: move up by page within the same column
971        if self.selection_mode.is_cell() {
972            if let Some((row_ix, col_ix)) = self.selected_cell {
973                let target = row_ix.saturating_sub(step);
974                self.set_selected_cell(target, col_ix, cx);
975            } else {
976                // No cell selected, select first cell
977                self.set_selected_cell(0, 0, cx);
978            }
979            return;
980        }
981
982        // Row selection mode
983        let current = self.selected_row.unwrap_or(0);
984        let target = current.saturating_sub(step);
985        self.set_selected_row(target, cx);
986    }
987
988    pub(super) fn action_select_page_down(
989        &mut self,
990        _: &SelectPageDown,
991        _: &mut Window,
992        cx: &mut Context<Self>,
993    ) {
994        let rows_count = self.delegate.rows_count(cx);
995        if rows_count == 0 {
996            return;
997        }
998
999        let step = self.page_item_count();
1000
1001        // Cell selection mode: move down by page within the same column
1002        if self.selection_mode.is_cell() {
1003            if let Some((row_ix, col_ix)) = self.selected_cell {
1004                let max_row = rows_count.saturating_sub(1);
1005                let target = (row_ix + step).min(max_row);
1006                self.set_selected_cell(target, col_ix, cx);
1007            } else {
1008                // No cell selected, select first cell
1009                self.set_selected_cell(0, 0, cx);
1010            }
1011            return;
1012        }
1013
1014        // Row selection mode
1015        let current = self.selected_row.unwrap_or(0);
1016        let max_row = rows_count.saturating_sub(1);
1017        let target = (current + step).min(max_row);
1018        self.set_selected_row(target, cx);
1019    }
1020
1021    pub(super) fn action_select_prev_col(
1022        &mut self,
1023        _: &SelectPrevColumn,
1024        _: &mut Window,
1025        cx: &mut Context<Self>,
1026    ) {
1027        let columns_count = self.delegate.columns_count(cx);
1028
1029        // Cell selection mode: move left within the same row
1030        if self.selection_mode.is_cell() {
1031            if let Some((row_ix, col_ix)) = self.selected_cell {
1032                let new_col = if col_ix > 0 {
1033                    col_ix.saturating_sub(1)
1034                } else if self.loop_selection {
1035                    columns_count.saturating_sub(1)
1036                } else {
1037                    col_ix
1038                };
1039                self.set_selected_cell(row_ix, new_col, cx);
1040            } else {
1041                // No cell selected, select first cell
1042                self.set_selected_cell(0, 0, cx);
1043            }
1044            return;
1045        }
1046
1047        // Column selection mode
1048        let mut selected_col = self.selected_col.unwrap_or(0);
1049        if selected_col > 0 {
1050            selected_col = selected_col.saturating_sub(1);
1051        } else {
1052            if self.loop_selection {
1053                selected_col = columns_count.saturating_sub(1);
1054            }
1055        }
1056        self.set_selected_col(selected_col, cx);
1057    }
1058
1059    pub(super) fn action_select_next_col(
1060        &mut self,
1061        _: &SelectNextColumn,
1062        _: &mut Window,
1063        cx: &mut Context<Self>,
1064    ) {
1065        let columns_count = self.delegate.columns_count(cx);
1066
1067        // Cell selection mode: move right within the same row
1068        if self.selection_mode.is_cell() {
1069            if let Some((row_ix, col_ix)) = self.selected_cell {
1070                let new_col = if col_ix < columns_count.saturating_sub(1) {
1071                    col_ix + 1
1072                } else if self.loop_selection {
1073                    0
1074                } else {
1075                    col_ix
1076                };
1077                self.set_selected_cell(row_ix, new_col, cx);
1078            } else {
1079                // No cell selected, select first cell
1080                self.set_selected_cell(0, 0, cx);
1081            }
1082            return;
1083        }
1084
1085        // Column selection mode
1086        let mut selected_col = self.selected_col.unwrap_or(0);
1087        if selected_col < columns_count.saturating_sub(1) {
1088            selected_col += 1;
1089        } else {
1090            if self.loop_selection {
1091                selected_col = 0;
1092            }
1093        }
1094
1095        self.set_selected_col(selected_col, cx);
1096    }
1097
1098    /// Scroll table when mouse position is near the edge of the table bounds.
1099    fn scroll_table_by_col_resizing(
1100        &mut self,
1101        mouse_position: Point<Pixels>,
1102        col_group: &ColGroup,
1103    ) {
1104        // Do nothing if pos out of the table bounds right for avoid scroll to the right.
1105        if mouse_position.x > self.bounds.right() {
1106            return;
1107        }
1108
1109        let mut offset = self.horizontal_scroll_handle.offset();
1110        let col_bounds = col_group.bounds;
1111
1112        if mouse_position.x < self.bounds.left()
1113            && col_bounds.right() < self.bounds.left() + px(20.)
1114        {
1115            offset.x += px(1.);
1116        } else if mouse_position.x > self.bounds.right()
1117            && col_bounds.right() > self.bounds.right() - px(20.)
1118        {
1119            offset.x -= px(1.);
1120        }
1121
1122        self.horizontal_scroll_handle.set_offset(offset);
1123    }
1124
1125    /// The `ix`` is the index of the col to resize,
1126    /// and the `size` is the new size for the col.
1127    fn resize_cols(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
1128        if !self.col_resizable {
1129            return;
1130        }
1131
1132        let mut changed = false;
1133        if let Some(col_group) = self.col_groups.get_mut(ix) {
1134            if col_group.is_resizable() {
1135                let new_width = size.clamp(col_group.column.min_width, col_group.column.max_width);
1136                if col_group.width != new_width {
1137                    col_group.width = new_width;
1138                    changed = true;
1139                }
1140            }
1141        }
1142
1143        if changed {
1144            self.update_header_layout(cx);
1145            cx.notify();
1146        }
1147    }
1148
1149    fn perform_sort(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) {
1150        if !self.sortable {
1151            return;
1152        }
1153
1154        let sort = self.col_groups.get(col_ix).and_then(|g| g.column.sort);
1155        if sort.is_none() {
1156            return;
1157        }
1158
1159        let sort = sort.unwrap();
1160        let sort = match sort {
1161            ColumnSort::Ascending => ColumnSort::Default,
1162            ColumnSort::Descending => ColumnSort::Ascending,
1163            ColumnSort::Default => ColumnSort::Descending,
1164        };
1165
1166        for (ix, col_group) in self.col_groups.iter_mut().enumerate() {
1167            if ix == col_ix {
1168                col_group.column.sort = Some(sort);
1169            } else {
1170                if col_group.column.sort.is_some() {
1171                    col_group.column.sort = Some(ColumnSort::Default);
1172                }
1173            }
1174        }
1175
1176        self.delegate_mut().perform_sort(col_ix, sort, window, cx);
1177
1178        cx.notify();
1179    }
1180
1181    fn move_column(
1182        &mut self,
1183        col_ix: usize,
1184        to_ix: usize,
1185        window: &mut Window,
1186        cx: &mut Context<Self>,
1187    ) {
1188        if col_ix == to_ix {
1189            return;
1190        }
1191
1192        self.delegate.move_column(col_ix, to_ix, window, cx);
1193        let col_group = self.col_groups.remove(col_ix);
1194        self.col_groups.insert(to_ix, col_group);
1195
1196        cx.emit(TableEvent::MoveColumn(col_ix, to_ix));
1197        cx.notify();
1198    }
1199
1200    /// Resolve the insertion gap for a column-header drag at the window
1201    /// coordinate `x`, or `None` when dropping there would not move the
1202    /// dragged column at `drag_col_ix`.
1203    fn drag_gap_at(&self, x: Pixels, drag_col_ix: usize) -> Option<usize> {
1204        let fixed_count = self.fixed_left_cols_count();
1205
1206        // A column can only be reordered within its own region: rendering
1207        // pins the first `fixed_count` columns, so a cross-region move would
1208        // change which columns are pinned without updating their `fixed`
1209        // flags.
1210        let drag_in_fixed = drag_col_ix < fixed_count;
1211        let pointer_in_fixed = fixed_count > 0 && x < self.fixed_head_cols_bounds.right();
1212        if drag_in_fixed != pointer_in_fixed {
1213            return None;
1214        }
1215
1216        // Columns scrolled beneath the fixed region keep stale bounds, so
1217        // resolve `x` against the fixed columns alone when it falls in that
1218        // region, and against the visible scrollable columns otherwise.
1219        let candidates = if pointer_in_fixed {
1220            0..fixed_count
1221        } else {
1222            self.calculate_visible_leaf_col_range(fixed_count).0
1223        };
1224
1225        // The gap sits after the last candidate column whose center is left of `x`.
1226        let mut gap = candidates.start;
1227        for ix in candidates {
1228            if x < self.col_groups[ix].bounds.center().x {
1229                break;
1230            }
1231            gap = ix + 1;
1232        }
1233
1234        // No gap if dropping there would put the dragged column back to
1235        // where it already is.
1236        if gap == drag_col_ix || gap == drag_col_ix + 1 {
1237            None
1238        } else {
1239            Some(gap)
1240        }
1241    }
1242
1243    /// Dispatch delegate's `load_more` method when the visible range is near the end.
1244    fn load_more_if_need(
1245        &mut self,
1246        rows_count: usize,
1247        visible_end: usize,
1248        window: &mut Window,
1249        cx: &mut Context<Self>,
1250    ) {
1251        let threshold = self.delegate.load_more_threshold();
1252        // Securely handle subtract logic to prevent attempt to subtract with overflow
1253        if visible_end >= rows_count.saturating_sub(threshold) {
1254            if !self.delegate.has_more(cx) {
1255                return;
1256            }
1257
1258            self._load_more_task = cx.spawn_in(window, async move |view, window| {
1259                _ = view.update_in(window, |view, window, cx| {
1260                    view.delegate.load_more(window, cx);
1261                });
1262            });
1263        }
1264    }
1265
1266    fn update_visible_range_if_need(
1267        &mut self,
1268        visible_range: Range<usize>,
1269        axis: Axis,
1270        window: &mut Window,
1271        cx: &mut Context<Self>,
1272    ) {
1273        // Skip when visible range is only 1 item.
1274        // The visual_list will use first item to measure.
1275        if visible_range.len() <= 1 {
1276            return;
1277        }
1278
1279        if axis == Axis::Vertical {
1280            if self.visible_range.rows == visible_range {
1281                return;
1282            }
1283            self.delegate_mut()
1284                .visible_rows_changed(visible_range.clone(), window, cx);
1285            self.visible_range.rows = visible_range;
1286        } else {
1287            if self.visible_range.cols == visible_range {
1288                return;
1289            }
1290            self.delegate_mut()
1291                .visible_columns_changed(visible_range.clone(), window, cx);
1292            self.visible_range.cols = visible_range;
1293        }
1294    }
1295
1296    fn render_cell(
1297        &self,
1298        _row_ix: Option<usize>,
1299        col_ix: usize,
1300        _window: &mut Window,
1301        _cx: &mut Context<Self>,
1302    ) -> Div {
1303        let Some(col_group) = self.col_groups.get(col_ix) else {
1304            return div();
1305        };
1306
1307        let col_width = col_group.width;
1308        let col_padding = col_group.column.paddings;
1309
1310        div()
1311            .w(col_width)
1312            .h_full()
1313            .flex_shrink_0()
1314            .overflow_hidden()
1315            .whitespace_nowrap()
1316            .table_cell_size(self.options.size)
1317            .map(|this| match col_padding {
1318                Some(padding) => this
1319                    .pl(padding.left)
1320                    .pr(padding.right)
1321                    .pt(padding.top)
1322                    .pb(padding.bottom),
1323                None => this,
1324            })
1325    }
1326
1327    /// Show Column selection style, when the column is selected and the selection state is Column.
1328    /// Note: When a cell is selected, column selection style is not shown.
1329    fn render_col_wrap(
1330        &self,
1331        _row_ix: Option<usize>,
1332        col_ix: usize,
1333        _: &mut Window,
1334        cx: &mut Context<Self>,
1335    ) -> Div {
1336        let el = h_flex().h_full();
1337        let selectable = self.col_selectable
1338            && self
1339                .col_groups
1340                .get(col_ix)
1341                .map(|col_group| col_group.column.selectable)
1342                .unwrap_or(false);
1343
1344        // Don't show column selection if a cell is selected
1345        if self.selection_mode.is_cell() {
1346            return el;
1347        }
1348
1349        if selectable && self.selected_col == Some(col_ix) && self.selection_mode.is_column() {
1350            el.bg(cx.theme().tokens.table_active)
1351        } else {
1352            el
1353        }
1354    }
1355
1356    /// The other half of that band, for the boundary on the *left* of column
1357    /// `ix` — the one owned by column `ix - 1`.
1358    ///
1359    /// It is positioned absolutely so that reaching back over the boundary
1360    /// costs the header row no width, and it paints after this column's own
1361    /// cell so that it, and not a column reorder, receives the drag.
1362    fn render_leading_resize_handle(
1363        &self,
1364        ix: usize,
1365        _: &mut Window,
1366        cx: &mut Context<Self>,
1367    ) -> impl IntoElement {
1368        let Some(prev) = ix.checked_sub(1).filter(|ix| self.is_col_resizable(*ix)) else {
1369            return div().into_any_element();
1370        };
1371
1372        self.resize_handle_band(prev, ("resizable-handle-leading", ix).into(), cx)
1373            .absolute()
1374            .top_0()
1375            .left_0()
1376            .h_full()
1377            .w(HANDLE_PADDING)
1378            .into_any_element()
1379    }
1380
1381    /// The half of column `ix`'s resize handle that lies inside column `ix`,
1382    /// along with the hairline that marks the boundary.
1383    ///
1384    /// It is [`HANDLE_PADDING`] wide, like the half on the far side, so the
1385    /// grab area is symmetric about the boundary. The negative margin keeps
1386    /// the band's net contribution to the header row zero, and `justify_end`
1387    /// pins the hairline to the column edge.
1388    fn render_resize_handle(
1389        &self,
1390        ix: usize,
1391        _: &mut Window,
1392        cx: &mut Context<Self>,
1393    ) -> impl IntoElement {
1394        if !self.is_col_resizable(ix) {
1395            return div().into_any_element();
1396        }
1397
1398        let group_id = SharedString::from(format!("resizable-handle:{}", ix));
1399
1400        self.resize_handle_band(ix, ("resizable-handle", ix).into(), cx)
1401            .group(group_id.clone())
1402            .h_full()
1403            .w(HANDLE_PADDING)
1404            .ml(-HANDLE_PADDING)
1405            .justify_end()
1406            .items_center()
1407            .child(
1408                div()
1409                    .h_full()
1410                    .justify_center()
1411                    .bg(cx.theme().table_row_border)
1412                    .group_hover(&group_id, |this| this.bg(cx.theme().border).h_full())
1413                    .w(px(1.)),
1414            )
1415            .into_any_element()
1416    }
1417
1418    fn is_col_resizable(&self, ix: usize) -> bool {
1419        self.col_resizable
1420            && self
1421                .col_groups
1422                .get(ix)
1423                .map(|col| col.is_resizable())
1424                .unwrap_or(false)
1425    }
1426
1427    /// The interactive band that drags the boundary on the right of column
1428    /// `ix`, carrying no styling that places or paints it.
1429    ///
1430    /// Paint order decides which element is offered a drag first: later
1431    /// siblings win, and a header cell starts a column reorder. So a single
1432    /// band straddling the boundary would lose its outer half to the next
1433    /// column's cell. Each boundary is covered by two bands instead, one in
1434    /// the `th` on either side, each rendered after that `th`'s own cell and
1435    /// so above everything it overlaps.
1436    fn resize_handle_band(
1437        &self,
1438        ix: usize,
1439        id: ElementId,
1440        cx: &mut Context<Self>,
1441    ) -> Stateful<Div> {
1442        h_flex()
1443            .id(id)
1444            .occlude()
1445            .cursor_col_resize()
1446            .on_drag_move(
1447                cx.listener(move |view, e: &DragMoveEvent<ResizeColumn>, window, cx| {
1448                    match e.drag(cx) {
1449                        ResizeColumn((entity_id, ix)) => {
1450                            if cx.entity_id() != *entity_id {
1451                                return;
1452                            }
1453
1454                            // sync col widths into real widths
1455                            // TODO: Consider to remove this, this may not need now.
1456                            // for (_, col_group) in view.col_groups.iter_mut().enumerate() {
1457                            //     col_group.width = col_group.bounds.size.width;
1458                            // }
1459
1460                            let ix = *ix;
1461                            view.resizing_col = Some(ix);
1462
1463                            let col_group = view
1464                                .col_groups
1465                                .get(ix)
1466                                .expect("BUG: invalid col index")
1467                                .clone();
1468
1469                            view.resize_cols(
1470                                ix,
1471                                e.event.position.x - HANDLE_SIZE - col_group.bounds.left(),
1472                                window,
1473                                cx,
1474                            );
1475
1476                            // scroll the table if the drag is near the edge
1477                            view.scroll_table_by_col_resizing(e.event.position, &col_group);
1478                        }
1479                    };
1480                }),
1481            )
1482            .on_drag(ResizeColumn((cx.entity_id(), ix)), |drag, _, _, cx| {
1483                cx.stop_propagation();
1484                cx.new(|_| drag.clone())
1485            })
1486            .on_mouse_up_out(
1487                MouseButton::Left,
1488                cx.listener(|view, _, _, cx| {
1489                    if view.resizing_col.is_none() {
1490                        return;
1491                    }
1492
1493                    view.resizing_col = None;
1494
1495                    let new_widths = view.col_groups.iter().map(|g| g.width).collect();
1496                    cx.emit(TableEvent::ColumnWidthsChanged(new_widths));
1497                    cx.notify();
1498                }),
1499            )
1500    }
1501
1502    /// Render the row header cell (when cell_selectable is enabled)
1503    fn render_row_header_cell(
1504        &self,
1505        row_ix: usize,
1506        is_head: bool,
1507        cx: &mut Context<Self>,
1508    ) -> impl IntoElement {
1509        div()
1510            .id(("row-header", row_ix))
1511            .w_3()
1512            .h_full()
1513            .border_r_1()
1514            .border_color(cx.theme().table_row_border)
1515            .bg(cx.theme().tokens.table_head)
1516            .flex_shrink_0()
1517            .table_cell_size(self.options.size)
1518            .when(!is_head, |this| {
1519                this.when(self.row_selectable, |this| {
1520                    this.on_click(cx.listener(move |table, _, _window, cx| {
1521                        table.set_selected_row(row_ix, cx);
1522                    }))
1523                })
1524            })
1525    }
1526
1527    fn render_sort_icon(
1528        &self,
1529        col_ix: usize,
1530        col_group: &ColGroup,
1531        _: &mut Window,
1532        cx: &mut Context<Self>,
1533    ) -> Option<impl IntoElement> {
1534        if !self.sortable {
1535            return None;
1536        }
1537
1538        let Some(sort) = col_group.column.sort else {
1539            return None;
1540        };
1541
1542        let (icon, is_on) = match sort {
1543            ColumnSort::Ascending => (IconName::SortAscending, true),
1544            ColumnSort::Descending => (IconName::SortDescending, true),
1545            ColumnSort::Default => (IconName::ChevronsUpDown, false),
1546        };
1547
1548        Some(
1549            div()
1550                .id(("icon-sort", col_ix))
1551                .p(px(2.))
1552                .rounded(cx.theme().radius / 2.)
1553                .map(|this| match is_on {
1554                    true => this,
1555                    false => this.opacity(0.5),
1556                })
1557                .hover(|this| this.bg(cx.theme().tokens.secondary).opacity(7.))
1558                .active(|this| this.bg(cx.theme().tokens.secondary_active).opacity(1.))
1559                .on_click(
1560                    cx.listener(move |table, _, window, cx| table.perform_sort(col_ix, window, cx)),
1561                )
1562                .child(
1563                    Icon::new(icon)
1564                        .size_3()
1565                        .text_color(cx.theme().secondary_foreground),
1566                ),
1567        )
1568    }
1569
1570    /// Render the column header.
1571    /// The children must be one by one items.
1572    /// Because the horizontal scroll handle will use the child_item_bounds to
1573    /// calculate the item position for itself's `scroll_to_item` method.
1574    fn render_th(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) -> Div {
1575        let entity_id = cx.entity_id();
1576        let col_group = self.col_groups.get(col_ix).expect("BUG: invalid col index");
1577
1578        let movable = self.col_movable && col_group.column.movable;
1579        let paddings = col_group.column.paddings;
1580        let name = col_group.column.name.clone();
1581
1582        h_flex()
1583            .h_full()
1584            .child(
1585                self.render_cell(None, col_ix, window, cx)
1586                    .id(("col-header", col_ix))
1587                    .on_click(cx.listener(move |this, _, window, cx| {
1588                        this.on_col_head_click(col_ix, window, cx);
1589                    }))
1590                    .child(
1591                        h_flex()
1592                            .size_full()
1593                            .justify_between()
1594                            .items_center()
1595                            .child(self.delegate.render_th(col_ix, window, cx))
1596                            .when_some(paddings, |this, paddings| {
1597                                // Leave right space for the sort icon, if this column have custom padding
1598                                let offset_pr =
1599                                    self.options.size.table_cell_padding().right - paddings.right;
1600                                this.pr(offset_pr.max(px(0.)))
1601                            })
1602                            .children(self.render_sort_icon(col_ix, &col_group, window, cx)),
1603                    )
1604                    .when(movable, |this| {
1605                        this.on_drag(
1606                            DragColumn {
1607                                entity_id,
1608                                col_ix,
1609                                name,
1610                                width: col_group.width,
1611                            },
1612                            |drag, _, _, cx| {
1613                                cx.stop_propagation();
1614                                cx.new(|_| drag.clone())
1615                            },
1616                        )
1617                    })
1618                    .map(|this| {
1619                        // Draw the insertion indicator on the left edge of the gap
1620                        // column, or on the right edge of the last column for the
1621                        // trailing gap. Use an absolutely positioned overlay instead
1622                        // of a border, to avoid shifting the cell content.
1623                        let last_gap = col_ix + 1 == self.col_groups.len();
1624                        match self.col_drag_gap {
1625                            Some(gap)
1626                                if cx.has_active_drag()
1627                                    && (gap == col_ix || (last_gap && gap == col_ix + 1)) =>
1628                            {
1629                                let right_side = gap == col_ix + 1;
1630                                this.relative().child(
1631                                    div()
1632                                        .absolute()
1633                                        .top_0()
1634                                        .bottom_0()
1635                                        .w(px(2.))
1636                                        .map(|d| if right_side { d.right_0() } else { d.left_0() })
1637                                        .bg(cx.theme().drag_border),
1638                                )
1639                            }
1640                            _ => this,
1641                        }
1642                    }),
1643            )
1644            // resize handle cell right side
1645            .child(self.render_resize_handle(col_ix, window, cx))
1646            // resize handle cell left side
1647            .child(self.render_leading_resize_handle(col_ix, window, cx))
1648            // to save the bounds of this col.
1649            .on_prepaint({
1650                let view = cx.entity().clone();
1651                move |bounds, _, cx| view.update(cx, |r, _| r.col_groups[col_ix].bounds = bounds)
1652            })
1653    }
1654
1655    /// Compute the visible non-fixed leaf-column range for header rendering.
1656    ///
1657    /// Returns `(visible_range, left_spacer_width)` where:
1658    /// - `visible_range` is the column-index range that should be rendered.
1659    /// - `left_spacer_width` is the total width of the off-screen left columns,
1660    ///   used as a spacer div to keep visible columns at the correct position.
1661    ///
1662    /// On the first frame `self.bounds` is zero, so a fallback that covers all
1663    /// columns is returned to avoid a blank header on initial paint.
1664    fn calculate_visible_leaf_col_range(
1665        &self,
1666        left_columns_count: usize,
1667    ) -> (Range<usize>, Pixels) {
1668        let total_cols = self.col_groups.len();
1669
1670        if self.bounds.size.width == px(0.) {
1671            return (left_columns_count..total_cols, px(0.));
1672        }
1673
1674        let fixed_width = self.fixed_head_cols_bounds.size.width;
1675        let available_width = (self.bounds.size.width - fixed_width).max(px(0.));
1676        // The scroll handle offset is negative when scrolled right; negate it
1677        // to obtain a positive distance from the left edge of the scroll area.
1678        let scroll_x = (-self.horizontal_scroll_handle.offset().x).max(px(0.));
1679
1680        // Walk left-to-right through non-fixed columns to find the first one
1681        // whose right edge enters the viewport. The accumulated width of the
1682        // skipped columns becomes the left spacer width.
1683        let mut range_start = left_columns_count;
1684        let mut left_spacer = px(0.);
1685        let mut cumulative = px(0.);
1686        for i in left_columns_count..total_cols {
1687            let right_edge = cumulative + self.col_groups[i].width;
1688            if right_edge > scroll_x {
1689                range_start = i;
1690                left_spacer = cumulative;
1691                break;
1692            }
1693            cumulative = right_edge;
1694        }
1695
1696        // Continue from `range_start` (skipping already-scanned columns) to
1697        // find the last column still within the viewport. The 200 px overdraw
1698        // buffer prevents a visible flash when the user scrolls quickly.
1699        let right_bound = scroll_x + available_width + px(200.);
1700        let mut range_end = total_cols;
1701        let mut cumulative = left_spacer; // already summed widths before `range_start`
1702        for i in range_start..total_cols {
1703            cumulative += self.col_groups[i].width;
1704            if cumulative > right_bound {
1705                range_end = (i + 1).min(total_cols);
1706                break;
1707            }
1708        }
1709
1710        (range_start..range_end, left_spacer)
1711    }
1712
1713    fn render_table_header(
1714        &mut self,
1715        left_columns_count: usize,
1716        window: &mut Window,
1717        cx: &mut Context<Self>,
1718    ) -> impl IntoElement {
1719        let view = cx.entity().clone();
1720        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
1721
1722        // Header leaf-column virtualization.
1723        //
1724        // `render_th` creates interactive elements with resize-handle listeners.
1725        // Calling it for every column every frame is O(n) in column count; with
1726        // 1000+ columns this alone drops FPS below 60 even in release mode.
1727        //
1728        // We restrict rendering to the columns currently visible inside the
1729        // overflow-scroll viewport, surrounding them with inert spacer divs:
1730        //
1731        //   [left_spacer] [visible columns…] [right_spacer] [last_empty_col]
1732        //
1733        // The spacers preserve the flex container's total content width so that
1734        // the scrollbar range stays correct.
1735        let total_cols = self.col_groups.len();
1736        let (visible_col_range, left_spacer) =
1737            self.calculate_visible_leaf_col_range(left_columns_count);
1738
1739        let layout_len = self.header_layout.len();
1740
1741        // Reset fixed head columns bounds, if no fixed columns are present
1742        if left_columns_count == 0 {
1743            self.fixed_head_cols_bounds = Bounds::default();
1744        }
1745
1746        let mut header = self.delegate_mut().render_header(window, cx);
1747        let style = header.style().clone();
1748        let layout = self.header_layout.clone();
1749
1750        header
1751            .h_flex()
1752            .w_full()
1753            .flex_shrink_0()
1754            .bg(cx.theme().tokens.table_head)
1755            .text_color(cx.theme().table_head_foreground)
1756            .refine_style(&style)
1757            .on_drag_move(cx.listener(|table, e: &DragMoveEvent<DragColumn>, _, cx| {
1758                let drag = e.drag(cx);
1759                let (drag_entity_id, drag_col_ix) = (drag.entity_id, drag.col_ix);
1760
1761                let gap =
1762                    if drag_entity_id == cx.entity_id() && e.bounds.contains(&e.event.position) {
1763                        table.drag_gap_at(e.event.position.x, drag_col_ix)
1764                    } else {
1765                        None
1766                    };
1767
1768                if table.col_drag_gap != gap {
1769                    table.col_drag_gap = gap;
1770                    cx.notify();
1771                }
1772            }))
1773            .on_drop(cx.listener(|table, drag: &DragColumn, window, cx| {
1774                if drag.entity_id != cx.entity_id() {
1775                    return;
1776                }
1777
1778                // Insert the dragged column into the indicated gap.
1779                let Some(gap) = table.col_drag_gap.take() else {
1780                    return;
1781                };
1782                let to_ix = if drag.col_ix < gap { gap - 1 } else { gap };
1783                table.move_column(drag.col_ix, to_ix, window, cx);
1784            }))
1785            .when(self.cell_selectable && self.row_header, |this| {
1786                this.child(self.render_row_header_cell(0, true, cx))
1787            })
1788            .when(left_columns_count > 0, |this| {
1789                let view = view.clone();
1790                // Render left fixed columns
1791                this.child(
1792                    h_flex()
1793                        .relative()
1794                        .h_full()
1795                        .bg(cx.theme().tokens.table_head)
1796                        .child(v_flex().min_w_full().flex_shrink_0().children(
1797                            layout.iter().enumerate().map(|(_row_ix, row_cells)| {
1798                                h_flex()
1799                                    .min_w_full()
1800                                    .h(self.options.size.table_row_height())
1801                                    .border_b_1()
1802                                    .border_color(cx.theme().border)
1803                                    .children(row_cells.iter().filter_map(|cell| {
1804                                        if cell.start_leaf_col_ix < left_columns_count {
1805                                            if cell.is_leaf {
1806                                                if let Some(ix) = cell.leaf_col_ix {
1807                                                    return Some(
1808                                                        self.render_th(ix, window, cx)
1809                                                            .into_any_element(),
1810                                                    );
1811                                                }
1812                                            } else {
1813                                                return Some(
1814                                                    self.delegate_mut()
1815                                                        .render_group_th(
1816                                                            &cell.label,
1817                                                            cell.col_span,
1818                                                            cell.width,
1819                                                            window,
1820                                                            cx,
1821                                                        )
1822                                                        .into_any_element(),
1823                                                );
1824                                            }
1825                                        }
1826                                        None
1827                                    }))
1828                            }),
1829                        ))
1830                        .child(
1831                            // Fixed columns border
1832                            div()
1833                                .absolute()
1834                                .top_0()
1835                                .right_0()
1836                                .bottom_0()
1837                                .w_0()
1838                                .flex_shrink_0()
1839                                .border_r_1()
1840                                .border_color(cx.theme().border),
1841                        )
1842                        .on_prepaint(move |bounds, _, cx| {
1843                            view.update(cx, |r, _| r.fixed_head_cols_bounds = bounds)
1844                        }),
1845                )
1846            })
1847            .child(
1848                // Columns
1849                h_flex()
1850                    .id("table-head")
1851                    .size_full()
1852                    .overflow_scroll()
1853                    .relative()
1854                    .track_scroll(&horizontal_scroll_handle)
1855                    .bg(cx.theme().tokens.table_head)
1856                    .child(v_flex().min_w_full().flex_shrink_0().children(
1857                        layout.iter().enumerate().map(|(row_ix, row_cells)| {
1858                            let is_leaf_row = row_ix + 1 == layout_len;
1859                            h_flex()
1860                                .min_w_full()
1861                                .h(self.options.size.table_row_height())
1862                                .border_b_1()
1863                                .border_color(cx.theme().border)
1864                                .map(|this| {
1865                                    if is_leaf_row {
1866                                        // Leaf row: apply the spacer virtualization pattern.
1867                                        // Only columns in `visible_range` are rendered; the two
1868                                        // spacer divs preserve the container's total content width
1869                                        // so the scrollbar range stays correct.
1870                                        this.when(left_spacer > px(0.), |r| {
1871                                            r.child(div().w(left_spacer).h_full().flex_shrink_0())
1872                                        })
1873                                        .children(row_cells.iter().filter_map(|cell| {
1874                                            if cell.is_leaf {
1875                                                let ix = cell.leaf_col_ix?;
1876                                                if !visible_col_range.contains(&ix) {
1877                                                    return None;
1878                                                }
1879                                                Some(
1880                                                    self.render_th(ix, window, cx)
1881                                                        .into_any_element(),
1882                                                )
1883                                            } else {
1884                                                None
1885                                            }
1886                                        }))
1887                                        .when(visible_col_range.end < total_cols, |r| {
1888                                            let right_spacer: Pixels = self.col_groups
1889                                                [visible_col_range.end..total_cols]
1890                                                .iter()
1891                                                .map(|g| g.width)
1892                                                .sum();
1893                                            r.child(div().w(right_spacer).h_full().flex_shrink_0())
1894                                        })
1895                                        .child(self.delegate.render_last_empty_col(window, cx))
1896                                    } else {
1897                                        // Group header rows have far fewer cells (one per group),
1898                                        // so the cost of rendering all of them is negligible.
1899                                        this.children(row_cells.iter().filter_map(|cell| {
1900                                            if cell.start_leaf_col_ix >= left_columns_count {
1901                                                if cell.is_leaf {
1902                                                    if let Some(ix) = cell.leaf_col_ix {
1903                                                        return Some(
1904                                                            self.render_th(ix, window, cx)
1905                                                                .into_any_element(),
1906                                                        );
1907                                                    }
1908                                                } else {
1909                                                    return Some(
1910                                                        self.delegate_mut()
1911                                                            .render_group_th(
1912                                                                &cell.label,
1913                                                                cell.col_span,
1914                                                                cell.width,
1915                                                                window,
1916                                                                cx,
1917                                                            )
1918                                                            .into_any_element(),
1919                                                    );
1920                                                }
1921                                            }
1922                                            None
1923                                        }))
1924                                        .child(self.delegate.render_last_empty_col(window, cx))
1925                                    }
1926                                })
1927                        }),
1928                    )),
1929            )
1930    }
1931
1932    #[allow(clippy::too_many_arguments)]
1933    fn render_table_row(
1934        &mut self,
1935        row_ix: usize,
1936        rows_count: usize,
1937        left_columns_count: usize,
1938        col_sizes: Rc<Vec<gpui::Size<Pixels>>>,
1939        columns_count: usize,
1940        is_filled: bool,
1941        window: &mut Window,
1942        cx: &mut Context<Self>,
1943    ) -> Stateful<Div> {
1944        let horizontal_scroll_handle = self.horizontal_scroll_handle.clone();
1945        let is_stripe_row = self.options.stripe && row_ix % 2 != 0;
1946        let is_selected = self.selected_row == Some(row_ix);
1947        let view = cx.entity().clone();
1948        let row_height = self.options.size.table_row_height();
1949
1950        if row_ix < rows_count {
1951            let is_last_row = row_ix + 1 == rows_count;
1952            let need_render_border = is_selected || !is_last_row || !is_filled;
1953
1954            let mut tr = self.delegate.render_tr(row_ix, window, cx);
1955            let style = tr.style().clone();
1956
1957            tr.h_flex()
1958                .w_full()
1959                .h(row_height)
1960                .when(need_render_border, |this| {
1961                    this.border_b_1().border_color(cx.theme().table_row_border)
1962                })
1963                .when(is_stripe_row, |this| this.bg(cx.theme().tokens.table_even))
1964                .refine_style(&style)
1965                .hover(|this| {
1966                    if is_selected || self.right_clicked_row == Some(row_ix) {
1967                        this
1968                    } else {
1969                        this.bg(cx.theme().tokens.table_hover)
1970                    }
1971                })
1972                .when(self.cell_selectable && self.row_header, |this| {
1973                    this.child(self.render_row_header_cell(row_ix, false, cx))
1974                })
1975                .when(left_columns_count > 0, |this| {
1976                    // Left fixed columns
1977                    this.child(
1978                        h_flex()
1979                            .relative()
1980                            .h_full()
1981                            .children({
1982                                let mut items = Vec::with_capacity(left_columns_count);
1983
1984                                (0..left_columns_count).for_each(|col_ix| {
1985                                    let is_cell_selected = self.selected_cell
1986                                        == Some((row_ix, col_ix))
1987                                        && self.selection_mode.is_cell();
1988                                    let is_cell_right_clicked =
1989                                        self.right_clicked_cell == Some((row_ix, col_ix));
1990
1991                                    items.push(
1992                                        self.render_col_wrap(Some(row_ix), col_ix, window, cx)
1993                                            .child(
1994                                                self.render_cell(Some(row_ix), col_ix, window, cx)
1995                                                    .id(format!("table-cell:{}:{}", row_ix, col_ix))
1996                                                    .relative()
1997                                                    .child(self.measure_render_td(
1998                                                        row_ix, col_ix, window, cx,
1999                                                    ))
2000                                                    .when(is_cell_selected, |this| {
2001                                                        this.child(
2002                                                            div()
2003                                                                .absolute()
2004                                                                .inset_0()
2005                                                                .bg(cx.theme().tokens.table_active)
2006                                                                .border_1()
2007                                                                .border_color(
2008                                                                    cx.theme().table_active_border,
2009                                                                ),
2010                                                        )
2011                                                    })
2012                                                    .when(
2013                                                        is_cell_right_clicked && !is_cell_selected,
2014                                                        |this| {
2015                                                            this.child(
2016                                                                div()
2017                                                                    .absolute()
2018                                                                    .inset_0()
2019                                                                    .border_1()
2020                                                                    .border_color(
2021                                                                        cx.theme()
2022                                                                            .table_active_border
2023                                                                            .opacity(0.5),
2024                                                                    ),
2025                                                            )
2026                                                        },
2027                                                    )
2028                                                    .when(self.cell_selectable, |this| {
2029                                                        this.on_click(cx.listener(
2030                                                            move |table, e, window, cx| {
2031                                                                table.on_cell_click(
2032                                                                    e, row_ix, col_ix, window, cx,
2033                                                                );
2034                                                            },
2035                                                        ))
2036                                                        .on_mouse_down(
2037                                                            MouseButton::Right,
2038                                                            cx.listener(
2039                                                                move |table, e, window, cx| {
2040                                                                    table.on_cell_right_click(
2041                                                                        e, row_ix, col_ix, window,
2042                                                                        cx,
2043                                                                    );
2044                                                                },
2045                                                            ),
2046                                                        )
2047                                                    }),
2048                                            ),
2049                                    );
2050                                });
2051
2052                                items
2053                            })
2054                            .child(
2055                                // Fixed columns border
2056                                div()
2057                                    .absolute()
2058                                    .top_0()
2059                                    .right_0()
2060                                    .bottom_0()
2061                                    .w_0()
2062                                    .flex_shrink_0()
2063                                    .border_r_1()
2064                                    .border_color(cx.theme().border),
2065                            ),
2066                    )
2067                })
2068                .child(
2069                    h_flex()
2070                        .flex_1()
2071                        .h_full()
2072                        .overflow_hidden()
2073                        .relative()
2074                        .child(
2075                            crate::virtual_list::virtual_list(
2076                                view,
2077                                row_ix,
2078                                Axis::Horizontal,
2079                                col_sizes,
2080                                {
2081                                    move |table, visible_range: Range<usize>, window, cx| {
2082                                        table.update_visible_range_if_need(
2083                                            visible_range.clone(),
2084                                            Axis::Horizontal,
2085                                            window,
2086                                            cx,
2087                                        );
2088
2089                                        let mut items = Vec::with_capacity(
2090                                            visible_range.end - visible_range.start,
2091                                        );
2092
2093                                        visible_range.for_each(|col_ix| {
2094                                            let col_ix = col_ix + left_columns_count;
2095                                            let is_cell_selected = table.selected_cell
2096                                                == Some((row_ix, col_ix))
2097                                                && table.selection_mode.is_cell();
2098                                            let is_cell_right_clicked =
2099                                                table.right_clicked_cell == Some((row_ix, col_ix));
2100
2101                                            let el = table
2102                                                .render_col_wrap(Some(row_ix), col_ix, window, cx)
2103                                                .child(
2104                                                    table
2105                                                        .render_cell(
2106                                                            Some(row_ix),
2107                                                            col_ix,
2108                                                            window,
2109                                                            cx,
2110                                                        )
2111                                                        .id(format!(
2112                                                            "table-cell-{}:{}",
2113                                                            row_ix, col_ix
2114                                                        ))
2115                                                        .relative()
2116                                                        .child(table.measure_render_td(
2117                                                            row_ix, col_ix, window, cx,
2118                                                        ))
2119                                                        .when(is_cell_selected, |this| {
2120                                                            this.child(
2121                                                                div()
2122                                                                    .absolute()
2123                                                                    .inset_0()
2124                                                                    .bg(cx
2125                                                                        .theme()
2126                                                                        .tokens
2127                                                                        .table_active)
2128                                                                    .border_1()
2129                                                                    .border_color(
2130                                                                        cx.theme()
2131                                                                            .table_active_border,
2132                                                                    ),
2133                                                            )
2134                                                        })
2135                                                        .when(
2136                                                            is_cell_right_clicked
2137                                                                && !is_cell_selected,
2138                                                            |this| {
2139                                                                this.child(
2140                                                                    div()
2141                                                                        .absolute()
2142                                                                        .inset_0()
2143                                                                        .border_1()
2144                                                                        .border_color(
2145                                                                            cx.theme()
2146                                                                                .table_active_border
2147                                                                                .opacity(0.5),
2148                                                                        ),
2149                                                                )
2150                                                            },
2151                                                        )
2152                                                        .when(table.cell_selectable, |this| {
2153                                                            this.on_click(cx.listener(
2154                                                                move |table, e, window, cx| {
2155                                                                    cx.stop_propagation();
2156                                                                    table.on_cell_click(
2157                                                                        e, row_ix, col_ix, window,
2158                                                                        cx,
2159                                                                    );
2160                                                                },
2161                                                            ))
2162                                                            .on_mouse_down(
2163                                                                MouseButton::Right,
2164                                                                cx.listener(
2165                                                                    move |table, e, window, cx| {
2166                                                                        table.on_cell_right_click(
2167                                                                            e, row_ix, col_ix,
2168                                                                            window, cx,
2169                                                                        );
2170                                                                    },
2171                                                                ),
2172                                                            )
2173                                                        }),
2174                                                );
2175
2176                                            items.push(el);
2177                                        });
2178
2179                                        items
2180                                    }
2181                                },
2182                            )
2183                            .with_scroll_handle(&self.horizontal_scroll_handle),
2184                        )
2185                        .child(self.delegate.render_last_empty_col(window, cx)),
2186                )
2187                // Row selected style
2188                // Note: Don't show row selection if a cell is selected
2189                .when_some(self.selected_row, |this, _| {
2190                    this.when(is_selected && self.selection_mode.is_row(), |this| {
2191                        this.map(|this| {
2192                            if cx.theme().list.active_highlight {
2193                                this.border_color(gpui::transparent_white()).child(
2194                                    div()
2195                                        .top(if row_ix == 0 { px(0.) } else { px(-1.) })
2196                                        .left(px(0.))
2197                                        .right(px(0.))
2198                                        .bottom(px(-1.))
2199                                        .absolute()
2200                                        .bg(cx.theme().tokens.table_active)
2201                                        .border_1()
2202                                        .border_color(cx.theme().table_active_border),
2203                                )
2204                            } else {
2205                                this.bg(cx.theme().tokens.accent)
2206                            }
2207                        })
2208                    })
2209                })
2210                // Row right click row style
2211                .when(self.right_clicked_row == Some(row_ix), |this| {
2212                    this.border_color(gpui::transparent_white()).child(
2213                        div()
2214                            .top(if row_ix == 0 { px(0.) } else { px(-1.) })
2215                            .left(px(0.))
2216                            .right(px(0.))
2217                            .bottom(px(-1.))
2218                            .absolute()
2219                            .border_1()
2220                            .border_color(cx.theme().selection),
2221                    )
2222                })
2223                .on_mouse_down(
2224                    MouseButton::Right,
2225                    cx.listener(move |this, e, window, cx| {
2226                        this.on_row_right_click(e, Some(row_ix), window, cx);
2227                    }),
2228                )
2229                .on_click(cx.listener(move |this, e, window, cx| {
2230                    this.on_row_left_click(e, row_ix, window, cx);
2231                }))
2232        } else {
2233            // Render fake rows to fill the rest table space
2234            self.delegate
2235                .render_tr(row_ix, window, cx)
2236                .h_flex()
2237                .w_full()
2238                .h(row_height)
2239                .border_b_1()
2240                .border_color(cx.theme().table_row_border)
2241                .when(is_stripe_row, |this| this.bg(cx.theme().tokens.table_even))
2242                .when(self.cell_selectable && self.row_header, |this| {
2243                    // Render empty row header cell for fake rows
2244                    this.child(
2245                        div()
2246                            .w(px(40.))
2247                            .h_full()
2248                            .flex_shrink_0()
2249                            .table_cell_size(self.options.size),
2250                    )
2251                })
2252                .children((0..columns_count).map(|col_ix| {
2253                    h_flex()
2254                        .left(horizontal_scroll_handle.offset().x)
2255                        .child(self.render_cell(None, col_ix, window, cx))
2256                }))
2257                .child(self.delegate.render_last_empty_col(window, cx))
2258        }
2259    }
2260
2261    /// Calculate the extra rows needed to fill the table empty space when `stripe` is true.
2262    fn calculate_extra_rows_needed(
2263        &self,
2264        total_height: Pixels,
2265        actual_height: Pixels,
2266        row_height: Pixels,
2267    ) -> usize {
2268        let mut extra_rows_needed = 0;
2269
2270        let remaining_height = total_height - actual_height;
2271        if remaining_height > px(0.) {
2272            extra_rows_needed = (remaining_height / row_height).floor() as usize;
2273        }
2274
2275        extra_rows_needed
2276    }
2277
2278    #[inline]
2279    fn measure_render_td(
2280        &mut self,
2281        row_ix: usize,
2282        col_ix: usize,
2283        window: &mut Window,
2284        cx: &mut Context<Self>,
2285    ) -> impl IntoElement {
2286        if !crate::measure_enable() {
2287            return self
2288                .delegate
2289                .render_td(row_ix, col_ix, window, cx)
2290                .into_any_element();
2291        }
2292
2293        let start = std::time::Instant::now();
2294        let el = self.delegate.render_td(row_ix, col_ix, window, cx);
2295        self._measure.push(start.elapsed());
2296        el.into_any_element()
2297    }
2298
2299    fn measure(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
2300        if !crate::measure_enable() {
2301            return;
2302        }
2303
2304        // Print avg measure time of each td
2305        if self._measure.len() > 0 {
2306            let total = self
2307                ._measure
2308                .iter()
2309                .fold(Duration::default(), |acc, d| acc + *d);
2310            let avg = total / self._measure.len() as u32;
2311            eprintln!(
2312                "last render {} cells total: {:?}, avg: {:?}",
2313                self._measure.len(),
2314                total,
2315                avg,
2316            );
2317        }
2318        self._measure.clear();
2319    }
2320
2321    fn render_vertical_scrollbar(
2322        &mut self,
2323        _: &mut Window,
2324        _: &mut Context<Self>,
2325    ) -> Option<impl IntoElement> {
2326        Some(
2327            div()
2328                .absolute()
2329                .top(self.options.size.table_row_height() * self.header_layout.len().max(1) as f32)
2330                .right_0()
2331                .bottom_0()
2332                .w(Scrollbar::width())
2333                .child(
2334                    Scrollbar::vertical(&self.vertical_scroll_handle)
2335                        .viewport_from_layout()
2336                        .max_fps(60),
2337                ),
2338        )
2339    }
2340
2341    fn render_horizontal_scrollbar(
2342        &mut self,
2343        _: &mut Window,
2344        _: &mut Context<Self>,
2345    ) -> impl IntoElement {
2346        div()
2347            .absolute()
2348            .left(self.fixed_head_cols_bounds.size.width)
2349            .right_0()
2350            .bottom_0()
2351            .h(Scrollbar::width())
2352            .child(Scrollbar::horizontal(&self.horizontal_scroll_handle).viewport_from_layout())
2353    }
2354}
2355
2356impl<D> Focusable for TableState<D>
2357where
2358    D: TableDelegate,
2359{
2360    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
2361        self.focus_handle.clone()
2362    }
2363}
2364impl<D> EventEmitter<TableEvent> for TableState<D> where D: TableDelegate {}
2365
2366impl<D> Render for TableState<D>
2367where
2368    D: TableDelegate,
2369{
2370    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2371        self.measure(window, cx);
2372
2373        let columns_count = self.delegate.columns_count(cx);
2374        let left_columns_count = self
2375            .col_groups
2376            .iter()
2377            .filter(|col| self.col_fixed && col.column.fixed == Some(ColumnFixed::Left))
2378            .count();
2379        let rows_count = self.delegate.rows_count(cx);
2380        let loading = self.delegate.loading(cx);
2381
2382        let row_height = self.options.size.table_row_height();
2383        let total_height = self
2384            .vertical_scroll_handle
2385            .0
2386            .borrow()
2387            .base_handle
2388            .bounds()
2389            .size
2390            .height;
2391        let actual_height = row_height * rows_count as f32;
2392        let extra_rows_count =
2393            self.calculate_extra_rows_needed(total_height, actual_height, row_height);
2394        let render_rows_count = if self.options.stripe {
2395            rows_count + extra_rows_count
2396        } else {
2397            rows_count
2398        };
2399        let right_clicked_row = self.right_clicked_row;
2400        let is_filled = total_height > Pixels::ZERO && total_height <= actual_height;
2401
2402        let loading_view = if loading {
2403            Some(
2404                self.delegate
2405                    .render_loading(self.options.size, window, cx)
2406                    .into_any_element(),
2407            )
2408        } else {
2409            None
2410        };
2411
2412        let empty_view = if rows_count == 0 {
2413            Some(
2414                div()
2415                    .size_full()
2416                    .child(self.delegate.render_empty(window, cx))
2417                    .into_any_element(),
2418            )
2419        } else {
2420            None
2421        };
2422
2423        let inner_table = v_flex()
2424            .id("table-inner")
2425            .size_full()
2426            .overflow_hidden()
2427            .child(self.render_table_header(left_columns_count, window, cx))
2428            .context_menu({
2429                let view = cx.entity().clone();
2430                move |this, window: &mut Window, cx: &mut Context<PopupMenu>| {
2431                    if let Some(row_ix) = view.read(cx).right_clicked_row {
2432                        view.update(cx, |menu, cx| {
2433                            menu.delegate_mut().context_menu(row_ix, this, window, cx)
2434                        })
2435                    } else {
2436                        this
2437                    }
2438                }
2439            })
2440            .map(|this| {
2441                if rows_count == 0 {
2442                    this.children(empty_view)
2443                } else {
2444                    this.child(
2445                        h_flex().id("table-body").flex_grow_1().size_full().child(
2446                            uniform_list(
2447                                "table-uniform-list",
2448                                render_rows_count,
2449                                cx.processor(
2450                                    move |table, visible_range: Range<usize>, window, cx| {
2451                                        // Use `col.width` (always up-to-date) rather than
2452                                        // `col.bounds.size.width`, which is only set after
2453                                        // prepaint and is therefore zero on the first frame.
2454                                        let col_sizes: Rc<Vec<gpui::Size<Pixels>>> = Rc::new(
2455                                            table
2456                                                .col_groups
2457                                                .iter()
2458                                                .skip(left_columns_count)
2459                                                .map(|col| gpui::Size {
2460                                                    width: col.width,
2461                                                    height: px(0.),
2462                                                })
2463                                                .collect(),
2464                                        );
2465
2466                                        table.load_more_if_need(
2467                                            rows_count,
2468                                            visible_range.end,
2469                                            window,
2470                                            cx,
2471                                        );
2472                                        table.update_visible_range_if_need(
2473                                            visible_range.clone(),
2474                                            Axis::Vertical,
2475                                            window,
2476                                            cx,
2477                                        );
2478
2479                                        if visible_range.end > rows_count {
2480                                            table.scroll_to_row(
2481                                                std::cmp::min(
2482                                                    visible_range.start,
2483                                                    rows_count.saturating_sub(1),
2484                                                ),
2485                                                cx,
2486                                            );
2487                                        }
2488
2489                                        let mut items = Vec::with_capacity(
2490                                            visible_range.end.saturating_sub(visible_range.start),
2491                                        );
2492
2493                                        // Render fake rows to fill the table
2494                                        visible_range.for_each(|row_ix| {
2495                                            // Render real rows for available data
2496                                            items.push(table.render_table_row(
2497                                                row_ix,
2498                                                rows_count,
2499                                                left_columns_count,
2500                                                col_sizes.clone(),
2501                                                columns_count,
2502                                                is_filled,
2503                                                window,
2504                                                cx,
2505                                            ));
2506                                        });
2507
2508                                        items
2509                                    },
2510                                ),
2511                            )
2512                            .flex_grow_1()
2513                            .size_full()
2514                            .with_sizing_behavior(ListSizingBehavior::Auto)
2515                            .track_scroll(&self.vertical_scroll_handle)
2516                            .into_any_element(),
2517                        ),
2518                    )
2519                }
2520            });
2521
2522        div()
2523            .size_full()
2524            .children(loading_view)
2525            .when(!loading, |this| {
2526                this.child(inner_table)
2527                    .child(ScrollableMask::new(
2528                        Axis::Horizontal,
2529                        &self.horizontal_scroll_handle,
2530                    ))
2531                    // Keep vertical wheel scrolling from leaking into an
2532                    // ancestor scroller. Skipped when the table is empty:
2533                    // the `uniform_list` is not rendered then, so the
2534                    // handle's offset and `max_offset` are stale.
2535                    .when(rows_count > 0, |this| {
2536                        this.child(ScrollableMask::new(
2537                            Axis::Vertical,
2538                            &self.vertical_scroll_handle.0.borrow().base_handle,
2539                        ))
2540                    })
2541                    .when(right_clicked_row.is_some(), |this| {
2542                        this.on_mouse_down_out(cx.listener(|this, e, window, cx| {
2543                            this.on_row_right_click(e, None, window, cx);
2544                            cx.notify();
2545                        }))
2546                    })
2547            })
2548            .on_prepaint({
2549                let state = cx.entity();
2550                move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds)
2551            })
2552            .when(!window.is_inspector_picking(cx), |this| {
2553                this.child(
2554                    div()
2555                        .absolute()
2556                        .top_0()
2557                        .size_full()
2558                        .when(self.options.scrollbar_visible.bottom, |this| {
2559                            this.child(self.render_horizontal_scrollbar(window, cx))
2560                        })
2561                        .when(
2562                            self.options.scrollbar_visible.right && rows_count > 0,
2563                            |this| this.children(self.render_vertical_scrollbar(window, cx)),
2564                        ),
2565                )
2566            })
2567    }
2568}