mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 14:49:07 -04:00
Gamepad device events - Web/WASM (#1414)
Add gamepad support for stdweb and web-sys, as well as web-specific gamepad examples. * [web] Fix compilation error from device api * [wasm] Apply device api changes * [wasm] Format and cleanup * [wasm32] Implement gamepad connections * [wasm] Harmonize * [Test] Made some tests with wasm-pack * Quick fix instant non supporting Hash trait * Fix on_received_character * [web_sys] Split add_event and add_window_event * [web] split device implementations * Update tests/web...still does not work * [tests/web] do not ignore index.html * [web/web_sys] split canvas and window * [tests/web] enable stack trace * [web] fix borrowmut * [web_sys] fix gamepad registration * [web] harmonize naming * [web_sys] create global emitter * [web] implement gamepad buttons * [web] implement gamepad axis * [web] cleanup * [web] update test * [web] move tests/web to examples/web * [web] axis does produce stick event * [web] Support Stick event * [web] implement gamepad to stdweb * [web] rename examples/web to examples/wasm * [web/web-sys] Move gamepad_manager from backend * [web/web_sys] implement EventLoop::gamepads * [web/web_sys] Drain gamepad events * [web/stdweb] apply web_sys changes * [web] update web/examples * [web] move gamepads code to gamepad_manager * [web] simplify and optimise * [web] replace EventCode to GamepadAxis and GamepadButton structs * [web] reuse gamepad events due to chrome issue * [web] rumble does not work * [web/stdweb] try debugging * [web] fix Chrome gamepad not updated * [web/stdweb] created an example * [examples] fix paths * fix warnings * [web/examples] update comments * [web/stdweb] add experimental support to vibrate() * [web] add CR
This commit is contained in:
committed by
GitHub
parent
0729074ce3
commit
e004bd2bb3
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
.cargo/
|
||||
util/
|
||||
*~
|
||||
*.wasm
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -106,9 +106,24 @@ features = [
|
||||
'KeyboardEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'Navigator',
|
||||
'PointerEvent',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
'WheelEvent',
|
||||
'Gamepad',
|
||||
'GamepadAxisMoveEvent',
|
||||
'GamepadAxisMoveEventInit',
|
||||
'GamepadButton',
|
||||
'GamepadButtonEvent',
|
||||
'GamepadButtonEventInit',
|
||||
'GamepadEvent',
|
||||
'GamepadEventInit',
|
||||
'GamepadHand',
|
||||
'GamepadHapticActuator',
|
||||
'GamepadHapticActuatorType',
|
||||
'GamepadMappingType',
|
||||
'GamepadPose',
|
||||
'GamepadServiceTest'
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||
|
||||
11
examples/web/gamepad/stdweb/Cargo.toml
Normal file
11
examples/web/gamepad/stdweb/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "stdweb-gamepad"
|
||||
version = "0.1.0"
|
||||
authors = ["furiouzz <christophe.massolin@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
winit = { path = "../../../../", features = [ "stdweb" ] }
|
||||
stdweb = "0.4.20"
|
||||
80
examples/web/gamepad/stdweb/src/main.rs
Normal file
80
examples/web/gamepad/stdweb/src/main.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use winit::{
|
||||
event::{device::GamepadEvent, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
use stdweb::js;
|
||||
|
||||
/**
|
||||
* Build example (from examples/web/gamepad/stdweb):
|
||||
* cargo web build
|
||||
* Run example (from examples/web/gamepad/stdweb):
|
||||
* cargo web start
|
||||
* Development (from project root):
|
||||
* npx nodemon --watch src --watch examples/web/gamepad/stdweb/src -e rs --exec 'cargo web check'
|
||||
*/
|
||||
|
||||
pub fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Gamepad tests")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let deadzone = 0.12;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::GamepadEvent(gamepad_handle, event) => match event {
|
||||
GamepadEvent::Axis {
|
||||
axis_id,
|
||||
axis,
|
||||
value,
|
||||
stick,
|
||||
} if value > deadzone => {
|
||||
let string = format!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick);
|
||||
js! { console.log( @{string} ); }
|
||||
}
|
||||
|
||||
GamepadEvent::Stick {
|
||||
x_id,
|
||||
y_id,
|
||||
x_value,
|
||||
y_value,
|
||||
side,
|
||||
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
|
||||
let string = format!(
|
||||
"Stick {:#?} {:#?} {:#?} {:#?} {:#?}",
|
||||
x_id, y_id, x_value, y_value, side
|
||||
);
|
||||
js! { console.log( @{string} ); }
|
||||
}
|
||||
|
||||
GamepadEvent::Button {
|
||||
button_id,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let string = format!("Button {:#?} {:#?} {:#?}", button_id, button, state);
|
||||
js! { console.log( @{string} ); }
|
||||
}
|
||||
|
||||
GamepadEvent::Added => {
|
||||
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
|
||||
js! { console.log( @{string} ); }
|
||||
}
|
||||
GamepadEvent::Removed => {
|
||||
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
|
||||
js! { console.log( @{string} ); }
|
||||
}
|
||||
|
||||
_ => {}
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
7
examples/web/gamepad/websys/.gitignore
vendored
Normal file
7
examples/web/gamepad/websys/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
.DS_Store
|
||||
20
examples/web/gamepad/websys/Cargo.toml
Normal file
20
examples/web/gamepad/websys/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "websys-gamepad"
|
||||
version = "0.0.1"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
winit = { path = "../../../../", features = [ "web-sys" ] }
|
||||
wasm-bindgen = "0.2.45"
|
||||
wasm-bindgen-test = "0.3.8"
|
||||
web-sys = { version = "0.3.22", features = [ "console" ] }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = "0.1.6"
|
||||
23
examples/web/gamepad/websys/files/gamepad.html
Normal file
23
examples/web/gamepad/websys/files/gamepad.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Gamepad</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="my_id"></canvas>
|
||||
<script>
|
||||
window.Module = {
|
||||
canvas: document.getElementById('my_id')
|
||||
}
|
||||
</script>
|
||||
<script type="module">
|
||||
import example_gamepad from "../pkg/websys_examples.js"
|
||||
example_gamepad()
|
||||
.then((m) => console.log('Success', m))
|
||||
.catch((e) => console.log('Ar error occured', e))
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
78
examples/web/gamepad/websys/src/lib.rs
Normal file
78
examples/web/gamepad/websys/src/lib.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
mod utils;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::{
|
||||
event::{device::GamepadEvent, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Build example (from examples/gamepad/websys):
|
||||
* wasm-pack build --target web
|
||||
* Run web server (from examples/gamepad/websys):
|
||||
* npx http-server
|
||||
* Open your browser at http://localhost:8000/files/${EXAMPLE}.html
|
||||
* Development (from project root):
|
||||
* npx nodemon --watch src --watch examples/web/gamepad/websys/src -e rs --exec 'cd examples/web/gamepad/websys && wasm-pack build --target web'
|
||||
*/
|
||||
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn example_gamepad() {
|
||||
utils::set_panic_hook(); // needed for error stack trace
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Gamepad tests")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let deadzone = 0.12;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
Event::GamepadEvent(gamepad_handle, event) => {
|
||||
match event {
|
||||
GamepadEvent::Axis {
|
||||
axis_id,
|
||||
axis,
|
||||
value,
|
||||
stick,
|
||||
} if value > deadzone => {
|
||||
console_log!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick)
|
||||
},
|
||||
|
||||
GamepadEvent::Stick {
|
||||
x_id, y_id, x_value, y_value, side
|
||||
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
|
||||
console_log!("Stick {:#?} {:#?} {:#?} {:#?} {:#?}", x_id, y_id, x_value, y_value, side)
|
||||
},
|
||||
|
||||
GamepadEvent::Button {
|
||||
button_id,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
console_log!("Button {:#?} {:#?} {:#?}", button_id, button, state)
|
||||
},
|
||||
|
||||
GamepadEvent::Added => {
|
||||
console_log!("[{:?}] {:#?}", gamepad_handle, event)
|
||||
},
|
||||
GamepadEvent::Removed => console_log!("[{:?}] {:#?}", gamepad_handle, event),
|
||||
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
10
examples/web/gamepad/websys/src/utils.rs
Normal file
10
examples/web/gamepad/websys/src/utils.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
// #[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl<T> Event<T> {
|
||||
}
|
||||
|
||||
/// The reason the event loop is resuming.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StartCause {
|
||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
||||
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Id(pub i32);
|
||||
|
||||
impl Id {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
Id(0)
|
||||
}
|
||||
}
|
||||
37
src/platform_impl/web/device/gamepad/constants.rs
Normal file
37
src/platform_impl/web/device/gamepad/constants.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::event::device::{GamepadAxis, GamepadButton};
|
||||
|
||||
pub(crate) static BUTTONS: [GamepadButton; 16] = [
|
||||
GamepadButton::South,
|
||||
GamepadButton::East,
|
||||
GamepadButton::West,
|
||||
GamepadButton::North,
|
||||
GamepadButton::LeftTrigger,
|
||||
GamepadButton::RightTrigger,
|
||||
GamepadButton::LeftShoulder,
|
||||
GamepadButton::RightShoulder,
|
||||
GamepadButton::Select,
|
||||
GamepadButton::Start,
|
||||
GamepadButton::LeftStick,
|
||||
GamepadButton::RightStick,
|
||||
GamepadButton::DPadUp,
|
||||
GamepadButton::DPadDown,
|
||||
GamepadButton::DPadLeft,
|
||||
GamepadButton::DPadRight,
|
||||
];
|
||||
|
||||
pub(crate) static AXES: [GamepadAxis; 6] = [
|
||||
GamepadAxis::LeftStickX,
|
||||
GamepadAxis::LeftStickY,
|
||||
GamepadAxis::RightStickX,
|
||||
GamepadAxis::RightStickY,
|
||||
GamepadAxis::LeftTrigger,
|
||||
GamepadAxis::RightTrigger,
|
||||
];
|
||||
|
||||
pub(crate) fn button_code(index: usize) -> Option<GamepadButton> {
|
||||
BUTTONS.get(index).map(|ev| ev.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn axis_code(index: usize) -> Option<GamepadAxis> {
|
||||
AXES.get(index).map(|ev| ev.clone())
|
||||
}
|
||||
167
src/platform_impl/web/device/gamepad/manager.rs
Normal file
167
src/platform_impl/web/device/gamepad/manager.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use super::utils;
|
||||
use crate::event::device;
|
||||
use crate::platform_impl::platform::{backend, device::gamepad, GamepadHandle, event_loop::global};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct Manager {
|
||||
pub(crate) gamepads: Vec<backend::gamepad::Gamepad>,
|
||||
pub(crate) events: VecDeque<(backend::gamepad::Gamepad, device::GamepadEvent)>,
|
||||
pub(crate) global_window: Option<global::Shared>,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gamepads: Vec::new(),
|
||||
events: VecDeque::new(),
|
||||
global_window: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Register global window to fetch gamepads.
|
||||
// Due to Chrome issue, I prefer to use its gamepad list
|
||||
pub fn set_global_window(&mut self, global_window: global::Shared) {
|
||||
self.global_window.replace(global_window);
|
||||
}
|
||||
|
||||
// Get an updated raw gamepad and generate a new mapping
|
||||
pub fn collect_gamepads(&self) -> Option<Vec<backend::gamepad::Gamepad>> {
|
||||
self.global_window.as_ref().map(|w| w.get_gamepads())
|
||||
}
|
||||
|
||||
// Collect gamepad events (buttons/axes/sticks)
|
||||
// dispatch to handler and update gamepads
|
||||
pub fn collect_events<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut((device::GamepadHandle, device::GamepadEvent)),
|
||||
{
|
||||
let opt_new_gamepads = self.collect_gamepads();
|
||||
if opt_new_gamepads.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_gamepads = opt_new_gamepads.unwrap();
|
||||
let old_gamepads = &self.gamepads;
|
||||
|
||||
let mut old_index = 0;
|
||||
let mut new_index = 0;
|
||||
|
||||
// Collect events
|
||||
loop {
|
||||
match (old_gamepads.get(old_index), new_gamepads.get(new_index)) {
|
||||
(Some(old), Some(new)) if old.index() == new.index() => {
|
||||
// Button events
|
||||
let buttons = old.mapping.buttons().zip(new.mapping.buttons()).enumerate();
|
||||
for (btn_index, (old_button, new_button)) in buttons {
|
||||
match (old_button, new_button) {
|
||||
(false, true) => {
|
||||
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, true)))
|
||||
}
|
||||
(true, false) => {
|
||||
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, false)))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Axis events
|
||||
let axes = old.mapping.axes().zip(new.mapping.axes()).enumerate();
|
||||
for (axis_index, (old_axis, new_axis)) in axes {
|
||||
if old_axis != new_axis {
|
||||
self.events.push_back((new.clone(), utils::gamepad_axis(axis_index, new_axis)))
|
||||
}
|
||||
}
|
||||
|
||||
// Stick events
|
||||
let mut old_axes = old.mapping.axes();
|
||||
let mut new_axes = new.mapping.axes();
|
||||
|
||||
let old_left = (old_axes.next(), old_axes.next());
|
||||
let new_left = (new_axes.next(), new_axes.next());
|
||||
if old_left != new_left {
|
||||
if let (Some(x), Some(y)) = (new_left.0, new_left.1) {
|
||||
self.events.push_back((
|
||||
new.clone(),
|
||||
utils::gamepad_stick(0, 1, x, y, device::Side::Left),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let old_right = (old_axes.next(), old_axes.next());
|
||||
let new_right = (new_axes.next(), new_axes.next());
|
||||
if old_right != new_right {
|
||||
if let (Some(x), Some(y)) = (new_right.0, new_right.1) {
|
||||
self.events.push_back((
|
||||
new.clone(),
|
||||
utils::gamepad_stick(2, 3, x, y, device::Side::Right),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Increment indices
|
||||
old_index += 1;
|
||||
new_index += 1;
|
||||
},
|
||||
|
||||
// Connect
|
||||
(None, Some(new)) => {
|
||||
self.events.push_back((
|
||||
new.clone(),
|
||||
device::GamepadEvent::Added,
|
||||
));
|
||||
new_index += 1;
|
||||
},
|
||||
|
||||
// Connect
|
||||
(Some(old), Some(new)) if old.index > new.index => {
|
||||
self.events.push_back((
|
||||
new.clone(),
|
||||
device::GamepadEvent::Added,
|
||||
));
|
||||
new_index += 1;
|
||||
},
|
||||
|
||||
// Disconnect
|
||||
(Some(old), Some(_new)) => {
|
||||
self.events.push_back((
|
||||
old.clone(),
|
||||
device::GamepadEvent::Removed,
|
||||
));
|
||||
old_index += 1;
|
||||
},
|
||||
|
||||
// Disconnect
|
||||
(Some(old), None) => {
|
||||
self.events.push_back((
|
||||
old.clone(),
|
||||
device::GamepadEvent::Removed,
|
||||
));
|
||||
old_index += 1;
|
||||
},
|
||||
|
||||
// Break loop
|
||||
(None, None) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch events and drain events vec
|
||||
loop {
|
||||
if let Some((gamepad, event)) = self.events.pop_front() {
|
||||
handler((
|
||||
device::GamepadHandle(GamepadHandle {
|
||||
id: gamepad.index,
|
||||
gamepad: gamepad::Shared::Raw(gamepad),
|
||||
}),
|
||||
event,
|
||||
));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update gamepads
|
||||
self.gamepads = new_gamepads;
|
||||
}
|
||||
}
|
||||
23
src/platform_impl/web/device/gamepad/mapping.rs
Normal file
23
src/platform_impl/web/device/gamepad/mapping.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Mapping {
|
||||
Standard { buttons: [bool; 16], axes: [f64; 6] },
|
||||
NoMapping { buttons: Vec<bool>, axes: Vec<f64> },
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
pub(crate) fn buttons<'a>(&'a self) -> impl Iterator<Item = bool> + 'a {
|
||||
match self {
|
||||
Mapping::Standard { buttons, .. } => buttons.iter(),
|
||||
Mapping::NoMapping { buttons, .. } => buttons.iter(),
|
||||
}
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn axes<'a>(&'a self) -> impl Iterator<Item = f64> + 'a {
|
||||
match self {
|
||||
Mapping::Standard { axes, .. } => axes.iter(),
|
||||
Mapping::NoMapping { axes, .. } => axes.iter(),
|
||||
}
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
99
src/platform_impl/web/device/gamepad/mod.rs
Normal file
99
src/platform_impl/web/device/gamepad/mod.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
mod manager;
|
||||
mod mapping;
|
||||
mod utils;
|
||||
|
||||
pub mod constants;
|
||||
pub use manager::Manager;
|
||||
pub use mapping::Mapping;
|
||||
|
||||
use crate::event::device::{BatteryLevel, RumbleError};
|
||||
use crate::platform_impl::platform::backend;
|
||||
use std::fmt;
|
||||
|
||||
pub enum Shared {
|
||||
Raw(backend::gamepad::Gamepad),
|
||||
Dummy,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
// An integer that is auto-incremented to be unique for each device
|
||||
// currently connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
|
||||
pub fn id(&self) -> i32 {
|
||||
match self {
|
||||
Shared::Raw(g) => g.index() as i32,
|
||||
Shared::Dummy => -1,
|
||||
}
|
||||
}
|
||||
|
||||
// A string containing some information about the controller.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
|
||||
pub fn info(&self) -> String {
|
||||
match self {
|
||||
Shared::Raw(g) => g.id(),
|
||||
Shared::Dummy => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// A boolean indicating whether the gamepad is still connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
|
||||
pub fn connected(&self) -> bool {
|
||||
match self {
|
||||
Shared::Raw(g) => g.connected(),
|
||||
Shared::Dummy => false,
|
||||
}
|
||||
}
|
||||
|
||||
// [EXPERIMENTAL] An array containing GamepadHapticActuator objects,
|
||||
// each of which represents haptic feedback hardware available on the controller.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
|
||||
pub fn rumble(&self, left_speed: f64, _right_speed: f64) -> Result<(), RumbleError> {
|
||||
match self {
|
||||
Shared::Dummy => Ok(()),
|
||||
Shared::Raw(g) => {
|
||||
g.vibrate(left_speed, 1000f64);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dummy(&self) -> bool {
|
||||
match self {
|
||||
Shared::Dummy => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port(&self) -> Option<u8> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn battery_level(&self) -> Option<BatteryLevel> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Shared::Raw(g) => Shared::Raw(g.clone()),
|
||||
Shared::Dummy => Shared::Dummy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Shared {
|
||||
fn default() -> Self {
|
||||
Shared::Dummy
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Shared {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
if self.is_dummy() {
|
||||
write!(f, "Gamepad (Dummy)")
|
||||
} else {
|
||||
write!(f, "Gamepad ({}#{})", self.id(), self.info())
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/platform_impl/web/device/gamepad/utils.rs
Normal file
44
src/platform_impl/web/device/gamepad/utils.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use crate::event::{ElementState, device};
|
||||
use super::constants;
|
||||
|
||||
pub fn gamepad_button(code: usize, pressed: bool) -> device::GamepadEvent {
|
||||
let button_id = code as u32;
|
||||
let button = constants::button_code(code);
|
||||
|
||||
let state = if pressed {
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
|
||||
device::GamepadEvent::Button {
|
||||
button_id,
|
||||
button,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gamepad_axis(code: usize, value: f64) -> device::GamepadEvent {
|
||||
let axis_id = code as u32;
|
||||
let axis = constants::axis_code(code);
|
||||
|
||||
device::GamepadEvent::Axis {
|
||||
axis_id,
|
||||
axis,
|
||||
value,
|
||||
stick: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gamepad_stick(x_code: usize, y_code: usize, x_value: f64, y_value: f64, side: device::Side) -> device::GamepadEvent {
|
||||
let x_id = x_code as u32;
|
||||
let y_id = y_code as u32;
|
||||
|
||||
device::GamepadEvent::Stick {
|
||||
x_id,
|
||||
y_id,
|
||||
x_value,
|
||||
y_value,
|
||||
side,
|
||||
}
|
||||
}
|
||||
161
src/platform_impl/web/device/mod.rs
Normal file
161
src/platform_impl/web/device/mod.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
pub mod gamepad;
|
||||
|
||||
use super::event_loop::EventLoop;
|
||||
use crate::event::device;
|
||||
|
||||
use std::{
|
||||
cmp::{Eq, Ordering, PartialEq, PartialOrd},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct MouseId(pub i32);
|
||||
|
||||
unsafe impl Send for MouseId {}
|
||||
unsafe impl Sync for MouseId {}
|
||||
|
||||
impl MouseId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn enumerate<'a, T>(
|
||||
event_loop: &'a EventLoop<T>,
|
||||
) -> impl 'a + Iterator<Item = device::MouseId> {
|
||||
event_loop.mice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MouseId> for device::MouseId {
|
||||
fn from(platform_id: MouseId) -> Self {
|
||||
Self(platform_id)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct KeyboardId(pub i32);
|
||||
|
||||
unsafe impl Send for KeyboardId {}
|
||||
unsafe impl Sync for KeyboardId {}
|
||||
|
||||
impl KeyboardId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn enumerate<'a, T>(
|
||||
event_loop: &'a EventLoop<T>,
|
||||
) -> impl 'a + Iterator<Item = device::KeyboardId> {
|
||||
event_loop.keyboards()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyboardId> for device::KeyboardId {
|
||||
fn from(platform_id: KeyboardId) -> Self {
|
||||
Self(platform_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct HidId(pub i32);
|
||||
|
||||
unsafe impl Send for HidId {}
|
||||
unsafe impl Sync for HidId {}
|
||||
|
||||
impl HidId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn enumerate<'a, T>(
|
||||
event_loop: &'a EventLoop<T>,
|
||||
) -> impl 'a + Iterator<Item = device::HidId> {
|
||||
event_loop.hids()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HidId> for device::HidId {
|
||||
fn from(platform_id: HidId) -> Self {
|
||||
Self(platform_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct GamepadHandle {
|
||||
pub(crate) id: i32,
|
||||
pub(crate) gamepad: gamepad::Shared,
|
||||
}
|
||||
|
||||
unsafe impl Send for GamepadHandle {}
|
||||
unsafe impl Sync for GamepadHandle {}
|
||||
|
||||
impl GamepadHandle {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
Self {
|
||||
id: -1,
|
||||
gamepad: gamepad::Shared::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.gamepad.connected()
|
||||
}
|
||||
|
||||
pub fn enumerate<'a, T>(
|
||||
event_loop: &'a EventLoop<T>,
|
||||
) -> impl 'a + Iterator<Item = device::GamepadHandle> {
|
||||
event_loop.gamepads()
|
||||
}
|
||||
|
||||
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> {
|
||||
self.gamepad.rumble(left_speed, right_speed)
|
||||
}
|
||||
|
||||
pub fn port(&self) -> Option<u8> {
|
||||
self.gamepad.port()
|
||||
}
|
||||
|
||||
pub fn battery_level(&self) -> Option<device::BatteryLevel> {
|
||||
self.gamepad.battery_level()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for GamepadHandle {}
|
||||
|
||||
impl PartialEq for GamepadHandle {
|
||||
#[inline(always)]
|
||||
fn eq(&self, othr: &Self) -> bool {
|
||||
self.id == othr.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for GamepadHandle {
|
||||
#[inline(always)]
|
||||
fn cmp(&self, othr: &Self) -> Ordering {
|
||||
self.id.cmp(&othr.id)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for GamepadHandle {
|
||||
#[inline(always)]
|
||||
fn partial_cmp(&self, othr: &Self) -> Option<Ordering> {
|
||||
self.id.partial_cmp(&othr.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for GamepadHandle {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state)
|
||||
}
|
||||
}
|
||||
81
src/platform_impl/web/event_loop/global.rs
Normal file
81
src/platform_impl/web/event_loop/global.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use super::super::device::{gamepad, GamepadHandle};
|
||||
use super::backend;
|
||||
use crate::event::device;
|
||||
use std::{cell::RefCell, rc::Rc, collections::HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Window {
|
||||
raw: RefCell<Option<backend::window::Shared>>,
|
||||
gamepads: Rc<RefCell<HashSet<i32>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Shared(Rc<Window>);
|
||||
|
||||
impl Shared {
|
||||
pub fn new() -> Self {
|
||||
Self(Rc::new(Window {
|
||||
raw: RefCell::new(None),
|
||||
gamepads: Rc::new(RefCell::new(HashSet::new())),
|
||||
}))
|
||||
}
|
||||
|
||||
// Request window object and listen global events
|
||||
pub fn register_events(&self) -> Result<(), crate::error::OsError> {
|
||||
if (*self.0.raw.borrow()).is_none() {
|
||||
let shared = backend::window::Shared::create()?;
|
||||
let mut window = shared.0.borrow_mut();
|
||||
|
||||
let shared_gamepads = self.0.gamepads.clone();
|
||||
window.on_gamepad_connected(move |gamepad: backend::gamepad::Gamepad| {
|
||||
let mut gamepads = shared_gamepads.borrow_mut();
|
||||
let index = gamepad.index();
|
||||
if !gamepads.contains(&index) {
|
||||
gamepads.insert(index);
|
||||
}
|
||||
});
|
||||
|
||||
let shared_gamepads = self.0.gamepads.clone();
|
||||
window.on_gamepad_disconnected(move |gamepad: backend::gamepad::Gamepad| {
|
||||
let mut gamepads = shared_gamepads.borrow_mut();
|
||||
let index = gamepad.index();
|
||||
if gamepads.contains(&index) {
|
||||
gamepads.remove(&index);
|
||||
}
|
||||
});
|
||||
|
||||
self.0.raw.replace(Some(shared.clone()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Google Chrome create an array of [null, null, null, null].
|
||||
// To fix that issue, I create my own list of gamepads
|
||||
// by listening "gamepadconnected" and "gamepaddisconnected"
|
||||
pub fn get_gamepads(&self) -> Vec<backend::gamepad::Gamepad> {
|
||||
let gamepads = self.0.gamepads.borrow_mut();
|
||||
backend::get_gamepads()
|
||||
.filter(|g| gamepads.contains(&g.index()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Return gamepads handles required for EventLoop::gamepads()
|
||||
pub fn get_gamepad_handles(&self) -> Vec<device::GamepadHandle> {
|
||||
self.get_gamepads()
|
||||
.iter()
|
||||
.map(|gamepad| {
|
||||
device::GamepadHandle(GamepadHandle {
|
||||
id: gamepad.index,
|
||||
gamepad: gamepad::Shared::Raw(gamepad.clone()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
Shared(self.0.clone())
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ mod proxy;
|
||||
mod runner;
|
||||
mod state;
|
||||
mod window_target;
|
||||
pub(crate) mod global;
|
||||
|
||||
pub use self::proxy::Proxy;
|
||||
pub use self::window_target::WindowTarget;
|
||||
|
||||
use super::{backend, device, monitor, window};
|
||||
use super::{backend, monitor, window};
|
||||
use crate::event::Event;
|
||||
use crate::event_loop as root;
|
||||
|
||||
@@ -64,4 +65,20 @@ impl<T> EventLoop<T> {
|
||||
pub fn window_target(&self) -> &root::EventLoopWindowTarget<T> {
|
||||
&self.elw
|
||||
}
|
||||
|
||||
pub fn mice(&self) -> impl '_ + Iterator<Item = crate::event::device::MouseId> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
pub fn keyboards(&self) -> impl '_ + Iterator<Item = crate::event::device::KeyboardId> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
pub fn hids(&self) -> impl '_ + Iterator<Item = crate::event::device::HidId> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
pub fn gamepads(&self) -> impl '_ + Iterator<Item = crate::event::device::GamepadHandle> {
|
||||
self.elw.p.collect_gamepads().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::{backend, state::State};
|
||||
use crate::event::{Event, StartCause, WindowEvent};
|
||||
use crate::event_loop as root;
|
||||
use crate::window::WindowId;
|
||||
use crate::platform_impl::platform::device::gamepad;
|
||||
|
||||
use instant::{Duration, Instant};
|
||||
use std::{
|
||||
@@ -24,6 +25,7 @@ pub struct Execution<T> {
|
||||
events: RefCell<VecDeque<Event<T>>>,
|
||||
id: RefCell<u32>,
|
||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||
gamepad_manager: RefCell<gamepad::Manager>,
|
||||
}
|
||||
|
||||
struct Runner<T> {
|
||||
@@ -49,9 +51,14 @@ impl<T: 'static> Shared<T> {
|
||||
events: RefCell::new(VecDeque::new()),
|
||||
id: RefCell::new(0),
|
||||
redraw_pending: RefCell::new(HashSet::new()),
|
||||
gamepad_manager: RefCell::new(gamepad::Manager::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn set_global_window(&self, global_window: super::global::Shared) {
|
||||
self.0.gamepad_manager.borrow_mut().set_global_window(global_window);
|
||||
}
|
||||
|
||||
// Set the event callback to use for the event loop runner
|
||||
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
||||
// over a RootEventLoopWindowTarget reference
|
||||
@@ -138,6 +145,14 @@ impl<T: 'static> Shared<T> {
|
||||
&mut control,
|
||||
);
|
||||
}
|
||||
// Collect all global events
|
||||
let mut gamepad_manager = self.0.gamepad_manager.borrow_mut();
|
||||
let instance = self.clone();
|
||||
gamepad_manager.collect_events(move |(handle, event)| {
|
||||
instance.handle_event(Event::GamepadEvent(handle, event), &mut control);
|
||||
});
|
||||
|
||||
// Every events are cleared
|
||||
self.handle_event(Event::EventsCleared, &mut control);
|
||||
self.apply_control_flow(control);
|
||||
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
use super::{backend, device, proxy::Proxy, runner, window};
|
||||
use super::{backend, proxy::Proxy, runner, window, global};
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
|
||||
use crate::event::{device, ElementState, Event, KeyboardInput, WindowEvent};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::platform_impl::platform::device::{KeyboardId, MouseId};
|
||||
use crate::window::WindowId;
|
||||
use std::clone::Clone;
|
||||
|
||||
pub struct WindowTarget<T: 'static> {
|
||||
pub(crate) runner: runner::Shared<T>,
|
||||
pub(crate) global_window: global::Shared,
|
||||
}
|
||||
|
||||
impl<T> Clone for WindowTarget<T> {
|
||||
fn clone(&self) -> Self {
|
||||
WindowTarget {
|
||||
runner: self.runner.clone(),
|
||||
global_window: self.global_window.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +23,7 @@ impl<T> WindowTarget<T> {
|
||||
pub fn new() -> Self {
|
||||
WindowTarget {
|
||||
runner: runner::Shared::new(),
|
||||
global_window: global::Shared::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +32,7 @@ impl<T> WindowTarget<T> {
|
||||
}
|
||||
|
||||
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
|
||||
self.runner.set_global_window(self.global_window.clone());
|
||||
self.runner.set_listener(event_handler);
|
||||
}
|
||||
|
||||
@@ -36,6 +40,14 @@ impl<T> WindowTarget<T> {
|
||||
window::Id(self.runner.generate_id())
|
||||
}
|
||||
|
||||
pub fn collect_gamepads(&self) -> Vec<crate::event::device::GamepadHandle> {
|
||||
self.global_window.get_gamepad_handles()
|
||||
}
|
||||
|
||||
pub fn register_global_events(&self) -> Result<(), crate::error::OsError> {
|
||||
self.global_window.register_events()
|
||||
}
|
||||
|
||||
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
|
||||
let runner = self.runner.clone();
|
||||
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
||||
@@ -57,34 +69,28 @@ impl<T> WindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DeviceId(unsafe { device::Id::dummy() }),
|
||||
input: KeyboardInput {
|
||||
scancode,
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
},
|
||||
},
|
||||
});
|
||||
runner.send_event(Event::KeyboardEvent(
|
||||
device::KeyboardId(unsafe { KeyboardId::dummy() }),
|
||||
device::KeyboardEvent::Input(KeyboardInput {
|
||||
scancode,
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DeviceId(unsafe { device::Id::dummy() }),
|
||||
input: KeyboardInput {
|
||||
scancode,
|
||||
state: ElementState::Released,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
},
|
||||
},
|
||||
});
|
||||
runner.send_event(Event::KeyboardEvent(
|
||||
device::KeyboardId(unsafe { KeyboardId::dummy() }),
|
||||
device::KeyboardEvent::Input(KeyboardInput {
|
||||
scancode,
|
||||
state: ElementState::Released,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
@@ -96,31 +102,26 @@ impl<T> WindowTarget<T> {
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_cursor_leave(move |pointer_id| {
|
||||
canvas.on_cursor_leave(move || {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::CursorLeft {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
},
|
||||
event: WindowEvent::CursorLeft,
|
||||
});
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_cursor_enter(move |pointer_id| {
|
||||
canvas.on_cursor_enter(move || {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::CursorEntered {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
},
|
||||
event: WindowEvent::CursorEntered,
|
||||
});
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
|
||||
canvas.on_cursor_move(move |position, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
@@ -128,42 +129,33 @@ impl<T> WindowTarget<T> {
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_mouse_press(move |pointer_id, button, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::MouseInput {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
canvas.on_mouse_press(move |pointer_id, button| {
|
||||
runner.send_event(Event::MouseEvent(
|
||||
device::MouseId(MouseId(pointer_id)),
|
||||
device::MouseEvent::Button {
|
||||
state: ElementState::Pressed,
|
||||
button,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_mouse_release(move |pointer_id, button, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::MouseInput {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
canvas.on_mouse_release(move |pointer_id, button| {
|
||||
runner.send_event(Event::MouseEvent(
|
||||
device::MouseId(MouseId(pointer_id)),
|
||||
device::MouseEvent::Button {
|
||||
state: ElementState::Released,
|
||||
button,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::MouseWheel {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
delta,
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
canvas.on_mouse_wheel(move |pointer_id, delta| {
|
||||
runner.send_event(Event::MouseEvent(
|
||||
device::MouseId(MouseId(pointer_id)),
|
||||
device::MouseEvent::Wheel(delta.0, delta.1),
|
||||
));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
|
||||
@@ -19,7 +19,6 @@ mod backend;
|
||||
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
|
||||
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
|
||||
|
||||
pub use self::device::Id as DeviceId;
|
||||
pub use self::error::OsError;
|
||||
pub use self::event_loop::{
|
||||
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
|
||||
@@ -29,3 +28,5 @@ pub use self::window::{
|
||||
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
|
||||
Window,
|
||||
};
|
||||
|
||||
pub(crate) use self::device::*;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::event;
|
||||
use super::utils;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use std::cell::RefCell;
|
||||
@@ -129,9 +129,9 @@ impl Canvas {
|
||||
{
|
||||
self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
|
||||
handler(
|
||||
event::scan_code(&event),
|
||||
event::virtual_key_code(&event),
|
||||
event::keyboard_modifiers(&event),
|
||||
utils::scan_code(&event),
|
||||
utils::virtual_key_code(&event),
|
||||
utils::keyboard_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
}
|
||||
@@ -142,9 +142,9 @@ impl Canvas {
|
||||
{
|
||||
self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
|
||||
handler(
|
||||
event::scan_code(&event),
|
||||
event::virtual_key_code(&event),
|
||||
event::keyboard_modifiers(&event),
|
||||
utils::scan_code(&event),
|
||||
utils::virtual_key_code(&event),
|
||||
utils::keyboard_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
}
|
||||
@@ -159,75 +159,65 @@ impl Canvas {
|
||||
// viable/compatible alternative as of now. `beforeinput` is still widely
|
||||
// unsupported.
|
||||
self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
|
||||
handler(event::codepoint(&event));
|
||||
handler(utils::codepoint(&event));
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32),
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
|
||||
handler(event.pointer_id());
|
||||
self.on_cursor_leave = Some(self.add_event(move |_event: PointerOutEvent| {
|
||||
handler();
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32),
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
|
||||
handler(event.pointer_id());
|
||||
self.on_cursor_enter = Some(self.add_event(move |_event: PointerOverEvent| {
|
||||
handler();
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||
F: 'static + FnMut(i32, MouseButton),
|
||||
{
|
||||
self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_button(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
handler(event.pointer_id(), utils::mouse_button(&event));
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_mouse_press<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||
F: 'static + FnMut(i32, MouseButton),
|
||||
{
|
||||
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_button(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
handler(event.pointer_id(), utils::mouse_button(&event));
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
|
||||
F: 'static + FnMut(LogicalPosition, ModifiersState),
|
||||
{
|
||||
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_position(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
utils::mouse_position(&event),
|
||||
utils::mouse_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||
F: 'static + FnMut(i32, (f64, f64)),
|
||||
{
|
||||
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
|
||||
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||
handler(0, delta, event::mouse_modifiers(&event));
|
||||
}
|
||||
let delta = utils::mouse_scroll_delta(&event);
|
||||
handler(0, delta);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
82
src/platform_impl/web/stdweb/gamepad.rs
Normal file
82
src/platform_impl/web/stdweb/gamepad.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::{cmp::PartialEq};
|
||||
use crate::platform_impl::platform::device;
|
||||
use super::utils;
|
||||
use stdweb::js;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Gamepad {
|
||||
pub(crate) index: i32,
|
||||
pub(crate) raw: stdweb::web::Gamepad,
|
||||
pub(crate) mapping: device::gamepad::Mapping,
|
||||
}
|
||||
|
||||
impl Gamepad {
|
||||
pub fn new(raw: stdweb::web::Gamepad) -> Self {
|
||||
let mapping = utils::create_mapping(&raw);
|
||||
|
||||
Self {
|
||||
index: raw.index(),
|
||||
raw,
|
||||
mapping,
|
||||
}
|
||||
}
|
||||
|
||||
// An integer that is auto-incremented to be unique for each device
|
||||
// currently connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
|
||||
pub fn index(&self) -> i32 {
|
||||
self.raw.index()
|
||||
}
|
||||
|
||||
// A string containing some information about the controller.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
|
||||
pub fn id(&self) -> String {
|
||||
self.raw.id()
|
||||
}
|
||||
|
||||
// A boolean indicating whether the gamepad is still connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
|
||||
pub fn connected(&self) -> bool {
|
||||
self.raw.connected()
|
||||
}
|
||||
|
||||
// EXPERIMENTAL
|
||||
#[allow(dead_code)]
|
||||
pub fn vibrate(&self, value: f64, duration: f64) {
|
||||
let index = self.index;
|
||||
js! {
|
||||
const gamepads = navigator.getGamepads();
|
||||
let gamepad = null;
|
||||
for (let i = 0; i < gamepads.length; i++) {
|
||||
if (gamepads[i] && gamepads[i].index == @{index}) {
|
||||
gamepad = gamepads[i];
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!gamepad || !gamepad.hapticActuators) return;
|
||||
for (let i = 0; i < gamepad.hapticActuators.length; i++) {
|
||||
const actuator = gamepad.hapticActuators[i];
|
||||
if (actuator && actuator.type === "vibration") {
|
||||
actuator.pulse(@{value}, @{duration});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Gamepad {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
index: self.index,
|
||||
raw: self.raw.clone(),
|
||||
mapping: self.mapping.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Gamepad {
|
||||
#[inline(always)]
|
||||
fn eq(&self, othr: &Self) -> bool {
|
||||
self.raw.index() == othr.raw.index()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
mod canvas;
|
||||
mod event;
|
||||
pub mod gamepad;
|
||||
mod timeout;
|
||||
mod utils;
|
||||
pub mod window;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::timeout::Timeout;
|
||||
@@ -50,3 +52,9 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
|
||||
stdweb::web::Gamepad::get_all()
|
||||
.into_iter()
|
||||
.filter_map(|gamepad| gamepad.map(|gamepad| gamepad::Gamepad::new(gamepad)))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::platform::device::gamepad;
|
||||
|
||||
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
|
||||
use stdweb::web::{
|
||||
event::{IKeyboardEvent, IMouseEvent, MouseWheelEvent},
|
||||
Gamepad, GamepadMappingType,
|
||||
};
|
||||
use stdweb::{js, unstable::TryInto, JsSerialize};
|
||||
|
||||
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
|
||||
@@ -30,15 +34,10 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
|
||||
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> (f64, f64) {
|
||||
let x = event.delta_x();
|
||||
let y = event.delta_y();
|
||||
|
||||
match event.delta_mode() {
|
||||
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
|
||||
MouseWheelDeltaMode::Page => None,
|
||||
}
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
|
||||
@@ -227,3 +226,36 @@ pub fn codepoint(event: &impl IKeyboardEvent) -> char {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
event.key().chars().next().unwrap()
|
||||
}
|
||||
|
||||
pub fn create_mapping(raw: &Gamepad) -> gamepad::Mapping {
|
||||
match raw.mapping() {
|
||||
GamepadMappingType::Standard => {
|
||||
let mut buttons = [false; 16];
|
||||
let mut axes = [0.0; 6];
|
||||
|
||||
for (index, button) in raw
|
||||
.buttons()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.take(buttons.len())
|
||||
{
|
||||
buttons[index] = button.pressed();
|
||||
}
|
||||
|
||||
for (index, axis) in raw.axes().into_iter().enumerate().take(axes.len()) {
|
||||
axes[index] = axis;
|
||||
}
|
||||
|
||||
gamepad::Mapping::Standard { buttons, axes }
|
||||
}
|
||||
_ => {
|
||||
let buttons = raw
|
||||
.buttons()
|
||||
.into_iter()
|
||||
.map(|button| button.pressed())
|
||||
.collect();
|
||||
let axes = raw.axes();
|
||||
gamepad::Mapping::NoMapping { buttons, axes }
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/platform_impl/web/stdweb/window.rs
Normal file
77
src/platform_impl/web/stdweb/window.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use super::gamepad;
|
||||
use crate::error::OsError as RootOE;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use stdweb::web;
|
||||
use stdweb::web::{IEventTarget, event::IGamepadEvent};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Shared(pub Rc<RefCell<Window>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Window {
|
||||
raw: web::Window,
|
||||
on_gamepad_connected: Option<web::EventListenerHandle>,
|
||||
on_gamepad_disconnected: Option<web::EventListenerHandle>,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let global = Window::create()?;
|
||||
Ok(Shared(Rc::new(RefCell::new(global))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
Shared(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let raw = stdweb::web::window();
|
||||
|
||||
Ok(Window {
|
||||
raw,
|
||||
on_gamepad_connected: None,
|
||||
on_gamepad_disconnected: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(gamepad::Gamepad),
|
||||
{
|
||||
self.on_gamepad_connected = Some(self.add_event(
|
||||
move |event: stdweb::web::event::GamepadConnectedEvent| {
|
||||
let gamepad = event.gamepad();
|
||||
handler(gamepad::Gamepad::new(gamepad));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(gamepad::Gamepad),
|
||||
{
|
||||
self.on_gamepad_connected = Some(self.add_event(
|
||||
move |event: stdweb::web::event::GamepadDisconnectedEvent| {
|
||||
let gamepad = event.gamepad();
|
||||
handler(gamepad::Gamepad::new(gamepad));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn add_event<E, F>(&self, mut handler: F) -> web::EventListenerHandle
|
||||
where
|
||||
E: web::event::ConcreteEvent,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
self.raw.add_event_listener(move |event: E| {
|
||||
event.stop_propagation();
|
||||
event.cancel_bubble();
|
||||
|
||||
handler(event);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
use super::event;
|
||||
use super::utils;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
|
||||
use web_sys::{
|
||||
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent,
|
||||
};
|
||||
|
||||
pub struct Canvas {
|
||||
raw: HtmlCanvasElement,
|
||||
@@ -107,46 +109,56 @@ impl Canvas {
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
|
||||
handler();
|
||||
}));
|
||||
self.on_blur = Some(self.add_event(
|
||||
"blur",
|
||||
move |_: FocusEvent| {
|
||||
handler();
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_focus<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
|
||||
handler();
|
||||
}));
|
||||
self.on_focus = Some(self.add_event(
|
||||
"focus",
|
||||
move |_: FocusEvent| {
|
||||
handler();
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||
{
|
||||
self.on_keyboard_release =
|
||||
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
|
||||
self.on_keyboard_release = Some(self.add_user_event(
|
||||
"keyup",
|
||||
move |event: KeyboardEvent| {
|
||||
handler(
|
||||
event::scan_code(&event),
|
||||
event::virtual_key_code(&event),
|
||||
event::keyboard_modifiers(&event),
|
||||
utils::scan_code(&event),
|
||||
utils::virtual_key_code(&event),
|
||||
utils::keyboard_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||
{
|
||||
self.on_keyboard_press =
|
||||
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
|
||||
self.on_keyboard_press = Some(self.add_user_event(
|
||||
"keydown",
|
||||
move |event: KeyboardEvent| {
|
||||
handler(
|
||||
event::scan_code(&event),
|
||||
event::virtual_key_code(&event),
|
||||
event::keyboard_modifiers(&event),
|
||||
utils::scan_code(&event),
|
||||
utils::virtual_key_code(&event),
|
||||
utils::keyboard_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_received_character<F>(&mut self, mut handler: F)
|
||||
@@ -161,83 +173,85 @@ impl Canvas {
|
||||
self.on_received_character = Some(self.add_user_event(
|
||||
"keypress",
|
||||
move |event: KeyboardEvent| {
|
||||
handler(event::codepoint(&event));
|
||||
handler(utils::codepoint(&event));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32),
|
||||
{
|
||||
self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| {
|
||||
handler(event.pointer_id());
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32),
|
||||
{
|
||||
self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| {
|
||||
handler(event.pointer_id());
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||
F: 'static + FnMut(i32, MouseButton),
|
||||
{
|
||||
self.on_mouse_release = Some(self.add_user_event(
|
||||
self.on_mouse_release = Some(self.add_event(
|
||||
"pointerup",
|
||||
move |event: PointerEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_button(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
handler(event.pointer_id(), utils::mouse_button(&event));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_mouse_press<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||
F: 'static + FnMut(i32, MouseButton),
|
||||
{
|
||||
self.on_mouse_press = Some(self.add_user_event(
|
||||
self.on_mouse_press = Some(self.add_event(
|
||||
"pointerdown",
|
||||
move |event: PointerEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_button(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
handler(event.pointer_id(), utils::mouse_button(&event));
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, (f64, f64)),
|
||||
{
|
||||
self.on_mouse_wheel = Some(self.add_event(
|
||||
"wheel",
|
||||
move |event: WheelEvent| {
|
||||
let delta = utils::mouse_scroll_delta(&event);
|
||||
handler(0, delta);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_cursor_leave = Some(self.add_event(
|
||||
"pointerout",
|
||||
move |_event: PointerEvent| {
|
||||
handler();
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
self.on_cursor_enter = Some(self.add_event(
|
||||
"pointerover",
|
||||
move |_event: PointerEvent| {
|
||||
handler();
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
|
||||
F: 'static + FnMut(LogicalPosition, ModifiersState),
|
||||
{
|
||||
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_position(&event),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
||||
{
|
||||
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
|
||||
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||
handler(0, delta, event::mouse_modifiers(&event));
|
||||
}
|
||||
}));
|
||||
self.on_cursor_move = Some(self.add_event(
|
||||
"pointermove",
|
||||
move |event: PointerEvent| {
|
||||
handler(
|
||||
utils::mouse_position(&event),
|
||||
utils::mouse_modifiers(&event),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
|
||||
@@ -248,7 +262,11 @@ impl Canvas {
|
||||
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
|
||||
}
|
||||
|
||||
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||
fn add_event<E, F>(
|
||||
&self,
|
||||
event_name: &str,
|
||||
mut handler: F,
|
||||
) -> Closure<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
@@ -273,7 +291,11 @@ impl Canvas {
|
||||
// The difference between add_event and add_user_event is that the latter has a special meaning
|
||||
// for browser security. A user event is a deliberate action by the user (like a mouse or key
|
||||
// press) and is the only time things like a fullscreen request may be successfully completed.)
|
||||
fn add_user_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||
fn add_user_event<E, F>(
|
||||
&self,
|
||||
event_name: &str,
|
||||
mut handler: F,
|
||||
) -> Closure<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
@@ -281,16 +303,19 @@ impl Canvas {
|
||||
let wants_fullscreen = self.wants_fullscreen.clone();
|
||||
let canvas = self.raw.clone();
|
||||
|
||||
self.add_event(event_name, move |event: E| {
|
||||
handler(event);
|
||||
self.add_event(
|
||||
event_name,
|
||||
move |event: E| {
|
||||
handler(event);
|
||||
|
||||
if *wants_fullscreen.borrow() {
|
||||
canvas
|
||||
.request_fullscreen()
|
||||
.expect("Failed to enter fullscreen");
|
||||
*wants_fullscreen.borrow_mut() = false;
|
||||
}
|
||||
})
|
||||
if *wants_fullscreen.borrow() {
|
||||
canvas
|
||||
.request_fullscreen()
|
||||
.expect("Failed to enter fullscreen");
|
||||
*wants_fullscreen.borrow_mut() = false;
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
|
||||
75
src/platform_impl/web/web_sys/gamepad.rs
Normal file
75
src/platform_impl/web/web_sys/gamepad.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use super::utils;
|
||||
use crate::platform_impl::platform::device;
|
||||
use std::cmp::PartialEq;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Gamepad {
|
||||
pub(crate) index: i32,
|
||||
pub(crate) raw: web_sys::Gamepad,
|
||||
pub(crate) mapping: device::gamepad::Mapping,
|
||||
}
|
||||
|
||||
impl Gamepad {
|
||||
pub fn new(raw: web_sys::Gamepad) -> Self {
|
||||
let mapping = utils::create_mapping(&raw);
|
||||
|
||||
Self {
|
||||
index: raw.index() as i32,
|
||||
raw,
|
||||
mapping,
|
||||
}
|
||||
}
|
||||
|
||||
// An integer that is auto-incremented to be unique for each device
|
||||
// currently connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
|
||||
pub fn index(&self) -> i32 {
|
||||
self.raw.index() as i32
|
||||
}
|
||||
|
||||
// A string containing some information about the controller.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
|
||||
pub fn id(&self) -> String {
|
||||
self.raw.id()
|
||||
}
|
||||
|
||||
// A boolean indicating whether the gamepad is still connected to the system.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
|
||||
pub fn connected(&self) -> bool {
|
||||
self.raw.connected()
|
||||
}
|
||||
|
||||
// An array containing GamepadHapticActuator objects,
|
||||
// each of which represents haptic feedback hardware available on the controller.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
|
||||
pub fn vibrate(&self, value: f64, duration: f64) {
|
||||
for actuator in self.raw.haptic_actuators().values() {
|
||||
actuator.ok().and_then(|a| {
|
||||
let actuator: web_sys::GamepadHapticActuator = a.into();
|
||||
match actuator.type_() {
|
||||
web_sys::GamepadHapticActuatorType::Vibration => {
|
||||
actuator.pulse(value, duration).ok()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Gamepad {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
index: self.index,
|
||||
raw: self.raw.clone(),
|
||||
mapping: self.mapping.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Gamepad {
|
||||
#[inline(always)]
|
||||
fn eq(&self, othr: &Self) -> bool {
|
||||
self.raw.index() == othr.raw.index()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
mod canvas;
|
||||
mod event;
|
||||
pub mod gamepad;
|
||||
mod timeout;
|
||||
mod utils;
|
||||
pub mod window;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::timeout::Timeout;
|
||||
pub use canvas::Canvas;
|
||||
pub use timeout::Timeout;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform::web::WindowExtWebSys;
|
||||
@@ -68,3 +70,16 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
|
||||
let mut gamepads: Vec<gamepad::Gamepad> = Vec::new();
|
||||
let web_gamepads = web_sys::window().unwrap().navigator().get_gamepads().ok().unwrap();
|
||||
for index in 0..web_gamepads.length() {
|
||||
let jsvalue = web_gamepads.get(index);
|
||||
if !jsvalue.is_null() {
|
||||
let gamepad: web_sys::Gamepad = jsvalue.into();
|
||||
gamepads.push(gamepad::Gamepad::new(gamepad));
|
||||
}
|
||||
}
|
||||
gamepads.into_iter()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::platform;
|
||||
|
||||
use std::convert::TryInto;
|
||||
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
|
||||
use web_sys::{Gamepad, GamepadButton, GamepadMappingType, KeyboardEvent, MouseEvent, WheelEvent};
|
||||
|
||||
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
|
||||
match event.button() {
|
||||
@@ -29,15 +30,10 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
|
||||
pub fn mouse_scroll_delta(event: &WheelEvent) -> (f64, f64) {
|
||||
let x = event.delta_x();
|
||||
let y = event.delta_y();
|
||||
|
||||
match event.delta_mode() {
|
||||
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
|
||||
WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
|
||||
_ => None,
|
||||
}
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn scan_code(event: &KeyboardEvent) -> ScanCode {
|
||||
@@ -225,3 +221,44 @@ pub fn codepoint(event: &KeyboardEvent) -> char {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
event.key().chars().next().unwrap()
|
||||
}
|
||||
|
||||
pub fn create_mapping(raw: &Gamepad) -> platform::device::gamepad::Mapping {
|
||||
match raw.mapping() {
|
||||
GamepadMappingType::Standard => {
|
||||
let mut buttons = [false; 16];
|
||||
let mut axes = [0.0; 6];
|
||||
|
||||
let gbuttons = raw.buttons();
|
||||
for index in 0..buttons.len() {
|
||||
let button: GamepadButton = gbuttons.get(index as u32).into();
|
||||
buttons[index] = button.pressed();
|
||||
}
|
||||
|
||||
let gaxes = raw.axes();
|
||||
for index in 0..axes.len() {
|
||||
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
|
||||
axes[index] = axe;
|
||||
}
|
||||
|
||||
platform::device::gamepad::Mapping::Standard { buttons, axes }
|
||||
}
|
||||
_ => {
|
||||
let mut buttons: Vec<bool> = Vec::new();
|
||||
let mut axes: Vec<f64> = Vec::new();
|
||||
|
||||
let gbuttons = raw.buttons();
|
||||
for index in 0..gbuttons.length() {
|
||||
let button: GamepadButton = gbuttons.get(index as u32).into();
|
||||
buttons.push(button.pressed());
|
||||
}
|
||||
|
||||
let gaxes = raw.axes();
|
||||
for index in 0..gaxes.length() {
|
||||
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
|
||||
axes.push(axe);
|
||||
}
|
||||
|
||||
platform::device::gamepad::Mapping::NoMapping { buttons, axes }
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/platform_impl/web/web_sys/window.rs
Normal file
88
src/platform_impl/web/web_sys/window.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use super::gamepad;
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::platform_impl::OsError;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::GamepadEvent;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Shared(pub Rc<RefCell<Window>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Window {
|
||||
raw: web_sys::Window,
|
||||
on_gamepad_connected: Option<Closure<dyn FnMut(GamepadEvent)>>,
|
||||
on_gamepad_disconnected: Option<Closure<dyn FnMut(GamepadEvent)>>,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let global = Window::create()?;
|
||||
Ok(Shared(Rc::new(RefCell::new(global))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
Shared(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let raw =
|
||||
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
|
||||
|
||||
Ok(Window {
|
||||
raw,
|
||||
on_gamepad_connected: None,
|
||||
on_gamepad_disconnected: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(gamepad::Gamepad),
|
||||
{
|
||||
self.on_gamepad_connected = Some(self.add_event(
|
||||
"gamepadconnected",
|
||||
move |event: GamepadEvent| {
|
||||
let gamepad = event
|
||||
.gamepad()
|
||||
.expect("[gamepadconnected] expected gamepad");
|
||||
handler(gamepad::Gamepad::new(gamepad));
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(gamepad::Gamepad),
|
||||
{
|
||||
self.on_gamepad_disconnected = Some(self.add_event(
|
||||
"gamepaddisconnected",
|
||||
move |event: GamepadEvent| {
|
||||
let gamepad = event
|
||||
.gamepad()
|
||||
.expect("[gamepaddisconnected] expected gamepad");
|
||||
handler(gamepad::Gamepad::new(gamepad));
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
let closure = Closure::wrap(Box::new(move |event: E| {
|
||||
handler(event);
|
||||
}) as Box<dyn FnMut(E)>);
|
||||
|
||||
self.raw
|
||||
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to add event listener with callback");
|
||||
|
||||
closure
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ impl Window {
|
||||
|
||||
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
||||
|
||||
target.register_global_events()?;
|
||||
target.register(&mut canvas, id);
|
||||
|
||||
let window = Window {
|
||||
|
||||
Reference in New Issue
Block a user