1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11pub type ArrangementId = Uuid;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct MonitorInfo {
17 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub name: Option<String>,
20
21 #[serde(default)]
23 pub index: usize,
24
25 #[serde(default)]
27 pub position: (i32, i32),
28
29 #[serde(default)]
31 pub size: (u32, u32),
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct TabSnapshot {
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub cwd: Option<String>,
40
41 #[serde(default)]
43 pub title: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct WindowSnapshot {
49 pub monitor: MonitorInfo,
51
52 pub position_relative: (i32, i32),
54
55 pub size: (u32, u32),
57
58 pub tabs: Vec<TabSnapshot>,
60
61 #[serde(default)]
63 pub active_tab_index: usize,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct WindowArrangement {
69 pub id: ArrangementId,
71
72 pub name: String,
74
75 pub monitor_layout: Vec<MonitorInfo>,
77
78 pub windows: Vec<WindowSnapshot>,
80
81 #[serde(default)]
83 pub created_at: String,
84
85 #[serde(default)]
87 pub order: usize,
88}
89
90#[derive(Debug, Clone, Default)]
92pub struct ArrangementManager {
93 arrangements: HashMap<ArrangementId, WindowArrangement>,
95
96 order: Vec<ArrangementId>,
98}
99
100impl ArrangementManager {
101 pub fn new() -> Self {
103 Self {
104 arrangements: HashMap::new(),
105 order: Vec::new(),
106 }
107 }
108
109 pub fn from_arrangements(arrangements: Vec<WindowArrangement>) -> Self {
111 let mut manager = Self::new();
112 for arrangement in arrangements {
113 manager.add(arrangement);
114 }
115 manager.sort_by_order();
116 manager
117 }
118
119 pub fn add(&mut self, arrangement: WindowArrangement) {
121 let id = arrangement.id;
122 if !self.order.contains(&id) {
123 self.order.push(id);
124 }
125 self.arrangements.insert(id, arrangement);
126 }
127
128 pub fn get(&self, id: &ArrangementId) -> Option<&WindowArrangement> {
130 self.arrangements.get(id)
131 }
132
133 pub fn get_mut(&mut self, id: &ArrangementId) -> Option<&mut WindowArrangement> {
135 self.arrangements.get_mut(id)
136 }
137
138 pub fn update(&mut self, arrangement: WindowArrangement) {
140 let id = arrangement.id;
141 if self.arrangements.contains_key(&id) {
142 self.arrangements.insert(id, arrangement);
143 }
144 }
145
146 pub fn remove(&mut self, id: &ArrangementId) -> Option<WindowArrangement> {
148 self.order.retain(|aid| aid != id);
149 self.arrangements.remove(id)
150 }
151
152 pub fn arrangements_ordered(&self) -> Vec<&WindowArrangement> {
154 self.order
155 .iter()
156 .filter_map(|id| self.arrangements.get(id))
157 .collect()
158 }
159
160 pub fn to_vec(&self) -> Vec<WindowArrangement> {
162 self.arrangements_ordered().into_iter().cloned().collect()
163 }
164
165 pub fn len(&self) -> usize {
167 self.arrangements.len()
168 }
169
170 pub fn is_empty(&self) -> bool {
172 self.arrangements.is_empty()
173 }
174
175 pub fn find_by_name(&self, name: &str) -> Option<&WindowArrangement> {
177 let lower = name.to_lowercase();
178 self.arrangements
179 .values()
180 .find(|a| a.name.to_lowercase() == lower)
181 }
182
183 pub fn move_up(&mut self, id: &ArrangementId) {
185 if let Some(pos) = self.order.iter().position(|aid| aid == id)
186 && pos > 0
187 {
188 self.order.swap(pos, pos - 1);
189 self.update_orders();
190 }
191 }
192
193 pub fn move_down(&mut self, id: &ArrangementId) {
195 if let Some(pos) = self.order.iter().position(|aid| aid == id)
196 && pos < self.order.len() - 1
197 {
198 self.order.swap(pos, pos + 1);
199 self.update_orders();
200 }
201 }
202
203 fn sort_by_order(&mut self) {
205 self.order.sort_by_key(|id| {
206 self.arrangements
207 .get(id)
208 .map(|a| a.order)
209 .unwrap_or(usize::MAX)
210 });
211 }
212
213 fn update_orders(&mut self) {
215 for (i, id) in self.order.iter().enumerate() {
216 if let Some(arrangement) = self.arrangements.get_mut(id) {
217 arrangement.order = i;
218 }
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn make_arrangement(name: &str, order: usize) -> WindowArrangement {
228 WindowArrangement {
229 id: Uuid::new_v4(),
230 name: name.to_string(),
231 monitor_layout: Vec::new(),
232 windows: Vec::new(),
233 created_at: String::new(),
234 order,
235 }
236 }
237
238 #[test]
239 fn test_manager_basic_operations() {
240 let mut manager = ArrangementManager::new();
241 assert!(manager.is_empty());
242
243 let arr = make_arrangement("Test", 0);
244 let id = arr.id;
245 manager.add(arr);
246
247 assert_eq!(manager.len(), 1);
248 assert!(manager.get(&id).is_some());
249 assert_eq!(manager.get(&id).unwrap().name, "Test");
250
251 let removed = manager.remove(&id);
252 assert!(removed.is_some());
253 assert!(manager.is_empty());
254 }
255
256 #[test]
257 fn test_manager_ordering() {
258 let mut manager = ArrangementManager::new();
259
260 let a1 = make_arrangement("First", 0);
261 let a2 = make_arrangement("Second", 1);
262 let a3 = make_arrangement("Third", 2);
263
264 let id1 = a1.id;
265 let id2 = a2.id;
266 let id3 = a3.id;
267
268 manager.add(a1);
269 manager.add(a2);
270 manager.add(a3);
271
272 let ordered = manager.arrangements_ordered();
273 assert_eq!(ordered.len(), 3);
274 assert_eq!(ordered[0].id, id1);
275 assert_eq!(ordered[1].id, id2);
276 assert_eq!(ordered[2].id, id3);
277
278 manager.move_up(&id2);
280 let ordered = manager.arrangements_ordered();
281 assert_eq!(ordered[0].id, id2);
282 assert_eq!(ordered[1].id, id1);
283
284 manager.move_down(&id2);
286 let ordered = manager.arrangements_ordered();
287 assert_eq!(ordered[0].id, id1);
288 assert_eq!(ordered[1].id, id2);
289 }
290
291 #[test]
292 fn test_find_by_name() {
293 let mut manager = ArrangementManager::new();
294 manager.add(make_arrangement("Work Setup", 0));
295 manager.add(make_arrangement("Home Setup", 1));
296
297 assert!(manager.find_by_name("work setup").is_some());
298 assert!(manager.find_by_name("HOME SETUP").is_some());
299 assert!(manager.find_by_name("nonexistent").is_none());
300 }
301
302 #[test]
303 fn test_serialization() {
304 let arr = WindowArrangement {
305 id: Uuid::new_v4(),
306 name: "Test".to_string(),
307 monitor_layout: vec![MonitorInfo {
308 name: Some("DELL U2720Q".to_string()),
309 index: 0,
310 position: (0, 0),
311 size: (2560, 1440),
312 }],
313 windows: vec![WindowSnapshot {
314 monitor: MonitorInfo {
315 name: Some("DELL U2720Q".to_string()),
316 index: 0,
317 position: (0, 0),
318 size: (2560, 1440),
319 },
320 position_relative: (100, 200),
321 size: (800, 600),
322 tabs: vec![TabSnapshot {
323 cwd: Some("/home/user".to_string()),
324 title: "bash".to_string(),
325 }],
326 active_tab_index: 0,
327 }],
328 created_at: "2024-01-01T00:00:00Z".to_string(),
329 order: 0,
330 };
331
332 let yaml = serde_yaml::to_string(&arr).unwrap();
333 let deserialized: WindowArrangement = serde_yaml::from_str(&yaml).unwrap();
334
335 assert_eq!(deserialized.id, arr.id);
336 assert_eq!(deserialized.name, arr.name);
337 assert_eq!(deserialized.windows.len(), 1);
338 assert_eq!(deserialized.windows[0].tabs.len(), 1);
339 assert_eq!(
340 deserialized.windows[0].tabs[0].cwd,
341 Some("/home/user".to_string())
342 );
343 }
344}