use crate::container::Justify;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FlexItem {
pub base: f32,
pub grow: f32,
pub shrink: f32,
pub min: f32,
pub max: f32,
}
impl FlexItem {
pub fn new(base: f32, grow: f32, shrink: f32, min: f32, max: f32) -> Self {
Self {
base: if base.is_finite() { base.max(0.0) } else { 0.0 },
grow: if grow.is_finite() && grow > 0.0 {
grow
} else {
0.0
},
shrink: if shrink.is_finite() && shrink >= 0.0 {
shrink
} else {
1.0
},
min: if min.is_finite() { min.max(0.0) } else { 0.0 },
max: if max.is_nan() {
f32::INFINITY
} else if max.is_infinite() {
if max > 0.0 { f32::INFINITY } else { 0.0 }
} else {
max.max(0.0)
},
}
}
fn clamp(self, v: f32) -> f32 {
let (mn, mx) = if self.min > self.max {
(self.max, self.max)
} else {
(self.min, self.max)
};
v.clamp(mn, mx)
}
}
pub fn distribute_flex(items: &[FlexItem], sizes: &mut [f32], gaps: f32, avail_main: f32) {
assert_eq!(items.len(), sizes.len());
if items.is_empty() {
return;
}
for (item, size) in items.iter().zip(sizes.iter_mut()) {
if !size.is_finite() {
*size = 0.0;
}
*size = item.clamp(*size);
}
if !avail_main.is_finite() || avail_main.is_nan() {
return;
}
let gaps = if gaps.is_finite() { gaps.max(0.0) } else { 0.0 };
const EPS: f32 = 0.001;
for _ in 0..items.len() + 1 {
let used: f32 = sizes.iter().sum::<f32>() + gaps;
let free = avail_main - used;
if free.abs() <= EPS || free.is_nan() {
break;
}
if free > 0.0 {
let mut sum_grow = 0.0;
for (item, size) in items.iter().zip(sizes.iter()) {
if item.grow > 0.0 && *size < item.max - EPS {
sum_grow += item.grow;
}
}
if !(sum_grow > 0.0) {
break;
}
for (item, size) in items.iter().zip(sizes.iter_mut()) {
if item.grow > 0.0 && *size < item.max - EPS {
*size = item.clamp(*size + free * item.grow / sum_grow);
}
}
} else {
let mut deficit = -free;
for pass in [true, false] {
if deficit <= EPS {
break;
}
let mut pool = 0.0;
for (item, size) in items.iter().zip(sizes.iter()) {
let is_flex = item.grow > 0.0;
if is_flex == pass && *size > item.min + EPS {
pool += item.shrink * *size;
}
}
if !pass {
let flex_ready = items
.iter()
.zip(sizes.iter())
.any(|(it, s)| it.grow > 0.0 && *s > it.min + EPS);
if flex_ready {
continue;
}
}
if !(pool > 0.0) {
continue;
}
let take = pool.min(deficit * items_scale(items, sizes, pass));
for (item, size) in items.iter().zip(sizes.iter_mut()) {
let is_flex = item.grow > 0.0;
if is_flex == pass && *size > item.min + EPS {
let w = item.shrink * *size;
let cut = take * w / pool;
*size = item.clamp(*size - cut);
}
}
let used2: f32 = sizes.iter().sum::<f32>() + gaps;
deficit = (used2 - avail_main).max(0.0);
}
}
}
}
fn items_scale(_items: &[FlexItem], _sizes: &[f32], _flex_pass: bool) -> f32 {
1.0
}
pub fn justify_layout(
justify: Justify,
count: usize,
base_gap: f32,
used: f32,
avail: f32,
) -> (f32, f32) {
let extra = if avail.is_finite() {
(avail - used).max(0.0)
} else {
0.0
};
let _ = base_gap;
justify.distribute(count, base_gap, extra)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f32, b: f32) -> bool {
(a - b).abs() < 0.05
}
#[test]
fn grow_splits_free_space() {
let items = [
FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
FlexItem::new(50.0, 3.0, 1.0, 0.0, f32::INFINITY),
];
let mut sizes = [100.0, 50.0];
distribute_flex(&items, &mut sizes, 0.0, 400.0);
assert!(approx(sizes[0] + sizes[1], 400.0));
assert!(approx(sizes[0], 162.5));
assert!(approx(sizes[1], 237.5));
}
#[test]
fn max_cap_overflow_goes_to_sibling() {
let items = [
FlexItem::new(0.0, 1.0, 1.0, 0.0, 200.0),
FlexItem::new(0.0, 1.0, 1.0, 0.0, f32::INFINITY),
];
let mut sizes = [0.0, 0.0];
distribute_flex(&items, &mut sizes, 0.0, 400.0);
assert!(approx(sizes[0], 200.0), "{sizes:?}");
assert!(approx(sizes[1], 200.0), "{sizes:?}");
}
#[test]
fn shrink_prefers_scaled_weight() {
let items = [
FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
];
let mut sizes = [100.0, 100.0];
distribute_flex(&items, &mut sizes, 0.0, 120.0);
assert!(approx(sizes[0] + sizes[1], 120.0), "{sizes:?}");
}
#[test]
fn zero_shrink_item_keeps_size() {
let items = [
FlexItem::new(100.0, 0.0, 0.0, 0.0, f32::INFINITY),
FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
];
let mut sizes = [100.0, 100.0];
distribute_flex(&items, &mut sizes, 0.0, 120.0);
assert!(approx(sizes[0], 100.0), "{sizes:?}");
assert!(approx(sizes[1], 20.0), "{sizes:?}");
}
}