divider.rs

  1use gpui::{Hsla, IntoElement};
  2
  3use crate::prelude::*;
  4
  5pub fn divider() -> Divider {
  6    Divider {
  7        style: DividerStyle::Solid,
  8        direction: DividerDirection::Horizontal,
  9        color: DividerColor::default(),
 10        inset: false,
 11    }
 12}
 13
 14pub fn vertical_divider() -> Divider {
 15    Divider {
 16        style: DividerStyle::Solid,
 17        direction: DividerDirection::Vertical,
 18        color: DividerColor::default(),
 19        inset: false,
 20    }
 21}
 22
 23#[derive(Clone, Copy, PartialEq)]
 24enum DividerStyle {
 25    Solid,
 26    Dashed,
 27}
 28
 29#[derive(Clone, Copy, PartialEq)]
 30enum DividerDirection {
 31    Horizontal,
 32    Vertical,
 33}
 34
 35/// The color of a [`Divider`].
 36#[derive(Default)]
 37pub enum DividerColor {
 38    Border,
 39    BorderFaded,
 40    #[default]
 41    BorderVariant,
 42}
 43
 44impl DividerColor {
 45    pub fn hsla(self, cx: &mut App) -> Hsla {
 46        match self {
 47            DividerColor::Border => cx.theme().colors().border,
 48            DividerColor::BorderFaded => cx.theme().colors().border.opacity(0.6),
 49            DividerColor::BorderVariant => cx.theme().colors().border_variant,
 50        }
 51    }
 52}
 53
 54#[derive(IntoElement, RegisterComponent)]
 55pub struct Divider {
 56    style: DividerStyle,
 57    direction: DividerDirection,
 58    color: DividerColor,
 59    inset: bool,
 60}
 61
 62impl RenderOnce for Divider {
 63    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
 64        match self.style {
 65            DividerStyle::Solid => self.render_solid(cx).into_any_element(),
 66            DividerStyle::Dashed => self.render_dashed(cx).into_any_element(),
 67        }
 68    }
 69}
 70
 71impl Divider {
 72    pub fn horizontal() -> Self {
 73        Self {
 74            style: DividerStyle::Solid,
 75            direction: DividerDirection::Horizontal,
 76            color: DividerColor::default(),
 77            inset: false,
 78        }
 79    }
 80
 81    pub fn vertical() -> Self {
 82        Self {
 83            style: DividerStyle::Solid,
 84            direction: DividerDirection::Vertical,
 85            color: DividerColor::default(),
 86            inset: false,
 87        }
 88    }
 89
 90    pub fn horizontal_dashed() -> Self {
 91        Self {
 92            style: DividerStyle::Dashed,
 93            direction: DividerDirection::Horizontal,
 94            color: DividerColor::default(),
 95            inset: false,
 96        }
 97    }
 98
 99    pub fn vertical_dashed() -> Self {
100        Self {
101            style: DividerStyle::Dashed,
102            direction: DividerDirection::Vertical,
103            color: DividerColor::default(),
104            inset: false,
105        }
106    }
107
108    pub const fn inset(mut self) -> Self {
109        self.inset = true;
110        self
111    }
112
113    pub const fn color(mut self, color: DividerColor) -> Self {
114        self.color = color;
115        self
116    }
117
118    pub fn render_solid(self, cx: &mut App) -> impl IntoElement {
119        div()
120            .map(|this| match self.direction {
121                DividerDirection::Horizontal => {
122                    this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
123                }
124                DividerDirection::Vertical => {
125                    this.w_px().h_full().when(self.inset, |this| this.my_1p5())
126                }
127            })
128            .bg(self.color.hsla(cx))
129    }
130
131    // TODO: Use canvas or a shader here
132    // This obviously is a short term approach
133    pub fn render_dashed(self, cx: &mut App) -> impl IntoElement {
134        let segment_count = 128;
135        let segment_count_f = segment_count as f32;
136        let segment_min_w = 6.;
137        let base = match self.direction {
138            DividerDirection::Horizontal => h_flex(),
139            DividerDirection::Vertical => v_flex(),
140        };
141        let (w, h) = match self.direction {
142            DividerDirection::Horizontal => (px(segment_min_w), px(1.)),
143            DividerDirection::Vertical => (px(1.), px(segment_min_w)),
144        };
145        let color = self.color.hsla(cx);
146        let total_min_w = segment_min_w * segment_count_f * 2.; // * 2 because of the gap
147
148        base.min_w(px(total_min_w))
149            .map(|this| {
150                if self.direction == DividerDirection::Horizontal {
151                    this.w_full().h_px()
152                } else {
153                    this.w_px().h_full()
154                }
155            })
156            .gap(px(segment_min_w))
157            .overflow_hidden()
158            .children(
159                (0..segment_count).map(|_| div().flex_grow().flex_shrink_0().w(w).h(h).bg(color)),
160            )
161    }
162}
163
164impl Component for Divider {
165    fn scope() -> ComponentScope {
166        ComponentScope::Layout
167    }
168
169    fn description() -> Option<&'static str> {
170        Some(
171            "Visual separator used to create divisions between groups of content or sections in a layout.",
172        )
173    }
174
175    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
176        Some(
177            v_flex()
178                .gap_6()
179                .children(vec![
180                    example_group_with_title(
181                        "Horizontal Dividers",
182                        vec![
183                            single_example("Default", Divider::horizontal().into_any_element()),
184                            single_example(
185                                "Border Color",
186                                Divider::horizontal()
187                                    .color(DividerColor::Border)
188                                    .into_any_element(),
189                            ),
190                            single_example(
191                                "Inset",
192                                Divider::horizontal().inset().into_any_element(),
193                            ),
194                            single_example(
195                                "Dashed",
196                                Divider::horizontal_dashed().into_any_element(),
197                            ),
198                        ],
199                    ),
200                    example_group_with_title(
201                        "Vertical Dividers",
202                        vec![
203                            single_example(
204                                "Default",
205                                div().h_16().child(Divider::vertical()).into_any_element(),
206                            ),
207                            single_example(
208                                "Border Color",
209                                div()
210                                    .h_16()
211                                    .child(Divider::vertical().color(DividerColor::Border))
212                                    .into_any_element(),
213                            ),
214                            single_example(
215                                "Inset",
216                                div()
217                                    .h_16()
218                                    .child(Divider::vertical().inset())
219                                    .into_any_element(),
220                            ),
221                            single_example(
222                                "Dashed",
223                                div()
224                                    .h_16()
225                                    .child(Divider::vertical_dashed())
226                                    .into_any_element(),
227                            ),
228                        ],
229                    ),
230                    example_group_with_title(
231                        "Example Usage",
232                        vec![single_example(
233                            "Between Content",
234                            v_flex()
235                                .gap_4()
236                                .px_4()
237                                .child(Label::new("Section One"))
238                                .child(Divider::horizontal())
239                                .child(Label::new("Section Two"))
240                                .child(Divider::horizontal_dashed())
241                                .child(Label::new("Section Three"))
242                                .into_any_element(),
243                        )],
244                    ),
245                ])
246                .into_any_element(),
247        )
248    }
249}