1use crate::{
2 error::Error,
3 game_input::GameInput,
4 render::Renderer,
5 settings::{ControlSettings, ControllerSettings, Settings, controller::*},
6 ui,
7};
8use common_base::span;
9use crossbeam_channel as channel;
10use gilrs::{Button as GilButton, EventType, Gilrs};
11use hashbrown::HashMap;
12use itertools::Itertools;
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15use strum::{AsRefStr, EnumIter};
16use tracing::{error, warn};
17use vek::*;
18use winit::monitor::VideoModeHandle;
19
20#[derive(
22 Clone,
23 Copy,
24 Debug,
25 PartialEq,
26 Eq,
27 PartialOrd,
28 Ord,
29 Hash,
30 Deserialize,
31 Serialize,
32 AsRefStr,
33 EnumIter,
34)]
35pub enum MenuInput {
36 Up,
37 Down,
38 Left,
39 Right,
40 ScrollUp,
41 ScrollDown,
42 ScrollLeft,
43 ScrollRight,
44 Home,
45 End,
46 Apply,
47 Back,
48 Exit,
49}
50
51impl MenuInput {
52 pub fn get_localization_key(&self) -> &str { self.as_ref() }
53}
54
55#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
56pub enum AnalogMenuInput {
57 MoveX(f32),
58 MoveY(f32),
59 ScrollX(f32),
60 ScrollY(f32),
61}
62
63#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
64pub enum AnalogGameInput {
65 MovementX(f32),
66 MovementY(f32),
67 CameraX(f32),
68 CameraY(f32),
69}
70
71#[derive(Clone, Debug)]
73pub enum Event {
74 Close,
76 Resize(Vec2<u32>),
78 ScaleFactorChanged(f64),
80 Moved(Vec2<u32>),
82 CursorPan(Vec2<f32>),
84 CursorMove(Vec2<f32>),
86 MouseButton(MouseButton, PressState),
88 Zoom(f32),
90 InputUpdate(GameInput, bool),
92 Ui(ui::Event),
94 IcedUi(ui::ice::Event),
96 ViewDistanceChanged(u32),
98 SettingsChanged,
100 Focused(bool),
102 MenuInput(MenuInput, bool),
105 AnalogMenuInput(AnalogMenuInput),
107 AnalogGameInput(AnalogGameInput),
109 ScreenshotMessage(String),
111}
112
113pub type MouseButton = winit::event::MouseButton;
114pub type PressState = winit::event::ElementState;
115pub type EventLoop = winit::event_loop::EventLoop<()>;
116
117#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
118pub enum KeyMouse {
119 Key(winit::keyboard::Key),
120 Mouse(winit::event::MouseButton),
121}
122
123impl KeyMouse {
124 pub fn into_upper(mut self) -> Self {
125 if let KeyMouse::Key(winit::keyboard::Key::Character(c)) = &mut self {
126 *c = c.to_ascii_uppercase().into();
127 }
128 self
129 }
130
131 pub fn display_string(&self) -> String {
133 use self::KeyMouse::*;
134 use winit::{event::MouseButton, keyboard::Key::*};
135
136 match self {
137 Key(key) => match key {
138 Named(key) => format!("{key:?}"),
139 Character(c) => c.to_string(),
140 Unidentified(key) => format!("Unknown ({key:?})"),
141 Dead(dead) => format!("Dead ({dead:?})"),
142 },
143 Mouse(MouseButton::Left) => String::from("Left Click"),
144 Mouse(MouseButton::Right) => String::from("Right Click"),
145 Mouse(MouseButton::Middle) => String::from("Middle Click"),
146 Mouse(MouseButton::Forward) => String::from("Mouse Forward"),
147 Mouse(MouseButton::Back) => String::from("Mouse Back"),
148 Mouse(MouseButton::Other(button)) => {
149 format!("Mouse {}", button + 3)
151 },
152 }
153 }
154
155 pub fn try_shortened(&self) -> Option<String> {
158 use self::KeyMouse::*;
159 use winit::event::MouseButton;
160 let key_string = match self {
161 Mouse(MouseButton::Left) => "M1",
162 Mouse(MouseButton::Right) => "M2",
163 Mouse(MouseButton::Middle) => "M3",
164 Mouse(MouseButton::Other(button)) => {
165 return Some(format!("M{}", button + 3));
167 },
168 _ => return None,
169 };
170
171 Some(key_string.to_owned())
172 }
173
174 pub fn display_shortest(&self) -> String {
179 self.try_shortened()
180 .unwrap_or_else(|| self.display_string())
181 }
182}
183
184#[derive(Clone, Copy, Debug)]
185pub enum RemappingMode {
186 RemapKeyboard(GameInput),
187 RemapGamepadButtons(GameInput),
188 RemapGamepadLayers(GameInput),
189 RemapGamepadMenu(MenuInput),
190 None,
191}
192
193#[derive(Clone, Copy, Debug)]
194pub enum LastInput {
195 KeyboardMouse,
196 Controller,
197}
198
199pub struct Window {
200 renderer: Renderer,
201 window: Arc<winit::window::Window>,
202 cursor_grabbed: bool,
203 pub pan_sensitivity: u32,
204 pub zoom_sensitivity: u32,
205 pub zoom_inversion: bool,
206 pub mouse_y_inversion: bool,
207 fullscreen: FullScreenSettings,
208 modifiers: winit::keyboard::ModifiersState,
209 resized: bool,
212 scale_factor: f64,
213 needs_refresh_resize: bool,
214 keypress_map: HashMap<GameInput, winit::event::ElementState>,
215 pub remapping_mode: RemappingMode,
216 events: Vec<Event>,
217 pub focused: bool,
218 gilrs: Option<Gilrs>,
219 pub controller_modifiers: Vec<Button>,
220 cursor_position: winit::dpi::PhysicalPosition<f64>,
221 mouse_emulation_vec: Vec2<f32>,
222 last_input: LastInput,
223 message_sender: channel::Sender<String>,
225 message_receiver: channel::Receiver<String>,
226 take_screenshot: bool,
228 toggle_fullscreen: bool,
229 pub gamelayer_mod1: bool,
232 pub gamelayer_mod2: bool,
233}
234
235impl Window {
236 pub fn new(
237 settings: &Settings,
238 runtime: &tokio::runtime::Runtime,
239 ) -> Result<(Window, EventLoop), Error> {
240 let event_loop = EventLoop::new().unwrap();
241
242 let window = settings.graphics.window;
243
244 #[allow(unused_mut)] let mut attributes = winit::window::Window::default_attributes()
246 .with_title("Veloren")
247 .with_inner_size(winit::dpi::LogicalSize::new(
248 window.size[0] as f64,
249 window.size[1] as f64,
250 ))
251 .with_maximized(window.maximised);
252
253 #[cfg(target_os = "linux")]
254 {
255 use winit::platform::wayland::WindowAttributesExtWayland;
256 attributes = attributes.with_name("net.veloren.veloren", "veloren");
257 }
258
259 #[cfg(target_family = "windows")]
262 let attributes = winit::platform::windows::WindowAttributesExtWindows::with_drag_and_drop(
263 attributes, false,
264 );
265
266 #[expect(deprecated)]
267 let window = Arc::new(event_loop.create_window(attributes).unwrap());
268
269 let renderer = Renderer::new(
270 Arc::clone(&window),
271 settings.graphics.render_mode.clone(),
272 runtime,
273 )?;
274
275 let keypress_map = HashMap::new();
276
277 let gilrs = match Gilrs::new() {
278 Ok(gilrs) => Some(gilrs),
279 Err(gilrs::Error::NotImplemented(_dummy)) => {
280 warn!("Controller input is unsupported on this platform.");
281 None
282 },
283 Err(gilrs::Error::InvalidAxisToBtn) => {
284 error!(
285 "Invalid AxisToBtn controller mapping. Falling back to no controller support."
286 );
287 None
288 },
289 Err(gilrs::Error::Other(e)) => {
290 error!(
291 ?e,
292 "Platform-specific error when creating a Gilrs instance. Falling back to no \
293 controller support."
294 );
295 None
296 },
297 Err(e) => {
298 error!(
299 ?e,
300 "Unspecified error when creating a Gilrs instance. Falling back to no \
301 controller support."
302 );
303 None
304 },
305 };
306
307 let (message_sender, message_receiver): (
308 channel::Sender<String>,
309 channel::Receiver<String>,
310 ) = channel::unbounded::<String>();
311
312 let scale_factor = window.scale_factor();
313
314 let mut this = Self {
315 renderer,
316 window,
317 cursor_grabbed: false,
318 pan_sensitivity: settings.gameplay.pan_sensitivity,
319 zoom_sensitivity: settings.gameplay.zoom_sensitivity,
320 zoom_inversion: settings.gameplay.zoom_inversion,
321 mouse_y_inversion: settings.gameplay.mouse_y_inversion,
322 fullscreen: FullScreenSettings::default(),
323 modifiers: Default::default(),
324 scale_factor,
325 resized: false,
326 needs_refresh_resize: false,
327 keypress_map,
328 remapping_mode: RemappingMode::None,
329 events: Vec::new(),
330 focused: true,
331 gilrs,
332 controller_modifiers: Vec::new(),
333 cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
334 mouse_emulation_vec: Vec2::zero(),
335 last_input: LastInput::KeyboardMouse,
336 message_sender,
338 message_receiver,
339 take_screenshot: false,
340 toggle_fullscreen: false,
341 gamelayer_mod1: true,
342 gamelayer_mod2: false,
343 };
344
345 this.set_fullscreen_mode(settings.graphics.fullscreen);
346
347 Ok((this, event_loop))
348 }
349
350 pub fn renderer(&self) -> &Renderer { &self.renderer }
351
352 pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
353
354 pub fn resolve_deduplicated_events(
355 &mut self,
356 settings: &mut Settings,
357 config_dir: &std::path::Path,
358 ) {
359 if self.take_screenshot {
361 self.take_screenshot = false;
362 self.take_screenshot(settings);
363 }
364 if self.toggle_fullscreen {
365 self.toggle_fullscreen = false;
366 self.toggle_fullscreen(settings, config_dir);
367 }
368 }
369
370 pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
371 span!(_guard, "fetch_events", "Window::fetch_events");
372
373 let controller = &mut settings.controller;
374 if self.needs_refresh_resize {
376 let scale_factor = self.window.scale_factor();
377 let physical = self.window.inner_size();
378
379 let logical_size =
380 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
381 self.events
382 .push(Event::Ui(ui::Event::new_resize(logical_size)));
383 self.events.push(Event::IcedUi(iced::Event::Window(
384 iced::window::Event::Resized {
385 width: logical_size.x as u32,
386 height: logical_size.y as u32,
387 },
388 )));
389 self.events.push(Event::ScaleFactorChanged(scale_factor));
390 self.needs_refresh_resize = false;
391 }
392
393 if self.resized {
395 self.resized = false;
396 let physical = self.window.inner_size();
400 let scale_factor = self.window.scale_factor();
401 let is_maximized = self.window.is_maximized();
402
403 self.renderer
404 .on_resize(Vec2::new(physical.width, physical.height));
405 self.events
406 .push(Event::Resize(Vec2::new(physical.width, physical.height)));
407
408 let logical_size =
409 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
410
411 self.events
413 .push(Event::Ui(ui::Event::new_resize(logical_size)));
414 self.events.push(Event::IcedUi(iced::Event::Window(
415 iced::window::Event::Resized {
416 width: logical_size.x as u32,
417 height: logical_size.y as u32,
418 },
419 )));
420
421 if logical_size.x >= 1.0 && logical_size.y >= 1.0 {
426 settings.graphics.window.size = [logical_size.x as u32, logical_size.y as u32];
427 }
428 settings.graphics.window.maximised = is_maximized;
429 }
430
431 for message in self.message_receiver.try_iter() {
433 self.events.push(Event::ScreenshotMessage(message))
434 }
435
436 if let Some(gilrs) = &mut self.gilrs {
437 while let Some(event) = gilrs.next_event() {
438 fn handle_buttons(
439 settings: &mut ControllerSettings,
440 remapping: &mut RemappingMode,
441 modifiers: &mut Vec<Button>,
442 events: &mut Vec<Event>,
443 button: &Button,
444 is_pressed: bool,
445 last_input: &mut LastInput,
446 mod1: bool,
447 mod2: bool,
448 ) {
449 *last_input = LastInput::Controller;
451
452 if settings.modifier_buttons.contains(button) {
453 if is_pressed {
454 modifiers.push(*button);
455 } else if let Some(index) =
461 modifiers.iter().position(|modifier| modifier == button)
462 {
463 modifiers.remove(index);
464 }
465 }
466
467 if let Some(game_inputs) = Window::map_controller_input(
470 settings, remapping, modifiers, button, mod1, mod2,
471 ) {
472 for game_input in game_inputs {
473 events.push(Event::InputUpdate(*game_input, is_pressed));
474 }
475 }
476 }
477
478 match event.event {
479 EventType::ButtonPressed(button, code)
480 | EventType::ButtonRepeated(button, code) => {
481 handle_buttons(
482 controller,
483 &mut self.remapping_mode,
484 &mut self.controller_modifiers,
485 &mut self.events,
486 &Button::from((button, code)),
487 true,
488 &mut self.last_input,
489 self.gamelayer_mod1,
490 self.gamelayer_mod2,
491 );
492 },
493 EventType::ButtonReleased(button, code) => {
494 handle_buttons(
495 controller,
496 &mut self.remapping_mode,
497 &mut self.controller_modifiers,
498 &mut self.events,
499 &Button::from((button, code)),
500 false,
501 &mut self.last_input,
502 self.gamelayer_mod1,
503 self.gamelayer_mod2,
504 );
505 },
506 EventType::ButtonChanged(button, _value, code) => {
507 if let Some(actions) = controller
508 .inverse_game_analog_button_map
509 .get(&AnalogButton::from((button, code)))
510 {
511 #[expect(clippy::never_loop)]
512 for action in actions {
513 match *action {}
514 }
515 }
516 if let Some(actions) = controller
517 .inverse_menu_analog_button_map
518 .get(&AnalogButton::from((button, code)))
519 {
520 #[expect(clippy::never_loop)]
521 for action in actions {
522 match *action {}
523 }
524 }
525 },
526
527 EventType::AxisChanged(axis, value, code) => {
528 let value = if controller.inverted_axes.contains(&Axis::from((axis, code)))
529 {
530 -value
531 } else {
532 value
533 };
534
535 let value =
536 controller.apply_axis_deadzone(&Axis::from((axis, code)), value);
537
538 if value.abs() > 0.0001 {
540 self.last_input = LastInput::Controller;
541 }
542
543 if self.cursor_grabbed {
544 if let Some(actions) = controller
545 .inverse_game_axis_map
546 .get(&Axis::from((axis, code)))
547 {
548 for action in actions {
549 match *action {
550 AxisGameAction::MovementX => {
551 self.events.push(Event::AnalogGameInput(
552 AnalogGameInput::MovementX(value),
553 ));
554 },
555 AxisGameAction::MovementY => {
556 self.events.push(Event::AnalogGameInput(
557 AnalogGameInput::MovementY(value),
558 ));
559 },
560 AxisGameAction::CameraX => {
561 self.events.push(Event::AnalogGameInput(
562 AnalogGameInput::CameraX(
563 value * controller.pan_sensitivity as f32
564 / 100.0,
565 ),
566 ));
567 },
568 AxisGameAction::CameraY => {
569 let pan_invert_y = match controller.pan_invert_y {
570 true => -1.0,
571 false => 1.0,
572 };
573
574 self.events.push(Event::AnalogGameInput(
575 AnalogGameInput::CameraY(
576 -value
577 * controller.pan_sensitivity as f32
578 * pan_invert_y
579 / 100.0,
580 ),
581 ));
582 },
583 }
584 }
585 }
586 } else if let Some(actions) = controller
587 .inverse_menu_axis_map
588 .get(&Axis::from((axis, code)))
589 {
590 for action in actions {
592 match *action {
593 AxisMenuAction::MoveX => {
594 self.events.push(Event::AnalogMenuInput(
595 AnalogMenuInput::MoveX(value),
596 ));
597 },
598 AxisMenuAction::MoveY => {
599 self.events.push(Event::AnalogMenuInput(
600 AnalogMenuInput::MoveY(value),
601 ));
602 },
603 AxisMenuAction::ScrollX => {
604 self.events.push(Event::AnalogMenuInput(
605 AnalogMenuInput::ScrollX(value),
606 ));
607 },
608 AxisMenuAction::ScrollY => {
609 self.events.push(Event::AnalogMenuInput(
610 AnalogMenuInput::ScrollY(value),
611 ));
612 },
613 }
614 }
615 }
616 },
617 _ => {},
618 }
619 }
620 }
621
622 let mut events = std::mem::take(&mut self.events);
623 if !self.cursor_grabbed {
626 events = events
627 .into_iter()
628 .filter_map(|event| match event {
629 Event::AnalogMenuInput(input) => match input {
630 AnalogMenuInput::MoveX(d) => {
631 self.mouse_emulation_vec.x = d;
632 None
633 },
634 AnalogMenuInput::MoveY(d) => {
635 self.mouse_emulation_vec.y = -d;
637 None
638 },
639 input => Some(Event::AnalogMenuInput(input)),
640 },
641 Event::MenuInput(menu_input, state) => {
642 let mouse_button = match menu_input {
644 MenuInput::Apply => conrod_core::input::state::mouse::Button::Left,
645 MenuInput::Back => conrod_core::input::state::mouse::Button::Right,
646 _ => return Some(event),
647 };
648 Some(match state {
649 true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
650 conrod_core::input::Button::Mouse(mouse_button),
651 ))),
652 false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
653 conrod_core::input::Button::Mouse(mouse_button),
654 ))),
655 })
656 },
657 _ => Some(event),
658 })
659 .collect();
660
661 let sensitivity = controller.mouse_emulation_sensitivity;
662 self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
665 }
666
667 events
668 }
669
670 pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
671 use winit::event::DeviceEvent;
672
673 let mouse_y_inversion = match self.mouse_y_inversion {
674 true => -1.0,
675 false => 1.0,
676 };
677
678 match event {
679 DeviceEvent::MouseMotion {
680 delta: (dx, dy), ..
681 } if self.focused => {
682 self.last_input = LastInput::KeyboardMouse;
684
685 let delta = Vec2::new(
686 dx as f32 * (self.pan_sensitivity as f32 / 100.0),
687 dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
688 );
689
690 if self.cursor_grabbed {
691 self.events.push(Event::CursorPan(delta));
692 } else {
693 self.events.push(Event::CursorMove(delta));
694 }
695 },
696 _ => {},
697 }
698 }
699
700 pub fn handle_window_event(
701 &mut self,
702 event: winit::event::WindowEvent,
703 settings: &mut Settings,
704 ) {
705 use winit::event::WindowEvent;
706
707 let controls = &mut settings.controls;
708
709 match event {
710 WindowEvent::CloseRequested => self.events.push(Event::Close),
711 WindowEvent::Resized(_) => {
712 self.resized = true;
713 },
714 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
715 self.scale_factor = scale_factor;
717 self.events.push(Event::ScaleFactorChanged(scale_factor));
718 },
719 WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
720 self.events
721 .push(Event::Moved(Vec2::new(x as u32, y as u32)));
722 },
723 WindowEvent::MouseInput { button, state, .. } => {
724 if let (true, Some(game_inputs)) =
725 (
727 self.cursor_grabbed,
728 Window::map_input(
729 KeyMouse::Mouse(button),
730 controls,
731 &mut self.remapping_mode,
732 &mut self.last_input,
733 ),
734 )
735 {
736 for game_input in game_inputs {
737 self.events.push(Event::InputUpdate(
738 *game_input,
739 state == winit::event::ElementState::Pressed,
740 ));
741 }
742 }
743 self.events.push(Event::MouseButton(button, state));
744 },
745 WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers.state(),
746 WindowEvent::KeyboardInput {
747 event,
748 is_synthetic,
749 ..
750 } => {
751 if event.repeat {
753 return;
754 }
755 if matches!(event.state, winit::event::ElementState::Pressed) && is_synthetic {
758 return;
759 }
760 if matches!(event, winit::event::KeyEvent {
763 state: winit::event::ElementState::Pressed,
764 logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::F4),
765 ..
766 }) && self.modifiers.alt_key()
767 {
768 return;
769 }
770
771 if let Some(game_inputs) = Window::map_input(
772 KeyMouse::Key(event.logical_key),
773 controls,
774 &mut self.remapping_mode,
775 &mut self.last_input,
776 ) {
777 for game_input in game_inputs {
778 match game_input {
779 GameInput::Fullscreen => {
780 if event.state == winit::event::ElementState::Pressed
781 && !Self::is_pressed(
782 &mut self.keypress_map,
783 GameInput::Fullscreen,
784 )
785 {
786 self.toggle_fullscreen = !self.toggle_fullscreen;
787 }
788 Self::set_pressed(
789 &mut self.keypress_map,
790 GameInput::Fullscreen,
791 event.state,
792 );
793 },
794 GameInput::Screenshot => {
795 self.take_screenshot = event.state
796 == winit::event::ElementState::Pressed
797 && !Self::is_pressed(
798 &mut self.keypress_map,
799 GameInput::Screenshot,
800 );
801 Self::set_pressed(
802 &mut self.keypress_map,
803 GameInput::Screenshot,
804 event.state,
805 );
806 },
807 _ => self.events.push(Event::InputUpdate(
808 *game_input,
809 event.state == winit::event::ElementState::Pressed,
810 )),
811 }
812 }
813 }
814 },
815 WindowEvent::Focused(state) => {
816 self.focused = state;
817 self.events.push(Event::Focused(state));
818 },
819 WindowEvent::CursorMoved { position, .. } => {
820 if self.cursor_grabbed {
821 self.reset_cursor_position();
822 } else {
823 self.cursor_position = position;
824 }
825 },
826 WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
827 const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
828 self.events.push(Event::Zoom({
829 let y = match delta {
830 winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
831 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
837 };
838 y * (self.zoom_sensitivity as f32 / 100.0)
839 * if self.zoom_inversion { -1.0 } else { 1.0 }
840 * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
841 }))
842 },
843 _ => {},
844 }
845 }
846
847 pub fn offset_cursor(&self, d: Vec2<f32>) {
849 if d != Vec2::zero()
850 && let Err(err) = self
851 .window
852 .set_cursor_position(winit::dpi::LogicalPosition::new(
853 d.x as f64 + self.cursor_position.x,
854 d.y as f64 + self.cursor_position.y,
855 ))
856 {
857 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
859 SPAM_GUARD.call_once(|| {
860 error!("Error setting cursor position: {:?}", err);
861 })
862 }
863 }
864
865 pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
866
867 pub fn grab_cursor(&mut self, grab: bool) {
868 use winit::window::CursorGrabMode;
869
870 self.cursor_grabbed = grab;
871 self.window.set_cursor_visible(!grab);
872 let res = if grab {
873 self.window
874 .set_cursor_grab(CursorGrabMode::Locked)
875 .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
876 } else {
877 self.window.set_cursor_grab(CursorGrabMode::None)
878 };
879
880 if let Err(e) = res {
881 error!(?e, ?grab, "Failed to toggle cursor grab");
882 }
883 }
884
885 fn reset_cursor_position(&self) {
889 if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
890 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
892 SPAM_GUARD.call_once(|| {
893 error!("Error resetting cursor position: {:?}", err);
894 })
895 }
896 }
897
898 pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
899 let fullscreen = FullScreenSettings {
900 enabled: !self.is_fullscreen(),
901 ..settings.graphics.fullscreen
902 };
903
904 self.set_fullscreen_mode(fullscreen);
905 settings.graphics.fullscreen = fullscreen;
906 settings.save_to_file_warn(config_dir);
907 }
908
909 pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
910
911 fn select_video_mode_rec(
915 &self,
916 resolution: [u16; 2],
917 bit_depth: Option<u16>,
918 refresh_rate_millihertz: Option<u32>,
919 correct_res: Option<Vec<VideoModeHandle>>,
920 correct_depth: Option<Option<VideoModeHandle>>,
921 correct_rate: Option<Option<VideoModeHandle>>,
922 ) -> Option<VideoModeHandle> {
923 let correct_res = match correct_res {
927 Some(correct_res) => correct_res,
928 None => self
929 .window
930 .current_monitor()?
931 .video_modes()
932 .filter(|mode| mode.size().width == resolution[0] as u32)
933 .filter(|mode| mode.size().height == resolution[1] as u32)
934 .collect(),
935 };
936
937 match bit_depth {
938 Some(depth) => {
940 let correct_depth = correct_depth.unwrap_or_else(|| {
942 correct_res
943 .iter()
944 .find(|mode| mode.bit_depth() == depth)
945 .cloned()
946 });
947
948 match refresh_rate_millihertz {
949 Some(rate) => {
951 let correct_rate = correct_rate.unwrap_or_else(|| {
953 correct_res
954 .iter()
955 .find(|mode| mode.refresh_rate_millihertz() == rate)
956 .cloned()
957 });
958
959 correct_res
964 .iter()
965 .filter(|mode| mode.bit_depth() == depth)
966 .find(|mode| mode.refresh_rate_millihertz() == rate)
967 .cloned()
968 .or_else(|| {
969 if correct_depth.is_none() && correct_rate.is_none() {
970 warn!(
971 "Bit depth and refresh rate specified in settings are \
972 incompatible with the monitor. Choosing highest bit \
973 depth and refresh rate possible instead."
974 );
975 }
976
977 self.select_video_mode_rec(
978 resolution,
979 correct_depth.is_some().then_some(depth),
980 correct_rate.is_some().then_some(rate),
981 Some(correct_res),
982 Some(correct_depth),
983 Some(correct_rate),
984 )
985 })
986 },
987 None => match correct_depth {
991 Some(mode) => Some(mode),
992 None => {
993 warn!(
994 "Bit depth specified in settings is incompatible with the \
995 monitor. Choosing highest bit depth possible instead."
996 );
997
998 self.select_video_mode_rec(
999 resolution,
1000 None,
1001 None,
1002 Some(correct_res),
1003 Some(correct_depth),
1004 None,
1005 )
1006 },
1007 },
1008 }
1009 },
1010 None => match refresh_rate_millihertz {
1012 Some(rate) => {
1014 let correct_rate = correct_rate.unwrap_or_else(|| {
1016 correct_res
1017 .iter()
1018 .find(|mode| mode.refresh_rate_millihertz() == rate)
1019 .cloned()
1020 });
1021
1022 match correct_rate {
1025 Some(mode) => Some(mode),
1026 None => {
1027 warn!(
1028 "Refresh rate specified in settings is incompatible with the \
1029 monitor. Choosing highest refresh rate possible instead."
1030 );
1031
1032 self.select_video_mode_rec(
1033 resolution,
1034 None,
1035 None,
1036 Some(correct_res),
1037 None,
1038 Some(correct_rate),
1039 )
1040 },
1041 }
1042 },
1043 None => correct_res
1047 .into_iter()
1048 .sorted_by_key(|mode| mode.bit_depth())
1050 .max_by_key(|mode| mode.refresh_rate_millihertz()),
1051 },
1052 }
1053 }
1054
1055 fn select_video_mode(
1056 &self,
1057 resolution: [u16; 2],
1058 bit_depth: Option<u16>,
1059 refresh_rate_millihertz: Option<u32>,
1060 ) -> Option<VideoModeHandle> {
1061 match self.select_video_mode_rec(
1071 resolution,
1072 bit_depth,
1073 refresh_rate_millihertz,
1074 None,
1075 None,
1076 None,
1077 ) {
1078 Some(mode) => Some(mode),
1079 None => {
1082 warn!(
1083 "Resolution specified in settings is incompatible with the monitor. Choosing \
1084 highest resolution possible instead."
1085 );
1086 if let Some(monitor) = self.window.current_monitor() {
1087 let mode = monitor
1088 .video_modes()
1089 .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1091 .sorted_by_key(|mode| mode.bit_depth())
1092 .max_by_key(|mode| mode.size().width);
1093
1094 if mode.is_none() {
1095 warn!("Failed to select video mode, no video modes available!!")
1096 }
1097
1098 mode
1099 } else {
1100 warn!("Failed to select video mode, can't get the current monitor!");
1101 None
1102 }
1103 },
1104 }
1105 }
1106
1107 pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1108 let window = &self.window;
1109 self.fullscreen = fullscreen;
1110 window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1111 FullscreenMode::Exclusive => {
1112 if let Some(video_mode) = self.select_video_mode(
1113 fullscreen.resolution,
1114 fullscreen.bit_depth,
1115 fullscreen.refresh_rate_millihertz,
1116 ) {
1117 winit::window::Fullscreen::Exclusive(video_mode)
1118 } else {
1119 warn!(
1120 "Failed to select a video mode for exclusive fullscreen. Falling back to \
1121 borderless fullscreen."
1122 );
1123 winit::window::Fullscreen::Borderless(None)
1124 }
1125 },
1126 FullscreenMode::Borderless => {
1127 winit::window::Fullscreen::Borderless(None)
1129 },
1130 }));
1131 }
1132
1133 pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1134
1135 pub fn set_size(&mut self, new_size: Vec2<u32>) {
1136 self.window
1137 .set_min_inner_size(Some(winit::dpi::LogicalSize::new(
1138 new_size.x as f64,
1139 new_size.y as f64,
1140 )));
1141 }
1142
1143 pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1144
1145 pub fn take_screenshot(&mut self, settings: &Settings) {
1146 let sender = self.message_sender.clone();
1147 let mut path = settings.screenshots_path.clone();
1148 self.renderer.create_screenshot(move |image| {
1149 use std::time::SystemTime;
1150
1151 let image = match image {
1153 Ok(i) => i,
1154 Err(e) => {
1155 warn!(?e, "Couldn't generate screenshot");
1156 let _result = sender.send(format!("Error when generating screenshot: {}", e));
1157 return;
1158 },
1159 };
1160
1161 if !path.exists()
1163 && let Err(e) = std::fs::create_dir_all(&path)
1164 {
1165 warn!(?e, ?path, "Couldn't create folder for screenshot");
1166 let _result = sender.send(String::from("Couldn't create folder for screenshot"));
1167 }
1168 path.push(format!(
1169 "screenshot_{}.png",
1170 SystemTime::now()
1171 .duration_since(SystemTime::UNIX_EPOCH)
1172 .map(|d| d.as_millis())
1173 .unwrap_or(0)
1174 ));
1175 if let Err(e) = image.save(&path) {
1177 warn!(?e, ?path, "Couldn't save screenshot");
1178 let _result = sender.send(String::from("Couldn't save screenshot"));
1179 } else {
1180 let _result =
1181 sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1182 }
1183 });
1184 }
1185
1186 fn is_pressed(
1187 map: &mut HashMap<GameInput, winit::event::ElementState>,
1188 input: GameInput,
1189 ) -> bool {
1190 *(map
1191 .entry(input)
1192 .or_insert(winit::event::ElementState::Released))
1193 == winit::event::ElementState::Pressed
1194 }
1195
1196 fn set_pressed(
1197 map: &mut HashMap<GameInput, winit::event::ElementState>,
1198 input: GameInput,
1199 state: winit::event::ElementState,
1200 ) {
1201 map.insert(input, state);
1202 }
1203
1204 fn map_input<'a>(
1209 key_mouse: KeyMouse,
1210 controls: &'a mut ControlSettings,
1211 remapping: &mut RemappingMode,
1212 last_input: &mut LastInput,
1213 ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1214 let key_mouse = key_mouse.into_upper();
1215
1216 *last_input = LastInput::KeyboardMouse;
1218
1219 match *remapping {
1220 RemappingMode::RemapKeyboard(game_input) => {
1221 controls.modify_binding(game_input, key_mouse);
1222 *remapping = RemappingMode::None;
1223 None
1224 },
1225 RemappingMode::None => controls
1226 .get_associated_game_inputs(&key_mouse)
1227 .map(|game_inputs| game_inputs.iter()),
1228 _ => None,
1229 }
1230 }
1231
1232 #[expect(clippy::get_first)]
1234 fn map_controller_input<'a>(
1235 controller: &'a mut ControllerSettings,
1236 remapping: &mut RemappingMode,
1237 modifiers: &[Button],
1238 button: &Button,
1239 mod1_input: bool,
1240 mod2_input: bool,
1241 ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1242 match *remapping {
1243 RemappingMode::RemapGamepadLayers(game_input) => {
1244 let new_layer_entry = LayerEntry {
1246 button: *button,
1247 mod1: if mod1_input {
1248 Button::Simple(GilButton::RightTrigger)
1249 } else {
1250 Button::Simple(GilButton::Unknown)
1251 },
1252 mod2: if mod2_input {
1253 Button::Simple(GilButton::LeftTrigger)
1254 } else {
1255 Button::Simple(GilButton::Unknown)
1256 },
1257 };
1258 controller.modify_layer_binding(game_input, new_layer_entry);
1259 *remapping = RemappingMode::None;
1260 None
1261 },
1262 RemappingMode::RemapGamepadButtons(game_input) => {
1263 controller.modify_button_binding(game_input, *button);
1264 *remapping = RemappingMode::None;
1265 None
1266 },
1267 RemappingMode::RemapGamepadMenu(menu_input) => {
1268 controller.modify_menu_binding(menu_input, *button);
1269 *remapping = RemappingMode::None;
1270 None
1271 },
1272 RemappingMode::None => {
1273 let l_entry1 = LayerEntry {
1276 button: *button,
1277 mod1: modifiers.get(0).copied().unwrap_or_default(),
1278 mod2: modifiers.get(1).copied().unwrap_or_default(),
1279 };
1280 let l_entry2 = LayerEntry {
1281 button: *button,
1282 mod1: modifiers.get(1).copied().unwrap_or_default(),
1283 mod2: modifiers.get(0).copied().unwrap_or_default(),
1284 };
1285
1286 if let Some(game_inputs) = controller.get_associated_game_layer_inputs(&l_entry1) {
1288 Some(game_inputs.iter())
1289 } else if let Some(game_inputs) =
1290 controller.get_associated_game_layer_inputs(&l_entry2)
1291 {
1292 Some(game_inputs.iter())
1293 } else {
1294 controller
1296 .get_associated_game_button_inputs(button)
1297 .map(|game_inputs| game_inputs.iter())
1298 }
1299
1300 },
1302 _ => None,
1303 }
1304 }
1305
1306 pub fn set_remapping_mode(&mut self, r_mode: RemappingMode) { self.remapping_mode = r_mode; }
1307
1308 pub fn reset_mapping_mode(&mut self) { self.remapping_mode = RemappingMode::None; }
1309
1310 pub fn window(&self) -> &winit::window::Window { &self.window }
1311
1312 pub fn modifiers(&self) -> winit::keyboard::ModifiersState { self.modifiers }
1313
1314 pub fn scale_factor(&self) -> f64 { self.scale_factor }
1315
1316 pub fn last_input(&self) -> LastInput { self.last_input }
1317}
1318
1319#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1320pub enum FullscreenMode {
1321 Exclusive,
1322 #[serde(other)]
1323 #[default]
1324 Borderless,
1325}
1326
1327#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1328#[serde(default)]
1329pub struct WindowSettings {
1330 pub size: [u32; 2],
1331 pub maximised: bool,
1332}
1333
1334impl Default for WindowSettings {
1335 fn default() -> Self {
1336 Self {
1337 size: [1280, 720],
1338 maximised: false,
1339 }
1340 }
1341}
1342
1343#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1344#[serde(default)]
1345pub struct FullScreenSettings {
1346 pub enabled: bool,
1347 pub mode: FullscreenMode,
1348 pub resolution: [u16; 2],
1349 pub bit_depth: Option<u16>,
1350 pub refresh_rate_millihertz: Option<u32>,
1351}
1352
1353impl Default for FullScreenSettings {
1354 fn default() -> Self {
1355 Self {
1356 enabled: true,
1357 mode: FullscreenMode::Borderless,
1358 resolution: [1920, 1080],
1359 bit_depth: None,
1360 refresh_rate_millihertz: None,
1361 }
1362 }
1363}