1use std::cell::RefCell;
2use std::path::PathBuf;
3use std::rc::Rc;
4use std::sync::Arc;
5use std::time::Instant;
6
7use anyhow::Result;
8use winit::application::ApplicationHandler;
9use winit::event::{ElementState, KeyEvent, WindowEvent};
10use winit::event_loop::{ActiveEventLoop, EventLoop};
11use winit::keyboard::{Key, NamedKey};
12use winit::window::{Window, WindowId};
13
14use crate::renderer::Renderer;
15use crate::renderer::camera::CameraBounds;
16
17use super::input::InputState;
18use super::touch::{TouchState, TouchPhase};
19
20pub struct RenderState {
22 pub renderer: Option<Renderer>,
23 pub input: InputState,
24 pub touch: TouchState,
25 pub sprite_commands: Vec<crate::renderer::SpriteCommand>,
26 pub camera_x: f32,
27 pub camera_y: f32,
28 pub camera_zoom: f32,
29 pub camera_bounds: Option<CameraBounds>,
30 pub delta_time: f64,
31 pub pending_capture_tx: Option<crate::agent::ResponseSender>,
33}
34
35impl RenderState {
36 pub fn new() -> Self {
37 Self {
38 renderer: None,
39 input: InputState::default(),
40 touch: TouchState::default(),
41 sprite_commands: Vec::new(),
42 camera_x: 0.0,
43 camera_y: 0.0,
44 camera_zoom: 1.0,
45 camera_bounds: None,
46 delta_time: 0.0,
47 pending_capture_tx: None,
48 }
49 }
50}
51
52pub struct DevConfig {
54 pub entry_file: PathBuf,
55 pub title: String,
56 pub width: u32,
57 pub height: u32,
58}
59
60pub type FrameCallback = Box<dyn FnMut(&mut RenderState) -> Result<()>>;
63
64struct AppState {
65 window: Option<Arc<Window>>,
66 config: DevConfig,
67 render_state: Rc<RefCell<RenderState>>,
68 frame_callback: FrameCallback,
69 last_frame: Instant,
70 scale_factor: f64,
72}
73
74impl ApplicationHandler for AppState {
75 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
76 if self.window.is_some() {
77 return;
78 }
79
80 let attrs = Window::default_attributes()
81 .with_title(&self.config.title)
82 .with_inner_size(winit::dpi::LogicalSize::new(
83 self.config.width,
84 self.config.height,
85 ));
86
87 let window = Arc::new(
88 event_loop
89 .create_window(attrs)
90 .expect("Failed to create window"),
91 );
92
93 self.scale_factor = window.scale_factor();
94
95 match Renderer::new(window.clone()) {
96 Ok(renderer) => {
97 self.render_state.borrow_mut().renderer = Some(renderer);
98 }
99 Err(e) => {
100 eprintln!("Failed to initialize renderer: {e}");
101 event_loop.exit();
102 return;
103 }
104 }
105
106 self.window = Some(window);
107 self.last_frame = Instant::now();
108 }
109
110 fn window_event(
111 &mut self,
112 event_loop: &ActiveEventLoop,
113 _window_id: WindowId,
114 event: WindowEvent,
115 ) {
116 match event {
117 WindowEvent::CloseRequested => {
118 event_loop.exit();
119 }
120
121 WindowEvent::Resized(new_size) => {
122 let mut state = self.render_state.borrow_mut();
123 if let Some(ref mut renderer) = state.renderer {
124 renderer.resize(new_size.width, new_size.height, self.scale_factor as f32);
125 }
126 }
127
128 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
129 self.scale_factor = scale_factor;
130 }
131
132 WindowEvent::KeyboardInput {
133 event:
134 KeyEvent {
135 logical_key,
136 state: key_state,
137 ..
138 },
139 ..
140 } => {
141 let key_name = key_to_string(&logical_key);
142 let mut state = self.render_state.borrow_mut();
143 match key_state {
144 ElementState::Pressed => state.input.key_down(&key_name),
145 ElementState::Released => state.input.key_up(&key_name),
146 }
147 }
148
149 WindowEvent::CursorMoved { position, .. } => {
150 let logical_x = position.x as f32 / self.scale_factor as f32;
152 let logical_y = position.y as f32 / self.scale_factor as f32;
153 let mut state = self.render_state.borrow_mut();
154 state.input.mouse_move(logical_x, logical_y);
155 }
156
157 WindowEvent::MouseInput { state: button_state, button, .. } => {
158 let mut state = self.render_state.borrow_mut();
159 let button_id: u8 = match button {
160 winit::event::MouseButton::Left => 0,
161 winit::event::MouseButton::Right => 1,
162 winit::event::MouseButton::Middle => 2,
163 winit::event::MouseButton::Back => 3,
164 winit::event::MouseButton::Forward => 4,
165 winit::event::MouseButton::Other(id) => id.min(255) as u8,
166 };
167 match button_state {
168 ElementState::Pressed => {
169 state.input.mouse_button_down(button_id);
170 let key_name = match button_id {
172 0 => "MouseLeft",
173 1 => "MouseRight",
174 2 => "MouseMiddle",
175 _ => return,
176 };
177 state.input.key_down(key_name);
178 }
179 ElementState::Released => {
180 state.input.mouse_button_up(button_id);
181 let key_name = match button_id {
182 0 => "MouseLeft",
183 1 => "MouseRight",
184 2 => "MouseMiddle",
185 _ => return,
186 };
187 state.input.key_up(key_name);
188 }
189 }
190 }
191
192 WindowEvent::Touch(touch) => {
193 let logical_x = touch.location.x as f32 / self.scale_factor as f32;
194 let logical_y = touch.location.y as f32 / self.scale_factor as f32;
195 let phase = match touch.phase {
196 winit::event::TouchPhase::Started => TouchPhase::Start,
197 winit::event::TouchPhase::Moved => TouchPhase::Move,
198 winit::event::TouchPhase::Ended => TouchPhase::End,
199 winit::event::TouchPhase::Cancelled => TouchPhase::Cancel,
200 };
201 let now = self.last_frame.elapsed().as_secs_f64();
202 let mut state = self.render_state.borrow_mut();
203 state.touch.touch_event(touch.id, logical_x, logical_y, phase, now);
204 }
205
206 WindowEvent::RedrawRequested => {
207 let now = Instant::now();
208 let dt = now.duration_since(self.last_frame).as_secs_f64().min(0.25);
209 self.last_frame = now;
210
211 {
212 let mut state = self.render_state.borrow_mut();
213 state.delta_time = dt;
214 }
215
216 {
218 let mut state = self.render_state.borrow_mut();
219 if let Err(e) = (self.frame_callback)(&mut state) {
220 eprintln!("Frame callback error: {e}");
221 }
222 }
223
224 {
226 let mut state = self.render_state.borrow_mut();
227 state.input.begin_frame();
228 state.touch.begin_frame();
229 }
230
231 {
233 let mut state = self.render_state.borrow_mut();
234 let cam_x = state.camera_x;
236 let cam_y = state.camera_y;
237 let cam_zoom = state.camera_zoom;
238 let cam_bounds = state.camera_bounds;
239 let commands = std::mem::take(&mut state.sprite_commands);
240
241 if let Some(ref mut renderer) = state.renderer {
242 renderer.camera.x = cam_x;
243 renderer.camera.y = cam_y;
244 renderer.camera.zoom = cam_zoom;
245 renderer.camera.bounds = cam_bounds;
246 renderer.camera.clamp_to_bounds();
247 renderer.frame_commands = commands;
248
249 if let Err(e) = renderer.render_frame() {
250 eprintln!("Render error: {e}");
251 }
252
253 if let Some(png_bytes) = renderer.capture_result.take() {
255 if let Some(tx) = state.pending_capture_tx.take() {
256 let b64 = crate::agent::mcp::base64_encode(&png_bytes);
257 let resp = crate::agent::InspectorResponse {
258 status: 200,
259 content_type: "image/png".into(),
260 body: b64,
261 };
262 let _ = tx.send(resp);
263 }
264 }
265 }
266 }
267
268 if let Some(ref window) = self.window {
269 window.request_redraw();
270 }
271 }
272
273 _ => {}
274 }
275 }
276
277 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
278 if let Some(ref window) = self.window {
279 window.request_redraw();
280 }
281 }
282}
283
284fn key_to_string(key: &Key) -> String {
286 match key {
287 Key::Named(named) => match named {
288 NamedKey::ArrowUp => "ArrowUp".to_string(),
289 NamedKey::ArrowDown => "ArrowDown".to_string(),
290 NamedKey::ArrowLeft => "ArrowLeft".to_string(),
291 NamedKey::ArrowRight => "ArrowRight".to_string(),
292 NamedKey::Space => "Space".to_string(),
293 NamedKey::Enter => "Enter".to_string(),
294 NamedKey::Escape => "Escape".to_string(),
295 NamedKey::Backspace => "Backspace".to_string(),
296 NamedKey::Tab => "Tab".to_string(),
297 NamedKey::Shift => "Shift".to_string(),
298 NamedKey::Control => "Control".to_string(),
299 NamedKey::Alt => "Alt".to_string(),
300 other => format!("{other:?}"),
301 },
302 Key::Character(c) => c.to_string(),
303 _ => "Unknown".to_string(),
304 }
305}
306
307pub fn run_event_loop(
309 config: DevConfig,
310 render_state: Rc<RefCell<RenderState>>,
311 frame_callback: FrameCallback,
312) -> Result<()> {
313 let event_loop = EventLoop::new()?;
314
315 let mut app = AppState {
316 window: None,
317 config,
318 render_state,
319 frame_callback,
320 last_frame: Instant::now(),
321 scale_factor: 1.0,
322 };
323
324 event_loop.run_app(&mut app)?;
325 Ok(())
326}