1use gpui::{
2 AnyElement, App, Component, ElementId, Hsla, IntoElement, Pixels, RenderOnce, ScrollHandle,
3 Window, div, prelude::*, px,
4};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7enum FlexDirection {
8 Row,
9 Column,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13enum CrossAlign {
14 Start,
15 Center,
16 End,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20enum MainAlign {
21 Start,
22 Center,
23 End,
24 Between,
25}
26
27pub struct Flex {
28 children: Vec<AnyElement>,
29 direction: Option<FlexDirection>,
30 wrap: bool,
31 gap: Option<Pixels>,
32 padding: Option<Pixels>,
33 padding_x: Option<Pixels>,
34 padding_y: Option<Pixels>,
35 margin_y: Option<Pixels>,
36 height: Option<Pixels>,
37 width: Option<Pixels>,
38 width_percent: Option<f32>,
39 h_full: bool,
40 w_full: bool,
41 size_full: bool,
42 flex_1: bool,
43 flex_none: bool,
44 min_h_0: bool,
45 bg: Option<Hsla>,
46 text_color: Option<Hsla>,
47 text_size: Option<Pixels>,
48 bold: bool,
49 border: bool,
50 border_color: Option<Hsla>,
51 rounded: Option<Pixels>,
52 relative: bool,
53 overflow_hidden: bool,
54 overflow_y_scroll: bool,
55 id: Option<ElementId>,
56 scroll_handle: Option<ScrollHandle>,
57 align: Option<CrossAlign>,
58 justify: Option<MainAlign>,
59}
60
61impl Flex {
62 pub fn new() -> Self {
63 Self {
64 children: Vec::new(),
65 direction: None,
66 wrap: false,
67 gap: None,
68 padding: None,
69 padding_x: None,
70 padding_y: None,
71 margin_y: None,
72 height: None,
73 width: None,
74 width_percent: None,
75 h_full: false,
76 w_full: false,
77 size_full: false,
78 flex_1: false,
79 flex_none: false,
80 min_h_0: false,
81 bg: None,
82 text_color: None,
83 text_size: None,
84 bold: false,
85 border: false,
86 border_color: None,
87 rounded: None,
88 relative: false,
89 overflow_hidden: false,
90 overflow_y_scroll: false,
91 id: None,
92 scroll_handle: None,
93 align: None,
94 justify: None,
95 }
96 }
97
98 pub fn row(mut self) -> Self {
99 self.direction = Some(FlexDirection::Row);
100 self
101 }
102
103 pub fn column(mut self) -> Self {
104 self.direction = Some(FlexDirection::Column);
105 self
106 }
107
108 pub fn wrap(mut self) -> Self {
109 self.wrap = true;
110 self
111 }
112
113 pub fn gap_px(mut self, gap: f32) -> Self {
114 self.gap = Some(px(gap));
115 self
116 }
117
118 pub fn gap_sm(self) -> Self {
119 self.gap_px(8.0)
120 }
121
122 pub fn gap_md(self) -> Self {
123 self.gap_px(12.0)
124 }
125
126 pub fn gap_lg(self) -> Self {
127 self.gap_px(16.0)
128 }
129
130 pub fn gap_xl(self) -> Self {
131 self.gap_px(24.0)
132 }
133
134 pub fn padding_px(mut self, padding: f32) -> Self {
135 self.padding = Some(px(padding));
136 self
137 }
138
139 pub fn padding_sm(self) -> Self {
140 self.padding_px(8.0)
141 }
142
143 pub fn padding_md(self) -> Self {
144 self.padding_px(16.0)
145 }
146
147 pub fn padding_lg(self) -> Self {
148 self.padding_px(24.0)
149 }
150
151 pub fn padding_x_px(mut self, padding: f32) -> Self {
152 self.padding_x = Some(px(padding));
153 self
154 }
155
156 pub fn padding_x_units(self, padding: f32) -> Self {
157 self.padding_x_px(padding)
158 }
159
160 pub fn padding_y_px(mut self, padding: f32) -> Self {
161 self.padding_y = Some(px(padding));
162 self
163 }
164
165 pub fn margin_y_px(mut self, margin: f32) -> Self {
166 self.margin_y = Some(px(margin));
167 self
168 }
169
170 pub fn margin_y_units(self, margin: f32) -> Self {
171 self.margin_y_px(margin)
172 }
173
174 pub fn height_px(mut self, height: f32) -> Self {
175 self.height = Some(px(height));
176 self
177 }
178
179 pub fn height_units(self, height: f32) -> Self {
180 self.height_px(height)
181 }
182
183 pub fn width_px(mut self, width: f32) -> Self {
184 self.width = Some(px(width));
185 self
186 }
187
188 pub fn width_percent(mut self, percent: f32) -> Self {
189 self.width_percent = Some((percent / 100.0).clamp(0.0, 1.0));
190 self
191 }
192
193 pub fn h_full(mut self) -> Self {
194 self.h_full = true;
195 self
196 }
197
198 pub fn w_full(mut self) -> Self {
199 self.w_full = true;
200 self
201 }
202
203 pub fn size_full(mut self) -> Self {
204 self.size_full = true;
205 self
206 }
207
208 pub fn flex_1(mut self) -> Self {
209 self.flex_1 = true;
210 self
211 }
212
213 pub fn flex_none(mut self) -> Self {
214 self.flex_none = true;
215 self
216 }
217
218 pub fn min_h_0(mut self) -> Self {
219 self.min_h_0 = true;
220 self
221 }
222
223 pub fn bg(mut self, color: Hsla) -> Self {
224 self.bg = Some(color);
225 self
226 }
227
228 pub fn text_color(mut self, color: Hsla) -> Self {
229 self.text_color = Some(color);
230 self
231 }
232
233 pub fn text_size_px(mut self, size: f32) -> Self {
234 self.text_size = Some(px(size));
235 self
236 }
237
238 pub fn text_xs(self) -> Self {
239 self.text_size_px(12.0)
240 }
241
242 pub fn text_sm(self) -> Self {
243 self.text_size_px(14.0)
244 }
245
246 pub fn bold(mut self) -> Self {
247 self.bold = true;
248 self
249 }
250
251 pub fn border(mut self) -> Self {
252 self.border = true;
253 self
254 }
255
256 pub fn border_color(mut self, color: Hsla) -> Self {
257 self.border_color = Some(color);
258 self
259 }
260
261 pub fn rounded_px(mut self, radius: f32) -> Self {
262 self.rounded = Some(px(radius));
263 self
264 }
265
266 pub fn rounded_units(self, radius: f32) -> Self {
267 self.rounded_px(radius)
268 }
269
270 pub fn rounded_md(mut self) -> Self {
271 self.rounded = Some(px(8.0));
272 self
273 }
274
275 pub fn rounded_pill(mut self) -> Self {
276 self.rounded = Some(px(999.0));
277 self
278 }
279
280 pub fn relative(mut self) -> Self {
281 self.relative = true;
282 self
283 }
284
285 pub fn overflow_hidden(mut self) -> Self {
286 self.overflow_hidden = true;
287 self
288 }
289
290 pub fn overflow_y_scroll(mut self) -> Self {
291 self.overflow_y_scroll = true;
292 self
293 }
294
295 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
296 self.id = Some(id.into());
297 self
298 }
299
300 pub fn track_scroll(mut self, handle: &ScrollHandle) -> Self {
301 self.scroll_handle = Some(handle.clone());
302 self
303 }
304
305 pub fn align_start(mut self) -> Self {
306 self.align = Some(CrossAlign::Start);
307 self
308 }
309
310 pub fn align_center(mut self) -> Self {
311 self.align = Some(CrossAlign::Center);
312 self
313 }
314
315 pub fn align_end(mut self) -> Self {
316 self.align = Some(CrossAlign::End);
317 self
318 }
319
320 pub fn justify_start(mut self) -> Self {
321 self.justify = Some(MainAlign::Start);
322 self
323 }
324
325 pub fn justify_center(mut self) -> Self {
326 self.justify = Some(MainAlign::Center);
327 self
328 }
329
330 pub fn justify_end(mut self) -> Self {
331 self.justify = Some(MainAlign::End);
332 self
333 }
334
335 pub fn justify_between(mut self) -> Self {
336 self.justify = Some(MainAlign::Between);
337 self
338 }
339
340 pub fn center(self) -> Self {
341 self.align_center().justify_center()
342 }
343
344 pub fn child(mut self, child: impl IntoElement) -> Self {
345 self.children.push(child.into_any_element());
346 self
347 }
348
349 pub fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
350 self.children
351 .extend(children.into_iter().map(|child| child.into_any_element()));
352 self
353 }
354}
355
356impl RenderOnce for Flex {
357 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
358 let mut el = div();
359
360 if self.direction.is_some()
361 || self.align.is_some()
362 || self.justify.is_some()
363 || self.gap.is_some()
364 {
365 el = el.flex();
366 }
367
368 match self.direction {
369 Some(FlexDirection::Row) => el = el.flex_row(),
370 Some(FlexDirection::Column) => el = el.flex_col(),
371 None => {}
372 }
373
374 if self.wrap {
375 el = el.flex_wrap();
376 }
377 if let Some(gap) = self.gap {
378 el = el.gap(gap);
379 }
380 if let Some(padding) = self.padding {
381 el = el.p(padding);
382 }
383 if let Some(padding_x) = self.padding_x {
384 el = el.px(padding_x);
385 }
386 if let Some(padding_y) = self.padding_y {
387 el = el.py(padding_y);
388 }
389 if let Some(margin_y) = self.margin_y {
390 el = el.my(margin_y);
391 }
392 if let Some(height) = self.height {
393 el = el.h(height);
394 }
395 if let Some(width) = self.width {
396 el = el.w(width);
397 }
398 if let Some(width_percent) = self.width_percent {
399 el = el.w(gpui::relative(width_percent));
400 }
401 if self.h_full {
402 el = el.h_full();
403 }
404 if self.w_full {
405 el = el.w_full();
406 }
407 if self.size_full {
408 el = el.size_full();
409 }
410 if self.flex_1 {
411 el = el.flex_1();
412 }
413 if self.flex_none {
414 el = el.flex_none();
415 }
416 if self.min_h_0 {
417 el = el.min_h_0();
418 }
419 if let Some(bg) = self.bg {
420 el = el.bg(bg);
421 }
422 if let Some(color) = self.text_color {
423 el = el.text_color(color);
424 }
425 if let Some(size) = self.text_size {
426 el = el.text_size(size);
427 }
428 if self.bold {
429 el = el.font_weight(gpui::FontWeight::BOLD);
430 }
431 if self.border {
432 el = el.border_1();
433 }
434 if let Some(color) = self.border_color {
435 el = el.border_color(color);
436 }
437 if let Some(radius) = self.rounded {
438 el = el.rounded(radius);
439 }
440 if self.relative {
441 el = el.relative();
442 }
443 if self.overflow_hidden {
444 el = el.overflow_hidden();
445 }
446
447 match self.align {
448 Some(CrossAlign::Start) => el = el.items_start(),
449 Some(CrossAlign::Center) => el = el.items_center(),
450 Some(CrossAlign::End) => el = el.items_end(),
451 None => {}
452 }
453 match self.justify {
454 Some(MainAlign::Start) => el = el.justify_start(),
455 Some(MainAlign::Center) => el = el.justify_center(),
456 Some(MainAlign::End) => el = el.justify_end(),
457 Some(MainAlign::Between) => el = el.justify_between(),
458 None => {}
459 }
460
461 if let Some(id) = self.id {
462 let mut stateful = el.id(id);
463 if self.overflow_y_scroll {
464 stateful = stateful.overflow_y_scroll();
465 }
466 if let Some(scroll_handle) = self.scroll_handle {
467 stateful = stateful.track_scroll(&scroll_handle);
468 }
469 stateful.children(self.children).into_any_element()
470 } else {
471 el.children(self.children).into_any_element()
472 }
473 }
474}
475
476impl IntoElement for Flex {
477 type Element = Component<Self>;
478 fn into_element(self) -> Self::Element {
479 Component::new(self)
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn flex_tracks_scroll_container_configuration() {
489 let handle = ScrollHandle::new();
490 let flex = Flex::new()
491 .column()
492 .height_px(320.0)
493 .id("test-scroll")
494 .overflow_y_scroll()
495 .track_scroll(&handle);
496
497 assert_eq!(flex.direction, Some(FlexDirection::Column));
498 assert_eq!(flex.height, Some(px(320.0)));
499 assert!(flex.overflow_y_scroll);
500 assert!(flex.scroll_handle.is_some());
501 }
502
503 #[test]
504 fn flex_tracks_visual_box_configuration() {
505 let flex = Flex::new()
506 .row()
507 .wrap()
508 .gap_lg()
509 .padding_md()
510 .width_percent(75.0)
511 .rounded_md()
512 .border();
513
514 assert_eq!(flex.direction, Some(FlexDirection::Row));
515 assert!(flex.wrap);
516 assert_eq!(flex.gap, Some(px(16.0)));
517 assert_eq!(flex.padding, Some(px(16.0)));
518 assert_eq!(flex.width_percent, Some(0.75));
519 assert_eq!(flex.rounded, Some(px(8.0)));
520 assert!(flex.border);
521 }
522}