1use std::rc::Rc;
2
3use gpui::{
4 div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
5 Pixels, Stateful, StatefulInteractiveElement,
6};
7use smallvec::SmallVec;
8
9use crate::{
10 disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label,
11 Toggle,
12};
13use crate::{prelude::*, GraphicSlot};
14
15pub enum ListHeaderMeta {
16 Tools(Vec<IconButton>),
17 // TODO: This should be a button
18 Button(Label),
19 Text(Label),
20}
21
22#[derive(IntoElement)]
23pub struct ListHeader {
24 label: SharedString,
25 left_icon: Option<Icon>,
26 meta: Option<ListHeaderMeta>,
27 toggle: Toggle,
28 on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
29 inset: bool,
30 selected: bool,
31}
32
33impl ListHeader {
34 pub fn new(label: impl Into<SharedString>) -> Self {
35 Self {
36 label: label.into(),
37 left_icon: None,
38 meta: None,
39 inset: false,
40 toggle: Toggle::NotToggleable,
41 on_toggle: None,
42 selected: false,
43 }
44 }
45
46 pub fn toggle(mut self, toggle: Toggle) -> Self {
47 self.toggle = toggle;
48 self
49 }
50
51 pub fn on_toggle(
52 mut self,
53 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
54 ) -> Self {
55 self.on_toggle = Some(Rc::new(on_toggle));
56 self
57 }
58
59 pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
60 self.left_icon = left_icon;
61 self
62 }
63
64 pub fn right_button(self, button: IconButton) -> Self {
65 self.meta(Some(ListHeaderMeta::Tools(vec![button])))
66 }
67
68 pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
69 self.meta = meta;
70 self
71 }
72
73 pub fn selected(mut self, selected: bool) -> Self {
74 self.selected = selected;
75 self
76 }
77}
78
79impl RenderOnce for ListHeader {
80 type Rendered = Div;
81
82 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
83 let disclosure_control = disclosure_control(self.toggle, self.on_toggle);
84
85 let meta = match self.meta {
86 Some(ListHeaderMeta::Tools(icons)) => div().child(
87 h_stack()
88 .gap_2()
89 .items_center()
90 .children(icons.into_iter().map(|i| i.color(Color::Muted))),
91 ),
92 Some(ListHeaderMeta::Button(label)) => div().child(label),
93 Some(ListHeaderMeta::Text(label)) => div().child(label),
94 None => div(),
95 };
96
97 h_stack()
98 .w_full()
99 .bg(cx.theme().colors().surface_background)
100 .relative()
101 .child(
102 div()
103 .h_5()
104 .when(self.inset, |this| this.px_2())
105 .when(self.selected, |this| {
106 this.bg(cx.theme().colors().ghost_element_selected)
107 })
108 .flex()
109 .flex_1()
110 .items_center()
111 .justify_between()
112 .w_full()
113 .gap_1()
114 .child(
115 h_stack()
116 .gap_1()
117 .child(
118 div()
119 .flex()
120 .gap_1()
121 .items_center()
122 .children(self.left_icon.map(|i| {
123 IconElement::new(i)
124 .color(Color::Muted)
125 .size(IconSize::Small)
126 }))
127 .child(Label::new(self.label.clone()).color(Color::Muted)),
128 )
129 .child(disclosure_control),
130 )
131 .child(meta),
132 )
133 }
134}
135
136#[derive(IntoElement, Clone)]
137pub struct ListSubHeader {
138 label: SharedString,
139 left_icon: Option<Icon>,
140 inset: bool,
141}
142
143impl ListSubHeader {
144 pub fn new(label: impl Into<SharedString>) -> Self {
145 Self {
146 label: label.into(),
147 left_icon: None,
148 inset: false,
149 }
150 }
151
152 pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
153 self.left_icon = left_icon;
154 self
155 }
156}
157
158impl RenderOnce for ListSubHeader {
159 type Rendered = Div;
160
161 fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
162 h_stack().flex_1().w_full().relative().py_1().child(
163 div()
164 .h_6()
165 .when(self.inset, |this| this.px_2())
166 .flex()
167 .flex_1()
168 .w_full()
169 .gap_1()
170 .items_center()
171 .justify_between()
172 .child(
173 div()
174 .flex()
175 .gap_1()
176 .items_center()
177 .children(self.left_icon.map(|i| {
178 IconElement::new(i)
179 .color(Color::Muted)
180 .size(IconSize::Small)
181 }))
182 .child(Label::new(self.label.clone()).color(Color::Muted)),
183 ),
184 )
185 }
186}
187
188#[derive(IntoElement)]
189pub struct ListItem {
190 id: ElementId,
191 selected: bool,
192 // TODO: Reintroduce this
193 // disclosure_control_style: DisclosureControlVisibility,
194 indent_level: usize,
195 indent_step_size: Pixels,
196 left_slot: Option<GraphicSlot>,
197 toggle: Toggle,
198 inset: bool,
199 on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
200 on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
201 on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
202 children: SmallVec<[AnyElement; 2]>,
203}
204
205impl ListItem {
206 pub fn new(id: impl Into<ElementId>) -> Self {
207 Self {
208 id: id.into(),
209 selected: false,
210 indent_level: 0,
211 indent_step_size: px(12.),
212 left_slot: None,
213 toggle: Toggle::NotToggleable,
214 inset: false,
215 on_click: None,
216 on_secondary_mouse_down: None,
217 on_toggle: None,
218 children: SmallVec::new(),
219 }
220 }
221
222 pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
223 self.on_click = Some(Rc::new(handler));
224 self
225 }
226
227 pub fn on_secondary_mouse_down(
228 mut self,
229 handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
230 ) -> Self {
231 self.on_secondary_mouse_down = Some(Rc::new(handler));
232 self
233 }
234
235 pub fn inset(mut self, inset: bool) -> Self {
236 self.inset = inset;
237 self
238 }
239
240 pub fn indent_level(mut self, indent_level: usize) -> Self {
241 self.indent_level = indent_level;
242 self
243 }
244
245 pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
246 self.indent_step_size = indent_step_size;
247 self
248 }
249
250 pub fn toggle(mut self, toggle: Toggle) -> Self {
251 self.toggle = toggle;
252 self
253 }
254
255 pub fn on_toggle(
256 mut self,
257 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
258 ) -> Self {
259 self.on_toggle = Some(Rc::new(on_toggle));
260 self
261 }
262
263 pub fn selected(mut self, selected: bool) -> Self {
264 self.selected = selected;
265 self
266 }
267
268 pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
269 self.left_slot = Some(left_content);
270 self
271 }
272
273 pub fn left_icon(mut self, left_icon: Icon) -> Self {
274 self.left_slot = Some(GraphicSlot::Icon(left_icon));
275 self
276 }
277
278 pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
279 self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
280 self
281 }
282}
283
284impl RenderOnce for ListItem {
285 type Rendered = Stateful<Div>;
286
287 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
288 div()
289 .id(self.id)
290 .relative()
291 // TODO: Add focus state
292 // .when(self.state == InteractionState::Focused, |this| {
293 // this.border()
294 // .border_color(cx.theme().colors().border_focused)
295 // })
296 .when(self.inset, |this| this.rounded_md())
297 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
298 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
299 .when(self.selected, |this| {
300 this.bg(cx.theme().colors().ghost_element_selected)
301 })
302 .when_some(self.on_click, |this, on_click| {
303 this.cursor_pointer().on_click(move |event, cx| {
304 // HACK: GPUI currently fires `on_click` with any mouse button,
305 // but we only care about the left button.
306 if event.down.button == MouseButton::Left {
307 (on_click)(event, cx)
308 }
309 })
310 })
311 .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
312 this.on_mouse_down(MouseButton::Right, move |event, cx| {
313 (on_mouse_down)(event, cx)
314 })
315 })
316 .child(
317 div()
318 .when(self.inset, |this| this.px_2())
319 .ml(self.indent_level as f32 * self.indent_step_size)
320 .flex()
321 .gap_1()
322 .items_center()
323 .relative()
324 .child(disclosure_control(self.toggle, self.on_toggle))
325 .map(|this| match self.left_slot {
326 Some(GraphicSlot::Icon(i)) => this.child(
327 IconElement::new(i)
328 .size(IconSize::Small)
329 .color(Color::Muted),
330 ),
331 Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
332 Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
333 None => this,
334 })
335 .children(self.children),
336 )
337 }
338}
339
340impl ParentElement for ListItem {
341 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
342 &mut self.children
343 }
344}
345
346#[derive(IntoElement, Clone)]
347pub struct ListSeparator;
348
349impl ListSeparator {
350 pub fn new() -> Self {
351 Self
352 }
353}
354
355impl RenderOnce for ListSeparator {
356 type Rendered = Div;
357
358 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
359 div().h_px().w_full().bg(cx.theme().colors().border_variant)
360 }
361}
362
363#[derive(IntoElement)]
364pub struct List {
365 /// Message to display when the list is empty
366 /// Defaults to "No items"
367 empty_message: SharedString,
368 header: Option<ListHeader>,
369 toggle: Toggle,
370 children: SmallVec<[AnyElement; 2]>,
371}
372
373impl RenderOnce for List {
374 type Rendered = Div;
375
376 fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
377 let list_content = match (self.children.is_empty(), self.toggle) {
378 (false, _) => div().children(self.children),
379 (true, Toggle::Toggled(false)) => div(),
380 (true, _) => div().child(Label::new(self.empty_message.clone()).color(Color::Muted)),
381 };
382
383 v_stack()
384 .w_full()
385 .py_1()
386 .children(self.header.map(|header| header))
387 .child(list_content)
388 }
389}
390
391impl List {
392 pub fn new() -> Self {
393 Self {
394 empty_message: "No items".into(),
395 header: None,
396 toggle: Toggle::NotToggleable,
397 children: SmallVec::new(),
398 }
399 }
400
401 pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
402 self.empty_message = empty_message.into();
403 self
404 }
405
406 pub fn header(mut self, header: ListHeader) -> Self {
407 self.header = Some(header);
408 self
409 }
410
411 pub fn toggle(mut self, toggle: Toggle) -> Self {
412 self.toggle = toggle;
413 self
414 }
415}
416
417impl ParentElement for List {
418 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
419 &mut self.children
420 }
421}