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}