Skip to content

Commit

Permalink
Overhaul device events API and add gamepad support on Windows (#804)
Browse files Browse the repository at this point in the history
* Initial implementation

* Corrected RAWINPUT buffer sizing

* Mostly complete XInput implementation

* XInput triggers

* Add preliminary CHANGELOG entry.

* match unix common API to evl 2.0

* wayland: eventloop2.0

* make EventLoopProxy require T: 'static

* Revamp device event API, as well as several misc. fixes on Windows:

* When you have multiple windows, you no longer receive duplicate device
  events
* Mouse Device Events now send X-button input
* Mouse Device Events now send horizontal scroll wheel input

* Add MouseEvent documentation and Device ID debug passthrough

* Improve type safety on get_raw_input_data

* Remove button_id field from MouseEvent::Button in favor of �utton

* Remove regex dependency on Windows

* Remove axis filtering in XInput

* Make gamepads not use lazy_static

* Publicly expose gamepad rumble

* Unstack DeviceEvent and fix examples/tests

* Add HANDLE retrieval method to DeviceExtWindows

* Add distinction between non-joystick axes and joystick axes.

This helps with properly calculating the deadzone for controller
joysticks. One potential issue is that the `Stick` variant isn't used
for *all* joysticks, which could be potentially confusing - for example,
raw input joysticks will never use the `Stick` variant because we don't
understand the semantic meaning of raw input joystick axes.

* Add ability to get gamepad port

* Fix xinput controller hot swapping

* Add functions for enumerating attached devices

* Clamp input to [0.0, 1.0] on gamepad rumble

* Expose gamepad rumble errors

* Add method to check if device is still connected

* Add docs

* Rename AxisHint and ButtonHint to GamepadAxis and GamepadButton

* Add CHANGELOG entry

* Update CHANGELOG.md

* Add HidId and MovedAbsolute

* Fix xinput deprecation warnings

* Add ability to retrieve gamepad battery level

* Fix weird imports in gamepad example

* Update CHANGELOG.md

* Resolve francesca64 comments
  • Loading branch information
Osspial committed Jun 21, 2019
1 parent b1b5aef commit afcbcfa
Show file tree
Hide file tree
Showing 24 changed files with 1,996 additions and 423 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.

- Improve event API documentation.
- Overhaul device event API:
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
- **Breaking**: Remove `DeviceEvent::Text` variant.
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
- **Breaking**: Removed device IDs from `WindowEvent` variants.
- Add `enumerate` function on device ID types to list all attached devices of that type.
- Add `is_connected` function on device ID types check if the specified device is still available.
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
- Add `handle` function to retrieve the underlying `HANDLE`.
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
- Added gamepad support on Windows via raw input and XInput.

# Version 0.19.1 (2019-04-08)

- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ objc = "0.2.3"

[target.'cfg(target_os = "windows")'.dependencies]
bitflags = "1"
rusty-xinput = "1.0"

[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
Expand All @@ -49,6 +50,7 @@ features = [
"commctrl",
"dwmapi",
"errhandlingapi",
"hidpi",
"hidusage",
"libloaderapi",
"objbase",
Expand All @@ -64,6 +66,7 @@ features = [
"wingdi",
"winnt",
"winuser",
"xinput",
]

[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion examples/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() {

event_loop.run(move |event, _, control_flow| {
match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
Event::WindowEvent { event: WindowEvent::KeyboardInput(KeyboardInput { state: ElementState::Pressed, .. }), .. } => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
Expand Down
13 changes: 5 additions & 8 deletions examples/cursor_grab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ fn main() {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input: KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,
Expand Down
12 changes: 4 additions & 8 deletions examples/fullscreen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,11 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
} => match (virtual_code, state) {
}) => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
#[cfg(target_os = "macos")]
Expand Down
41 changes: 41 additions & 0 deletions examples/gamepad.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
extern crate winit;
use winit::window::WindowBuilder;
use winit::event::{Event, WindowEvent};
use winit::event::device::{GamepadEvent, GamepadHandle};
use winit::event_loop::{EventLoop, ControlFlow};

fn main() {
let event_loop = EventLoop::new();

let _window = WindowBuilder::new()
.with_title("The world's worst video game")
.build(&event_loop)
.unwrap();

println!("enumerating gamepads:");
for gamepad in GamepadHandle::enumerate(&event_loop) {
println!(" gamepad={:?}\tport={:?}\tbattery level={:?}", gamepad, gamepad.port(), gamepad.battery_level());
}

let deadzone = 0.12;

event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
// Discard any Axis events that has a corresponding Stick event.
GamepadEvent::Axis{stick: true, ..} => (),

// Discard any Stick event that falls inside the stick's deadzone.
GamepadEvent::Stick{x_value, y_value, ..}
if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone
=> (),

_ => println!("[{:?}] {:#?}", gamepad_handle, event)
}
},
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit,
_ => ()
}
});
}
58 changes: 58 additions & 0 deletions examples/gamepad_rumble.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
extern crate winit;
use winit::event_loop::EventLoop;
use std::time::Instant;

#[derive(Debug, Clone)]
enum Rumble {
None,
Left,
Right,
}

fn main() {
let event_loop = EventLoop::new();

// You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will
// allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here
// because it makes this example more concise.
let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::<Vec<_>>();

let rumble_patterns = &[
(0.5, Rumble::None),
(2.0, Rumble::Left),
(0.5, Rumble::None),
(2.0, Rumble::Right),
];
let mut rumble_iter = rumble_patterns.iter().cloned().cycle();

let mut active_pattern = rumble_iter.next().unwrap();
let mut timeout = active_pattern.0;
let mut timeout_start = Instant::now();

event_loop.run(move |_, _, _| {
if timeout <= active_pattern.0 {
let t = (timeout / active_pattern.0) * std::f64::consts::PI;
let intensity = t.sin();

for g in &gamepads {
let result = match active_pattern.1 {
Rumble::Left => g.rumble(intensity, 0.0),
Rumble::Right => g.rumble(0.0, intensity),
Rumble::None => Ok(()),
};

if let Err(e) = result {
println!("Rumble failed: {:?}", e);
}
}

timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0;
} else {
active_pattern = rumble_iter.next().unwrap();
println!("Rumbling {:?} for {:?} seconds", active_pattern.1, active_pattern.0);

timeout = 0.0;
timeout_start = Instant::now();
}
});
}
12 changes: 4 additions & 8 deletions examples/handling_close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,11 @@ fn main() {
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
} => match virtual_code {
}) => match virtual_code {
Y => {
if close_requested {
// This is where you'll want to do any cleanup you need.
Expand Down
8 changes: 4 additions & 4 deletions examples/multithreaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ fn main() {
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
WindowEvent::KeyboardInput { input: KeyboardInput {
WindowEvent::KeyboardInput (KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
}, .. } => {
}) => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
use self::VirtualKeyCode::*;
Expand Down Expand Up @@ -99,9 +99,9 @@ fn main() {
match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput { input: KeyboardInput {
| WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Escape),
.. }, .. } => {
.. }) => {
window_senders.remove(&window_id);
},
_ => if let Some(tx) = window_senders.get(&window_id) {
Expand Down
2 changes: 1 addition & 1 deletion examples/multiwindow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn main() {
*control_flow = ControlFlow::Exit;
}
},
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
WindowEvent::KeyboardInput(KeyboardInput { state: ElementState::Pressed, .. }) => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
},
Expand Down
12 changes: 4 additions & 8 deletions examples/resizable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,11 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
} => {
}) => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);
Expand Down
Loading

0 comments on commit afcbcfa

Please sign in to comment.