tree_view_item.rs

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