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