1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
7pub enum FlexDirection {
8 #[default]
10 Row,
11 RowReverse,
13 Column,
15 ColumnReverse,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
21pub enum FlexJustify {
22 #[default]
24 Start,
25 End,
27 Center,
29 SpaceBetween,
31 SpaceAround,
33 SpaceEvenly,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
39pub enum FlexAlign {
40 Start,
42 End,
44 #[default]
46 Center,
47 Stretch,
49 Baseline,
51}
52
53#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
55pub struct FlexItem {
56 pub grow: f32,
58 pub shrink: f32,
60 pub basis: Option<f32>,
62 pub align_self: Option<FlexAlign>,
64 pub collapse_if_empty: bool,
67}
68
69impl FlexItem {
70 #[must_use]
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 #[must_use]
78 pub const fn grow(mut self, grow: f32) -> Self {
79 self.grow = grow;
80 self
81 }
82
83 #[must_use]
85 pub const fn shrink(mut self, shrink: f32) -> Self {
86 self.shrink = shrink;
87 self
88 }
89
90 #[must_use]
92 pub const fn basis(mut self, basis: f32) -> Self {
93 self.basis = Some(basis);
94 self
95 }
96
97 #[must_use]
99 pub const fn align_self(mut self, align: FlexAlign) -> Self {
100 self.align_self = Some(align);
101 self
102 }
103
104 #[must_use]
106 pub const fn collapse_if_empty(mut self) -> Self {
107 self.collapse_if_empty = true;
108 self
109 }
110}
111
112#[must_use]
115#[allow(dead_code)]
116pub(crate) fn distribute_flex(items: &[FlexItem], sizes: &[f32], available: f32) -> Vec<f32> {
117 if items.is_empty() {
118 return Vec::new();
119 }
120
121 let collapsed: Vec<bool> = items
123 .iter()
124 .zip(sizes.iter())
125 .map(|(item, &size)| item.collapse_if_empty && size == 0.0)
126 .collect();
127
128 let total_size: f32 = sizes
130 .iter()
131 .zip(collapsed.iter())
132 .filter(|(_, &is_collapsed)| !is_collapsed)
133 .map(|(&s, _)| s)
134 .sum();
135
136 let remaining = available - total_size;
137
138 if remaining.abs() < 0.001 {
139 return sizes.to_vec();
140 }
141
142 if remaining > 0.0 {
143 let total_grow: f32 = items
145 .iter()
146 .zip(collapsed.iter())
147 .filter(|(_, &is_collapsed)| !is_collapsed)
148 .map(|(i, _)| i.grow)
149 .sum();
150
151 if total_grow > 0.0 {
152 return sizes
153 .iter()
154 .zip(items.iter())
155 .zip(collapsed.iter())
156 .map(|((&size, item), &is_collapsed)| {
157 if is_collapsed {
158 0.0
159 } else {
160 size + (remaining * item.grow / total_grow)
161 }
162 })
163 .collect();
164 }
165 } else {
166 let total_shrink: f32 = items
168 .iter()
169 .zip(collapsed.iter())
170 .filter(|(_, &is_collapsed)| !is_collapsed)
171 .map(|(i, _)| i.shrink)
172 .sum();
173
174 if total_shrink > 0.0 {
175 return sizes
176 .iter()
177 .zip(items.iter())
178 .zip(collapsed.iter())
179 .map(|((&size, item), &is_collapsed)| {
180 if is_collapsed {
181 0.0
182 } else {
183 (size + (remaining * item.shrink / total_shrink)).max(0.0)
184 }
185 })
186 .collect();
187 }
188 }
189
190 sizes
192 .iter()
193 .zip(collapsed.iter())
194 .map(|(&size, &is_collapsed)| if is_collapsed { 0.0 } else { size })
195 .collect()
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_flex_direction_default() {
204 assert_eq!(FlexDirection::default(), FlexDirection::Row);
205 }
206
207 #[test]
208 fn test_flex_justify_default() {
209 assert_eq!(FlexJustify::default(), FlexJustify::Start);
210 }
211
212 #[test]
213 fn test_flex_align_default() {
214 assert_eq!(FlexAlign::default(), FlexAlign::Center);
215 }
216
217 #[test]
218 fn test_flex_item_builder() {
219 let item = FlexItem::new()
220 .grow(1.0)
221 .shrink(0.0)
222 .basis(100.0)
223 .align_self(FlexAlign::Start);
224
225 assert_eq!(item.grow, 1.0);
226 assert_eq!(item.shrink, 0.0);
227 assert_eq!(item.basis, Some(100.0));
228 assert_eq!(item.align_self, Some(FlexAlign::Start));
229 }
230
231 #[test]
232 fn test_distribute_flex_empty() {
233 let result = distribute_flex(&[], &[], 100.0);
234 assert!(result.is_empty());
235 }
236
237 #[test]
238 fn test_distribute_flex_exact_fit() {
239 let items = vec![FlexItem::new(), FlexItem::new()];
240 let sizes = vec![50.0, 50.0];
241 let result = distribute_flex(&items, &sizes, 100.0);
242 assert_eq!(result, vec![50.0, 50.0]);
243 }
244
245 #[test]
246 fn test_distribute_flex_grow() {
247 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
248 let sizes = vec![25.0, 25.0];
249 let result = distribute_flex(&items, &sizes, 100.0);
250 assert_eq!(result, vec![50.0, 50.0]);
251 }
252
253 #[test]
254 fn test_distribute_flex_grow_uneven() {
255 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(3.0)];
256 let sizes = vec![0.0, 0.0];
257 let result = distribute_flex(&items, &sizes, 100.0);
258 assert_eq!(result, vec![25.0, 75.0]);
259 }
260
261 #[test]
262 fn test_distribute_flex_shrink() {
263 let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(1.0)];
264 let sizes = vec![75.0, 75.0];
265 let result = distribute_flex(&items, &sizes, 100.0);
266 assert_eq!(result, vec![50.0, 50.0]);
267 }
268
269 #[test]
274 fn test_flex_direction_clone() {
275 let dir = FlexDirection::Column;
276 let cloned = dir;
277 assert_eq!(dir, cloned);
278 }
279
280 #[test]
281 fn test_flex_direction_all_variants() {
282 assert_eq!(FlexDirection::Row, FlexDirection::Row);
283 assert_eq!(FlexDirection::RowReverse, FlexDirection::RowReverse);
284 assert_eq!(FlexDirection::Column, FlexDirection::Column);
285 assert_eq!(FlexDirection::ColumnReverse, FlexDirection::ColumnReverse);
286 }
287
288 #[test]
289 fn test_flex_direction_debug() {
290 let dir = FlexDirection::Row;
291 let debug = format!("{:?}", dir);
292 assert!(debug.contains("Row"));
293 }
294
295 #[test]
300 fn test_flex_justify_all_variants() {
301 assert_eq!(FlexJustify::Start, FlexJustify::Start);
302 assert_eq!(FlexJustify::End, FlexJustify::End);
303 assert_eq!(FlexJustify::Center, FlexJustify::Center);
304 assert_eq!(FlexJustify::SpaceBetween, FlexJustify::SpaceBetween);
305 assert_eq!(FlexJustify::SpaceAround, FlexJustify::SpaceAround);
306 assert_eq!(FlexJustify::SpaceEvenly, FlexJustify::SpaceEvenly);
307 }
308
309 #[test]
310 fn test_flex_justify_clone() {
311 let justify = FlexJustify::SpaceBetween;
312 let cloned = justify;
313 assert_eq!(justify, cloned);
314 }
315
316 #[test]
317 fn test_flex_justify_debug() {
318 let justify = FlexJustify::Center;
319 let debug = format!("{:?}", justify);
320 assert!(debug.contains("Center"));
321 }
322
323 #[test]
328 fn test_flex_align_all_variants() {
329 assert_eq!(FlexAlign::Start, FlexAlign::Start);
330 assert_eq!(FlexAlign::End, FlexAlign::End);
331 assert_eq!(FlexAlign::Center, FlexAlign::Center);
332 assert_eq!(FlexAlign::Stretch, FlexAlign::Stretch);
333 assert_eq!(FlexAlign::Baseline, FlexAlign::Baseline);
334 }
335
336 #[test]
337 fn test_flex_align_clone() {
338 let align = FlexAlign::Stretch;
339 let cloned = align;
340 assert_eq!(align, cloned);
341 }
342
343 #[test]
344 fn test_flex_align_debug() {
345 let align = FlexAlign::Baseline;
346 let debug = format!("{:?}", align);
347 assert!(debug.contains("Baseline"));
348 }
349
350 #[test]
355 fn test_flex_item_default() {
356 let item = FlexItem::default();
357 assert_eq!(item.grow, 0.0);
358 assert_eq!(item.shrink, 0.0);
359 assert_eq!(item.basis, None);
360 assert_eq!(item.align_self, None);
361 }
362
363 #[test]
364 fn test_flex_item_new() {
365 let item = FlexItem::new();
366 assert_eq!(item.grow, 0.0);
367 assert_eq!(item.shrink, 0.0);
368 }
369
370 #[test]
371 fn test_flex_item_grow_only() {
372 let item = FlexItem::new().grow(2.5);
373 assert_eq!(item.grow, 2.5);
374 assert_eq!(item.shrink, 0.0);
375 }
376
377 #[test]
378 fn test_flex_item_shrink_only() {
379 let item = FlexItem::new().shrink(0.5);
380 assert_eq!(item.shrink, 0.5);
381 assert_eq!(item.grow, 0.0);
382 }
383
384 #[test]
385 fn test_flex_item_basis_only() {
386 let item = FlexItem::new().basis(200.0);
387 assert_eq!(item.basis, Some(200.0));
388 }
389
390 #[test]
391 fn test_flex_item_align_self_only() {
392 let item = FlexItem::new().align_self(FlexAlign::End);
393 assert_eq!(item.align_self, Some(FlexAlign::End));
394 }
395
396 #[test]
397 fn test_flex_item_clone() {
398 let item = FlexItem::new().grow(1.0).shrink(0.5);
399 let cloned = item;
400 assert_eq!(item.grow, cloned.grow);
401 assert_eq!(item.shrink, cloned.shrink);
402 }
403
404 #[test]
405 fn test_flex_item_debug() {
406 let item = FlexItem::new().grow(1.0);
407 let debug = format!("{:?}", item);
408 assert!(debug.contains("FlexItem"));
409 }
410
411 #[test]
416 fn test_distribute_flex_no_grow_no_shrink() {
417 let items = vec![FlexItem::new(), FlexItem::new()];
418 let sizes = vec![30.0, 30.0];
419 let result = distribute_flex(&items, &sizes, 100.0);
420 assert_eq!(result, vec![30.0, 30.0]);
422 }
423
424 #[test]
425 fn test_distribute_flex_single_item_grow() {
426 let items = vec![FlexItem::new().grow(1.0)];
427 let sizes = vec![50.0];
428 let result = distribute_flex(&items, &sizes, 100.0);
429 assert_eq!(result, vec![100.0]);
430 }
431
432 #[test]
433 fn test_distribute_flex_single_item_shrink() {
434 let items = vec![FlexItem::new().shrink(1.0)];
435 let sizes = vec![150.0];
436 let result = distribute_flex(&items, &sizes, 100.0);
437 assert_eq!(result, vec![100.0]);
438 }
439
440 #[test]
441 fn test_distribute_flex_shrink_uneven() {
442 let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(3.0)];
443 let sizes = vec![100.0, 100.0];
444 let result = distribute_flex(&items, &sizes, 100.0);
445 assert_eq!(result, vec![75.0, 25.0]);
449 }
450
451 #[test]
452 fn test_distribute_flex_shrink_to_zero() {
453 let items = vec![FlexItem::new().shrink(1.0)];
454 let sizes = vec![50.0];
455 let result = distribute_flex(&items, &sizes, 0.0);
457 assert_eq!(result, vec![0.0]); }
459
460 #[test]
461 fn test_distribute_flex_mixed_grow() {
462 let items = vec![
463 FlexItem::new().grow(0.0), FlexItem::new().grow(1.0), ];
466 let sizes = vec![50.0, 0.0];
467 let result = distribute_flex(&items, &sizes, 100.0);
468 assert_eq!(result, vec![50.0, 50.0]);
469 }
470
471 #[test]
472 fn test_distribute_flex_three_items() {
473 let items = vec![
474 FlexItem::new().grow(1.0),
475 FlexItem::new().grow(2.0),
476 FlexItem::new().grow(1.0),
477 ];
478 let sizes = vec![0.0, 0.0, 0.0];
479 let result = distribute_flex(&items, &sizes, 100.0);
480 assert_eq!(result, vec![25.0, 50.0, 25.0]);
481 }
482
483 #[test]
484 fn test_distribute_flex_near_exact_fit() {
485 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
486 let sizes = vec![49.9995, 50.0005];
487 let result = distribute_flex(&items, &sizes, 100.0);
488 assert_eq!(result, vec![49.9995, 50.0005]);
490 }
491
492 #[test]
497 fn test_flex_item_collapse_if_empty() {
498 let item = FlexItem::new().collapse_if_empty();
499 assert!(item.collapse_if_empty);
500 }
501
502 #[test]
503 fn test_flex_item_collapse_if_empty_default_false() {
504 let item = FlexItem::new();
505 assert!(!item.collapse_if_empty);
506 }
507
508 #[test]
509 fn test_distribute_flex_collapsed_item_stays_zero() {
510 let items = vec![
511 FlexItem::new().grow(1.0).collapse_if_empty(),
512 FlexItem::new().grow(1.0),
513 ];
514 let sizes = vec![0.0, 50.0]; let result = distribute_flex(&items, &sizes, 100.0);
516 assert_eq!(result, vec![0.0, 100.0]);
518 }
519
520 #[test]
521 fn test_distribute_flex_collapsed_doesnt_participate_in_grow() {
522 let items = vec![
523 FlexItem::new().grow(1.0).collapse_if_empty(),
524 FlexItem::new().grow(1.0),
525 FlexItem::new().grow(1.0),
526 ];
527 let sizes = vec![0.0, 25.0, 25.0]; let result = distribute_flex(&items, &sizes, 100.0);
529 assert_eq!(result, vec![0.0, 50.0, 50.0]);
531 }
532
533 #[test]
534 fn test_distribute_flex_collapsed_with_size_not_collapsed() {
535 let items = vec![
537 FlexItem::new().grow(1.0).collapse_if_empty(),
538 FlexItem::new().grow(1.0),
539 ];
540 let sizes = vec![30.0, 30.0]; let result = distribute_flex(&items, &sizes, 100.0);
542 assert_eq!(result, vec![50.0, 50.0]);
544 }
545
546 #[test]
547 fn test_distribute_flex_all_collapsed() {
548 let items = vec![
549 FlexItem::new().grow(1.0).collapse_if_empty(),
550 FlexItem::new().grow(1.0).collapse_if_empty(),
551 ];
552 let sizes = vec![0.0, 0.0]; let result = distribute_flex(&items, &sizes, 100.0);
554 assert_eq!(result, vec![0.0, 0.0]);
556 }
557
558 #[test]
559 fn test_distribute_flex_collapsed_in_shrink() {
560 let items = vec![
561 FlexItem::new().shrink(1.0).collapse_if_empty(),
562 FlexItem::new().shrink(1.0),
563 ];
564 let sizes = vec![0.0, 120.0]; let result = distribute_flex(&items, &sizes, 100.0);
566 assert_eq!(result, vec![0.0, 100.0]);
568 }
569}