1use ratatui::layout::{Constraint, Layout, Rect};
2
3pub fn auto_grid(area: Rect, n: usize, spacing: u16) -> Vec<Rect> {
30 if n == 0 {
31 return Vec::new();
32 }
33
34 let cols = (n as f64).sqrt().ceil() as u16;
35 let rows = ((n as f64) / f64::from(cols)).ceil() as u16;
36
37 let row_constraints: Vec<Constraint> =
38 std::iter::repeat_n(Constraint::Ratio(1, rows.into()), rows as usize).collect();
39
40 let col_constraints: Vec<Constraint> =
41 std::iter::repeat_n(Constraint::Ratio(1, cols.into()), cols as usize).collect();
42
43 let row_areas = Layout::vertical(row_constraints)
44 .spacing(spacing)
45 .split(area);
46
47 let mut out = Vec::with_capacity(n);
48 'outer: for r in 0..rows as usize {
49 let col_areas = Layout::horizontal(col_constraints.clone())
50 .spacing(spacing)
51 .split(row_areas[r]);
52 for &rect in col_areas.iter() {
53 if out.len() == n {
54 break 'outer;
55 }
56 out.push(rect);
57 }
58 }
59 out
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn empty_grid() {
68 let area = Rect::new(0, 0, 100, 100);
69 let result = auto_grid(area, 0, 0);
70 assert_eq!(result.len(), 0);
71 }
72
73 #[test]
74 fn single_cell() {
75 let area = Rect::new(0, 0, 100, 100);
76 let result = auto_grid(area, 1, 0);
77 assert_eq!(result.len(), 1);
78 assert_eq!(result[0], area, "Single cell should fill entire area");
79 }
80
81 #[test]
82 fn four_cells_perfect_square() {
83 let area = Rect::new(0, 0, 100, 100);
84 let result = auto_grid(area, 4, 0);
85
86 assert_eq!(result.len(), 4);
87
88 assert_eq!(result[0].x, 0);
90 assert_eq!(result[0].y, 0);
91 assert_eq!(result[0].width, 50);
92 assert_eq!(result[0].height, 50);
93
94 assert_eq!(result[1].x, 50);
96 assert_eq!(result[1].y, 0);
97
98 assert_eq!(result[2].x, 0);
100 assert_eq!(result[2].y, 50);
101
102 assert_eq!(result[3].x, 50);
104 assert_eq!(result[3].y, 50);
105 }
106
107 #[test]
108 fn nine_cells() {
109 let area = Rect::new(0, 0, 99, 99);
110 let result = auto_grid(area, 9, 0);
111
112 assert_eq!(result.len(), 9);
113
114 assert_eq!(result[0].x, 0);
116 assert_eq!(result[0].y, 0);
117 assert_eq!(result[0].width, 33);
118 assert_eq!(result[0].height, 33);
119
120 assert_eq!(result[8].x, 66);
122 assert_eq!(result[8].y, 66);
123 }
124
125 #[test]
126 fn non_square_grid() {
127 let area = Rect::new(0, 0, 100, 100);
128 let result = auto_grid(area, 6, 0);
129
130 assert_eq!(result.len(), 6);
131
132 assert_eq!(result[0].y, result[1].y);
134 assert_eq!(result[1].y, result[2].y);
135
136 assert_eq!(result[3].y, result[4].y);
138 assert_eq!(result[4].y, result[5].y);
139
140 assert_ne!(result[0].y, result[3].y);
141 }
142
143 #[test]
144 fn with_spacing() {
145 let area = Rect::new(0, 0, 100, 100);
146 let result_no_spacing = auto_grid(area, 4, 0);
147 let result_with_spacing = auto_grid(area, 4, 2);
148
149 assert_eq!(result_no_spacing.len(), 4);
150 assert_eq!(result_with_spacing.len(), 4);
151
152 assert!(result_with_spacing[0].width <= result_no_spacing[0].width);
153 assert!(result_with_spacing[0].height <= result_no_spacing[0].height);
154
155 let gap_no_spacing =
156 result_no_spacing[1].x - (result_no_spacing[0].x + result_no_spacing[0].width);
157 let gap_with_spacing =
158 result_with_spacing[1].x - (result_with_spacing[0].x + result_with_spacing[0].width);
159 assert!(gap_with_spacing >= gap_no_spacing);
160 }
161
162 #[test]
163 fn all_cells_within_bounds() {
164 let area = Rect::new(10, 10, 200, 150);
165 let result = auto_grid(area, 7, 1);
166
167 for (i, rect) in result.iter().enumerate() {
168 assert!(
169 rect.x >= area.x,
170 "Cell {} x position {} should be >= area.x {}",
171 i,
172 rect.x,
173 area.x
174 );
175 assert!(
176 rect.y >= area.y,
177 "Cell {} y position {} should be >= area.y {}",
178 i,
179 rect.y,
180 area.y
181 );
182 assert!(
183 rect.x + rect.width <= area.x + area.width,
184 "Cell {} right edge should be within area bounds",
185 i
186 );
187 assert!(
188 rect.y + rect.height <= area.y + area.height,
189 "Cell {} bottom edge should be withing area bounds",
190 i
191 );
192 }
193 }
194
195 #[test]
196 fn row_major_order() {
197 let area = Rect::new(0, 0, 100, 100);
198 let result = auto_grid(area, 6, 0);
199
200 assert_eq!(result[0].y, result[1].y);
201 assert_eq!(result[1].y, result[2].y);
202
203 assert_eq!(result[3].y, result[4].y);
204 assert_eq!(result[4].y, result[5].y);
205
206 assert!(result[0].y < result[3].y);
207 }
208
209 #[test]
210 fn exact_count_returned() {
211 for n in 1..=20 {
212 let area = Rect::new(0, 0, 100, 100);
213 let result = auto_grid(area, n, 0);
214 assert_eq!(
215 result.len(),
216 n,
217 "should return exactly {} cells, got {}",
218 n,
219 result.len()
220 );
221 }
222 }
223}