1use crate::container::Justify;
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct FlexItem {
5 pub base: f32,
6 pub grow: f32,
7 pub shrink: f32,
8 pub min: f32,
9 pub max: f32,
10}
11
12impl FlexItem {
13 pub fn new(base: f32, grow: f32, shrink: f32, min: f32, max: f32) -> Self {
14 Self {
15 base: if base.is_finite() { base.max(0.0) } else { 0.0 },
16 grow: if grow.is_finite() && grow > 0.0 {
17 grow
18 } else {
19 0.0
20 },
21 shrink: if shrink.is_finite() && shrink >= 0.0 {
22 shrink
23 } else {
24 1.0
25 },
26 min: if min.is_finite() { min.max(0.0) } else { 0.0 },
27 max: if max.is_nan() {
28 f32::INFINITY
29 } else if max.is_infinite() {
30 if max > 0.0 { f32::INFINITY } else { 0.0 }
31 } else {
32 max.max(0.0)
33 },
34 }
35 }
36
37 fn clamp(self, v: f32) -> f32 {
38 let (mn, mx) = if self.min > self.max {
39 (self.max, self.max)
40 } else {
41 (self.min, self.max)
42 };
43 v.clamp(mn, mx)
44 }
45}
46
47pub fn distribute_flex(items: &[FlexItem], sizes: &mut [f32], gaps: f32, avail_main: f32) {
48 assert_eq!(items.len(), sizes.len());
49 if items.is_empty() {
50 return;
51 }
52 for (item, size) in items.iter().zip(sizes.iter_mut()) {
53 if !size.is_finite() {
54 *size = 0.0;
55 }
56 *size = item.clamp(*size);
57 }
58 if !avail_main.is_finite() || avail_main.is_nan() {
59 return;
60 }
61 let gaps = if gaps.is_finite() { gaps.max(0.0) } else { 0.0 };
62 const EPS: f32 = 0.001;
63
64 for _ in 0..items.len() + 1 {
65 let used: f32 = sizes.iter().sum::<f32>() + gaps;
66 let free = avail_main - used;
67 if free.abs() <= EPS || free.is_nan() {
68 break;
69 }
70 if free > 0.0 {
71 let mut sum_grow = 0.0;
72 for (item, size) in items.iter().zip(sizes.iter()) {
73 if item.grow > 0.0 && *size < item.max - EPS {
74 sum_grow += item.grow;
75 }
76 }
77 if !(sum_grow > 0.0) {
78 break;
79 }
80 for (item, size) in items.iter().zip(sizes.iter_mut()) {
81 if item.grow > 0.0 && *size < item.max - EPS {
82 *size = item.clamp(*size + free * item.grow / sum_grow);
83 }
84 }
85 } else {
86 let mut deficit = -free;
87 for pass in [true, false] {
88 if deficit <= EPS {
89 break;
90 }
91 let mut pool = 0.0;
92 for (item, size) in items.iter().zip(sizes.iter()) {
93 let is_flex = item.grow > 0.0;
94 if is_flex == pass && *size > item.min + EPS {
95 pool += item.shrink * *size;
96 }
97 }
98
99 if !pass {
100 let flex_ready = items
101 .iter()
102 .zip(sizes.iter())
103 .any(|(it, s)| it.grow > 0.0 && *s > it.min + EPS);
104 if flex_ready {
105 continue;
106 }
107 }
108 if !(pool > 0.0) {
109 continue;
110 }
111 let take = pool.min(deficit * items_scale(items, sizes, pass));
112
113 for (item, size) in items.iter().zip(sizes.iter_mut()) {
114 let is_flex = item.grow > 0.0;
115 if is_flex == pass && *size > item.min + EPS {
116 let w = item.shrink * *size;
117 let cut = take * w / pool;
118 *size = item.clamp(*size - cut);
119 }
120 }
121 let used2: f32 = sizes.iter().sum::<f32>() + gaps;
122 deficit = (used2 - avail_main).max(0.0);
123 }
124 }
127 }
128}
129
130fn items_scale(_items: &[FlexItem], _sizes: &[f32], _flex_pass: bool) -> f32 {
131 1.0
132}
133
134pub fn justify_layout(
135 justify: Justify,
136 count: usize,
137 base_gap: f32,
138 used: f32,
139 avail: f32,
140) -> (f32, f32) {
141 let extra = if avail.is_finite() {
142 (avail - used).max(0.0)
143 } else {
144 0.0
145 };
146 let _ = base_gap;
147 justify.distribute(count, base_gap, extra)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 fn approx(a: f32, b: f32) -> bool {
155 (a - b).abs() < 0.05
156 }
157
158 #[test]
159 fn grow_splits_free_space() {
160 let items = [
161 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
162 FlexItem::new(50.0, 3.0, 1.0, 0.0, f32::INFINITY),
163 ];
164 let mut sizes = [100.0, 50.0];
165 distribute_flex(&items, &mut sizes, 0.0, 400.0);
166 assert!(approx(sizes[0] + sizes[1], 400.0));
167 assert!(approx(sizes[0], 162.5));
168 assert!(approx(sizes[1], 237.5));
169 }
170
171 #[test]
172 fn max_cap_overflow_goes_to_sibling() {
173 let items = [
174 FlexItem::new(0.0, 1.0, 1.0, 0.0, 200.0),
175 FlexItem::new(0.0, 1.0, 1.0, 0.0, f32::INFINITY),
176 ];
177 let mut sizes = [0.0, 0.0];
178 distribute_flex(&items, &mut sizes, 0.0, 400.0);
179 assert!(approx(sizes[0], 200.0), "{sizes:?}");
180 assert!(approx(sizes[1], 200.0), "{sizes:?}");
181 }
182
183 #[test]
184 fn shrink_prefers_scaled_weight() {
185 let items = [
186 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
187 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
188 ];
189 let mut sizes = [100.0, 100.0];
190 distribute_flex(&items, &mut sizes, 0.0, 120.0);
191 assert!(approx(sizes[0] + sizes[1], 120.0), "{sizes:?}");
192 }
193
194 #[test]
195 fn zero_shrink_item_keeps_size() {
196 let items = [
197 FlexItem::new(100.0, 0.0, 0.0, 0.0, f32::INFINITY),
198 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
199 ];
200 let mut sizes = [100.0, 100.0];
201 distribute_flex(&items, &mut sizes, 0.0, 120.0);
202 assert!(approx(sizes[0], 100.0), "{sizes:?}");
203 assert!(approx(sizes[1], 20.0), "{sizes:?}");
204 }
205}