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 label: None,
12 }
13}
14
15pub fn vertical_divider() -> Divider {
16 Divider {
17 style: DividerStyle::Solid,
18 direction: DividerDirection::Vertical,
19 color: DividerColor::default(),
20 inset: false,
21 label: None,
22 }
23}
24
25#[derive(Clone, Copy, PartialEq)]
26enum DividerStyle {
27 Solid,
28 Dashed,
29}
30
31#[derive(Clone, Copy, PartialEq)]
32enum DividerDirection {
33 Horizontal,
34 Vertical,
35}
36
37#[derive(Default)]
39pub enum DividerColor {
40 Border,
41 BorderFaded,
42 #[default]
43 BorderVariant,
44}
45
46impl DividerColor {
47 pub fn hsla(self, cx: &mut App) -> Hsla {
48 match self {
49 DividerColor::Border => semantic::border(cx),
50 DividerColor::BorderFaded => semantic::border(cx).opacity(0.6),
51 DividerColor::BorderVariant => semantic::border_muted(cx),
52 }
53 }
54}
55
56#[derive(IntoElement, RegisterComponent)]
57pub struct Divider {
58 style: DividerStyle,
59 direction: DividerDirection,
60 color: DividerColor,
61 inset: bool,
62 label: Option<SharedString>,
63}
64
65impl Divider {
66 pub fn horizontal() -> Self {
67 Self {
68 style: DividerStyle::Solid,
69 direction: DividerDirection::Horizontal,
70 color: DividerColor::default(),
71 inset: false,
72 label: None,
73 }
74 }
75
76 pub fn vertical() -> Self {
77 Self {
78 style: DividerStyle::Solid,
79 direction: DividerDirection::Vertical,
80 color: DividerColor::default(),
81 inset: false,
82 label: None,
83 }
84 }
85
86 pub fn horizontal_dashed() -> Self {
87 Self {
88 style: DividerStyle::Dashed,
89 direction: DividerDirection::Horizontal,
90 color: DividerColor::default(),
91 inset: false,
92 label: None,
93 }
94 }
95
96 pub fn vertical_dashed() -> Self {
97 Self {
98 style: DividerStyle::Dashed,
99 direction: DividerDirection::Vertical,
100 color: DividerColor::default(),
101 inset: false,
102 label: None,
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 label(mut self, label: impl Into<SharedString>) -> Self {
119 self.label = Some(label.into());
120 self
121 }
122
123 pub fn render_solid(self, base: Div, cx: &mut App) -> impl IntoElement {
124 base.bg(self.color.hsla(cx))
125 }
126
127 pub fn render_dashed(self, base: Div) -> impl IntoElement {
128 base.relative().child(
129 canvas(
130 |_, _, _| {},
131 move |bounds, _, window, cx| {
132 let mut builder = PathBuilder::stroke(px(1.)).dash_array(&[px(4.), px(2.)]);
133 let (start, end) = match self.direction {
134 DividerDirection::Horizontal => {
135 let x = bounds.origin.x;
136 let y = bounds.origin.y + px(0.5);
137 (point(x, y), point(x + bounds.size.width, y))
138 }
139 DividerDirection::Vertical => {
140 let x = bounds.origin.x + px(0.5);
141 let y = bounds.origin.y;
142 (point(x, y), point(x, y + bounds.size.height))
143 }
144 };
145 builder.move_to(start);
146 builder.line_to(end);
147 if let Ok(line) = builder.build() {
148 window.paint_path(line, self.color.hsla(cx));
149 }
150 },
151 )
152 .absolute()
153 .size_full(),
154 )
155 }
156}
157
158impl RenderOnce for Divider {
159 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
160 if self.direction == DividerDirection::Horizontal {
161 if let Some(label) = self.label.clone() {
162 let line_color = self.color.hsla(cx);
163 return h_flex()
164 .w_full()
165 .items_center()
166 .gap_2()
167 .child(div().h_px().flex_1().bg(line_color))
168 .child(
169 Label::new(label)
170 .size(LabelSize::XSmall)
171 .color(Color::Muted),
172 )
173 .child(div().h_px().flex_1().bg(line_color))
174 .into_any_element();
175 }
176 }
177
178 let base = match self.direction {
179 DividerDirection::Horizontal => div()
180 .min_w_0()
181 .h_px()
182 .w_full()
183 .when(self.inset, |this| this.mx_1p5()),
184 DividerDirection::Vertical => div()
185 .min_w_0()
186 .w_px()
187 .h_full()
188 .when(self.inset, |this| this.my_1p5()),
189 };
190
191 match self.style {
192 DividerStyle::Solid => self.render_solid(base, cx).into_any_element(),
193 DividerStyle::Dashed => self.render_dashed(base).into_any_element(),
194 }
195 }
196}
197
198impl Component for Divider {
199 fn scope() -> ComponentScope {
200 ComponentScope::Layout
201 }
202
203 fn description() -> Option<&'static str> {
204 Some(
205 "Visual separator used to create divisions between groups of content or sections in a layout.",
206 )
207 }
208
209 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
210 Some(
211 v_flex()
212 .gap_6()
213 .children(vec![
214 example_group_with_title(
215 "Horizontal Dividers",
216 vec![
217 single_example("Default", Divider::horizontal().into_any_element()),
218 single_example(
219 "Border Color",
220 Divider::horizontal()
221 .color(DividerColor::Border)
222 .into_any_element(),
223 ),
224 single_example(
225 "Inset",
226 Divider::horizontal().inset().into_any_element(),
227 ),
228 single_example(
229 "Dashed",
230 Divider::horizontal_dashed().into_any_element(),
231 ),
232 single_example(
233 "Labeled",
234 Divider::horizontal().label("OR").into_any_element(),
235 ),
236 ],
237 ),
238 example_group_with_title(
239 "Vertical Dividers",
240 vec![
241 single_example(
242 "Default",
243 div().h_16().child(Divider::vertical()).into_any_element(),
244 ),
245 single_example(
246 "Border Color",
247 div()
248 .h_16()
249 .child(Divider::vertical().color(DividerColor::Border))
250 .into_any_element(),
251 ),
252 single_example(
253 "Inset",
254 div()
255 .h_16()
256 .child(Divider::vertical().inset())
257 .into_any_element(),
258 ),
259 single_example(
260 "Dashed",
261 div()
262 .h_16()
263 .child(Divider::vertical_dashed())
264 .into_any_element(),
265 ),
266 ],
267 ),
268 example_group_with_title(
269 "Example Usage",
270 vec![single_example(
271 "Between Content",
272 v_flex()
273 .w_full()
274 .gap_4()
275 .px_4()
276 .child(Label::new("Section One"))
277 .child(Divider::horizontal())
278 .child(Label::new("Section Two"))
279 .child(Divider::horizontal_dashed())
280 .child(Label::new("Section Three"))
281 .child(Divider::horizontal().label("End"))
282 .into_any_element(),
283 )],
284 ),
285 ])
286 .into_any_element(),
287 )
288 }
289}