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