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