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