1use crate::{prelude::*, Avatar, Disclosure, Icon, IconElement, IconSize};
2use gpui::{
3 px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
4 Stateful,
5};
6use smallvec::SmallVec;
7
8#[derive(IntoElement)]
9pub struct ListItem {
10 id: ElementId,
11 selected: bool,
12 // TODO: Reintroduce this
13 // disclosure_control_style: DisclosureControlVisibility,
14 indent_level: usize,
15 indent_step_size: Pixels,
16 left_slot: Option<AnyElement>,
17 toggle: Option<bool>,
18 inset: bool,
19 on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
20 on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
21 tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
22 on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
23 children: SmallVec<[AnyElement; 2]>,
24}
25
26impl ListItem {
27 pub fn new(id: impl Into<ElementId>) -> Self {
28 Self {
29 id: id.into(),
30 selected: false,
31 indent_level: 0,
32 indent_step_size: px(12.),
33 left_slot: None,
34 toggle: None,
35 inset: false,
36 on_click: None,
37 on_secondary_mouse_down: None,
38 on_toggle: None,
39 tooltip: None,
40 children: SmallVec::new(),
41 }
42 }
43
44 pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
45 self.on_click = Some(Box::new(handler));
46 self
47 }
48
49 pub fn on_secondary_mouse_down(
50 mut self,
51 handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
52 ) -> Self {
53 self.on_secondary_mouse_down = Some(Box::new(handler));
54 self
55 }
56
57 pub fn on_drag(
58 mut self,
59 handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
60 ) -> Self {
61 self.on_secondary_mouse_down = Some(Box::new(handler));
62 self
63 }
64
65 pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
66 self.tooltip = Some(Box::new(tooltip));
67 self
68 }
69
70 pub fn inset(mut self, inset: bool) -> Self {
71 self.inset = inset;
72 self
73 }
74
75 pub fn indent_level(mut self, indent_level: usize) -> Self {
76 self.indent_level = indent_level;
77 self
78 }
79
80 pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
81 self.indent_step_size = indent_step_size;
82 self
83 }
84
85 pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
86 self.toggle = toggle.into();
87 self
88 }
89
90 pub fn on_toggle(
91 mut self,
92 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
93 ) -> Self {
94 self.on_toggle = Some(Box::new(on_toggle));
95 self
96 }
97
98 pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
99 self.left_slot = Some(left_content.into_any_element());
100 self
101 }
102
103 pub fn left_icon(mut self, left_icon: Icon) -> Self {
104 self.left_slot = Some(
105 IconElement::new(left_icon)
106 .size(IconSize::Small)
107 .color(Color::Muted)
108 .into_any_element(),
109 );
110 self
111 }
112
113 pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
114 self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element());
115 self
116 }
117}
118
119impl Selectable for ListItem {
120 fn selected(mut self, selected: bool) -> Self {
121 self.selected = selected;
122 self
123 }
124}
125
126impl ParentElement for ListItem {
127 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
128 &mut self.children
129 }
130}
131
132impl RenderOnce for ListItem {
133 type Rendered = Stateful<Div>;
134
135 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
136 div()
137 .id(self.id)
138 .relative()
139 // TODO: Add focus state
140 // .when(self.state == InteractionState::Focused, |this| {
141 // this.border()
142 // .border_color(cx.theme().colors().border_focused)
143 // })
144 .when(self.inset, |this| this.rounded_md())
145 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
146 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
147 .when(self.selected, |this| {
148 this.bg(cx.theme().colors().ghost_element_selected)
149 })
150 .when_some(self.on_click, |this, on_click| {
151 this.cursor_pointer().on_click(move |event, cx| {
152 // HACK: GPUI currently fires `on_click` with any mouse button,
153 // but we only care about the left button.
154 if event.down.button == MouseButton::Left {
155 (on_click)(event, cx)
156 }
157 })
158 })
159 .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
160 this.on_mouse_down(MouseButton::Right, move |event, cx| {
161 (on_mouse_down)(event, cx)
162 })
163 })
164 .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
165 .child(
166 div()
167 .when(self.inset, |this| this.px_2())
168 .ml(self.indent_level as f32 * self.indent_step_size)
169 .flex()
170 .gap_1()
171 .items_center()
172 .relative()
173 .children(
174 self.toggle
175 .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
176 )
177 .children(self.left_slot)
178 .children(self.children),
179 )
180 }
181}