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