Skip to main content

gpui_component/table/
data_table.rs

1use crate::{
2    ActiveTheme, Sizable, Size,
3    actions::{
4        Cancel, SelectDown, SelectFirst, SelectLast, SelectNextColumn, SelectPageDown,
5        SelectPageUp, SelectPrevColumn, SelectUp,
6    },
7    table::{TableDelegate, TableState},
8};
9use gpui::{
10    App, Edges, Entity, Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement,
11    RenderOnce, Styled, Window, div, prelude::FluentBuilder,
12};
13
14const CONTEXT: &'static str = "DataTable";
15pub(super) fn init(cx: &mut App) {
16    cx.bind_keys([
17        KeyBinding::new("escape", Cancel, Some(CONTEXT)),
18        KeyBinding::new("up", SelectUp, Some(CONTEXT)),
19        KeyBinding::new("down", SelectDown, Some(CONTEXT)),
20        KeyBinding::new("left", SelectPrevColumn, Some(CONTEXT)),
21        KeyBinding::new("right", SelectNextColumn, Some(CONTEXT)),
22        KeyBinding::new("home", SelectFirst, Some(CONTEXT)),
23        KeyBinding::new("end", SelectLast, Some(CONTEXT)),
24        KeyBinding::new("pageup", SelectPageUp, Some(CONTEXT)),
25        KeyBinding::new("pagedown", SelectPageDown, Some(CONTEXT)),
26        KeyBinding::new("tab", SelectNextColumn, Some(CONTEXT)),
27        KeyBinding::new("shift-tab", SelectPrevColumn, Some(CONTEXT)),
28    ]);
29}
30
31pub(super) struct TableOptions {
32    pub(super) scrollbar_visible: Edges<bool>,
33    /// Set stripe style of the table.
34    pub(super) stripe: bool,
35    /// Set to use border style of the table.
36    pub(super) bordered: bool,
37    /// The cell size of the table.
38    pub(super) size: Size,
39}
40
41impl Default for TableOptions {
42    fn default() -> Self {
43        Self {
44            scrollbar_visible: Edges::all(true),
45            stripe: false,
46            bordered: true,
47            size: Size::default(),
48        }
49    }
50}
51
52/// A table element with support for row, column, and cell selection.
53///
54/// # Features
55///
56/// - **Multiple Selection Modes**: Support for row, column, and cell selection
57/// - **Cell Selection**: Click to select individual cells, with keyboard navigation
58/// - **Virtual Scrolling**: Efficient rendering of large datasets
59/// - **Resizable Columns**: Drag column borders to resize
60/// - **Movable Columns**: Drag column headers to reorder
61/// - **Fixed Columns**: Pin columns to the left side
62/// - **Sortable Columns**: Click column headers to sort
63/// - **Context Menus**: Right-click support for rows and cells
64///
65/// # Cell Selection Mode
66///
67/// When cell selection is enabled via [`TableState::cell_selectable()`]:
68/// - Click on cells to select them
69/// - A row header column appears on the left for selecting entire rows
70///   (use [`TableState::row_header()`] to hide it)
71/// - Keyboard navigation (arrow keys, Tab, Home, End, PageUp, PageDown) works at cell level
72/// - Right-click and double-click events are supported
73///
74/// See [`TableState`] for more details on cell selection.
75///
76/// # Example
77///
78/// ```rust,ignore
79/// let table_state = cx.new(|cx| {
80///     TableState::new(delegate, cx)
81///         .cell_selectable(true)
82///         .row_selectable(true)
83/// });
84///
85/// DataTable::new(&table_state)
86///     .stripe(true)
87///     .bordered(true)
88/// ```
89#[derive(IntoElement)]
90pub struct DataTable<D: TableDelegate> {
91    state: Entity<TableState<D>>,
92    options: TableOptions,
93}
94
95impl<D> DataTable<D>
96where
97    D: TableDelegate,
98{
99    /// Create a new DataTable element with the given [`TableState`].
100    pub fn new(state: &Entity<TableState<D>>) -> Self {
101        Self {
102            state: state.clone(),
103            options: TableOptions::default(),
104        }
105    }
106
107    /// Set to use stripe style of the table, default to false.
108    pub fn stripe(mut self, stripe: bool) -> Self {
109        self.options.stripe = stripe;
110        self
111    }
112
113    /// Set to use border style of the table, default to true.
114    pub fn bordered(mut self, bordered: bool) -> Self {
115        self.options.bordered = bordered;
116        self
117    }
118
119    /// Set scrollbar visibility.
120    pub fn scrollbar_visible(mut self, vertical: bool, horizontal: bool) -> Self {
121        self.options.scrollbar_visible = Edges {
122            right: vertical,
123            bottom: horizontal,
124            ..Default::default()
125        };
126        self
127    }
128}
129
130impl<D> Sizable for DataTable<D>
131where
132    D: TableDelegate,
133{
134    fn with_size(mut self, size: impl Into<Size>) -> Self {
135        self.options.size = size.into();
136        self
137    }
138}
139
140impl<D> RenderOnce for DataTable<D>
141where
142    D: TableDelegate,
143{
144    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
145        let bordered = self.options.bordered;
146        let focus_handle = self.state.focus_handle(cx);
147        self.state.update(cx, |state, _| {
148            state.options = self.options;
149        });
150
151        div()
152            .id("table")
153            .size_full()
154            .key_context(CONTEXT)
155            .track_focus(&focus_handle)
156            .on_action(window.listener_for(&self.state, TableState::action_cancel))
157            .on_action(window.listener_for(&self.state, TableState::action_select_next))
158            .on_action(window.listener_for(&self.state, TableState::action_select_prev))
159            .on_action(window.listener_for(&self.state, TableState::action_select_next_col))
160            .on_action(window.listener_for(&self.state, TableState::action_select_prev_col))
161            .on_action(window.listener_for(&self.state, TableState::action_select_first_column))
162            .on_action(window.listener_for(&self.state, TableState::action_select_last_column))
163            .on_action(window.listener_for(&self.state, TableState::action_select_page_up))
164            .on_action(window.listener_for(&self.state, TableState::action_select_page_down))
165            .bg(cx.theme().tokens.table)
166            .when(bordered, |this| {
167                this.rounded(cx.theme().radius)
168                    .border_1()
169                    .border_color(cx.theme().border)
170            })
171            .child(self.state)
172    }
173}