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