swamp_window/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::dpi::PhysicalSize;
6use std::sync::Arc;
7use swamp_log::prelude::debug;
8use winit::application::ApplicationHandler;
9use winit::dpi;
10use winit::dpi::PhysicalPosition;
11use winit::error::EventLoopError;
12use winit::event::{
13    DeviceEvent, DeviceId, ElementState, InnerSizeWriter, MouseButton, MouseScrollDelta, Touch,
14    TouchPhase, WindowEvent,
15};
16use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
17use winit::keyboard::PhysicalKey;
18use winit::window::{Window, WindowAttributes, WindowId};
19
20#[cfg(target_arch = "wasm32")]
21use winit::platform::web::WindowAttributesExtWebSys;
22
23#[cfg(target_arch = "wasm32")]
24use web_sys::window;
25
26#[cfg(target_arch = "wasm32")]
27use web_sys::wasm_bindgen::JsCast;
28
29/// `AppHandler` - Handle window, cursor, mouse and keyboard events, designed for games and graphical applications.
30///
31/// Think of `AppHandler` as your app’s backstage crew, handling everything
32/// from window setup to keyboard and mouse inputs, and making sure each frame
33/// redraws smoothly.
34pub trait AppHandler {
35    // Query functions
36
37    /// Returns the minimum window size (width, height) in pixels that the application requires.
38    ///
39    /// This can be used to enforce a minimum size on the window, preventing it from
40    /// being resized below this dimension.
41    fn min_size(&self) -> (u16, u16);
42
43    /// Returns the starting window size (width, height) in pixels when the application launches.
44    ///
45    /// This size will be used to set the initial window dimensions on startup.
46    fn start_size(&self) -> (u16, u16);
47
48    fn cursor_should_be_visible(&self) -> bool;
49
50    // Window Events
51
52    /// Called to trigger a redraw of the application’s content.
53    ///
54    /// This method is generally called when the window needs to refresh its
55    /// contents, such as after a resize or focus change.
56    /// Return false if application should close
57    fn redraw(&mut self) -> bool;
58
59    /// Called when the application window gains focus.
60    ///
61    /// This can be used to resume or activate specific behaviors when the window
62    /// becomes active.
63    fn got_focus(&mut self);
64
65    /// Called when the application window loses focus.
66    ///
67    /// Useful for suspending actions or input handling when the application
68    /// window is not in the foreground.
69    fn lost_focus(&mut self);
70
71    /// Called after the application window has been created and is ready to use.
72    ///
73    /// Use this method to perform any initialization that requires access to the window,
74    /// such as setting up rendering contexts.
75    ///
76    /// # Parameters
77    /// - `window`: A reference-counted pointer to the application window.
78    fn window_created(&mut self, window: Arc<Window>);
79
80    /// Called whenever the window is resized, providing the new physical size.
81    ///
82    /// This method should handle adjustments to the application’s layout and content
83    /// based on the window’s new dimensions.
84    ///
85    /// # Parameters
86    /// - `size`: The new size of the window in physical pixels.
87    fn resized(&mut self, size: PhysicalSize<u32>);
88
89    // Keyboard Events
90
91    /// Processes keyboard input events, such as key presses and releases.
92    ///
93    /// # Parameters
94    /// - `element_state`: Indicates whether the key is pressed or released.
95    /// - `physical_key`: The physical key that was pressed or released.
96    fn keyboard_input(&mut self, element_state: ElementState, physical_key: PhysicalKey);
97
98    // Cursor (Pointer) Events
99
100    /// Called when the cursor enters the window.
101    ///
102    /// This can trigger visual changes or status updates when the cursor moves
103    /// into the application window area.
104    fn cursor_entered(&mut self);
105
106    /// Called when the cursor leaves the window.
107    ///
108    /// This can be used to revert visual changes or trigger actions when the
109    /// cursor exits the application window.
110    fn cursor_left(&mut self);
111
112    /// Handles cursor movement within the window, providing the new position.
113    ///
114    /// # Parameters
115    /// - `physical_position`: The current position of the cursor in physical
116    ///   screen coordinates.
117    fn cursor_moved(&mut self, physical_position: PhysicalPosition<u32>);
118
119    // Mouse Events
120
121    /// Handles mouse button input events, such as presses and releases.
122    ///
123    /// # Parameters
124    /// - `element_state`: Indicates whether the mouse button is pressed or released.
125    /// - `button`: The mouse button that was pressed or released.
126    fn mouse_input(&mut self, element_state: ElementState, button: MouseButton);
127
128    /// Processes mouse wheel events, which indicate scrolling actions.
129    ///
130    /// # Parameters
131    /// - `delta`: The amount of scroll, which may be specified in lines or pixels.
132    /// - `touch_phase`: The phase of the scroll gesture, which can indicate
133    ///   the start, movement, or end of the gesture.
134    fn mouse_wheel(&mut self, delta: MouseScrollDelta, touch_phase: TouchPhase);
135
136    fn pinch_gesture(&mut self, delta: f64, touch_phase: TouchPhase);
137
138    /// Handles mouse motion. the delta follows no standard, so it is up to the game to apply
139    /// a factor as it sees fit.
140    fn mouse_motion(&mut self, delta: (f64, f64));
141
142    // Touch Events
143
144    /// Handles touch input events, such as screen touches and gestures.
145    ///
146    /// # Parameters
147    /// - `touch`: Describes the touch event, including position, phase, and other
148    ///   touch-specific information.
149    fn touch(&mut self, touch: Touch);
150
151    // Environment or Screen Events
152
153    /// Handles changes to the display scale factor, usually due to monitor DPI changes.
154    ///
155    /// This method receives the new scale factor and a writer to update the inner
156    /// size of the application.
157    ///
158    /// # Parameters
159    /// - `scale_factor`: The new scale factor, which may be applied to adjust
160    ///   rendering.
161    /// - `inner_size_writer`: A writer to update the inner size.
162    fn scale_factor_changed(&mut self, scale_factor: f64, inner_size_writer: InnerSizeWriter);
163}
164
165pub struct App<'a> {
166    window: Option<Arc<Window>>,
167    handler: &'a mut (dyn AppHandler),
168    is_focused: bool,
169    cursor_is_visible: bool,
170    title: String,
171
172    // TODO: Move these
173    min_physical_size: PhysicalSize<u32>,
174    start_physical_size: PhysicalSize<u32>,
175}
176
177impl<'a> App<'a> {
178    pub fn new(
179        handler: &'a mut dyn AppHandler,
180        title: &str,
181        min_size: (u16, u16),
182        start_size: (u16, u16),
183    ) -> Self {
184        let min_physical_size = PhysicalSize::new(min_size.0 as u32, min_size.1 as u32);
185        let start_physical_size = PhysicalSize::new(start_size.0 as u32, start_size.1 as u32);
186
187        /*
188        let mut window_attributes = WindowAttributes::default().with_title(title);
189           let window_attributes = WindowAttributes::default()
190           .with_title(title)
191           .with_resizable(true)
192           .with_inner_size(start_logical_size)
193           .with_min_inner_size(min_logical_size);
194
195        */
196
197        Self {
198            handler,
199            window: None,
200            is_focused: false,
201            cursor_is_visible: true,
202            title: title.to_string(),
203            min_physical_size,
204            start_physical_size,
205        }
206    }
207}
208
209impl ApplicationHandler for App<'_> {
210    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
211        if self.window.is_none() {
212            debug!("creating new window");
213
214            let window_attributes = WindowAttributes::default()
215                .with_title(self.title.as_str())
216                .with_resizable(true)
217                .with_inner_size(self.start_physical_size)
218                .with_min_inner_size(self.min_physical_size);
219
220            #[cfg(target_arch = "wasm32")]
221            // Create the window attributes
222            let canvas = window()
223                .unwrap()
224                .document()
225                .unwrap()
226                .get_element_by_id("swamp_canvas")
227                .unwrap()
228                .dyn_into::<web_sys::HtmlCanvasElement>()
229                .unwrap();
230
231            #[cfg(target_arch = "wasm32")]
232            window_attributes.clone().with_canvas(Some(canvas));
233
234            let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
235
236            self.window = Some(window.clone());
237
238            self.handler.window_created(window);
239
240            // This tells winit that we want another frame after this one
241            //  self.window.as_ref().unwrap().request_redraw();
242        }
243    }
244    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
245        if id != self.window.as_ref().unwrap().id() {
246            return;
247        }
248
249        match event {
250            WindowEvent::CloseRequested => event_loop.exit(),
251            WindowEvent::Resized(physical_size) => {
252                self.handler.resized(physical_size);
253
254                // This tells winit that we want another frame after this one
255                self.window.as_ref().unwrap().request_redraw();
256            }
257            WindowEvent::RedrawRequested => {
258                // This tells winit that we want another frame after this one
259                self.window.as_ref().unwrap().request_redraw();
260
261                let window = self.window.as_mut().unwrap();
262                let cursor_visible_request = self.handler.cursor_should_be_visible();
263                if cursor_visible_request != self.cursor_is_visible {
264                    window.set_cursor_visible(cursor_visible_request);
265                    self.cursor_is_visible = cursor_visible_request;
266                }
267
268                if self.window.is_some() {
269                    let wants_to_keep_going = self.handler.redraw();
270                    if !wants_to_keep_going {
271                        event_loop.exit();
272                    }
273                }
274            }
275            WindowEvent::Focused(is_focus) => {
276                self.is_focused = is_focus;
277                if is_focus {
278                    self.handler.got_focus();
279                } else {
280                    // usually you might want to stop or lower audio, maybe lower rendering frequency, etc
281                    self.handler.lost_focus();
282                }
283            }
284            WindowEvent::KeyboardInput { event, .. } => {
285                self.handler.keyboard_input(event.state, event.physical_key);
286            }
287
288            WindowEvent::CursorMoved { position, .. } => self.handler.cursor_moved(
289                dpi::PhysicalPosition::<u32>::new(position.x as u32, position.y as u32),
290            ),
291
292            WindowEvent::CursorEntered { .. } => self.handler.cursor_entered(),
293
294            WindowEvent::CursorLeft { .. } => self.handler.cursor_left(),
295
296            WindowEvent::MouseWheel { delta, phase, .. } => self.handler.mouse_wheel(delta, phase),
297
298            WindowEvent::MouseInput { state, button, .. } => {
299                self.handler.mouse_input(state, button);
300            }
301
302            WindowEvent::Touch(touch_data) => self.handler.touch(touch_data),
303
304            WindowEvent::ScaleFactorChanged {
305                scale_factor,
306                inner_size_writer,
307            } =>
308            // Changing the display’s resolution.
309            // Changing the display’s scale factor (e.g. in Control Panel on Windows).
310            // Moving the window to a display with a different scale factor.
311            {
312                self.handler
313                    .scale_factor_changed(scale_factor, inner_size_writer)
314            }
315
316            WindowEvent::PinchGesture { delta, phase, .. } => {
317                // Opinionated: pinch in feels like a positive movement
318                let correct_delta = -delta;
319                self.handler.pinch_gesture(correct_delta, phase);
320            }
321
322            // --------------------------------------------
323
324            // WindowEvent::ModifiersChanged(_) => {} // modifiers comes in as KeyboardInput anyway, so we can ignore this one.
325            // WindowEvent::Ime(_) => {} // IME is outside the scope of events, and not supported on all platforms, e.g. Web.
326
327            // Gestures could be relevant, but we leave them for future versions
328            //WindowEvent::PinchGesture { .. } => {}
329            //WindowEvent::PanGesture { .. } => {}
330            //WindowEvent::DoubleTapGesture { .. } => {}
331            //WindowEvent::RotationGesture { .. } => {}
332            // WindowEvent::TouchpadPressure { .. } => {} // only on some macbooks and similar, not relevant for multiplatform games.
333            //WindowEvent::AxisMotion { .. } => {} // intentionally not supported, since we want to use platform-specific api:s for gamepad input
334            // WindowEvent::ThemeChanged(_) => {} // mostly unsupported and not really related to games.
335            // WindowEvent::Occluded(_) => {} not available on most platforms anyway
336            // WindowEvent::ActivationTokenDone { .. } => {} winit handles this normally, so no need to implement it.
337            // WindowEvent::Moved(_) => {} // since this is not supported on all platforms, it should not be exposed in this library
338            // WindowEvent::Destroyed => {} // this is handled internally
339            // since this crate is mostly for games, this file operations are outside the scope.
340            //WindowEvent::DroppedFile(_) => {}
341            //WindowEvent::HoveredFile(_) => {}
342            //WindowEvent::HoveredFileCancelled => {}
343            _ => {}
344        }
345    }
346
347    fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) {
348        if let DeviceEvent::MouseMotion { delta } = event {
349            if self.is_focused {
350                self.handler.mouse_motion(delta);
351            }
352        }
353        /*
354        match event {
355            // DeviceEvent::MouseWheel { .. } => {},
356            //DeviceEvent::Button { .. } => { }
357            //DeviceEvent::Added => {}
358            //DeviceEvent::Removed => {}
359            //DeviceEvent::Motion { .. } => { }
360            //DeviceEvent::Key(_) => {}
361            _ => {}
362        }
363         */
364    }
365
366    fn suspended(&mut self, _: &ActiveEventLoop) {}
367
368    fn exiting(&mut self, _: &ActiveEventLoop) {}
369}
370
371/// A struct responsible for managing the application window lifecycle.
372///
373/// The `WindowRunner` struct provides functionality to run an application
374/// that utilizes an event loop for window management. It abstracts the details
375/// of creating and running the event loop, making it easier to integrate window
376/// handling into your game application.
377pub struct WindowRunner;
378
379impl WindowRunner {
380    /// Runs the application with the provided handler.
381    ///
382    /// This method initializes an event loop and starts the application by
383    /// executing the provided `AppHandler`. The event loop runs in a polling
384    /// mode, allowing for responsive event handling. It is not guaranteed to ever return.
385    ///
386    /// # Parameters
387    ///
388    /// - `handler`: A mutable reference to an object implementing the `AppHandler`
389    ///   trait, which defines the behavior of the application in response to events.
390    ///
391    /// # Returns
392    ///
393    /// This method returns a `Result<(), EventLoopError>`.
394    /// If an error occurs during event loop creation, it returns an `EventLoopError`.
395    ///
396    /// # Note
397    ///
398    /// It is not guaranteed to ever return, as the event loop will run indefinitely
399    /// until the application is terminated.
400    pub fn run_app(handler: &mut dyn AppHandler, title: &str) -> Result<(), EventLoopError> {
401        let event_loop = EventLoop::new()?;
402        event_loop.set_control_flow(ControlFlow::Poll);
403        let min_size = handler.min_size();
404        let start_size = handler.start_size();
405        let mut app = App::new(handler, title, min_size, start_size);
406        let _ = event_loop.run_app(&mut app);
407        Ok(())
408    }
409}