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