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