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