Added Controller support

This commit is contained in:
Mckol 2020-03-10 21:00:13 +00:00
parent 5598fe7166
commit 8838682f0b
11 changed files with 1100 additions and 53 deletions

View File

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- weapon control system - weapon control system
- Game pauses when in singleplayer and pause menu - Game pauses when in singleplayer and pause menu
- Added authentication system (to play on the official server register on https://account.veloren.net) - Added authentication system (to play on the official server register on https://account.veloren.net)
- Added gamepad/controller support
### Changed ### Changed

249
Cargo.lock generated
View File

@ -160,7 +160,7 @@ source = "git+https://gitlab.com/veloren/auth.git?rev=65571ade0d954a0e0bd995fdb3
dependencies = [ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"uuid", "uuid 0.7.4",
] ]
[[package]] [[package]]
@ -174,7 +174,7 @@ dependencies = [
"reqwest", "reqwest",
"rust-argon2 0.6.1", "rust-argon2 0.6.1",
"serde_json", "serde_json",
"uuid", "uuid 0.7.4",
] ]
[[package]] [[package]]
@ -203,14 +203,20 @@ dependencies = [
[[package]] [[package]]
name = "backtrace-sys" name = "backtrace-sys"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
] ]
[[package]]
name = "base-x"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.9.3" version = "0.9.3"
@ -248,15 +254,16 @@ dependencies = [
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.51.1" version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" checksum = "6bb26d6a69a335b8cb0e7c7e9775cd5666611dc50a37177c3f2cedcfc040e8c8"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cexpr", "cexpr",
"cfg-if", "cfg-if",
"clang-sys", "clang-sys",
"lazy_static", "lazy_static",
"lazycell",
"peeking_take_while", "peeking_take_while",
"proc-macro2 1.0.9", "proc-macro2 1.0.9",
"quote 1.0.3", "quote 1.0.3",
@ -360,15 +367,6 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 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]] [[package]]
name = "c_vec" name = "c_vec"
version = "1.3.3" version = "1.3.3"
@ -419,11 +417,11 @@ dependencies = [
[[package]] [[package]]
name = "cexpr" name = "cexpr"
version = "0.3.6" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [ dependencies = [
"nom", "nom 5.1.1",
] ]
[[package]] [[package]]
@ -471,9 +469,9 @@ checksum = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
[[package]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "0.28.1" version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" checksum = "f92986241798376849e1a007827041fed9bb36195822c2049d18e174420e0534"
dependencies = [ dependencies = [
"glob", "glob",
"libc", "libc",
@ -614,9 +612,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "copypasta" name = "copypasta"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fcbfb17a41091ba8bad1233d0178fda058968a71998dde3f11189f0b4aa9da" checksum = "865e9675691e2a7dfc806b16ef2dd5dd536e26ea9b8046519767d79be03aeb6a"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win",
"objc", "objc",
@ -683,9 +681,9 @@ dependencies = [
[[package]] [[package]]
name = "coreaudio-sys" name = "coreaudio-sys"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8f5954c1c7ccb55340443e8b29fca24013545a5e7d72c1ca7db4fc02b982ce" checksum = "e81f1c165c33ffab90a03077ac3b03462b34d5947145dfa48102e063d581502c"
dependencies = [ dependencies = [
"bindgen", "bindgen",
] ]
@ -703,7 +701,7 @@ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"num-traits 0.2.11", "num-traits 0.2.11",
"stdweb", "stdweb 0.1.3",
"winapi 0.3.8", "winapi 0.3.8",
] ]
@ -917,9 +915,9 @@ dependencies = [
[[package]] [[package]]
name = "derivative" name = "derivative"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" checksum = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135"
dependencies = [ dependencies = [
"proc-macro2 0.4.30", "proc-macro2 0.4.30",
"quote 0.6.13", "quote 0.6.13",
@ -965,6 +963,12 @@ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.1.4" version = "0.1.4"
@ -989,7 +993,7 @@ dependencies = [
"byteorder 1.3.4", "byteorder 1.3.4",
"lazy_static", "lazy_static",
"log 0.4.8", "log 0.4.8",
"nom", "nom 4.2.3",
] ]
[[package]] [[package]]
@ -1400,6 +1404,40 @@ dependencies = [
"lzw", "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]] [[package]]
name = "gio" name = "gio"
version = "0.4.1" version = "0.4.1"
@ -1882,6 +1920,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c346c299e3fe8ef94dc10c2c0253d858a69aac1245157a3bf4125915d528caf" 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]] [[package]]
name = "iovec" name = "iovec"
version = "0.1.4" version = "0.1.4"
@ -2017,6 +2065,16 @@ dependencies = [
"vcpkg", "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]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.0.25" version = "1.0.25"
@ -2087,6 +2145,15 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -2287,6 +2354,19 @@ dependencies = [
"void", "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]] [[package]]
name = "nodrop" name = "nodrop"
version = "0.1.14" version = "0.1.14"
@ -2312,6 +2392,16 @@ dependencies = [
"version_check 0.1.5", "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]] [[package]]
name = "notify" name = "notify"
version = "5.0.0-pre.2" version = "5.0.0-pre.2"
@ -2977,7 +3067,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"libc", "libc",
"rand_chacha 0.2.1", "rand_chacha 0.2.2",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc 0.2.0", "rand_hc 0.2.0",
"rand_pcg 0.2.1", "rand_pcg 0.2.1",
@ -2995,11 +3085,11 @@ dependencies = [
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [ dependencies = [
"c2-chacha", "ppv-lite86",
"rand_core 0.5.1", "rand_core 0.5.1",
] ]
@ -3403,6 +3493,17 @@ dependencies = [
"stb_truetype", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.2" version = "1.0.2"
@ -3660,7 +3761,7 @@ dependencies = [
"dlib", "dlib",
"lazy_static", "lazy_static",
"memmap", "memmap",
"nix", "nix 0.14.1",
"wayland-client 0.21.13", "wayland-client 0.21.13",
"wayland-commons 0.21.13", "wayland-commons 0.21.13",
"wayland-protocols 0.21.13", "wayland-protocols 0.21.13",
@ -3677,18 +3778,18 @@ dependencies = [
"dlib", "dlib",
"lazy_static", "lazy_static",
"memmap", "memmap",
"nix", "nix 0.14.1",
"wayland-client 0.23.6", "wayland-client 0.23.6",
"wayland-protocols 0.23.6", "wayland-protocols 0.23.6",
] ]
[[package]] [[package]]
name = "smithay-clipboard" name = "smithay-clipboard"
version = "0.3.7" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a59486e68b5596f664deedf01c46297f4af60379adae20175357a814d40f69e" checksum = "917e8ec7f535cd1a6cbf749c8866c24d67c548a80ac48c8e88a182eab5c07bd1"
dependencies = [ dependencies = [
"nix", "nix 0.14.1",
"smithay-client-toolkit 0.6.6", "smithay-client-toolkit 0.6.6",
] ]
@ -3751,6 +3852,57 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" 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]] [[package]]
name = "sum_type" name = "sum_type"
version = "0.2.0" version = "0.2.0"
@ -4090,6 +4242,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
[[package]] [[package]]
name = "uvth" name = "uvth"
version = "3.1.1" version = "3.1.1"
@ -4107,6 +4265,12 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]] [[package]]
name = "vek" name = "vek"
version = "0.9.12" version = "0.9.12"
@ -4239,6 +4403,7 @@ dependencies = [
"gfx", "gfx",
"gfx_device_gl", "gfx_device_gl",
"gfx_window_glutin", "gfx_window_glutin",
"gilrs",
"git2", "git2",
"glsl-include", "glsl-include",
"glutin", "glutin",
@ -4285,7 +4450,7 @@ dependencies = [
"packed_simd", "packed_simd",
"pretty_env_logger", "pretty_env_logger",
"rand 0.7.3", "rand 0.7.3",
"rand_chacha 0.2.1", "rand_chacha 0.2.2",
"rayon", "rayon",
"ron", "ron",
"roots", "roots",
@ -4417,7 +4582,7 @@ dependencies = [
"bitflags", "bitflags",
"downcast-rs", "downcast-rs",
"libc", "libc",
"nix", "nix 0.14.1",
"wayland-commons 0.21.13", "wayland-commons 0.21.13",
"wayland-scanner 0.21.13", "wayland-scanner 0.21.13",
"wayland-sys 0.21.13", "wayland-sys 0.21.13",
@ -4432,7 +4597,7 @@ dependencies = [
"bitflags", "bitflags",
"downcast-rs", "downcast-rs",
"libc", "libc",
"nix", "nix 0.14.1",
"wayland-commons 0.23.6", "wayland-commons 0.23.6",
"wayland-scanner 0.23.6", "wayland-scanner 0.23.6",
"wayland-sys 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec"
dependencies = [ dependencies = [
"nix", "nix 0.14.1",
"wayland-sys 0.21.13", "wayland-sys 0.21.13",
] ]
@ -4454,7 +4619,7 @@ version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb"
dependencies = [ dependencies = [
"nix", "nix 0.14.1",
"wayland-sys 0.23.6", "wayland-sys 0.23.6",
] ]

View File

@ -35,6 +35,9 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
# Mathematics # Mathematics
vek = { version = "0.9.9", features = ["serde"] } vek = { version = "0.9.9", features = ["serde"] }
# Controller
gilrs = { version = "0.7", features = ["serde"] }
# Singleplayer # Singleplayer
server = { package = "veloren-server", path = "../server", optional = true } server = { package = "veloren-server", path = "../server", optional = true }

329
voxygen/src/controller.rs Normal file
View File

@ -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<Button, Vec<GameInput>>,
pub menu_button_map: HashMap<Button, Vec<MenuInput>>,
pub game_analog_button_map: HashMap<AnalogButton, Vec<AnalogButtonGameAction>>,
pub menu_analog_button_map: HashMap<AnalogButton, Vec<AnalogButtonMenuAction>>,
pub game_axis_map: HashMap<Axis, Vec<AxisGameAction>>,
pub menu_axis_map: HashMap<Axis, Vec<AxisMenuAction>>,
pub pan_sensitivity: u32,
pub axis_deadzones: HashMap<Axis, f32>,
pub button_deadzones: HashMap<AnalogButton, f32>,
pub mouse_emulation_sensitivity: u32,
pub inverted_axes: Vec<Axis>,
}
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),
}
}
}

View File

@ -5,6 +5,7 @@ pub struct KeyState {
pub left: bool, pub left: bool,
pub up: bool, pub up: bool,
pub down: bool, pub down: bool,
pub analog_matrix: Vec2<f32>,
} }
impl KeyState { impl KeyState {
@ -14,16 +15,21 @@ impl KeyState {
left: false, left: false,
up: false, up: false,
down: false, down: false,
analog_matrix: Vec2::zero(),
} }
} }
pub fn dir_vec(&self) -> Vec2<f32> { pub fn dir_vec(&self) -> Vec2<f32> {
let dir = Vec2::<f32>::new( let dir = if self.analog_matrix == Vec2::zero() {
Vec2::<f32>::new(
if self.right { 1.0 } else { 0.0 } + if self.left { -1.0 } else { 0.0 }, 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 }, 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 dir
} else { } else {
dir.normalized() dir.normalized()

View File

@ -6,6 +6,7 @@
pub mod ui; pub mod ui;
pub mod anim; pub mod anim;
pub mod audio; pub mod audio;
pub mod controller;
mod ecs; mod ecs;
pub mod error; pub mod error;
pub mod hud; pub mod hud;

View File

@ -15,7 +15,7 @@ use crate::{
create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals,
PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline,
}, },
window::Event, window::{AnalogGameInput, Event},
}; };
use common::{ use common::{
comp, comp,
@ -50,6 +50,7 @@ pub struct Scene {
lights: Consts<Light>, lights: Consts<Light>,
shadows: Consts<Shadow>, shadows: Consts<Shadow>,
camera: Camera, camera: Camera,
camera_input_state: Vec2<f32>,
skybox: Skybox, skybox: Skybox,
postprocess: PostProcess, postprocess: PostProcess,
@ -85,6 +86,7 @@ impl Scene {
.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]) .create_consts(&[Shadow::default(); MAX_SHADOW_COUNT])
.unwrap(), .unwrap(),
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
camera_input_state: Vec2::zero(),
skybox: Skybox { skybox: Skybox {
model: renderer.create_model(&create_skybox_mesh()).unwrap(), 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)); .zoom_switch(delta * (0.05 + self.camera.get_distance() * 0.01));
true 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 // All other events are unhandled
_ => false, _ => false,
} }
@ -185,6 +198,12 @@ impl Scene {
_ => 1_f32, _ => 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. // Alter camera position to match player.
let tilt = self.camera.get_orientation().y; let tilt = self.camera.get_orientation().y;
let dist = self.camera.get_distance(); let dist = self.camera.get_distance();

View File

@ -5,7 +5,7 @@ use crate::{
key_state::KeyState, key_state::KeyState,
render::Renderer, render::Renderer,
scene::{camera, Scene, SceneData}, scene::{camera, Scene, SceneData},
window::{Event, GameInput}, window::{AnalogGameInput, Event, GameInput},
Direction, Error, GlobalState, PlayState, PlayStateResult, Direction, Error, GlobalState, PlayState, PlayStateResult,
}; };
use client::{self, Client, Event::Chat}; use client::{self, Client, Event::Chat};
@ -365,6 +365,17 @@ impl PlayState for SessionState {
Event::InputUpdate(GameInput::Charge, state) => { Event::InputUpdate(GameInput::Charge, state) => {
self.inputs.charge.set_state(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 // Pass all other events to the scene
event => { event => {

View File

@ -7,7 +7,7 @@ use crate::{
}; };
use directories::{ProjectDirs, UserDirs}; use directories::{ProjectDirs, UserDirs};
use glutin::{MouseButton, VirtualKeyCode}; use glutin::{MouseButton, VirtualKeyCode};
use hashbrown::HashSet; use hashbrown::{HashMap, HashSet};
use log::warn; use log::warn;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{fs, io::prelude::*, path::PathBuf}; 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<crate::controller::Axis, f32>,
pub button_deadzones: HashMap<crate::controller::AnalogButton, f32>,
pub mouse_emulation_sensitivity: u32,
pub inverted_axes: Vec<crate::controller::Axis>,
}
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. /// `GameplaySettings` contains sensitivity and gameplay options.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
@ -298,6 +517,7 @@ pub struct Settings {
pub logon_commands: Vec<String>, pub logon_commands: Vec<String>,
pub language: LanguageSettings, pub language: LanguageSettings,
pub screenshots_path: PathBuf, pub screenshots_path: PathBuf,
pub controller: GamepadSettings,
} }
impl Default for Settings { impl Default for Settings {
@ -329,6 +549,7 @@ impl Default for Settings {
logon_commands: Vec::new(), logon_commands: Vec::new(),
language: LanguageSettings::default(), language: LanguageSettings::default(),
screenshots_path, screenshots_path,
controller: GamepadSettings::default(),
} }
} }
} }

View File

@ -634,8 +634,8 @@ impl Ui {
mesh.push_quad(create_ui_quad( mesh.push_quad(create_ui_quad(
gl_aabr(rect), gl_aabr(rect),
Aabr { Aabr {
min: Vec2::new(0.0, 0.0), min: Vec2::zero(),
max: Vec2::new(0.0, 0.0), max: Vec2::zero(),
}, },
color, color,
UiMode::Geometry, UiMode::Geometry,

View File

@ -1,15 +1,17 @@
use crate::{ use crate::{
controller::*,
render::{Renderer, WinColorFmt, WinDepthFmt}, render::{Renderer, WinColorFmt, WinDepthFmt},
settings::Settings, settings::Settings,
ui, Error, ui, Error,
}; };
use gilrs::{EventType, Gilrs};
use hashbrown::HashMap; use hashbrown::HashMap;
use log::{error, warn}; use log::{error, warn};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use vek::*; 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum GameInput { pub enum GameInput {
Primary, Primary,
@ -49,6 +51,40 @@ pub enum GameInput {
Charge, 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. /// Represents an incoming event from the window.
#[derive(Clone)] #[derive(Clone)]
pub enum Event { pub enum Event {
@ -76,6 +112,13 @@ pub enum Event {
SettingsChanged, SettingsChanged,
/// The window is (un)focused /// The window is (un)focused
Focused(bool), 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; pub type MouseButton = winit::MouseButton;
@ -277,6 +320,10 @@ pub struct Window {
keypress_map: HashMap<GameInput, glutin::ElementState>, keypress_map: HashMap<GameInput, glutin::ElementState>,
supplement_events: Vec<Event>, supplement_events: Vec<Event>,
focused: bool, focused: bool,
gilrs: Option<Gilrs>,
controller_settings: ControllerSettings,
cursor_position: winit::dpi::LogicalPosition,
mouse_emulation_vec: Vec2<f32>,
} }
impl Window { impl Window {
@ -414,6 +461,30 @@ impl Window {
let keypress_map = HashMap::new(); 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 { let mut this = Self {
events_loop, events_loop,
renderer: Renderer::new( renderer: Renderer::new(
@ -437,6 +508,10 @@ impl Window {
keypress_map, keypress_map,
supplement_events: vec![], supplement_events: vec![],
focused: true, 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); this.fullscreen(settings.graphics.fullscreen);
@ -477,6 +552,7 @@ impl Window {
}; };
let mut toggle_fullscreen = false; let mut toggle_fullscreen = false;
let mut take_screenshot = false; let mut take_screenshot = false;
let mut cursor_position = None;
self.events_loop.poll_events(|event| { self.events_loop.poll_events(|event| {
// Get events for ui. // Get events for ui.
@ -556,6 +632,9 @@ impl Window {
*focused = state; *focused = state;
events.push(Event::Focused(state)); events.push(Event::Focused(state));
}, },
glutin::WindowEvent::CursorMoved { position, .. } => {
cursor_position = Some(position);
},
_ => {}, _ => {},
}, },
glutin::Event::DeviceEvent { event, .. } => match event { 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 { if take_screenshot {
self.take_screenshot(&settings); self.take_screenshot(&settings);
} }
@ -601,9 +684,217 @@ impl Window {
self.toggle_fullscreen(settings); 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<Event>,
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 events
} }
/// Moves cursor by an offset
pub fn offset_cursor(&self, d: Vec2<f32>) -> 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> { pub fn swap_buffers(&self) -> Result<(), Error> {
self.window self.window
.swap_buffers() .swap_buffers()