Skip to main content

veloren_voxygen/
window.rs

1use crate::{
2    error::Error,
3    game_input::GameInput,
4    render::Renderer,
5    settings::{ControlSettings, ControllerSettings, Settings, controller::*},
6    ui,
7};
8use common_base::span;
9use crossbeam_channel as channel;
10use gilrs::{Button as GilButton, EventType, Gilrs};
11use hashbrown::{HashMap, hash_set::Iter};
12use itertools::Itertools;
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15use strum::{AsRefStr, EnumIter};
16use tracing::{error, warn};
17use vek::*;
18use winit::monitor::VideoModeHandle;
19
20/// Represents a key that the game menus recognise after input mapping
21#[derive(
22    Clone,
23    Copy,
24    Debug,
25    PartialEq,
26    Eq,
27    PartialOrd,
28    Ord,
29    Hash,
30    Deserialize,
31    Serialize,
32    AsRefStr,
33    EnumIter,
34)]
35pub enum MenuInput {
36    Up,
37    Down,
38    Left,
39    Right,
40    ScrollUp,
41    ScrollDown,
42    ScrollLeft,
43    ScrollRight,
44    PageDown,
45    PageUp,
46    Apply,
47    Back,
48    Exit,
49    LocalFocus,
50    GlobalFocus,
51    EmulateLeftClick,
52    EmulateRightClick,
53}
54
55/// Manages an Iterator over MenuInputs or GameInputs
56pub enum MappedInput<'a> {
57    Menu(Iter<'a, MenuInput>),
58    Game(Iter<'a, GameInput>),
59}
60
61impl MenuInput {
62    pub fn get_localization_key(&self) -> &str { self.as_ref() }
63}
64
65#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
66pub enum AnalogMenuInput {
67    MoveX(f32),
68    MoveY(f32),
69    ScrollX(f32),
70    ScrollY(f32),
71}
72
73#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
74pub enum AnalogGameInput {
75    MovementX(f32),
76    MovementY(f32),
77    CameraX(f32),
78    CameraY(f32),
79}
80
81/// Represents an incoming event from the window.
82#[derive(Clone, Debug)]
83pub enum Event {
84    /// The window has been requested to close.
85    Close,
86    /// The window has been resized.
87    Resize(Vec2<u32>),
88    /// The window scale factor has been changed
89    ScaleFactorChanged(f64),
90    /// The window has been moved.
91    Moved(Vec2<u32>),
92    /// The cursor has been panned across the screen while grabbed.
93    CursorPan(Vec2<f32>),
94    /// The cursor has been moved across the screen while ungrabbed.
95    CursorMove(Vec2<f32>),
96    /// A mouse button has been pressed or released
97    MouseButton(MouseButton, PressState),
98    /// The camera has been requested to zoom.
99    Zoom(f32),
100    /// A key that the game recognises has been pressed or released.
101    InputUpdate(GameInput, bool),
102    /// Event that the ui uses.
103    Ui(ui::Event),
104    /// Event that the iced ui uses.
105    IcedUi(ui::ice::Event),
106    /// The view distance has changed.
107    ViewDistanceChanged(u32),
108    /// Game settings have changed.
109    SettingsChanged,
110    /// The window is (un)focused
111    Focused(bool),
112    /// A key that the game recognises for menu navigation has been pressed or
113    /// released
114    MenuInput(MenuInput, bool),
115    /// Update of the analog inputs recognized by the menus
116    AnalogMenuInput(AnalogMenuInput),
117    /// Update of the analog inputs recognized by the game
118    AnalogGameInput(AnalogGameInput),
119    /// We tried to save a screenshot
120    ScreenshotMessage(String),
121}
122
123pub type MouseButton = winit::event::MouseButton;
124pub type PressState = winit::event::ElementState;
125pub type EventLoop = winit::event_loop::EventLoop<()>;
126
127#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
128pub enum KeyMouse {
129    Key(winit::keyboard::Key),
130    Mouse(winit::event::MouseButton),
131}
132
133impl KeyMouse {
134    pub fn into_upper(mut self) -> Self {
135        if let KeyMouse::Key(winit::keyboard::Key::Character(c)) = &mut self {
136            *c = c.to_ascii_uppercase().into();
137        }
138        self
139    }
140
141    /// Returns key description (e.g Left Shift)
142    pub fn display_string(&self) -> String {
143        use self::KeyMouse::*;
144        use winit::{event::MouseButton, keyboard::Key::*};
145
146        match self {
147            Key(key) => match key {
148                Named(key) => format!("{key:?}"),
149                Character(c) => c.to_string(),
150                Unidentified(key) => format!("Unknown ({key:?})"),
151                Dead(dead) => format!("Dead ({dead:?})"),
152            },
153            Mouse(MouseButton::Left) => String::from("Left Click"),
154            Mouse(MouseButton::Right) => String::from("Right Click"),
155            Mouse(MouseButton::Middle) => String::from("Middle Click"),
156            Mouse(MouseButton::Forward) => String::from("Mouse Forward"),
157            Mouse(MouseButton::Back) => String::from("Mouse Back"),
158            Mouse(MouseButton::Other(button)) => {
159                // Additional mouse buttons after middle click start at 1
160                format!("Mouse {}", button + 3)
161            },
162        }
163    }
164
165    /// If it exists, returns the shortened version of a key name
166    /// (e.g. Left Click -> M1)
167    pub fn try_shortened(&self) -> Option<String> {
168        use self::KeyMouse::*;
169        use winit::event::MouseButton;
170        let key_string = match self {
171            Mouse(MouseButton::Left) => "M1",
172            Mouse(MouseButton::Right) => "M2",
173            Mouse(MouseButton::Middle) => "M3",
174            Mouse(MouseButton::Other(button)) => {
175                // Additional mouse buttons after middle click start at 1
176                return Some(format!("M{}", button + 3));
177            },
178            _ => return None,
179        };
180
181        Some(key_string.to_owned())
182    }
183
184    /// Returns shortest name of key (e.g. Left Click - M1)
185    /// If key doesn't have shorter version, use regular one.
186    ///
187    /// Use it in case if space does really matter.
188    pub fn display_shortest(&self) -> String {
189        self.try_shortened()
190            .unwrap_or_else(|| self.display_string())
191    }
192}
193
194#[derive(Clone, Copy, Debug)]
195pub enum RemappingMode {
196    RemapKeyboard(GameInput),
197    RemapKeyboardMenu(MenuInput),
198    RemapGamepadButtons(GameInput),
199    RemapGamepadLayers(GameInput),
200    RemapGamepadMenu(MenuInput),
201    None,
202}
203
204// TODO: implement controller layout detection/configuration
205#[derive(Clone, Copy, Debug)]
206pub enum ControllerType {
207    Xbox,
208    Nintendo,
209    Playstation,
210    None,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq)]
214pub enum LastInput {
215    Keyboard,
216    Mouse,
217    Controller,
218}
219
220pub struct Window {
221    renderer: Renderer,
222    window: Arc<winit::window::Window>,
223    cursor_grabbed: bool,
224    pub pan_sensitivity: u32,
225    pub zoom_sensitivity: u32,
226    pub zoom_inversion: bool,
227    pub mouse_y_inversion: bool,
228    fullscreen: FullScreenSettings,
229    modifiers: winit::keyboard::ModifiersState,
230    // Track if at least one Resized event has occured since the last `fetch_events` call
231    // Used for deduplication of resizes.
232    resized: bool,
233    scale_factor: f64,
234    needs_refresh_resize: bool,
235    keypress_map: HashMap<GameInput, winit::event::ElementState>,
236    pub remapping_mode: RemappingMode,
237    events: Vec<Event>,
238    pub focused: bool,
239    gilrs: Option<Gilrs>,
240    pub controller_modifiers: Vec<Button>,
241    cursor_position: winit::dpi::PhysicalPosition<f64>,
242    mouse_emulation_vec: Vec2<f32>,
243    controller_type: ControllerType,
244    last_input: LastInput,
245    pub menu_open: bool,
246    last_input_type_menu: bool,
247    // Currently used to send and receive screenshot result messages
248    message_sender: channel::Sender<String>,
249    message_receiver: channel::Receiver<String>,
250    // Used for screenshots & fullscreen toggle to deduplicate/postpone to after event handler
251    take_screenshot: bool,
252    toggle_fullscreen: bool,
253    // Used for storing the state of checkboxes on controls>gamepad>gamelayers, does not need to be
254    // saved to file, so initialized here
255    pub gamelayer_mod1: bool,
256    pub gamelayer_mod2: bool,
257}
258
259impl Window {
260    pub fn new(
261        settings: &Settings,
262        runtime: &tokio::runtime::Runtime,
263    ) -> Result<(Window, EventLoop), Error> {
264        let event_loop = EventLoop::new().unwrap();
265
266        let window = settings.graphics.window;
267
268        #[allow(unused_mut)] //ensure no weird issues on different platforms
269        let mut attributes = winit::window::Window::default_attributes()
270            .with_title("Veloren")
271            .with_inner_size(winit::dpi::LogicalSize::new(
272                window.size[0] as f64,
273                window.size[1] as f64,
274            ))
275            .with_maximized(window.maximised);
276
277        #[cfg(not(any(target_os = "windows", target_os = "macos")))]
278        {
279            use winit::platform::wayland::WindowAttributesExtWayland;
280            attributes = attributes.with_name("net.veloren.veloren", "veloren");
281        }
282
283        // Avoid cpal / winit OleInitialize conflict
284        // See: https://github.com/rust-windowing/winit/pull/1524
285        #[cfg(target_family = "windows")]
286        let attributes = winit::platform::windows::WindowAttributesExtWindows::with_drag_and_drop(
287            attributes, false,
288        );
289
290        #[expect(deprecated)]
291        let window = Arc::new(event_loop.create_window(attributes).unwrap());
292
293        let renderer = Renderer::new(
294            Arc::clone(&window),
295            settings.graphics.render_mode.clone(),
296            runtime,
297        )?;
298
299        let keypress_map = HashMap::new();
300
301        let gilrs = match Gilrs::new() {
302            Ok(gilrs) => Some(gilrs),
303            Err(gilrs::Error::NotImplemented(_dummy)) => {
304                warn!("Controller input is unsupported on this platform.");
305                None
306            },
307            Err(gilrs::Error::InvalidAxisToBtn) => {
308                error!(
309                    "Invalid AxisToBtn controller mapping. Falling back to no controller support."
310                );
311                None
312            },
313            Err(gilrs::Error::Other(e)) => {
314                error!(
315                    ?e,
316                    "Platform-specific error when creating a Gilrs instance. Falling back to no \
317                     controller support."
318                );
319                None
320            },
321            Err(e) => {
322                error!(
323                    ?e,
324                    "Unspecified error when creating a Gilrs instance. Falling back to no \
325                     controller support."
326                );
327                None
328            },
329        };
330
331        let (message_sender, message_receiver): (
332            channel::Sender<String>,
333            channel::Receiver<String>,
334        ) = channel::unbounded::<String>();
335
336        let scale_factor = window.scale_factor();
337
338        let mut this = Self {
339            renderer,
340            window,
341            cursor_grabbed: false,
342            pan_sensitivity: settings.gameplay.pan_sensitivity,
343            zoom_sensitivity: settings.gameplay.zoom_sensitivity,
344            zoom_inversion: settings.gameplay.zoom_inversion,
345            mouse_y_inversion: settings.gameplay.mouse_y_inversion,
346            fullscreen: FullScreenSettings::default(),
347            modifiers: Default::default(),
348            scale_factor,
349            resized: false,
350            needs_refresh_resize: false,
351            keypress_map,
352            remapping_mode: RemappingMode::None,
353            events: Vec::new(),
354            focused: true,
355            gilrs,
356            controller_modifiers: Vec::new(),
357            cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
358            mouse_emulation_vec: Vec2::zero(),
359            controller_type: ControllerType::Xbox,
360            last_input: LastInput::Mouse,
361            menu_open: false,
362            last_input_type_menu: false,
363            // Currently used to send and receive screenshot result messages
364            message_sender,
365            message_receiver,
366            take_screenshot: false,
367            toggle_fullscreen: false,
368            gamelayer_mod1: true,
369            gamelayer_mod2: false,
370        };
371
372        this.set_fullscreen_mode(settings.graphics.fullscreen);
373
374        Ok((this, event_loop))
375    }
376
377    pub fn renderer(&self) -> &Renderer { &self.renderer }
378
379    pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
380
381    pub fn resolve_deduplicated_events(
382        &mut self,
383        settings: &mut Settings,
384        config_dir: &std::path::Path,
385    ) {
386        // Handle screenshots and toggling fullscreen
387        if self.take_screenshot {
388            self.take_screenshot = false;
389            self.take_screenshot(settings);
390        }
391        if self.toggle_fullscreen {
392            self.toggle_fullscreen = false;
393            self.toggle_fullscreen(settings, config_dir);
394        }
395    }
396
397    pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
398        span!(_guard, "fetch_events", "Window::fetch_events");
399
400        let controller = &mut settings.controller;
401        // Refresh ui size (used when changing playstates)
402        if self.needs_refresh_resize {
403            let scale_factor = self.window.scale_factor();
404            let physical = self.window.inner_size();
405
406            let logical_size =
407                Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
408            self.events
409                .push(Event::Ui(ui::Event::new_resize(logical_size)));
410            self.events.push(Event::IcedUi(iced::Event::Window(
411                iced::window::Event::Resized {
412                    width: logical_size.x as u32,
413                    height: logical_size.y as u32,
414                },
415            )));
416            self.events.push(Event::ScaleFactorChanged(scale_factor));
417            self.needs_refresh_resize = false;
418        }
419
420        // Handle deduplicated resizing that occured
421        if self.resized {
422            self.resized = false;
423            // We don't use the size provided by the event because more resize events could
424            // have happened since, making the value outdated, so we must query directly
425            // from the window to prevent errors
426            let physical = self.window.inner_size();
427            let scale_factor = self.window.scale_factor();
428            let is_maximized = self.window.is_maximized();
429
430            self.renderer
431                .on_resize(Vec2::new(physical.width, physical.height));
432            self.events
433                .push(Event::Resize(Vec2::new(physical.width, physical.height)));
434
435            let logical_size =
436                Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
437
438            // Emit event for the UI
439            self.events
440                .push(Event::Ui(ui::Event::new_resize(logical_size)));
441            self.events.push(Event::IcedUi(iced::Event::Window(
442                iced::window::Event::Resized {
443                    width: logical_size.x as u32,
444                    height: logical_size.y as u32,
445                },
446            )));
447
448            // Save new window state in settings
449            //
450            // We don't save the size if it's less than 1 because wgpu fails to create a
451            // surface with zero size.
452            if logical_size.x >= 1.0 && logical_size.y >= 1.0 {
453                settings.graphics.window.size = [logical_size.x as u32, logical_size.y as u32];
454            }
455            settings.graphics.window.maximised = is_maximized;
456        }
457
458        // Receive any messages sent through the message channel
459        for message in self.message_receiver.try_iter() {
460            self.events.push(Event::ScreenshotMessage(message))
461        }
462
463        if let Some(gilrs) = &mut self.gilrs {
464            while let Some(event) = gilrs.next_event() {
465                fn handle_buttons(
466                    settings: &mut ControllerSettings,
467                    remapping: &mut RemappingMode,
468                    modifiers: &mut Vec<Button>,
469                    events: &mut Vec<Event>,
470                    button: &Button,
471                    is_pressed: bool,
472                    last_input: &mut LastInput,
473                    last_input_menu: &mut bool,
474                    mod1: bool,
475                    mod2: bool,
476                    menu_open: bool,
477                ) {
478                    // update last input to be controller if button was pressed
479                    *last_input = LastInput::Controller;
480
481                    if settings.modifier_buttons.contains(button) {
482                        if is_pressed {
483                            modifiers.push(*button);
484                        // There is a possibility of voxygen not having
485                        // registered the initial press event (either because it
486                        // hadn't started yet, or whatever else) hence the
487                        // modifier has no position in the list, unwrapping
488                        // here would cause a crash in those cases
489                        } else if let Some(index) =
490                            modifiers.iter().position(|modifier| modifier == button)
491                        {
492                            modifiers.remove(index);
493                        }
494                    }
495
496                    if let Some(mapped_inputs) = Window::map_controller_input(
497                        settings, remapping, modifiers, button, mod1, mod2, menu_open,
498                    ) {
499                        match mapped_inputs {
500                            // Fetch actions to perform; if nothing was returned, then either no
501                            // action was assigned to the input, or a remapping action was performed
502                            MappedInput::Menu(menu_inputs) => {
503                                *last_input_menu = true;
504                                for menu_input in menu_inputs {
505                                    events.push(Event::MenuInput(*menu_input, is_pressed));
506                                }
507                            },
508                            MappedInput::Game(game_inputs) => {
509                                *last_input_menu = false;
510                                // Prioritize game layers over buttons
511                                for game_input in game_inputs {
512                                    events.push(Event::InputUpdate(*game_input, is_pressed));
513                                }
514                            },
515                        }
516                    }
517                }
518
519                match event.event {
520                    EventType::ButtonPressed(button, code)
521                    | EventType::ButtonRepeated(button, code) => {
522                        handle_buttons(
523                            controller,
524                            &mut self.remapping_mode,
525                            &mut self.controller_modifiers,
526                            &mut self.events,
527                            &Button::from((button, code)),
528                            true,
529                            &mut self.last_input,
530                            &mut self.last_input_type_menu,
531                            self.gamelayer_mod1,
532                            self.gamelayer_mod2,
533                            self.menu_open,
534                        );
535                    },
536                    EventType::ButtonReleased(button, code) => {
537                        handle_buttons(
538                            controller,
539                            &mut self.remapping_mode,
540                            &mut self.controller_modifiers,
541                            &mut self.events,
542                            &Button::from((button, code)),
543                            false,
544                            &mut self.last_input,
545                            &mut self.last_input_type_menu,
546                            self.gamelayer_mod1,
547                            self.gamelayer_mod2,
548                            self.menu_open,
549                        );
550                    },
551                    EventType::ButtonChanged(button, _value, code) => {
552                        if let Some(actions) = controller
553                            .inverse_game_analog_button_map
554                            .get(&AnalogButton::from((button, code)))
555                        {
556                            #[expect(clippy::never_loop)]
557                            for action in actions {
558                                match *action {}
559                            }
560                        }
561                        if let Some(actions) = controller
562                            .inverse_menu_analog_button_map
563                            .get(&AnalogButton::from((button, code)))
564                        {
565                            #[expect(clippy::never_loop)]
566                            for action in actions {
567                                match *action {}
568                            }
569                        }
570                    },
571
572                    EventType::AxisChanged(axis, value, code) => {
573                        let value = if controller.inverted_axes.contains(&Axis::from((axis, code)))
574                        {
575                            -value
576                        } else {
577                            value
578                        };
579
580                        let value =
581                            controller.apply_axis_deadzone(&Axis::from((axis, code)), value);
582
583                        // update last input to be controller if axis was moved
584                        if value.abs() > 0.0001 {
585                            self.last_input = LastInput::Controller;
586                        }
587
588                        if self.cursor_grabbed {
589                            if let Some(actions) = controller
590                                .inverse_game_axis_map
591                                .get(&Axis::from((axis, code)))
592                            {
593                                for action in actions {
594                                    match *action {
595                                        AxisGameAction::MovementX => {
596                                            self.events.push(Event::AnalogGameInput(
597                                                AnalogGameInput::MovementX(value),
598                                            ));
599                                        },
600                                        AxisGameAction::MovementY => {
601                                            self.events.push(Event::AnalogGameInput(
602                                                AnalogGameInput::MovementY(value),
603                                            ));
604                                        },
605                                        AxisGameAction::CameraX => {
606                                            self.events.push(Event::AnalogGameInput(
607                                                AnalogGameInput::CameraX(
608                                                    value * controller.pan_sensitivity as f32
609                                                        / 100.0,
610                                                ),
611                                            ));
612                                        },
613                                        AxisGameAction::CameraY => {
614                                            let pan_invert_y = match controller.pan_invert_y {
615                                                true => -1.0,
616                                                false => 1.0,
617                                            };
618
619                                            self.events.push(Event::AnalogGameInput(
620                                                AnalogGameInput::CameraY(
621                                                    -value
622                                                        * controller.pan_sensitivity as f32
623                                                        * pan_invert_y
624                                                        / 100.0,
625                                                ),
626                                            ));
627                                        },
628                                    }
629                                }
630                            }
631                        } else if let Some(actions) = controller
632                            .inverse_menu_axis_map
633                            .get(&Axis::from((axis, code)))
634                        {
635                            // TODO: possibly add sensitivity settings when this is used
636                            for action in actions {
637                                match *action {
638                                    AxisMenuAction::MoveX => {
639                                        self.events.push(Event::AnalogMenuInput(
640                                            AnalogMenuInput::MoveX(value),
641                                        ));
642                                    },
643                                    AxisMenuAction::MoveY => {
644                                        self.events.push(Event::AnalogMenuInput(
645                                            AnalogMenuInput::MoveY(value),
646                                        ));
647                                    },
648                                    AxisMenuAction::ScrollX => {
649                                        self.events.push(Event::AnalogMenuInput(
650                                            AnalogMenuInput::ScrollX(value),
651                                        ));
652                                    },
653                                    AxisMenuAction::ScrollY => {
654                                        self.events.push(Event::AnalogMenuInput(
655                                            AnalogMenuInput::ScrollY(value),
656                                        ));
657                                    },
658                                }
659                            }
660                        }
661                    },
662                    _ => {},
663                }
664            }
665        }
666
667        let mut events = std::mem::take(&mut self.events);
668        // Mouse emulation for the menus, to be removed when a proper menu navigation
669        // system is available
670        if !self.cursor_grabbed {
671            events = events
672                .into_iter()
673                .filter_map(|event| match event {
674                    Event::AnalogMenuInput(input) => match input {
675                        AnalogMenuInput::MoveX(d) => {
676                            self.mouse_emulation_vec.x = d;
677                            None
678                        },
679                        AnalogMenuInput::MoveY(d) => {
680                            // This just has to be inverted for some reason
681                            self.mouse_emulation_vec.y = -d;
682                            None
683                        },
684                        input => Some(Event::AnalogMenuInput(input)),
685                    },
686                    Event::MenuInput(menu_input, state) => {
687                        // determine if left mouse or right mouse button
688                        let mouse_button = match menu_input {
689                            MenuInput::EmulateLeftClick => {
690                                conrod_core::input::state::mouse::Button::Left
691                            },
692                            MenuInput::EmulateRightClick => {
693                                conrod_core::input::state::mouse::Button::Right
694                            },
695                            _ => return Some(event),
696                        };
697                        Some(match state {
698                            true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
699                                conrod_core::input::Button::Mouse(mouse_button),
700                            ))),
701                            false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
702                                conrod_core::input::Button::Mouse(mouse_button),
703                            ))),
704                        })
705                    },
706                    _ => Some(event),
707                })
708                .collect();
709
710            let sensitivity = controller.mouse_emulation_sensitivity;
711            // TODO: make this independent of framerate
712            // TODO: consider multiplying by scale factor
713            self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
714        }
715
716        events
717    }
718
719    pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
720        use winit::event::DeviceEvent;
721
722        let mouse_y_inversion = match self.mouse_y_inversion {
723            true => -1.0,
724            false => 1.0,
725        };
726
727        match event {
728            DeviceEvent::MouseMotion {
729                delta: (dx, dy), ..
730            } if self.focused => {
731                // update last input to be Mouse if motion was made
732                self.last_input = LastInput::Mouse;
733
734                let delta = Vec2::new(
735                    dx as f32 * (self.pan_sensitivity as f32 / 100.0),
736                    dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
737                );
738
739                if self.cursor_grabbed {
740                    self.events.push(Event::CursorPan(delta));
741                } else {
742                    self.events.push(Event::CursorMove(delta));
743                }
744            },
745            _ => {},
746        }
747    }
748
749    pub fn handle_window_event(
750        &mut self,
751        event: winit::event::WindowEvent,
752        settings: &mut Settings,
753    ) {
754        use winit::event::WindowEvent;
755
756        let controls = &mut settings.controls;
757
758        match event {
759            WindowEvent::CloseRequested => self.events.push(Event::Close),
760            WindowEvent::Resized(_) => {
761                self.resized = true;
762            },
763            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
764                // TODO: is window resized event emitted? or do we need to handle that here?
765                self.scale_factor = scale_factor;
766                self.events.push(Event::ScaleFactorChanged(scale_factor));
767            },
768            WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
769                self.events
770                    .push(Event::Moved(Vec2::new(x as u32, y as u32)));
771            },
772            WindowEvent::MouseInput { button, state, .. } => {
773                let map_input = Window::map_input(
774                    KeyMouse::Mouse(button),
775                    controls,
776                    &mut self.remapping_mode,
777                    &mut self.last_input,
778                    self.menu_open,
779                );
780                // Mouse input not mapped to input if it is not grabbed
781                if self.cursor_grabbed
782                    && let Some(MappedInput::Game(game_inputs)) = map_input
783                {
784                    for game_input in game_inputs {
785                        self.events.push(Event::InputUpdate(
786                            *game_input,
787                            state == winit::event::ElementState::Pressed,
788                        ));
789                    }
790                }
791                self.events.push(Event::MouseButton(button, state));
792            },
793            WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers.state(),
794            WindowEvent::KeyboardInput {
795                event,
796                is_synthetic,
797                ..
798            } => {
799                // Ignore key repeat events for GameInputs
800                if event.repeat {
801                    return;
802                }
803                // Ignore synthetic keydown events so that we don't e.g. get tabs when
804                // alt-tabbing back into the window
805                if matches!(event.state, winit::event::ElementState::Pressed) && is_synthetic {
806                    return;
807                }
808                // Ignore Alt-F4 so we don't try to do anything heavy like take a screenshot
809                // when the window is about to close
810                if matches!(event, winit::event::KeyEvent {
811                    state: winit::event::ElementState::Pressed,
812                    logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::F4),
813                    ..
814                }) && self.modifiers.alt_key()
815                {
816                    return;
817                }
818
819                if let Some(mapped_inputs) = Window::map_input(
820                    KeyMouse::Key(event.logical_key),
821                    controls,
822                    &mut self.remapping_mode,
823                    &mut self.last_input,
824                    self.menu_open,
825                ) {
826                    match mapped_inputs {
827                        MappedInput::Menu(menu_inputs) => {
828                            self.last_input_type_menu = true;
829                            for menu_input in menu_inputs {
830                                self.events.push(Event::MenuInput(
831                                    *menu_input,
832                                    event.state == winit::event::ElementState::Pressed,
833                                ));
834                            }
835                        },
836                        MappedInput::Game(game_inputs) => {
837                            self.last_input_type_menu = false;
838                            for game_input in game_inputs {
839                                match game_input {
840                                    GameInput::Fullscreen => {
841                                        if event.state == winit::event::ElementState::Pressed
842                                            && !Self::is_pressed(
843                                                &mut self.keypress_map,
844                                                GameInput::Fullscreen,
845                                            )
846                                        {
847                                            self.toggle_fullscreen = !self.toggle_fullscreen;
848                                        }
849                                        Self::set_pressed(
850                                            &mut self.keypress_map,
851                                            GameInput::Fullscreen,
852                                            event.state,
853                                        );
854                                    },
855                                    GameInput::Screenshot => {
856                                        self.take_screenshot = event.state
857                                            == winit::event::ElementState::Pressed
858                                            && !Self::is_pressed(
859                                                &mut self.keypress_map,
860                                                GameInput::Screenshot,
861                                            );
862                                        Self::set_pressed(
863                                            &mut self.keypress_map,
864                                            GameInput::Screenshot,
865                                            event.state,
866                                        );
867                                    },
868                                    _ => self.events.push(Event::InputUpdate(
869                                        *game_input,
870                                        event.state == winit::event::ElementState::Pressed,
871                                    )),
872                                }
873                            }
874                        },
875                    }
876                }
877            },
878            WindowEvent::Focused(state) => {
879                self.focused = state;
880                self.events.push(Event::Focused(state));
881            },
882            WindowEvent::CursorMoved { position, .. } => {
883                if self.cursor_grabbed {
884                    self.reset_cursor_position();
885                } else {
886                    self.cursor_position = position;
887                }
888            },
889            WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
890                const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
891                self.events.push(Event::Zoom({
892                    let y = match delta {
893                        winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
894                        // TODO: Check to see if there is a better way to find the "line
895                        // height" than just hardcoding 16.0 pixels.  Alternately we could
896                        // get rid of this and have the user set zoom sensitivity, since
897                        // it's unlikely people would expect a configuration file to work
898                        // across operating systems.
899                        winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
900                    };
901                    y * (self.zoom_sensitivity as f32 / 100.0)
902                        * if self.zoom_inversion { -1.0 } else { 1.0 }
903                        * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
904                }))
905            },
906            _ => {},
907        }
908    }
909
910    /// Moves cursor by an offset
911    pub fn offset_cursor(&self, d: Vec2<f32>) {
912        if d != Vec2::zero()
913            && let Err(err) = self
914                .window
915                .set_cursor_position(winit::dpi::LogicalPosition::new(
916                    d.x as f64 + self.cursor_position.x,
917                    d.y as f64 + self.cursor_position.y,
918                ))
919        {
920            // Log this error once rather than every frame
921            static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
922            SPAM_GUARD.call_once(|| {
923                error!("Error setting cursor position: {:?}", err);
924            })
925        }
926    }
927
928    pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
929
930    pub fn grab_cursor(&mut self, grab: bool) {
931        use winit::window::CursorGrabMode;
932
933        self.cursor_grabbed = grab;
934        self.window.set_cursor_visible(!grab);
935        let res = if grab {
936            self.window
937                .set_cursor_grab(CursorGrabMode::Locked)
938                .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
939        } else {
940            self.window.set_cursor_grab(CursorGrabMode::None)
941        };
942
943        if let Err(e) = res {
944            error!(?e, ?grab, "Failed to toggle cursor grab");
945        }
946    }
947
948    /// Reset the cursor position to the last position
949    /// This is used when handling the CursorMoved event to maintain the cursor
950    /// position when it is grabbed
951    fn reset_cursor_position(&self) {
952        if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
953            // Log this error once rather than every frame
954            static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
955            SPAM_GUARD.call_once(|| {
956                error!("Error resetting cursor position: {:?}", err);
957            })
958        }
959    }
960
961    pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
962        let fullscreen = FullScreenSettings {
963            enabled: !self.is_fullscreen(),
964            ..settings.graphics.fullscreen
965        };
966
967        self.set_fullscreen_mode(fullscreen);
968        settings.graphics.fullscreen = fullscreen;
969        settings.save_to_file_warn(config_dir);
970    }
971
972    pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
973
974    /// Select a video mode that fits the specified requirements
975    /// Returns None if a matching video mode doesn't exist or if
976    /// the current monitor can't be retrieved
977    fn select_video_mode_rec(
978        &self,
979        resolution: [u16; 2],
980        bit_depth: Option<u16>,
981        refresh_rate_millihertz: Option<u32>,
982        correct_res: Option<Vec<VideoModeHandle>>,
983        correct_depth: Option<Option<VideoModeHandle>>,
984        correct_rate: Option<Option<VideoModeHandle>>,
985    ) -> Option<VideoModeHandle> {
986        // if a previous iteration of this method filtered the available video modes for
987        // the correct resolution already, load that value, otherwise filter it
988        // in this iteration
989        let correct_res = match correct_res {
990            Some(correct_res) => correct_res,
991            None => self
992                .window
993                .current_monitor()?
994                .video_modes()
995                .filter(|mode| mode.size().width == resolution[0] as u32)
996                .filter(|mode| mode.size().height == resolution[1] as u32)
997                .collect(),
998        };
999
1000        match bit_depth {
1001            // A bit depth is given
1002            Some(depth) => {
1003                // analogous to correct_res
1004                let correct_depth = correct_depth.unwrap_or_else(|| {
1005                    correct_res
1006                        .iter()
1007                        .find(|mode| mode.bit_depth() == depth)
1008                        .cloned()
1009                });
1010
1011                match refresh_rate_millihertz {
1012                    // A bit depth and a refresh rate is given
1013                    Some(rate) => {
1014                        // analogous to correct_res
1015                        let correct_rate = correct_rate.unwrap_or_else(|| {
1016                            correct_res
1017                                .iter()
1018                                .find(|mode| mode.refresh_rate_millihertz() == rate)
1019                                .cloned()
1020                        });
1021
1022                        // if no video mode with the given bit depth and refresh rate exists, fall
1023                        // back to a video mode that fits the resolution and either bit depth or
1024                        // refresh rate depending on which parameter was causing the correct video
1025                        // mode not to be found
1026                        correct_res
1027                            .iter()
1028                            .filter(|mode| mode.bit_depth() == depth)
1029                            .find(|mode| mode.refresh_rate_millihertz() == rate)
1030                            .cloned()
1031                            .or_else(|| {
1032                                if correct_depth.is_none() && correct_rate.is_none() {
1033                                    warn!(
1034                                        "Bit depth and refresh rate specified in settings are \
1035                                         incompatible with the monitor. Choosing highest bit \
1036                                         depth and refresh rate possible instead."
1037                                    );
1038                                }
1039
1040                                self.select_video_mode_rec(
1041                                    resolution,
1042                                    correct_depth.is_some().then_some(depth),
1043                                    correct_rate.is_some().then_some(rate),
1044                                    Some(correct_res),
1045                                    Some(correct_depth),
1046                                    Some(correct_rate),
1047                                )
1048                            })
1049                    },
1050                    // A bit depth and no refresh rate is given
1051                    // if no video mode with the given bit depth exists, fall
1052                    // back to a video mode that fits only the resolution
1053                    None => match correct_depth {
1054                        Some(mode) => Some(mode),
1055                        None => {
1056                            warn!(
1057                                "Bit depth specified in settings is incompatible with the \
1058                                 monitor. Choosing highest bit depth possible instead."
1059                            );
1060
1061                            self.select_video_mode_rec(
1062                                resolution,
1063                                None,
1064                                None,
1065                                Some(correct_res),
1066                                Some(correct_depth),
1067                                None,
1068                            )
1069                        },
1070                    },
1071                }
1072            },
1073            // No bit depth is given
1074            None => match refresh_rate_millihertz {
1075                // No bit depth and a refresh rate is given
1076                Some(rate) => {
1077                    // analogous to correct_res
1078                    let correct_rate = correct_rate.unwrap_or_else(|| {
1079                        correct_res
1080                            .iter()
1081                            .find(|mode| mode.refresh_rate_millihertz() == rate)
1082                            .cloned()
1083                    });
1084
1085                    // if no video mode with the given bit depth exists, fall
1086                    // back to a video mode that fits only the resolution
1087                    match correct_rate {
1088                        Some(mode) => Some(mode),
1089                        None => {
1090                            warn!(
1091                                "Refresh rate specified in settings is incompatible with the \
1092                                 monitor. Choosing highest refresh rate possible instead."
1093                            );
1094
1095                            self.select_video_mode_rec(
1096                                resolution,
1097                                None,
1098                                None,
1099                                Some(correct_res),
1100                                None,
1101                                Some(correct_rate),
1102                            )
1103                        },
1104                    }
1105                },
1106                // No bit depth and no refresh rate is given
1107                // get the video mode with the specified resolution and the max bit depth and
1108                // refresh rate
1109                None => correct_res
1110                    .into_iter()
1111                    // Prefer bit depth over refresh rate
1112                    .sorted_by_key(|mode| mode.bit_depth())
1113                    .max_by_key(|mode| mode.refresh_rate_millihertz()),
1114            },
1115        }
1116    }
1117
1118    fn select_video_mode(
1119        &self,
1120        resolution: [u16; 2],
1121        bit_depth: Option<u16>,
1122        refresh_rate_millihertz: Option<u32>,
1123    ) -> Option<VideoModeHandle> {
1124        // (resolution, bit depth, refresh rate) represents a video mode
1125        // spec: as specified
1126        // max: maximum value available
1127
1128        // order of fallbacks as follows:
1129        // (spec, spec, spec)
1130        // (spec, spec, max), (spec, max, spec)
1131        // (spec, max, max)
1132        // (max, max, max)
1133        match self.select_video_mode_rec(
1134            resolution,
1135            bit_depth,
1136            refresh_rate_millihertz,
1137            None,
1138            None,
1139            None,
1140        ) {
1141            Some(mode) => Some(mode),
1142            // if there is no video mode with the specified resolution,
1143            // fall back to the video mode with max resolution, bit depth and refresh rate
1144            None => {
1145                warn!(
1146                    "Resolution specified in settings is incompatible with the monitor. Choosing \
1147                     highest resolution possible instead."
1148                );
1149                if let Some(monitor) = self.window.current_monitor() {
1150                    let mode = monitor
1151                        .video_modes()
1152                        // Prefer bit depth over refresh rate
1153                        .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1154                        .sorted_by_key(|mode| mode.bit_depth())
1155                        .max_by_key(|mode| mode.size().width);
1156
1157                    if mode.is_none() {
1158                        warn!("Failed to select video mode, no video modes available!!")
1159                    }
1160
1161                    mode
1162                } else {
1163                    warn!("Failed to select video mode, can't get the current monitor!");
1164                    None
1165                }
1166            },
1167        }
1168    }
1169
1170    pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1171        let window = &self.window;
1172        self.fullscreen = fullscreen;
1173        window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1174            FullscreenMode::Exclusive => {
1175                if let Some(video_mode) = self.select_video_mode(
1176                    fullscreen.resolution,
1177                    fullscreen.bit_depth,
1178                    fullscreen.refresh_rate_millihertz,
1179                ) {
1180                    winit::window::Fullscreen::Exclusive(video_mode)
1181                } else {
1182                    warn!(
1183                        "Failed to select a video mode for exclusive fullscreen. Falling back to \
1184                         borderless fullscreen."
1185                    );
1186                    winit::window::Fullscreen::Borderless(None)
1187                }
1188            },
1189            FullscreenMode::Borderless => {
1190                // None here will fullscreen on the current monitor
1191                winit::window::Fullscreen::Borderless(None)
1192            },
1193        }));
1194    }
1195
1196    pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1197
1198    pub fn set_size(&mut self, new_size: Vec2<u32>) {
1199        self.window
1200            .set_min_inner_size(Some(winit::dpi::LogicalSize::new(
1201                new_size.x as f64,
1202                new_size.y as f64,
1203            )));
1204    }
1205
1206    pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1207
1208    pub fn take_screenshot(&mut self, settings: &Settings) {
1209        let sender = self.message_sender.clone();
1210        let mut path = settings.screenshots_path.clone();
1211        self.renderer.create_screenshot(move |image| {
1212            use std::time::SystemTime;
1213
1214            // Handle any error if there was one when generating the image.
1215            let image = match image {
1216                Ok(i) => i,
1217                Err(e) => {
1218                    warn!(?e, "Couldn't generate screenshot");
1219                    let _result = sender.send(format!("Error when generating screenshot: {}", e));
1220                    return;
1221                },
1222            };
1223
1224            // Check if folder exists and create it if it does not
1225            if !path.exists()
1226                && let Err(e) = std::fs::create_dir_all(&path)
1227            {
1228                warn!(?e, ?path, "Couldn't create folder for screenshot");
1229                let _result = sender.send(String::from("Couldn't create folder for screenshot"));
1230            }
1231            path.push(format!(
1232                "screenshot_{}.png",
1233                SystemTime::now()
1234                    .duration_since(SystemTime::UNIX_EPOCH)
1235                    .map(|d| d.as_millis())
1236                    .unwrap_or(0)
1237            ));
1238            // Try to save the image
1239            if let Err(e) = image.save(&path) {
1240                warn!(?e, ?path, "Couldn't save screenshot");
1241                let _result = sender.send(String::from("Couldn't save screenshot"));
1242            } else {
1243                let _result =
1244                    sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1245            }
1246        });
1247    }
1248
1249    fn is_pressed(
1250        map: &mut HashMap<GameInput, winit::event::ElementState>,
1251        input: GameInput,
1252    ) -> bool {
1253        *(map
1254            .entry(input)
1255            .or_insert(winit::event::ElementState::Released))
1256            == winit::event::ElementState::Pressed
1257    }
1258
1259    fn set_pressed(
1260        map: &mut HashMap<GameInput, winit::event::ElementState>,
1261        input: GameInput,
1262        state: winit::event::ElementState,
1263    ) {
1264        map.insert(input, state);
1265    }
1266
1267    // Function used to handle Mouse and Key events. It first checks if we're in
1268    // remapping mode for a specific GameInput or MenuInput. If we are, we modify
1269    // the binding of that GameInput/MenuInput with the KeyMouse passed. Else,
1270    // we return an iterator of the GameInputs/MenuInputs for that KeyMouse. If
1271    // a menu is open, it will return a MenuInput unless there is not one available
1272    // (which it will then return GameInput). If a menu is not open, return
1273    // GameInput.
1274    fn map_input<'a>(
1275        key_mouse: KeyMouse,
1276        controls: &'a mut ControlSettings,
1277        remapping: &mut RemappingMode,
1278        last_input: &mut LastInput,
1279        menu_open: bool,
1280    ) -> Option<MappedInput<'a>> {
1281        let key_mouse = key_mouse.into_upper();
1282
1283        // update last input to be Keyboard if button was pressed
1284        match key_mouse {
1285            KeyMouse::Key(_) => *last_input = LastInput::Keyboard,
1286            KeyMouse::Mouse(_) => *last_input = LastInput::Mouse,
1287        }
1288
1289        match *remapping {
1290            RemappingMode::RemapKeyboard(game_input) => {
1291                controls.modify_binding(game_input, key_mouse);
1292                *remapping = RemappingMode::None;
1293                None
1294            },
1295            RemappingMode::RemapKeyboardMenu(menu_input) => {
1296                controls.modify_menu_binding(menu_input, key_mouse);
1297                *remapping = RemappingMode::None;
1298                None
1299            },
1300            RemappingMode::None => {
1301                if !menu_open {
1302                    // If a menu is not open, simply return any game inputs
1303                    controls
1304                        .get_associated_game_inputs(&key_mouse)
1305                        .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1306                } else {
1307                    // If a menu is open, return the associated MenuInput if any, otherwise return
1308                    // the associated GameInput
1309                    if let Some(menu_inputs) = controls.get_associated_menu_inputs(&key_mouse)
1310                        && !menu_inputs.is_empty()
1311                    {
1312                        return Some(MappedInput::Menu(menu_inputs.iter()));
1313                    }
1314
1315                    controls
1316                        .get_associated_game_inputs(&key_mouse)
1317                        .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1318                }
1319            },
1320            _ => None,
1321        }
1322    }
1323
1324    // Same thing as map_input, but for controller actions
1325    #[expect(clippy::get_first)]
1326    fn map_controller_input<'a>(
1327        controller: &'a mut ControllerSettings,
1328        remapping: &mut RemappingMode,
1329        modifiers: &[Button],
1330        button: &Button,
1331        mod1_input: bool,
1332        mod2_input: bool,
1333        menu_open: bool,
1334    ) -> Option<MappedInput<'a>> {
1335        match *remapping {
1336            RemappingMode::RemapGamepadLayers(game_input) => {
1337                // create the new layer entry
1338                let new_layer_entry = LayerEntry {
1339                    button: *button,
1340                    mod1: if mod1_input {
1341                        Button::Simple(GilButton::RightTrigger)
1342                    } else {
1343                        Button::Simple(GilButton::Unknown)
1344                    },
1345                    mod2: if mod2_input {
1346                        Button::Simple(GilButton::LeftTrigger)
1347                    } else {
1348                        Button::Simple(GilButton::Unknown)
1349                    },
1350                };
1351                controller.modify_layer_binding(game_input, new_layer_entry);
1352                *remapping = RemappingMode::None;
1353                None
1354            },
1355            RemappingMode::RemapGamepadButtons(game_input) => {
1356                controller.modify_button_binding(game_input, *button);
1357                *remapping = RemappingMode::None;
1358                None
1359            },
1360            RemappingMode::RemapGamepadMenu(menu_input) => {
1361                controller.modify_menu_binding(menu_input, *button);
1362                *remapping = RemappingMode::None;
1363                None
1364            },
1365            RemappingMode::None => {
1366                // have to check l_entry1 and l_entry2 so LB+RB can be treated equivalent to
1367                // RB+LB
1368                let l_entry1 = LayerEntry {
1369                    button: *button,
1370                    mod1: modifiers.get(0).copied().unwrap_or_default(),
1371                    mod2: modifiers.get(1).copied().unwrap_or_default(),
1372                };
1373                let l_entry2 = LayerEntry {
1374                    button: *button,
1375                    mod1: modifiers.get(1).copied().unwrap_or_default(),
1376                    mod2: modifiers.get(0).copied().unwrap_or_default(),
1377                };
1378
1379                if !menu_open {
1380                    // If a menu is not open, simply return any game inputs
1381
1382                    // First check layer entries
1383                    if let Some(game_inputs) =
1384                        controller.get_associated_game_layer_inputs(&l_entry1)
1385                    {
1386                        Some(MappedInput::Game(game_inputs.iter()))
1387                    } else if let Some(game_inputs) =
1388                        controller.get_associated_game_layer_inputs(&l_entry2)
1389                    {
1390                        Some(MappedInput::Game(game_inputs.iter()))
1391                    } else {
1392                        // check button entries
1393                        controller
1394                            .get_associated_game_button_inputs(button)
1395                            .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1396                    }
1397                } else {
1398                    // If a menu is open, return the associated MenuInput if any, otherwise return
1399                    // the associated GameInput
1400                    if let Some(menu_inputs) = controller.get_associated_game_menu_inputs(button)
1401                        && !menu_inputs.is_empty()
1402                    {
1403                        return Some(MappedInput::Menu(menu_inputs.iter()));
1404                    }
1405
1406                    // First check layer entries
1407                    if let Some(game_inputs) =
1408                        controller.get_associated_game_layer_inputs(&l_entry1)
1409                    {
1410                        Some(MappedInput::Game(game_inputs.iter()))
1411                    } else if let Some(game_inputs) =
1412                        controller.get_associated_game_layer_inputs(&l_entry2)
1413                    {
1414                        Some(MappedInput::Game(game_inputs.iter()))
1415                    } else {
1416                        // check button entries
1417                        controller
1418                            .get_associated_game_button_inputs(button)
1419                            .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1420                    }
1421                }
1422            },
1423            _ => None,
1424        }
1425    }
1426
1427    pub fn set_remapping_mode(&mut self, r_mode: RemappingMode) { self.remapping_mode = r_mode; }
1428
1429    pub fn reset_mapping_mode(&mut self) { self.remapping_mode = RemappingMode::None; }
1430
1431    pub fn window(&self) -> &winit::window::Window { &self.window }
1432
1433    pub fn modifiers(&self) -> winit::keyboard::ModifiersState { self.modifiers }
1434
1435    pub fn scale_factor(&self) -> f64 { self.scale_factor }
1436
1437    pub fn last_input(&self) -> LastInput { self.last_input }
1438
1439    /// Returns true if the last input type was MenuInput, otherwise returns
1440    /// false
1441    pub fn last_input_type_menu(&self) -> bool { self.last_input_type_menu }
1442
1443    pub fn controller_type(&self) -> ControllerType { self.controller_type }
1444}
1445
1446#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1447pub enum FullscreenMode {
1448    Exclusive,
1449    #[serde(other)]
1450    #[default]
1451    Borderless,
1452}
1453
1454#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1455#[serde(default)]
1456pub struct WindowSettings {
1457    pub size: [u32; 2],
1458    pub maximised: bool,
1459}
1460
1461impl Default for WindowSettings {
1462    fn default() -> Self {
1463        Self {
1464            size: [1280, 720],
1465            maximised: false,
1466        }
1467    }
1468}
1469
1470#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1471#[serde(default)]
1472pub struct FullScreenSettings {
1473    pub enabled: bool,
1474    pub mode: FullscreenMode,
1475    pub resolution: [u16; 2],
1476    pub bit_depth: Option<u16>,
1477    pub refresh_rate_millihertz: Option<u32>,
1478}
1479
1480impl Default for FullScreenSettings {
1481    fn default() -> Self {
1482        Self {
1483            enabled: true,
1484            mode: FullscreenMode::Borderless,
1485            resolution: [1920, 1080],
1486            bit_depth: None,
1487            refresh_rate_millihertz: None,
1488        }
1489    }
1490}