1use serde::Serialize;
9
10use crate::target::WindowInfo;
11
12#[derive(Debug, Clone, PartialEq, Serialize)]
14pub struct SpaceScreen {
15 pub index: u32,
18 pub name: String,
21 pub x: i32,
23 pub y: i32,
25 pub width: u32,
27 pub height: u32,
29 pub is_primary: bool,
31 pub windows: Vec<WindowInfo>,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize)]
39pub struct SpacesReport {
40 pub screens: Vec<SpaceScreen>,
42 pub note: String,
45}
46
47pub fn group_windows(
52 displays: &[crate::types::DisplayInfo],
53 windows: &[WindowInfo],
54) -> Vec<SpaceScreen> {
55 let mut out: Vec<SpaceScreen> = displays
56 .iter()
57 .enumerate()
58 .map(|(index, display)| SpaceScreen {
59 index: index as u32,
60 name: display.name.clone(),
61 x: display.x,
62 y: display.y,
63 width: display.width,
64 height: display.height,
65 is_primary: display.is_primary,
66 windows: Vec::new(),
67 })
68 .collect();
69 if out.is_empty() {
70 return out;
71 }
72 let mut orphan: Vec<WindowInfo> = Vec::new();
73 for window in windows {
74 let center_x = window.bounds.x + (window.bounds.width as i32) / 2;
75 let center_y = window.bounds.y + (window.bounds.height as i32) / 2;
76 let owner = out.iter_mut().find(|screen| {
77 point_inside(
78 center_x,
79 center_y,
80 screen.x,
81 screen.y,
82 screen.width,
83 screen.height,
84 )
85 });
86 match owner {
87 Some(screen) => screen.windows.push(window.clone()),
88 None => orphan.push(window.clone()),
89 }
90 }
91 if !orphan.is_empty() {
92 let first = &mut out[0];
93 first.windows.append(&mut orphan);
94 }
95 for screen in out.iter_mut() {
96 screen.windows.sort_by_key(|window| window.id);
97 }
98 out
99}
100
101fn point_inside(x: i32, y: i32, sx: i32, sy: i32, sw: u32, sh: u32) -> bool {
102 x >= sx && y >= sy && x < sx + (sw as i32) && y < sy + (sh as i32)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::target::WindowBounds;
109 use crate::types::DisplayInfo;
110
111 fn window(id: u32, x: i32, y: i32, w: u32, h: u32) -> WindowInfo {
112 WindowInfo {
113 id,
114 pid: 1,
115 app: "Test".to_string(),
116 title: format!("win {id}"),
117 bounds: WindowBounds {
118 x,
119 y,
120 width: w,
121 height: h,
122 },
123 z: 0,
124 layer: 0,
125 on_screen: true,
126 ax_matched: false,
127 }
128 }
129
130 fn display(index: u32, x: i32, y: i32, w: u32, h: u32) -> DisplayInfo {
131 DisplayInfo {
132 id: index,
133 name: format!("Display {index}"),
134 x,
135 y,
136 width: w,
137 height: h,
138 scale: 1.0,
139 is_primary: index == 0,
140 }
141 }
142
143 #[test]
144 fn group_assigns_windows_by_center() {
145 let screens = vec![
146 display(0, 0, 0, 1000, 1000),
147 display(1, 1000, 0, 1000, 1000),
148 ];
149 let windows = vec![
150 window(1, 100, 100, 200, 200),
151 window(2, 1100, 100, 200, 200),
152 ];
153 let groups = group_windows(&screens, &windows);
154 assert_eq!(groups.len(), 2);
155 assert_eq!(groups[0].windows.len(), 1);
156 assert_eq!(groups[0].windows[0].id, 1);
157 assert_eq!(groups[1].windows.len(), 1);
158 assert_eq!(groups[1].windows[0].id, 2);
159 }
160
161 #[test]
162 fn orphan_window_falls_into_the_first_screen() {
163 let screens = vec![display(0, 0, 0, 1000, 1000)];
164 let windows = vec![window(7, 5000, 5000, 200, 200)];
165 let groups = group_windows(&screens, &windows);
166 assert_eq!(groups.len(), 1);
167 assert_eq!(groups[0].windows.len(), 1);
168 assert_eq!(groups[0].windows[0].id, 7);
169 }
170
171 #[test]
172 fn empty_display_list_yields_empty_group() {
173 let groups = group_windows(&[], &[]);
174 assert!(groups.is_empty());
175 }
176}