1use crate::session::Layout;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub struct PanePosition {
5 pub x: u16,
6 pub y: u16,
7 pub width: u16,
8 pub height: u16,
9}
10
11pub fn compute_layout(
12 layout: &Layout,
13 pane_count: usize,
14 area_width: u16,
15 area_height: u16,
16) -> Vec<PanePosition> {
17 if pane_count == 0 {
18 return Vec::new();
19 }
20 if pane_count == 1 {
21 return vec![PanePosition {
22 x: 0,
23 y: 0,
24 width: area_width,
25 height: area_height,
26 }];
27 }
28
29 match layout {
30 Layout::Single => vec![PanePosition {
31 x: 0,
32 y: 0,
33 width: area_width,
34 height: area_height,
35 }],
36 Layout::EvenHorizontal => compute_even_horizontal(pane_count, area_width, area_height),
37 Layout::EvenVertical => compute_even_vertical(pane_count, area_width, area_height),
38 Layout::MainVertical => compute_main_vertical(pane_count, area_width, area_height),
39 Layout::Tiled => compute_tiled(pane_count, area_width, area_height),
40 Layout::Custom(ref tree) => crate::layout_dsl::compute_tree_layout(tree, area_width, area_height),
41 }
42}
43
44fn compute_even_horizontal(
45 pane_count: usize,
46 area_width: u16,
47 area_height: u16,
48) -> Vec<PanePosition> {
49 let n = pane_count as u16;
50 let borders = n - 1;
51 let usable = area_width.saturating_sub(borders);
52 let base_width = usable / n;
53 let remainder = usable % n;
54
55 let mut positions = Vec::with_capacity(pane_count);
56 let mut x = 0u16;
57 for i in 0..n {
58 let w = if i == n - 1 {
60 base_width + remainder
61 } else {
62 base_width
63 };
64 positions.push(PanePosition {
65 x,
66 y: 0,
67 width: w,
68 height: area_height,
69 });
70 x += w + 1; }
72 positions
73}
74
75fn compute_even_vertical(
76 pane_count: usize,
77 area_width: u16,
78 area_height: u16,
79) -> Vec<PanePosition> {
80 let n = pane_count as u16;
81 let borders = n - 1;
82 let usable = area_height.saturating_sub(borders);
83 let base_height = usable / n;
84 let remainder = usable % n;
85
86 let mut positions = Vec::with_capacity(pane_count);
87 let mut y = 0u16;
88 for i in 0..n {
89 let h = if i == n - 1 {
90 base_height + remainder
91 } else {
92 base_height
93 };
94 positions.push(PanePosition {
95 x: 0,
96 y,
97 width: area_width,
98 height: h,
99 });
100 y += h + 1;
101 }
102 positions
103}
104
105fn compute_main_vertical(
106 pane_count: usize,
107 area_width: u16,
108 area_height: u16,
109) -> Vec<PanePosition> {
110 let left_width = area_width / 2;
111 let right_width = area_width.saturating_sub(left_width).saturating_sub(1);
112
113 let mut positions = Vec::with_capacity(pane_count);
114
115 positions.push(PanePosition {
117 x: 0,
118 y: 0,
119 width: left_width,
120 height: area_height,
121 });
122
123 let right_count = (pane_count - 1) as u16;
125 if right_count == 0 {
126 return positions;
127 }
128
129 let right_x = left_width + 1; let borders = right_count - 1;
131 let usable_height = area_height.saturating_sub(borders);
132 let base_height = usable_height / right_count;
133 let remainder = usable_height % right_count;
134
135 let mut y = 0u16;
136 for i in 0..right_count {
137 let h = if i == right_count - 1 {
138 base_height + remainder
139 } else {
140 base_height
141 };
142 positions.push(PanePosition {
143 x: right_x,
144 y,
145 width: right_width,
146 height: h,
147 });
148 y += h + 1;
149 }
150 positions
151}
152
153fn compute_tiled(pane_count: usize, area_width: u16, area_height: u16) -> Vec<PanePosition> {
154 let n = pane_count;
155 let cols = (n as f64).sqrt().ceil() as usize;
156 let rows = n.div_ceil(cols);
157
158 let row_borders = (rows as u16).saturating_sub(1);
159 let usable_height = area_height.saturating_sub(row_borders);
160 let base_row_height = usable_height / rows as u16;
161
162 let mut positions = Vec::with_capacity(n);
163 let mut pane_idx = 0;
164 let mut y = 0u16;
165
166 for row in 0..rows {
167 let panes_in_row = if row == rows - 1 {
168 n - pane_idx } else {
170 cols
171 };
172
173 let row_height = if row == rows - 1 {
174 area_height.saturating_sub(y)
176 } else {
177 base_row_height
178 };
179
180 let row_col_borders = (panes_in_row as u16).saturating_sub(1);
182 let row_usable_width = area_width.saturating_sub(row_col_borders);
183 let col_width = row_usable_width / panes_in_row as u16;
184 let width_remainder = row_usable_width % panes_in_row as u16;
185
186 let mut x = 0u16;
187 for col in 0..panes_in_row {
188 let w = if col == panes_in_row - 1 {
189 col_width + width_remainder
190 } else {
191 col_width
192 };
193 positions.push(PanePosition {
194 x,
195 y,
196 width: w,
197 height: row_height,
198 });
199 x += w + 1;
200 pane_idx += 1;
201 }
202
203 y += row_height + 1; }
205 positions
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn single_fills_area() {
214 let result = compute_layout(&Layout::Single, 1, 80, 24);
215 assert_eq!(result.len(), 1);
216 assert_eq!(
217 result[0],
218 PanePosition {
219 x: 0,
220 y: 0,
221 width: 80,
222 height: 24,
223 }
224 );
225 }
226
227 #[test]
228 fn even_horizontal_two_panes() {
229 let result = compute_layout(&Layout::EvenHorizontal, 2, 81, 24);
230 assert_eq!(result.len(), 2);
231 assert_eq!(
232 result[0],
233 PanePosition {
234 x: 0,
235 y: 0,
236 width: 40,
237 height: 24,
238 }
239 );
240 assert_eq!(
241 result[1],
242 PanePosition {
243 x: 41,
244 y: 0,
245 width: 40,
246 height: 24,
247 }
248 );
249 }
250
251 #[test]
252 fn even_horizontal_three_panes() {
253 let result = compute_layout(&Layout::EvenHorizontal, 3, 80, 24);
254 assert_eq!(result.len(), 3);
255 let total_width: u16 = result.iter().map(|p| p.width).sum();
257 assert_eq!(total_width, 80 - 2); assert_eq!(result[0].x, 0);
259 assert_eq!(result[1].x, result[0].width + 1);
260 assert_eq!(result[2].x, result[1].x + result[1].width + 1);
261 }
262
263 #[test]
264 fn even_vertical_two_panes() {
265 let result = compute_layout(&Layout::EvenVertical, 2, 80, 25);
266 assert_eq!(result.len(), 2);
267 assert_eq!(
268 result[0],
269 PanePosition {
270 x: 0,
271 y: 0,
272 width: 80,
273 height: 12,
274 }
275 );
276 assert_eq!(
277 result[1],
278 PanePosition {
279 x: 0,
280 y: 13,
281 width: 80,
282 height: 12,
283 }
284 );
285 }
286
287 #[test]
288 fn main_vertical_three_panes() {
289 let result = compute_layout(&Layout::MainVertical, 3, 80, 24);
290 assert_eq!(result.len(), 3);
291 assert_eq!(result[0].x, 0);
293 assert_eq!(result[0].width, 40);
294 assert_eq!(result[0].height, 24);
295 assert_eq!(result[1].x, 41);
297 assert_eq!(result[1].width, 39);
298 assert_eq!(result[2].x, 41);
299 assert_eq!(result[2].width, 39);
300 assert_eq!(result[1].y, 0);
302 assert_eq!(result[1].height, 11);
303 assert_eq!(result[2].y, 12);
304 assert_eq!(result[2].height, 12);
305 }
306
307 #[test]
308 fn tiled_four_panes() {
309 let result = compute_layout(&Layout::Tiled, 4, 81, 25);
311 assert_eq!(result.len(), 4);
312 assert_eq!(
315 result[0],
316 PanePosition {
317 x: 0,
318 y: 0,
319 width: 40,
320 height: 12,
321 }
322 );
323 assert_eq!(
324 result[1],
325 PanePosition {
326 x: 41,
327 y: 0,
328 width: 40,
329 height: 12,
330 }
331 );
332 assert_eq!(
333 result[2],
334 PanePosition {
335 x: 0,
336 y: 13,
337 width: 40,
338 height: 12,
339 }
340 );
341 assert_eq!(
342 result[3],
343 PanePosition {
344 x: 41,
345 y: 13,
346 width: 40,
347 height: 12,
348 }
349 );
350 }
351
352 #[test]
353 fn tiled_five_panes() {
354 let result = compute_layout(&Layout::Tiled, 5, 80, 25);
356 assert_eq!(result.len(), 5);
357 assert_eq!(result[0].y, 0);
359 assert_eq!(result[1].y, 0);
360 assert_eq!(result[2].y, 0);
361 assert_eq!(result[0].width, 26);
362 assert_eq!(result[1].width, 26);
363 assert_eq!(result[2].width, 26);
364 let row1_y = result[3].y;
366 assert_eq!(result[3].y, row1_y);
367 assert_eq!(result[4].y, row1_y);
368 assert_eq!(result[3].width, 39);
369 assert_eq!(result[4].width, 40);
370 }
371
372 #[test]
373 fn zero_panes_returns_empty() {
374 let result = compute_layout(&Layout::EvenHorizontal, 0, 80, 24);
375 assert!(result.is_empty());
376 }
377
378 #[test]
379 fn one_pane_any_layout_fills_area() {
380 for layout in [
381 Layout::Single,
382 Layout::EvenHorizontal,
383 Layout::EvenVertical,
384 Layout::MainVertical,
385 Layout::Tiled,
386 ] {
387 let result = compute_layout(&layout, 1, 120, 40);
388 assert_eq!(result.len(), 1, "layout: {:?}", layout);
389 assert_eq!(
390 result[0],
391 PanePosition {
392 x: 0,
393 y: 0,
394 width: 120,
395 height: 40,
396 },
397 "layout: {:?}",
398 layout,
399 );
400 }
401 }
402
403 #[test]
404 fn small_terminal_no_overflow() {
405 let result = compute_layout(&Layout::EvenHorizontal, 3, 3, 3);
407 assert_eq!(result.len(), 3);
408 for pos in &result {
410 assert!(pos.x <= 3);
411 assert!(pos.y <= 3);
412 }
413 }
414
415 #[test]
416 fn small_terminal_tiled() {
417 let result = compute_layout(&Layout::Tiled, 4, 5, 5);
418 assert_eq!(result.len(), 4);
419 for pos in &result {
420 assert!(pos.x < 10);
421 assert!(pos.y < 10);
422 }
423 }
424
425 #[test]
426 fn even_horizontal_widths_contiguous() {
427 for pane_count in 2..=6 {
429 let width = 100u16;
430 let result = compute_layout(&Layout::EvenHorizontal, pane_count, width, 24);
431 let total: u16 = result.iter().map(|p| p.width).sum::<u16>()
432 + (pane_count as u16 - 1); assert_eq!(total, width, "pane_count={}", pane_count);
434 }
435 }
436
437 #[test]
438 fn even_vertical_heights_contiguous() {
439 for pane_count in 2..=6 {
440 let height = 50u16;
441 let result = compute_layout(&Layout::EvenVertical, pane_count, 80, height);
442 let total: u16 = result.iter().map(|p| p.height).sum::<u16>()
443 + (pane_count as u16 - 1);
444 assert_eq!(total, height, "pane_count={}", pane_count);
445 }
446 }
447}