1use ratatui::layout::Rect;
5
6use crate::tmux::Pane;
7
8pub fn map_rect(p: &Pane, area: Rect, win_w: u16, win_h: u16) -> Rect {
10 let sx = area.width as f32 / win_w.max(1) as f32;
11 let sy = area.height as f32 / win_h.max(1) as f32;
12 let x = (area.x + (p.left as f32 * sx).round() as u16).min(area.right().saturating_sub(1));
13 let y = (area.y + (p.top as f32 * sy).round() as u16).min(area.bottom().saturating_sub(1));
14 let max_w = area.right().saturating_sub(x).max(1);
15 let max_h = area.bottom().saturating_sub(y).max(1);
16 let w = ((p.width as f32 * sx).round() as u16).clamp(1, max_w);
17 let h = ((p.height as f32 * sy).round() as u16).clamp(1, max_h);
18 Rect { x, y, width: w, height: h }
19}
20
21pub fn rects_for(panes: &[Pane], canvas: Rect, win_w: u16, win_h: u16) -> Vec<(usize, Rect)> {
22 panes.iter().enumerate().map(|(i, p)| (i, map_rect(p, canvas, win_w, win_h))).collect()
23}
24
25pub fn contains(r: Rect, col: u16, row: u16) -> bool {
26 col >= r.x && col < r.right() && row >= r.y && row < r.bottom()
27}
28
29pub fn hit(rects: &[(usize, Rect)], col: u16, row: u16) -> Option<usize> {
32 rects
33 .iter()
34 .filter(|(_, r)| contains(*r, col, row))
35 .min_by_key(|(_, r)| r.width as u32 * r.height as u32)
36 .map(|(i, _)| *i)
37}
38
39pub fn tile_at(tiles: &[(String, Rect)], col: u16, row: u16) -> Option<String> {
41 tiles.iter().find(|(_, r)| contains(*r, col, row)).map(|(id, _)| id.clone())
42}
43
44pub fn mpane_at(mpanes: &[(String, String, Rect)], col: u16, row: u16) -> Option<(String, String)> {
46 mpanes
47 .iter()
48 .filter(|(_, _, r)| contains(*r, col, row))
49 .min_by_key(|(_, _, r)| r.width as u32 * r.height as u32)
50 .map(|(w, p, _)| (w.clone(), p.clone()))
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::tmux::Pane;
57
58 fn pane(left: u16, top: u16, width: u16, height: u16) -> Pane {
59 Pane {
60 id: "%0".into(),
61 index: "0".into(),
62 left,
63 top,
64 width,
65 height,
66 active: false,
67 cmd: "sh".into(),
68 }
69 }
70
71 #[test]
72 fn contains_is_half_open() {
73 let r = Rect { x: 2, y: 3, width: 4, height: 2 };
74 assert!(contains(r, 2, 3));
75 assert!(contains(r, 5, 4));
76 assert!(!contains(r, 6, 3)); assert!(!contains(r, 2, 5)); }
79
80 #[test]
81 fn map_rect_fills_canvas_when_pane_fills_window() {
82 let canvas = Rect { x: 0, y: 0, width: 80, height: 24 };
83 let m = map_rect(&pane(0, 0, 100, 50), canvas, 100, 50);
84 assert_eq!(m, canvas);
85 }
86
87 #[test]
88 fn map_rect_scales_a_right_half_pane() {
89 let canvas = Rect { x: 0, y: 0, width: 80, height: 24 };
90 let m = map_rect(&pane(50, 0, 50, 50), canvas, 100, 50);
91 assert_eq!(m, Rect { x: 40, y: 0, width: 40, height: 24 });
92 }
93
94 #[test]
95 fn hit_prefers_the_smallest_containing_box() {
96 let rects = [
97 (0usize, Rect { x: 0, y: 0, width: 10, height: 10 }),
98 (1usize, Rect { x: 2, y: 2, width: 3, height: 3 }),
99 ];
100 assert_eq!(hit(&rects, 3, 3), Some(1));
101 assert_eq!(hit(&rects, 0, 0), Some(0));
102 assert_eq!(hit(&rects, 50, 50), None);
103 }
104}