pub(crate) fn distribute(total: i32, weights: &[f64]) -> Vec<i32> {
let n = weights.len();
let mut shares = vec![0i32; n];
if total <= 0 || n == 0 {
return shares;
}
let sum: f64 = weights.iter().filter(|w| **w > 0.0).sum();
if sum <= 0.0 {
return shares;
}
let mut remainders: Vec<(usize, f64)> = Vec::with_capacity(n);
let mut used: i64 = 0;
for (i, &w) in weights.iter().enumerate() {
if w <= 0.0 {
continue;
}
let exact = total as f64 * (w / sum);
let floor = exact.floor();
shares[i] = floor as i32;
used += floor as i64;
remainders.push((i, exact - floor));
}
let mut leftover = (total as i64 - used) as i32;
remainders.sort_by(|a, b| {
b.1.partial_cmp(&a.1)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.0.cmp(&b.0))
});
for (i, _) in remainders {
if leftover <= 0 {
break;
}
shares[i] += 1;
leftover -= 1;
}
shares
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct FlexItem {
pub basis: i32,
pub min: i32,
pub max: i32,
pub grow: f64,
pub shrink: f64,
}
pub(crate) fn resolve_main_sizes(items: &[FlexItem], available: i32) -> Vec<i32> {
let n = items.len();
let mut sizes: Vec<i32> = items
.iter()
.map(|i| i.basis.clamp(i.min, i.max.max(i.min)))
.collect();
if n == 0 {
return sizes;
}
let mut frozen = vec![false; n];
loop {
let used: i32 = sizes.iter().sum();
let free = available - used;
let weights: Vec<f64> = (0..n)
.map(|i| {
if frozen[i] {
0.0
} else if free > 0 {
items[i].grow
} else {
items[i].shrink * items[i].basis.max(0) as f64
}
})
.collect();
if free == 0 || weights.iter().all(|w| *w <= 0.0) {
break;
}
let deltas = distribute(free.abs(), &weights);
let mut violated = false;
for i in 0..n {
if deltas[i] == 0 {
continue;
}
let target = if free > 0 {
sizes[i] + deltas[i]
} else {
sizes[i] - deltas[i]
};
let clamped = target.clamp(items[i].min.max(0), items[i].max.max(items[i].min.max(0)));
if clamped != target {
violated = true;
frozen[i] = true;
}
sizes[i] = clamped;
}
if !violated {
break; }
}
sizes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn largest_remainder_is_exact_and_stable() {
assert_eq!(distribute(10, &[1.0, 1.0, 1.0]), vec![4, 3, 3]);
let shares = distribute(7, &[2.0, 1.0, 1.0]);
assert_eq!(shares.iter().sum::<i32>(), 7);
assert_eq!(shares, vec![3, 2, 2]);
assert_eq!(distribute(5, &[0.0, 1.0]), vec![0, 5]);
assert_eq!(distribute(5, &[0.0, 0.0]), vec![0, 0]);
}
#[test]
fn grow_respects_max_and_redistributes() {
let items = [
FlexItem {
basis: 0,
min: 0,
max: 3,
grow: 1.0,
shrink: 1.0,
},
FlexItem {
basis: 0,
min: 0,
max: i32::MAX,
grow: 1.0,
shrink: 1.0,
},
];
let sizes = resolve_main_sizes(&items, 10);
assert_eq!(sizes, vec![3, 7]);
assert_eq!(sizes.iter().sum::<i32>(), 10);
}
#[test]
fn shrink_respects_min() {
let items = [
FlexItem {
basis: 8,
min: 6,
max: i32::MAX,
grow: 0.0,
shrink: 1.0,
},
FlexItem {
basis: 8,
min: 0,
max: i32::MAX,
grow: 0.0,
shrink: 1.0,
},
];
let sizes = resolve_main_sizes(&items, 10);
assert_eq!(sizes, vec![6, 4]);
assert_eq!(sizes.iter().sum::<i32>(), 10);
}
#[test]
fn no_flex_leaves_sizes_alone() {
let items = [
FlexItem {
basis: 4,
min: 0,
max: i32::MAX,
grow: 0.0,
shrink: 0.0,
},
FlexItem {
basis: 2,
min: 0,
max: i32::MAX,
grow: 0.0,
shrink: 0.0,
},
];
assert_eq!(resolve_main_sizes(&items, 20), vec![4, 2]);
}
}