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, hash_set::Iter};
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 PageDown,
45 PageUp,
46 Apply,
47 Back,
48 Exit,
49 LocalFocus,
50 GlobalFocus,
51 EmulateLeftClick,
52 EmulateRightClick,
53}
54
55pub enum MappedInput<'a> {
57 Menu(Iter<'a, MenuInput>),
58 Game(Iter<'a, GameInput>),
59}
60
61impl MenuInput {
62 pub fn get_localization_key(&self) -> &str { self.as_ref() }
63}
64
65#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
66pub enum AnalogMenuInput {
67 MoveX(f32),
68 MoveY(f32),
69 ScrollX(f32),
70 ScrollY(f32),
71}
72
73#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
74pub enum AnalogGameInput {
75 MovementX(f32),
76 MovementY(f32),
77 CameraX(f32),
78 CameraY(f32),
79}
80
81#[derive(Clone, Debug)]
83pub enum Event {
84 Close,
86 Resize(Vec2<u32>),
88 ScaleFactorChanged(f64),
90 Moved(Vec2<u32>),
92 CursorPan(Vec2<f32>),
94 CursorMove(Vec2<f32>),
96 MouseButton(MouseButton, PressState),
98 Zoom(f32),
100 InputUpdate(GameInput, bool),
102 Ui(ui::Event),
104 IcedUi(ui::ice::Event),
106 ViewDistanceChanged(u32),
108 SettingsChanged,
110 Focused(bool),
112 MenuInput(MenuInput, bool),
115 AnalogMenuInput(AnalogMenuInput),
117 AnalogGameInput(AnalogGameInput),
119 ScreenshotMessage(String),
121}
122
123pub type MouseButton = winit::event::MouseButton;
124pub type PressState = winit::event::ElementState;
125pub type EventLoop = winit::event_loop::EventLoop<()>;
126
127#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
128pub enum KeyMouse {
129 Key(winit::keyboard::Key),
130 Mouse(winit::event::MouseButton),
131}
132
133impl KeyMouse {
134 pub fn into_upper(mut self) -> Self {
135 if let KeyMouse::Key(winit::keyboard::Key::Character(c)) = &mut self {
136 *c = c.to_ascii_uppercase().into();
137 }
138 self
139 }
140
141 pub fn display_string(&self) -> String {
143 use self::KeyMouse::*;
144 use winit::{event::MouseButton, keyboard::Key::*};
145
146 match self {
147 Key(key) => match key {
148 Named(key) => format!("{key:?}"),
149 Character(c) => c.to_string(),
150 Unidentified(key) => format!("Unknown ({key:?})"),
151 Dead(dead) => format!("Dead ({dead:?})"),
152 },
153 Mouse(MouseButton::Left) => String::from("Left Click"),
154 Mouse(MouseButton::Right) => String::from("Right Click"),
155 Mouse(MouseButton::Middle) => String::from("Middle Click"),
156 Mouse(MouseButton::Forward) => String::from("Mouse Forward"),
157 Mouse(MouseButton::Back) => String::from("Mouse Back"),
158 Mouse(MouseButton::Other(button)) => {
159 format!("Mouse {}", button + 3)
161 },
162 }
163 }
164
165 pub fn try_shortened(&self) -> Option<String> {
168 use self::KeyMouse::*;
169 use winit::event::MouseButton;
170 let key_string = match self {
171 Mouse(MouseButton::Left) => "M1",
172 Mouse(MouseButton::Right) => "M2",
173 Mouse(MouseButton::Middle) => "M3",
174 Mouse(MouseButton::Other(button)) => {
175 return Some(format!("M{}", button + 3));
177 },
178 _ => return None,
179 };
180
181 Some(key_string.to_owned())
182 }
183
184 pub fn display_shortest(&self) -> String {
189 self.try_shortened()
190 .unwrap_or_else(|| self.display_string())
191 }
192}
193
194#[derive(Clone, Copy, Debug)]
195pub enum RemappingMode {
196 RemapKeyboard(GameInput),
197 RemapKeyboardMenu(MenuInput),
198 RemapGamepadButtons(GameInput),
199 RemapGamepadLayers(GameInput),
200 RemapGamepadMenu(MenuInput),
201 None,
202}
203
204#[derive(Clone, Copy, Debug)]
206pub enum ControllerType {
207 Xbox,
208 Nintendo,
209 Playstation,
210 None,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq)]
214pub enum LastInput {
215 Keyboard,
216 Mouse,
217 Controller,
218}
219
220pub struct Window {
221 renderer: Renderer,
222 window: Arc<winit::window::Window>,
223 cursor_grabbed: bool,
224 pub pan_sensitivity: u32,
225 pub zoom_sensitivity: u32,
226 pub zoom_inversion: bool,
227 pub mouse_y_inversion: bool,
228 fullscreen: FullScreenSettings,
229 modifiers: winit::keyboard::ModifiersState,
230 resized: bool,
233 scale_factor: f64,
234 needs_refresh_resize: bool,
235 keypress_map: HashMap<GameInput, winit::event::ElementState>,
236 pub remapping_mode: RemappingMode,
237 events: Vec<Event>,
238 pub focused: bool,
239 gilrs: Option<Gilrs>,
240 pub controller_modifiers: Vec<Button>,
241 pub menu_open: bool,
242 cursor_position: winit::dpi::PhysicalPosition<f64>,
243 mouse_emulation_vec: Vec2<f32>,
244 controller_type: ControllerType,
245 last_input: LastInput,
246 message_sender: channel::Sender<String>,
248 message_receiver: channel::Receiver<String>,
249 take_screenshot: bool,
251 toggle_fullscreen: bool,
252 pub gamelayer_mod1: bool,
255 pub gamelayer_mod2: bool,
256}
257
258impl Window {
259 pub fn new(
260 settings: &Settings,
261 runtime: &tokio::runtime::Runtime,
262 ) -> Result<(Window, EventLoop), Error> {
263 let event_loop = EventLoop::new().unwrap();
264
265 let window = settings.graphics.window;
266
267 #[allow(unused_mut)] let mut attributes = winit::window::Window::default_attributes()
269 .with_title("Veloren")
270 .with_inner_size(winit::dpi::LogicalSize::new(
271 window.size[0] as f64,
272 window.size[1] as f64,
273 ))
274 .with_maximized(window.maximised);
275
276 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
277 {
278 use winit::platform::wayland::WindowAttributesExtWayland;
279 attributes = attributes.with_name("net.veloren.veloren", "veloren");
280 }
281
282 #[cfg(target_family = "windows")]
285 let attributes = winit::platform::windows::WindowAttributesExtWindows::with_drag_and_drop(
286 attributes, false,
287 );
288
289 #[expect(deprecated)]
290 let window = Arc::new(event_loop.create_window(attributes).unwrap());
291
292 let renderer = Renderer::new(
293 Arc::clone(&window),
294 settings.graphics.render_mode.clone(),
295 runtime,
296 )?;
297
298 let keypress_map = HashMap::new();
299
300 let gilrs = match Gilrs::new() {
301 Ok(gilrs) => Some(gilrs),
302 Err(gilrs::Error::NotImplemented(_dummy)) => {
303 warn!("Controller input is unsupported on this platform.");
304 None
305 },
306 Err(gilrs::Error::InvalidAxisToBtn) => {
307 error!(
308 "Invalid AxisToBtn controller mapping. Falling back to no controller support."
309 );
310 None
311 },
312 Err(gilrs::Error::Other(e)) => {
313 error!(
314 ?e,
315 "Platform-specific error when creating a Gilrs instance. Falling back to no \
316 controller support."
317 );
318 None
319 },
320 Err(e) => {
321 error!(
322 ?e,
323 "Unspecified error when creating a Gilrs instance. Falling back to no \
324 controller support."
325 );
326 None
327 },
328 };
329
330 let (message_sender, message_receiver): (
331 channel::Sender<String>,
332 channel::Receiver<String>,
333 ) = channel::unbounded::<String>();
334
335 let scale_factor = window.scale_factor();
336
337 let mut this = Self {
338 renderer,
339 window,
340 cursor_grabbed: false,
341 pan_sensitivity: settings.gameplay.pan_sensitivity,
342 zoom_sensitivity: settings.gameplay.zoom_sensitivity,
343 zoom_inversion: settings.gameplay.zoom_inversion,
344 mouse_y_inversion: settings.gameplay.mouse_y_inversion,
345 fullscreen: FullScreenSettings::default(),
346 modifiers: Default::default(),
347 scale_factor,
348 resized: false,
349 needs_refresh_resize: false,
350 keypress_map,
351 remapping_mode: RemappingMode::None,
352 events: Vec::new(),
353 focused: true,
354 gilrs,
355 controller_modifiers: Vec::new(),
356 menu_open: false,
357 cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
358 mouse_emulation_vec: Vec2::zero(),
359 controller_type: ControllerType::Xbox,
360 last_input: LastInput::Mouse,
361 message_sender,
363 message_receiver,
364 take_screenshot: false,
365 toggle_fullscreen: false,
366 gamelayer_mod1: true,
367 gamelayer_mod2: false,
368 };
369
370 this.set_fullscreen_mode(settings.graphics.fullscreen);
371
372 Ok((this, event_loop))
373 }
374
375 pub fn renderer(&self) -> &Renderer { &self.renderer }
376
377 pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
378
379 pub fn resolve_deduplicated_events(
380 &mut self,
381 settings: &mut Settings,
382 config_dir: &std::path::Path,
383 ) {
384 if self.take_screenshot {
386 self.take_screenshot = false;
387 self.take_screenshot(settings);
388 }
389 if self.toggle_fullscreen {
390 self.toggle_fullscreen = false;
391 self.toggle_fullscreen(settings, config_dir);
392 }
393 }
394
395 pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
396 span!(_guard, "fetch_events", "Window::fetch_events");
397
398 let controller = &mut settings.controller;
399 if self.needs_refresh_resize {
401 let scale_factor = self.window.scale_factor();
402 let physical = self.window.inner_size();
403
404 let logical_size =
405 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
406 self.events
407 .push(Event::Ui(ui::Event::new_resize(logical_size)));
408 self.events.push(Event::IcedUi(iced::Event::Window(
409 iced::window::Event::Resized {
410 width: logical_size.x as u32,
411 height: logical_size.y as u32,
412 },
413 )));
414 self.events.push(Event::ScaleFactorChanged(scale_factor));
415 self.needs_refresh_resize = false;
416 }
417
418 if self.resized {
420 self.resized = false;
421 let physical = self.window.inner_size();
425 let scale_factor = self.window.scale_factor();
426 let is_maximized = self.window.is_maximized();
427
428 self.renderer
429 .on_resize(Vec2::new(physical.width, physical.height));
430 self.events
431 .push(Event::Resize(Vec2::new(physical.width, physical.height)));
432
433 let logical_size =
434 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
435
436 self.events
438 .push(Event::Ui(ui::Event::new_resize(logical_size)));
439 self.events.push(Event::IcedUi(iced::Event::Window(
440 iced::window::Event::Resized {
441 width: logical_size.x as u32,
442 height: logical_size.y as u32,
443 },
444 )));
445
446 if logical_size.x >= 1.0 && logical_size.y >= 1.0 {
451 settings.graphics.window.size = [logical_size.x as u32, logical_size.y as u32];
452 }
453 settings.graphics.window.maximised = is_maximized;
454 }
455
456 for message in self.message_receiver.try_iter() {
458 self.events.push(Event::ScreenshotMessage(message))
459 }
460
461 if let Some(gilrs) = &mut self.gilrs {
462 while let Some(event) = gilrs.next_event() {
463 fn handle_buttons(
464 settings: &mut ControllerSettings,
465 remapping: &mut RemappingMode,
466 modifiers: &mut Vec<Button>,
467 events: &mut Vec<Event>,
468 button: &Button,
469 is_pressed: bool,
470 last_input: &mut LastInput,
471 mod1: bool,
472 mod2: bool,
473 menu_open: bool,
474 ) {
475 *last_input = LastInput::Controller;
477
478 if settings.modifier_buttons.contains(button) {
479 if is_pressed {
480 modifiers.push(*button);
481 } else if let Some(index) =
487 modifiers.iter().position(|modifier| modifier == button)
488 {
489 modifiers.remove(index);
490 }
491 }
492
493 if let Some(mapped_inputs) = Window::map_controller_input(
494 settings, remapping, modifiers, button, mod1, mod2, menu_open,
495 ) {
496 match mapped_inputs {
497 MappedInput::Menu(menu_inputs) => {
500 for menu_input in menu_inputs {
501 events.push(Event::MenuInput(*menu_input, is_pressed));
502 }
503 },
504 MappedInput::Game(game_inputs) => {
505 for game_input in game_inputs {
507 events.push(Event::InputUpdate(*game_input, is_pressed));
508 }
509 },
510 }
511 }
512 }
513
514 match event.event {
515 EventType::ButtonPressed(button, code)
516 | EventType::ButtonRepeated(button, code) => {
517 handle_buttons(
518 controller,
519 &mut self.remapping_mode,
520 &mut self.controller_modifiers,
521 &mut self.events,
522 &Button::from((button, code)),
523 true,
524 &mut self.last_input,
525 self.gamelayer_mod1,
526 self.gamelayer_mod2,
527 self.menu_open,
528 );
529 },
530 EventType::ButtonReleased(button, code) => {
531 handle_buttons(
532 controller,
533 &mut self.remapping_mode,
534 &mut self.controller_modifiers,
535 &mut self.events,
536 &Button::from((button, code)),
537 false,
538 &mut self.last_input,
539 self.gamelayer_mod1,
540 self.gamelayer_mod2,
541 self.menu_open,
542 );
543 },
544 EventType::ButtonChanged(button, _value, code) => {
545 if let Some(actions) = controller
546 .inverse_game_analog_button_map
547 .get(&AnalogButton::from((button, code)))
548 {
549 #[expect(clippy::never_loop)]
550 for action in actions {
551 match *action {}
552 }
553 }
554 if let Some(actions) = controller
555 .inverse_menu_analog_button_map
556 .get(&AnalogButton::from((button, code)))
557 {
558 #[expect(clippy::never_loop)]
559 for action in actions {
560 match *action {}
561 }
562 }
563 },
564
565 EventType::AxisChanged(axis, value, code) => {
566 let value = if controller.inverted_axes.contains(&Axis::from((axis, code)))
567 {
568 -value
569 } else {
570 value
571 };
572
573 let value =
574 controller.apply_axis_deadzone(&Axis::from((axis, code)), value);
575
576 if value.abs() > 0.0001 {
578 self.last_input = LastInput::Controller;
579 }
580
581 if self.cursor_grabbed {
582 if let Some(actions) = controller
583 .inverse_game_axis_map
584 .get(&Axis::from((axis, code)))
585 {
586 for action in actions {
587 match *action {
588 AxisGameAction::MovementX => {
589 self.events.push(Event::AnalogGameInput(
590 AnalogGameInput::MovementX(value),
591 ));
592 },
593 AxisGameAction::MovementY => {
594 self.events.push(Event::AnalogGameInput(
595 AnalogGameInput::MovementY(value),
596 ));
597 },
598 AxisGameAction::CameraX => {
599 self.events.push(Event::AnalogGameInput(
600 AnalogGameInput::CameraX(
601 value * controller.pan_sensitivity as f32
602 / 100.0,
603 ),
604 ));
605 },
606 AxisGameAction::CameraY => {
607 let pan_invert_y = match controller.pan_invert_y {
608 true => -1.0,
609 false => 1.0,
610 };
611
612 self.events.push(Event::AnalogGameInput(
613 AnalogGameInput::CameraY(
614 -value
615 * controller.pan_sensitivity as f32
616 * pan_invert_y
617 / 100.0,
618 ),
619 ));
620 },
621 }
622 }
623 }
624 } else if let Some(actions) = controller
625 .inverse_menu_axis_map
626 .get(&Axis::from((axis, code)))
627 {
628 for action in actions {
630 match *action {
631 AxisMenuAction::MoveX => {
632 self.events.push(Event::AnalogMenuInput(
633 AnalogMenuInput::MoveX(value),
634 ));
635 },
636 AxisMenuAction::MoveY => {
637 self.events.push(Event::AnalogMenuInput(
638 AnalogMenuInput::MoveY(value),
639 ));
640 },
641 AxisMenuAction::ScrollX => {
642 self.events.push(Event::AnalogMenuInput(
643 AnalogMenuInput::ScrollX(value),
644 ));
645 },
646 AxisMenuAction::ScrollY => {
647 self.events.push(Event::AnalogMenuInput(
648 AnalogMenuInput::ScrollY(value),
649 ));
650 },
651 }
652 }
653 }
654 },
655 _ => {},
656 }
657 }
658 }
659
660 let mut events = std::mem::take(&mut self.events);
661 if !self.cursor_grabbed {
664 events = events
665 .into_iter()
666 .filter_map(|event| match event {
667 Event::AnalogMenuInput(input) => match input {
668 AnalogMenuInput::MoveX(d) => {
669 self.mouse_emulation_vec.x = d;
670 None
671 },
672 AnalogMenuInput::MoveY(d) => {
673 self.mouse_emulation_vec.y = -d;
675 None
676 },
677 input => Some(Event::AnalogMenuInput(input)),
678 },
679 Event::MenuInput(menu_input, state) => {
680 let mouse_button = match menu_input {
682 MenuInput::EmulateLeftClick => {
683 conrod_core::input::state::mouse::Button::Left
684 },
685 MenuInput::EmulateRightClick => {
686 conrod_core::input::state::mouse::Button::Right
687 },
688 _ => return Some(event),
689 };
690 Some(match state {
691 true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
692 conrod_core::input::Button::Mouse(mouse_button),
693 ))),
694 false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
695 conrod_core::input::Button::Mouse(mouse_button),
696 ))),
697 })
698 },
699 _ => Some(event),
700 })
701 .collect();
702
703 let sensitivity = controller.mouse_emulation_sensitivity;
704 self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
707 }
708
709 events
710 }
711
712 pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
713 use winit::event::DeviceEvent;
714
715 let mouse_y_inversion = match self.mouse_y_inversion {
716 true => -1.0,
717 false => 1.0,
718 };
719
720 match event {
721 DeviceEvent::MouseMotion {
722 delta: (dx, dy), ..
723 } if self.focused => {
724 self.last_input = LastInput::Mouse;
726
727 let delta = Vec2::new(
728 dx as f32 * (self.pan_sensitivity as f32 / 100.0),
729 dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
730 );
731
732 if self.cursor_grabbed {
733 self.events.push(Event::CursorPan(delta));
734 } else {
735 self.events.push(Event::CursorMove(delta));
736 }
737 },
738 _ => {},
739 }
740 }
741
742 pub fn handle_window_event(
743 &mut self,
744 event: winit::event::WindowEvent,
745 settings: &mut Settings,
746 ) {
747 use winit::event::WindowEvent;
748
749 let controls = &mut settings.controls;
750
751 match event {
752 WindowEvent::CloseRequested => self.events.push(Event::Close),
753 WindowEvent::Resized(_) => {
754 self.resized = true;
755 },
756 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
757 self.scale_factor = scale_factor;
759 self.events.push(Event::ScaleFactorChanged(scale_factor));
760 },
761 WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
762 self.events
763 .push(Event::Moved(Vec2::new(x as u32, y as u32)));
764 },
765 WindowEvent::MouseInput { button, state, .. } => {
766 if self.cursor_grabbed
768 && let Some(mapped_inputs) = Window::map_input(
769 KeyMouse::Mouse(button),
770 controls,
771 &mut self.remapping_mode,
772 &mut self.last_input,
773 self.menu_open,
774 )
775 && let MappedInput::Game(game_inputs) = mapped_inputs
776 {
777 for game_input in game_inputs {
778 self.events.push(Event::InputUpdate(
779 *game_input,
780 state == winit::event::ElementState::Pressed,
781 ));
782 }
783 }
784 self.events.push(Event::MouseButton(button, state));
785 },
786 WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers.state(),
787 WindowEvent::KeyboardInput {
788 event,
789 is_synthetic,
790 ..
791 } => {
792 if event.repeat {
794 return;
795 }
796 if matches!(event.state, winit::event::ElementState::Pressed) && is_synthetic {
799 return;
800 }
801 if matches!(event, winit::event::KeyEvent {
804 state: winit::event::ElementState::Pressed,
805 logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::F4),
806 ..
807 }) && self.modifiers.alt_key()
808 {
809 return;
810 }
811
812 if let Some(mapped_inputs) = Window::map_input(
813 KeyMouse::Key(event.logical_key),
814 controls,
815 &mut self.remapping_mode,
816 &mut self.last_input,
817 self.menu_open,
818 ) {
819 match mapped_inputs {
820 MappedInput::Menu(menu_inputs) => {
821 for menu_input in menu_inputs {
822 self.events.push(Event::MenuInput(
823 *menu_input,
824 event.state == winit::event::ElementState::Pressed,
825 ));
826 }
827 },
828 MappedInput::Game(game_inputs) => {
829 for game_input in game_inputs {
830 match game_input {
831 GameInput::Fullscreen => {
832 if event.state == winit::event::ElementState::Pressed
833 && !Self::is_pressed(
834 &mut self.keypress_map,
835 GameInput::Fullscreen,
836 )
837 {
838 self.toggle_fullscreen = !self.toggle_fullscreen;
839 }
840 Self::set_pressed(
841 &mut self.keypress_map,
842 GameInput::Fullscreen,
843 event.state,
844 );
845 },
846 GameInput::Screenshot => {
847 self.take_screenshot = event.state
848 == winit::event::ElementState::Pressed
849 && !Self::is_pressed(
850 &mut self.keypress_map,
851 GameInput::Screenshot,
852 );
853 Self::set_pressed(
854 &mut self.keypress_map,
855 GameInput::Screenshot,
856 event.state,
857 );
858 },
859 _ => self.events.push(Event::InputUpdate(
860 *game_input,
861 event.state == winit::event::ElementState::Pressed,
862 )),
863 }
864 }
865 },
866 }
867 }
868 },
869 WindowEvent::Focused(state) => {
870 self.focused = state;
871 self.events.push(Event::Focused(state));
872 },
873 WindowEvent::CursorMoved { position, .. } => {
874 if self.cursor_grabbed {
875 self.reset_cursor_position();
876 } else {
877 self.cursor_position = position;
878 }
879 },
880 WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
881 const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
882 self.events.push(Event::Zoom({
883 let y = match delta {
884 winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
885 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
891 };
892 y * (self.zoom_sensitivity as f32 / 100.0)
893 * if self.zoom_inversion { -1.0 } else { 1.0 }
894 * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
895 }))
896 },
897 _ => {},
898 }
899 }
900
901 pub fn offset_cursor(&self, d: Vec2<f32>) {
903 if d != Vec2::zero()
904 && let Err(err) = self
905 .window
906 .set_cursor_position(winit::dpi::LogicalPosition::new(
907 d.x as f64 + self.cursor_position.x,
908 d.y as f64 + self.cursor_position.y,
909 ))
910 {
911 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
913 SPAM_GUARD.call_once(|| {
914 error!("Error setting cursor position: {:?}", err);
915 })
916 }
917 }
918
919 pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
920
921 pub fn grab_cursor(&mut self, grab: bool) {
922 use winit::window::CursorGrabMode;
923
924 self.cursor_grabbed = grab;
925 self.window.set_cursor_visible(!grab);
926 let res = if grab {
927 self.window
928 .set_cursor_grab(CursorGrabMode::Locked)
929 .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
930 } else {
931 self.window.set_cursor_grab(CursorGrabMode::None)
932 };
933
934 if let Err(e) = res {
935 error!(?e, ?grab, "Failed to toggle cursor grab");
936 }
937 }
938
939 fn reset_cursor_position(&self) {
943 if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
944 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
946 SPAM_GUARD.call_once(|| {
947 error!("Error resetting cursor position: {:?}", err);
948 })
949 }
950 }
951
952 pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
953 let fullscreen = FullScreenSettings {
954 enabled: !self.is_fullscreen(),
955 ..settings.graphics.fullscreen
956 };
957
958 self.set_fullscreen_mode(fullscreen);
959 settings.graphics.fullscreen = fullscreen;
960 settings.save_to_file_warn(config_dir);
961 }
962
963 pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
964
965 fn select_video_mode_rec(
969 &self,
970 resolution: [u16; 2],
971 bit_depth: Option<u16>,
972 refresh_rate_millihertz: Option<u32>,
973 correct_res: Option<Vec<VideoModeHandle>>,
974 correct_depth: Option<Option<VideoModeHandle>>,
975 correct_rate: Option<Option<VideoModeHandle>>,
976 ) -> Option<VideoModeHandle> {
977 let correct_res = match correct_res {
981 Some(correct_res) => correct_res,
982 None => self
983 .window
984 .current_monitor()?
985 .video_modes()
986 .filter(|mode| mode.size().width == resolution[0] as u32)
987 .filter(|mode| mode.size().height == resolution[1] as u32)
988 .collect(),
989 };
990
991 match bit_depth {
992 Some(depth) => {
994 let correct_depth = correct_depth.unwrap_or_else(|| {
996 correct_res
997 .iter()
998 .find(|mode| mode.bit_depth() == depth)
999 .cloned()
1000 });
1001
1002 match refresh_rate_millihertz {
1003 Some(rate) => {
1005 let correct_rate = correct_rate.unwrap_or_else(|| {
1007 correct_res
1008 .iter()
1009 .find(|mode| mode.refresh_rate_millihertz() == rate)
1010 .cloned()
1011 });
1012
1013 correct_res
1018 .iter()
1019 .filter(|mode| mode.bit_depth() == depth)
1020 .find(|mode| mode.refresh_rate_millihertz() == rate)
1021 .cloned()
1022 .or_else(|| {
1023 if correct_depth.is_none() && correct_rate.is_none() {
1024 warn!(
1025 "Bit depth and refresh rate specified in settings are \
1026 incompatible with the monitor. Choosing highest bit \
1027 depth and refresh rate possible instead."
1028 );
1029 }
1030
1031 self.select_video_mode_rec(
1032 resolution,
1033 correct_depth.is_some().then_some(depth),
1034 correct_rate.is_some().then_some(rate),
1035 Some(correct_res),
1036 Some(correct_depth),
1037 Some(correct_rate),
1038 )
1039 })
1040 },
1041 None => match correct_depth {
1045 Some(mode) => Some(mode),
1046 None => {
1047 warn!(
1048 "Bit depth specified in settings is incompatible with the \
1049 monitor. Choosing highest bit depth possible instead."
1050 );
1051
1052 self.select_video_mode_rec(
1053 resolution,
1054 None,
1055 None,
1056 Some(correct_res),
1057 Some(correct_depth),
1058 None,
1059 )
1060 },
1061 },
1062 }
1063 },
1064 None => match refresh_rate_millihertz {
1066 Some(rate) => {
1068 let correct_rate = correct_rate.unwrap_or_else(|| {
1070 correct_res
1071 .iter()
1072 .find(|mode| mode.refresh_rate_millihertz() == rate)
1073 .cloned()
1074 });
1075
1076 match correct_rate {
1079 Some(mode) => Some(mode),
1080 None => {
1081 warn!(
1082 "Refresh rate specified in settings is incompatible with the \
1083 monitor. Choosing highest refresh rate possible instead."
1084 );
1085
1086 self.select_video_mode_rec(
1087 resolution,
1088 None,
1089 None,
1090 Some(correct_res),
1091 None,
1092 Some(correct_rate),
1093 )
1094 },
1095 }
1096 },
1097 None => correct_res
1101 .into_iter()
1102 .sorted_by_key(|mode| mode.bit_depth())
1104 .max_by_key(|mode| mode.refresh_rate_millihertz()),
1105 },
1106 }
1107 }
1108
1109 fn select_video_mode(
1110 &self,
1111 resolution: [u16; 2],
1112 bit_depth: Option<u16>,
1113 refresh_rate_millihertz: Option<u32>,
1114 ) -> Option<VideoModeHandle> {
1115 match self.select_video_mode_rec(
1125 resolution,
1126 bit_depth,
1127 refresh_rate_millihertz,
1128 None,
1129 None,
1130 None,
1131 ) {
1132 Some(mode) => Some(mode),
1133 None => {
1136 warn!(
1137 "Resolution specified in settings is incompatible with the monitor. Choosing \
1138 highest resolution possible instead."
1139 );
1140 if let Some(monitor) = self.window.current_monitor() {
1141 let mode = monitor
1142 .video_modes()
1143 .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1145 .sorted_by_key(|mode| mode.bit_depth())
1146 .max_by_key(|mode| mode.size().width);
1147
1148 if mode.is_none() {
1149 warn!("Failed to select video mode, no video modes available!!")
1150 }
1151
1152 mode
1153 } else {
1154 warn!("Failed to select video mode, can't get the current monitor!");
1155 None
1156 }
1157 },
1158 }
1159 }
1160
1161 pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1162 let window = &self.window;
1163 self.fullscreen = fullscreen;
1164 window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1165 FullscreenMode::Exclusive => {
1166 if let Some(video_mode) = self.select_video_mode(
1167 fullscreen.resolution,
1168 fullscreen.bit_depth,
1169 fullscreen.refresh_rate_millihertz,
1170 ) {
1171 winit::window::Fullscreen::Exclusive(video_mode)
1172 } else {
1173 warn!(
1174 "Failed to select a video mode for exclusive fullscreen. Falling back to \
1175 borderless fullscreen."
1176 );
1177 winit::window::Fullscreen::Borderless(None)
1178 }
1179 },
1180 FullscreenMode::Borderless => {
1181 winit::window::Fullscreen::Borderless(None)
1183 },
1184 }));
1185 }
1186
1187 pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1188
1189 pub fn set_size(&mut self, new_size: Vec2<u32>) {
1190 self.window
1191 .set_min_inner_size(Some(winit::dpi::LogicalSize::new(
1192 new_size.x as f64,
1193 new_size.y as f64,
1194 )));
1195 }
1196
1197 pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1198
1199 pub fn take_screenshot(&mut self, settings: &Settings) {
1200 let sender = self.message_sender.clone();
1201 let mut path = settings.screenshots_path.clone();
1202 self.renderer.create_screenshot(move |image| {
1203 use std::time::SystemTime;
1204
1205 let image = match image {
1207 Ok(i) => i,
1208 Err(e) => {
1209 warn!(?e, "Couldn't generate screenshot");
1210 let _result = sender.send(format!("Error when generating screenshot: {}", e));
1211 return;
1212 },
1213 };
1214
1215 if !path.exists()
1217 && let Err(e) = std::fs::create_dir_all(&path)
1218 {
1219 warn!(?e, ?path, "Couldn't create folder for screenshot");
1220 let _result = sender.send(String::from("Couldn't create folder for screenshot"));
1221 }
1222 path.push(format!(
1223 "screenshot_{}.png",
1224 SystemTime::now()
1225 .duration_since(SystemTime::UNIX_EPOCH)
1226 .map(|d| d.as_millis())
1227 .unwrap_or(0)
1228 ));
1229 if let Err(e) = image.save(&path) {
1231 warn!(?e, ?path, "Couldn't save screenshot");
1232 let _result = sender.send(String::from("Couldn't save screenshot"));
1233 } else {
1234 let _result =
1235 sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1236 }
1237 });
1238 }
1239
1240 fn is_pressed(
1241 map: &mut HashMap<GameInput, winit::event::ElementState>,
1242 input: GameInput,
1243 ) -> bool {
1244 *(map
1245 .entry(input)
1246 .or_insert(winit::event::ElementState::Released))
1247 == winit::event::ElementState::Pressed
1248 }
1249
1250 fn set_pressed(
1251 map: &mut HashMap<GameInput, winit::event::ElementState>,
1252 input: GameInput,
1253 state: winit::event::ElementState,
1254 ) {
1255 map.insert(input, state);
1256 }
1257
1258 fn map_input<'a>(
1266 key_mouse: KeyMouse,
1267 controls: &'a mut ControlSettings,
1268 remapping: &mut RemappingMode,
1269 last_input: &mut LastInput,
1270 menu_open: bool,
1271 ) -> Option<MappedInput<'a>> {
1272 let key_mouse = key_mouse.into_upper();
1273
1274 *last_input = LastInput::Keyboard;
1276
1277 match *remapping {
1278 RemappingMode::RemapKeyboard(game_input) => {
1279 controls.modify_binding(game_input, key_mouse);
1280 *remapping = RemappingMode::None;
1281 None
1282 },
1283 RemappingMode::RemapKeyboardMenu(menu_input) => {
1284 controls.modify_menu_binding(menu_input, key_mouse);
1285 *remapping = RemappingMode::None;
1286 None
1287 },
1288 RemappingMode::None => {
1289 if !menu_open {
1290 controls
1292 .get_associated_game_inputs(&key_mouse)
1293 .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1294 } else {
1295 if let Some(menu_inputs) = controls.get_associated_menu_inputs(&key_mouse)
1298 && !menu_inputs.is_empty()
1299 {
1300 return Some(MappedInput::Menu(menu_inputs.iter()));
1301 }
1302
1303 controls
1304 .get_associated_game_inputs(&key_mouse)
1305 .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1306 }
1307 },
1308 _ => None,
1309 }
1310 }
1311
1312 #[expect(clippy::get_first)]
1314 fn map_controller_input<'a>(
1315 controller: &'a mut ControllerSettings,
1316 remapping: &mut RemappingMode,
1317 modifiers: &[Button],
1318 button: &Button,
1319 mod1_input: bool,
1320 mod2_input: bool,
1321 menu_open: bool,
1322 ) -> Option<MappedInput<'a>> {
1323 match *remapping {
1324 RemappingMode::RemapGamepadLayers(game_input) => {
1325 let new_layer_entry = LayerEntry {
1327 button: *button,
1328 mod1: if mod1_input {
1329 Button::Simple(GilButton::RightTrigger)
1330 } else {
1331 Button::Simple(GilButton::Unknown)
1332 },
1333 mod2: if mod2_input {
1334 Button::Simple(GilButton::LeftTrigger)
1335 } else {
1336 Button::Simple(GilButton::Unknown)
1337 },
1338 };
1339 controller.modify_layer_binding(game_input, new_layer_entry);
1340 *remapping = RemappingMode::None;
1341 None
1342 },
1343 RemappingMode::RemapGamepadButtons(game_input) => {
1344 controller.modify_button_binding(game_input, *button);
1345 *remapping = RemappingMode::None;
1346 None
1347 },
1348 RemappingMode::RemapGamepadMenu(menu_input) => {
1349 controller.modify_menu_binding(menu_input, *button);
1350 *remapping = RemappingMode::None;
1351 None
1352 },
1353 RemappingMode::None => {
1354 let l_entry1 = LayerEntry {
1357 button: *button,
1358 mod1: modifiers.get(0).copied().unwrap_or_default(),
1359 mod2: modifiers.get(1).copied().unwrap_or_default(),
1360 };
1361 let l_entry2 = LayerEntry {
1362 button: *button,
1363 mod1: modifiers.get(1).copied().unwrap_or_default(),
1364 mod2: modifiers.get(0).copied().unwrap_or_default(),
1365 };
1366
1367 if !menu_open {
1368 if let Some(game_inputs) =
1372 controller.get_associated_game_layer_inputs(&l_entry1)
1373 {
1374 Some(MappedInput::Game(game_inputs.iter()))
1375 } else if let Some(game_inputs) =
1376 controller.get_associated_game_layer_inputs(&l_entry2)
1377 {
1378 Some(MappedInput::Game(game_inputs.iter()))
1379 } else {
1380 controller
1382 .get_associated_game_button_inputs(button)
1383 .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1384 }
1385 } else {
1386 if let Some(menu_inputs) = controller.get_associated_game_menu_inputs(button)
1389 && !menu_inputs.is_empty()
1390 {
1391 return Some(MappedInput::Menu(menu_inputs.iter()));
1392 }
1393
1394 if let Some(game_inputs) =
1396 controller.get_associated_game_layer_inputs(&l_entry1)
1397 {
1398 Some(MappedInput::Game(game_inputs.iter()))
1399 } else if let Some(game_inputs) =
1400 controller.get_associated_game_layer_inputs(&l_entry2)
1401 {
1402 Some(MappedInput::Game(game_inputs.iter()))
1403 } else {
1404 controller
1406 .get_associated_game_button_inputs(button)
1407 .map(|game_inputs| MappedInput::Game(game_inputs.iter()))
1408 }
1409 }
1410 },
1411 _ => None,
1412 }
1413 }
1414
1415 pub fn set_remapping_mode(&mut self, r_mode: RemappingMode) { self.remapping_mode = r_mode; }
1416
1417 pub fn reset_mapping_mode(&mut self) { self.remapping_mode = RemappingMode::None; }
1418
1419 pub fn window(&self) -> &winit::window::Window { &self.window }
1420
1421 pub fn modifiers(&self) -> winit::keyboard::ModifiersState { self.modifiers }
1422
1423 pub fn scale_factor(&self) -> f64 { self.scale_factor }
1424
1425 pub fn last_input(&self) -> LastInput { self.last_input }
1426
1427 pub fn controller_type(&self) -> ControllerType { self.controller_type }
1428}
1429
1430#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1431pub enum FullscreenMode {
1432 Exclusive,
1433 #[serde(other)]
1434 #[default]
1435 Borderless,
1436}
1437
1438#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1439#[serde(default)]
1440pub struct WindowSettings {
1441 pub size: [u32; 2],
1442 pub maximised: bool,
1443}
1444
1445impl Default for WindowSettings {
1446 fn default() -> Self {
1447 Self {
1448 size: [1280, 720],
1449 maximised: false,
1450 }
1451 }
1452}
1453
1454#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1455#[serde(default)]
1456pub struct FullScreenSettings {
1457 pub enabled: bool,
1458 pub mode: FullscreenMode,
1459 pub resolution: [u16; 2],
1460 pub bit_depth: Option<u16>,
1461 pub refresh_rate_millihertz: Option<u32>,
1462}
1463
1464impl Default for FullScreenSettings {
1465 fn default() -> Self {
1466 Self {
1467 enabled: true,
1468 mode: FullscreenMode::Borderless,
1469 resolution: [1920, 1080],
1470 bit_depth: None,
1471 refresh_rate_millihertz: None,
1472 }
1473 }
1474}