Skip to main content

agpu/
multiwindow.rs

1//! Multi-window support — manage multiple windows from a single application.
2//!
3//! Provides [`WindowId`], [`WindowConfig`], and [`WindowManager`] for
4//! creating and tracking multiple windows.
5
6use crate::core::{Rect, Size};
7
8/// Unique identifier for a window.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct WindowId(u64);
11
12impl WindowId {
13    pub fn new(id: u64) -> Self {
14        Self(id)
15    }
16
17    pub fn raw(&self) -> u64 {
18        self.0
19    }
20}
21
22impl std::fmt::Display for WindowId {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "Window({})", self.0)
25    }
26}
27
28/// Configuration for creating a new window.
29#[derive(Debug, Clone)]
30pub struct WindowConfig {
31    pub title: String,
32    pub size: Size,
33    pub resizable: bool,
34    pub decorations: bool,
35    pub transparent: bool,
36    pub always_on_top: bool,
37}
38
39impl WindowConfig {
40    pub fn new(title: impl Into<String>) -> Self {
41        Self {
42            title: title.into(),
43            size: Size::new(800.0, 600.0),
44            resizable: true,
45            decorations: true,
46            transparent: false,
47            always_on_top: false,
48        }
49    }
50
51    pub fn size(mut self, width: f32, height: f32) -> Self {
52        self.size = Size::new(width, height);
53        self
54    }
55
56    pub fn resizable(mut self, resizable: bool) -> Self {
57        self.resizable = resizable;
58        self
59    }
60
61    pub fn decorations(mut self, decorations: bool) -> Self {
62        self.decorations = decorations;
63        self
64    }
65
66    pub fn transparent(mut self, transparent: bool) -> Self {
67        self.transparent = transparent;
68        self
69    }
70
71    pub fn always_on_top(mut self, on_top: bool) -> Self {
72        self.always_on_top = on_top;
73        self
74    }
75}
76
77impl Default for WindowConfig {
78    fn default() -> Self {
79        Self::new("Untitled")
80    }
81}
82
83/// State of a managed window.
84#[derive(Debug, Clone)]
85pub struct WindowState {
86    pub id: WindowId,
87    pub config: WindowConfig,
88    pub area: Rect,
89    pub focused: bool,
90    pub visible: bool,
91}
92
93/// Manages multiple windows and their lifecycle.
94pub struct WindowManager {
95    windows: Vec<WindowState>,
96    next_id: u64,
97    focused: Option<WindowId>,
98}
99
100impl WindowManager {
101    pub fn new() -> Self {
102        Self {
103            windows: Vec::new(),
104            next_id: 1,
105            focused: None,
106        }
107    }
108
109    /// Create a new window and return its ID.
110    pub fn create(&mut self, config: WindowConfig) -> WindowId {
111        let id = WindowId::new(self.next_id);
112        self.next_id += 1;
113        let area = Rect::new(0.0, 0.0, config.size.width, config.size.height);
114        self.windows.push(WindowState {
115            id,
116            config,
117            area,
118            focused: false,
119            visible: true,
120        });
121        id
122    }
123
124    /// Close a window by ID. Returns true if the window existed.
125    pub fn close(&mut self, id: WindowId) -> bool {
126        let len_before = self.windows.len();
127        self.windows.retain(|w| w.id != id);
128        if self.focused == Some(id) {
129            self.focused = self.windows.last().map(|w| w.id);
130        }
131        self.windows.len() < len_before
132    }
133
134    /// Focus a window by ID.
135    pub fn focus(&mut self, id: WindowId) {
136        for w in &mut self.windows {
137            w.focused = w.id == id;
138        }
139        self.focused = Some(id);
140    }
141
142    /// Get the currently focused window ID.
143    pub fn focused(&self) -> Option<WindowId> {
144        self.focused
145    }
146
147    /// Get all window states.
148    pub fn windows(&self) -> &[WindowState] {
149        &self.windows
150    }
151
152    /// Get a window state by ID.
153    pub fn get(&self, id: WindowId) -> Option<&WindowState> {
154        self.windows.iter().find(|w| w.id == id)
155    }
156
157    /// Get a mutable window state by ID.
158    pub fn get_mut(&mut self, id: WindowId) -> Option<&mut WindowState> {
159        self.windows.iter_mut().find(|w| w.id == id)
160    }
161
162    /// Number of open windows.
163    pub fn count(&self) -> usize {
164        self.windows.len()
165    }
166}
167
168impl Default for WindowManager {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn window_id_display() {
180        let id = WindowId::new(42);
181        assert_eq!(format!("{id}"), "Window(42)");
182    }
183
184    #[test]
185    fn window_config_defaults() {
186        let cfg = WindowConfig::default();
187        assert!(cfg.resizable);
188        assert!(cfg.decorations);
189        assert!(!cfg.transparent);
190    }
191
192    #[test]
193    fn window_config_builder() {
194        let cfg = WindowConfig::new("Test")
195            .size(1024.0, 768.0)
196            .resizable(false)
197            .always_on_top(true);
198        assert_eq!(cfg.title, "Test");
199        assert!(!cfg.resizable);
200        assert!(cfg.always_on_top);
201    }
202
203    #[test]
204    fn window_manager_create() {
205        let mut wm = WindowManager::new();
206        let id = wm.create(WindowConfig::new("Main"));
207        assert_eq!(wm.count(), 1);
208        assert!(wm.get(id).is_some());
209    }
210
211    #[test]
212    fn window_manager_multiple() {
213        let mut wm = WindowManager::new();
214        let id1 = wm.create(WindowConfig::new("One"));
215        let id2 = wm.create(WindowConfig::new("Two"));
216        assert_ne!(id1, id2);
217        assert_eq!(wm.count(), 2);
218    }
219
220    #[test]
221    fn window_manager_close() {
222        let mut wm = WindowManager::new();
223        let id = wm.create(WindowConfig::new("X"));
224        assert!(wm.close(id));
225        assert_eq!(wm.count(), 0);
226        assert!(!wm.close(id)); // already closed
227    }
228
229    #[test]
230    fn window_manager_focus() {
231        let mut wm = WindowManager::new();
232        let id1 = wm.create(WindowConfig::new("A"));
233        let id2 = wm.create(WindowConfig::new("B"));
234        wm.focus(id1);
235        assert_eq!(wm.focused(), Some(id1));
236        wm.focus(id2);
237        assert_eq!(wm.focused(), Some(id2));
238        assert!(!wm.get(id1).unwrap().focused);
239        assert!(wm.get(id2).unwrap().focused);
240    }
241
242    #[test]
243    fn window_manager_close_focused_shifts() {
244        let mut wm = WindowManager::new();
245        let id1 = wm.create(WindowConfig::new("A"));
246        let id2 = wm.create(WindowConfig::new("B"));
247        wm.focus(id1);
248        wm.close(id1);
249        assert_eq!(wm.focused(), Some(id2));
250    }
251}