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, GraphicSlot, Icon, IconElement, IconSize, Toggleable};
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<GraphicSlot>,
20 toggle: Toggleable,
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: Toggleable::NotToggleable,
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: Toggleable) -> Self {
74 self.toggle = toggle;
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 selected(mut self, selected: bool) -> Self {
87 self.selected = selected;
88 self
89 }
90
91 pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
92 self.left_slot = Some(left_content);
93 self
94 }
95
96 pub fn left_icon(mut self, left_icon: Icon) -> Self {
97 self.left_slot = Some(GraphicSlot::Icon(left_icon));
98 self
99 }
100
101 pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
102 self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
103 self
104 }
105}
106
107impl ParentElement for ListItem {
108 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
109 &mut self.children
110 }
111}
112
113impl RenderOnce for ListItem {
114 type Rendered = Stateful<Div>;
115
116 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
117 div()
118 .id(self.id)
119 .relative()
120 // TODO: Add focus state
121 // .when(self.state == InteractionState::Focused, |this| {
122 // this.border()
123 // .border_color(cx.theme().colors().border_focused)
124 // })
125 .when(self.inset, |this| this.rounded_md())
126 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
127 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
128 .when(self.selected, |this| {
129 this.bg(cx.theme().colors().ghost_element_selected)
130 })
131 .when_some(self.on_click, |this, on_click| {
132 this.cursor_pointer().on_click(move |event, cx| {
133 // HACK: GPUI currently fires `on_click` with any mouse button,
134 // but we only care about the left button.
135 if event.down.button == MouseButton::Left {
136 (on_click)(event, cx)
137 }
138 })
139 })
140 .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
141 this.on_mouse_down(MouseButton::Right, move |event, cx| {
142 (on_mouse_down)(event, cx)
143 })
144 })
145 .child(
146 div()
147 .when(self.inset, |this| this.px_2())
148 .ml(self.indent_level as f32 * self.indent_step_size)
149 .flex()
150 .gap_1()
151 .items_center()
152 .relative()
153 .children(
154 Disclosure::from_toggleable(self.toggle)
155 .map(|disclosure| disclosure.on_toggle(self.on_toggle)),
156 )
157 .map(|this| match self.left_slot {
158 Some(GraphicSlot::Icon(i)) => this.child(
159 IconElement::new(i)
160 .size(IconSize::Small)
161 .color(Color::Muted),
162 ),
163 Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
164 Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
165 None => this,
166 })
167 .children(self.children),
168 )
169 }
170}