list_header.rs

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