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