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 let left_content = match self.left_slot.clone() {
289 Some(GraphicSlot::Icon(i)) => Some(
290 h_stack().child(
291 IconElement::new(i)
292 .size(IconSize::Small)
293 .color(Color::Muted),
294 ),
295 ),
296 Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))),
297 Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
298 None => None,
299 };
300
301 div()
302 .id(self.id)
303 .relative()
304 // TODO: Add focus state
305 // .when(self.state == InteractionState::Focused, |this| {
306 // this.border()
307 // .border_color(cx.theme().colors().border_focused)
308 // })
309 .when(self.inset, |this| this.rounded_md())
310 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
311 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
312 .when(self.selected, |this| {
313 this.bg(cx.theme().colors().ghost_element_selected)
314 })
315 .when_some(self.on_click.clone(), |this, on_click| {
316 this.cursor_pointer().on_click(move |event, cx| {
317 // HACK: GPUI currently fires `on_click` with any mouse button,
318 // but we only care about the left button.
319 if event.down.button == MouseButton::Left {
320 (on_click)(event, cx)
321 }
322 })
323 })
324 .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
325 this.on_mouse_down(MouseButton::Right, move |event, cx| {
326 (on_mouse_down)(event, cx)
327 })
328 })
329 .child(
330 div()
331 .when(self.inset, |this| this.px_2())
332 .ml(self.indent_level as f32 * self.indent_step_size)
333 .flex()
334 .gap_1()
335 .items_center()
336 .relative()
337 .child(disclosure_control(self.toggle, self.on_toggle))
338 .children(left_content)
339 .children(self.children),
340 )
341 }
342}
343
344impl ParentElement for ListItem {
345 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
346 &mut self.children
347 }
348}
349
350#[derive(IntoElement, Clone)]
351pub struct ListSeparator;
352
353impl ListSeparator {
354 pub fn new() -> Self {
355 Self
356 }
357}
358
359impl RenderOnce for ListSeparator {
360 type Rendered = Div;
361
362 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
363 div().h_px().w_full().bg(cx.theme().colors().border_variant)
364 }
365}
366
367#[derive(IntoElement)]
368pub struct List {
369 /// Message to display when the list is empty
370 /// Defaults to "No items"
371 empty_message: SharedString,
372 header: Option<ListHeader>,
373 toggle: Toggle,
374 children: SmallVec<[AnyElement; 2]>,
375}
376
377impl RenderOnce for List {
378 type Rendered = Div;
379
380 fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
381 let list_content = match (self.children.is_empty(), self.toggle) {
382 (false, _) => div().children(self.children),
383 (true, Toggle::Toggled(false)) => div(),
384 (true, _) => div().child(Label::new(self.empty_message.clone()).color(Color::Muted)),
385 };
386
387 v_stack()
388 .w_full()
389 .py_1()
390 .children(self.header.map(|header| header))
391 .child(list_content)
392 }
393}
394
395impl List {
396 pub fn new() -> Self {
397 Self {
398 empty_message: "No items".into(),
399 header: None,
400 toggle: Toggle::NotToggleable,
401 children: SmallVec::new(),
402 }
403 }
404
405 pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
406 self.empty_message = empty_message.into();
407 self
408 }
409
410 pub fn header(mut self, header: ListHeader) -> Self {
411 self.header = Some(header);
412 self
413 }
414
415 pub fn toggle(mut self, toggle: Toggle) -> Self {
416 self.toggle = toggle;
417 self
418 }
419}
420
421impl ParentElement for List {
422 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
423 &mut self.children
424 }
425}