diff --git a/CHANGELOG.md b/CHANGELOG.md index 37648ad8bf..4edcbdd1c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - weapon control system - Game pauses when in singleplayer and pause menu - Added authentication system (to play on the official server register on https://account.veloren.net) +- Added gamepad/controller support ### Changed diff --git a/Cargo.lock b/Cargo.lock index a7da69e687..22b3939864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ source = "git+https://gitlab.com/veloren/auth.git?rev=65571ade0d954a0e0bd995fdb3 dependencies = [ "rand 0.7.3", "serde", - "uuid", + "uuid 0.7.4", ] [[package]] @@ -174,7 +174,7 @@ dependencies = [ "reqwest", "rust-argon2 0.6.1", "serde_json", - "uuid", + "uuid 0.7.4", ] [[package]] @@ -203,14 +203,20 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" +checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" dependencies = [ "cc", "libc", ] +[[package]] +name = "base-x" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" + [[package]] name = "base64" version = "0.9.3" @@ -248,15 +254,16 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.51.1" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" +checksum = "6bb26d6a69a335b8cb0e7c7e9775cd5666611dc50a37177c3f2cedcfc040e8c8" dependencies = [ "bitflags", "cexpr", "cfg-if", "clang-sys", "lazy_static", + "lazycell", "peeking_take_while", "proc-macro2 1.0.9", "quote 1.0.3", @@ -360,15 +367,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - [[package]] name = "c_vec" version = "1.3.3" @@ -419,11 +417,11 @@ dependencies = [ [[package]] name = "cexpr" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ - "nom", + "nom 5.1.1", ] [[package]] @@ -471,9 +469,9 @@ checksum = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" [[package]] name = "clang-sys" -version = "0.28.1" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" +checksum = "f92986241798376849e1a007827041fed9bb36195822c2049d18e174420e0534" dependencies = [ "glob", "libc", @@ -614,9 +612,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "copypasta" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fcbfb17a41091ba8bad1233d0178fda058968a71998dde3f11189f0b4aa9da" +checksum = "865e9675691e2a7dfc806b16ef2dd5dd536e26ea9b8046519767d79be03aeb6a" dependencies = [ "clipboard-win", "objc", @@ -683,9 +681,9 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8f5954c1c7ccb55340443e8b29fca24013545a5e7d72c1ca7db4fc02b982ce" +checksum = "e81f1c165c33ffab90a03077ac3b03462b34d5947145dfa48102e063d581502c" dependencies = [ "bindgen", ] @@ -703,7 +701,7 @@ dependencies = [ "lazy_static", "libc", "num-traits 0.2.11", - "stdweb", + "stdweb 0.1.3", "winapi 0.3.8", ] @@ -917,9 +915,9 @@ dependencies = [ [[package]] name = "derivative" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" +checksum = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", @@ -965,6 +963,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dispatch" version = "0.1.4" @@ -989,7 +993,7 @@ dependencies = [ "byteorder 1.3.4", "lazy_static", "log 0.4.8", - "nom", + "nom 4.2.3", ] [[package]] @@ -1400,6 +1404,40 @@ dependencies = [ "lzw", ] +[[package]] +name = "gilrs" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122bb249f904e5f4ac73fc514b9b2ce6cce3af511f5df00ffc8000e47de6b290" +dependencies = [ + "fnv", + "gilrs-core", + "log 0.4.8", + "serde", + "stdweb 0.4.20", + "uuid 0.8.1", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdd4ea2d919ecb594362fa26b0f172729b9ee9b95e407fbad95e0a49cadc143" +dependencies = [ + "core-foundation 0.6.4", + "io-kit-sys", + "libc", + "libudev-sys", + "log 0.4.8", + "nix 0.15.0", + "rusty-xinput", + "stdweb 0.4.20", + "uuid 0.8.1", + "vec_map", + "winapi 0.3.8", +] + [[package]] name = "gio" version = "0.4.1" @@ -1882,6 +1920,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c346c299e3fe8ef94dc10c2c0253d858a69aac1245157a3bf4125915d528caf" +[[package]] +name = "io-kit-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21dcc74995dd4cd090b147e79789f8d65959cbfb5f0b118002db869ea3bd0a0" +dependencies = [ + "core-foundation-sys 0.6.2", + "mach", +] + [[package]] name = "iovec" version = "0.1.4" @@ -2017,6 +2065,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "libz-sys" version = "1.0.25" @@ -2087,6 +2145,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" +[[package]] +name = "mach" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2287,6 +2354,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2312,6 +2392,16 @@ dependencies = [ "version_check 0.1.5", ] +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "memchr", + "version_check 0.9.1", +] + [[package]] name = "notify" version = "5.0.0-pre.2" @@ -2977,7 +3067,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha 0.2.1", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", "rand_pcg 0.2.1", @@ -2995,11 +3085,11 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "c2-chacha", + "ppv-lite86", "rand_core 0.5.1", ] @@ -3403,6 +3493,17 @@ dependencies = [ "stb_truetype", ] +[[package]] +name = "rusty-xinput" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2aa654bc32eb9ca14cce1a084abc9dfe43949a4547c35269a094c39272db3bb" +dependencies = [ + "lazy_static", + "log 0.4.8", + "winapi 0.3.8", +] + [[package]] name = "ryu" version = "1.0.2" @@ -3660,7 +3761,7 @@ dependencies = [ "dlib", "lazy_static", "memmap", - "nix", + "nix 0.14.1", "wayland-client 0.21.13", "wayland-commons 0.21.13", "wayland-protocols 0.21.13", @@ -3677,18 +3778,18 @@ dependencies = [ "dlib", "lazy_static", "memmap", - "nix", + "nix 0.14.1", "wayland-client 0.23.6", "wayland-protocols 0.23.6", ] [[package]] name = "smithay-clipboard" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a59486e68b5596f664deedf01c46297f4af60379adae20175357a814d40f69e" +checksum = "917e8ec7f535cd1a6cbf749c8866c24d67c548a80ac48c8e88a182eab5c07bd1" dependencies = [ - "nix", + "nix 0.14.1", "smithay-client-toolkit 0.6.6", ] @@ -3751,6 +3852,57 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "serde", + "serde_json", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.9", + "quote 1.0.3", + "serde", + "serde_derive", + "syn 1.0.16", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.9", + "quote 1.0.3", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.16", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "sum_type" version = "0.2.0" @@ -4090,6 +4242,12 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" + [[package]] name = "uvth" version = "3.1.1" @@ -4107,6 +4265,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + [[package]] name = "vek" version = "0.9.12" @@ -4239,6 +4403,7 @@ dependencies = [ "gfx", "gfx_device_gl", "gfx_window_glutin", + "gilrs", "git2", "glsl-include", "glutin", @@ -4285,7 +4450,7 @@ dependencies = [ "packed_simd", "pretty_env_logger", "rand 0.7.3", - "rand_chacha 0.2.1", + "rand_chacha 0.2.2", "rayon", "ron", "roots", @@ -4417,7 +4582,7 @@ dependencies = [ "bitflags", "downcast-rs", "libc", - "nix", + "nix 0.14.1", "wayland-commons 0.21.13", "wayland-scanner 0.21.13", "wayland-sys 0.21.13", @@ -4432,7 +4597,7 @@ dependencies = [ "bitflags", "downcast-rs", "libc", - "nix", + "nix 0.14.1", "wayland-commons 0.23.6", "wayland-scanner 0.23.6", "wayland-sys 0.23.6", @@ -4444,7 +4609,7 @@ version = "0.21.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" dependencies = [ - "nix", + "nix 0.14.1", "wayland-sys 0.21.13", ] @@ -4454,7 +4619,7 @@ version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" dependencies = [ - "nix", + "nix 0.14.1", "wayland-sys 0.23.6", ] diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index a4e5c7cff5..21049a4c4c 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -35,6 +35,9 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } # Mathematics vek = { version = "0.9.9", features = ["serde"] } +# Controller +gilrs = { version = "0.7", features = ["serde"] } + # Singleplayer server = { package = "veloren-server", path = "../server", optional = true } diff --git a/voxygen/src/controller.rs b/voxygen/src/controller.rs new file mode 100644 index 0000000000..77049acc21 --- /dev/null +++ b/voxygen/src/controller.rs @@ -0,0 +1,329 @@ +//! Module containing controller-specific abstractions allowing complex +//! keybindings + +use crate::window::{GameInput, MenuInput}; +use gilrs::{ev::Code as GilCode, Axis as GilAxis, Button as GilButton}; +use hashbrown::HashMap; +use serde_derive::{Deserialize, Serialize}; + +/// Contains all controller related settings and keymaps +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ControllerSettings { + pub game_button_map: HashMap>, + pub menu_button_map: HashMap>, + pub game_analog_button_map: HashMap>, + pub menu_analog_button_map: HashMap>, + pub game_axis_map: HashMap>, + pub menu_axis_map: HashMap>, + pub pan_sensitivity: u32, + pub axis_deadzones: HashMap, + pub button_deadzones: HashMap, + pub mouse_emulation_sensitivity: u32, + pub inverted_axes: Vec, +} + +impl ControllerSettings { + pub fn apply_axis_deadzone(&self, k: &Axis, input: f32) -> f32 { + let threshold = *self.axis_deadzones.get(k).unwrap_or(&0.2); + + // This could be one comparison per handled event faster if threshold was + // guaranteed to fall into <0, 1) range + let input_abs = input.abs(); + if input_abs <= threshold || threshold >= 1.0 { + 0.0 + } else if threshold <= 0.0 { + input + } else { + (input_abs - threshold) / (1.0 - threshold) * input.signum() + } + } + + pub fn apply_button_deadzone(&self, k: &AnalogButton, input: f32) -> f32 { + let threshold = *self.button_deadzones.get(k).unwrap_or(&0.2); + + // This could be one comparison per handled event faster if threshold was + // guaranteed to fall into <0, 1) range + if input <= threshold || threshold >= 1.0 { + 0.0 + } else if threshold <= 0.0 { + input + } else { + (input - threshold) / (1.0 - threshold) + } + } +} + +impl From<&crate::settings::GamepadSettings> for ControllerSettings { + fn from(settings: &crate::settings::GamepadSettings) -> Self { + Self { + game_button_map: { + let mut map: HashMap<_, Vec<_>> = HashMap::new(); + map.entry(settings.game_buttons.primary) + .or_default() + .push(GameInput::Primary); + map.entry(settings.game_buttons.secondary) + .or_default() + .push(GameInput::Secondary); + map.entry(settings.game_buttons.toggle_cursor) + .or_default() + .push(GameInput::ToggleCursor); + map.entry(settings.game_buttons.escape) + .or_default() + .push(GameInput::Escape); + map.entry(settings.game_buttons.enter) + .or_default() + .push(GameInput::Enter); + map.entry(settings.game_buttons.command) + .or_default() + .push(GameInput::Command); + map.entry(settings.game_buttons.move_forward) + .or_default() + .push(GameInput::MoveForward); + map.entry(settings.game_buttons.move_left) + .or_default() + .push(GameInput::MoveLeft); + map.entry(settings.game_buttons.move_back) + .or_default() + .push(GameInput::MoveBack); + map.entry(settings.game_buttons.move_right) + .or_default() + .push(GameInput::MoveRight); + map.entry(settings.game_buttons.jump) + .or_default() + .push(GameInput::Jump); + map.entry(settings.game_buttons.sit) + .or_default() + .push(GameInput::Sit); + map.entry(settings.game_buttons.glide) + .or_default() + .push(GameInput::Glide); + map.entry(settings.game_buttons.climb) + .or_default() + .push(GameInput::Climb); + map.entry(settings.game_buttons.climb_down) + .or_default() + .push(GameInput::ClimbDown); + map.entry(settings.game_buttons.wall_leap) + .or_default() + .push(GameInput::WallLeap); + map.entry(settings.game_buttons.mount) + .or_default() + .push(GameInput::Mount); + map.entry(settings.game_buttons.map) + .or_default() + .push(GameInput::Map); + map.entry(settings.game_buttons.bag) + .or_default() + .push(GameInput::Bag); + map.entry(settings.game_buttons.quest_log) + .or_default() + .push(GameInput::QuestLog); + map.entry(settings.game_buttons.character_window) + .or_default() + .push(GameInput::CharacterWindow); + map.entry(settings.game_buttons.social) + .or_default() + .push(GameInput::Social); + map.entry(settings.game_buttons.spellbook) + .or_default() + .push(GameInput::Spellbook); + map.entry(settings.game_buttons.settings) + .or_default() + .push(GameInput::Settings); + map.entry(settings.game_buttons.help) + .or_default() + .push(GameInput::Help); + map.entry(settings.game_buttons.toggle_interface) + .or_default() + .push(GameInput::ToggleInterface); + map.entry(settings.game_buttons.toggle_debug) + .or_default() + .push(GameInput::ToggleDebug); + map.entry(settings.game_buttons.fullscreen) + .or_default() + .push(GameInput::Fullscreen); + map.entry(settings.game_buttons.screenshot) + .or_default() + .push(GameInput::Screenshot); + map.entry(settings.game_buttons.toggle_ingame_ui) + .or_default() + .push(GameInput::ToggleIngameUi); + map.entry(settings.game_buttons.roll) + .or_default() + .push(GameInput::Roll); + map.entry(settings.game_buttons.respawn) + .or_default() + .push(GameInput::Respawn); + map.entry(settings.game_buttons.interact) + .or_default() + .push(GameInput::Interact); + map.entry(settings.game_buttons.toggle_wield) + .or_default() + .push(GameInput::ToggleWield); + map.entry(settings.game_buttons.charge) + .or_default() + .push(GameInput::Charge); + map + }, + menu_button_map: { + let mut map: HashMap<_, Vec<_>> = HashMap::new(); + map.entry(settings.menu_buttons.up) + .or_default() + .push(MenuInput::Up); + map.entry(settings.menu_buttons.down) + .or_default() + .push(MenuInput::Down); + map.entry(settings.menu_buttons.left) + .or_default() + .push(MenuInput::Left); + map.entry(settings.menu_buttons.right) + .or_default() + .push(MenuInput::Right); + map.entry(settings.menu_buttons.scroll_up) + .or_default() + .push(MenuInput::ScrollUp); + map.entry(settings.menu_buttons.scroll_down) + .or_default() + .push(MenuInput::ScrollDown); + map.entry(settings.menu_buttons.scroll_left) + .or_default() + .push(MenuInput::ScrollLeft); + map.entry(settings.menu_buttons.scroll_right) + .or_default() + .push(MenuInput::ScrollRight); + map.entry(settings.menu_buttons.home) + .or_default() + .push(MenuInput::Home); + map.entry(settings.menu_buttons.end) + .or_default() + .push(MenuInput::End); + map.entry(settings.menu_buttons.apply) + .or_default() + .push(MenuInput::Apply); + map.entry(settings.menu_buttons.back) + .or_default() + .push(MenuInput::Back); + map.entry(settings.menu_buttons.exit) + .or_default() + .push(MenuInput::Exit); + map + }, + game_analog_button_map: HashMap::new(), + menu_analog_button_map: HashMap::new(), + game_axis_map: { + let mut map: HashMap<_, Vec<_>> = HashMap::new(); + map.entry(settings.game_axis.movement_x) + .or_default() + .push(AxisGameAction::MovementX); + map.entry(settings.game_axis.movement_y) + .or_default() + .push(AxisGameAction::MovementY); + map.entry(settings.game_axis.camera_x) + .or_default() + .push(AxisGameAction::CameraX); + map.entry(settings.game_axis.camera_y) + .or_default() + .push(AxisGameAction::CameraY); + map + }, + menu_axis_map: { + let mut map: HashMap<_, Vec<_>> = HashMap::new(); + map.entry(settings.menu_axis.move_x) + .or_default() + .push(AxisMenuAction::MoveX); + map.entry(settings.menu_axis.move_y) + .or_default() + .push(AxisMenuAction::MoveY); + map.entry(settings.menu_axis.scroll_x) + .or_default() + .push(AxisMenuAction::ScrollX); + map.entry(settings.menu_axis.scroll_y) + .or_default() + .push(AxisMenuAction::ScrollY); + map + }, + pan_sensitivity: settings.pan_sensitivity, + axis_deadzones: settings.axis_deadzones.clone(), + button_deadzones: settings.button_deadzones.clone(), + mouse_emulation_sensitivity: settings.mouse_emulation_sensitivity, + inverted_axes: settings.inverted_axes.clone(), + } + } +} + +/// All the menu actions you can bind to an Axis +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AxisMenuAction { + MoveX, + MoveY, + ScrollX, + ScrollY, +} + +/// All the game actions you can bind to an Axis +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AxisGameAction { + MovementX, + MovementY, + CameraX, + CameraY, +} + +/// All the menu actions you can bind to an analog button +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AnalogButtonMenuAction {} + +/// All the game actions you can bind to an analog button +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AnalogButtonGameAction {} + +/// Button::Simple(GilButton::Unknown) is invalid and equal to mapping an action +/// to nothing +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum Button { + Simple(GilButton), + EventCode(u32), +} + +/// AnalogButton::Simple(GilButton::Unknown) is invalid and equal to mapping an +/// action to nothing +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum AnalogButton { + Simple(GilButton), + EventCode(u32), +} + +/// Axis::Simple(GilAxis::Unknown) is invalid and equal to mapping an action to +/// nothing +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum Axis { + Simple(GilAxis), + EventCode(u32), +} + +impl From<(GilAxis, GilCode)> for Axis { + fn from((axis, code): (GilAxis, GilCode)) -> Self { + match axis { + GilAxis::Unknown => Self::EventCode(code.into_u32()), + _ => Self::Simple(axis), + } + } +} + +impl From<(GilButton, GilCode)> for Button { + fn from((button, code): (GilButton, GilCode)) -> Self { + match button { + GilButton::Unknown => Self::EventCode(code.into_u32()), + _ => Self::Simple(button), + } + } +} + +impl From<(GilButton, GilCode)> for AnalogButton { + fn from((button, code): (GilButton, GilCode)) -> Self { + match button { + GilButton::Unknown => Self::EventCode(code.into_u32()), + _ => Self::Simple(button), + } + } +} diff --git a/voxygen/src/key_state.rs b/voxygen/src/key_state.rs index 2921c4ce6c..e07474e0db 100644 --- a/voxygen/src/key_state.rs +++ b/voxygen/src/key_state.rs @@ -5,6 +5,7 @@ pub struct KeyState { pub left: bool, pub up: bool, pub down: bool, + pub analog_matrix: Vec2, } impl KeyState { @@ -14,16 +15,21 @@ impl KeyState { left: false, up: false, down: false, + analog_matrix: Vec2::zero(), } } pub fn dir_vec(&self) -> Vec2 { - let dir = Vec2::::new( - if self.right { 1.0 } else { 0.0 } + if self.left { -1.0 } else { 0.0 }, - if self.up { 1.0 } else { 0.0 } + if self.down { -1.0 } else { 0.0 }, - ); + let dir = if self.analog_matrix == Vec2::zero() { + Vec2::::new( + if self.right { 1.0 } else { 0.0 } + if self.left { -1.0 } else { 0.0 }, + if self.up { 1.0 } else { 0.0 } + if self.down { -1.0 } else { 0.0 }, + ) + } else { + self.analog_matrix + }; - if dir.magnitude_squared() == 0.0 { + if dir.magnitude_squared() <= 1.0 { dir } else { dir.normalized() diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 56877b2131..e1866d1188 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -6,6 +6,7 @@ pub mod ui; pub mod anim; pub mod audio; +pub mod controller; mod ecs; pub mod error; pub mod hud; diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 84c6aa2f3e..02996bca7e 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -15,7 +15,7 @@ use crate::{ create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, }, - window::Event, + window::{AnalogGameInput, Event}, }; use common::{ comp, @@ -50,6 +50,7 @@ pub struct Scene { lights: Consts, shadows: Consts, camera: Camera, + camera_input_state: Vec2, skybox: Skybox, postprocess: PostProcess, @@ -85,6 +86,7 @@ impl Scene { .create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]) .unwrap(), camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), + camera_input_state: Vec2::zero(), skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), @@ -146,6 +148,17 @@ impl Scene { .zoom_switch(delta * (0.05 + self.camera.get_distance() * 0.01)); true }, + Event::AnalogGameInput(input) => match input { + AnalogGameInput::CameraX(d) => { + self.camera_input_state.x = d; + true + }, + AnalogGameInput::CameraY(d) => { + self.camera_input_state.y = d; + true + }, + _ => false, + }, // All other events are unhandled _ => false, } @@ -185,6 +198,12 @@ impl Scene { _ => 1_f32, }; + // Add the analog input to camera + self.camera + .rotate_by(Vec3::from([self.camera_input_state.x, 0.0, 0.0])); + self.camera + .rotate_by(Vec3::from([0.0, self.camera_input_state.y, 0.0])); + // Alter camera position to match player. let tilt = self.camera.get_orientation().y; let dist = self.camera.get_distance(); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index cf47210669..d4538d081e 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -5,7 +5,7 @@ use crate::{ key_state::KeyState, render::Renderer, scene::{camera, Scene, SceneData}, - window::{Event, GameInput}, + window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client, Event::Chat}; @@ -365,6 +365,17 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::Charge, state) => { self.inputs.charge.set_state(state); }, + Event::AnalogGameInput(input) => match input { + AnalogGameInput::MovementX(v) => { + self.key_state.analog_matrix.x = v; + }, + AnalogGameInput::MovementY(v) => { + self.key_state.analog_matrix.y = v; + }, + other => { + self.scene.handle_input_event(Event::AnalogGameInput(other)); + }, + }, // Pass all other events to the scene event => { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 854464422f..78431b2dd1 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -7,7 +7,7 @@ use crate::{ }; use directories::{ProjectDirs, UserDirs}; use glutin::{MouseButton, VirtualKeyCode}; -use hashbrown::HashSet; +use hashbrown::{HashMap, HashSet}; use log::warn; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; @@ -105,6 +105,225 @@ impl Default for ControlSettings { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct GamepadSettings { + pub game_buttons: con_settings::GameButtons, + pub menu_buttons: con_settings::MenuButtons, + pub game_axis: con_settings::GameAxis, + pub menu_axis: con_settings::MenuAxis, + pub game_analog_buttons: con_settings::GameAnalogButton, + pub menu_analog_buttons: con_settings::MenuAnalogButton, + pub pan_sensitivity: u32, + pub pan_invert_y: bool, + pub axis_deadzones: HashMap, + pub button_deadzones: HashMap, + pub mouse_emulation_sensitivity: u32, + pub inverted_axes: Vec, +} + +impl Default for GamepadSettings { + fn default() -> Self { + Self { + game_buttons: con_settings::GameButtons::default(), + menu_buttons: con_settings::MenuButtons::default(), + game_axis: con_settings::GameAxis::default(), + menu_axis: con_settings::MenuAxis::default(), + game_analog_buttons: con_settings::GameAnalogButton::default(), + menu_analog_buttons: con_settings::MenuAnalogButton::default(), + pan_sensitivity: 10, + pan_invert_y: false, + axis_deadzones: HashMap::new(), + button_deadzones: HashMap::new(), + mouse_emulation_sensitivity: 12, + inverted_axes: Vec::new(), + } + } +} + +pub mod con_settings { + use crate::controller::*; + use gilrs::{Axis as GilAxis, Button as GilButton}; + use serde_derive::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct GameButtons { + pub primary: Button, + pub secondary: Button, + pub toggle_cursor: Button, + pub escape: Button, + pub enter: Button, + pub command: Button, + pub move_forward: Button, + pub move_left: Button, + pub move_back: Button, + pub move_right: Button, + pub jump: Button, + pub sit: Button, + pub glide: Button, + pub climb: Button, + pub climb_down: Button, + pub wall_leap: Button, + pub mount: Button, + pub map: Button, + pub bag: Button, + pub quest_log: Button, + pub character_window: Button, + pub social: Button, + pub spellbook: Button, + pub settings: Button, + pub help: Button, + pub toggle_interface: Button, + pub toggle_debug: Button, + pub fullscreen: Button, + pub screenshot: Button, + pub toggle_ingame_ui: Button, + pub roll: Button, + pub respawn: Button, + pub interact: Button, + pub toggle_wield: Button, + pub charge: Button, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct MenuButtons { + pub up: Button, + pub down: Button, + pub left: Button, + pub right: Button, + pub scroll_up: Button, + pub scroll_down: Button, + pub scroll_left: Button, + pub scroll_right: Button, + pub home: Button, + pub end: Button, + pub apply: Button, + pub back: Button, + pub exit: Button, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct GameAxis { + pub movement_x: Axis, + pub movement_y: Axis, + pub camera_x: Axis, + pub camera_y: Axis, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct MenuAxis { + pub move_x: Axis, + pub move_y: Axis, + pub scroll_x: Axis, + pub scroll_y: Axis, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct GameAnalogButton {} + + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(default)] + pub struct MenuAnalogButton {} + + impl Default for GameButtons { + fn default() -> Self { + // binding to unknown = getting skipped from processing + Self { + primary: Button::Simple(GilButton::RightTrigger2), + secondary: Button::Simple(GilButton::LeftTrigger2), + toggle_cursor: Button::Simple(GilButton::Select), + escape: Button::Simple(GilButton::Mode), + enter: Button::Simple(GilButton::Unknown), + command: Button::Simple(GilButton::Unknown), + move_forward: Button::Simple(GilButton::Unknown), + move_left: Button::Simple(GilButton::Unknown), + move_back: Button::Simple(GilButton::Unknown), + move_right: Button::Simple(GilButton::Unknown), + jump: Button::Simple(GilButton::South), + sit: Button::Simple(GilButton::West), + glide: Button::Simple(GilButton::LeftTrigger), + climb: Button::Simple(GilButton::South), + climb_down: Button::Simple(GilButton::Unknown), + wall_leap: Button::Simple(GilButton::Unknown), + mount: Button::Simple(GilButton::North), + map: Button::Simple(GilButton::DPadRight), + bag: Button::Simple(GilButton::DPadDown), + quest_log: Button::Simple(GilButton::Unknown), + character_window: Button::Simple(GilButton::Unknown), + social: Button::Simple(GilButton::Unknown), + spellbook: Button::Simple(GilButton::Unknown), + settings: Button::Simple(GilButton::Unknown), + help: Button::Simple(GilButton::Unknown), + toggle_interface: Button::Simple(GilButton::Unknown), + toggle_debug: Button::Simple(GilButton::Unknown), + fullscreen: Button::Simple(GilButton::Unknown), + screenshot: Button::Simple(GilButton::DPadUp), + toggle_ingame_ui: Button::Simple(GilButton::Unknown), + roll: Button::Simple(GilButton::RightTrigger), + respawn: Button::Simple(GilButton::RightTrigger2), + interact: Button::Simple(GilButton::LeftTrigger2), + toggle_wield: Button::Simple(GilButton::DPadLeft), + charge: Button::Simple(GilButton::Unknown), + } + } + } + + impl Default for MenuButtons { + fn default() -> Self { + Self { + up: Button::Simple(GilButton::Unknown), + down: Button::Simple(GilButton::Unknown), + left: Button::Simple(GilButton::Unknown), + right: Button::Simple(GilButton::Unknown), + scroll_up: Button::Simple(GilButton::Unknown), + scroll_down: Button::Simple(GilButton::Unknown), + scroll_left: Button::Simple(GilButton::Unknown), + scroll_right: Button::Simple(GilButton::Unknown), + home: Button::Simple(GilButton::DPadUp), + end: Button::Simple(GilButton::DPadDown), + apply: Button::Simple(GilButton::South), + back: Button::Simple(GilButton::East), + exit: Button::Simple(GilButton::Mode), + } + } + } + + impl Default for GameAxis { + fn default() -> Self { + Self { + movement_x: Axis::Simple(GilAxis::LeftStickX), + movement_y: Axis::Simple(GilAxis::LeftStickY), + camera_x: Axis::Simple(GilAxis::RightStickX), + camera_y: Axis::Simple(GilAxis::RightStickY), + } + } + } + + impl Default for MenuAxis { + fn default() -> Self { + Self { + move_x: Axis::Simple(GilAxis::RightStickX), + move_y: Axis::Simple(GilAxis::RightStickY), + scroll_x: Axis::Simple(GilAxis::LeftStickX), + scroll_y: Axis::Simple(GilAxis::LeftStickY), + } + } + } + + impl Default for GameAnalogButton { + fn default() -> Self { Self {} } + } + + impl Default for MenuAnalogButton { + fn default() -> Self { Self {} } + } +} + /// `GameplaySettings` contains sensitivity and gameplay options. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -298,6 +517,7 @@ pub struct Settings { pub logon_commands: Vec, pub language: LanguageSettings, pub screenshots_path: PathBuf, + pub controller: GamepadSettings, } impl Default for Settings { @@ -329,6 +549,7 @@ impl Default for Settings { logon_commands: Vec::new(), language: LanguageSettings::default(), screenshots_path, + controller: GamepadSettings::default(), } } } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 1016c2a134..a1e18915f6 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -634,8 +634,8 @@ impl Ui { mesh.push_quad(create_ui_quad( gl_aabr(rect), Aabr { - min: Vec2::new(0.0, 0.0), - max: Vec2::new(0.0, 0.0), + min: Vec2::zero(), + max: Vec2::zero(), }, color, UiMode::Geometry, diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 59c9b6c4ed..f052ec73a2 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,15 +1,17 @@ use crate::{ + controller::*, render::{Renderer, WinColorFmt, WinDepthFmt}, settings::Settings, ui, Error, }; +use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; use log::{error, warn}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use vek::*; -/// Represents a key that the game recognises after keyboard mapping. +/// Represents a key that the game recognises after input mapping. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum GameInput { Primary, @@ -49,6 +51,40 @@ pub enum GameInput { Charge, } +/// Represents a key that the game menus recognise after input mapping +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum MenuInput { + Up, + Down, + Left, + Right, + ScrollUp, + ScrollDown, + ScrollLeft, + ScrollRight, + Home, + End, + Apply, + Back, + Exit, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum AnalogMenuInput { + MoveX(f32), + MoveY(f32), + ScrollX(f32), + ScrollY(f32), +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum AnalogGameInput { + MovementX(f32), + MovementY(f32), + CameraX(f32), + CameraY(f32), +} + /// Represents an incoming event from the window. #[derive(Clone)] pub enum Event { @@ -76,6 +112,13 @@ pub enum Event { SettingsChanged, /// The window is (un)focused Focused(bool), + /// A key that the game recognises for menu navigation has been pressed or + /// released + MenuInput(MenuInput, bool), + /// Update of the analog inputs recognized by the menus + AnalogMenuInput(AnalogMenuInput), + /// Update of the analog inputs recognized by the game + AnalogGameInput(AnalogGameInput), } pub type MouseButton = winit::MouseButton; @@ -277,6 +320,10 @@ pub struct Window { keypress_map: HashMap, supplement_events: Vec, focused: bool, + gilrs: Option, + controller_settings: ControllerSettings, + cursor_position: winit::dpi::LogicalPosition, + mouse_emulation_vec: Vec2, } impl Window { @@ -414,6 +461,30 @@ impl Window { let keypress_map = HashMap::new(); + let gilrs = match Gilrs::new() { + Ok(gilrs) => Some(gilrs), + Err(gilrs::Error::NotImplemented(_dummy)) => { + warn!("Controller input is unsupported on this platform."); + None + }, + Err(gilrs::Error::InvalidAxisToBtn) => { + error!( + "Invalid AxisToBtn controller mapping. Falling back to no controller support." + ); + None + }, + Err(gilrs::Error::Other(err)) => { + error!( + "Platform-specific error when creating a Gilrs instance: `{}`. Falling back \ + to no controller support.", + err + ); + None + }, + }; + + let controller_settings = ControllerSettings::from(&settings.controller); + let mut this = Self { events_loop, renderer: Renderer::new( @@ -437,6 +508,10 @@ impl Window { keypress_map, supplement_events: vec![], focused: true, + gilrs, + controller_settings, + cursor_position: winit::dpi::LogicalPosition::new(0.0, 0.0), + mouse_emulation_vec: Vec2::zero(), }; this.fullscreen(settings.graphics.fullscreen); @@ -477,6 +552,7 @@ impl Window { }; let mut toggle_fullscreen = false; let mut take_screenshot = false; + let mut cursor_position = None; self.events_loop.poll_events(|event| { // Get events for ui. @@ -556,6 +632,9 @@ impl Window { *focused = state; events.push(Event::Focused(state)); }, + glutin::WindowEvent::CursorMoved { position, .. } => { + cursor_position = Some(position); + }, _ => {}, }, glutin::Event::DeviceEvent { event, .. } => match event { @@ -593,6 +672,10 @@ impl Window { } }); + if let Some(pos) = cursor_position { + self.cursor_position = pos; + } + if take_screenshot { self.take_screenshot(&settings); } @@ -601,9 +684,217 @@ impl Window { self.toggle_fullscreen(settings); } + if let Some(gilrs) = &mut self.gilrs { + while let Some(event) = gilrs.next_event() { + fn handle_buttons( + settings: &ControllerSettings, + events: &mut Vec, + button: &Button, + is_pressed: bool, + ) { + if let Some(evs) = settings.game_button_map.get(button) { + for ev in evs { + events.push(Event::InputUpdate(*ev, is_pressed)); + } + } + if let Some(evs) = settings.menu_button_map.get(button) { + for ev in evs { + events.push(Event::MenuInput(*ev, is_pressed)); + } + } + } + + match event.event { + EventType::ButtonPressed(button, code) + | EventType::ButtonRepeated(button, code) => { + handle_buttons( + &self.controller_settings, + &mut events, + &Button::from((button, code)), + true, + ); + }, + EventType::ButtonReleased(button, code) => { + handle_buttons( + &self.controller_settings, + &mut events, + &Button::from((button, code)), + false, + ); + }, + EventType::ButtonChanged(button, _value, code) => { + if let Some(actions) = self + .controller_settings + .game_analog_button_map + .get(&AnalogButton::from((button, code))) + { + for action in actions { + match *action {} + } + } + if let Some(actions) = self + .controller_settings + .menu_analog_button_map + .get(&AnalogButton::from((button, code))) + { + for action in actions { + match *action {} + } + } + }, + + EventType::AxisChanged(axis, value, code) => { + let value = match self + .controller_settings + .inverted_axes + .contains(&Axis::from((axis, code))) + { + true => value * -1.0, + false => value, + }; + + let value = self + .controller_settings + .apply_axis_deadzone(&Axis::from((axis, code)), value); + + if self.cursor_grabbed { + if let Some(actions) = self + .controller_settings + .game_axis_map + .get(&Axis::from((axis, code))) + { + for action in actions { + match *action { + AxisGameAction::MovementX => { + events.push(Event::AnalogGameInput( + AnalogGameInput::MovementX(value), + )); + }, + AxisGameAction::MovementY => { + events.push(Event::AnalogGameInput( + AnalogGameInput::MovementY(value), + )); + }, + AxisGameAction::CameraX => { + events.push(Event::AnalogGameInput( + AnalogGameInput::CameraX( + value + * self.controller_settings.pan_sensitivity + as f32 + / 100.0, + ), + )); + }, + AxisGameAction::CameraY => { + events.push(Event::AnalogGameInput( + AnalogGameInput::CameraY( + value + * self.controller_settings.pan_sensitivity + as f32 + / 100.0, + ), + )); + }, + } + } + } + } else if let Some(actions) = self + .controller_settings + .menu_axis_map + .get(&Axis::from((axis, code))) + { + // TODO: possibly add sensitivity settings when this is used + for action in actions { + match *action { + AxisMenuAction::MoveX => { + events.push(Event::AnalogMenuInput( + AnalogMenuInput::MoveX(value), + )); + }, + AxisMenuAction::MoveY => { + events.push(Event::AnalogMenuInput( + AnalogMenuInput::MoveY(value), + )); + }, + AxisMenuAction::ScrollX => { + events.push(Event::AnalogMenuInput( + AnalogMenuInput::ScrollX(value), + )); + }, + AxisMenuAction::ScrollY => { + events.push(Event::AnalogMenuInput( + AnalogMenuInput::ScrollY(value), + )); + }, + } + } + } + }, + EventType::Connected => {}, + EventType::Disconnected => {}, + EventType::Dropped => {}, + } + } + } + + // Mouse emulation for the menus, to be removed when a proper menu navigation + // system is available + if !self.cursor_grabbed { + events = events + .into_iter() + .filter_map(|event| match event { + Event::AnalogMenuInput(input) => match input { + AnalogMenuInput::MoveX(d) => { + self.mouse_emulation_vec.x = d; + None + }, + AnalogMenuInput::MoveY(d) => { + // This just has to be inverted for some reason + self.mouse_emulation_vec.y = d * -1.0; + None + }, + _ => { + let event = Event::AnalogMenuInput(input); + Some(event) + }, + }, + Event::MenuInput(input, state) => match input { + MenuInput::Apply => Some(match state { + true => Event::Ui(ui::Event(conrod_core::event::Input::Press( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + false => Event::Ui(ui::Event(conrod_core::event::Input::Release( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + }), + _ => Some(event), + }, + _ => Some(event), + }) + .collect(); + let sensitivity = self.controller_settings.mouse_emulation_sensitivity; + if self.mouse_emulation_vec != Vec2::zero() { + self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32) + .unwrap_or(()); + } + } events } + /// Moves cursor by an offset + pub fn offset_cursor(&self, d: Vec2) -> Result<(), String> { + self.window + .window() + .set_cursor_position(winit::dpi::LogicalPosition::new( + d.x as f64 + self.cursor_position.x, + d.y as f64 + self.cursor_position.y, + )) + } + pub fn swap_buffers(&self) -> Result<(), Error> { self.window .swap_buffers()