tree_view_item.rs

  1use std::sync::Arc;
  2
  3use gpui::{AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Role};
  4
  5use crate::{Disclosure, prelude::*};
  6
  7#[derive(IntoElement, RegisterComponent)]
  8pub struct TreeViewItem {
  9    id: ElementId,
 10    group_name: Option<SharedString>,
 11    label: SharedString,
 12    expanded: bool,
 13    selected: bool,
 14    disabled: bool,
 15    focused: bool,
 16    default_expanded: bool,
 17    root_item: bool,
 18    level: Option<usize>,
 19    position_in_set: Option<usize>,
 20    size_of_set: Option<usize>,
 21    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
 22    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 23    on_hover: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
 24    on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 25    on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut Window, &mut App) + 'static>>,
 26    tab_index: Option<isize>,
 27    focus_handle: Option<gpui::FocusHandle>,
 28}
 29
 30impl TreeViewItem {
 31    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
 32        Self {
 33            id: id.into(),
 34            group_name: None,
 35            label: label.into(),
 36            expanded: false,
 37            selected: false,
 38            disabled: false,
 39            focused: false,
 40            default_expanded: false,
 41            root_item: false,
 42            level: None,
 43            position_in_set: None,
 44            size_of_set: None,
 45            tooltip: None,
 46            on_click: None,
 47            on_hover: None,
 48            on_toggle: None,
 49            on_secondary_mouse_down: None,
 50            tab_index: None,
 51            focus_handle: None,
 52        }
 53    }
 54
 55    pub fn group_name(mut self, group_name: impl Into<SharedString>) -> Self {
 56        self.group_name = Some(group_name.into());
 57        self
 58    }
 59
 60    pub fn on_click(
 61        mut self,
 62        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 63    ) -> Self {
 64        self.on_click = Some(Box::new(handler));
 65        self
 66    }
 67
 68    pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
 69        self.on_hover = Some(Box::new(handler));
 70        self
 71    }
 72
 73    pub fn on_secondary_mouse_down(
 74        mut self,
 75        handler: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
 76    ) -> Self {
 77        self.on_secondary_mouse_down = Some(Box::new(handler));
 78        self
 79    }
 80
 81    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
 82        self.tooltip = Some(Box::new(tooltip));
 83        self
 84    }
 85
 86    pub fn tab_index(mut self, tab_index: isize) -> Self {
 87        self.tab_index = Some(tab_index);
 88        self
 89    }
 90
 91    pub fn expanded(mut self, toggle: bool) -> Self {
 92        self.expanded = toggle;
 93        self
 94    }
 95
 96    pub fn default_expanded(mut self, default_expanded: bool) -> Self {
 97        self.default_expanded = default_expanded;
 98        self
 99    }
