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 24, 2019
1 parent a195ce8 commit 85110e1
Show file tree
Hide file tree
Showing 24 changed files with 2,074 additions and 475 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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
10 changes: 3 additions & 7 deletions examples/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@ fn main() {
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
},
}),
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
Expand Down
14 changes: 5 additions & 9 deletions examples/cursor_grab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +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
52 changes: 52 additions & 0 deletions examples/gamepad.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use winit::{
event::{
device::{GamepadEvent, GamepadHandle},
Event, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};

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,
_ => (),
}
});
}
60 changes: 60 additions & 0 deletions examples/gamepad_rumble.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::time::Instant;
use winit::event_loop::EventLoop;

#[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 @@ -41,15 +41,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 {
Y => {
if close_requested {
Expand Down
24 changes: 8 additions & 16 deletions examples/multithreaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,12 @@ fn main() {
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
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 @@ -105,14 +101,10 @@ fn main() {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
| WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Escape),
..
} => {
}) => {
window_senders.remove(&window_id);
}
_ => {
Expand Down
10 changes: 3 additions & 7 deletions examples/multiwindow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@ 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 @@ -21,15 +21,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 85110e1

Please sign in to comment.