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 for pass in [true, false] {
106 if deficit <= EPS {
107 break;
108 }
109 let mut pool = 0.0;
110 for (item, size) in items.iter().zip(sizes.iter()) {
111 let is_flex = item.grow > 0.0;
112 if is_flex == pass && *size > item.min + EPS {
113 pool += item.shrink * *size;
114 }
115 }
116
117 if !pass {
118 let flex_ready = items
119 .iter()
120 .zip(sizes.iter())
121 .any(|(it, s)| it.grow > 0.0 && *s > it.min + EPS);
122 if flex_ready {
123 continue;
124 }
125 }
126 if !(pool > 0.0) {
127 continue;
128 }
129 let take = pool.min(deficit * items_scale(items, sizes, pass));
130
131 for (item, size) in items.iter().zip(sizes.iter_mut()) {
132 let is_flex = item.grow > 0.0;
133 if is_flex == pass && *size > item.min + EPS {
134 let w = item.shrink * *size;
135 let cut = take * w / pool;
136 *size = item.clamp(*size - cut);
137 }
138 }
139 let used2: f32 = sizes.iter().sum::<f32>() + gaps;
140 deficit = (used2 - avail_main).max(0.0);
141 }
142 }
145 }
146}
147
148fn items_scale(_items: &[FlexItem], _sizes: &[f32], _flex_pass: bool) -> f32 {
149 1.0
150}
151
152pub fn justify_layout(
157 justify: Justify,
158 count: usize,
159 base_gap: f32,
160 used: f32,
161 avail: f32,
162) -> (f32, f32) {
163 let extra = if avail.is_finite() {
164 (avail - used).max(0.0)
165 } else {
166 0.0
167 };
168 let _ = base_gap;
169 justify.distribute(count, base_gap, extra)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn approx(a: f32, b: f32) -> bool {
177 (a - b).abs() < 0.05
178 }
179
180 #[test]
181 fn grow_splits_free_space() {
182 let items = [
183 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
184 FlexItem::new(50.0, 3.0, 1.0, 0.0, f32::INFINITY),
185 ];
186 let mut sizes = [100.0, 50.0];
187 distribute_flex(&items, &mut sizes, 0.0, 400.0);
188 assert!(approx(sizes[0] + sizes[1], 400.0));
189 assert!(approx(sizes[0], 162.5));
190 assert!(approx(sizes[1], 237.5));
191 }
192
193 #[test]
194 fn max_cap_overflow_goes_to_sibling() {
195 let items = [
196 FlexItem::new(0.0, 1.0, 1.0, 0.0, 200.0),
197 FlexItem::new(0.0, 1.0, 1.0, 0.0, f32::INFINITY),
198 ];
199 let mut sizes = [0.0, 0.0];
200 distribute_flex(&items, &mut sizes, 0.0, 400.0);
201 assert!(approx(sizes[0], 200.0), "{sizes:?}");
202 assert!(approx(sizes[1], 200.0), "{sizes:?}");
203 }
204
205 #[test]
206 fn shrink_prefers_scaled_weight() {
207 let items = [
208 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
209 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
210 ];
211 let mut sizes = [100.0, 100.0];
212 distribute_flex(&items, &mut sizes, 0.0, 120.0);
213 assert!(approx(sizes[0] + sizes[1], 120.0), "{sizes:?}");
214 }
215
216 #[test]
217 fn zero_shrink_item_keeps_size() {
218 let items = [
219 FlexItem::new(100.0, 0.0, 0.0, 0.0, f32::INFINITY),
220 FlexItem::new(100.0, 1.0, 1.0, 0.0, f32::INFINITY),
221 ];
222 let mut sizes = [100.0, 100.0];
223 distribute_flex(&items, &mut sizes, 0.0, 120.0);
224 assert!(approx(sizes[0], 100.0), "{sizes:?}");
225 assert!(approx(sizes[1], 20.0), "{sizes:?}");
226 }
227}