Skip to main content

mittens_engine/engine/
windowing.rs

1use std::sync::Arc;
2use std::time::Instant;
3
4use crate::engine::startup_trace::{StartupCheckpoint, log_startup_progress};
5use crate::engine::user_input::UserInput;
6use crate::engine::{EngineError, EngineResult};
7
8use winit::application::ApplicationHandler;
9use winit::event::{ElementState, KeyEvent, WindowEvent};
10use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
11use winit::keyboard::{Key, NamedKey};
12use winit::window::{Window, WindowAttributes, WindowId};
13
14/// Minimal winit wrapper (2025 winit style: ApplicationHandler).
15pub struct Windowing;
16
17impl Windowing {
18    /// Run a Universe in a winit window with default input handling.
19    pub fn run_app(universe: crate::engine::Universe) -> EngineResult<()> {
20        Self::run_app_with_input(universe, UserInput::new())
21    }
22
23    /// Run a Universe in a winit window with a caller-provided `UserInput`.
24    pub fn run_app_with_input(
25        universe: crate::engine::Universe,
26        user_input: UserInput,
27    ) -> EngineResult<()> {
28        let event_loop = EventLoop::new()
29            .map_err(|error| EngineError::Windowing(format!("event loop init failed: {error}")))?;
30        event_loop.set_control_flow(ControlFlow::Poll);
31
32        let mut app = App {
33            window: None,
34            universe: Some(universe),
35            last_frame: None,
36            user_input,
37            startup_error: None,
38        };
39
40        event_loop.run_app(&mut app).map_err(|error| {
41            EngineError::Windowing(format!("window event loop failed: {error}"))
42        })?;
43
44        if let Some(error) = app.startup_error.take() {
45            return Err(error);
46        }
47
48        Ok(())
49    }
50}
51
52struct App {
53    window: Option<Arc<Window>>,
54    universe: Option<crate::engine::Universe>,
55    last_frame: Option<Instant>,
56    user_input: UserInput,
57    startup_error: Option<EngineError>,
58}
59
60impl ApplicationHandler for App {
61    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
62        if self.window.is_some() {
63            return;
64        }
65        log_startup_progress(StartupCheckpoint::EventLoopResumed);
66
67        let preferred_window_size = self
68            .universe
69            .as_mut()
70            .and_then(|universe| universe.take_preferred_window_size())
71            .unwrap_or([1024, 768]);
72
73        let attrs: WindowAttributes = Window::default_attributes()
74            .with_title("mittens 0.5.1")
75            .with_inner_size(winit::dpi::LogicalSize::new(
76                preferred_window_size[0] as f64,
77                preferred_window_size[1] as f64,
78            ))
79            .with_resizable(true);
80
81        let window = match event_loop.create_window(attrs) {
82            Ok(window) => window,
83            Err(error) => {
84                self.startup_error = Some(EngineError::Windowing(format!(
85                    "window creation failed: {error}"
86                )));
87                event_loop.exit();
88                return;
89            }
90        };
91        let window = Arc::new(window);
92        log_startup_progress(StartupCheckpoint::WindowCreated);
93
94        // Initialize renderer backend for this window via Universe
95        if let Some(universe) = self.universe.as_mut() {
96            if let Err(error) = universe.init_renderer_for_window(&window) {
97                self.startup_error = Some(EngineError::Windowing(format!(
98                    "renderer init failed: {error}"
99                )));
100                event_loop.exit();
101                return;
102            }
103        }
104        log_startup_progress(StartupCheckpoint::RendererInitialized);
105
106        self.window = Some(window);
107        self.last_frame = Some(Instant::now());
108
109        if let Some(w) = &self.window {
110            w.request_redraw();
111        }
112    }
113
114    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
115        // Feed input events into our input handler, but keep window lifecycle/render events here.
116        // This intentionally ignores resize/draw.
117        let _was_input_event = self.user_input.handle_window_event(&event);
118
119        match event {
120            WindowEvent::CloseRequested => event_loop.exit(),
121
122            WindowEvent::KeyboardInput {
123                event:
124                    KeyEvent {
125                        logical_key: Key::Named(NamedKey::Escape),
126                        state: ElementState::Pressed,
127                        ..
128                    },
129                ..
130            } => event_loop.exit(),
131
132            WindowEvent::Resized(size) => {
133                if let Some(universe) = self.universe.as_mut() {
134                    universe.resize_renderer(size);
135                }
136                if let Some(w) = &self.window {
137                    // w.pre_present_notify();
138                    w.request_redraw();
139                }
140            }
141
142            WindowEvent::RedrawRequested => {
143                log_startup_progress(StartupCheckpoint::FirstRedrawRequested);
144                // Start of our "frame" from an input perspective: compute deltas, but keep
145                // edge-triggered sets so they remain visible during `universe.update`.
146                self.user_input.start_frame();
147
148                let now = Instant::now();
149                let dt = self
150                    .last_frame
151                    .replace(now)
152                    .map(|prev| (now - prev).as_secs_f32())
153                    .unwrap_or(0.0);
154
155                let universe = self.universe.as_mut().expect("universe missing");
156
157                universe.update(dt, self.user_input.state());
158
159                universe.render();
160
161                // Clear edge-triggered sets after the frame has consumed them.
162                self.user_input.end_frame();
163
164                if let Some(w) = &self.window {
165                    // w.pre_present_notify();
166                    w.request_redraw();
167                }
168            }
169
170            _ => {}
171        }
172    }
173}