Skip to main content

arcane_engine/platform/
window.rs

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;
15
16use super::input::InputState;
17
18/// Shared render state accessible from both the event loop and scripting ops.
19pub struct RenderState {
20    pub renderer: Option<Renderer>,
21    pub input: InputState,
22    pub sprite_commands: Vec<crate::renderer::SpriteCommand>,
23    pub camera_x: f32,
24    pub camera_y: f32,
25    pub camera_zoom: f32,
26    pub delta_time: f64,
27}
28
29impl RenderState {
30    pub fn new() -> Self {
31        Self {
32            renderer: None,
33            input: InputState::default(),
34            sprite_commands: Vec::new(),
35            camera_x: 0.0,
36            camera_y: 0.0,
37            camera_zoom: 1.0,
38            delta_time: 0.0,
39        }
40    }
41}
42
43/// Configuration for the dev window.
44pub struct DevConfig {
45    pub entry_file: PathBuf,
46    pub title: String,
47    pub width: u32,
48    pub height: u32,
49}
50
51/// Callback invoked each frame to run the TS step function.
52/// Returns the list of sprite commands to render.
53pub type FrameCallback = Box<dyn FnMut(&mut RenderState) -> Result<()>>;
54
55struct AppState {
56    window: Option<Arc<Window>>,
57    config: DevConfig,
58    render_state: Rc<RefCell<RenderState>>,
59    frame_callback: FrameCallback,
60    last_frame: Instant,
61}
62
63impl ApplicationHandler for AppState {
64    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
65        if self.window.is_some() {
66            return;
67        }
68
69        let attrs = Window::default_attributes()
70            .with_title(&self.config.title)
71            .with_inner_size(winit::dpi::LogicalSize::new(
72                self.config.width,
73                self.config.height,
74            ));
75
76        let window = Arc::new(
77            event_loop
78                .create_window(attrs)
79                .expect("Failed to create window"),
80        );
81
82        match Renderer::new(window.clone()) {
83            Ok(renderer) => {
84                self.render_state.borrow_mut().renderer = Some(renderer);
85            }
86            Err(e) => {
87                eprintln!("Failed to initialize renderer: {e}");
88                event_loop.exit();
89                return;
90            }
91        }
92
93        self.window = Some(window);
94        self.last_frame = Instant::now();
95    }
96
97    fn window_event(
98        &mut self,
99        event_loop: &ActiveEventLoop,
100        _window_id: WindowId,
101        event: WindowEvent,
102    ) {
103        match event {
104            WindowEvent::CloseRequested => {
105                event_loop.exit();
106            }
107
108            WindowEvent::Resized(new_size) => {
109                let mut state = self.render_state.borrow_mut();
110                if let Some(ref mut renderer) = state.renderer {
111                    renderer.resize(new_size.width, new_size.height);
112                }
113            }
114
115            WindowEvent::KeyboardInput {
116                event:
117                    KeyEvent {
118                        logical_key,
119                        state: key_state,
120                        ..
121                    },
122                ..
123            } => {
124                let key_name = key_to_string(&logical_key);
125                let mut state = self.render_state.borrow_mut();
126                match key_state {
127                    ElementState::Pressed => state.input.key_down(&key_name),
128                    ElementState::Released => state.input.key_up(&key_name),
129                }
130            }
131
132            WindowEvent::CursorMoved { position, .. } => {
133                let mut state = self.render_state.borrow_mut();
134                state.input.mouse_move(position.x as f32, position.y as f32);
135            }
136
137            WindowEvent::MouseInput { state: button_state, button, .. } => {
138                let mut state = self.render_state.borrow_mut();
139                let button_id: u8 = match button {
140                    winit::event::MouseButton::Left => 0,
141                    winit::event::MouseButton::Right => 1,
142                    winit::event::MouseButton::Middle => 2,
143                    winit::event::MouseButton::Back => 3,
144                    winit::event::MouseButton::Forward => 4,
145                    winit::event::MouseButton::Other(id) => id.min(255) as u8,
146                };
147                match button_state {
148                    ElementState::Pressed => {
149                        state.input.mouse_buttons.insert(button_id);
150                        // Also add to keys_pressed so isKeyPressed works
151                        let key_name = match button_id {
152                            0 => "MouseLeft",
153                            1 => "MouseRight",
154                            2 => "MouseMiddle",
155                            _ => return,
156                        };
157                        state.input.key_down(key_name);
158                    }
159                    ElementState::Released => {
160                        state.input.mouse_buttons.remove(&button_id);
161                        let key_name = match button_id {
162                            0 => "MouseLeft",
163                            1 => "MouseRight",
164                            2 => "MouseMiddle",
165                            _ => return,
166                        };
167                        state.input.key_up(key_name);
168                    }
169                }
170            }
171
172            WindowEvent::RedrawRequested => {
173                let now = Instant::now();
174                let dt = now.duration_since(self.last_frame).as_secs_f64();
175                self.last_frame = now;
176
177                {
178                    let mut state = self.render_state.borrow_mut();
179                    state.delta_time = dt;
180                }
181
182                // Run the TS frame callback (calls ops that populate sprite_commands)
183                {
184                    let mut state = self.render_state.borrow_mut();
185                    if let Err(e) = (self.frame_callback)(&mut state) {
186                        eprintln!("Frame callback error: {e}");
187                    }
188                }
189
190                // Clear per-frame input AFTER the callback has read it
191                {
192                    let mut state = self.render_state.borrow_mut();
193                    state.input.begin_frame();
194                }
195
196                // Transfer sprite commands and camera to renderer, then render
197                {
198                    let mut state = self.render_state.borrow_mut();
199                    // Extract values before borrowing renderer mutably
200                    let cam_x = state.camera_x;
201                    let cam_y = state.camera_y;
202                    let cam_zoom = state.camera_zoom;
203                    let commands = std::mem::take(&mut state.sprite_commands);
204
205                    if let Some(ref mut renderer) = state.renderer {
206                        renderer.camera.x = cam_x;
207                        renderer.camera.y = cam_y;
208                        renderer.camera.zoom = cam_zoom;
209                        renderer.frame_commands = commands;
210
211                        if let Err(e) = renderer.render_frame() {
212                            eprintln!("Render error: {e}");
213                        }
214                    }
215                }
216
217                if let Some(ref window) = self.window {
218                    window.request_redraw();
219                }
220            }
221
222            _ => {}
223        }
224    }
225
226    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
227        if let Some(ref window) = self.window {
228            window.request_redraw();
229        }
230    }
231}
232
233/// Convert a winit logical key to a string name for the TS API.
234fn key_to_string(key: &Key) -> String {
235    match key {
236        Key::Named(named) => match named {
237            NamedKey::ArrowUp => "ArrowUp".to_string(),
238            NamedKey::ArrowDown => "ArrowDown".to_string(),
239            NamedKey::ArrowLeft => "ArrowLeft".to_string(),
240            NamedKey::ArrowRight => "ArrowRight".to_string(),
241            NamedKey::Space => "Space".to_string(),
242            NamedKey::Enter => "Enter".to_string(),
243            NamedKey::Escape => "Escape".to_string(),
244            NamedKey::Backspace => "Backspace".to_string(),
245            NamedKey::Tab => "Tab".to_string(),
246            NamedKey::Shift => "Shift".to_string(),
247            NamedKey::Control => "Control".to_string(),
248            NamedKey::Alt => "Alt".to_string(),
249            other => format!("{other:?}"),
250        },
251        Key::Character(c) => c.to_string(),
252        _ => "Unknown".to_string(),
253    }
254}
255
256/// Run the event loop. This blocks until the window is closed.
257pub fn run_event_loop(
258    config: DevConfig,
259    render_state: Rc<RefCell<RenderState>>,
260    frame_callback: FrameCallback,
261) -> Result<()> {
262    let event_loop = EventLoop::new()?;
263
264    let mut app = AppState {
265        window: None,
266        config,
267        render_state,
268        frame_callback,
269        last_frame: Instant::now(),
270    };
271
272    event_loop.run_app(&mut app)?;
273    Ok(())
274}