Using the cool, [recently added](https://github.com/zed-industries/zed/pull/40940) `focus-visible` support in some components. This will be particularly nice in the settings UI, as it will not display the focus styles if you're navigating it with a pointer device as opposed to the keyboard. Release Notes: - N/A
295 lines
11 KiB
Rust
295 lines
11 KiB
Rust
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<SharedString>,
|
|
label: SharedString,
|
|
expanded: bool,
|
|
selected: bool,
|
|
disabled: bool,
|
|
focused: bool,
|
|
default_expanded: bool,
|
|
root_item: bool,
|
|
tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
|
|
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
|
on_hover: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
|
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
|
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut Window, &mut App) + 'static>>,
|
|
tab_index: Option<isize>,
|
|
focus_handle: Option<gpui::FocusHandle>,
|
|
}
|
|
|
|
impl TreeViewItem {
|
|
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> 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<SharedString>) -> 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<AnyElement> {
|
|
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(),
|
|
)
|
|
}
|
|
}
|