astrelis_render/window_manager.rs
1///! WindowManager - Manages multiple windows and eliminates boilerplate
2///!
3///! This module provides a high-level abstraction for managing multiple windows,
4///! automatically handling common events like resizing and providing a clean API
5///! for rendering.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use astrelis_winit::{
11 WindowId,
12 app::AppCtx,
13 event::{Event, EventBatch, HandleStatus},
14 window::WindowDescriptor,
15};
16
17use crate::{
18 context::GraphicsContext,
19 window::{RenderableWindow, WindowContextDescriptor},
20};
21
22/// Manages multiple renderable windows with automatic event handling.
23///
24/// The WindowManager eliminates the boilerplate of manually managing a
25/// `HashMap<WindowId, RenderableWindow>` and handling common events like resizing.
26///
27/// # Example
28///
29/// ```no_run
30/// use astrelis_render::{WindowManager, GraphicsContext, RenderTarget, Color};
31/// use astrelis_winit::app::{App, AppCtx};
32/// use astrelis_winit::{WindowId, FrameTime};
33/// use astrelis_winit::window::WindowBackend;
34/// use astrelis_winit::event::EventBatch;
35///
36/// struct MyApp {
37/// window_manager: WindowManager,
38/// }
39///
40/// impl App for MyApp {
41/// fn update(&mut self, _ctx: &mut AppCtx, _time: &FrameTime) {}
42/// fn render(&mut self, _ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch) {
43/// self.window_manager.render_window(window_id, events, |window, _events| {
44/// // Resize already handled automatically!
45/// let mut frame = window.begin_drawing();
46/// frame.clear_and_render(RenderTarget::Surface, Color::BLACK, |_pass| {
47/// // Your rendering here
48/// });
49/// frame.finish();
50/// });
51/// }
52/// }
53/// ```
54pub struct WindowManager {
55 graphics: Arc<GraphicsContext>,
56 windows: HashMap<WindowId, RenderableWindow>,
57}
58
59impl WindowManager {
60 /// Creates a new WindowManager with the given graphics context.
61 ///
62 /// # Example
63 ///
64 /// ```no_run
65 /// use astrelis_render::{WindowManager, GraphicsContext};
66 /// use std::sync::Arc;
67 ///
68 /// let graphics = GraphicsContext::new_owned_sync_or_panic();
69 /// let window_manager = WindowManager::new(graphics);
70 /// ```
71 pub fn new(graphics: Arc<GraphicsContext>) -> Self {
72 Self {
73 graphics,
74 windows: HashMap::new(),
75 }
76 }
77
78 /// Creates a new window and adds it to the manager.
79 ///
80 /// Returns the WindowId of the created window.
81 ///
82 /// # Example
83 ///
84 /// ```no_run
85 /// use astrelis_render::WindowManager;
86 /// use astrelis_winit::window::{WindowDescriptor, WindowBackend};
87 ///
88 /// # fn example(window_manager: &mut WindowManager, ctx: &mut astrelis_winit::app::AppCtx) {
89 /// let window_id = window_manager.create_window(
90 /// ctx,
91 /// WindowDescriptor {
92 /// title: "My Window".to_string(),
93 /// ..Default::default()
94 /// },
95 /// );
96 /// # }
97 /// ```
98 pub fn create_window(&mut self, ctx: &mut AppCtx, descriptor: WindowDescriptor) -> Result<WindowId, crate::context::GraphicsError> {
99 self.create_window_with_descriptor(ctx, descriptor, WindowContextDescriptor::default())
100 }
101
102 /// Creates a new window with a custom rendering context descriptor.
103 ///
104 /// # Example
105 ///
106 /// ```no_run
107 /// use astrelis_render::{WindowManager, WindowContextDescriptor, wgpu};
108 /// use astrelis_winit::window::{WindowDescriptor, WindowBackend};
109 ///
110 /// # fn example(window_manager: &mut WindowManager, ctx: &mut astrelis_winit::app::AppCtx) {
111 /// let window_id = window_manager.create_window_with_descriptor(
112 /// ctx,
113 /// WindowDescriptor::default(),
114 /// WindowContextDescriptor {
115 /// present_mode: Some(wgpu::PresentMode::Mailbox),
116 /// ..Default::default()
117 /// },
118 /// );
119 /// # }
120 /// ```
121 pub fn create_window_with_descriptor(
122 &mut self,
123 ctx: &mut AppCtx,
124 descriptor: WindowDescriptor,
125 window_descriptor: WindowContextDescriptor,
126 ) -> Result<WindowId, crate::context::GraphicsError> {
127 let window = ctx.create_window(descriptor).expect("Failed to create window");
128 let id = window.id();
129 let renderable = RenderableWindow::new_with_descriptor(window, self.graphics.clone(), window_descriptor)?;
130 self.windows.insert(id, renderable);
131 Ok(id)
132 }
133
134 /// Gets a reference to a window by its ID.
135 ///
136 /// Returns `None` if the window doesn't exist.
137 pub fn get_window(&self, id: WindowId) -> Option<&RenderableWindow> {
138 self.windows.get(&id)
139 }
140
141 /// Gets a mutable reference to a window by its ID.
142 ///
143 /// Returns `None` if the window doesn't exist.
144 pub fn get_window_mut(&mut self, id: WindowId) -> Option<&mut RenderableWindow> {
145 self.windows.get_mut(&id)
146 }
147
148 /// Removes a window from the manager.
149 ///
150 /// Returns the removed window if it existed.
151 pub fn remove_window(&mut self, id: WindowId) -> Option<RenderableWindow> {
152 self.windows.remove(&id)
153 }
154
155 /// Returns the number of windows being managed.
156 pub fn window_count(&self) -> usize {
157 self.windows.len()
158 }
159
160 /// Returns an iterator over all window IDs.
161 pub fn window_ids(&self) -> impl Iterator<Item = WindowId> + '_ {
162 self.windows.keys().copied()
163 }
164
165 /// Renders a window with automatic event handling.
166 ///
167 /// This method:
168 /// 1. Automatically handles common events (resize, etc.)
169 /// 2. Calls your render closure with the window and remaining events
170 /// 3. Returns immediately if the window doesn't exist
171 ///
172 /// # Example
173 ///
174 /// ```no_run
175 /// use astrelis_render::{WindowManager, RenderTarget, Color};
176 /// use astrelis_winit::window::WindowBackend;
177 ///
178 /// # fn example(window_manager: &mut WindowManager, window_id: astrelis_winit::WindowId, events: &mut astrelis_winit::event::EventBatch) {
179 /// window_manager.render_window(window_id, events, |window, events| {
180 /// // Handle custom events if needed
181 /// events.dispatch(|_event| {
182 /// // Your event handling
183 /// astrelis_winit::event::HandleStatus::ignored()
184 /// });
185 ///
186 /// // Render
187 /// let mut frame = window.begin_drawing();
188 /// frame.clear_and_render(RenderTarget::Surface, Color::BLACK, |_pass| {
189 /// // Your rendering
190 /// });
191 /// frame.finish();
192 /// });
193 /// # }
194 /// ```
195 pub fn render_window<F>(&mut self, id: WindowId, events: &mut EventBatch, mut render_fn: F)
196 where
197 F: FnMut(&mut RenderableWindow, &mut EventBatch),
198 {
199 let Some(window) = self.windows.get_mut(&id) else {
200 return;
201 };
202
203 // Handle common events automatically
204 events.dispatch(|event| {
205 match event {
206 Event::WindowResized(size) => {
207 window.resized(*size);
208 HandleStatus::consumed()
209 }
210 _ => HandleStatus::ignored(),
211 }
212 });
213
214 // Call user's render function with remaining events
215 render_fn(window, events);
216 }
217
218 /// Renders a window with automatic event handling, passing a closure that returns a result.
219 ///
220 /// This is useful when rendering might fail and you want to propagate errors.
221 ///
222 /// # Example
223 ///
224 /// ```no_run
225 /// use astrelis_render::{WindowManager, RenderTarget, Color};
226 /// use astrelis_winit::window::WindowBackend;
227 ///
228 /// # fn example(window_manager: &mut WindowManager, window_id: astrelis_winit::WindowId, events: &mut astrelis_winit::event::EventBatch) -> Result<(), String> {
229 /// window_manager.render_window_result(window_id, events, |window, _events| {
230 /// let mut frame = window.begin_drawing();
231 /// frame.clear_and_render(RenderTarget::Surface, Color::BLACK, |_pass| {
232 /// // Rendering that might fail
233 /// });
234 /// frame.finish();
235 /// Ok(())
236 /// })
237 /// # }
238 /// ```
239 pub fn render_window_result<F, E>(&mut self, id: WindowId, events: &mut EventBatch, mut render_fn: F) -> Result<(), E>
240 where
241 F: FnMut(&mut RenderableWindow, &mut EventBatch) -> Result<(), E>,
242 {
243 let Some(window) = self.windows.get_mut(&id) else {
244 // Window doesn't exist - not an error, just skip
245 return Ok(());
246 };
247
248 // Handle common events automatically
249 events.dispatch(|event| {
250 match event {
251 Event::WindowResized(size) => {
252 window.resized(*size);
253 HandleStatus::consumed()
254 }
255 _ => HandleStatus::ignored(),
256 }
257 });
258
259 // Call user's render function with remaining events
260 render_fn(window, events)
261 }
262
263 /// Gets the shared graphics context.
264 pub fn graphics(&self) -> &Arc<GraphicsContext> {
265 &self.graphics
266 }
267
268 /// Iterates over all windows with their IDs.
269 pub fn iter(&self) -> impl Iterator<Item = (WindowId, &RenderableWindow)> {
270 self.windows.iter().map(|(&id, window)| (id, window))
271 }
272
273 /// Iterates mutably over all windows with their IDs.
274 pub fn iter_mut(&mut self) -> impl Iterator<Item = (WindowId, &mut RenderableWindow)> {
275 self.windows.iter_mut().map(|(&id, window)| (id, window))
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_window_manager_creation() {
285 let graphics = GraphicsContext::new_owned_sync_or_panic();
286 let manager = WindowManager::new(graphics.clone());
287
288 assert_eq!(manager.window_count(), 0);
289 assert_eq!(manager.graphics().as_ref() as *const _, graphics.as_ref() as *const _);
290 }
291
292 #[test]
293 fn test_window_manager_window_count() {
294 let graphics = GraphicsContext::new_owned_sync_or_panic();
295 let manager = WindowManager::new(graphics);
296
297 assert_eq!(manager.window_count(), 0);
298
299 // Note: We can't actually create windows without a running event loop,
300 // so this test just verifies the count starts at 0
301 }
302
303 #[test]
304 fn test_window_manager_window_ids_empty() {
305 let graphics = GraphicsContext::new_owned_sync_or_panic();
306 let manager = WindowManager::new(graphics);
307
308 assert_eq!(manager.window_ids().count(), 0);
309 }
310}