gpui_component/
divider.rs1use 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#[derive(Clone, Copy, PartialEq, Default)]
9pub enum DividerStyle {
10 #[default]
11 Solid,
12 Dashed,
13}
14
15#[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 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 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 pub fn vertical_dashed() -> Self {
53 Self::vertical().dashed()
54 }
55
56 pub fn horizontal_dashed() -> Self {
58 Self::horizontal().dashed()
59 }
60
61 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
63 self.label = Some(label.into());
64 self
65 }
66
67 pub fn color(mut self, color: impl Into<Hsla>) -> Self {
69 self.color = Some(color.into());
70 self
71 }
72
73 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}