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                    // Extra qualifier because both Ctrl and Win map to Cmd on Mac
306                    "Left Cmd (Super)"
307                } else {
308                    // Assume Linux, BSD, etc.
309                    "Super"
310                }
311            },
312            // Most keyboards don't have this key, so throw in all the qualifiers
313            Key(RWin) => {
314                if cfg!(target_family = "windows") {
315                    "Right Win"
316                } else if cfg!(target_os = "macos") {
317                    // Extra qualifier because both Ctrl and Win map to Cmd on Mac
318                    "Right Cmd (Super)"
319                } else {
320                    // Assume Linux, BSD, etc.
321                    "Right Super"
322                }
323            },
324            Key(Semicolon) => ";",
325            Key(Slash) => "/",
326            Key(Sleep) => "Sleep",
327            Key(Stop) => "Media Stop",
328            Key(NumpadSubtract) => "Num -",
329            Key(Sysrq) => "Sysrq",
330            Key(Tab) => "Tab",
331            Key(Underline) => "_",
332            Key(Unlabeled) => "No Name",
333            Key(VolumeDown) => "Volume Down",
334            Key(VolumeUp) => "Volume Up",
335            Key(Wake) => "Wake",
336            Key(WebBack) => "Browser Back",
337            Key(WebFavorites) => "Browser Favorites",
338            Key(WebForward) => "Browser Forward",
339            Key(WebHome) => "Browser Home",
340            Key(WebRefresh) => "Browser Refresh",
341            Key(WebSearch) => "Browser Search",
342            Key(WebStop) => "Browser Stop",
343            Key(Yen) => "Yen",
344            Key(Copy) => "Copy",
345            Key(Paste) => "Paste",
346            Key(Cut) => "Cut",
347            Key(Asterisk) => "*",
348            Mouse(MouseButton::Left) => "Left Click",
349            Mouse(MouseButton::Right) => "Right Click",
350            Mouse(MouseButton::Middle) => "Middle Click",
351            Mouse(MouseButton::Other(button)) => {
352                // Additional mouse buttons after middle click start at 1
353                return format!("Mouse {}", button + 3);
354            },
355            ScanKey(scancode) => {
356                return if let Some(layout) = key_layout {
357                    layout.get_key_as_string(*scancode)
358                } else {
359                    format!("Unknown (0x{:X})", scancode)
360                };
361            },
362        };
363
364        key_string.to_owned()
365    }
366
367    /// If it exists, returns the shortened version of a key name
368    /// (e.g. Left Click -> M1)
369    pub fn try_shortened(&self, _key_layout: &Option<KeyLayout>) -> Option<String> {
370        use self::KeyMouse::*;
371        use winit::event::{MouseButton, VirtualKeyCode::*};
372        let key_string = match self {
373            Mouse(MouseButton::Left) => "M1",
374            Mouse(MouseButton::Right) => "M2",
375            Mouse(MouseButton::Middle) => "M3",
376            Mouse(MouseButton::Other(button)) => {
377                // Additional mouse buttons after middle click start at 1
378                return Some(format!("M{}", button + 3));
379            },
380            Key(Back) => "Back",
381            Key(LShift) => "LShft",
382            Key(RShift) => "RShft",
383            _ => return None,
384        };
385
386        Some(key_string.to_owned())
387    }
388
389    /// Returns shortest name of key (e.g. Left Click - M1)
390    /// If key doesn't have shorter version, use regular one.
391    ///
392    /// Use it in case if space does really matter.
393    pub fn display_shortest(&self, key_layout: &Option<KeyLayout>) -> String {
394        self.try_shortened(key_layout)
395            .unwrap_or_else(|| self.display_string(key_layout))
396    }
397}
398
399pub struct Window {
400    renderer: Renderer,
401    window: winit::window::Window,
402    cursor_grabbed: bool,
403    pub pan_sensitivity: u32,
404    pub zoom_sensitivity: u32,
405    pub zoom_inversion: bool,
406    pub mouse_y_inversion: bool,
407    fullscreen: FullScreenSettings,
408    modifiers: winit::event::ModifiersState,
409    // Track if at least one Resized event has occured since the last `fetch_events` call
410    // Used for deduplication of resizes.
411    resized: bool,
412    scale_factor: f64,
413    needs_refresh_resize: bool,
414    keypress_map: HashMap<GameInput, winit::event::ElementState>,
415    pub remapping_keybindings: Option<GameInput>,
416    events: Vec<Event>,
417    pub focused: bool,
418    gilrs: Option<Gilrs>,
419    pub controller_settings: ControllerSettings,
420    pub controller_modifiers: Vec<Button>,
421    cursor_position: winit::dpi::PhysicalPosition<f64>,
422    mouse_emulation_vec: Vec2<f32>,
423    // Currently used to send and receive screenshot result messages
424    message_sender: channel::Sender<String>,
425    message_receiver: channel::Receiver<String>,
426    // Used for screenshots & fullscreen toggle to deduplicate/postpone to after event handler
427    take_screenshot: bool,
428    toggle_fullscreen: bool,
429    pub key_layout: Option<KeyLayout>,
430}
431
432impl Window {
433    pub fn new(
434        settings: &Settings,
435        runtime: &tokio::runtime::Runtime,
436    ) -> Result<(Window, EventLoop), Error> {
437        let event_loop = EventLoop::new();
438
439        let window = settings.graphics.window;
440
441        let win_builder = winit::window::WindowBuilder::new()
442            .with_title("Veloren")
443            .with_inner_size(winit::dpi::LogicalSize::new(
444                window.size[0] as f64,
445                window.size[1] as f64,
446            ))
447            .with_maximized(window.maximised);
448
449        // Avoid cpal / winit OleInitialize conflict
450        // See: https://github.com/rust-windowing/winit/pull/1524
451        #[cfg(target_family = "windows")]
452        let win_builder = winit::platform::windows::WindowBuilderExtWindows::with_drag_and_drop(
453            win_builder,
454            false,
455        );
456
457        let window = win_builder.build(&event_loop).unwrap();
458
459        let renderer = Renderer::new(&window, settings.graphics.render_mode.clone(), runtime)?;
460
461        let keypress_map = HashMap::new();
462
463        let gilrs = match Gilrs::new() {
464            Ok(gilrs) => Some(gilrs),
465            Err(gilrs::Error::NotImplemented(_dummy)) => {
466                warn!("Controller input is unsupported on this platform.");
467                None
468            },
469            Err(gilrs::Error::InvalidAxisToBtn) => {
470                error!(
471                    "Invalid AxisToBtn controller mapping. Falling back to no controller support."
472                );
473                None
474            },
475            Err(gilrs::Error::Other(e)) => {
476                error!(
477                    ?e,
478                    "Platform-specific error when creating a Gilrs instance. Falling back to no \
479                     controller support."
480                );
481                None
482            },
483            Err(e) => {
484                error!(
485                    ?e,
486                    "Unspecified error when creating a Gilrs instance. Falling back to no \
487                     controller support."
488                );
489                None
490            },
491        };
492
493        let controller_settings = ControllerSettings::from(&settings.controller);
494
495        let (message_sender, message_receiver): (
496            channel::Sender<String>,
497            channel::Receiver<String>,
498        ) = channel::unbounded::<String>();
499
500        let scale_factor = window.scale_factor();
501
502        let key_layout = match KeyLayout::new_from_window(&window) {
503            Ok(kl) => Some(kl),
504            Err(err) => {
505                warn!(
506                    ?err,
507                    "Failed to construct the scancode to keyname mapper, falling back to \
508                     displaying Unknown(<scancode>)."
509                );
510                None
511            },
512        };
513
514        let mut this = Self {
515            renderer,
516            window,
517            cursor_grabbed: false,
518            pan_sensitivity: settings.gameplay.pan_sensitivity,
519            zoom_sensitivity: settings.gameplay.zoom_sensitivity,
520            zoom_inversion: settings.gameplay.zoom_inversion,
521            mouse_y_inversion: settings.gameplay.mouse_y_inversion,
522            fullscreen: FullScreenSettings::default(),
523            modifiers: Default::default(),
524            scale_factor,
525            resized: false,
526            needs_refresh_resize: false,
527            keypress_map,
528            remapping_keybindings: None,
529            events: Vec::new(),
530            focused: true,
531            gilrs,
532            controller_settings,
533            controller_modifiers: Vec::new(),
534            cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
535            mouse_emulation_vec: Vec2::zero(),
536            // Currently used to send and receive screenshot result messages
537            message_sender,
538            message_receiver,
539            take_screenshot: false,
540            toggle_fullscreen: false,
541            key_layout,
542        };
543
544        this.set_fullscreen_mode(settings.graphics.fullscreen);
545
546        Ok((this, event_loop))
547    }
548
549    pub fn renderer(&self) -> &Renderer { &self.renderer }
550
551    pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
552
553    pub fn resolve_deduplicated_events(
554        &mut self,
555        settings: &mut Settings,
556        config_dir: &std::path::Path,
557    ) {
558        // Handle screenshots and toggling fullscreen
559        if self.take_screenshot {
560            self.take_screenshot = false;
561            self.take_screenshot(settings);
562        }
563        if self.toggle_fullscreen {
564            self.toggle_fullscreen = false;
565            self.toggle_fullscreen(settings, config_dir);
566        }
567    }
568
569    #[expect(clippy::get_first)]
570    pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
571        span!(_guard, "fetch_events", "Window::fetch_events");
572        // Refresh ui size (used when changing playstates)
573        if self.needs_refresh_resize {
574            let scale_factor = self.window.scale_factor();
575            let physical = self.window.inner_size();
576
577            let logical_size =
578                Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
579            self.events
580                .push(Event::Ui(ui::Event::new_resize(logical_size)));
581            self.events.push(Event::IcedUi(iced::Event::Window(
582                iced::window::Event::Resized {
583                    width: logical_size.x as u32,
584                    height: logical_size.y as u32,
585                },
586            )));
587            self.events.push(Event::ScaleFactorChanged(scale_factor));
588            self.needs_refresh_resize = false;
589        }
590
591        // Handle deduplicated resizing that occured
592        if self.resized {
593            self.resized = false;
594            // We don't use the size provided by the event because more resize events could
595            // have happened since, making the value outdated, so we must query directly
596            // from the window to prevent errors
597            let physical = self.window.inner_size();
598            let scale_factor = self.window.scale_factor();
599            let is_maximized = self.window.is_maximized();
600
601            self.renderer
602                .on_resize(Vec2::new(physical.width, physical.height));
603            self.events
604                .push(Event::Resize(Vec2::new(physical.width, physical.height)));
605
606            let logical_size =
607                Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
608
609            // Emit event for the UI
610            self.events
611                .push(Event::Ui(ui::Event::new_resize(logical_size)));
612            self.events.push(Event::IcedUi(iced::Event::Window(
613                iced::window::Event::Resized {
614                    width: logical_size.x as u32,
615                    height: logical_size.y as u32,
616                },
617            )));
618
619            // Save new window state in settings
620            //
621            // We don't save the size if it's less than 1 because wgpu fails to create a
622            // surface with zero size.
623            if logical_size.x >= 1.0 && logical_size.y >= 1.0 {
624                settings.graphics.window.size = [logical_size.x as u32, logical_size.y as u32];
625            }
626            settings.graphics.window.maximised = is_maximized;
627        }
628
629        // Receive any messages sent through the message channel
630        for message in self.message_receiver.try_iter() {
631            self.events.push(Event::ScreenshotMessage(message))
632        }
633
634        if let Some(gilrs) = &mut self.gilrs {
635            while let Some(event) = gilrs.next_event() {
636                fn handle_buttons(
637                    settings: &ControllerSettings,
638                    modifiers: &mut Vec<Button>,
639                    events: &mut Vec<Event>,
640                    button: &Button,
641                    is_pressed: bool,
642                ) {
643                    if settings.modifier_buttons.contains(button) {
644                        if is_pressed {
645                            modifiers.push(*button);
646                        // There is a possibility of voxygen not having
647                        // registered the initial press event (either because it
648                        // hadn't started yet, or whatever else) hence the
649                        // modifier has no position in the list, unwrapping
650                        // here would cause a crash in those cases
651                        } else if let Some(index) =
652                            modifiers.iter().position(|modifier| modifier == button)
653                        {
654                            modifiers.remove(index);
655                        }
656                    }
657
658                    // have to make two LayerEntries so LB+RB can be treated equivalent to RB+LB
659                    let l_entry1 = LayerEntry {
660                        button: *button,
661                        mod1: modifiers.get(0).copied().unwrap_or_default(),
662                        mod2: modifiers.get(1).copied().unwrap_or_default(),
663                    };
664                    let l_entry2 = LayerEntry {
665                        button: *button,
666                        mod1: modifiers.get(1).copied().unwrap_or_default(),
667                        mod2: modifiers.get(0).copied().unwrap_or_default(),
668                    };
669
670                    // have to check l_entry1 and then l_entry2 so LB+RB can be treated equivalent
671                    // to RB+LB
672                    if let Some(evs) = settings.inverse_layer_button_map.get(&l_entry1) {
673                        for ev in evs {
674                            events.push(Event::InputUpdate(*ev, is_pressed));
675                        }
676                    } else if let Some(evs) = settings.inverse_layer_button_map.get(&l_entry2) {
677                        for ev in evs {
678                            events.push(Event::InputUpdate(*ev, is_pressed));
679                        }
680                    }
681                    if let Some(evs) = settings.inverse_game_button_map.get(button) {
682                        for ev in evs {
683                            events.push(Event::InputUpdate(*ev, is_pressed));
684                        }
685                    }
686                    if let Some(evs) = settings.inverse_menu_button_map.get(button) {
687                        for ev in evs {
688                            events.push(Event::MenuInput(*ev, is_pressed));
689                        }
690                    }
691                }
692
693                match event.event {
694                    EventType::ButtonPressed(button, code)
695                    | EventType::ButtonRepeated(button, code) => {
696                        handle_buttons(
697                            &self.controller_settings,
698                            &mut self.controller_modifiers,
699                            &mut self.events,
700                            &Button::from((button, code)),
701                            true,
702                        );
703                    },
704                    EventType::ButtonReleased(button, code) => {
705                        handle_buttons(
706                            &self.controller_settings,
707                            &mut self.controller_modifiers,
708                            &mut self.events,
709                            &Button::from((button, code)),
710                            false,
711                        );
712                    },
713                    EventType::ButtonChanged(button, _value, code) => {
714                        if let Some(actions) = self
715                            .controller_settings
716                            .inverse_game_analog_button_map
717                            .get(&AnalogButton::from((button, code)))
718                        {
719                            #[expect(clippy::never_loop)]
720                            for action in actions {
721                                match *action {}
722                            }
723                        }
724                        if let Some(actions) = self
725                            .controller_settings
726                            .inverse_menu_analog_button_map
727                            .get(&AnalogButton::from((button, code)))
728                        {
729                            #[expect(clippy::never_loop)]
730                            for action in actions {
731                                match *action {}
732                            }
733                        }
734                    },
735
736                    EventType::AxisChanged(axis, value, code) => {
737                        let value = if self
738                            .controller_settings
739                            .inverted_axes
740                            .contains(&Axis::from((axis, code)))
741                        {
742                            -value
743                        } else {
744                            value
745                        };
746
747                        let value = self
748                            .controller_settings
749                            .apply_axis_deadzone(&Axis::from((axis, code)), value);
750
751                        if self.cursor_grabbed {
752                            if let Some(actions) = self
753                                .controller_settings
754                                .inverse_game_axis_map
755                                .get(&Axis::from((axis, code)))
756                            {
757                                for action in actions {
758                                    match *action {
759                                        AxisGameAction::MovementX => {
760                                            self.events.push(Event::AnalogGameInput(
761                                                AnalogGameInput::MovementX(value),
762                                            ));
763                                        },
764                                        AxisGameAction::MovementY => {
765                                            self.events.push(Event::AnalogGameInput(
766                                                AnalogGameInput::MovementY(value),
767                                            ));
768                                        },
769                                        AxisGameAction::CameraX => {
770                                            self.events.push(Event::AnalogGameInput(
771                                                AnalogGameInput::CameraX(
772                                                    value
773                                                        * self.controller_settings.pan_sensitivity
774                                                            as f32
775                                                        / 100.0,
776                                                ),
777                                            ));
778                                        },
779                                        AxisGameAction::CameraY => {
780                                            let pan_invert_y =
781                                                match self.controller_settings.pan_invert_y {
782                                                    true => -1.0,
783                                                    false => 1.0,
784                                                };
785
786                                            self.events.push(Event::AnalogGameInput(
787                                                AnalogGameInput::CameraY(
788                                                    -value
789                                                        * self.controller_settings.pan_sensitivity
790                                                            as f32
791                                                        * pan_invert_y
792                                                        / 100.0,
793                                                ),
794                                            ));
795                                        },
796                                    }
797                                }
798                            }
799                        } else if let Some(actions) = self
800                            .controller_settings
801                            .inverse_menu_axis_map
802                            .get(&Axis::from((axis, code)))
803                        {
804                            // TODO: possibly add sensitivity settings when this is used
805                            for action in actions {
806                                match *action {
807                                    AxisMenuAction::MoveX => {
808                                        self.events.push(Event::AnalogMenuInput(
809                                            AnalogMenuInput::MoveX(value),
810                                        ));
811                                    },
812                                    AxisMenuAction::MoveY => {
813                                        self.events.push(Event::AnalogMenuInput(
814                                            AnalogMenuInput::MoveY(value),
815                                        ));
816                                    },
817                                    AxisMenuAction::ScrollX => {
818                                        self.events.push(Event::AnalogMenuInput(
819                                            AnalogMenuInput::ScrollX(value),
820                                        ));
821                                    },
822                                    AxisMenuAction::ScrollY => {
823                                        self.events.push(Event::AnalogMenuInput(
824                                            AnalogMenuInput::ScrollY(value),
825                                        ));
826                                    },
827                                }
828                            }
829                        }
830                    },
831                    _ => {},
832                }
833            }
834        }
835
836        let mut events = std::mem::take(&mut self.events);
837        // Mouse emulation for the menus, to be removed when a proper menu navigation
838        // system is available
839        if !self.cursor_grabbed {
840            events = events
841                .into_iter()
842                .filter_map(|event| match event {
843                    Event::AnalogMenuInput(input) => match input {
844                        AnalogMenuInput::MoveX(d) => {
845                            self.mouse_emulation_vec.x = d;
846                            None
847                        },
848                        AnalogMenuInput::MoveY(d) => {
849                            // This just has to be inverted for some reason
850                            self.mouse_emulation_vec.y = d * -1.0;
851                            None
852                        },
853                        input => Some(Event::AnalogMenuInput(input)),
854                    },
855                    Event::MenuInput(MenuInput::Apply, state) => Some(match state {
856                        true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
857                            conrod_core::input::Button::Mouse(
858                                conrod_core::input::state::mouse::Button::Left,
859                            ),
860                        ))),
861                        false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
862                            conrod_core::input::Button::Mouse(
863                                conrod_core::input::state::mouse::Button::Left,
864                            ),
865                        ))),
866                    }),
867                    _ => Some(event),
868                })
869                .collect();
870
871            let sensitivity = self.controller_settings.mouse_emulation_sensitivity;
872            // TODO: make this independent of framerate
873            // TODO: consider multiplying by scale factor
874            self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
875        }
876
877        events
878    }
879
880    pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
881        use winit::event::DeviceEvent;
882
883        let mouse_y_inversion = match self.mouse_y_inversion {
884            true => -1.0,
885            false => 1.0,
886        };
887
888        match event {
889            DeviceEvent::MouseMotion {
890                delta: (dx, dy), ..
891            } if self.focused => {
892                let delta = Vec2::new(
893                    dx as f32 * (self.pan_sensitivity as f32 / 100.0),
894                    dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
895                );
896
897                if self.cursor_grabbed {
898                    self.events.push(Event::CursorPan(delta));
899                } else {
900                    self.events.push(Event::CursorMove(delta));
901                }
902            },
903            _ => {},
904        }
905    }
906
907    pub fn handle_window_event(
908        &mut self,
909        event: winit::event::WindowEvent,
910        settings: &mut Settings,
911    ) {
912        use winit::event::WindowEvent;
913
914        let controls = &mut settings.controls;
915
916        match event {
917            WindowEvent::CloseRequested => self.events.push(Event::Close),
918            WindowEvent::Resized(_) => {
919                self.resized = true;
920            },
921            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
922                // TODO: is window resized event emitted? or do we need to handle that here?
923                self.scale_factor = scale_factor;
924                self.events.push(Event::ScaleFactorChanged(scale_factor));
925            },
926            WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
927                self.events
928                    .push(Event::Moved(Vec2::new(x as u32, y as u32)));
929            },
930            WindowEvent::ReceivedCharacter(c) => self.events.push(Event::Char(c)),
931            WindowEvent::MouseInput { button, state, .. } => {
932                if let (true, Some(game_inputs)) =
933                    // Mouse input not mapped to input if it is not grabbed
934                    (
935                        self.cursor_grabbed,
936                        Window::map_input(
937                            KeyMouse::Mouse(button),
938                            controls,
939                            &mut self.remapping_keybindings,
940                        ),
941                    )
942                {
943                    for game_input in game_inputs {
944                        self.events.push(Event::InputUpdate(
945                            *game_input,
946                            state == winit::event::ElementState::Pressed,
947                        ));
948                    }
949                }
950                self.events.push(Event::MouseButton(button, state));
951            },
952            WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
953            WindowEvent::KeyboardInput {
954                input,
955                is_synthetic,
956                ..
957            } => {
958                // Ignore synthetic tab presses so that we don't get tabs when alt-tabbing back
959                // into the window
960                if matches!(
961                    input.virtual_keycode,
962                    Some(winit::event::VirtualKeyCode::Tab)
963                ) && is_synthetic
964                {
965                    return;
966                }
967                // Ignore Alt-F4 so we don't try to do anything heavy like take a screenshot
968                // when the window is about to close
969                if matches!(input, winit::event::KeyboardInput {
970                    state: winit::event::ElementState::Pressed,
971                    virtual_keycode: Some(winit::event::VirtualKeyCode::F4),
972                    ..
973                }) && self.modifiers.alt()
974                {
975                    return;
976                }
977
978                let input_key = match input.virtual_keycode {
979                    Some(key) => KeyMouse::Key(key),
980                    None => KeyMouse::ScanKey(input.scancode),
981                };
982
983                if let Some(game_inputs) =
984                    Window::map_input(input_key, controls, &mut self.remapping_keybindings)
985                {
986                    for game_input in game_inputs {
987                        match game_input {
988                            GameInput::Fullscreen => {
989                                if input.state == winit::event::ElementState::Pressed
990                                    && !Self::is_pressed(
991                                        &mut self.keypress_map,
992                                        GameInput::Fullscreen,
993                                    )
994                                {
995                                    self.toggle_fullscreen = !self.toggle_fullscreen;
996                                }
997                                Self::set_pressed(
998                                    &mut self.keypress_map,
999                                    GameInput::Fullscreen,
1000                                    input.state,
1001                                );
1002                            },
1003                            GameInput::Screenshot => {
1004                                self.take_screenshot = input.state
1005                                    == winit::event::ElementState::Pressed
1006                                    && !Self::is_pressed(
1007                                        &mut self.keypress_map,
1008                                        GameInput::Screenshot,
1009                                    );
1010                                Self::set_pressed(
1011                                    &mut self.keypress_map,
1012                                    GameInput::Screenshot,
1013                                    input.state,
1014                                );
1015                            },
1016                            _ => self.events.push(Event::InputUpdate(
1017                                *game_input,
1018                                input.state == winit::event::ElementState::Pressed,
1019                            )),
1020                        }
1021                    }
1022                }
1023            },
1024            WindowEvent::Focused(state) => {
1025                self.focused = state;
1026                self.events.push(Event::Focused(state));
1027            },
1028            WindowEvent::CursorMoved { position, .. } => {
1029                if self.cursor_grabbed {
1030                    self.reset_cursor_position();
1031                } else {
1032                    self.cursor_position = position;
1033                }
1034            },
1035            WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
1036                const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
1037                self.events.push(Event::Zoom({
1038                    let y = match delta {
1039                        winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
1040                        // TODO: Check to see if there is a better way to find the "line
1041                        // height" than just hardcoding 16.0 pixels.  Alternately we could
1042                        // get rid of this and have the user set zoom sensitivity, since
1043                        // it's unlikely people would expect a configuration file to work
1044                        // across operating systems.
1045                        winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
1046                    };
1047                    y * (self.zoom_sensitivity as f32 / 100.0)
1048                        * if self.zoom_inversion { -1.0 } else { 1.0 }
1049                        * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
1050                }))
1051            },
1052            _ => {},
1053        }
1054    }
1055
1056    /// Moves cursor by an offset
1057    pub fn offset_cursor(&self, d: Vec2<f32>) {
1058        if d != Vec2::zero() {
1059            if let Err(err) = self
1060                .window
1061                .set_cursor_position(winit::dpi::LogicalPosition::new(
1062                    d.x as f64 + self.cursor_position.x,
1063                    d.y as f64 + self.cursor_position.y,
1064                ))
1065            {
1066                // Log this error once rather than every frame
1067                static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
1068                SPAM_GUARD.call_once(|| {
1069                    error!("Error setting cursor position: {:?}", err);
1070                })
1071            }
1072        }
1073    }
1074
1075    pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
1076
1077    pub fn grab_cursor(&mut self, grab: bool) {
1078        use winit::window::CursorGrabMode;
1079
1080        self.cursor_grabbed = grab;
1081        self.window.set_cursor_visible(!grab);
1082        let res = if grab {
1083            self.window
1084                .set_cursor_grab(CursorGrabMode::Locked)
1085                .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
1086        } else {
1087            self.window.set_cursor_grab(CursorGrabMode::None)
1088        };
1089
1090        if let Err(e) = res {
1091            error!(?e, ?grab, "Failed to toggle cursor grab");
1092        }
1093    }
1094
1095    /// Reset the cursor position to the last position
1096    /// This is used when handling the CursorMoved event to maintain the cursor
1097    /// position when it is grabbed
1098    fn reset_cursor_position(&self) {
1099        if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
1100            // Log this error once rather than every frame
1101            static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
1102            SPAM_GUARD.call_once(|| {
1103                error!("Error resetting cursor position: {:?}", err);
1104            })
1105        }
1106    }
1107
1108    pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
1109        let fullscreen = FullScreenSettings {
1110            enabled: !self.is_fullscreen(),
1111            ..settings.graphics.fullscreen
1112        };
1113
1114        self.set_fullscreen_mode(fullscreen);
1115        settings.graphics.fullscreen = fullscreen;
1116        settings.save_to_file_warn(config_dir);
1117    }
1118
1119    pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
1120
1121    /// Select a video mode that fits the specified requirements
1122    /// Returns None if a matching video mode doesn't exist or if
1123    /// the current monitor can't be retrieved
1124    fn select_video_mode_rec(
1125        &self,
1126        resolution: [u16; 2],
1127        bit_depth: Option<u16>,
1128        refresh_rate_millihertz: Option<u32>,
1129        correct_res: Option<Vec<VideoMode>>,
1130        correct_depth: Option<Option<VideoMode>>,
1131        correct_rate: Option<Option<VideoMode>>,
1132    ) -> Option<VideoMode> {
1133        // if a previous iteration of this method filtered the available video modes for
1134        // the correct resolution already, load that value, otherwise filter it
1135        // in this iteration
1136        let correct_res = match correct_res {
1137            Some(correct_res) => correct_res,
1138            None => self
1139                .window
1140                .current_monitor()?
1141                .video_modes()
1142                .filter(|mode| mode.size().width == resolution[0] as u32)
1143                .filter(|mode| mode.size().height == resolution[1] as u32)
1144                .collect(),
1145        };
1146
1147        match bit_depth {
1148            // A bit depth is given
1149            Some(depth) => {
1150                // analogous to correct_res
1151                let correct_depth = correct_depth.unwrap_or_else(|| {
1152                    correct_res
1153                        .iter()
1154                        .find(|mode| mode.bit_depth() == depth)
1155                        .cloned()
1156                });
1157
1158                match refresh_rate_millihertz {
1159                    // A bit depth and a refresh rate is given
1160                    Some(rate) => {
1161                        // analogous to correct_res
1162                        let correct_rate = correct_rate.unwrap_or_else(|| {
1163                            correct_res
1164                                .iter()
1165                                .find(|mode| mode.refresh_rate_millihertz() == rate)
1166                                .cloned()
1167                        });
1168
1169                        // if no video mode with the given bit depth and refresh rate exists, fall
1170                        // back to a video mode that fits the resolution and either bit depth or
1171                        // refresh rate depending on which parameter was causing the correct video
1172                        // mode not to be found
1173                        correct_res
1174                            .iter()
1175                            .filter(|mode| mode.bit_depth() == depth)
1176                            .find(|mode| mode.refresh_rate_millihertz() == rate)
1177                            .cloned()
1178                            .or_else(|| {
1179                                if correct_depth.is_none() && correct_rate.is_none() {
1180                                    warn!(
1181                                        "Bit depth and refresh rate specified in settings are \
1182                                         incompatible with the monitor. Choosing highest bit \
1183                                         depth and refresh rate possible instead."
1184                                    );
1185                                }
1186
1187                                self.select_video_mode_rec(
1188                                    resolution,
1189                                    correct_depth.is_some().then_some(depth),
1190                                    correct_rate.is_some().then_some(rate),
1191                                    Some(correct_res),
1192                                    Some(correct_depth),
1193                                    Some(correct_rate),
1194                                )
1195                            })
1196                    },
1197                    // A bit depth and no refresh rate is given
1198                    // if no video mode with the given bit depth exists, fall
1199                    // back to a video mode that fits only the resolution
1200                    None => match correct_depth {
1201                        Some(mode) => Some(mode),
1202                        None => {
1203                            warn!(
1204                                "Bit depth specified in settings is incompatible with the \
1205                                 monitor. Choosing highest bit depth possible instead."
1206                            );
1207
1208                            self.select_video_mode_rec(
1209                                resolution,
1210                                None,
1211                                None,
1212                                Some(correct_res),
1213                                Some(correct_depth),
1214                                None,
1215                            )
1216                        },
1217                    },
1218                }
1219            },
1220            // No bit depth is given
1221            None => match refresh_rate_millihertz {
1222                // No bit depth and a refresh rate is given
1223                Some(rate) => {
1224                    // analogous to correct_res
1225                    let correct_rate = correct_rate.unwrap_or_else(|| {
1226                        correct_res
1227                            .iter()
1228                            .find(|mode| mode.refresh_rate_millihertz() == rate)
1229                            .cloned()
1230                    });
1231
1232                    // if no video mode with the given bit depth exists, fall
1233                    // back to a video mode that fits only the resolution
1234                    match correct_rate {
1235                        Some(mode) => Some(mode),
1236                        None => {
1237                            warn!(
1238                                "Refresh rate specified in settings is incompatible with the \
1239                                 monitor. Choosing highest refresh rate possible instead."
1240                            );
1241
1242                            self.select_video_mode_rec(
1243                                resolution,
1244                                None,
1245                                None,
1246                                Some(correct_res),
1247                                None,
1248                                Some(correct_rate),
1249                            )
1250                        },
1251                    }
1252                },
1253                // No bit depth and no refresh rate is given
1254                // get the video mode with the specified resolution and the max bit depth and
1255                // refresh rate
1256                None => correct_res
1257                    .into_iter()
1258                    // Prefer bit depth over refresh rate
1259                    .sorted_by_key(|mode| mode.bit_depth())
1260                    .max_by_key(|mode| mode.refresh_rate_millihertz()),
1261            },
1262        }
1263    }
1264
1265    fn select_video_mode(
1266        &self,
1267        resolution: [u16; 2],
1268        bit_depth: Option<u16>,
1269        refresh_rate_millihertz: Option<u32>,
1270    ) -> Option<VideoMode> {
1271        // (resolution, bit depth, refresh rate) represents a video mode
1272        // spec: as specified
1273        // max: maximum value available
1274
1275        // order of fallbacks as follows:
1276        // (spec, spec, spec)
1277        // (spec, spec, max), (spec, max, spec)
1278        // (spec, max, max)
1279        // (max, max, max)
1280        match self.select_video_mode_rec(
1281            resolution,
1282            bit_depth,
1283            refresh_rate_millihertz,
1284            None,
1285            None,
1286            None,
1287        ) {
1288            Some(mode) => Some(mode),
1289            // if there is no video mode with the specified resolution,
1290            // fall back to the video mode with max resolution, bit depth and refresh rate
1291            None => {
1292                warn!(
1293                    "Resolution specified in settings is incompatible with the monitor. Choosing \
1294                     highest resolution possible instead."
1295                );
1296                if let Some(monitor) = self.window.current_monitor() {
1297                    let mode = monitor
1298                        .video_modes()
1299                        // Prefer bit depth over refresh rate
1300                        .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1301                        .sorted_by_key(|mode| mode.bit_depth())
1302                        .max_by_key(|mode| mode.size().width);
1303
1304                    if mode.is_none() {
1305                        warn!("Failed to select video mode, no video modes available!!")
1306                    }
1307
1308                    mode
1309                } else {
1310                    warn!("Failed to select video mode, can't get the current monitor!");
1311                    None
1312                }
1313            },
1314        }
1315    }
1316
1317    pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1318        let window = &self.window;
1319        self.fullscreen = fullscreen;
1320        window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1321            FullscreenMode::Exclusive => {
1322                if let Some(video_mode) = self.select_video_mode(
1323                    fullscreen.resolution,
1324                    fullscreen.bit_depth,
1325                    fullscreen.refresh_rate_millihertz,
1326                ) {
1327                    winit::window::Fullscreen::Exclusive(video_mode)
1328                } else {
1329                    warn!(
1330                        "Failed to select a video mode for exclusive fullscreen. Falling back to \
1331                         borderless fullscreen."
1332                    );
1333                    winit::window::Fullscreen::Borderless(None)
1334                }
1335            },
1336            FullscreenMode::Borderless => {
1337                // None here will fullscreen on the current monitor
1338                winit::window::Fullscreen::Borderless(None)
1339            },
1340        }));
1341    }
1342
1343    pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1344
1345    pub fn set_size(&mut self, new_size: Vec2<u32>) {
1346        self.window.set_inner_size(winit::dpi::LogicalSize::new(
1347            new_size.x as f64,
1348            new_size.y as f64,
1349        ));
1350    }
1351
1352    pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1353
1354    pub fn take_screenshot(&mut self, settings: &Settings) {
1355        let sender = self.message_sender.clone();
1356        let mut path = settings.screenshots_path.clone();
1357        self.renderer.create_screenshot(move |image| {
1358            use std::time::SystemTime;
1359
1360            // Handle any error if there was one when generating the image.
1361            let image = match image {
1362                Ok(i) => i,
1363                Err(e) => {
1364                    warn!(?e, "Couldn't generate screenshot");
1365                    let _result = sender.send(format!("Error when generating screenshot: {}", e));
1366                    return;
1367                },
1368            };
1369
1370            // Check if folder exists and create it if it does not
1371            if !path.exists() {
1372                if let Err(e) = std::fs::create_dir_all(&path) {
1373                    warn!(?e, ?path, "Couldn't create folder for screenshot");
1374                    let _result =
1375                        sender.send(String::from("Couldn't create folder for screenshot"));
1376                }
1377            }
1378            path.push(format!(
1379                "screenshot_{}.png",
1380                SystemTime::now()
1381                    .duration_since(SystemTime::UNIX_EPOCH)
1382                    .map(|d| d.as_millis())
1383                    .unwrap_or(0)
1384            ));
1385            // Try to save the image
1386            if let Err(e) = image.save(&path) {
1387                warn!(?e, ?path, "Couldn't save screenshot");
1388                let _result = sender.send(String::from("Couldn't save screenshot"));
1389            } else {
1390                let _result =
1391                    sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1392            }
1393        });
1394    }
1395
1396    fn is_pressed(
1397        map: &mut HashMap<GameInput, winit::event::ElementState>,
1398        input: GameInput,
1399    ) -> bool {
1400        *(map
1401            .entry(input)
1402            .or_insert(winit::event::ElementState::Released))
1403            == winit::event::ElementState::Pressed
1404    }
1405
1406    fn set_pressed(
1407        map: &mut HashMap<GameInput, winit::event::ElementState>,
1408        input: GameInput,
1409        state: winit::event::ElementState,
1410    ) {
1411        map.insert(input, state);
1412    }
1413
1414    // Function used to handle Mouse and Key events. It first checks if we're in
1415    // remapping mode for a specific GameInput. If we are, we modify the binding
1416    // of that GameInput with the KeyMouse passed. Else, we return an iterator of
1417    // the GameInputs for that KeyMouse.
1418    fn map_input<'a>(
1419        key_mouse: KeyMouse,
1420        controls: &'a mut ControlSettings,
1421        remapping: &mut Option<GameInput>,
1422    ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1423        match *remapping {
1424            // TODO: save settings
1425            Some(game_input) => {
1426                controls.modify_binding(game_input, key_mouse);
1427                *remapping = None;
1428                None
1429            },
1430            None => controls
1431                .get_associated_game_inputs(&key_mouse)
1432                .map(|game_inputs| game_inputs.iter()),
1433        }
1434    }
1435
1436    pub fn set_keybinding_mode(&mut self, game_input: GameInput) {
1437        self.remapping_keybindings = Some(game_input);
1438    }
1439
1440    pub fn window(&self) -> &winit::window::Window { &self.window }
1441
1442    pub fn modifiers(&self) -> winit::event::ModifiersState { self.modifiers }
1443
1444    pub fn scale_factor(&self) -> f64 { self.scale_factor }
1445}
1446
1447#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1448pub enum FullscreenMode {
1449    Exclusive,
1450    #[serde(other)]
1451    #[default]
1452    Borderless,
1453}
1454
1455#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1456#[serde(default)]
1457pub struct WindowSettings {
1458    pub size: [u32; 2],
1459    pub maximised: bool,
1460}
1461
1462impl Default for WindowSettings {
1463    fn default() -> Self {
1464        Self {
1465            size: [1280, 720],
1466            maximised: false,
1467        }
1468    }
1469}
1470
1471#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1472#[serde(default)]
1473pub struct FullScreenSettings {
1474    pub enabled: bool,
1475    pub mode: FullscreenMode,
1476    pub resolution: [u16; 2],
1477    pub bit_depth: Option<u16>,
1478    pub refresh_rate_millihertz: Option<u32>,
1479}
1480
1481impl Default for FullScreenSettings {
1482    fn default() -> Self {
1483        Self {
1484            enabled: true,
1485            mode: FullscreenMode::Borderless,
1486            resolution: [1920, 1080],
1487            bit_depth: None,
1488            refresh_rate_millihertz: None,
1489        }
1490    }
1491}