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