divider.rs

  1use gpui::{Hsla, IntoElement, PathBuilder, canvas, point};
  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 Divider {
 63    pub fn horizontal() -> Self {
 64        Self {
 65            style: DividerStyle::Solid,
 66            direction: DividerDirection::Horizontal,
 67            color: DividerColor::default(),
 68            inset: false,
 69        }
 70    }
 71
 72    pub fn vertical() -> Self {
 73        Self {
 74            style: DividerStyle::Solid,
 75            direction: DividerDirection::Vertical,
 76            color: DividerColor::default(),
 77            inset: false,
 78        }
 79    }
 80
 81    pub fn horizontal_dashed() -> Self {
 82        Self {
 83            style: DividerStyle::Dashed,
 84            direction: DividerDirection::Horizontal,
 85            color: DividerColor::default(),
 86            inset: false,
 87        }
 88    }
 89
 90    pub fn vertical_dashed() -> Self {
 91        Self {
 92            style: DividerStyle::Dashed,
 93            direction: DividerDirection::Vertical,
 94            color: DividerColor::default(),
 95            inset: false,
 96        }
 97    }
 98
 99    pub fn inset(mut self) -> Self {
100        self.inset = true;
101        self
102    }
103
104    pub fn color(mut self, color: DividerColor) -> Self {
105        self.color = color;
106        self
107    }
108
109    pub fn render_solid(self, base: Div, cx: &mut App) -> impl IntoElement {
110        base.bg(self.color.hsla(cx))
111    }
112
113    pub fn render_dashed(self, base: Div) -> impl IntoElement {
114        base.relative().child(
115            canvas(
116                |_, _, _| {},
117                move |bounds, _, window, cx| {
118                    let mut builder = PathBuilder::stroke(px(1.)).dash_array(&[px(4.), px(2.)]);
119                    let (start, end) = match self.direction {
120                        DividerDirection::Horizontal => {
121                            let x = bounds.origin.x;
122                            let y = bounds.origin.y + px(0.5);
123                            (point(x, y), point(x + bounds.size.width, y))
124                        }
125                        DividerDirection::Vertical => {
126                            let x = bounds.origin.x + px(0.5);
127                            let y = bounds.origin.y;
128                            (point(x, y), point(x, y + bounds.size.height))
129                        }
130                    };
131                    builder.move_to(start);
132                    builder.line_to(end);
133                    if let Ok(line) = builder.build() {
134                        window.paint_path(line, self.color.hsla(cx));
135                    }
136                },
137            )
138            .absolute()
139            .size_full(),
140        )
141    }
142}
143
144impl RenderOnce for Divider {
145    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
146        let base = match self.direction {
147            DividerDirection::Horizontal => div()
148                .min_w_0()
149                .flex_none()
150                .h_px()
151                .w_full()
152                .when(self.inset, |this| this.mx_1p5()),
153            DividerDirection::Vertical => div()
154                .min_w_0()
155                .flex_none()
156                .w_px()
157                .h_full()
158                .when(self.inset, |this| this.my_1p5()),
159        };
160
161        match self.style {
162            DividerStyle::Solid => self.render_solid(base, cx).into_any_element(),
163            DividerStyle::Dashed => self.render_dashed(base).into_any_element(),
164        }
165    }
166}
167
168impl Component for Divider {
169    fn scope() -> ComponentScope {
170        ComponentScope::Layout
171    }
172
173    fn description() -> Option<&'static str> {
174        Some(
175            "Visual separator used to create divisions between groups of content or sections in a layout.",
176        )
177    }
178
179    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
180        Some(
181            v_flex()
182                .gap_6()
183                .children(vec![
184                    example_group_with_title(
185                        "Horizontal Dividers",
186                        vec![
187                            single_example("Default", Divider::horizontal().into_any_element()),
188                            single_example(
189                                "Border Color",
190                                Divider::horizontal()
191                                    .color(DividerColor::Border)
192                                    .into_any_element(),
193                            ),
194                            single_example(
195                                "Inset",
196                                Divider::horizontal().inset().into_any_element(),
197                            ),
198                            single_example(
199                                "Dashed",
200                                Divider::horizontal_dashed().into_any_element(),
201                            ),
202                        ],
203                    ),
204                    example_group_with_title(
205                        "Vertical Dividers",
206                        vec![
207                            single_example(
208                                "Default",
209                                div().h_16().child(Divider::vertical()).into_any_element(),
210                            ),
211                            single_example(
212                                "Border Color",
213                                div()
214                                    .h_16()
215                                    .child(Divider::vertical().color(DividerColor::Border))
216                                    .into_any_element(),
217                            ),
218                            single_example(
219                                "Inset",
220                                div()
221                                    .h_16()
222                                    .child(Divider::vertical().inset())
223                                    .into_any_element(),
224                            ),
225                            single_example(
226                                "Dashed",
227                                div()
228                                    .h_16()
229                                    .child(Divider::vertical_dashed())
230                                    .into_any_element(),
231                            ),
232                        ],
233                    ),
234                    example_group_with_title(
235                        "Example Usage",
236                        vec![single_example(
237                            "Between Content",
238                            v_flex()
239                                .w_full()
240                                .gap_4()
241                                .px_4()
242                                .child(Label::new("Section One"))
243                                .child(Divider::horizontal())
244                                .child(Label::new("Section Two"))
245                                .child(Divider::horizontal_dashed())
246                                .child(Label::new("Section Three"))
247                                .into_any_element(),
248                        )],
249                    ),
250                ])
251                .into_any_element(),
252        )
253    }
254}