1use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10
11use crate::{Rect, UiError, WidgetTree};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct WindowId(pub u64);
21
22impl WindowId {
23 pub const PRIMARY: Self = WindowId(1);
25}
26
27impl std::fmt::Display for WindowId {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(f, "Window({})", self.0)
30 }
31}
32
33#[derive(Clone, Debug)]
37pub struct WindowConfig {
38 pub title: String,
40 pub width: f32,
42 pub height: f32,
44 pub resizable: bool,
46 pub decorations: bool,
48 pub transparent: bool,
50 pub always_on_top: bool,
52}
53
54impl Default for WindowConfig {
55 fn default() -> Self {
56 WindowConfig {
57 title: String::new(),
58 width: 800.0,
59 height: 600.0,
60 resizable: true,
61 decorations: true,
62 transparent: false,
63 always_on_top: false,
64 }
65 }
66}
67
68impl WindowConfig {
69 pub fn new(title: impl Into<String>) -> Self {
71 WindowConfig {
72 title: title.into(),
73 ..Default::default()
74 }
75 }
76
77 pub fn width(mut self, w: f32) -> Self {
79 self.width = w;
80 self
81 }
82
83 pub fn height(mut self, h: f32) -> Self {
85 self.height = h;
86 self
87 }
88
89 pub fn resizable(mut self, r: bool) -> Self {
91 self.resizable = r;
92 self
93 }
94
95 pub fn decorations(mut self, d: bool) -> Self {
97 self.decorations = d;
98 self
99 }
100
101 pub fn transparent(mut self, t: bool) -> Self {
103 self.transparent = t;
104 self
105 }
106
107 pub fn always_on_top(mut self, a: bool) -> Self {
109 self.always_on_top = a;
110 self
111 }
112}
113
114#[derive(Clone, Debug)]
118#[non_exhaustive]
119pub enum WindowEvent {
120 Created(WindowId),
122 Closed(WindowId),
124 Resized {
126 id: WindowId,
128 width: f32,
130 height: f32,
132 },
133 FocusGained(WindowId),
135 FocusLost(WindowId),
137 Message {
139 from: WindowId,
141 to: WindowId,
143 payload: String,
145 },
146}
147
148#[derive(Clone, Debug, Default)]
156pub struct WindowChannel {
157 queues: Arc<Mutex<HashMap<WindowId, Vec<String>>>>,
158}
159
160impl WindowChannel {
161 pub fn new() -> Self {
163 WindowChannel::default()
164 }
165
166 pub fn send(&self, to: WindowId, payload: impl Into<String>) -> Result<(), UiError> {
173 let mut guard = self
174 .queues
175 .lock()
176 .map_err(|_| UiError::Focus("window-channel lock poisoned".into()))?;
177 guard.entry(to).or_default().push(payload.into());
178 Ok(())
179 }
180
181 pub fn drain_messages(&self, id: WindowId) -> Result<Vec<String>, UiError> {
188 let mut guard = self
189 .queues
190 .lock()
191 .map_err(|_| UiError::Focus("window-channel lock poisoned".into()))?;
192 Ok(guard.remove(&id).unwrap_or_default())
193 }
194
195 pub fn pending_count(&self, id: WindowId) -> usize {
197 self.queues
198 .lock()
199 .map(|g| g.get(&id).map_or(0, Vec::len))
200 .unwrap_or(0)
201 }
202}
203
204pub struct WindowManager {
213 next_id: u64,
214 trees: HashMap<WindowId, WidgetTree>,
215 configs: HashMap<WindowId, WindowConfig>,
216 channel: WindowChannel,
217}
218
219impl WindowManager {
220 pub fn new(primary_rect: Rect) -> Self {
222 let mut trees = HashMap::new();
223 let mut configs = HashMap::new();
224 trees.insert(WindowId::PRIMARY, WidgetTree::new(primary_rect));
225 configs.insert(WindowId::PRIMARY, WindowConfig::new("Main"));
226 WindowManager {
227 next_id: 2,
228 trees,
229 configs,
230 channel: WindowChannel::new(),
231 }
232 }
233
234 pub fn create_window(&mut self, config: WindowConfig) -> WindowId {
239 let id = WindowId(self.next_id);
240 self.next_id += 1;
241 let rect = Rect::new(0.0, 0.0, config.width, config.height);
242 self.trees.insert(id, WidgetTree::new(rect));
243 self.configs.insert(id, config);
244 id
245 }
246
247 pub fn destroy_window(&mut self, id: WindowId) -> Result<(), UiError> {
253 if id == WindowId::PRIMARY {
254 return Err(UiError::Focus("cannot destroy the primary window".into()));
255 }
256 if self.trees.remove(&id).is_none() {
257 return Err(UiError::Focus(format!("window {id} not found")));
258 }
259 self.configs.remove(&id);
260 Ok(())
261 }
262
263 pub fn tree(&self, id: WindowId) -> Option<&WidgetTree> {
265 self.trees.get(&id)
266 }
267
268 pub fn tree_mut(&mut self, id: WindowId) -> Option<&mut WidgetTree> {
270 self.trees.get_mut(&id)
271 }
272
273 pub fn config(&self, id: WindowId) -> Option<&WindowConfig> {
275 self.configs.get(&id)
276 }
277
278 pub fn window_ids(&self) -> Vec<WindowId> {
280 let mut ids: Vec<WindowId> = self.trees.keys().copied().collect();
281 ids.sort();
282 ids
283 }
284
285 pub fn window_count(&self) -> usize {
287 self.trees.len()
288 }
289
290 pub fn channel(&self) -> &WindowChannel {
292 &self.channel
293 }
294
295 pub fn resize_window(&mut self, id: WindowId, width: f32, height: f32) {
301 if let Some(cfg) = self.configs.get_mut(&id) {
302 cfg.width = width;
303 cfg.height = height;
304 }
305 if let Some(tree) = self.trees.get_mut(&id) {
306 *tree = WidgetTree::new(Rect::new(0.0, 0.0, width, height));
307 }
308 }
309}
310
311impl Default for WindowManager {
312 fn default() -> Self {
313 Self::new(Rect::new(0.0, 0.0, 800.0, 600.0))
314 }
315}
316
317#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn primary_window_always_present() {
325 let mgr = WindowManager::default();
326 assert!(mgr.tree(WindowId::PRIMARY).is_some());
327 assert_eq!(mgr.window_count(), 1);
328 }
329
330 #[test]
331 fn create_window_returns_unique_ids() {
332 let mut mgr = WindowManager::default();
333 let id1 = mgr.create_window(WindowConfig::new("w1"));
334 let id2 = mgr.create_window(WindowConfig::new("w2"));
335 assert_ne!(id1, id2);
336 assert_ne!(id1, WindowId::PRIMARY);
337 assert_ne!(id2, WindowId::PRIMARY);
338 }
339
340 #[test]
341 fn create_and_destroy_window() {
342 let mut mgr = WindowManager::default();
343 let id = mgr.create_window(WindowConfig::new("secondary"));
344 assert_eq!(mgr.window_count(), 2);
345 mgr.destroy_window(id).expect("destroy ok");
346 assert_eq!(mgr.window_count(), 1);
347 assert!(mgr.tree(id).is_none());
348 }
349
350 #[test]
351 fn destroy_primary_window_is_err() {
352 let mut mgr = WindowManager::default();
353 assert!(mgr.destroy_window(WindowId::PRIMARY).is_err());
354 }
355
356 #[test]
357 fn destroy_nonexistent_window_is_err() {
358 let mut mgr = WindowManager::default();
359 assert!(mgr.destroy_window(WindowId(99)).is_err());
360 }
361
362 #[test]
363 fn window_ids_sorted() {
364 let mut mgr = WindowManager::default();
365 let id2 = mgr.create_window(WindowConfig::default());
366 let id3 = mgr.create_window(WindowConfig::default());
367 let ids = mgr.window_ids();
368 assert_eq!(ids[0], WindowId::PRIMARY);
369 assert!(ids.contains(&id2));
370 assert!(ids.contains(&id3));
371 for w in ids.windows(2) {
373 assert!(w[0] < w[1]);
374 }
375 }
376
377 #[test]
378 fn window_channel_send_and_drain() {
379 let ch = WindowChannel::new();
380 let wid = WindowId(10);
381 ch.send(wid, "hello").unwrap();
382 ch.send(wid, "world").unwrap();
383 let msgs = ch.drain_messages(wid).unwrap();
384 assert_eq!(msgs, vec!["hello", "world"]);
385 assert_eq!(ch.pending_count(wid), 0);
387 }
388
389 #[test]
390 fn window_channel_pending_count() {
391 let ch = WindowChannel::new();
392 let wid = WindowId(20);
393 assert_eq!(ch.pending_count(wid), 0);
394 ch.send(wid, "a").unwrap();
395 ch.send(wid, "b").unwrap();
396 assert_eq!(ch.pending_count(wid), 2);
397 ch.drain_messages(wid).unwrap();
398 assert_eq!(ch.pending_count(wid), 0);
399 }
400
401 #[test]
402 fn window_channel_clone_shares_state() {
403 let ch = WindowChannel::new();
404 let ch2 = ch.clone();
405 let wid = WindowId(30);
406 ch.send(wid, "msg").unwrap();
407 assert_eq!(ch2.pending_count(wid), 1);
409 let msgs = ch2.drain_messages(wid).unwrap();
410 assert_eq!(msgs, vec!["msg"]);
411 }
412
413 #[test]
414 fn window_channel_separate_queues_per_window() {
415 let ch = WindowChannel::new();
416 let a = WindowId(40);
417 let b = WindowId(41);
418 ch.send(a, "for-a").unwrap();
419 ch.send(b, "for-b").unwrap();
420 assert_eq!(ch.pending_count(a), 1);
421 assert_eq!(ch.pending_count(b), 1);
422 let drained_a = ch.drain_messages(a).unwrap();
423 assert_eq!(drained_a, vec!["for-a"]);
424 assert_eq!(ch.pending_count(b), 1); }
426
427 #[test]
428 fn window_config_builder() {
429 let cfg = WindowConfig::new("Test")
430 .width(1024.0)
431 .height(768.0)
432 .resizable(false)
433 .decorations(false)
434 .transparent(true)
435 .always_on_top(true);
436 assert_eq!(cfg.title, "Test");
437 assert_eq!(cfg.width, 1024.0);
438 assert_eq!(cfg.height, 768.0);
439 assert!(!cfg.resizable);
440 assert!(!cfg.decorations);
441 assert!(cfg.transparent);
442 assert!(cfg.always_on_top);
443 }
444
445 #[test]
446 fn resize_window_updates_config_and_tree() {
447 let mut mgr = WindowManager::default();
448 mgr.resize_window(WindowId::PRIMARY, 1920.0, 1080.0);
449 let cfg = mgr.config(WindowId::PRIMARY).unwrap();
450 assert_eq!(cfg.width, 1920.0);
451 assert_eq!(cfg.height, 1080.0);
452 }
453
454 #[test]
455 fn window_event_debug_is_non_empty() {
456 let e = WindowEvent::Created(WindowId::PRIMARY);
457 let s = format!("{e:?}");
458 assert!(!s.is_empty());
459 }
460
461 #[test]
462 fn window_id_display() {
463 assert_eq!(format!("{}", WindowId::PRIMARY), "Window(1)");
464 assert_eq!(format!("{}", WindowId(42)), "Window(42)");
465 }
466
467 #[test]
468 fn window_id_ordering() {
469 let mut ids = vec![WindowId(3), WindowId(1), WindowId(2)];
470 ids.sort();
471 assert_eq!(ids, vec![WindowId(1), WindowId(2), WindowId(3)]);
472 }
473}