100
101    pub fn on_toggle(
102        mut self,
103        on_toggle: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
104    ) -> Self {
105        self.on_toggle = Some(Arc::new(on_toggle));
106        self
107    }
108
109    pub fn root_item(mut self, root_item: bool) -> Self {
110        self.root_item = root_item;
111        self
112    }
113
114    pub fn focused(mut self, focused: bool) -> Self {
115        self.focused = focused;
116        self
117    }
118
119    pub fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
120        self.focus_handle = Some(focus_handle.clone());
121        self
122    }
123
124    pub fn level(mut self, level: usize) -> Self {
125        self.level = Some(level);
126        self
127    }
128
129    pub fn position_in_set(mut self, position: usize) -> Self {
130        self.position_in_set = Some(position);
131        self
132    }
133
134    pub fn size_of_set(mut self, size: usize) -> Self {
135        self.size_of_set = Some(size);
136        self
137    }
138}
139
140impl Disableable for TreeViewItem {
141    fn disabled(mut self, disabled: bool) -> Self {
142        self.disabled = disabled;
143        self
144    }
145}
146
147impl Toggleable for TreeViewItem {
148    fn toggle_state(mut self, selected: bool) -> Self {
149        self.selected = selected;
150        self
151    }
152}
153
154impl RenderOnce for TreeViewItem {
155    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
156        let selected_bg = cx.theme().colors().element_active.opacity(0.5);
157
158        let transparent_border = cx.theme().colors().border.opacity(0.);
159        let selected_border = cx.theme().colors().border.opacity(0.4);
160        let focused_border = cx.theme().colors().border_focused;
161
162        let item_size = rems_from_px(28.);
163        let indentation_line = h_flex().size(item_size).flex_none().justify_center().child(
164            div()
165                .w_px()
166                .h_full()
167                .bg(cx.theme().colors().border.opacity(0.5)),
168        );
169
170        h_flex()
171            .id(self.id)
172            .role(Role::TreeItem)
173            .aria_label(self.label.clone())
174            .aria_expanded(self.expanded)
175            .aria_selected(self.selected)
176            .when_some(self.level, |this, level| this.aria_level(level))
177            .when_some(self.position_in_set, |this, pos| {
178                this.aria_position_in_set(pos)
179            })
180            .when_some(self.size_of_set, |this, size| {
181                this.aria_size_of_set(size)
182            })
183            .when_some(self.group_name, |this, group| this.group(group))
184            .w_full()
185            .child(
186                h_flex()
187                    .id("inner_tree_view_item")
188                    .cursor_pointer()
189                    .size_full()
190                    .h(item_size)
191                    .rounded_sm()
192                    .border_1()
193                    .border_color(transparent_border)
194                    .focus_visible(|s| s.border_color(focused_border))
195                    .when(self.selected, |this| {
196                        this.border_color(selected_border).bg(selected_bg)
197                    })
198                    .hover(|s| s.bg(cx.theme().colors().element_hover))
199                    .map(|this| {
200                        let label = self.label;
201
202                        if self.root_item {
203                            this.px_1()
204                                .gap_2p5()
205                                .child(
206                                    Disclosure::new("toggle", self.expanded)
207                                        .when_some(
208                                            self.on_toggle.clone(),
209                                            |disclosure, on_toggle| {
210                                                disclosure.on_toggle_expanded(on_toggle)
211                                            },
212                                        )
213                                        .opened_icon(IconName::ChevronDown)
214                                        .closed_icon(IconName::ChevronRight),
215                                )
216                                .child(
217                                    Label::new(label)
218                                        .when(!self.selected, |this| this.color(Color::Muted)),
219                                )
220                        } else {
221                            this.child(indentation_line).child(
222                                h_flex()
223                                    .id("nested_inner_tree_view_item")
224                                    .w_full()
225                                    .flex_grow()
226                                    .px_1()
227                                    .child(
228                                        Label::new(label)
229                                            .when(!self.selected, |this| this.color(Color::Muted)),
230                                    ),
231                            )
232                        }
233                    })
234                    .when_some(self.focus_handle, |this, handle| this.track_focus(&handle))
235                    .when_some(self.tab_index, |this, index| this.tab_index(index))
236                    .when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover))
237                    .when_some(
238                        self.on_click.filter(|_| !self.disabled),
239                        |this, on_click| this.on_click(on_click),
240                    )
241                    .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
242                        this.on_mouse_down(MouseButton::Right, move |event, window, cx| {
243                            (on_mouse_down)(event, window, cx)
244                        })
245                    })
246                    .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip)),
247            )
248    }
249}
250
251impl Component for TreeViewItem {
252    fn scope() -> ComponentScope {
253        ComponentScope::Navigation
254    }
255
256    fn description() -> Option<&'static str> {
257        Some(
258            "A hierarchical list of items that may have a parent-child relationship where children can be toggled into view by expanding or collapsing their parent item.",
259        )
260    }
261
262    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
263        let container = || {
264            v_flex()
265                .p_2()
266                .w_64()
267                .border_1()
268                .border_color(cx.theme().colors().border_variant)
269                .bg(cx.theme().colors().panel_background)
270        };
271
272        Some(
273            example_group(vec![
274                single_example(
275                    "Basic Tree View",
276                    container()
277                        .child(
278                            TreeViewItem::new("index-1", "Tree Item Root #1")
279                                .root_item(true)
280                                .toggle_state(true),
281                        )
282                        .child(TreeViewItem::new("index-2", "Tree Item #2"))
283                        .child(TreeViewItem::new("index-3", "Tree Item #3"))
284                        .child(TreeViewItem::new("index-4", "Tree Item Root #2").root_item(true))
285                        .child(TreeViewItem::new("index-5", "Tree Item #5"))
286                        .child(TreeViewItem::new("index-6", "Tree Item #6"))
287                        .into_any_element(),
288                ),
289                single_example(
290                    "Active Child",
291                    container()
292                        .child(TreeViewItem::new("index-1", "Tree Item Root #1").root_item(true))
293                        .child(TreeViewItem::new("index-2", "Tree Item #2").toggle_state(true))
294                        .child(TreeViewItem::new("index-3", "Tree Item #3"))
295                        .into_any_element(),
296                ),
297                single_example(
298                    "Focused Parent",
299                    container()
300                        .child(
301                            TreeViewItem::new("index-1", "Tree Item Root #1")
302                                .root_item(true)
303                                .focused(true)
304                                .toggle_state(true),
305                        )
306                        .child(TreeViewItem::new("index-2", "Tree Item #2"))
307                        .child(TreeViewItem::new("index-3", "Tree Item #3"))
308                        .into_any_element(),
309                ),
310                single_example(
311                    "Focused Child",
312                    container()
313                        .child(
314                            TreeViewItem::new("index-1", "Tree Item Root #1")
315                                .root_item(true)
316                                .toggle_state(true),
317                        )
318                        .child(TreeViewItem::new("index-2", "Tree Item #2").focused(true))
319                        .child(TreeViewItem::new("index-3", "Tree Item #3"))
320                        .into_any_element(),
321                ),
322            ])
323            .into_any_element(),
324        )
325    }
326}