1use crate::{
2 controller::*,
3 error::Error,
4 game_input::GameInput,
5 render::Renderer,
6 settings::{ControlSettings, Settings, gamepad::con_settings::LayerEntry},
7 ui,
8};
9use common_base::span;
10use crossbeam_channel as channel;
11use gilrs::{EventType, Gilrs};
12use hashbrown::HashMap;
13use itertools::Itertools;
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use strum::{AsRefStr, EnumIter};
17use tracing::{error, warn};
18use vek::*;
19use winit::monitor::VideoModeHandle;
20
21#[derive(
23 Clone,
24 Copy,
25 Debug,
26 PartialEq,
27 Eq,
28 PartialOrd,
29 Ord,
30 Hash,
31 Deserialize,
32 Serialize,
33 AsRefStr,
34 EnumIter,
35)]
36pub enum MenuInput {
37 Up,
38 Down,
39 Left,
40 Right,
41 ScrollUp,
42 ScrollDown,
43 ScrollLeft,
44 ScrollRight,
45 Home,
46 End,
47 Apply,
48 Back,
49 Exit,
50}
51
52impl MenuInput {
53 pub fn get_localization_key(&self) -> &str { self.as_ref() }
54}
55
56#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
57pub enum AnalogMenuInput {
58 MoveX(f32),
59 MoveY(f32),
60 ScrollX(f32),
61 ScrollY(f32),
62}
63
64#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
65pub enum AnalogGameInput {
66 MovementX(f32),
67 MovementY(f32),
68 CameraX(f32),
69 CameraY(f32),
70}
71
72#[derive(Clone, Debug)]
74pub enum Event {
75 Close,
77 Resize(Vec2<u32>),
79 ScaleFactorChanged(f64),
81 Moved(Vec2<u32>),
83 CursorPan(Vec2<f32>),
85 CursorMove(Vec2<f32>),
87 MouseButton(MouseButton, PressState),
89 Zoom(f32),
91 InputUpdate(GameInput, bool),
93 Ui(ui::Event),
95 IcedUi(ui::ice::Event),
97 ViewDistanceChanged(u32),
99 SettingsChanged,
101 Focused(bool),
103 MenuInput(MenuInput, bool),
106 AnalogMenuInput(AnalogMenuInput),
108 AnalogGameInput(AnalogGameInput),
110 ScreenshotMessage(String),
112}
113
114pub type MouseButton = winit::event::MouseButton;
115pub type PressState = winit::event::ElementState;
116pub type EventLoop = winit::event_loop::EventLoop<()>;
117
118#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
119pub enum KeyMouse {
120 Key(winit::keyboard::Key),
121 Mouse(winit::event::MouseButton),
122}
123
124impl KeyMouse {
125 pub fn into_upper(mut self) -> Self {
126 if let KeyMouse::Key(winit::keyboard::Key::Character(c)) = &mut self {
127 *c = c.to_ascii_uppercase().into();
128 }
129 self
130 }
131
132 pub fn display_string(&self) -> String {
134 use self::KeyMouse::*;
135 use winit::{event::MouseButton, keyboard::Key::*};
136
137 match self {
138 Key(key) => match key {
139 Named(key) => format!("{key:?}"),
140 Character(c) => c.to_string(),
141 Unidentified(key) => format!("Unknown ({key:?})"),
142 Dead(dead) => format!("Dead ({dead:?})"),
143 },
144 Mouse(MouseButton::Left) => String::from("Left Click"),
145 Mouse(MouseButton::Right) => String::from("Right Click"),
146 Mouse(MouseButton::Middle) => String::from("Middle Click"),
147 Mouse(MouseButton::Forward) => String::from("Mouse Forward"),
148 Mouse(MouseButton::Back) => String::from("Mouse Back"),
149 Mouse(MouseButton::Other(button)) => {
150 format!("Mouse {}", button + 3)
152 },
153 }
154 }
155
156 pub fn try_shortened(&self) -> Option<String> {
159 use self::KeyMouse::*;
160 use winit::event::MouseButton;
161 let key_string = match self {
162 Mouse(MouseButton::Left) => "M1",
163 Mouse(MouseButton::Right) => "M2",
164 Mouse(MouseButton::Middle) => "M3",
165 Mouse(MouseButton::Other(button)) => {
166 return Some(format!("M{}", button + 3));
168 },
169 _ => return None,
170 };
171
172 Some(key_string.to_owned())
173 }
174
175 pub fn display_shortest(&self) -> String {
180 self.try_shortened()
181 .unwrap_or_else(|| self.display_string())
182 }
183}
184
185pub struct Window {
186 renderer: Renderer,
187 window: Arc<winit::window::Window>,
188 cursor_grabbed: bool,
189 pub pan_sensitivity: u32,
190 pub zoom_sensitivity: u32,
191 pub zoom_inversion: bool,
192 pub mouse_y_inversion: bool,
193 fullscreen: FullScreenSettings,
194 modifiers: winit::keyboard::ModifiersState,
195 resized: bool,
198 scale_factor: f64,
199 needs_refresh_resize: bool,
200 keypress_map: HashMap<GameInput, winit::event::ElementState>,
201 pub remapping_keybindings: Option<GameInput>,
202 events: Vec<Event>,
203 pub focused: bool,
204 gilrs: Option<Gilrs>,
205 pub controller_settings: ControllerSettings,
206 pub controller_modifiers: Vec<Button>,
207 cursor_position: winit::dpi::PhysicalPosition<f64>,
208 mouse_emulation_vec: Vec2<f32>,
209 message_sender: channel::Sender<String>,
211 message_receiver: channel::Receiver<String>,
212 take_screenshot: bool,
214 toggle_fullscreen: bool,
215}
216
217impl Window {
218 pub fn new(
219 settings: &Settings,
220 runtime: &tokio::runtime::Runtime,
221 ) -> Result<(Window, EventLoop), Error> {
222 let event_loop = EventLoop::new().unwrap();
223
224 let window = settings.graphics.window;
225
226 let attributes = winit::window::Window::default_attributes()
227 .with_title("Veloren")
228 .with_inner_size(winit::dpi::LogicalSize::new(
229 window.size[0] as f64,
230 window.size[1] as f64,
231 ))
232 .with_maximized(window.maximised);
233
234 #[cfg(target_family = "windows")]
237 let attributes = winit::platform::windows::WindowAttributesExtWindows::with_drag_and_drop(
238 attributes, false,
239 );
240
241 #[expect(deprecated)]
242 let window = Arc::new(event_loop.create_window(attributes).unwrap());
243
244 let renderer = Renderer::new(
245 Arc::clone(&window),
246 settings.graphics.render_mode.clone(),
247 runtime,
248 )?;
249
250 let keypress_map = HashMap::new();
251
252 let gilrs = match Gilrs::new() {
253 Ok(gilrs) => Some(gilrs),
254 Err(gilrs::Error::NotImplemented(_dummy)) => {
255 warn!("Controller input is unsupported on this platform.");
256 None
257 },
258 Err(gilrs::Error::InvalidAxisToBtn) => {
259 error!(
260 "Invalid AxisToBtn controller mapping. Falling back to no controller support."
261 );
262 None
263 },
264 Err(gilrs::Error::Other(e)) => {
265 error!(
266 ?e,
267 "Platform-specific error when creating a Gilrs instance. Falling back to no \
268 controller support."
269 );
270 None
271 },
272 Err(e) => {
273 error!(
274 ?e,
275 "Unspecified error when creating a Gilrs instance. Falling back to no \
276 controller support."
277 );
278 None
279 },
280 };
281
282 let controller_settings = ControllerSettings::from(&settings.controller);
283
284 let (message_sender, message_receiver): (
285 channel::Sender<String>,
286 channel::Receiver<String>,
287 ) = channel::unbounded::<String>();
288
289 let scale_factor = window.scale_factor();
290
291 let mut this = Self {
292 renderer,
293 window,
294 cursor_grabbed: false,
295 pan_sensitivity: settings.gameplay.pan_sensitivity,
296 zoom_sensitivity: settings.gameplay.zoom_sensitivity,
297 zoom_inversion: settings.gameplay.zoom_inversion,
298 mouse_y_inversion: settings.gameplay.mouse_y_inversion,
299 fullscreen: FullScreenSettings::default(),
300 modifiers: Default::default(),
301 scale_factor,
302 resized: false,
303 needs_refresh_resize: false,
304 keypress_map,
305 remapping_keybindings: None,
306 events: Vec::new(),
307 focused: true,
308 gilrs,
309 controller_settings,
310 controller_modifiers: Vec::new(),
311 cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
312 mouse_emulation_vec: Vec2::zero(),
313 message_sender,
315 message_receiver,
316 take_screenshot: false,
317 toggle_fullscreen: false,
318 };
319
320 this.set_fullscreen_mode(settings.graphics.fullscreen);
321
322 Ok((this, event_loop))
323 }
324
325 pub fn renderer(&self) -> &Renderer { &self.renderer }
326
327 pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
328
329 pub fn resolve_deduplicated_events(
330 &mut self,
331 settings: &mut Settings,
332 config_dir: &std::path::Path,
333 ) {
334 if self.take_screenshot {
336 self.take_screenshot = false;
337 self.take_screenshot(settings);
338 }
339 if self.toggle_fullscreen {
340 self.toggle_fullscreen = false;
341 self.toggle_fullscreen(settings, config_dir);
342 }
343 }
344
345 #[expect(clippy::get_first)]
346 pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
347 span!(_guard, "fetch_events", "Window::fetch_events");
348 if self.needs_refresh_resize {
350 let scale_factor = self.window.scale_factor();
351 let physical = self.window.inner_size();
352
353 let logical_size =
354 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
355 self.events
356 .push(Event::Ui(ui::Event::new_resize(logical_size)));
357 self.events.push(Event::IcedUi(iced::Event::Window(
358 iced::window::Event::Resized {
359 width: logical_size.x as u32,
360 height: logical_size.y as u32,
361 },
362 )));
363 self.events.push(Event::ScaleFactorChanged(scale_factor));
364 self.needs_refresh_resize = false;
365 }
366
367 if self.resized {
369 self.resized = false;
370 let physical = self.window.inner_size();
374 let scale_factor = self.window.scale_factor();
375 let is_maximized = self.window.is_maximized();
376
377 self.renderer
378 .on_resize(Vec2::new(physical.width, physical.height));
379 self.events
380 .push(Event::Resize(Vec2::new(physical.width, physical.height)));
381
382 let logical_size =
383 Vec2::from(<(f64, f64)>::from(physical.to_logical::<f64>(scale_factor)));
384
385 self.events
387 .push(Event::Ui(ui::Event::new_resize(logical_size)));
388 self.events.push(Event::IcedUi(iced::Event::Window(
389 iced::window::Event::Resized {
390 width: logical_size.x as u32,
391 height: logical_size.y as u32,
392 },
393 )));
394
395 if logical_size.x >= 1.0 && logical_size.y >= 1.0 {
400 settings.graphics.window.size = [logical_size.x as u32, logical_size.y as u32];
401 }
402 settings.graphics.window.maximised = is_maximized;
403 }
404
405 for message in self.message_receiver.try_iter() {
407 self.events.push(Event::ScreenshotMessage(message))
408 }
409
410 if let Some(gilrs) = &mut self.gilrs {
411 while let Some(event) = gilrs.next_event() {
412 fn handle_buttons(
413 settings: &ControllerSettings,
414 modifiers: &mut Vec<Button>,
415 events: &mut Vec<Event>,
416 button: &Button,
417 is_pressed: bool,
418 ) {
419 if settings.modifier_buttons.contains(button) {
420 if is_pressed {
421 modifiers.push(*button);
422 } else if let Some(index) =
428 modifiers.iter().position(|modifier| modifier == button)
429 {
430 modifiers.remove(index);
431 }
432 }
433
434 let l_entry1 = LayerEntry {
436 button: *button,
437 mod1: modifiers.get(0).copied().unwrap_or_default(),
438 mod2: modifiers.get(1).copied().unwrap_or_default(),
439 };
440 let l_entry2 = LayerEntry {
441 button: *button,
442 mod1: modifiers.get(1).copied().unwrap_or_default(),
443 mod2: modifiers.get(0).copied().unwrap_or_default(),
444 };
445
446 if let Some(evs) = settings.inverse_layer_button_map.get(&l_entry1) {
449 for ev in evs {
450 events.push(Event::InputUpdate(*ev, is_pressed));
451 }
452 } else if let Some(evs) = settings.inverse_layer_button_map.get(&l_entry2) {
453 for ev in evs {
454 events.push(Event::InputUpdate(*ev, is_pressed));
455 }
456 }
457 if let Some(evs) = settings.inverse_game_button_map.get(button) {
458 for ev in evs {
459 events.push(Event::InputUpdate(*ev, is_pressed));
460 }
461 }
462 if let Some(evs) = settings.inverse_menu_button_map.get(button) {
463 for ev in evs {
464 events.push(Event::MenuInput(*ev, is_pressed));
465 }
466 }
467 }
468
469 match event.event {
470 EventType::ButtonPressed(button, code)
471 | EventType::ButtonRepeated(button, code) => {
472 handle_buttons(
473 &self.controller_settings,
474 &mut self.controller_modifiers,
475 &mut self.events,
476 &Button::from((button, code)),
477 true,
478 );
479 },
480 EventType::ButtonReleased(button, code) => {
481 handle_buttons(
482 &self.controller_settings,
483 &mut self.controller_modifiers,
484 &mut self.events,
485 &Button::from((button, code)),
486 false,
487 );
488 },
489 EventType::ButtonChanged(button, _value, code) => {
490 if let Some(actions) = self
491 .controller_settings
492 .inverse_game_analog_button_map
493 .get(&AnalogButton::from((button, code)))
494 {
495 #[expect(clippy::never_loop)]
496 for action in actions {
497 match *action {}
498 }
499 }
500 if let Some(actions) = self
501 .controller_settings
502 .inverse_menu_analog_button_map
503 .get(&AnalogButton::from((button, code)))
504 {
505 #[expect(clippy::never_loop)]
506 for action in actions {
507 match *action {}
508 }
509 }
510 },
511
512 EventType::AxisChanged(axis, value, code) => {
513 let value = if self
514 .controller_settings
515 .inverted_axes
516 .contains(&Axis::from((axis, code)))
517 {
518 -value
519 } else {
520 value
521 };
522
523 let value = self
524 .controller_settings
525 .apply_axis_deadzone(&Axis::from((axis, code)), value);
526
527 if self.cursor_grabbed {
528 if let Some(actions) = self
529 .controller_settings
530 .inverse_game_axis_map
531 .get(&Axis::from((axis, code)))
532 {
533 for action in actions {
534 match *action {
535 AxisGameAction::MovementX => {
536 self.events.push(Event::AnalogGameInput(
537 AnalogGameInput::MovementX(value),
538 ));
539 },
540 AxisGameAction::MovementY => {
541 self.events.push(Event::AnalogGameInput(
542 AnalogGameInput::MovementY(value),
543 ));
544 },
545 AxisGameAction::CameraX => {
546 self.events.push(Event::AnalogGameInput(
547 AnalogGameInput::CameraX(
548 value
549 * self.controller_settings.pan_sensitivity
550 as f32
551 / 100.0,
552 ),
553 ));
554 },
555 AxisGameAction::CameraY => {
556 let pan_invert_y =
557 match self.controller_settings.pan_invert_y {
558 true => -1.0,
559 false => 1.0,
560 };
561
562 self.events.push(Event::AnalogGameInput(
563 AnalogGameInput::CameraY(
564 -value
565 * self.controller_settings.pan_sensitivity
566 as f32
567 * pan_invert_y
568 / 100.0,
569 ),
570 ));
571 },
572 }
573 }
574 }
575 } else if let Some(actions) = self
576 .controller_settings
577 .inverse_menu_axis_map
578 .get(&Axis::from((axis, code)))
579 {
580 for action in actions {
582 match *action {
583 AxisMenuAction::MoveX => {
584 self.events.push(Event::AnalogMenuInput(
585 AnalogMenuInput::MoveX(value),
586 ));
587 },
588 AxisMenuAction::MoveY => {
589 self.events.push(Event::AnalogMenuInput(
590 AnalogMenuInput::MoveY(value),
591 ));
592 },
593 AxisMenuAction::ScrollX => {
594 self.events.push(Event::AnalogMenuInput(
595 AnalogMenuInput::ScrollX(value),
596 ));
597 },
598 AxisMenuAction::ScrollY => {
599 self.events.push(Event::AnalogMenuInput(
600 AnalogMenuInput::ScrollY(value),
601 ));
602 },
603 }
604 }
605 }
606 },
607 _ => {},
608 }
609 }
610 }
611
612 let mut events = std::mem::take(&mut self.events);
613 if !self.cursor_grabbed {
616 events = events
617 .into_iter()
618 .filter_map(|event| match event {
619 Event::AnalogMenuInput(input) => match input {
620 AnalogMenuInput::MoveX(d) => {
621 self.mouse_emulation_vec.x = d;
622 None
623 },
624 AnalogMenuInput::MoveY(d) => {
625 self.mouse_emulation_vec.y = d * -1.0;
627 None
628 },
629 input => Some(Event::AnalogMenuInput(input)),
630 },
631 Event::MenuInput(MenuInput::Apply, state) => Some(match state {
632 true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
633 conrod_core::input::Button::Mouse(
634 conrod_core::input::state::mouse::Button::Left,
635 ),
636 ))),
637 false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
638 conrod_core::input::Button::Mouse(
639 conrod_core::input::state::mouse::Button::Left,
640 ),
641 ))),
642 }),
643 _ => Some(event),
644 })
645 .collect();
646
647 let sensitivity = self.controller_settings.mouse_emulation_sensitivity;
648 self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
651 }
652
653 events
654 }
655
656 pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
657 use winit::event::DeviceEvent;
658
659 let mouse_y_inversion = match self.mouse_y_inversion {
660 true => -1.0,
661 false => 1.0,
662 };
663
664 match event {
665 DeviceEvent::MouseMotion {
666 delta: (dx, dy), ..
667 } if self.focused => {
668 let delta = Vec2::new(
669 dx as f32 * (self.pan_sensitivity as f32 / 100.0),
670 dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
671 );
672
673 if self.cursor_grabbed {
674 self.events.push(Event::CursorPan(delta));
675 } else {
676 self.events.push(Event::CursorMove(delta));
677 }
678 },
679 _ => {},
680 }
681 }
682
683 pub fn handle_window_event(
684 &mut self,
685 event: winit::event::WindowEvent,
686 settings: &mut Settings,
687 ) {
688 use winit::event::WindowEvent;
689
690 let controls = &mut settings.controls;
691
692 match event {
693 WindowEvent::CloseRequested => self.events.push(Event::Close),
694 WindowEvent::Resized(_) => {
695 self.resized = true;
696 },
697 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
698 self.scale_factor = scale_factor;
700 self.events.push(Event::ScaleFactorChanged(scale_factor));
701 },
702 WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
703 self.events
704 .push(Event::Moved(Vec2::new(x as u32, y as u32)));
705 },
706 WindowEvent::MouseInput { button, state, .. } => {
707 if let (true, Some(game_inputs)) =
708 (
710 self.cursor_grabbed,
711 Window::map_input(
712 KeyMouse::Mouse(button),
713 controls,
714 &mut self.remapping_keybindings,
715 ),
716 )
717 {
718 for game_input in game_inputs {
719 self.events.push(Event::InputUpdate(
720 *game_input,
721 state == winit::event::ElementState::Pressed,
722 ));
723 }
724 }
725 self.events.push(Event::MouseButton(button, state));
726 },
727 WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers.state(),
728 WindowEvent::KeyboardInput {
729 event,
730 is_synthetic,
731 ..
732 } => {
733 if matches!(
736 event.logical_key,
737 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab)
738 ) && is_synthetic
739 {
740 return;
741 }
742 if matches!(event, winit::event::KeyEvent {
745 state: winit::event::ElementState::Pressed,
746 logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),
747 ..
748 }) && self.modifiers.alt_key()
749 {
750 return;
751 }
752
753 if let Some(game_inputs) = Window::map_input(
754 KeyMouse::Key(event.logical_key),
755 controls,
756 &mut self.remapping_keybindings,
757 ) {
758 for game_input in game_inputs {
759 match game_input {
760 GameInput::Fullscreen => {
761 if event.state == winit::event::ElementState::Pressed
762 && !Self::is_pressed(
763 &mut self.keypress_map,
764 GameInput::Fullscreen,
765 )
766 {
767 self.toggle_fullscreen = !self.toggle_fullscreen;
768 }
769 Self::set_pressed(
770 &mut self.keypress_map,
771 GameInput::Fullscreen,
772 event.state,
773 );
774 },
775 GameInput::Screenshot => {
776 self.take_screenshot = event.state
777 == winit::event::ElementState::Pressed
778 && !Self::is_pressed(
779 &mut self.keypress_map,
780 GameInput::Screenshot,
781 );
782 Self::set_pressed(
783 &mut self.keypress_map,
784 GameInput::Screenshot,
785 event.state,
786 );
787 },
788 _ => self.events.push(Event::InputUpdate(
789 *game_input,
790 event.state == winit::event::ElementState::Pressed,
791 )),
792 }
793 }
794 }
795 },
796 WindowEvent::Focused(state) => {
797 self.focused = state;
798 self.events.push(Event::Focused(state));
799 },
800 WindowEvent::CursorMoved { position, .. } => {
801 if self.cursor_grabbed {
802 self.reset_cursor_position();
803 } else {
804 self.cursor_position = position;
805 }
806 },
807 WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
808 const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
809 self.events.push(Event::Zoom({
810 let y = match delta {
811 winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
812 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
818 };
819 y * (self.zoom_sensitivity as f32 / 100.0)
820 * if self.zoom_inversion { -1.0 } else { 1.0 }
821 * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
822 }))
823 },
824 _ => {},
825 }
826 }
827
828 pub fn offset_cursor(&self, d: Vec2<f32>) {
830 if d != Vec2::zero() {
831 if let Err(err) = self
832 .window
833 .set_cursor_position(winit::dpi::LogicalPosition::new(
834 d.x as f64 + self.cursor_position.x,
835 d.y as f64 + self.cursor_position.y,
836 ))
837 {
838 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
840 SPAM_GUARD.call_once(|| {
841 error!("Error setting cursor position: {:?}", err);
842 })
843 }
844 }
845 }
846
847 pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
848
849 pub fn grab_cursor(&mut self, grab: bool) {
850 use winit::window::CursorGrabMode;
851
852 self.cursor_grabbed = grab;
853 self.window.set_cursor_visible(!grab);
854 let res = if grab {
855 self.window
856 .set_cursor_grab(CursorGrabMode::Locked)
857 .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
858 } else {
859 self.window.set_cursor_grab(CursorGrabMode::None)
860 };
861
862 if let Err(e) = res {
863 error!(?e, ?grab, "Failed to toggle cursor grab");
864 }
865 }
866
867 fn reset_cursor_position(&self) {
871 if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
872 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
874 SPAM_GUARD.call_once(|| {
875 error!("Error resetting cursor position: {:?}", err);
876 })
877 }
878 }
879
880 pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
881 let fullscreen = FullScreenSettings {
882 enabled: !self.is_fullscreen(),
883 ..settings.graphics.fullscreen
884 };
885
886 self.set_fullscreen_mode(fullscreen);
887 settings.graphics.fullscreen = fullscreen;
888 settings.save_to_file_warn(config_dir);
889 }
890
891 pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
892
893 fn select_video_mode_rec(
897 &self,
898 resolution: [u16; 2],
899 bit_depth: Option<u16>,
900 refresh_rate_millihertz: Option<u32>,
901 correct_res: Option<Vec<VideoModeHandle>>,
902 correct_depth: Option<Option<VideoModeHandle>>,
903 correct_rate: Option<Option<VideoModeHandle>>,
904 ) -> Option<VideoModeHandle> {
905 let correct_res = match correct_res {
909 Some(correct_res) => correct_res,
910 None => self
911 .window
912 .current_monitor()?
913 .video_modes()
914 .filter(|mode| mode.size().width == resolution[0] as u32)
915 .filter(|mode| mode.size().height == resolution[1] as u32)
916 .collect(),
917 };
918
919 match bit_depth {
920 Some(depth) => {
922 let correct_depth = correct_depth.unwrap_or_else(|| {
924 correct_res
925 .iter()
926 .find(|mode| mode.bit_depth() == depth)
927 .cloned()
928 });
929
930 match refresh_rate_millihertz {
931 Some(rate) => {
933 let correct_rate = correct_rate.unwrap_or_else(|| {
935 correct_res
936 .iter()
937 .find(|mode| mode.refresh_rate_millihertz() == rate)
938 .cloned()
939 });
940
941 correct_res
946 .iter()
947 .filter(|mode| mode.bit_depth() == depth)
948 .find(|mode| mode.refresh_rate_millihertz() == rate)
949 .cloned()
950 .or_else(|| {
951 if correct_depth.is_none() && correct_rate.is_none() {
952 warn!(
953 "Bit depth and refresh rate specified in settings are \
954 incompatible with the monitor. Choosing highest bit \
955 depth and refresh rate possible instead."
956 );
957 }
958
959 self.select_video_mode_rec(
960 resolution,
961 correct_depth.is_some().then_some(depth),
962 correct_rate.is_some().then_some(rate),
963 Some(correct_res),
964 Some(correct_depth),
965 Some(correct_rate),
966 )
967 })
968 },
969 None => match correct_depth {
973 Some(mode) => Some(mode),
974 None => {
975 warn!(
976 "Bit depth specified in settings is incompatible with the \
977 monitor. Choosing highest bit depth possible instead."
978 );
979
980 self.select_video_mode_rec(
981 resolution,
982 None,
983 None,
984 Some(correct_res),
985 Some(correct_depth),
986 None,
987 )
988 },
989 },
990 }
991 },
992 None => match refresh_rate_millihertz {
994 Some(rate) => {
996 let correct_rate = correct_rate.unwrap_or_else(|| {
998 correct_res
999 .iter()
1000 .find(|mode| mode.refresh_rate_millihertz() == rate)
1001 .cloned()
1002 });
1003
1004 match correct_rate {
1007 Some(mode) => Some(mode),
1008 None => {
1009 warn!(
1010 "Refresh rate specified in settings is incompatible with the \
1011 monitor. Choosing highest refresh rate possible instead."
1012 );
1013
1014 self.select_video_mode_rec(
1015 resolution,
1016 None,
1017 None,
1018 Some(correct_res),
1019 None,
1020 Some(correct_rate),
1021 )
1022 },
1023 }
1024 },
1025 None => correct_res
1029 .into_iter()
1030 .sorted_by_key(|mode| mode.bit_depth())
1032 .max_by_key(|mode| mode.refresh_rate_millihertz()),
1033 },
1034 }
1035 }
1036
1037 fn select_video_mode(
1038 &self,
1039 resolution: [u16; 2],
1040 bit_depth: Option<u16>,
1041 refresh_rate_millihertz: Option<u32>,
1042 ) -> Option<VideoModeHandle> {
1043 match self.select_video_mode_rec(
1053 resolution,
1054 bit_depth,
1055 refresh_rate_millihertz,
1056 None,
1057 None,
1058 None,
1059 ) {
1060 Some(mode) => Some(mode),
1061 None => {
1064 warn!(
1065 "Resolution specified in settings is incompatible with the monitor. Choosing \
1066 highest resolution possible instead."
1067 );
1068 if let Some(monitor) = self.window.current_monitor() {
1069 let mode = monitor
1070 .video_modes()
1071 .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1073 .sorted_by_key(|mode| mode.bit_depth())
1074 .max_by_key(|mode| mode.size().width);
1075
1076 if mode.is_none() {
1077 warn!("Failed to select video mode, no video modes available!!")
1078 }
1079
1080 mode
1081 } else {
1082 warn!("Failed to select video mode, can't get the current monitor!");
1083 None
1084 }
1085 },
1086 }
1087 }
1088
1089 pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1090 let window = &self.window;
1091 self.fullscreen = fullscreen;
1092 window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1093 FullscreenMode::Exclusive => {
1094 if let Some(video_mode) = self.select_video_mode(
1095 fullscreen.resolution,
1096 fullscreen.bit_depth,
1097 fullscreen.refresh_rate_millihertz,
1098 ) {
1099 winit::window::Fullscreen::Exclusive(video_mode)
1100 } else {
1101 warn!(
1102 "Failed to select a video mode for exclusive fullscreen. Falling back to \
1103 borderless fullscreen."
1104 );
1105 winit::window::Fullscreen::Borderless(None)
1106 }
1107 },
1108 FullscreenMode::Borderless => {
1109 winit::window::Fullscreen::Borderless(None)
1111 },
1112 }));
1113 }
1114
1115 pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1116
1117 pub fn set_size(&mut self, new_size: Vec2<u32>) {
1118 self.window
1119 .set_min_inner_size(Some(winit::dpi::LogicalSize::new(
1120 new_size.x as f64,
1121 new_size.y as f64,
1122 )));
1123 }
1124
1125 pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1126
1127 pub fn take_screenshot(&mut self, settings: &Settings) {
1128 let sender = self.message_sender.clone();
1129 let mut path = settings.screenshots_path.clone();
1130 self.renderer.create_screenshot(move |image| {
1131 use std::time::SystemTime;
1132
1133 let image = match image {
1135 Ok(i) => i,
1136 Err(e) => {
1137 warn!(?e, "Couldn't generate screenshot");
1138 let _result = sender.send(format!("Error when generating screenshot: {}", e));
1139 return;
1140 },
1141 };
1142
1143 if !path.exists() {
1145 if let Err(e) = std::fs::create_dir_all(&path) {
1146 warn!(?e, ?path, "Couldn't create folder for screenshot");
1147 let _result =
1148 sender.send(String::from("Couldn't create folder for screenshot"));
1149 }
1150 }
1151 path.push(format!(
1152 "screenshot_{}.png",
1153 SystemTime::now()
1154 .duration_since(SystemTime::UNIX_EPOCH)
1155 .map(|d| d.as_millis())
1156 .unwrap_or(0)
1157 ));
1158 if let Err(e) = image.save(&path) {
1160 warn!(?e, ?path, "Couldn't save screenshot");
1161 let _result = sender.send(String::from("Couldn't save screenshot"));
1162 } else {
1163 let _result =
1164 sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1165 }
1166 });
1167 }
1168
1169 fn is_pressed(
1170 map: &mut HashMap<GameInput, winit::event::ElementState>,
1171 input: GameInput,
1172 ) -> bool {
1173 *(map
1174 .entry(input)
1175 .or_insert(winit::event::ElementState::Released))
1176 == winit::event::ElementState::Pressed
1177 }
1178
1179 fn set_pressed(
1180 map: &mut HashMap<GameInput, winit::event::ElementState>,
1181 input: GameInput,
1182 state: winit::event::ElementState,
1183 ) {
1184 map.insert(input, state);
1185 }
1186
1187 fn map_input<'a>(
1192 key_mouse: KeyMouse,
1193 controls: &'a mut ControlSettings,
1194 remapping: &mut Option<GameInput>,
1195 ) -> Option<impl Iterator<Item = &'a GameInput> + use<'a>> {
1196 let key_mouse = key_mouse.into_upper();
1197
1198 match *remapping {
1199 Some(game_input) => {
1201 controls.modify_binding(game_input, key_mouse);
1202 *remapping = None;
1203 None
1204 },
1205 None => controls
1206 .get_associated_game_inputs(&key_mouse)
1207 .map(|game_inputs| game_inputs.iter()),
1208 }
1209 }
1210
1211 pub fn set_keybinding_mode(&mut self, game_input: GameInput) {
1212 self.remapping_keybindings = Some(game_input);
1213 }
1214
1215 pub fn window(&self) -> &winit::window::Window { &self.window }
1216
1217 pub fn modifiers(&self) -> winit::keyboard::ModifiersState { self.modifiers }
1218
1219 pub fn scale_factor(&self) -> f64 { self.scale_factor }
1220}
1221
1222#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1223pub enum FullscreenMode {
1224 Exclusive,
1225 #[serde(other)]
1226 #[default]
1227 Borderless,
1228}
1229
1230#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1231#[serde(default)]
1232pub struct WindowSettings {
1233 pub size: [u32; 2],
1234 pub maximised: bool,
1235}
1236
1237impl Default for WindowSettings {
1238 fn default() -> Self {
1239 Self {
1240 size: [1280, 720],
1241 maximised: false,
1242 }
1243 }
1244}
1245
1246#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1247#[serde(default)]
1248pub struct FullScreenSettings {
1249 pub enabled: bool,
1250 pub mode: FullscreenMode,
1251 pub resolution: [u16; 2],
1252 pub bit_depth: Option<u16>,
1253 pub refresh_rate_millihertz: Option<u32>,
1254}
1255
1256impl Default for FullScreenSettings {
1257 fn default() -> Self {
1258 Self {
1259 enabled: true,
1260 mode: FullscreenMode::Borderless,
1261 resolution: [1920, 1080],
1262 bit_depth: None,
1263 refresh_rate_millihertz: None,
1264 }
1265 }
1266}