1use sequoia_core::{Constraints, Length, Size};
4
5pub fn resolve_main(len: Length, intrinsic: f32, available: f32) -> (f32, f32) {
10 match len {
11 Length::Px(v) => (v, 0.0),
12 Length::Percent(p) => {
13 if available.is_finite() && p.is_finite() {
14 ((available * p / 100.0).max(0.0), 0.0)
15 } else {
16 (intrinsic, 0.0)
17 }
18 }
19 Length::Fill(w) => (intrinsic, w.max(0.0)),
20 Length::Fit => (intrinsic, 0.0),
21 }
22}
23
24pub fn resolve_cross(len: Length, intrinsic: f32, available: f32) -> f32 {
29 match len {
30 Length::Px(v) => v,
31 Length::Percent(p) => {
32 if available.is_finite() && p.is_finite() {
33 (available * p / 100.0).max(0.0)
34 } else {
35 intrinsic
36 }
37 }
38 Length::Fill(_) => {
39 if available.is_finite() {
40 available
41 } else {
42 intrinsic
43 }
44 }
45 Length::Fit => intrinsic,
46 }
47}
48
49pub fn resolve_flex_basis(
54 basis: Option<Length>,
55 fallback: (f32, f32),
56 intrinsic: f32,
57 available: f32,
58) -> (f32, f32) {
59 let Some(b) = basis else {
60 return fallback;
61 };
62 match b {
63 Length::Px(v) => (v.max(0.0), 0.0),
64 Length::Percent(p) => {
65 if available.is_finite() && p.is_finite() {
66 ((available * p / 100.0).max(0.0), 0.0)
67 } else {
68 fallback
69 }
70 }
71 Length::Fill(w) => (intrinsic, w.max(0.0)),
72 Length::Fit => (intrinsic, 0.0),
73 }
74}
75
76pub fn clamp_to(size: Size, constraints: Constraints) -> Size {
78 constraints.constrain(size)
79}
80
81pub fn distribute_main(bases: &mut [f32], grows: &[f32], free: f32) {
87 debug_assert_eq!(bases.len(), grows.len());
88
89 if bases.is_empty() || free == 0.0 || free.is_nan() {
90 return;
91 }
92
93 let sum_grow: f32 = grows.iter().copied().sum();
94
95 if free > 0.0 {
96 if sum_grow > 0.0 {
97 for (i, base) in bases.iter_mut().enumerate() {
98 if grows[i] > 0.0 {
99 *base += free * grows[i] / sum_grow;
100 }
101 }
102 }
103 return;
104 }
105
106 let mut deficit = -free;
107
108 let flex_sum: f32 = bases
109 .iter()
110 .zip(grows)
111 .filter(|(_, g)| **g > 0.0)
112 .map(|(b, _)| *b)
113 .sum();
114 if flex_sum > 0.0 {
115 let take = flex_sum.min(deficit);
116 let k = 1.0 - take / flex_sum;
117 for (i, base) in bases.iter_mut().enumerate() {
118 if grows[i] > 0.0 {
119 *base *= k;
120 }
121 }
122 deficit -= take;
123 }
124
125 if deficit > 0.0 {
126 let fixed_sum: f32 = bases
127 .iter()
128 .zip(grows)
129 .filter(|(_, g)| **g <= 0.0)
130 .map(|(b, _)| *b)
131 .sum();
132 if fixed_sum > 0.0 {
133 let k = ((fixed_sum - deficit) / fixed_sum).max(0.0);
134 for (i, base) in bases.iter_mut().enumerate() {
135 if grows[i] <= 0.0 {
136 *base *= k;
137 }
138 }
139 }
140 }
141}
142
143const CLAMP_EPS: f32 = 0.001;
144
145pub fn distribute_main_clamped(
151 bases: &mut [f32],
152 grows: &[f32],
153 mins: &[f32],
154 maxs: &[f32],
155 gaps: f32,
156 avail_main: f32,
157) {
158 debug_assert_eq!(bases.len(), grows.len());
159 if bases.is_empty() {
160 return;
161 }
162
163 let limit = |i: usize| -> (f32, f32) {
164 let mn = mins.get(i).copied().unwrap_or(0.0);
165 let mx = maxs.get(i).copied().unwrap_or(f32::INFINITY);
166 let mn = if mn.is_finite() { mn.max(0.0) } else { 0.0 };
167 let mx = if mx.is_nan() {
168 f32::INFINITY
169 } else if mx.is_infinite() {
170 if mx > 0.0 { f32::INFINITY } else { 0.0 }
171 } else {
172 mx.max(0.0)
173 };
174 if mn > mx { (mx, mx) } else { (mn, mx) }
175 };
176 let clamp_one = |i: usize, v: f32| -> f32 {
177 let (mn, mx) = limit(i);
178 v.clamp(mn, mx)
179 };
180 for (i, base) in bases.iter_mut().enumerate() {
181 *base = clamp_one(i, *base);
182 }
183 if !avail_main.is_finite() || avail_main.is_nan() {
184 return;
185 }
186 let gaps = if gaps.is_finite() { gaps.max(0.0) } else { 0.0 };
187
188 for _ in 0..bases.len() + 1 {
189 let used: f32 = bases.iter().sum::<f32>() + gaps;
190 let free = avail_main - used;
191 if free.abs() <= CLAMP_EPS || free.is_nan() {
192 break;
193 }
194 if free > 0.0 {
195 let grow_of = |i: usize| grows.get(i).copied().unwrap_or(0.0);
196 let mut sum_grow = 0.0;
197 for (i, base) in bases.iter().enumerate() {
198 let (_, mx) = limit(i);
199 if is_finite_positive(grow_of(i)) && *base < mx - CLAMP_EPS {
200 sum_grow += grow_of(i);
201 }
202 }
203 if !(sum_grow > 0.0) {
204 break;
205 }
206 for (i, base) in bases.iter_mut().enumerate() {
207 let (_, mx) = limit(i);
208 if is_finite_positive(grow_of(i)) && *base < mx - CLAMP_EPS {
209 *base = clamp_one(i, *base + free * grow_of(i) / sum_grow);
210 }
211 }
212 } else {
213 let grow_of = |i: usize| grows.get(i).copied().unwrap_or(0.0);
214 let flex_ready = bases
215 .iter()
216 .enumerate()
217 .any(|(i, b)| grow_of(i) > 0.0 && *b > limit(i).0 + CLAMP_EPS);
218
219 let mut pool_sum = 0.0;
220 for (i, base) in bases.iter().enumerate() {
221 let (mn, _) = limit(i);
222 let is_flex = grow_of(i) > 0.0;
223 if *base > mn + CLAMP_EPS && (is_flex || !flex_ready) {
224 pool_sum += *base;
225 }
226 }
227 if !(pool_sum > 0.0) {
228 break;
229 }
230 let mut deficit = -free;
231 let take = pool_sum.min(deficit);
232 let k = 1.0 - take / pool_sum;
233 for (i, base) in bases.iter_mut().enumerate() {
234 let (mn, _) = limit(i);
235 let is_flex = grow_of(i) > 0.0;
236 if *base > mn + CLAMP_EPS && (is_flex || !flex_ready) {
237 *base = clamp_one(i, *base * k);
238 }
239 }
240 deficit -= take;
241 if deficit <= CLAMP_EPS {
242 continue;
243 }
244 }
245 }
246}
247
248fn is_finite_positive(v: f32) -> bool {
249 v.is_finite() && v > 0.0
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 fn approx(a: f32, b: f32) -> bool {
257 (a - b).abs() < 0.01
258 }
259
260 #[test]
261 fn clamped_matches_plain_without_limits() {
262 let mut a = vec![100.0, 50.0];
263 let mut b = a.clone();
264 distribute_main(&mut a, &[1.0, 3.0], 250.0);
265 distribute_main_clamped(&mut b, &[1.0, 3.0], &[], &[], 0.0, 400.0);
266 assert!(approx(a[0], b[0]) && approx(a[1], b[1]));
267 assert!(approx(b[0] + b[1], 400.0));
268 }
269
270 #[test]
271 fn leftover_after_max_goes_to_siblings() {
272 let mut bases = vec![0.0, 0.0];
273 distribute_main_clamped(
274 &mut bases,
275 &[1.0, 1.0],
276 &[],
277 &[200.0, f32::INFINITY],
278 0.0,
279 400.0,
280 );
281 assert!(approx(bases[0], 200.0), "{bases:?}");
282 assert!(approx(bases[1], 200.0), "{bases:?}");
283 }
284
285 #[test]
286 fn deficit_stops_at_min_and_overflows() {
287 let mut bases = vec![60.0, 60.0];
288 distribute_main_clamped(&mut bases, &[0.0, 0.0], &[60.0, 0.0], &[], 0.0, 100.0);
289 assert!(approx(bases[0], 60.0), "{bases:?}");
290 assert!(approx(bases[1], 40.0), "{bases:?}");
291
292 let mut bases = vec![60.0, 60.0];
293 distribute_main_clamped(&mut bases, &[0.0, 0.0], &[60.0, 60.0], &[], 0.0, 100.0);
294 assert!(approx(bases[0], 60.0), "{bases:?}");
295 assert!(approx(bases[1], 60.0), "{bases:?}");
296 }
297
298 #[test]
299 fn unbounded_only_clamps() {
300 let mut bases = vec![500.0, 10.0];
301 distribute_main_clamped(
302 &mut bases,
303 &[1.0, 1.0],
304 &[],
305 &[200.0, f32::INFINITY],
306 0.0,
307 f32::INFINITY,
308 );
309 assert!(approx(bases[0], 200.0), "{bases:?}");
310 assert!(approx(bases[1], 10.0), "{bases:?}");
311 }
312}