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 tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
58 self.tooltip = Some(Box::new(tooltip));
59 self
60 }
61
62 pub fn inset(mut self, inset: bool) -> Self {
63 self.inset = inset;
64 self
65 }
66
67 pub fn indent_level(mut self, indent_level: usize) -> Self {
68 self.indent_level = indent_level;
69 self
70 }
71
72 pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
73 self.indent_step_size = indent_step_size;
74 self
75 }
76
77 pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
78 self.toggle = toggle.into();
79 self
80 }
81
82 pub fn on_toggle(
83 mut self,
84 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
85 ) -> Self {
86 self.on_toggle = Some(Box::new(on_toggle));
87 self
88 }
89
90 pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
91 self.left_slot = Some(left_content.into_any_element());
92 self
93 }
94
95 pub fn left_icon(mut self, left_icon: Icon) -> Self {
96 self.left_slot = Some(
97 IconElement::new(left_icon)
98 .size(IconSize::Small)
99 .color(Color::Muted)
100 .into_any_element(),
101 );
102 self
103 }
104
105 pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
106 self.left_slot = Some(Avatar::new(left_avatar).into_any_element());
107 self
108 }
109}
110
111impl Selectable for ListItem {
112 fn selected(mut self, selected: bool) -> Self {
113 self.selected = selected;
114 self
115 }
116}
117
118impl ParentElement for ListItem {
119 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
120 &mut self.children
121 }
122}
123
124impl RenderOnce for ListItem {
125 type Rendered = Stateful<Div>;
126
127 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
128 div()
129 .id(self.id)
130 .relative()
131 // TODO: Add focus state
132 // .when(self.state == InteractionState::Focused, |this| {
133 // this.border()
134 // .border_color(cx.theme().colors().border_focused)
135 // })
136 .when(self.inset, |this| this.rounded_md())
137 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
138 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
139 .when(self.selected, |this| {
140 this.bg(cx.theme().colors().ghost_element_selected)
141 })
142 .when_some(self.on_click, |this, on_click| {
143 this.cursor_pointer().on_click(move |event, cx| {
144 // HACK: GPUI currently fires `on_click` with any mouse button,
145 // but we only care about the left button.
146 if event.down.button == MouseButton::Left {
147 (on_click)(event, cx)
148 }
149 })
150 })
151 .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
152 this.on_mouse_down(MouseButton::Right, move |event, cx| {
153 (on_mouse_down)(event, cx)
154 })
155 })
156 .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
157 .child(
158 div()
159 .when(self.inset, |this| this.px_2())
160 .ml(self.indent_level as f32 * self.indent_step_size)
161 .flex()
162 .gap_1()
163 .items_center()
164 .relative()
165 .children(
166 self.toggle
167 .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
168 )
169 .children(self.left_slot)
170 .children(self.children),
171 )
172 }
173}