Skip to main content

sable_platform/
event.rs

1//! Event loop and application events.
2//!
3//! Provides the main event loop abstraction and application-level events.
4
5use winit::application::ApplicationHandler;
6use winit::dpi::PhysicalSize;
7use winit::event::{ElementState, WindowEvent};
8use winit::event_loop::ActiveEventLoop;
9use winit::window::WindowId;
10
11use crate::input::{InputEvent, KeyCode, MouseButton};
12use crate::Result;
13
14/// The main event loop for the application.
15pub struct EventLoop {
16    inner: winit::event_loop::EventLoop<()>,
17}
18
19impl EventLoop {
20    /// Create a new event loop.
21    ///
22    /// # Errors
23    ///
24    /// Returns an error if the event loop could not be created.
25    pub fn new() -> Result<Self> {
26        let inner = winit::event_loop::EventLoop::new()?;
27        Ok(Self { inner })
28    }
29
30    /// Get a reference to the inner winit event loop.
31    #[must_use]
32    pub fn inner(&self) -> &winit::event_loop::EventLoop<()> {
33        &self.inner
34    }
35
36    /// Run the event loop with a callback handler.
37    ///
38    /// The callback receives [`AppEvent`]s and a [`ControlFlow`] to control the loop.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the event loop fails to run.
43    pub fn run<F>(self, mut callback: F) -> Result<()>
44    where
45        F: FnMut(AppEvent, &mut ControlFlow) + 'static,
46    {
47        let mut control_flow = ControlFlow::default();
48        let mut handler = CallbackHandler {
49            callback: &mut callback,
50            control_flow: &mut control_flow,
51        };
52
53        self.inner.run_app(&mut handler)?;
54        Ok(())
55    }
56}
57
58/// Application-level events.
59#[derive(Debug, Clone)]
60pub enum AppEvent {
61    /// The window was resized.
62    Resized {
63        /// The new width in physical pixels.
64        width: u32,
65        /// The new height in physical pixels.
66        height: u32,
67    },
68    /// The window close button was pressed.
69    CloseRequested,
70    /// The window needs to be redrawn.
71    RedrawRequested,
72    /// The window gained or lost focus.
73    Focused(bool),
74    /// An input event occurred.
75    Input(InputEvent),
76    /// The application is about to exit.
77    LoopExiting,
78    /// A new frame is about to start (called at the start of each event batch).
79    NewEvents,
80    /// All events for this frame have been processed.
81    AboutToWait,
82}
83
84/// Controls the behavior of the event loop.
85#[derive(Debug, Default)]
86pub struct ControlFlow {
87    should_exit: bool,
88}
89
90impl ControlFlow {
91    /// Request the event loop to exit after this event is processed.
92    pub fn exit(&mut self) {
93        self.should_exit = true;
94    }
95
96    /// Check if an exit has been requested.
97    #[must_use]
98    pub fn should_exit(&self) -> bool {
99        self.should_exit
100    }
101}
102
103/// Internal handler that bridges winit's `ApplicationHandler` to our callback.
104struct CallbackHandler<'a, F>
105where
106    F: FnMut(AppEvent, &mut ControlFlow),
107{
108    callback: &'a mut F,
109    control_flow: &'a mut ControlFlow,
110}
111
112impl<F> ApplicationHandler for CallbackHandler<'_, F>
113where
114    F: FnMut(AppEvent, &mut ControlFlow),
115{
116    fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
117        // Window is ready, nothing specific to do here
118    }
119
120    fn window_event(
121        &mut self,
122        event_loop: &ActiveEventLoop,
123        _window_id: WindowId,
124        event: WindowEvent,
125    ) {
126        let app_event = match event {
127            WindowEvent::CloseRequested => Some(AppEvent::CloseRequested),
128            WindowEvent::Resized(PhysicalSize { width, height }) => {
129                Some(AppEvent::Resized { width, height })
130            }
131            WindowEvent::RedrawRequested => Some(AppEvent::RedrawRequested),
132            WindowEvent::Focused(focused) => Some(AppEvent::Focused(focused)),
133            WindowEvent::KeyboardInput { event, .. } => {
134                if let Some(key_code) = convert_key_code(event.physical_key) {
135                    let input_event = match event.state {
136                        ElementState::Pressed => InputEvent::KeyPressed(key_code),
137                        ElementState::Released => InputEvent::KeyReleased(key_code),
138                    };
139                    Some(AppEvent::Input(input_event))
140                } else {
141                    None
142                }
143            }
144            WindowEvent::MouseInput { state, button, .. } => {
145                let mouse_button = convert_mouse_button(button);
146                let input_event = match state {
147                    ElementState::Pressed => InputEvent::MousePressed(mouse_button),
148                    ElementState::Released => InputEvent::MouseReleased(mouse_button),
149                };
150                Some(AppEvent::Input(input_event))
151            }
152            WindowEvent::CursorMoved { position, .. } => {
153                Some(AppEvent::Input(InputEvent::MouseMoved {
154                    x: position.x,
155                    y: position.y,
156                }))
157            }
158            WindowEvent::MouseWheel { delta, .. } => {
159                let (x, y) = match delta {
160                    winit::event::MouseScrollDelta::LineDelta(x, y) => (f64::from(x), f64::from(y)),
161                    winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x, pos.y),
162                };
163                Some(AppEvent::Input(InputEvent::MouseWheel {
164                    delta_x: x,
165                    delta_y: y,
166                }))
167            }
168            _ => None,
169        };
170
171        if let Some(event) = app_event {
172            (self.callback)(event, self.control_flow);
173        }
174
175        if self.control_flow.should_exit() {
176            event_loop.exit();
177        }
178    }
179
180    fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: winit::event::StartCause) {
181        (self.callback)(AppEvent::NewEvents, self.control_flow);
182    }
183
184    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
185        (self.callback)(AppEvent::AboutToWait, self.control_flow);
186        if self.control_flow.should_exit() {
187            event_loop.exit();
188        }
189    }
190
191    fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
192        (self.callback)(AppEvent::LoopExiting, self.control_flow);
193    }
194}
195
196/// Convert winit key code to our key code.
197fn convert_key_code(key: winit::keyboard::PhysicalKey) -> Option<KeyCode> {
198    use winit::keyboard::{KeyCode as WinitKey, PhysicalKey};
199
200    match key {
201        PhysicalKey::Code(code) => Some(match code {
202            WinitKey::KeyA => KeyCode::A,
203            WinitKey::KeyB => KeyCode::B,
204            WinitKey::KeyC => KeyCode::C,
205            WinitKey::KeyD => KeyCode::D,
206            WinitKey::KeyE => KeyCode::E,
207            WinitKey::KeyF => KeyCode::F,
208            WinitKey::KeyG => KeyCode::G,
209            WinitKey::KeyH => KeyCode::H,
210            WinitKey::KeyI => KeyCode::I,
211            WinitKey::KeyJ => KeyCode::J,
212            WinitKey::KeyK => KeyCode::K,
213            WinitKey::KeyL => KeyCode::L,
214            WinitKey::KeyM => KeyCode::M,
215            WinitKey::KeyN => KeyCode::N,
216            WinitKey::KeyO => KeyCode::O,
217            WinitKey::KeyP => KeyCode::P,
218            WinitKey::KeyQ => KeyCode::Q,
219            WinitKey::KeyR => KeyCode::R,
220            WinitKey::KeyS => KeyCode::S,
221            WinitKey::KeyT => KeyCode::T,
222            WinitKey::KeyU => KeyCode::U,
223            WinitKey::KeyV => KeyCode::V,
224            WinitKey::KeyW => KeyCode::W,
225            WinitKey::KeyX => KeyCode::X,
226            WinitKey::KeyY => KeyCode::Y,
227            WinitKey::KeyZ => KeyCode::Z,
228            WinitKey::Digit0 => KeyCode::Num0,
229            WinitKey::Digit1 => KeyCode::Num1,
230            WinitKey::Digit2 => KeyCode::Num2,
231            WinitKey::Digit3 => KeyCode::Num3,
232            WinitKey::Digit4 => KeyCode::Num4,
233            WinitKey::Digit5 => KeyCode::Num5,
234            WinitKey::Digit6 => KeyCode::Num6,
235            WinitKey::Digit7 => KeyCode::Num7,
236            WinitKey::Digit8 => KeyCode::Num8,
237            WinitKey::Digit9 => KeyCode::Num9,
238            WinitKey::Escape => KeyCode::Escape,
239            WinitKey::Enter => KeyCode::Enter,
240            WinitKey::Space => KeyCode::Space,
241            WinitKey::Tab => KeyCode::Tab,
242            WinitKey::Backspace => KeyCode::Backspace,
243            WinitKey::ShiftLeft | WinitKey::ShiftRight => KeyCode::Shift,
244            WinitKey::ControlLeft | WinitKey::ControlRight => KeyCode::Control,
245            WinitKey::AltLeft | WinitKey::AltRight => KeyCode::Alt,
246            WinitKey::ArrowUp => KeyCode::Up,
247            WinitKey::ArrowDown => KeyCode::Down,
248            WinitKey::ArrowLeft => KeyCode::Left,
249            WinitKey::ArrowRight => KeyCode::Right,
250            WinitKey::F1 => KeyCode::F1,
251            WinitKey::F2 => KeyCode::F2,
252            WinitKey::F3 => KeyCode::F3,
253            WinitKey::F4 => KeyCode::F4,
254            WinitKey::F5 => KeyCode::F5,
255            WinitKey::F6 => KeyCode::F6,
256            WinitKey::F7 => KeyCode::F7,
257            WinitKey::F8 => KeyCode::F8,
258            WinitKey::F9 => KeyCode::F9,
259            WinitKey::F10 => KeyCode::F10,
260            WinitKey::F11 => KeyCode::F11,
261            WinitKey::F12 => KeyCode::F12,
262            _ => return None,
263        }),
264        PhysicalKey::Unidentified(_) => None,
265    }
266}
267
268/// Convert winit mouse button to our mouse button.
269fn convert_mouse_button(button: winit::event::MouseButton) -> MouseButton {
270    match button {
271        winit::event::MouseButton::Left => MouseButton::Left,
272        winit::event::MouseButton::Right => MouseButton::Right,
273        winit::event::MouseButton::Middle => MouseButton::Middle,
274        winit::event::MouseButton::Back => MouseButton::Back,
275        winit::event::MouseButton::Forward => MouseButton::Forward,
276        winit::event::MouseButton::Other(id) => MouseButton::Other(id),
277    }
278}
279