gpui_component/
divider.rs

1use crate::{ActiveTheme, StyledExt};
2use gpui::{
3    App, Axis, Div, Hsla, IntoElement, ParentElement, PathBuilder, RenderOnce, SharedString,
4    StyleRefinement, Styled, Window, canvas, div, point, prelude::FluentBuilder as _, px,
5};
6
7/// The style of the divider line.
8#[derive(Clone, Copy, PartialEq, Default)]
9pub enum DividerStyle {
10    #[default]
11    Solid,
12    Dashed,
13}
14
15/// A divider that can be either vertical or horizontal.
16#[derive(IntoElement)]
17pub struct Divider {
18    base: Div,
19    style: StyleRefinement,
20    label: Option<SharedString>,
21    axis: Axis,
22    color: Option<Hsla>,
23    line_style: DividerStyle,
24}
25
26impl Divider {
27    /// Creates a vertical divider.
28    pub fn vertical() -> Self {
29        Self {
30            base: div().h_full(),
31            axis: Axis::Vertical,
32            label: None,
33            color: None,
34            style: StyleRefinement::default(),
35            line_style: DividerStyle::Solid,
36        }
37    }
38
39    /// Creates a horizontal divider.
40    pub fn horizontal() -> Self {
41        Self {
42            base: div(),
43            axis: Axis::Horizontal,
44            label: None,
45            color: None,
46            style: StyleRefinement::default(),
47            line_style: DividerStyle::Solid,
48        }
49    }
50
51    /// Creates a vertical dashed divider.
52    pub fn vertical_dashed() -> Self {
53        Self::vertical().dashed()
54    }
55
56    /// Creates a horizontal dashed divider.
57    pub fn horizontal_dashed() -> Self {
58        Self::horizontal().dashed()
59    }
60
61    /// Sets the label for the divider.
62    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
63        self.label = Some(label.into());
64        self
65    }
66
67    /// Sets the color for the divider line.
68    pub fn color(mut self, color: impl Into<Hsla>) -> Self {
69        self.color = Some(color.into());
70        self
71    }
72
73    /// Sets the style of the divider to dashed.
74    pub fn dashed(mut self) -> Self {
75        self.line_style = DividerStyle::Dashed;
76        self
77    }
78
79    fn render_base(axis: Axis) -> Div {
80        div().absolute().map(|this| match axis {
81            Axis::Vertical => this.w(px(1.)).h_full(),
82            Axis::Horizontal => this.h(px(1.)).w_full(),
83        })
84    }
85
86    fn render_solid(axis: Axis, color: Hsla) -> impl IntoElement {
87        Self::render_base(axis).bg(color)
88    }
89
90    fn render_dashed(axis: Axis, color: Hsla) -> impl IntoElement {
91        Self::render_base(axis).child(
92            canvas(
93                move |_, _, _| {},
94                move |bounds, _, window, _| {
95                    let mut builder = PathBuilder::stroke(px(1.)).dash_array(&[px(4.), px(2.)]);
96                    let (start, end) = match axis {
97                        Axis::Horizontal => {
98                            let x = bounds.origin.x;
99                            let y = bounds.origin.y + px(0.5);
100                            (point(x, y), point(x + bounds.size.width, y))
101                        }
102                        Axis::Vertical => {
103                            let x = bounds.origin.x + px(0.5);
104                            let y = bounds.origin.y;
105                            (point(x, y), point(x, y + bounds.size.height))
106                        }
107                    };
108                    builder.move_to(start);
109                    builder.line_to(end);
110                    if let Ok(line) = builder.build() {
111                        window.paint_path(line, color);
112                    }
113                },
114            )
115            .size_full(),
116        )
117    }
118}
119
120impl Styled for Divider {
121    fn style(&mut self) -> &mut StyleRefinement {
122        &mut self.style
123    }
124}
125
126impl RenderOnce for Divider {
127    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
128        let color = self.color.unwrap_or(cx.theme().border);
129        let axis = self.axis;
130        let line_style = self.line_style;
131
132        self.base
133            .flex()
134            .flex_shrink_0()
135            .items_center()
136            .justify_center()
137            .refine_style(&self.style)
138            .child(match line_style {
139                DividerStyle::Solid => Self::render_solid(axis, color).into_any_element(),
140                DividerStyle::Dashed => Self::render_dashed(axis, color).into_any_element(),
141            })
142            .when_some(self.label, |this, label| {
143                this.child(
144                    div()
145                        .px_2()
146                        .py_1()
147                        .mx_auto()
148                        .text_xs()
149                        .bg(cx.theme().background)
150                        .text_color(cx.theme().muted_foreground)
151                        .child(label),
152                )
153            })
154    }
155}