veloren_voxygen/
window.rs

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