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