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 keyboard_keynames::key_layout::KeyLayout;
15use serde::{Deserialize, Serialize};
16use tracing::{error, warn};
17use vek::*;
18use winit::monitor::VideoMode;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
22pub enum MenuInput {
23 Up,
24 Down,
25 Left,
26 Right,
27 ScrollUp,
28 ScrollDown,
29 ScrollLeft,
30 ScrollRight,
31 Home,
32 End,
33 Apply,
34 Back,
35 Exit,
36}
37
38#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
39pub enum AnalogMenuInput {
40 MoveX(f32),
41 MoveY(f32),
42 ScrollX(f32),
43 ScrollY(f32),
44}
45
46#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
47pub enum AnalogGameInput {
48 MovementX(f32),
49 MovementY(f32),
50 CameraX(f32),
51 CameraY(f32),
52}
53
54#[derive(Clone, Debug)]
56pub enum Event {
57 Close,
59 Resize(Vec2<u32>),
61 ScaleFactorChanged(f64),
63 Moved(Vec2<u32>),
65 Char(char),
67 CursorPan(Vec2<f32>),
69 CursorMove(Vec2<f32>),
71 MouseButton(MouseButton, PressState),
73 Zoom(f32),
75 InputUpdate(GameInput, bool),
77 Ui(ui::Event),
79 IcedUi(ui::ice::Event),
81 ViewDistanceChanged(u32),
83 SettingsChanged,
85 Focused(bool),
87 MenuInput(MenuInput, bool),
90 AnalogMenuInput(AnalogMenuInput),
92 AnalogGameInput(AnalogGameInput),
94 ScreenshotMessage(String),
96}
97
98pub type MouseButton = winit::event::MouseButton;
99pub type PressState = winit::event::ElementState;
100pub type EventLoop = winit::event_loop::EventLoop<()>;
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
103pub enum KeyMouse {
104 Key(winit::event::VirtualKeyCode),
105 Mouse(winit::event::MouseButton),
106 ScanKey(winit::event::ScanCode),
107}
108
109impl KeyMouse {
110 pub fn display_string(&self, key_layout: &Option<KeyLayout>) -> String {
112 use self::KeyMouse::*;
113 use winit::event::{MouseButton, VirtualKeyCode::*};
114 let key_string = match self {
115 Key(Key1) => "1",
116 Key(Key2) => "2",
117 Key(Key3) => "3",
118 Key(Key4) => "4",
119 Key(Key5) => "5",
120 Key(Key6) => "6",
121 Key(Key7) => "7",
122 Key(Key8) => "8",
123 Key(Key9) => "9",
124 Key(Key0) => "0",
125 Key(A) => "A",
126 Key(B) => "B",
127 Key(C) => "C",
128 Key(D) => "D",
129 Key(E) => "E",
130 Key(F) => "F",
131 Key(G) => "G",
132 Key(H) => "H",
133 Key(I) => "I",
134 Key(J) => "J",
135 Key(K) => "K",
136 Key(L) => "L",
137 Key(M) => "M",
138 Key(N) => "N",
139 Key(O) => "O",
140 Key(P) => "P",
141 Key(Q) => "Q",
142 Key(R) => "R",
143 Key(S) => "S",
144 Key(T) => "T",
145 Key(U) => "U",
146 Key(V) => "V",
147 Key(W) => "W",
148 Key(X) => "X",
149 Key(Y) => "Y",
150 Key(Z) => "Z",
151 Key(Escape) => "ESC",
152 Key(F1) => "F1",
153 Key(F2) => "F2",
154 Key(F3) => "F3",
155 Key(F4) => "F4",
156 Key(F5) => "F5",
157 Key(F6) => "F6",
158 Key(F7) => "F7",
159 Key(F8) => "F8",
160 Key(F9) => "F9",
161 Key(F10) => "F10",
162 Key(F11) => "F11",
163 Key(F12) => "F12",
164 Key(F13) => "F13",
165 Key(F14) => "F14",
166 Key(F15) => "F15",
167 Key(F16) => "F16",
168 Key(F17) => "F17",
169 Key(F18) => "F18",
170 Key(F19) => "F19",
171 Key(F20) => "F20",
172 Key(F21) => "F21",
173 Key(F22) => "F22",
174 Key(F23) => "F23",
175 Key(F24) => "F24",
176 Key(Snapshot) => "Print Screen",
177 Key(Scroll) => "Scroll Lock",
178 Key(Pause) => "Pause/Break",
179 Key(Insert) => "Insert",
180 Key(Home) => "Home",
181 Key(Delete) => "Delete",
182 Key(End) => "End",
183 Key(PageDown) => "PageDown",
184 Key(PageUp) => "PageUp",
185 Key(Left) => "Left Arrow",
186 Key(Up) => "Up Arrow",
187 Key(Right) => "Right Arrow",
188 Key(Down) => "Down Arrow",
189 Key(Back) => "Backspace",
190 Key(Return) => "Enter",
191 Key(Space) => "Space",
192 Key(Compose) => "Compose",
193 Key(Caret) => "^",
194 Key(Numlock) => "Numlock",
195 Key(Numpad0) => "Numpad 0",
196 Key(Numpad1) => "Numpad 1",
197 Key(Numpad2) => "Numpad 2",
198 Key(Numpad3) => "Numpad 3",
199 Key(Numpad4) => "Numpad 4",
200 Key(Numpad5) => "Numpad 5",
201 Key(Numpad6) => "Numpad 6",
202 Key(Numpad7) => "Numpad 7",
203 Key(Numpad8) => "Numpad 8",
204 Key(Numpad9) => "Numpad 9",
205 Key(AbntC1) => "Abnt C1",
206 Key(AbntC2) => "Abnt C2",
207 Key(NumpadAdd) => "Numpad +",
208 Key(Apostrophe) => "'",
209 Key(Apps) => "Context Menu",
210 Key(At) => "@",
211 Key(Ax) => "Ax",
212 Key(Backslash) => "\\",
213 Key(Calculator) => "Calculator",
214 Key(Capital) => "Caps Lock",
215 Key(Colon) => ":",
216 Key(Comma) => ",",
217 Key(Convert) => "Convert",
218 Key(NumpadDecimal) => "Numpad .",
219 Key(NumpadDivide) => "Numpad /",
220 Key(Equals) => "=",
221 Key(Grave) => "`",
222 Key(Kana) => "Kana",
223 Key(Kanji) => "Kanji",
224 Key(LBracket) => "[",
225 Key(RBracket) => "]",
226 Key(Mail) => "Mail",
227 Key(MediaSelect) => "MediaSelect",
228 Key(MediaStop) => "MediaStop",
229 Key(Minus) => "-",
230 Key(Plus) => "+",
231 Key(NumpadMultiply) => "Numpad *",
232 Key(Mute) => "Mute",
233 Key(MyComputer) => "My Computer",
234 Key(NavigateBackward) => "Navigate Backward",
235 Key(NavigateForward) => "Navigate Forward",
236 Key(NoConvert) => "Non Convert",
237 Key(NumpadComma) => "Num ,",
238 Key(NumpadEnter) => "Num Enter",
239 Key(NumpadEquals) => "Num =",
240 Key(OEM102) => "<",
241 Key(Period) => ".",
242 Key(Power) => "Power",
243 Key(PlayPause) => "Play / Pause",
244 Key(PrevTrack) => "Prev Track",
245 Key(NextTrack) => "Next Track",
246 Key(LAlt) => {
247 if cfg!(target_os = "macos") {
248 "Left Option ⌥"
249 } else {
250 "Left Alt"
252 }
253 },
254 Key(RAlt) => {
255 if cfg!(target_os = "macos") {
256 "Right Option ⌥"
257 } else {
258 "Right Alt"
260 }
261 },
262 Key(LControl) => {
263 if cfg!(target_os = "macos") {
264 "Left Cmd ⌘"
265 } else {
266 "Left Ctrl"
268 }
269 },
270 Key(RControl) => {
271 if cfg!(target_os = "macos") {
272 "Right Cmd ⌘"
273 } else {
274 "Right Ctrl"
276 }
277 },
278 Key(LShift) => "Left Shift",
279 Key(RShift) => "Right Shift",
280 Key(LWin) => {
284 if cfg!(target_family = "windows") {
285 "Win ⊞"
286 } else if cfg!(target_os = "macos") {
287 "Left Cmd ⌘ (Super)" } else {
289 "Super"
291 }
292 },
293 Key(RWin) => {
295 if cfg!(target_family = "windows") {
296 "Right Win ⊞"
297 } else if cfg!(target_os = "macos") {
298 "Right Cmd ⌘ (Super)" } else {
300 "Right Super"
302 }
303 },
304 Key(Semicolon) => ";",
305 Key(Slash) => "/",
306 Key(Sleep) => "Sleep",
307 Key(Stop) => "Media Stop",
308 Key(NumpadSubtract) => "Num -",
309 Key(Sysrq) => "Sysrq",
310 Key(Tab) => "Tab",
311 Key(Underline) => "_",
312 Key(Unlabeled) => "No Name",
313 Key(VolumeDown) => "Volume Down",
314 Key(VolumeUp) => "Volume Up",
315 Key(Wake) => "Wake",
316 Key(WebBack) => "Browser Back",
317 Key(WebFavorites) => "Browser Favorites",
318 Key(WebForward) => "Browser Forward",
319 Key(WebHome) => "Browser Home",
320 Key(WebRefresh) => "Browser Refresh",
321 Key(WebSearch) => "Browser Search",
322 Key(WebStop) => "Browser Stop",
323 Key(Yen) => "Yen",
324 Key(Copy) => "Copy",
325 Key(Paste) => "Paste",
326 Key(Cut) => "Cut",
327 Key(Asterisk) => "*",
328 Mouse(MouseButton::Left) => "Left Click",
329 Mouse(MouseButton::Right) => "Right Click",
330 Mouse(MouseButton::Middle) => "Middle Click",
331 Mouse(MouseButton::Other(button)) => {
332 return format!("Mouse {}", button + 3);
334 },
335 ScanKey(scancode) => {
336 return if let Some(layout) = key_layout {
337 layout.get_key_as_string(*scancode)
338 } else {
339 format!("Unknown (0x{:X})", scancode)
340 };
341 },
342 };
343
344 key_string.to_owned()
345 }
346
347 pub fn try_shortened(&self, _key_layout: &Option<KeyLayout>) -> Option<String> {
350 use self::KeyMouse::*;
351 use winit::event::{MouseButton, VirtualKeyCode::*};
352 let key_string = match self {
353 Mouse(MouseButton::Left) => "M1",
354 Mouse(MouseButton::Right) => "M2",
355 Mouse(MouseButton::Middle) => "M3",
356 Mouse(MouseButton::Other(button)) => {
357 return Some(format!("M{}", button + 3));
359 },
360 Key(Back) => "Back",
361 Key(LShift) => "LShft",
362 Key(RShift) => "RShft",
363 _ => return None,
364 };
365
366 Some(key_string.to_owned())
367 }
368
369 pub fn display_shortest(&self, key_layout: &Option<KeyLayout>) -> String {
374 self.try_shortened(key_layout)
375 .unwrap_or_else(|| self.display_string(key_layout))
376 }
377}
378
379pub struct Window {
380 renderer: Renderer,
381 window: winit::window::Window,
382 cursor_grabbed: bool,
383 pub pan_sensitivity: u32,
384 pub zoom_sensitivity: u32,
385 pub zoom_inversion: bool,
386 pub mouse_y_inversion: bool,
387 fullscreen: FullScreenSettings,
388 modifiers: winit::event::ModifiersState,
389 resized: bool,
392 scale_factor: f64,
393 needs_refresh_resize: bool,
394 keypress_map: HashMap<GameInput, winit::event::ElementState>,
395 pub remapping_keybindings: Option<GameInput>,
396 pub keybinding_mode: bool,
398 events: Vec<Event>,
399 pub focused: bool,
400 gilrs: Option<Gilrs>,
401 pub controller_settings: ControllerSettings,
402 pub controller_modifiers: Vec<Button>,
403 cursor_position: winit::dpi::PhysicalPosition<f64>,
404 mouse_emulation_vec: Vec2<f32>,
405 message_sender: channel::Sender<String>,
407 message_receiver: channel::Receiver<String>,
408 take_screenshot: bool,
410 toggle_fullscreen: bool,
411 pub key_layout: Option<KeyLayout>,
412}
413
414impl Window {
415 pub fn new(
416 settings: &Settings,
417 runtime: &tokio::runtime::Runtime,
418 ) -> Result<(Window, EventLoop), Error> {
419 let event_loop = EventLoop::new();
420
421 let window = settings.graphics.window;
422
423 let win_builder = winit::window::WindowBuilder::new()
424 .with_title("Veloren")
425 .with_inner_size(winit::dpi::LogicalSize::new(
426 window.size[0] as f64,
427 window.size[1] as f64,
428 ))
429 .with_maximized(window.maximised);
430
431 #[cfg(target_family = "windows")]
434 let win_builder = winit::platform::windows::WindowBuilderExtWindows::with_drag_and_drop(
435 win_builder,
436 false,
437 );
438
439 let window = win_builder.build(&event_loop).unwrap();
440
441 let renderer = Renderer::new(&window, settings.graphics.render_mode.clone(), runtime)?;
442
443 let keypress_map = HashMap::new();
444
445 let gilrs = match Gilrs::new() {
446 Ok(gilrs) => Some(gilrs),
447 Err(gilrs::Error::NotImplemented(_dummy)) => {
448 warn!("Controller input is unsupported on this platform.");
449 None
450 },
451 Err(gilrs::Error::InvalidAxisToBtn) => {
452 error!(
453 "Invalid AxisToBtn controller mapping. Falling back to no controller support."
454 );
455 None
456 },
457 Err(gilrs::Error::Other(e)) => {
458 error!(
459 ?e,
460 "Platform-specific error when creating a Gilrs instance. Falling back to no \
461 controller support."
462 );
463 None
464 },
465 Err(e) => {
466 error!(
467 ?e,
468 "Unspecified error when creating a Gilrs instance. Falling back to no \
469 controller support."
470 );
471 None
472 },
473 };
474
475 let controller_settings = ControllerSettings::from(&settings.controller);
476
477 let (message_sender, message_receiver): (
478 channel::Sender<String>,
479 channel::Receiver<String>,
480 ) = channel::unbounded::<String>();
481
482 let scale_factor = window.scale_factor();
483
484 let key_layout = match KeyLayout::new_from_window(&window) {
485 Ok(kl) => Some(kl),
486 Err(err) => {
487 warn!(
488 ?err,
489 "Failed to construct the scancode to keyname mapper, falling back to \
490 displaying Unknown(<scancode>)."
491 );
492 None
493 },
494 };
495
496 let mut this = Self {
497 renderer,
498 window,
499 cursor_grabbed: false,
500 pan_sensitivity: settings.gameplay.pan_sensitivity,
501 zoom_sensitivity: settings.gameplay.zoom_sensitivity,
502 zoom_inversion: settings.gameplay.zoom_inversion,
503 mouse_y_inversion: settings.gameplay.mouse_y_inversion,
504 fullscreen: FullScreenSettings::default(),
505 modifiers: Default::default(),
506 scale_factor,
507 resized: false,
508 needs_refresh_resize: false,
509 keypress_map,
510 remapping_keybindings: None,
511 keybinding_mode: true,
512 events: Vec::new(),
513 focused: true,
514 gilrs,
515 controller_settings,
516 controller_modifiers: Vec::new(),
517 cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
518 mouse_emulation_vec: Vec2::zero(),
519 message_sender,
521 message_receiver,
522 take_screenshot: false,
523 toggle_fullscreen: false,
524 key_layout,
525 };
526
527 this.set_fullscreen_mode(settings.graphics.fullscreen);
528
529 Ok((this, event_loop))
530 }
531
532 pub fn renderer(&self) -> &Renderer { &self.renderer }
533
534 pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
535
536 pub fn resolve_deduplicated_events(
537 &mut self,
538 settings: &mut Settings,
539 config_dir: &std::path::Path,
540 ) {
541 if self.take_screenshot {
543 self.take_screenshot = false;
544 self.take_screenshot(settings);
545 }
546 if self.toggle_fullscreen {
547 self.toggle_fullscreen = false;
548 self.toggle_fullscreen(settings, config_dir);
549 }
550 }
551
552 #[expect(clippy::get_first)]
553 pub fn fetch_events(&mut self) -> Vec<Event> {
554 span!(_guard, "fetch_events", "Window::fetch_events");
555 if self.needs_refresh_resize {
557 let logical_size = self.logical_size();
558 self.events
559 .push(Event::Ui(ui::Event::new_resize(logical_size)));
560 self.events.push(Event::IcedUi(iced::Event::Window(
561 iced::window::Event::Resized {
562 width: logical_size.x as u32,
563 height: logical_size.y as u32,
564 },
565 )));
566 self.events
567 .push(Event::ScaleFactorChanged(self.scale_factor));
568 self.needs_refresh_resize = false;
569 }
570
571 if self.resized {
573 self.resized = false;
574 let physical = self.window.inner_size();
578
579 self.renderer
580 .on_resize(Vec2::new(physical.width, physical.height));
581 let winit::dpi::PhysicalSize { width, height } = physical;
584 self.events.push(Event::Resize(Vec2::new(width, height)));
585
586 let logical_size = Vec2::from(Into::<(f64, f64)>::into(
588 physical.to_logical::<f64>(self.window.scale_factor()),
589 ));
590 self.events
591 .push(Event::Ui(ui::Event::new_resize(logical_size)));
592 self.events.push(Event::IcedUi(iced::Event::Window(
593 iced::window::Event::Resized {
594 width: logical_size.x as u32,
595 height: logical_size.y as u32,
596 },
597 )));
598 }
599
600 for message in self.message_receiver.try_iter() {
602 self.events.push(Event::ScreenshotMessage(message))
603 }
604
605 if let Some(gilrs) = &mut self.gilrs {
606 while let Some(event) = gilrs.next_event() {
607 fn handle_buttons(
608 settings: &ControllerSettings,
609 modifiers: &mut Vec<Button>,
610 events: &mut Vec<Event>,
611 button: &Button,
612 is_pressed: bool,
613 ) {
614 if settings.modifier_buttons.contains(button) {
615 if is_pressed {
616 modifiers.push(*button);
617 } else if let Some(index) =
623 modifiers.iter().position(|modifier| modifier == button)
624 {
625 modifiers.remove(index);
626 }
627 }
628
629 let l_entry1 = LayerEntry {
631 button: *button,
632 mod1: modifiers.get(0).copied().unwrap_or_default(),
633 mod2: modifiers.get(1).copied().unwrap_or_default(),
634 };
635 let l_entry2 = LayerEntry {
636 button: *button,
637 mod1: modifiers.get(1).copied().unwrap_or_default(),
638 mod2: modifiers.get(0).copied().unwrap_or_default(),
639 };
640
641 if let Some(evs) = settings.layer_button_map.get(&l_entry1) {
644 for ev in evs {
645 events.push(Event::InputUpdate(*ev, is_pressed));
646 }
647 } else if let Some(evs) = settings.layer_button_map.get(&l_entry2) {
648 for ev in evs {
649 events.push(Event::InputUpdate(*ev, is_pressed));
650 }
651 }
652 if let Some(evs) = settings.game_button_map.get(button) {
653 for ev in evs {
654 events.push(Event::InputUpdate(*ev, is_pressed));
655 }
656 }
657 if let Some(evs) = settings.menu_button_map.get(button) {
658 for ev in evs {
659 events.push(Event::MenuInput(*ev, is_pressed));
660 }
661 }
662 }
663
664 match event.event {
665 EventType::ButtonPressed(button, code)
666 | EventType::ButtonRepeated(button, code) => {
667 handle_buttons(
668 &self.controller_settings,
669 &mut self.controller_modifiers,
670 &mut self.events,
671 &Button::from((button, code)),
672 true,
673 );
674 },
675 EventType::ButtonReleased(button, code) => {
676 handle_buttons(
677 &self.controller_settings,
678 &mut self.controller_modifiers,
679 &mut self.events,
680 &Button::from((button, code)),
681 false,
682 );
683 },
684 EventType::ButtonChanged(button, _value, code) => {
685 if let Some(actions) = self
686 .controller_settings
687 .game_analog_button_map
688 .get(&AnalogButton::from((button, code)))
689 {
690 #[expect(clippy::never_loop)]
691 for action in actions {
692 match *action {}
693 }
694 }
695 if let Some(actions) = self
696 .controller_settings
697 .menu_analog_button_map
698 .get(&AnalogButton::from((button, code)))
699 {
700 #[expect(clippy::never_loop)]
701 for action in actions {
702 match *action {}
703 }
704 }
705 },
706
707 EventType::AxisChanged(axis, value, code) => {
708 let value = if self
709 .controller_settings
710 .inverted_axes
711 .contains(&Axis::from((axis, code)))
712 {
713 -value
714 } else {
715 value
716 };
717
718 let value = self
719 .controller_settings
720 .apply_axis_deadzone(&Axis::from((axis, code)), value);
721
722 if self.cursor_grabbed {
723 if let Some(actions) = self
724 .controller_settings
725 .game_axis_map
726 .get(&Axis::from((axis, code)))
727 {
728 for action in actions {
729 match *action {
730 AxisGameAction::MovementX => {
731 self.events.push(Event::AnalogGameInput(
732 AnalogGameInput::MovementX(value),
733 ));
734 },
735 AxisGameAction::MovementY => {
736 self.events.push(Event::AnalogGameInput(
737 AnalogGameInput::MovementY(value),
738 ));
739 },
740 AxisGameAction::CameraX => {
741 self.events.push(Event::AnalogGameInput(
742 AnalogGameInput::CameraX(
743 value
744 * self.controller_settings.pan_sensitivity
745 as f32
746 / 100.0,
747 ),
748 ));
749 },
750 AxisGameAction::CameraY => {
751 let pan_invert_y =
752 match self.controller_settings.pan_invert_y {
753 true => -1.0,
754 false => 1.0,
755 };
756
757 self.events.push(Event::AnalogGameInput(
758 AnalogGameInput::CameraY(
759 -value
760 * self.controller_settings.pan_sensitivity
761 as f32
762 * pan_invert_y
763 / 100.0,
764 ),
765 ));
766 },
767 }
768 }
769 }
770 } else if let Some(actions) = self
771 .controller_settings
772 .menu_axis_map
773 .get(&Axis::from((axis, code)))
774 {
775 for action in actions {
777 match *action {
778 AxisMenuAction::MoveX => {
779 self.events.push(Event::AnalogMenuInput(
780 AnalogMenuInput::MoveX(value),
781 ));
782 },
783 AxisMenuAction::MoveY => {
784 self.events.push(Event::AnalogMenuInput(
785 AnalogMenuInput::MoveY(value),
786 ));
787 },
788 AxisMenuAction::ScrollX => {
789 self.events.push(Event::AnalogMenuInput(
790 AnalogMenuInput::ScrollX(value),
791 ));
792 },
793 AxisMenuAction::ScrollY => {
794 self.events.push(Event::AnalogMenuInput(
795 AnalogMenuInput::ScrollY(value),
796 ));
797 },
798 }
799 }
800 }
801 },
802 _ => {},
803 }
804 }
805 }
806
807 let mut events = std::mem::take(&mut self.events);
808 if !self.cursor_grabbed {
811 events = events
812 .into_iter()
813 .filter_map(|event| match event {
814 Event::AnalogMenuInput(input) => match input {
815 AnalogMenuInput::MoveX(d) => {
816 self.mouse_emulation_vec.x = d;
817 None
818 },
819 AnalogMenuInput::MoveY(d) => {
820 self.mouse_emulation_vec.y = d * -1.0;
822 None
823 },
824 input => Some(Event::AnalogMenuInput(input)),
825 },
826 Event::MenuInput(MenuInput::Apply, state) => Some(match state {
827 true => Event::Ui(ui::Event(conrod_core::event::Input::Press(
828 conrod_core::input::Button::Mouse(
829 conrod_core::input::state::mouse::Button::Left,
830 ),
831 ))),
832 false => Event::Ui(ui::Event(conrod_core::event::Input::Release(
833 conrod_core::input::Button::Mouse(
834 conrod_core::input::state::mouse::Button::Left,
835 ),
836 ))),
837 }),
838 _ => Some(event),
839 })
840 .collect();
841
842 let sensitivity = self.controller_settings.mouse_emulation_sensitivity;
843 self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
846 }
847
848 events
849 }
850
851 pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
852 use winit::event::DeviceEvent;
853
854 let mouse_y_inversion = match self.mouse_y_inversion {
855 true => -1.0,
856 false => 1.0,
857 };
858
859 match event {
860 DeviceEvent::MouseMotion {
861 delta: (dx, dy), ..
862 } if self.focused => {
863 let delta = Vec2::new(
864 dx as f32 * (self.pan_sensitivity as f32 / 100.0),
865 dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
866 );
867
868 if self.cursor_grabbed {
869 self.events.push(Event::CursorPan(delta));
870 } else {
871 self.events.push(Event::CursorMove(delta));
872 }
873 },
874 _ => {},
875 }
876 }
877
878 pub fn handle_window_event(
879 &mut self,
880 event: winit::event::WindowEvent,
881 settings: &mut Settings,
882 ) {
883 use winit::event::WindowEvent;
884
885 let controls = &mut settings.controls;
886
887 match event {
888 WindowEvent::CloseRequested => self.events.push(Event::Close),
889 WindowEvent::Resized(_) => {
890 self.resized = true;
891 },
892 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
893 self.scale_factor = scale_factor;
895 self.events.push(Event::ScaleFactorChanged(scale_factor));
896 },
897 WindowEvent::Moved(winit::dpi::PhysicalPosition { x, y }) => {
898 self.events
899 .push(Event::Moved(Vec2::new(x as u32, y as u32)));
900 },
901 WindowEvent::ReceivedCharacter(c) => self.events.push(Event::Char(c)),
902 WindowEvent::MouseInput { button, state, .. } => {
903 if let (true, Some(game_inputs)) =
904 (
906 self.cursor_grabbed,
907 Window::map_input(
908 KeyMouse::Mouse(button),
909 controls,
910 &mut self.remapping_keybindings,
911 ),
912 )
913 {
914 for game_input in game_inputs {
915 self.events.push(Event::InputUpdate(
916 *game_input,
917 state == winit::event::ElementState::Pressed,
918 ));
919 }
920 }
921 self.events.push(Event::MouseButton(button, state));
922 },
923 WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
924 WindowEvent::KeyboardInput {
925 input,
926 is_synthetic,
927 ..
928 } => {
929 if matches!(
932 input.virtual_keycode,
933 Some(winit::event::VirtualKeyCode::Tab)
934 ) && is_synthetic
935 {
936 return;
937 }
938 if matches!(input, winit::event::KeyboardInput {
941 state: winit::event::ElementState::Pressed,
942 virtual_keycode: Some(winit::event::VirtualKeyCode::F4),
943 ..
944 }) && self.modifiers.alt()
945 {
946 return;
947 }
948
949 let input_key = match input.virtual_keycode {
950 Some(key) => KeyMouse::Key(key),
951 None => KeyMouse::ScanKey(input.scancode),
952 };
953
954 if let Some(game_inputs) =
955 Window::map_input(input_key, controls, &mut self.remapping_keybindings)
956 {
957 for game_input in game_inputs {
958 match game_input {
959 GameInput::Fullscreen => {
960 if input.state == winit::event::ElementState::Pressed
961 && !Self::is_pressed(
962 &mut self.keypress_map,
963 GameInput::Fullscreen,
964 )
965 {
966 self.toggle_fullscreen = !self.toggle_fullscreen;
967 }
968 Self::set_pressed(
969 &mut self.keypress_map,
970 GameInput::Fullscreen,
971 input.state,
972 );
973 },
974 GameInput::Screenshot => {
975 self.take_screenshot = input.state
976 == winit::event::ElementState::Pressed
977 && !Self::is_pressed(
978 &mut self.keypress_map,
979 GameInput::Screenshot,
980 );
981 Self::set_pressed(
982 &mut self.keypress_map,
983 GameInput::Screenshot,
984 input.state,
985 );
986 },
987 _ => self.events.push(Event::InputUpdate(
988 *game_input,
989 input.state == winit::event::ElementState::Pressed,
990 )),
991 }
992 }
993 }
994 },
995 WindowEvent::Focused(state) => {
996 self.focused = state;
997 self.events.push(Event::Focused(state));
998 },
999 WindowEvent::CursorMoved { position, .. } => {
1000 if self.cursor_grabbed {
1001 self.reset_cursor_position();
1002 } else {
1003 self.cursor_position = position;
1004 }
1005 },
1006 WindowEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
1007 const DIFFERENCE_FROM_DEVICE_EVENT_ON_X11: f32 = -15.0;
1008 self.events.push(Event::Zoom({
1009 let y = match delta {
1010 winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
1011 winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
1017 };
1018 y * (self.zoom_sensitivity as f32 / 100.0)
1019 * if self.zoom_inversion { -1.0 } else { 1.0 }
1020 * DIFFERENCE_FROM_DEVICE_EVENT_ON_X11
1021 }))
1022 },
1023 _ => {},
1024 }
1025 }
1026
1027 pub fn offset_cursor(&self, d: Vec2<f32>) {
1029 if d != Vec2::zero() {
1030 if let Err(err) = self
1031 .window
1032 .set_cursor_position(winit::dpi::LogicalPosition::new(
1033 d.x as f64 + self.cursor_position.x,
1034 d.y as f64 + self.cursor_position.y,
1035 ))
1036 {
1037 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
1039 SPAM_GUARD.call_once(|| {
1040 error!("Error setting cursor position: {:?}", err);
1041 })
1042 }
1043 }
1044 }
1045
1046 pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed }
1047
1048 pub fn grab_cursor(&mut self, grab: bool) {
1049 use winit::window::CursorGrabMode;
1050
1051 self.cursor_grabbed = grab;
1052 self.window.set_cursor_visible(!grab);
1053 let res = if grab {
1054 self.window
1055 .set_cursor_grab(CursorGrabMode::Locked)
1056 .or_else(|_e| self.window.set_cursor_grab(CursorGrabMode::Confined))
1057 } else {
1058 self.window.set_cursor_grab(CursorGrabMode::None)
1059 };
1060
1061 if let Err(e) = res {
1062 error!(?e, ?grab, "Failed to toggle cursor grab");
1063 }
1064 }
1065
1066 fn reset_cursor_position(&self) {
1070 if let Err(err) = self.window.set_cursor_position(self.cursor_position) {
1071 static SPAM_GUARD: std::sync::Once = std::sync::Once::new();
1073 SPAM_GUARD.call_once(|| {
1074 error!("Error resetting cursor position: {:?}", err);
1075 })
1076 }
1077 }
1078
1079 pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) {
1080 let fullscreen = FullScreenSettings {
1081 enabled: !self.is_fullscreen(),
1082 ..settings.graphics.fullscreen
1083 };
1084
1085 self.set_fullscreen_mode(fullscreen);
1086 settings.graphics.fullscreen = fullscreen;
1087 settings.save_to_file_warn(config_dir);
1088 }
1089
1090 pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }
1091
1092 fn select_video_mode_rec(
1096 &self,
1097 resolution: [u16; 2],
1098 bit_depth: Option<u16>,
1099 refresh_rate_millihertz: Option<u32>,
1100 correct_res: Option<Vec<VideoMode>>,
1101 correct_depth: Option<Option<VideoMode>>,
1102 correct_rate: Option<Option<VideoMode>>,
1103 ) -> Option<VideoMode> {
1104 let correct_res = match correct_res {
1108 Some(correct_res) => correct_res,
1109 None => self
1110 .window
1111 .current_monitor()?
1112 .video_modes()
1113 .filter(|mode| mode.size().width == resolution[0] as u32)
1114 .filter(|mode| mode.size().height == resolution[1] as u32)
1115 .collect(),
1116 };
1117
1118 match bit_depth {
1119 Some(depth) => {
1121 let correct_depth = correct_depth.unwrap_or_else(|| {
1123 correct_res
1124 .iter()
1125 .find(|mode| mode.bit_depth() == depth)
1126 .cloned()
1127 });
1128
1129 match refresh_rate_millihertz {
1130 Some(rate) => {
1132 let correct_rate = correct_rate.unwrap_or_else(|| {
1134 correct_res
1135 .iter()
1136 .find(|mode| mode.refresh_rate_millihertz() == rate)
1137 .cloned()
1138 });
1139
1140 correct_res
1145 .iter()
1146 .filter(|mode| mode.bit_depth() == depth)
1147 .find(|mode| mode.refresh_rate_millihertz() == rate)
1148 .cloned()
1149 .or_else(|| {
1150 if correct_depth.is_none() && correct_rate.is_none() {
1151 warn!(
1152 "Bit depth and refresh rate specified in settings are \
1153 incompatible with the monitor. Choosing highest bit \
1154 depth and refresh rate possible instead."
1155 );
1156 }
1157
1158 self.select_video_mode_rec(
1159 resolution,
1160 correct_depth.is_some().then_some(depth),
1161 correct_rate.is_some().then_some(rate),
1162 Some(correct_res),
1163 Some(correct_depth),
1164 Some(correct_rate),
1165 )
1166 })
1167 },
1168 None => match correct_depth {
1172 Some(mode) => Some(mode),
1173 None => {
1174 warn!(
1175 "Bit depth specified in settings is incompatible with the \
1176 monitor. Choosing highest bit depth possible instead."
1177 );
1178
1179 self.select_video_mode_rec(
1180 resolution,
1181 None,
1182 None,
1183 Some(correct_res),
1184 Some(correct_depth),
1185 None,
1186 )
1187 },
1188 },
1189 }
1190 },
1191 None => match refresh_rate_millihertz {
1193 Some(rate) => {
1195 let correct_rate = correct_rate.unwrap_or_else(|| {
1197 correct_res
1198 .iter()
1199 .find(|mode| mode.refresh_rate_millihertz() == rate)
1200 .cloned()
1201 });
1202
1203 match correct_rate {
1206 Some(mode) => Some(mode),
1207 None => {
1208 warn!(
1209 "Refresh rate specified in settings is incompatible with the \
1210 monitor. Choosing highest refresh rate possible instead."
1211 );
1212
1213 self.select_video_mode_rec(
1214 resolution,
1215 None,
1216 None,
1217 Some(correct_res),
1218 None,
1219 Some(correct_rate),
1220 )
1221 },
1222 }
1223 },
1224 None => correct_res
1228 .into_iter()
1229 .sorted_by_key(|mode| mode.bit_depth())
1231 .max_by_key(|mode| mode.refresh_rate_millihertz()),
1232 },
1233 }
1234 }
1235
1236 fn select_video_mode(
1237 &self,
1238 resolution: [u16; 2],
1239 bit_depth: Option<u16>,
1240 refresh_rate_millihertz: Option<u32>,
1241 ) -> Option<VideoMode> {
1242 match self.select_video_mode_rec(
1252 resolution,
1253 bit_depth,
1254 refresh_rate_millihertz,
1255 None,
1256 None,
1257 None,
1258 ) {
1259 Some(mode) => Some(mode),
1260 None => {
1263 warn!(
1264 "Resolution specified in settings is incompatible with the monitor. Choosing \
1265 highest resolution possible instead."
1266 );
1267 if let Some(monitor) = self.window.current_monitor() {
1268 let mode = monitor
1269 .video_modes()
1270 .sorted_by_key(|mode| mode.refresh_rate_millihertz())
1272 .sorted_by_key(|mode| mode.bit_depth())
1273 .max_by_key(|mode| mode.size().width);
1274
1275 if mode.is_none() {
1276 warn!("Failed to select video mode, no video modes available!!")
1277 }
1278
1279 mode
1280 } else {
1281 warn!("Failed to select video mode, can't get the current monitor!");
1282 None
1283 }
1284 },
1285 }
1286 }
1287
1288 pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) {
1289 let window = &self.window;
1290 self.fullscreen = fullscreen;
1291 window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode {
1292 FullscreenMode::Exclusive => {
1293 if let Some(video_mode) = self.select_video_mode(
1294 fullscreen.resolution,
1295 fullscreen.bit_depth,
1296 fullscreen.refresh_rate_millihertz,
1297 ) {
1298 winit::window::Fullscreen::Exclusive(video_mode)
1299 } else {
1300 warn!(
1301 "Failed to select a video mode for exclusive fullscreen. Falling back to \
1302 borderless fullscreen."
1303 );
1304 winit::window::Fullscreen::Borderless(None)
1305 }
1306 },
1307 FullscreenMode::Borderless => {
1308 winit::window::Fullscreen::Borderless(None)
1310 },
1311 }));
1312 }
1313
1314 pub fn needs_refresh_resize(&mut self) { self.needs_refresh_resize = true; }
1315
1316 pub fn logical_size(&self) -> Vec2<f64> {
1317 let (w, h) = self
1318 .window
1319 .inner_size()
1320 .to_logical::<f64>(self.window.scale_factor())
1321 .into();
1322 Vec2::new(w, h)
1323 }
1324
1325 pub fn set_size(&mut self, new_size: Vec2<u16>) {
1326 self.window.set_inner_size(winit::dpi::LogicalSize::new(
1327 new_size.x as f64,
1328 new_size.y as f64,
1329 ));
1330 }
1331
1332 pub fn send_event(&mut self, event: Event) { self.events.push(event) }
1333
1334 pub fn take_screenshot(&mut self, settings: &Settings) {
1335 let sender = self.message_sender.clone();
1336 let mut path = settings.screenshots_path.clone();
1337 self.renderer.create_screenshot(move |image| {
1338 use std::time::SystemTime;
1339
1340 let image = match image {
1342 Ok(i) => i,
1343 Err(e) => {
1344 warn!(?e, "Couldn't generate screenshot");
1345 let _result = sender.send(format!("Error when generating screenshot: {}", e));
1346 return;
1347 },
1348 };
1349
1350 if !path.exists() {
1352 if let Err(e) = std::fs::create_dir_all(&path) {
1353 warn!(?e, ?path, "Couldn't create folder for screenshot");
1354 let _result =
1355 sender.send(String::from("Couldn't create folder for screenshot"));
1356 }
1357 }
1358 path.push(format!(
1359 "screenshot_{}.png",
1360 SystemTime::now()
1361 .duration_since(SystemTime::UNIX_EPOCH)
1362 .map(|d| d.as_millis())
1363 .unwrap_or(0)
1364 ));
1365 if let Err(e) = image.save(&path) {
1367 warn!(?e, ?path, "Couldn't save screenshot");
1368 let _result = sender.send(String::from("Couldn't save screenshot"));
1369 } else {
1370 let _result =
1371 sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
1372 }
1373 });
1374 }
1375
1376 fn is_pressed(
1377 map: &mut HashMap<GameInput, winit::event::ElementState>,
1378 input: GameInput,
1379 ) -> bool {
1380 *(map
1381 .entry(input)
1382 .or_insert(winit::event::ElementState::Released))
1383 == winit::event::ElementState::Pressed
1384 }
1385
1386 fn set_pressed(
1387 map: &mut HashMap<GameInput, winit::event::ElementState>,
1388 input: GameInput,
1389 state: winit::event::ElementState,
1390 ) {
1391 map.insert(input, state);
1392 }
1393
1394 fn map_input<'a>(
1399 key_mouse: KeyMouse,
1400 controls: &'a mut ControlSettings,
1401 remapping: &mut Option<GameInput>,
1402 ) -> Option<impl Iterator<Item = &'a GameInput>> {
1403 match *remapping {
1404 Some(game_input) => {
1406 controls.modify_binding(game_input, key_mouse);
1407 *remapping = None;
1408 None
1409 },
1410 None => controls
1411 .get_associated_game_inputs(&key_mouse)
1412 .map(|game_inputs| game_inputs.iter()),
1413 }
1414 }
1415
1416 pub fn set_keybinding_mode(&mut self, game_input: GameInput) {
1417 self.remapping_keybindings = Some(game_input);
1418 }
1419
1420 pub fn toggle_keybinding_mode(&mut self) { self.keybinding_mode = !self.keybinding_mode; }
1421
1422 pub fn window(&self) -> &winit::window::Window { &self.window }
1423
1424 pub fn modifiers(&self) -> winit::event::ModifiersState { self.modifiers }
1425
1426 pub fn scale_factor(&self) -> f64 { self.scale_factor }
1427}
1428
1429#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
1430pub enum FullscreenMode {
1431 Exclusive,
1432 #[serde(other)]
1433 #[default]
1434 Borderless,
1435}
1436
1437#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1438#[serde(default)]
1439pub struct WindowSettings {
1440 pub size: [u16; 2],
1441 pub maximised: bool,
1442}
1443
1444impl Default for WindowSettings {
1445 fn default() -> Self {
1446 Self {
1447 size: [1280, 720],
1448 maximised: false,
1449 }
1450 }
1451}
1452
1453#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
1454#[serde(default)]
1455pub struct FullScreenSettings {
1456 pub enabled: bool,
1457 pub mode: FullscreenMode,
1458 pub resolution: [u16; 2],
1459 pub bit_depth: Option<u16>,
1460 pub refresh_rate_millihertz: Option<u32>,
1461}
1462
1463impl Default for FullScreenSettings {
1464 fn default() -> Self {
1465 Self {
1466 enabled: true,
1467 mode: FullscreenMode::Borderless,
1468 resolution: [1920, 1080],
1469 bit_depth: None,
1470 refresh_rate_millihertz: None,
1471 }
1472 }
1473}