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