1use crate::style::{GridPlacement, GridTrackSize};
13
14#[derive(Debug, Clone)]
16pub struct GridItemPlacement {
17 pub child_index: usize,
19 pub col_start: usize,
21 pub col_end: usize,
23 pub row_start: usize,
25 pub row_end: usize,
27}
28
29pub fn resolve_tracks(
37 template: &[GridTrackSize],
38 available_space: f64,
39 gap: f64,
40 content_sizes: &[f64],
41) -> Vec<f64> {
42 if template.is_empty() {
43 return vec![];
44 }
45
46 let total_gap = if template.len() > 1 {
47 gap * (template.len() - 1) as f64
48 } else {
49 0.0
50 };
51 let space_after_gaps = (available_space - total_gap).max(0.0);
52
53 let mut sizes = vec![0.0_f64; template.len()];
54 let mut remaining = space_after_gaps;
55 let mut total_fr = 0.0_f64;
56
57 for (i, track) in template.iter().enumerate() {
59 match track {
60 GridTrackSize::Pt(pts) => {
61 sizes[i] = *pts;
62 remaining -= pts;
63 }
64 GridTrackSize::Auto => {
65 let content = content_sizes.get(i).copied().unwrap_or(0.0);
66 sizes[i] = content;
67 remaining -= content;
68 }
69 GridTrackSize::Fr(fr) => {
70 total_fr += fr;
71 }
72 GridTrackSize::MinMax(min, max) => {
73 let min_val = resolve_single_track(min, 0.0);
74 let max_val = resolve_single_track(max, space_after_gaps);
75 let content = content_sizes.get(i).copied().unwrap_or(0.0);
76 let val = content.max(min_val).min(max_val);
77 sizes[i] = val;
78 remaining -= val;
79 }
80 }
81 }
82
83 remaining = remaining.max(0.0);
85 if total_fr > 0.0 {
86 let fr_unit = remaining / total_fr;
87 for (i, track) in template.iter().enumerate() {
88 if let GridTrackSize::Fr(fr) = track {
89 sizes[i] = fr * fr_unit;
90 }
91 }
92 }
93
94 sizes
95}
96
97fn resolve_single_track(track: &GridTrackSize, available: f64) -> f64 {
99 match track {
100 GridTrackSize::Pt(pts) => *pts,
101 GridTrackSize::Fr(fr) => fr * available, GridTrackSize::Auto => 0.0,
103 GridTrackSize::MinMax(min, _) => resolve_single_track(min, available),
104 }
105}
106
107pub fn place_items(
112 placements: &[Option<&GridPlacement>],
113 num_columns: usize,
114) -> Vec<GridItemPlacement> {
115 if num_columns == 0 {
116 return vec![];
117 }
118
119 let num_items = placements.len();
120 let max_rows = num_items.div_ceil(num_columns) + num_items; let mut occupied = vec![vec![false; num_columns]; max_rows];
124 let mut result = Vec::with_capacity(num_items);
125
126 for (i, placement) in placements.iter().enumerate() {
128 if let Some(gp) = placement {
129 if gp.column_start.is_some() || gp.row_start.is_some() {
130 let col_start = gp
131 .column_start
132 .map(|c| (c - 1).max(0) as usize)
133 .unwrap_or(0);
134 let row_start = gp.row_start.map(|r| (r - 1).max(0) as usize).unwrap_or(0);
135
136 let col_span = if let (Some(cs), Some(ce)) = (gp.column_start, gp.column_end) {
137 ((ce - cs).max(1)) as usize
138 } else {
139 gp.column_span.unwrap_or(1) as usize
140 };
141
142 let row_span = if let (Some(rs), Some(re)) = (gp.row_start, gp.row_end) {
143 ((re - rs).max(1)) as usize
144 } else {
145 gp.row_span.unwrap_or(1) as usize
146 };
147
148 let col_end = (col_start + col_span).min(num_columns);
149 let row_end = row_start + row_span;
150
151 for r in row_start..row_end {
153 for c in col_start..col_end {
154 if r < occupied.len() && c < num_columns {
155 occupied[r][c] = true;
156 }
157 }
158 }
159
160 result.push(GridItemPlacement {
161 child_index: i,
162 col_start,
163 col_end,
164 row_start,
165 row_end,
166 });
167 }
168 }
169 }
170
171 let mut auto_row = 0;
173 let mut auto_col = 0;
174
175 for (i, placement) in placements.iter().enumerate() {
176 let is_explicit = if let Some(gp) = placement {
177 gp.column_start.is_some() || gp.row_start.is_some()
178 } else {
179 false
180 };
181
182 if is_explicit {
183 continue;
184 }
185
186 let col_span = placement.and_then(|gp| gp.column_span).unwrap_or(1) as usize;
187 let row_span = placement.and_then(|gp| gp.row_span).unwrap_or(1) as usize;
188
189 loop {
191 if auto_col + col_span > num_columns {
192 auto_col = 0;
193 auto_row += 1;
194 }
195
196 while auto_row + row_span > occupied.len() {
198 occupied.push(vec![false; num_columns]);
199 }
200
201 let mut fits = auto_col + col_span <= num_columns;
203 if fits {
204 'check: for row in occupied.iter().skip(auto_row).take(row_span) {
205 for &cell in row.iter().skip(auto_col).take(col_span) {
206 if cell {
207 fits = false;
208 break 'check;
209 }
210 }
211 }
212 }
213
214 if fits {
215 break;
216 }
217
218 auto_col += 1;
219 }
220
221 let col_end = auto_col + col_span;
223 let row_end = auto_row + row_span;
224
225 for r in auto_row..row_end {
226 for c in auto_col..col_end {
227 if r < occupied.len() && c < num_columns {
228 occupied[r][c] = true;
229 }
230 }
231 }
232
233 result.push(GridItemPlacement {
234 child_index: i,
235 col_start: auto_col,
236 col_end,
237 row_start: auto_row,
238 row_end,
239 });
240
241 auto_col = col_end;
242 }
243
244 result
245}
246
247pub fn compute_num_rows(placements: &[GridItemPlacement]) -> usize {
249 placements.iter().map(|p| p.row_end).max().unwrap_or(0)
250}
251
252pub fn column_x_offset(col: usize, col_widths: &[f64], gap: f64) -> f64 {
254 let mut x = 0.0;
255 for c in 0..col {
256 x += col_widths.get(c).copied().unwrap_or(0.0);
257 x += gap;
258 }
259 x
260}
261
262pub fn span_width(col_start: usize, col_end: usize, col_widths: &[f64], gap: f64) -> f64 {
264 let mut w = 0.0;
265 for c in col_start..col_end {
266 w += col_widths.get(c).copied().unwrap_or(0.0);
267 if c > col_start {
268 w += gap;
269 }
270 }
271 w
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_resolve_tracks_fixed() {
280 let tracks = vec![GridTrackSize::Pt(100.0), GridTrackSize::Pt(200.0)];
281 let sizes = resolve_tracks(&tracks, 400.0, 0.0, &[]);
282 assert_eq!(sizes.len(), 2);
283 assert!((sizes[0] - 100.0).abs() < 0.001);
284 assert!((sizes[1] - 200.0).abs() < 0.001);
285 }
286
287 #[test]
288 fn test_resolve_tracks_fr() {
289 let tracks = vec![
290 GridTrackSize::Pt(100.0),
291 GridTrackSize::Fr(1.0),
292 GridTrackSize::Fr(2.0),
293 ];
294 let sizes = resolve_tracks(&tracks, 400.0, 0.0, &[]);
295 assert_eq!(sizes.len(), 3);
296 assert!((sizes[0] - 100.0).abs() < 0.001);
297 assert!((sizes[1] - 100.0).abs() < 0.001); assert!((sizes[2] - 200.0).abs() < 0.001); }
300
301 #[test]
302 fn test_resolve_tracks_with_gap() {
303 let tracks = vec![GridTrackSize::Fr(1.0), GridTrackSize::Fr(1.0)];
304 let sizes = resolve_tracks(&tracks, 210.0, 10.0, &[]);
305 assert_eq!(sizes.len(), 2);
306 assert!((sizes[0] - 100.0).abs() < 0.001);
308 assert!((sizes[1] - 100.0).abs() < 0.001);
309 }
310
311 #[test]
312 fn test_resolve_tracks_auto() {
313 let tracks = vec![GridTrackSize::Auto, GridTrackSize::Fr(1.0)];
314 let content_sizes = vec![80.0, 0.0];
315 let sizes = resolve_tracks(&tracks, 400.0, 0.0, &content_sizes);
316 assert!((sizes[0] - 80.0).abs() < 0.001);
317 assert!((sizes[1] - 320.0).abs() < 0.001);
318 }
319
320 #[test]
321 fn test_place_items_auto() {
322 let placements: Vec<Option<&GridPlacement>> = vec![None; 6];
324 let result = place_items(&placements, 3);
325 assert_eq!(result.len(), 6);
326
327 assert_eq!(result[0].col_start, 0);
329 assert_eq!(result[0].row_start, 0);
330 assert_eq!(result[1].col_start, 1);
331 assert_eq!(result[1].row_start, 0);
332 assert_eq!(result[2].col_start, 2);
333 assert_eq!(result[2].row_start, 0);
334
335 assert_eq!(result[3].col_start, 0);
337 assert_eq!(result[3].row_start, 1);
338 assert_eq!(result[4].col_start, 1);
339 assert_eq!(result[4].row_start, 1);
340 assert_eq!(result[5].col_start, 2);
341 assert_eq!(result[5].row_start, 1);
342 }
343
344 #[test]
345 fn test_place_items_explicit() {
346 let gp = GridPlacement {
347 column_start: Some(2),
348 column_end: None,
349 row_start: Some(1),
350 row_end: None,
351 column_span: None,
352 row_span: None,
353 };
354 let placements: Vec<Option<&GridPlacement>> = vec![Some(&gp), None, None];
355 let result = place_items(&placements, 3);
356 assert_eq!(result.len(), 3);
357
358 let explicit = result.iter().find(|p| p.child_index == 0).unwrap();
360 assert_eq!(explicit.col_start, 1); assert_eq!(explicit.row_start, 0); }
363
364 #[test]
365 fn test_place_items_spanning() {
366 let gp = GridPlacement {
367 column_start: None,
368 column_end: None,
369 row_start: None,
370 row_end: None,
371 column_span: Some(2),
372 row_span: None,
373 };
374 let placements: Vec<Option<&GridPlacement>> = vec![Some(&gp), None, None];
375 let result = place_items(&placements, 3);
376
377 let spanning = result.iter().find(|p| p.child_index == 0).unwrap();
378 assert_eq!(spanning.col_start, 0);
379 assert_eq!(spanning.col_end, 2); }
381
382 #[test]
383 fn test_span_width() {
384 let widths = vec![100.0, 200.0, 150.0];
385 assert!((span_width(0, 1, &widths, 10.0) - 100.0).abs() < 0.001);
386 assert!((span_width(0, 2, &widths, 10.0) - 310.0).abs() < 0.001); assert!((span_width(0, 3, &widths, 10.0) - 470.0).abs() < 0.001); }
389
390 #[test]
391 fn test_column_x_offset() {
392 let widths = vec![100.0, 200.0, 150.0];
393 assert!((column_x_offset(0, &widths, 10.0) - 0.0).abs() < 0.001);
394 assert!((column_x_offset(1, &widths, 10.0) - 110.0).abs() < 0.001);
395 assert!((column_x_offset(2, &widths, 10.0) - 320.0).abs() < 0.001);
396 }
397}