veloren_voxygen/
window.rs

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