list_header.rs

  1use std::sync::Arc;
  2
  3use crate::{Disclosure, Label, h_flex, prelude::*};
  4use gpui::{AnyElement, ClickEvent};
  5use settings::Settings;
  6use theme::ThemeSettings;
  7
  8#[derive(IntoElement)]
  9pub struct ListHeader {
 10    /// The label of the header.
 11    label: SharedString,
 12    /// A slot for content that appears before the label, like an icon or avatar.
 13    start_slot: Option<AnyElement>,
 14    /// A slot for content that appears after the label, usually on the other side of the header.
 15    /// This might be a button, a disclosure arrow, a face pile, etc.
 16    end_slot: Option<AnyElement>,
 17    /// A slot for content that appears on hover after the label
 18    /// It will obscure the `end_slot` when visible.
 19    end_hover_slot: Option<AnyElement>,
 20    toggle: Option<bool>,
 21    on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + '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            start_slot: None,
 31            end_slot: None,
 32            end_hover_slot: None,
 33            inset: false,
 34            toggle: None,
 35            on_toggle: None,
 36            selected: false,
 37        }
 38    }
 39
 40    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
 41        self.toggle = toggle.into();
 42        self
 43    }
 44
 45    pub fn on_toggle(
 46        mut self,
 47        on_toggle: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 48    ) -> Self {
 49        self.on_toggle = Some(Arc::new(on_toggle));
 50        self
 51    }
 52
 53    pub fn start_slot<E: IntoElement>(mut self, start_slot: impl Into<Option<E>>) -> Self {
 54        self.start_slot = start_slot.into().map(IntoElement::into_any_element);
 55        self
 56    }
 57
 58    pub fn end_slot<E: IntoElement>(mut self, end_slot: impl Into<Option<E>>) -> Self {
 59        self.end_slot = end_slot.into().map(IntoElement::into_any_element);
 60        self
 61    }
 62
 63    pub fn end_hover_slot<E: IntoElement>(mut self, end_hover_slot: impl Into<Option<E>>) -> Self {
 64        self.end_hover_slot = end_hover_slot.into().map(IntoElement::into_any_element);
 65        self
 66    }
 67
 68    pub fn inset(mut self, inset: bool) -> Self {
 69        self.inset = inset;
 70        self
 71    }
 72}
 73
 74impl Toggleable for ListHeader {
 75    fn toggle_state(mut self, selected: bool) -> Self {
 76        self.selected = selected;
 77        self
 78    }
 79}
 80
 81impl RenderOnce for ListHeader {
 82    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 83        let ui_density = ThemeSettings::get_global(cx).ui_density;
 84
 85        h_flex()
 86            .id(self.label.clone())
 87            .w_full()
 88            .relative()
 89            .group("list_header")
 90            .child(
 91                div()
 92                    .map(|this| match ui_density {
 93                        theme::UiDensity::Comfortable => this.h_5(),
 94                        _ => this.h_7(),
 95                    })
 96                    .when(self.inset, |this| this.px_2())
 97                    .when(self.selected, |this| {
 98                        this.bg(cx.theme().colors().ghost_element_selected)
 99                    })
100                    .flex()
101                    .flex_1()
102                    .items_center()
103                    .justify_between()
104                    .w_full()
105                    .gap(DynamicSpacing::Base04.rems(cx))
106                    .child(
107                        h_flex()
108                            .gap(DynamicSpacing::Base04.rems(cx))
109                            .children(self.toggle.map(|is_open| {
110                                Disclosure::new("toggle", is_open).on_toggle(self.on_toggle.clone())
111                            }))
112                            .child(
113                                div()
114                                    .id("label_container")
115                                    .flex()
116                                    .gap(DynamicSpacing::Base04.rems(cx))
117                                    .items_center()
118                                    .children(self.start_slot)
119                                    .child(Label::new(self.label.clone()).color(Color::Muted))
120                                    .when_some(self.on_toggle, |this, on_toggle| {
121                                        this.on_click(move |event, window, cx| {
122                                            on_toggle(event, window, cx)
123                                        })
124                                    }),
125                            ),
126                    )
127                    .child(h_flex().children(self.end_slot))
128                    .when_some(self.end_hover_slot, |this, end_hover_slot| {
129                        this.child(
130                            div()
131                                .absolute()
132                                .right_0()
133                                .visible_on_hover("list_header")
134                                .child(end_hover_slot),
135                        )
136                    }),
137            )
138    }
139}