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 matches!(event.state, winit::event::ElementState::Pressed) && is_synthetic {
754 return;
755 }
756 if matches!(event, winit::event::KeyEvent {
759 state: winit::event::ElementState::Pressed,
760 logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::F4),
761 ..
762 }) && self.modifiers.alt_key()
763 {
764 return;
765 }
766
767 if let Some(game_inputs) = Window::map_input(
768 KeyMouse::Key(event.logical_key),
769 controls,
770 &mut self.remapping_mode,
771 &mut self.last_input,
772 ) {
773 for game_input in game_inputs {
774 match game_input {
775 GameInput::Fullscreen => {
776 if event.state == winit::event::ElementState::Pressed
777 && !Self::is_pressed(
778 &mut self.keypress_map,
779 GameInput::Fullscreen,
780 )
781 {
782 self.toggle_fullscreen = !self.toggle_fullscreen;
783 }
784 Self::set_pressed(
785 &mut self.keypress_map,
786 GameInput::Fullscreen,
787 event.state,
788 );
789 },
790 GameInput::Screenshot => {
791 self.take_screenshot = event.state
792 == winit::event::ElementState::Pressed
793 && !Self::is_pressed(
794 &mut self.keypress_map,
795 GameInput::Screenshot,
796 );
797 Self::set_pressed(
798 &mut self.keypress_map,
799 GameInput::Screenshot,
800 event.state,
801 );
802 },
803 _ => self.events.push(Event::InputUpdate(
804 *game_input,
805 event.state == winit::event::ElementState::Pressed,
806 )),
807 }
808 }
809 }
810 },
811 WindowEvent::Focused(state) => {
812 self.focused = state;
813 self.events.push(Event::Focused(state));
814 },
815 WindowEvent::CursorMoved { position, .. } => {
816 if self.cursor_grabbed {
817 self.reset_cursor_position();
818 } else {
819 self.cursor_position = position;
820 }
821 },
822 WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
823 const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
824 self.events.push(Event::Zoom({
825 let y = match delta {
826 winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
827 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
833 };
834 y * (self.zoom_sensitivity as f32 / 100.0)
835 * if self.zoom_inversion { -1.0 } else { 1.0 }
836 * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
837 }))
838 },
839 _ => {},
840 }
841 }
842
843 pub fn offset_cursor(&self, d: Vec2<f32>) {
845 if d != Vec2::zero()
846 && let Err(err) = self
847 .window
848 .set_cursor_position(winit::dpi::LogicalPosition::new(
849 d.x as f64 + self.cursor_position.x,
850 d.y as f64 + self.cursor_position.y,
851 ))
852 {
853 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
855 SPAM_GUARD.call_once(|| {
856 error!("Error setting cursor position: {:?}", err);
857 })
858 }
859 }
860
861 pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
862
863 pub fn grab_cursor(&mut self, grab: bool) {
864 use winit::window::CursorGrabMode;
865
866 self.cursor_grabbed = grab;
867 self.window.set_cursor_visible(!grab);
868 let res = if grab {
869 self.window
870 .set_cursor_grab(CursorGrabMode::Locked)
871 .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
872 } else {
873 self.window.set_cursor_grab(CursorGrabMode::None)
874 };
875
876 if let Err(e) = res {
877 error!(?e, ?grab, "Failed to toggle cursor grab");
878 }
879 }
880
881 fn reset_cursor_position(&self) {
885 if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
886 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
888 SPAM_GUARD.call_once(|| {
889 error!("Error resetting cursor position: {:?}", err);
890 })
891 }
892 }
893
894 pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
895 let fullscreen = FullScreenSettings {
896 enabled: !self.is_fullscreen(),
897 ..settings.graphics.fullscreen
898 };
899
900 self.set_fullscreen_mode(fullscreen);
901 settings.graphics.fullscreen = fullscreen;
902 settings.save_to_file_warn(config_dir);
903 }
904
905 pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
906
907 fn select_video_mode_rec(
911 &self,
912 resolution: [u16; 2],
913 bit_depth: Option<u16>,
914 refresh_rate_millihertz: Option<u32>,
915 correct_res: Option<Vec<VideoModeHandle>>,
916 correct_depth: Option<Option<VideoModeHandle>>,
917 correct_rate: Option<Option<VideoModeHandle>>,
918 ) -> Option<VideoModeHandle> {
919 let correct_res = match correct_res {
923 Some(correct_res) => correct_res,
924 None => self
925 .window
926 .current_monitor()?
927 .video_modes()
928 .filter(|mode| mode.size().width == resolution[0] as u32)
929 .filter(|mode| mode.size().height == resolution[1] as u32)
930 .collect(),
931 };
932
933 match bit_depth {
934 Some(depth) => {
936 let correct_depth = correct_depth.unwrap_or_else(|| {
938 correct_res
939 .iter()
940 .find(|mode| mode.bit_depth() == depth)
941 .cloned()
942 });
943
944 match refresh_rate_millihertz {
945 Some(rate) => {
947 let correct_rate = correct_rate.unwrap_or_else(|| {
949 correct_res
950 .iter()
951 .find(|mode| mode.refresh_rate_millihertz() == rate)
952 .cloned()
953 });
954
955 correct_res
960 .iter()
961 .filter(|mode| mode.bit_depth() == depth)
962 .find(|mode| mode.refresh_rate_millihertz() == rate)
963 .cloned()
964 .or_else(|| {
965 if correct_depth.is_none() && correct_rate.is_none() {
966 warn!(
967 "Bit depth and refresh rate specified in settings are \
968 incompatible with the monitor. Choosing highest bit \
969 depth and refresh rate possible instead."
970 );
971 }
972
973 self.select_video_mode_rec(
974 resolution,
975 correct_depth.is_some().then_some(depth),
976 correct_rate.is_some().then_some(rate),
977 Some(correct_res),
978 Some(correct_depth),
979 Some(correct_rate),
980 )
981 })
982 },
983 None => match correct_depth {
987 Some(mode) => Some(mode),
988 None => {
989 warn!(
990 "Bit depth specified in settings is incompatible with the \
991 monitor. Choosing highest bit depth possible instead."
992 );
993
994 self.select_video_mode_rec(
995 resolution,
996 None,
997 None,
998 Some(correct_res),
999 Some(correct_depth),
1000 None,
1001 )
1002 },
1003 },
1004 }
1005 },
1006 None => match refresh_rate_millihertz {
1008 Some(rate) => {
1010 let correct_rate = correct_rate.unwrap_or_else(|| {
1012 correct_res
1013 .iter()
1014 .find(|mode| mode.refresh_rate_millihertz() == rate)
1015 .cloned()
1016 });
1017
1018 match correct_rate {
1021 Some(mode) => Some(mode),
1022 None => {
1023 warn!(
1024 "Refresh rate specified in settings is incompatible with the \
1025 monitor. Choosing highest refresh rate possible instead."
1026 );
1027
1028 self.select_video_mode_rec(
1029 resolution,
1030 None,
1031 None,
1032 Some(correct_res),
1033 None,
1034 Some(correct_rate),
1035 )
1036 },
1037 }
1038 },
1039 None => correct_res
1043 .into_iter()
1044 .sorted_by_key(|mode| mode.bit_depth())
1046 .max_by_key(|mode| mode.refresh_rate_millihertz()),
1047 },
1048 }
1049 }
1050
1051 fn select_video_mode(
1052 &self,
1053 resolution: [u16; 2],
1054 bit_depth: Option<u16>,
1055 refresh_rate_millihertz: Option<u32>,
1056 ) -> Option<VideoModeHandle> {
1057 match self.select_video_mode_rec(
1067 resolution,
1068 bit_depth,
1069 refresh_rate_millihertz,
1070 None,
1071 None,
1072 None,
1073 ) {
1074 Some(mode) => Some(mode),
1075 None => {
1078 warn!(
1079 "Resolution specified in settings is incompatible with the monitor. Choosing \
1080 highest resolution possible instead."
1081 );
1082 if let Some(monitor) = self.window.current_monitor() {
1083 let mode = monitor
1084 .video_modes()
1085 .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1087 .sorted_by_key(|mode| mode.bit_depth())
1088 .max_by_key(|mode| mode.size().width);
1089
1090 if mode.is_none() {
1091 warn!("Failed to select video mode, no video modes available!!")
1092 }
1093
1094 mode
1095 } else {
1096 warn!("Failed to select video mode, can't get the current monitor!");
1097 None
1098 }
1099 },
1100 }
1101 }
1102
1103 pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1104 let window = &self.window;
1105 self.fullscreen = fullscreen;
1106 window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1107 FullscreenMode::Exclusive => {
1108 if let Some(video_mode) = self.select_video_mode(
1109 fullscreen.resolution,
1110 fullscreen.bit_depth,
1111 fullscreen.refresh_rate_millihertz,
1112 ) {
1113 winit::window::Fullscreen::Exclusive(video_mode)
1114 } else {
1115 warn!(
1116 "Failed to select a video mode for exclusive fullscreen. Falling back to \
1117 borderless fullscreen."
1118 );
1119 winit::window::Fullscreen::Borderless(None)
1120 }
1121 },
1122 FullscreenMode::Borderless => {
1123 winit::window::Fullscreen::Borderless(None)
1125 },
1126 }));
1127 }
1128
1129 pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1130
1131 pub fn set_size(&mut self, new_size: Vec2<u32>) {
1132 self.window
1133 .set_min_inner_size(Some(winit::dpi::LogicalSize::new(
1134 new_size.x as f64,
1135 new_size.y as f64,
1136 )));
1137 }
1138
1139 pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1140
1141 pub fn take_screenshot(&mut self, settings: &Settings) {
1142 let sender = self.message_sender.clone();
1143 let mut path = settings.screenshots_path.clone();
1144 self.renderer.create_screenshot(move |image| {
1145 use std::time::SystemTime;
1146
1147 let image = match image {
1149 Ok(i) => i,
1150 Err(e) => {
1151 warn!(?e, "Couldn't generate screenshot");
1152 let _result = sender.send(format!("Error when generating screenshot: {}", e));
1153 return;
1154 },
1155 };
1156
1157 if !path.exists()
1159 && let Err(e) = std::fs::create_dir_all(&path)
1160 {
1161 warn!(?e, ?path, "Couldn't create folder for screenshot");
1162 let _result = sender.send(String::from("Couldn't create folder for screenshot"));
1163 }
1164 path.push(format!(
1165 "screenshot_{}.png",
1166 SystemTime::now()
1167 .duration_since(SystemTime::UNIX_EPOCH)
1168 .map(|d| d.as_millis())
1169 .unwrap_or(0)
1170 ));
1171 if let Err(e) = image.save(&path) {
1173 warn!(?e, ?path, "Couldn't save screenshot");
1174 let _result = sender.send(String::from("Couldn't save screenshot"));
1175 } else {
1176 let _result =
1177 sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1178 }
1179 });
1180 }
1181
1182 fn is_pressed(
1183 map: &mut HashMap<GameInput, winit::event::ElementState>,
1184 input: GameInput,
1185 ) -> bool {
1186 *(map
1187 .entry(input)
1188 .or_insert(winit::event::ElementState::Released))
1189 == winit::event::ElementState::Pressed
1190 }
1191
1192 fn set_pressed(
1193 map: &mut HashMap<GameInput, winit::event::ElementState>,
1194 input: GameInput,
1195 state: winit::event::ElementState,
1196 ) {
1197 map.insert(input, state);
1198 }
1199
1200 fn map_input<'a>(
1205 key_mouse: KeyMouse,
1206 controls: &'a mut ControlSettings,
1207 remapping: &mut RemappingMode,
1208 last_input: &mut LastInput,
1209 ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1210 let key_mouse = key_mouse.into_upper();
1211
1212 *last_input = LastInput::KeyboardMouse;
1214
1215 match *remapping {
1216 RemappingMode::RemapKeyboard(game_input) => {
1217 controls.modify_binding(game_input, key_mouse);
1218 *remapping = RemappingMode::None;
1219 None
1220 },
1221 RemappingMode::None => controls
1222 .get_associated_game_inputs(&key_mouse)
1223 .map(|game_inputs| game_inputs.iter()),
1224 _ => None,
1225 }
1226 }
1227
1228 #[expect(clippy::get_first)]
1230 fn map_controller_input<'a>(
1231 controller: &'a mut ControllerSettings,
1232 remapping: &mut RemappingMode,
1233 modifiers: &[Button],
1234 button: &Button,
1235 mod1_input: bool,
1236 mod2_input: bool,
1237 ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1238 match *remapping {
1239 RemappingMode::RemapGamepadLayers(game_input) => {
1240 let new_layer_entry = LayerEntry {
1242 button: *button,
1243 mod1: if mod1_input {
1244 Button::Simple(GilButton::RightTrigger)
1245 } else {
1246 Button::Simple(GilButton::Unknown)
1247 },
1248 mod2: if mod2_input {
1249 Button::Simple(GilButton::LeftTrigger)
1250 } else {
1251 Button::Simple(GilButton::Unknown)
1252 },
1253 };
1254 controller.modify_layer_binding(game_input, new_layer_entry);
1255 *remapping = RemappingMode::None;
1256 None
1257 },
1258 RemappingMode::RemapGamepadButtons(game_input) => {
1259 controller.modify_button_binding(game_input, *button);
1260 *remapping = RemappingMode::None;
1261 None
1262 },
1263 RemappingMode::RemapGamepadMenu(menu_input) => {
1264 controller.modify_menu_binding(menu_input, *button);
1265 *remapping = RemappingMode::None;
1266 None
1267 },
1268 RemappingMode::None => {
1269 let l_entry1 = LayerEntry {
1272 button: *button,
1273 mod1: modifiers.get(0).copied().unwrap_or_default(),
1274 mod2: modifiers.get(1).copied().unwrap_or_default(),
1275 };
1276 let l_entry2 = LayerEntry {
1277 button: *button,
1278 mod1: modifiers.get(1).copied().unwrap_or_default(),
1279 mod2: modifiers.get(0).copied().unwrap_or_default(),
1280 };
1281
1282 if let Some(game_inputs) = controller.get_associated_game_layer_inputs(&l_entry1) {
1284 Some(game_inputs.iter())
1285 } else if let Some(game_inputs) =
1286 controller.get_associated_game_layer_inputs(&l_entry2)
1287 {
1288 Some(game_inputs.iter())
1289 } else {
1290 controller
1292 .get_associated_game_button_inputs(button)
1293 .map(|game_inputs| game_inputs.iter())
1294 }
1295
1296 },
1298 _ => None,
1299 }
1300 }
1301
1302 pub fn set_remapping_mode(&mut self, r_mode: RemappingMode) { self.remapping_mode = r_mode; }
1303
1304 pub fn reset_mapping_mode(&mut self) { self.remapping_mode = RemappingMode::None; }
1305
1306 pub fn window(&self) -> &winit::window::Window { &self.window }
1307
1308 pub fn modifiers(&self) -> winit::keyboard::ModifiersState { self.modifiers }
1309
1310 pub fn scale_factor(&self) -> f64 { self.scale_factor }
1311
1312 pub fn last_input(&self) -> LastInput { self.last_input }
1313}
1314
1315#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1316pub enum FullscreenMode {
1317 Exclusive,
1318 #[serde(other)]
1319 #[default]
1320 Borderless,
1321}
1322
1323#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1324#[serde(default)]
1325pub struct WindowSettings {
1326 pub size: [u32; 2],
1327 pub maximised: bool,
1328}
1329
1330impl Default for WindowSettings {
1331 fn default() -> Self {
1332 Self {
1333 size: [1280, 720],
1334 maximised: false,
1335 }
1336 }
1337}
1338
1339#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1340#[serde(default)]
1341pub struct FullScreenSettings {
1342 pub enabled: bool,
1343 pub mode: FullscreenMode,
1344 pub resolution: [u16; 2],
1345 pub bit_depth: Option<u16>,
1346 pub refresh_rate_millihertz: Option<u32>,
1347}
1348
1349impl Default for FullScreenSettings {
1350 fn default() -> Self {
1351 Self {
1352 enabled: true,
1353 mode: FullscreenMode::Borderless,
1354 resolution: [1920, 1080],
1355 bit_depth: None,
1356 refresh_rate_millihertz: None,
1357 }
1358 }
1359}