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
112fn compute_collapsed(items: &[FlexItem], sizes: &[f32]) -> Vec<bool> {
116 items
117 .iter()
118 .zip(sizes.iter())
119 .map(|(item, &size)| item.collapse_if_empty && size == 0.0)
120 .collect()
121}
122
123fn sum_flex_factor(
125 items: &[FlexItem],
126 collapsed: &[bool],
127 get_factor: fn(&FlexItem) -> f32,
128) -> f32 {
129 items
130 .iter()
131 .zip(collapsed.iter())
132 .filter(|(_, &is_collapsed)| !is_collapsed)
133 .map(|(item, _)| get_factor(item))
134 .sum()
135}
136
137fn apply_flex_adjustment(
139 sizes: &[f32],
140 items: &[FlexItem],
141 collapsed: &[bool],
142 remaining: f32,
143 total_factor: f32,
144 get_factor: fn(&FlexItem) -> f32,
145 clamp: bool,
146) -> Vec<f32> {
147 sizes
148 .iter()
149 .zip(items.iter())
150 .zip(collapsed.iter())
151 .map(|((&size, item), &is_collapsed)| {
152 if is_collapsed {
153 0.0
154 } else {
155 let adjusted = size + (remaining * get_factor(item) / total_factor);
156 if clamp {
157 adjusted.max(0.0)
158 } else {
159 adjusted
160 }
161 }
162 })
163 .collect()
164}
165
166#[must_use]
167#[allow(dead_code)]
168pub(crate) fn distribute_flex(items: &[FlexItem], sizes: &[f32], available: f32) -> Vec<f32> {
169 if items.is_empty() {
170 return Vec::new();
171 }
172
173 let collapsed = compute_collapsed(items, sizes);
175
176 let total_size: f32 = sizes
177 .iter()
178 .zip(collapsed.iter())
179 .filter(|(_, &is_collapsed)| !is_collapsed)
180 .map(|(&s, _)| s)
181 .sum();
182
183 let remaining = available - total_size;
184
185 if remaining.abs() < 0.001 {
186 return sizes.to_vec();
187 }
188
189 let get_grow: fn(&FlexItem) -> f32 = |i| i.grow;
190 let get_shrink: fn(&FlexItem) -> f32 = |i| i.shrink;
191
192 if remaining > 0.0 {
193 let total_grow = sum_flex_factor(items, &collapsed, get_grow);
194 if total_grow > 0.0 {
195 return apply_flex_adjustment(
196 sizes, items, &collapsed, remaining, total_grow, get_grow, false,
197 );
198 }
199 } else {
200 let total_shrink = sum_flex_factor(items, &collapsed, get_shrink);
201 if total_shrink > 0.0 {
202 return apply_flex_adjustment(
203 sizes,
204 items,
205 &collapsed,
206 remaining,
207 total_shrink,
208 get_shrink,
209 true,
210 );
211 }
212 }
213
214 sizes
216 .iter()
217 .zip(collapsed.iter())
218 .map(|(&size, &is_collapsed)| if is_collapsed { 0.0 } else { size })
219 .collect()
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_flex_direction_default() {
228 assert_eq!(FlexDirection::default(), FlexDirection::Row);
229 }
230
231 #[test]
232 fn test_flex_justify_default() {
233 assert_eq!(FlexJustify::default(), FlexJustify::Start);
234 }
235
236 #[test]
237 fn test_flex_align_default() {
238 assert_eq!(FlexAlign::default(), FlexAlign::Center);
239 }
240
241 #[test]
242 fn test_flex_item_builder() {
243 let item = FlexItem::new()
244 .grow(1.0)
245 .shrink(0.0)
246 .basis(100.0)
247 .align_self(FlexAlign::Start);
248
249 assert_eq!(item.grow, 1.0);
250 assert_eq!(item.shrink, 0.0);
251 assert_eq!(item.basis, Some(100.0));
252 assert_eq!(item.align_self, Some(FlexAlign::Start));
253 }
254
255 #[test]
256 fn test_distribute_flex_empty() {
257 let result = distribute_flex(&[], &[], 100.0);
258 assert!(result.is_empty());
259 }
260
261 #[test]
262 fn test_distribute_flex_exact_fit() {
263 let items = vec![FlexItem::new(), FlexItem::new()];
264 let sizes = vec![50.0, 50.0];
265 let result = distribute_flex(&items, &sizes, 100.0);
266 assert_eq!(result, vec![50.0, 50.0]);
267 }
268
269 #[test]
270 fn test_distribute_flex_grow() {
271 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
272 let sizes = vec![25.0, 25.0];
273 let result = distribute_flex(&items, &sizes, 100.0);
274 assert_eq!(result, vec![50.0, 50.0]);
275 }
276
277 #[test]
278 fn test_distribute_flex_grow_uneven() {
279 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(3.0)];
280 let sizes = vec![0.0, 0.0];
281 let result = distribute_flex(&items, &sizes, 100.0);
282 assert_eq!(result, vec![25.0, 75.0]);
283 }
284
285 #[test]
286 fn test_distribute_flex_shrink() {
287 let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(1.0)];
288 let sizes = vec![75.0, 75.0];
289 let result = distribute_flex(&items, &sizes, 100.0);
290 assert_eq!(result, vec![50.0, 50.0]);
291 }
292
293 #[test]
298 fn test_flex_direction_clone() {
299 let dir = FlexDirection::Column;
300 let cloned = dir;
301 assert_eq!(dir, cloned);
302 }
303
304 #[test]
305 fn test_flex_direction_all_variants() {
306 assert_eq!(FlexDirection::Row, FlexDirection::Row);
307 assert_eq!(FlexDirection::RowReverse, FlexDirection::RowReverse);
308 assert_eq!(FlexDirection::Column, FlexDirection::Column);
309 assert_eq!(FlexDirection::ColumnReverse, FlexDirection::ColumnReverse);
310 }
311
312 #[test]
313 fn test_flex_direction_debug() {
314 let dir = FlexDirection::Row;
315 let debug = format!("{:?}", dir);
316 assert!(debug.contains("Row"));
317 }
318
319 #[test]
324 fn test_flex_justify_all_variants() {
325 assert_eq!(FlexJustify::Start, FlexJustify::Start);
326 assert_eq!(FlexJustify::End, FlexJustify::End);
327 assert_eq!(FlexJustify::Center, FlexJustify::Center);
328 assert_eq!(FlexJustify::SpaceBetween, FlexJustify::SpaceBetween);
329 assert_eq!(FlexJustify::SpaceAround, FlexJustify::SpaceAround);
330 assert_eq!(FlexJustify::SpaceEvenly, FlexJustify::SpaceEvenly);
331 }
332
333 #[test]
334 fn test_flex_justify_clone() {
335 let justify = FlexJustify::SpaceBetween;
336 let cloned = justify;
337 assert_eq!(justify, cloned);
338 }
339
340 #[test]
341 fn test_flex_justify_debug() {
342 let justify = FlexJustify::Center;
343 let debug = format!("{:?}", justify);
344 assert!(debug.contains("Center"));
345 }
346
347 #[test]
352 fn test_flex_align_all_variants() {
353 assert_eq!(FlexAlign::Start, FlexAlign::Start);
354 assert_eq!(FlexAlign::End, FlexAlign::End);
355 assert_eq!(FlexAlign::Center, FlexAlign::Center);
356 assert_eq!(FlexAlign::Stretch, FlexAlign::Stretch);
357 assert_eq!(FlexAlign::Baseline, FlexAlign::Baseline);
358 }
359
360 #[test]
361 fn test_flex_align_clone() {
362 let align = FlexAlign::Stretch;
363 let cloned = align;
364 assert_eq!(align, cloned);
365 }
366
367 #[test]
368 fn test_flex_align_debug() {
369 let align = FlexAlign::Baseline;
370 let debug = format!("{:?}", align);
371 assert!(debug.contains("Baseline"));
372 }
373
374 #[test]
379 fn test_flex_item_default() {
380 let item = FlexItem::default();
381 assert_eq!(item.grow, 0.0);
382 assert_eq!(item.shrink, 0.0);
383 assert_eq!(item.basis, None);
384 assert_eq!(item.align_self, None);
385 }
386
387 #[test]
388 fn test_flex_item_new() {
389 let item = FlexItem::new();
390 assert_eq!(item.grow, 0.0);
391 assert_eq!(item.shrink, 0.0);
392 }
393
394 #[test]
395 fn test_flex_item_grow_only() {
396 let item = FlexItem::new().grow(2.5);
397 assert_eq!(item.grow, 2.5);
398 assert_eq!(item.shrink, 0.0);
399 }
400
401 #[test]
402 fn test_flex_item_shrink_only() {
403 let item = FlexItem::new().shrink(0.5);
404 assert_eq!(item.shrink, 0.5);
405 assert_eq!(item.grow, 0.0);
406 }
407
408 #[test]
409 fn test_flex_item_basis_only() {
410 let item = FlexItem::new().basis(200.0);
411 assert_eq!(item.basis, Some(200.0));
412 }
413
414 #[test]
415 fn test_flex_item_align_self_only() {
416 let item = FlexItem::new().align_self(FlexAlign::End);
417 assert_eq!(item.align_self, Some(FlexAlign::End));
418 }
419
420 #[test]
421 fn test_flex_item_clone() {
422 let item = FlexItem::new().grow(1.0).shrink(0.5);
423 let cloned = item;
424 assert_eq!(item.grow, cloned.grow);
425 assert_eq!(item.shrink, cloned.shrink);
426 }
427
428 #[test]
429 fn test_flex_item_debug() {
430 let item = FlexItem::new().grow(1.0);
431 let debug = format!("{:?}", item);
432 assert!(debug.contains("FlexItem"));
433 }
434
435 #[test]
440 fn test_distribute_flex_no_grow_no_shrink() {
441 let items = vec![FlexItem::new(), FlexItem::new()];
442 let sizes = vec![30.0, 30.0];
443 let result = distribute_flex(&items, &sizes, 100.0);
444 assert_eq!(result, vec![30.0, 30.0]);
446 }
447
448 #[test]
449 fn test_distribute_flex_single_item_grow() {
450 let items = vec![FlexItem::new().grow(1.0)];
451 let sizes = vec![50.0];
452 let result = distribute_flex(&items, &sizes, 100.0);
453 assert_eq!(result, vec![100.0]);
454 }
455
456 #[test]
457 fn test_distribute_flex_single_item_shrink() {
458 let items = vec![FlexItem::new().shrink(1.0)];
459 let sizes = vec![150.0];
460 let result = distribute_flex(&items, &sizes, 100.0);
461 assert_eq!(result, vec![100.0]);
462 }
463
464 #[test]
465 fn test_distribute_flex_shrink_uneven() {
466 let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(3.0)];
467 let sizes = vec![100.0, 100.0];
468 let result = distribute_flex(&items, &sizes, 100.0);
469 assert_eq!(result, vec![75.0, 25.0]);
473 }
474
475 #[test]
476 fn test_distribute_flex_shrink_to_zero() {
477 let items = vec![FlexItem::new().shrink(1.0)];
478 let sizes = vec![50.0];
479 let result = distribute_flex(&items, &sizes, 0.0);
481 assert_eq!(result, vec![0.0]); }
483
484 #[test]
485 fn test_distribute_flex_mixed_grow() {
486 let items = vec![
487 FlexItem::new().grow(0.0), FlexItem::new().grow(1.0), ];
490 let sizes = vec![50.0, 0.0];
491 let result = distribute_flex(&items, &sizes, 100.0);
492 assert_eq!(result, vec![50.0, 50.0]);
493 }
494
495 #[test]
496 fn test_distribute_flex_three_items() {
497 let items = vec![
498 FlexItem::new().grow(1.0),
499 FlexItem::new().grow(2.0),
500 FlexItem::new().grow(1.0),
501 ];
502 let sizes = vec![0.0, 0.0, 0.0];
503 let result = distribute_flex(&items, &sizes, 100.0);
504 assert_eq!(result, vec![25.0, 50.0, 25.0]);
505 }
506
507 #[test]
508 fn test_distribute_flex_near_exact_fit() {
509 let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
510 let sizes = vec![49.9995, 50.0005];
511 let result = distribute_flex(&items, &sizes, 100.0);
512 assert_eq!(result, vec![49.9995, 50.0005]);
514 }
515
516 #[test]
521 fn test_flex_item_collapse_if_empty() {
522 let item = FlexItem::new().collapse_if_empty();
523 assert!(item.collapse_if_empty);
524 }
525
526 #[test]
527 fn test_flex_item_collapse_if_empty_default_false() {
528 let item = FlexItem::new();
529 assert!(!item.collapse_if_empty);
530 }
531
532 #[test]
533 fn test_distribute_flex_collapsed_item_stays_zero() {
534 let items = vec![
535 FlexItem::new().grow(1.0).collapse_if_empty(),
536 FlexItem::new().grow(1.0),
537 ];
538 let sizes = vec![0.0, 50.0]; let result = distribute_flex(&items, &sizes, 100.0);
540 assert_eq!(result, vec![0.0, 100.0]);
542 }
543
544 #[test]
545 fn test_distribute_flex_collapsed_doesnt_participate_in_grow() {
546 let items = vec![
547 FlexItem::new().grow(1.0).collapse_if_empty(),
548 FlexItem::new().grow(1.0),
549 FlexItem::new().grow(1.0),
550 ];
551 let sizes = vec![0.0, 25.0, 25.0]; let result = distribute_flex(&items, &sizes, 100.0);
553 assert_eq!(result, vec![0.0, 50.0, 50.0]);
555 }
556
557 #[test]
558 fn test_distribute_flex_collapsed_with_size_not_collapsed() {
559 let items = vec![
561 FlexItem::new().grow(1.0).collapse_if_empty(),
562 FlexItem::new().grow(1.0),
563 ];
564 let sizes = vec![30.0, 30.0]; let result = distribute_flex(&items, &sizes, 100.0);
566 assert_eq!(result, vec![50.0, 50.0]);
568 }
569
570 #[test]
571 fn test_distribute_flex_all_collapsed() {
572 let items = vec![
573 FlexItem::new().grow(1.0).collapse_if_empty(),
574 FlexItem::new().grow(1.0).collapse_if_empty(),
575 ];
576 let sizes = vec![0.0, 0.0]; let result = distribute_flex(&items, &sizes, 100.0);
578 assert_eq!(result, vec![0.0, 0.0]);
580 }
581
582 #[test]
583 fn test_distribute_flex_collapsed_in_shrink() {
584 let items = vec![
585 FlexItem::new().shrink(1.0).collapse_if_empty(),
586 FlexItem::new().shrink(1.0),
587 ];
588 let sizes = vec![0.0, 120.0]; let result = distribute_flex(&items, &sizes, 100.0);
590 assert_eq!(result, vec![0.0, 100.0]);
592 }
593}