use std::sync::Arc; use gpui::{AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent}; use crate::{Disclosure, prelude::*}; #[derive(IntoElement, RegisterComponent)] pub struct TreeViewItem { id: ElementId, group_name: Option, label: SharedString, expanded: bool, selected: bool, disabled: bool, focused: bool, default_expanded: bool, root_item: bool, tooltip: Option AnyView + 'static>>, on_click: Option>, on_hover: Option>, on_toggle: Option>, on_secondary_mouse_down: Option>, tab_index: Option, focus_handle: Option, } impl TreeViewItem { pub fn new(id: impl Into, label: impl Into) -> Self { Self { id: id.into(), group_name: None, label: label.into(), expanded: false, selected: false, disabled: false, focused: false, default_expanded: false, root_item: false, tooltip: None, on_click: None, on_hover: None, on_toggle: None, on_secondary_mouse_down: None, tab_index: None, focus_handle: None, } } pub fn group_name(mut self, group_name: impl Into) -> Self { self.group_name = Some(group_name.into()); self } pub fn on_click( mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, ) -> Self { self.on_click = Some(Box::new(handler)); self } pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { self.on_hover = Some(Box::new(handler)); self } pub fn on_secondary_mouse_down( mut self, handler: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.on_secondary_mouse_down = Some(Box::new(handler)); self } pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self { self.tooltip = Some(Box::new(tooltip)); self } pub fn tab_index(mut self, tab_index: isize) -> Self { self.tab_index = Some(tab_index); self } pub fn expanded(mut self, toggle: bool) -> Self { self.expanded = toggle; self } pub fn default_expanded(mut self, default_expanded: bool) -> Self { self.default_expanded = default_expanded; self } pub fn on_toggle( mut self, on_toggle: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, ) -> Self { self.on_toggle = Some(Arc::new(on_toggle)); self } pub fn root_item(mut self, root_item: bool) -> Self { self.root_item = root_item; self } pub fn focused(mut self, focused: bool) -> Self { self.focused = focused; self } pub fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self { self.focus_handle = Some(focus_handle.clone()); self } } impl Disableable for TreeViewItem { fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self } } impl Toggleable for TreeViewItem { fn toggle_state(mut self, selected: bool) -> Self { self.selected = selected; self } } impl RenderOnce for TreeViewItem { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let selected_bg = cx.theme().colors().element_active.opacity(0.5); let transparent_border = cx.theme().colors().border.opacity(0.); let selected_border = cx.theme().colors().border.opacity(0.4); let focused_border = cx.theme().colors().border_focused; let item_size = rems_from_px(28.); let indentation_line = h_flex().size(item_size).flex_none().justify_center().child( div() .w_px() .h_full() .bg(cx.theme().colors().border.opacity(0.5)), ); h_flex() .id(self.id) .when_some(self.group_name, |this, group| this.group(group)) .w_full() .child( h_flex() .id("inner_tree_view_item") .cursor_pointer() .size_full() .h(item_size) .rounded_sm() .border_1() .border_color(transparent_border) .focus_visible(|s| s.border_color(focused_border)) .when(self.selected, |this| { this.border_color(selected_border).bg(selected_bg) }) .hover(|s| s.bg(cx.theme().colors().element_hover)) .map(|this| { let label = self.label; if self.root_item { this.px_1() .gap_2p5() .child( Disclosure::new("toggle", self.expanded) .when_some( self.on_toggle.clone(), |disclosure, on_toggle| { disclosure.on_toggle_expanded(on_toggle) }, ) .opened_icon(IconName::ChevronDown) .closed_icon(IconName::ChevronRight), ) .child( Label::new(label) .when(!self.selected, |this| this.color(Color::Muted)), ) } else { this.child(indentation_line).child( h_flex() .id("nested_inner_tree_view_item") .w_full() .flex_grow() .px_1() .child( Label::new(label) .when(!self.selected, |this| this.color(Color::Muted)), ), ) } }) .when_some(self.focus_handle, |this, handle| this.track_focus(&handle)) .when_some(self.tab_index, |this, index| this.tab_index(index)) .when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover)) .when_some( self.on_click.filter(|_| !self.disabled), |this, on_click| this.on_click(on_click), ) .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { this.on_mouse_down(MouseButton::Right, move |event, window, cx| { (on_mouse_down)(event, window, cx) }) }) .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip)), ) } } impl Component for TreeViewItem { fn scope() -> ComponentScope { ComponentScope::Navigation } fn description() -> Option<&'static str> { Some( "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.", ) } fn preview(_window: &mut Window, cx: &mut App) -> Option { let container = || { v_flex() .p_2() .w_64() .border_1() .border_color(cx.theme().colors().border_variant) .bg(cx.theme().colors().panel_background) }; Some( example_group(vec![ single_example( "Basic Tree View", container() .child( TreeViewItem::new("index-1", "Tree Item Root #1") .root_item(true) .toggle_state(true), ) .child(TreeViewItem::new("index-2", "Tree Item #2")) .child(TreeViewItem::new("index-3", "Tree Item #3")) .child(TreeViewItem::new("index-4", "Tree Item Root #2").root_item(true)) .child(TreeViewItem::new("index-5", "Tree Item #5")) .child(TreeViewItem::new("index-6", "Tree Item #6")) .into_any_element(), ), single_example( "Active Child", container() .child(TreeViewItem::new("index-1", "Tree Item Root #1").root_item(true)) .child(TreeViewItem::new("index-2", "Tree Item #2").toggle_state(true)) .child(TreeViewItem::new("index-3", "Tree Item #3")) .into_any_element(), ), single_example( "Focused Parent", container() .child( TreeViewItem::new("index-1", "Tree Item Root #1") .root_item(true) .focused(true) .toggle_state(true), ) .child(TreeViewItem::new("index-2", "Tree Item #2")) .child(TreeViewItem::new("index-3", "Tree Item #3")) .into_any_element(), ), single_example( "Focused Child", container() .child( TreeViewItem::new("index-1", "Tree Item Root #1") .root_item(true) .toggle_state(true), ) .child(TreeViewItem::new("index-2", "Tree Item #2").focused(true)) .child(TreeViewItem::new("index-3", "Tree Item #3")) .into_any_element(), ), ]) .into_any_element(), ) } }