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