1use std::rc::Rc;
2
3use gpui::{ClickEvent, Div};
4
5use crate::prelude::*;
6use crate::{disclosure_control, h_stack, Icon, IconButton, IconElement, IconSize, Label, Toggle};
7
8pub enum ListHeaderMeta {
9 Tools(Vec<IconButton>),
10 // TODO: This should be a button
11 Button(Label),
12 Text(Label),
13}
14
15#[derive(IntoElement)]
16pub struct ListHeader {
17 label: SharedString,
18 left_icon: Option<Icon>,
19 meta: Option<ListHeaderMeta>,
20 toggle: Toggle,
21 on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
22 inset: bool,
23 selected: bool,
24}
25
26impl ListHeader {
27 pub fn new(label: impl Into<SharedString>) -> Self {
28 Self {
29 label: label.into(),
30 left_icon: None,
31 meta: None,
32 inset: false,
33 toggle: Toggle::NotToggleable,
34 on_toggle: None,
35 selected: false,
36 }
37 }
38
39 pub fn toggle(mut self, toggle: Toggle) -> Self {
40 self.toggle = toggle;
41 self
42 }
43
44 pub fn on_toggle(
45 mut self,
46 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
47 ) -> Self {
48 self.on_toggle = Some(Rc::new(on_toggle));
49 self
50 }
51
52 pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
53 self.left_icon = left_icon;
54 self
55 }
56
57 pub fn right_button(self, button: IconButton) -> Self {
58 self.meta(Some(ListHeaderMeta::Tools(vec![button])))
59 }
60
61 pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
62 self.meta = meta;
63 self
64 }
65
66 pub fn selected(mut self, selected: bool) -> Self {
67 self.selected = selected;
68 self
69 }
70}
71
72impl RenderOnce for ListHeader {
73 type Rendered = Div;
74
75 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
76 let disclosure_control = disclosure_control(self.toggle, self.on_toggle);
77
78 let meta = match self.meta {
79 Some(ListHeaderMeta::Tools(icons)) => div().child(
80 h_stack()
81 .gap_2()
82 .items_center()
83 .children(icons.into_iter().map(|i| i.color(Color::Muted))),
84 ),
85 Some(ListHeaderMeta::Button(label)) => div().child(label),
86 Some(ListHeaderMeta::Text(label)) => div().child(label),
87 None => div(),
88 };
89
90 h_stack().w_full().relative().child(
91 div()
92 .h_5()
93 .when(self.inset, |this| this.px_2())
94 .when(self.selected, |this| {
95 this.bg(cx.theme().colors().ghost_element_selected)
96 })
97 .flex()
98 .flex_1()
99 .items_center()
100 .justify_between()
101 .w_full()
102 .gap_1()
103 .child(
104 h_stack()
105 .gap_1()
106 .child(
107 div()
108 .flex()
109 .gap_1()
110 .items_center()
111 .children(self.left_icon.map(|i| {
112 IconElement::new(i)
113 .color(Color::Muted)
114 .size(IconSize::Small)
115 }))
116 .child(Label::new(self.label.clone()).color(Color::Muted)),
117 )
118 .child(disclosure_control),
119 )
120 .child(meta),
121 )
122 }
123}