veloren_voxygen/
window.rs

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