Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:38:16.496Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

tree_view_item.rs

308 lines · 11.3 KB · rust
1use std::sync::Arc;
2
3use gpui::{AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Role};
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.4);
139        let focused_border = cx.theme().colors().border_focused;
140
141        let item_size = rems_from_px(28.);
142        let indentation_line = h_flex()
143            .h(item_size)
144            .w(px(22.))
145            .flex_none()
146            .justify_center()
147            .child(
148                div()
149                    .w_px()
150                    .h_full()
151                    .bg(cx.theme().colors().border.opacity(0.5)),
152            );
153
154        let aria_label = self.label.clone();
155        let root_item = self.root_item;
156        let expanded = self.expanded;
157        let selected = self.selected;
158
159        h_flex()
160            .id(self.id)
161            .when_some(self.group_name, |this, group| this.group(group))
162            .w_full()
163            .child(
164                h_flex()
165                    .id("inner_tree_view_item")
166                    // The accessible role and state live on this element
167                    // because it also carries the click handler and focus, so
168                    // assistive technology reports a single actionable tree
169                    // item rather than an inert container.
170                    .role(Role::TreeItem)
171                    .aria_label(aria_label)
172                    .aria_selected(selected)
173                    .aria_level(if root_item { 1 } else { 2 })
174                    .when(root_item, |this| this.aria_expanded(expanded))
175                    .cursor_pointer()
176                    .size_full()
177                    .h(item_size)
178                    .pl_0p5()
179                    .pr_1()
180                    .gap_2()
181                    .rounded_sm()
182                    .border_1()
183                    .border_color(transparent_border)
184                    .focus_visible(|s| s.border_color(focused_border))
185                    .when(self.selected, |this| {
186                        this.border_color(selected_border).bg(selected_bg)
187                    })
188                    .hover(|s| s.bg(cx.theme().colors().element_hover))
189                    .map(|this| {
190                        let label = self.label;
191
192                        if self.root_item {
193                            this.child(
194                                Disclosure::new("toggle", self.expanded)
195                                    .when_some(self.on_toggle.clone(), |disclosure, on_toggle| {
196                                        disclosure.on_toggle_expanded(on_toggle)
197                                    })
198                                    .opened_icon(IconName::ChevronDown)
199                                    .closed_icon(IconName::ChevronRight),
200                            )
201                            .child(
202                                Label::new(label)
203                                    .when(!self.selected, |this| this.color(Color::Muted)),
204                            )
205                        } else {
206                            this.child(indentation_line).child(
207                                h_flex()
208                                    .id("nested_inner_tree_view_item")
209                                    .w_full()
210                                    .flex_grow_1()
211                                    .child(
212                                        Label::new(label)
213                                            .when(!self.selected, |this| this.color(Color::Muted)),
214                                    ),
215                            )
216                        }
217                    })
218                    .when_some(self.focus_handle, |this, handle| this.track_focus(&handle))
219                    .when_some(self.tab_index, |this, index| this.tab_index(index))
220                    .when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover))
221                    .when_some(
222                        self.on_click.filter(|_| !self.disabled),
223                        |this, on_click| this.on_click(on_click),
224                    )
225                    .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
226                        this.on_mouse_down(MouseButton::Right, move |event, window, cx| {
227                            (on_mouse_down)(event, window, cx)
228                        })
229                    })
230                    .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip)),
231            )
232    }
233}
234
235impl Component for TreeViewItem {
236    fn scope() -> ComponentScope {
237        ComponentScope::Navigation
238    }
239
240    fn description() -> &'static str {
241        "A hierarchical list of items that may have a parent-child relationship \
242        where children can be toggled into view by expanding or collapsing their parent item."
243    }
244
245    fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
246        let container = || {
247            v_flex()
248                .p_2()
249                .w_64()
250                .border_1()
251                .border_color(cx.theme().colors().border_variant)
252                .bg(cx.theme().colors().panel_background)
253        };
254
255        example_group(vec![
256            single_example(
257                "Basic Tree View",
258                container()
259                    .child(
260                        TreeViewItem::new("index-1", "Tree Item Root #1")
261                            .root_item(true)
262                            .toggle_state(true),
263                    )
264                    .child(TreeViewItem::new("index-2", "Tree Item #2"))
265                    .child(TreeViewItem::new("index-3", "Tree Item #3"))
266                    .child(TreeViewItem::new("index-4", "Tree Item Root #2").root_item(true))
267                    .child(TreeViewItem::new("index-5", "Tree Item #5"))
268                    .child(TreeViewItem::new("index-6", "Tree Item #6"))
269                    .into_any_element(),
270            ),
271            single_example(
272                "Active Child",
273                container()
274                    .child(TreeViewItem::new("index-1", "Tree Item Root #1").root_item(true))
275                    .child(TreeViewItem::new("index-2", "Tree Item #2").toggle_state(true))
276                    .child(TreeViewItem::new("index-3", "Tree Item #3"))
277                    .into_any_element(),
278            ),
279            single_example(
280                "Focused Parent",
281                container()
282                    .child(
283                        TreeViewItem::new("index-1", "Tree Item Root #1")
284                            .root_item(true)
285                            .focused(true)
286                            .toggle_state(true),
287                    )
288                    .child(TreeViewItem::new("index-2", "Tree Item #2"))
289                    .child(TreeViewItem::new("index-3", "Tree Item #3"))
290                    .into_any_element(),
291            ),
292            single_example(
293                "Focused Child",
294                container()
295                    .child(
296                        TreeViewItem::new("index-1", "Tree Item Root #1")
297                            .root_item(true)
298                            .toggle_state(true),
299                    )
300                    .child(TreeViewItem::new("index-2", "Tree Item #2").focused(true))
301                    .child(TreeViewItem::new("index-3", "Tree Item #3"))
302                    .into_any_element(),
303            ),
304        ])
305        .into_any_element()
306    }
307}
308
Served at tenant.openagents/omega Member data and write actions are omitted.