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