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