Skip to main content

gpui_kit/data/
tree_grid.rs

1//! A virtualized grid over a caller-flattened hierarchy.
2//!
3//! `TreeGrid` does not discover topology, flatten nodes, or own expansion and
4//! selection. The caller supplies visible rows in reading order and receives
5//! selection and expansion intents. Only materialized viewport rows publish
6//! the ARIA hierarchy `TreeGrid > Row > GridCell`.
7//!
8//! Columns are fixed or flexible within the available width. There is no
9//! horizontal scrolling or frozen-column behavior.
10
11use std::rc::Rc;
12
13use gpui::{App, IntoElement, RenderOnce, SharedString, Window};
14
15use crate::data::grid::{DataGrid, GridRow, HierarchyRow};
16use crate::data::{Cell, GridColumn, GridLines, SelectionChange, SelectionMode};
17use crate::display::empty::{EmptyKind, EmptyState};
18use crate::foundation::{Disableable, Ident};
19
20type RenderRow = Rc<dyn Fn(usize, &mut Window, &mut App) -> TreeGridRow>;
21type SelectHandler = Rc<dyn Fn(SharedString, &mut Window, &mut App)>;
22type ExpandHandler = Rc<dyn Fn(SharedString, bool, &mut Window, &mut App)>;
23
24/// One already-visible hierarchy row, identified by stable business identity.
25#[derive(Debug)]
26pub struct TreeGridRow {
27    row: GridRow,
28    level: u32,
29    has_children: bool,
30    expanded: bool,
31    parent: Option<SharedString>,
32}
33
34impl TreeGridRow {
35    pub fn new(id: impl Into<SharedString>, level: u32) -> Self {
36        Self {
37            row: GridRow::new(id),
38            level: level.max(1),
39            has_children: false,
40            expanded: false,
41            parent: None,
42        }
43    }
44
45    pub fn text(mut self, text: impl Into<SharedString>) -> Self {
46        self.row = self.row.text(text);
47        self
48    }
49
50    pub fn cell(mut self, key: impl Into<SharedString>, cell: impl Into<Cell>) -> Self {
51        self.row = self.row.cell(key, cell);
52        self
53    }
54
55    pub fn branch(mut self, expanded: bool) -> Self {
56        self.has_children = true;
57        self.expanded = expanded;
58        self
59    }
60
61    /// The caller-supplied visible parent used by logical-start navigation.
62    pub fn parent(mut self, id: impl Into<SharedString>) -> Self {
63        self.parent = Some(id.into());
64        self
65    }
66
67    pub fn disabled(mut self, disabled: bool) -> Self {
68        self.row = self.row.disabled(disabled);
69        self
70    }
71
72    fn into_grid_row(self) -> GridRow {
73        self.row.hierarchy(HierarchyRow {
74            level: self.level,
75            has_children: self.has_children,
76            expanded: self.expanded,
77            parent: self.parent,
78        })
79    }
80}
81
82/// A product-neutral, caller-owned virtualized treegrid adapter.
83#[derive(IntoElement)]
84pub struct TreeGrid {
85    ident: Ident,
86    count: usize,
87    render_row: RenderRow,
88    columns: Vec<GridColumn>,
89    selected: Option<SharedString>,
90    visible_rows: Option<usize>,
91    row_height: Option<f32>,
92    lines: GridLines,
93    loading: bool,
94    failure: Option<SharedString>,
95    vacancy: Option<EmptyState>,
96    disabled: bool,
97    on_select: Option<SelectHandler>,
98    on_expand: Option<ExpandHandler>,
99}
100
101impl std::fmt::Debug for TreeGrid {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.debug_struct("TreeGrid")
104            .field("ident", &self.ident)
105            .field("count", &self.count)
106            .field("columns", &self.columns.len())
107            .field("disabled", &self.disabled)
108            .finish()
109    }
110}
111
112impl TreeGrid {
113    pub fn new(
114        ident: impl Into<Ident>,
115        visible_row_count: usize,
116        render_row: impl Fn(usize, &mut Window, &mut App) -> TreeGridRow + 'static,
117    ) -> Self {
118        Self {
119            ident: ident.into(),
120            count: visible_row_count,
121            render_row: Rc::new(render_row),
122            columns: vec![],
123            selected: None,
124            visible_rows: None,
125            row_height: None,
126            lines: GridLines::None,
127            loading: false,
128            failure: None,
129            vacancy: None,
130            disabled: false,
131            on_select: None,
132            on_expand: None,
133        }
134    }
135
136    pub fn column(mut self, column: GridColumn) -> Self {
137        self.columns.push(column);
138        self
139    }
140    pub fn columns(mut self, columns: impl IntoIterator<Item = GridColumn>) -> Self {
141        self.columns.extend(columns);
142        self
143    }
144    pub fn selected(mut self, id: impl Into<SharedString>) -> Self {
145        self.selected = Some(id.into());
146        self
147    }
148    pub fn visible_rows(mut self, rows: usize) -> Self {
149        self.visible_rows = Some(rows);
150        self
151    }
152    pub fn row_height(mut self, height: f32) -> Self {
153        self.row_height = Some(height);
154        self
155    }
156    pub fn lines(mut self, lines: GridLines) -> Self {
157        self.lines = lines;
158        self
159    }
160    pub fn loading(mut self, loading: bool) -> Self {
161        self.loading = loading;
162        self
163    }
164    pub fn failure(mut self, detail: impl Into<SharedString>) -> Self {
165        self.failure = Some(detail.into());
166        self
167    }
168    pub fn empty(mut self, state: EmptyState) -> Self {
169        self.vacancy = Some(state);
170        self
171    }
172    pub fn unavailable(mut self, title: impl Into<SharedString>) -> Self {
173        self.vacancy = Some(
174            EmptyState::new(self.ident.child("unavailable"), title).kind(EmptyKind::Unavailable),
175        );
176        self
177    }
178    pub fn on_select(
179        mut self,
180        handler: impl Fn(SharedString, &mut Window, &mut App) + 'static,
181    ) -> Self {
182        self.on_select = Some(Rc::new(handler));
183        self
184    }
185    pub fn on_expand(
186        mut self,
187        handler: impl Fn(SharedString, bool, &mut Window, &mut App) + 'static,
188    ) -> Self {
189        self.on_expand = Some(Rc::new(handler));
190        self
191    }
192}
193
194impl Disableable for TreeGrid {
195    fn disabled(mut self, disabled: bool) -> Self {
196        self.disabled = disabled;
197        self
198    }
199}
200
201impl RenderOnce for TreeGrid {
202    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
203        let render = self.render_row;
204        let mut grid = DataGrid::new(self.ident, self.count, move |index, window, cx| {
205            render(index, window, cx).into_grid_row()
206        })
207        .columns(self.columns)
208        .lines(self.lines)
209        .selection_mode(SelectionMode::Single)
210        .selected(self.selected)
211        .loading(self.loading)
212        .hierarchy_mode()
213        .disabled(self.disabled);
214        if let Some(rows) = self.visible_rows {
215            grid = grid.visible_rows(rows);
216        }
217        if let Some(height) = self.row_height {
218            grid = grid.row_height(height);
219        }
220        if let Some(failure) = self.failure {
221            grid = grid.failure(failure);
222        }
223        if let Some(empty) = self.vacancy {
224            grid = grid.empty(empty);
225        }
226        if let Some(handler) = self.on_select {
227            grid = grid.on_select(move |change, window, cx| {
228                if let SelectionChange::Replace(id) = change {
229                    handler(id.clone(), window, cx);
230                }
231            });
232        }
233        if let Some(handler) = self.on_expand {
234            grid = grid.on_expand(move |id, open, window, cx| handler(id, open, window, cx));
235        }
236        grid
237    }
238}