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