Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/gilrs/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"3fc0f761ce45ac7ed20aba55a649fd271e100c70380b0f8f269f82f8fc5df57e","Cargo.lock":"2c1a2aac5d9704d8350e047577d7eb812fa6940883384d4942de4eef824d4a05","Cargo.toml":"8f4eb610f70beb5d7362c4496345a4946b3e57860fc8a924c32e67e549a66f31","README.md":"04284efc366dffde21f2f36833761dcf7da208eb33ef6eff641002514d8205e3","SDL_GameControllerDB/LICENSE":"d8c5f566590c92edb42f6739b1d375e81369144eee79063224331eee5ee0d084","SDL_GameControllerDB/README.md":"525fde2e418f8e693a2a28e870c7547f50610e87cd3e5f175b042901b300a67d","SDL_GameControllerDB/duplicates.py":"7d5a666da6d468c68cc38394db9ac769b19094ca902dd896da8f7c97f1455695","SDL_GameControllerDB/gamecontrollerdb.txt":"328e720b0f556b8dbb622ed3b45992c58a039e13ac0b113231c61f3074ca20f6","SDL_GameControllerDB/mapping_guide.png":"b9b01f2e527e68c272ad90d76a974c29f9b1da2c2dcb0cad0b6b8c7a62ce9cf8","build.rs":"8ab22199e935baae7561a345bfceb3539cb4803afb91c154d245d5f8f622cf3e","examples/ev.rs":"3fdf0f69393685f0173ed99f6096c6a944ae6c3105ff11a5b8d8ef8bc8ec2cc7","examples/ff.rs":"46ff5172c162739bb1c465e3a8c5b8b1fa8660133d200a86dbe7bb61f0cbdc87","examples/ff_pos.rs":"49257e800a76b740a3a59f5314d6b531c5e1fea349e1efc2c90395f62aac6904","examples/gamepad_info.rs":"502050aaac741c946ed8e6f3517b593cc6150df2f41a783729a147b7daa5245a","examples/gui.rs":"201509b44ac32bd1d126d56ead27868769c9c6bb69628574611953831c2533c2","examples/wasm/README.md":"272d13d1a8ab491b5292cef1ca68268da0e4734c6f99ac49b0be2eb3bd46e271","examples/wasm/index.html":"62f018ea10704a6b9b24b802c5d850b0286b98ce09dd058cea6e19c2f4495b9f","examples/wasm/wasm_gui.ps1":"21bad1d8ecb46101bd33dcff8344060f75988359a6837ea0e4387747aa43bef8","examples/wasm/wasm_gui.sh":"1f2bdd04fda9311c0a98ce071d8a4b62fb4206d56bae4b9af1da7373ff594dc1","src/constants.rs":"79b2bcdb359939aa0fd41837c09b5180600c05f571b4081416ff1d797cb00d23","src/ev/filter.rs":"f61839cdab098223ff9f40a7c78ec9652e6af2444951c85ab593abd0dfc9cde8","src/ev/mod.rs":"3591a70bd71eef0ef9e0fa564ef216c1bd69a49f7adf3d8f032e44f791c79169","src/ev/state.rs":"80cb3d041d93102c900715a1899bc6d10ee1a7a8b5fa3b1c5cd3b15deeb28019","src/ff/base_effect.rs":"7d8eb113cff3fee9e276ba037e76d812690a86aef71166a00170a5820ed819f5","src/ff/effect_source.rs":"c0176a89083a7f900498d8a9afeb27ee3207b0117888f1b653c76a3cc3b19a63","src/ff/mod.rs":"5fce9e2710aa5891348f529b7fbb4b6abdd3e8321daa720dfdceef077a9f8a02","src/ff/server.rs":"589a92389cfcbb108055b66b3e7ae7ec301a7f4c02f71aba4f2999a6aef8afc7","src/ff/time.rs":"b7dbbb1d9a9c7dfba44d80cf4ec3b2a42556068c99a46aec232e9f9fa5d3b6d6","src/gamepad.rs":"e7ab67edf24d54c0a19333d2197b250a6983ea0312bacf0f4fb84a2e626e690f","src/lib.rs":"3a2c61c681ee41ddc05de30e8c588d4e7a8f1298d91f5b2033704e5b8fcb1cfb","src/mapping/mod.rs":"fcacc3c902a55e28a8fd01c5fd01756fab258e57447b3b1f71df57e773e96997","src/mapping/parser.rs":"2e41acc14040e03016248386096534010f8acb8bdc45c1e5cd06ab681e2c720e","src/utils.rs":"638a2905cd0c0792261b4aa094c55dfa6242bd1ce92b61caeb67e0d1b3db2863"},"package":"bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f"}

520
vendor/gilrs/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,520 @@
Change Log
==========
See also [gilrs-core changelog](../gilrs-core/CHANGELOG.md).
v0.11.0 - 2024-09-15
----------
### Breaking changes
- Mark Error enums, `EventType` and `Event` as `non_exhaustive`
### Added
- Added `EventType::ForceFeedbackEffectCompleted`
### Changed
- Minimal supported Rust version is now 1.73
- Updated dependencies
- Updated bundled mappings
### Fixed
- Fixed potential overflow in `btn_value`
v0.10.6 - 2024-03-16
----------
### Fixed
- `axis_dpad_to_button` filter will now properly generate release event when
axis value change from -1 to 1 (or 1 to -1) while skipping 0.
v0.10.5 - 2024-03-06
----------
### Added
- Added `vendor_id()` and `product_id()` to `Gamepad`.
v0.10.4 - 2023-12-03
----------
### Fixed
- Fixed `Gilrs::set_mapping*` returning `MappingError::NotConnected` for
connected gamepads.
- Fix not setting other axis to 0 when in deadzone range.
v0.10.3 - 2023-11-11
----------
### Changed
- All thread spawned by gilrs are now named. (!102)
- MSRV is now 1.65.
v0.10.2 - 2023-04-23
----------
### Added
- `Gilrs::next_event_blocking()`
### Fixed
- Parse more SDL specific buttons in mappings
- Recognize "xinput" UUID in mappings
- Parse axis ranges in button mappings
v0.10.1 - 2022-11-13
----------
### Added
- Supporting files and documentation for running the GUI example using Wasm in
a browser. See [examples/wasm/README.md](./examples/wasm/README.md)
### Changed
- Bundled SDL mappings are now filtered by platform, reducing binary size.
### Fixed
- GUI example crash when the current platform does not support force feedback.
v0.10.0 - 2022-11-06
--------------------
### Changed
- Windows now defaults to using Windows Gaming Input instead of xinput.
If you need to use xInput you can disable the `wgi` feature (It's enabled by
default) and enable the `xinput` feature.
``` toml
gilrs = {version = "0.10.0", default-features = false, features = ["wgi"]}
```
- Apps on Windows will now require a focused window to receive inputs by
default.
This is a limitation of Windows Gaming Input. It requires an in focus Window
be associated with the process to receive events. You can still switch back
to using xInput by turning off default features and enabling the `xinput`
feature.
Note: Some (Older?) devices may still report inputs without a window but this
is not the case for all devices so if you are writing a terminal based game,
use the `xinput` feature instead.
- Minimal supported rust version is now 1.64.
v0.9.0 - 2022-05-22
-------------------
### Changed
- wasm: web-sys/wasm-bindgen is now used by default, dependency on stdweb
and `wasm-bindgen` feature are removed.
- Minimal supported rust version is now 1.56.
- Changed `impl Into\<usize\> for GamepadId`
to `impl From\<GamepadId\> for usize`
### Fixed
- wasm: `next_event()` no longer panic if `getGamepads()` is not available.
v0.8.2 - 2021-12-30
-------------------
### Changed
- Minimal supported rust version is now 1.47
- `axis_dpad_to_btn` now also emits `ButtonChanged` events
### Fixed
- Fixed overflow when calculating axis value and min/max range was
i32::MIN/MAX (@wathiede)
v0.8.1 - 2021-03-30
-------------------
### Changed
- Updated bundled mappings
v0.8.0 - 2020-10-09
-------------------
### Added
- `Jitter`, `Repeat`, `GilrsBuilder`, and `Mapping` now implement `Default`.
- Errors now implement `source()`.
- `Code` now implements `Deserialize` and `Serialize` (@theunkn0wn1).
- Dpad is now supported on macOS (@cleancut).
### Changed
- Minimal supported version is now 1.40
- Non exhaustive enums now use `#[non_exhaustive]` instead of hidden variant.
- Renamed cargo feature `serde` to `serde-serialize`.
- Improved conversion of axis value to float. Values like 127 (when axis range
is 0-255) will now be correctly converted to 0.0.
### Removed
- Errors now longer implement deprecated methods (`source()`
and `description()`).
v0.7.4 - 2020-02-06
-------------------
### Added
- Added method to stop playing force feedback effect. (@photex)
### Fixed
- Fixed bug that caused forced feedback effects to never stop. (@photex)
v0.7.3 - 2019-11-30
-------------------
### Added
- Added support for serialization and deserialization for `Button`, `Axis`
and `AxisOrButton` with optional `serde` feature (@aleksijuvani).
### Fixed
- Fixed defaults mappings containing elements that gamepad doesn't have.
This also fixes state not working for `LeftTrigger` button on Windows.
v0.7.2 - 2019-08-06
-------------------
### Fixed
- Fixed loading mappings for wrong platform
v0.7.1 - 2019-03-04
-------------------
### Fixed
- Compilation on macOS.
- xinput: Calling `set_ff_state()` on devices that were never connected.
- `GamepadId` was not reexported from private module.
v0.7.0 - 2019-02-21
-------------------
### Added
- `GamepadId`
- `Gilrs::gamepad(id)`. This function is replacement
for `Index` operator and can return disconnected gamepads.
- Initial support for macOS (@jtakakura). There are still some functionality
missing, check related issues in #58.
- Wasm support, using stdweb (@ryanisaacg).
### Changed
- Change `Gamepad::uuid -> Uuid` to `Gamepad::uuid -> [u8; 16]`
- `gilrs` now uses `gilrs-core` crate as backend. Because of it,
there are some breaking changes to API.
- Functions that returned `&Gamepad` now return `Gamepad<'_>` proxy object.
- Renamed `Gilrs::get(id)` to `Gilrs::connected_gamepad(id)`.
- Moved `Gamepad::set_mapping{,_strict}()` to `Gilrs`. These functions now
also take gamepad id as additional argument.
- Minimal supported version is now 1.31.1. The crate can still be build with
older rustc, but it may change during next patch release.
- Instead using `usize` for gamepad ID, `GamepadId` is now used.
- Updated bundled SDL_GameControllerDB.
### Removed
- All functions that returned `&mut Gamepad`.
- `Gilrs` no longer implements `Index` and `IndexMut` operators. Use
`Gilrs::gamepad(id)` instead.
- `Gamepad::status()` and `Status` enum. `Gamepad::is_connected()` is
now sufficient to determine status of gamepad.
### Fixed
- xinput: Incorrect gamepad ID when more than one gamepad is connected
(@DTibbs).
- Deadzone filter no longer emits additional events. This resulted in emitting
more events until values normalized on some, often unrelated (like 0 for axis
around 0.5), value.
- Mappings from environment variable had lower priority than bundled mappings.
v0.6.1 - 2018-07-18
-------------------
### Added
- `ev::Code::into_u32()` (@rukai).
- `ev::{Button, Axis, AxisOrBtn}` now implements `Hash` (@sheath).
### Changed
- The URL of repository has changed to https://gitlab.com/gilrs-project/gilrs
- Updated bundled SDL_GameControllerDB.
### Fixed
- Various fixes to logging at incorrect log level. Thanks to @fuggles for
locating and reporting these issues.
- Possible panic in `Repeat` filter.
- `Axis::DPadY` was inverted on Linux.
v0.6.0 - 2018-02-11
-------------------
### Added
- Support for parsing SLD 2.0.6 mappings.
- `ButtonChanged` event. It contains value in range [0.0, 1.0].
- `GilrsBuilder::set_axis_to_btn()`. It allow to customize on which values
`ButtonePressed` and `ButtonReleased` are emitted.
- `GilrsBuilder::set_update_state` which control whether gamepad state should
be updated automatically.
- `ButtonState::value()`.
- `Mapping::insert_{btn,axis}()`.
- `Gampead::os_name()` and `Gamepad::map_name()`. (@rukai)
- `GilrsBuilder::add_env_mappings()`
and `GilrsBuilder::add_included_mappings()`,
allow to configure whether to load mappings from `SDL_GAMECONTROLLERCONFIG`
env
and bundled mappings. (@rukai)
- `Gilrs::insert_event()`.
- `Axis::second_axis()` returns the other axis of gamepad element. For
example,
this function will return `LeftStickX` for `LeftStickY`.
### Removed
- `Mapping` no longer implements `Index` and `IndexMut` operators. Use
`Mapping::insert_{btn,axis}()` methods to add new mappings.
- `Axis::{LeftTrigger, LeftTrigger2, RightTrigger, RightTrigger2}`. All events
with these are now button events. `ButtonChanged` event contains value.
- `Gilrs::gamepad()` and `Gilrs::gamepad_mut()` use `Index` operator instead.
### Changed
- Gilrs now require Rust 1.20.0 or newer.
- Updated bundled mappings.
- Renamed `Filter::filter` to `Filter::filter_ev` because RFC 2124 added
`filter` method to `Option` (our `Filter` is implemented
for `Option<Event>`).
- `Gamepad::deadzone()` now returns `Option<f32>` instead of `f32`.
- All axis events are now in range [-1.0, 1.0].
- `NativeEvCode` is replaced by `ev::Code`, a strongly typed struct that also
distinguish between axes and buttons.
- You can now create mappings from any axis to any button.
- `State` now tracks floating-point value of buttons.
- `State::value()` can now be used to also examine value of buttons.
- By default, gamepad state is updated automatically. If you customize event
filters, you can disable this behaviour
using `GilrsBuilder::set_update_state`.
- `Gilrs::new()` and `GilrsBuilder::build()` now returns `Result`. Dummy
context
can still be used, but only if result of failure is unsupported platform.
- Renamed `Gilrs::connected_gamepad()` and `Gilrs::connected_gamepad_mut()` to
`get()` and `get_mut()`.
- `Filter` and `FilterFn` now borrows `Gilrs` mutably.
- Windows: Gamepads are now named "Xbox Controller" instead of "XInput
Controller".
(@rukai)
### Fixed
- Incorrect ranges for some axes.
- Deadzone filter should no longer produce values outside of allowed range.
- When calculating deadzone, the value of second axis is no longer ignored.
This fixes situation, when sometimes axis would stay on value small to 0.0,
when it should be 0.0 instead.
- Deadzone threshold was half of what it should be.
- Linux: Fixed axis value normalization if neither minimal value is 0 nor
midpoint is 0. (@scottpleb)
- Linux: Ensure that axis values are clamped after normalization. (@scottpleb)
- Linux: Compilation error on architectures with `c_char = u8`.
v0.5.0 - 2017-09-24
-------------------
### Added
- `Mapping::remove_button()` and `Mapping::remove_axis()`.
- `GilrsBuilder` for customizing how `Gilrs` is created.
- Event filters. See `ev::filter` module for more info.
- `Gilrs::next_event()` - use it with `while let` loop in your event loop.
This allow to avoid borrow checker problems that `EventIterator` caused.
- New event `Dropped`. Used by filters to indicate that you should ignore
this event.
- New event `ButtonRepeated`. Can be emitted by `Repeat` filter.
- `Axis::{DPadX, DPadY}`
- `Gamepad::{button_name, axis_name, button_code, axis_code}` functions for
accessing mapping data.
- `Gamepad::axis_data, button_data` part of new extended gamepad state.
- `Gamepad::id()` returns gamepad ID.
- `Gilrs::update, inc, counter, reset_counter` part of new extended
gamepad state.
### Removed
- `Gilrs::with_mappings()` use `GilrsBuilder`.
- `Gilrs::poll_events()` and `EventIterator` use `Gilrs::next_event()`
instead.
### Changed
- Minimal rust version is now 1.19
- New gamepad state. Now can store state for any button or axis (previously was
only useful for named buttons and axes). Additionally it now also know when
last event happened. Basic usage with `is_pressed()` and `value()` methods is
same, but check out documentation for new features.
- Gamepad state now must be explicitly updated with `Gilrs::update(Event)`.
This change was necessary because filters can change events.
- `Event` is now a struct and contains common information like id of gamepad
and timestamp (new). Old enum was renamed to `EventType` and can be accessed
from `Event.event` public field.
- New force feedback module, including support for Windows. There are to many
changes to list them all here, so pleas check documentation and examples.
- Renamed `ff::Error::EffectNotSupported` to `ff::Error::NotSupported`.
- `Button::Unknown` and `Axis::Unknown` have now value of 0.
- `Gamepad::set_mapping()` (and `_strict` variant) now returns error when
creating mapping with `Button::Unknown` or `Axis::Unknown`. Additionally
`_strict` version does not allow `Button::{C, Z}` and Axis::{LeftZ, RightZ}.
- xinput: New values for `NativEvCode`
### Fixed
- Panic on `unreachable!()` when creating mapping with `Button::{C, Z,
Unknown}` or `Axis::{LeftZ, RightZ}`.
v0.4.4 — 2017-06-16
-------------------
### Changed
- Gilrs no longer uses `ioctl` crate on Linux. Because `ioctl` was deprecated
and all versions yanked, it was causing problems for new builds that didn't
have `ioctl` crate listed in Cargo.lock.
v0.4.3 — 2017-03-12
-------------------
### Added
- You can now iterate over mutable references to connected gamepads using
`Gilrs::gamepads_mut()`.
### Fixed
- Fixed `unreachable!()` panic on 32bit Linux
- Improved converting axes values to `f32` when using XInput
v0.4.2 - 2017-01-15
-------------------
### Changed
- Updated SDL_GameControllerDB to latest revision.
- Changes in axes values that are less than 1% are now ignored.
### Fixed
- Fixed multiple axes mapped to same axis name when mappings are incomplete.
- Values returned with `AxisChanged` event now have correctly applied
deadzones.
- Linux: Correctly handle event queue overrun.
v0.4.1 - 2016-12-12
-------------------
### Fixed
- Type inference error introduced by generic index in `<[T]>::get`
v0.4.0 - 2016-12-11
-------------------
### Added
- `Gamepad::mappings_source(&self)` which can be used to filter gamepads which
not provide unified controller layout
- `MappingsSource` enum
- You can now set custom mapping for gamepad with `Gamepad::set_mapping(…)`
- `Gilrs::with_mappings(&str)` to create Gilrs with additional gamepad mappings
### Changed
- Button and axis events now also have native event codes
- On Linux, if button or axis is not known, is now reported as `Unknown`
(previously all unknown events have been ignored)
- More devices are now treated as gamepads on Linux (use `mappings_source()` to
filter unwanted gamepads)
- Renamed `{Gamepad,GamepadState}::is_btn_pressed(Button)` to
`is_pressed(Button)`
- Renamed `{Gamepad,GamepadState}::axis_val(Axis)` to `value(Axis)`
### Fixed
- Integer overflow if button with keyboard code was pressed on Linux
- `Gilrs` should no longer panic if there are some unexpected problems with
Udev
- Fixed normalization of axes values on Linux
v0.3.1 - 2016-09-23
-------------------
### Fixed
- Fixed compilation error on non-x86_64 Linux
v0.3.0 - 2016-09-22
-------------------
### Added
- `Gamepad::power_info(&self)`
- `ff::Direction::from_radians(f32)` and `ff::Direction::from_vector([f32; 2])`
- `Gilrs::gamepads(&self)` which returns iterator over all connected gamepads
- `GamepadState` now implements `is_btn_pressed(Button)` and `axis_val(Axis)`
- `Gilrs` now implements `Index`and `IndexMut`
### Changed
- Rename `Button::Unknow` to `Button::Unknown`
- `Gamepad::name(&self)` now returns `&str` instead of `&String`
- Improved dead zone detection
- `Effect::play(&self, u16)` now returns `Result<(), Error>`
- Linux: Reduced memory usage
### Removed
- `ff::Direction` no longer implements `From<f32>`
### Fixed
- Buttons west and east are no longer swapped when using SDL2 mappings
- Linux: infinite loop after gamepad disconnects
- Linux: SDL2 mappings for gamepads that can also report mouse and keyboard
events now should works
v0.2.0 - 2016-08-18
------
### Changed
- Rename `Gilrs::pool_events()` to `Gilrs::poll_events()`
### Fixed
- Linux: Disconnected events are now emitted properly
- Linux: All force feedback effects are now dropped when gamepad disconnects

2475
vendor/gilrs/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

106
vendor/gilrs/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,106 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.73.0"
name = "gilrs"
version = "0.11.0"
authors = ["Arvamer <arvamer@gmail.com>"]
build = "build.rs"
exclude = ["controller.svg"]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Game Input Library for Rust"
documentation = "https://docs.rs/gilrs/"
readme = "README.md"
keywords = [
"gamepad",
"joystick",
"input",
]
categories = ["game-engines"]
license = "Apache-2.0/MIT"
repository = "https://gitlab.com/gilrs-project/gilrs"
[package.metadata.docs.rs]
features = ["serde-serialize"]
[lib]
name = "gilrs"
path = "src/lib.rs"
[[example]]
name = "ev"
path = "examples/ev.rs"
[[example]]
name = "ff"
path = "examples/ff.rs"
[[example]]
name = "ff_pos"
path = "examples/ff_pos.rs"
[[example]]
name = "gamepad_info"
path = "examples/gamepad_info.rs"
[[example]]
name = "gui"
path = "examples/gui.rs"
[dependencies.fnv]
version = "1.0"
[dependencies.gilrs-core]
version = "0.6.0"
default-features = false
[dependencies.log]
version = "0.4.1"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.uuid]
version = "1.0.0"
[dependencies.vec_map]
version = "0.8"
[dev-dependencies.console_log]
version = "1.0.0"
[dev-dependencies.eframe]
version = "0.19.0"
[dev-dependencies.env_logger]
version = "0.11.5"
[features]
default = ["wgi"]
serde-serialize = [
"serde",
"gilrs-core/serde-serialize",
]
wgi = ["gilrs-core/wgi"]
xinput = ["gilrs-core/xinput"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.console_error_panic_hook]
version = "0.1.7"
[badges.gitlab]
repository = "gilrs-project/gilrs"

107
vendor/gilrs/README.md vendored Normal file
View File

@@ -0,0 +1,107 @@
GilRs - Game Input Library for Rust
===================================
[![pipeline status](https://gitlab.com/gilrs-project/gilrs/badges/master/pipeline.svg)](https://gitlab.com/gilrs-project/gilrs/commits/master)
[![Crates.io](https://img.shields.io/crates/v/gilrs.svg)](https://crates.io/crates/gilrs)
[![Documentation](https://docs.rs/gilrs/badge.svg)](https://docs.rs/gilrs/)
[![Minimum rustc version](https://img.shields.io/badge/rustc-1.73.0+-yellow.svg)](https://gitlab.com/gilrs-project/gilrs)
[**Documentation (master)**](https://gilrs-project.gitlab.io/gilrs/doc/gilrs/)
GilRs abstract platform specific APIs to provide unified interfaces for working with gamepads.
Main features:
- Unified gamepad layout—buttons and axes are represented by familiar names
- Support for SDL2 mappings including `SDL_GAMECONTROLLERCONFIG` environment
variable which Steam uses
- Hotplugging—GilRs will try to assign new ID for new gamepads and reuse the same
ID for gamepads which reconnected
- Force feedback (rumble)
- Power information (is gamepad wired, current battery status)
The project's main repository [is on GitLab](https://gitlab.com/gilrs-project/gilrs)
although there is also a [GitHub mirror](https://github.com/Arvamer/gilrs).
Please use GitLab's issue tracker and merge requests.
This repository contains submodule; after you clone it, don't forget to run
`git submodule init; git submodule update` (or clone with `--recursive` flag)
or you will get compile errors.
Example
-------
```toml
[dependencies]
gilrs = "0.11.0"
```
```rust
use gilrs::{Gilrs, Button, Event};
let mut gilrs = Gilrs::new().unwrap();
// Iterate over all connected gamepads
for (_id, gamepad) in gilrs.gamepads() {
println!("{} is {:?}", gamepad.name(), gamepad.power_info());
}
let mut active_gamepad = None;
loop {
// Examine new events
while let Some(Event { id, event, time }) = gilrs.next_event() {
println!("{:?} New event from {}: {:?}", time, id, event);
active_gamepad = Some(id);
}
// You can also use cached gamepad state
if let Some(gamepad) = active_gamepad.map(|id| gilrs.gamepad(id)) {
if gamepad.is_pressed(Button::South) {
println!("Button South is pressed (XBox - A, PS - X)");
}
}
}
```
Supported features
------------------
| | Input | Hotplugging | Force feedback |
|------------------|:-----:|:-----------:|:--------------:|
| Linux/BSD (evdev)| ✓ | ✓ | ✓ |
| Windows (XInput) | ✓ | ✓ | ✓ |
| OS X | ✓ | ✓ | ✕ |
| Wasm | ✓ | ✓ | n/a |
| Android | ✕ | ✕ | ✕ |
Platform specific notes
======================
Linux/BSD (evdev)
-----
With evdev, GilRs read (and write, in case of force feedback) directly from appropriate
`/dev/input/event*` file. This mean that user have to have read and write access to this file.
On most distros it shouldn't be a problem, but if it is, you will have to create udev rule.
On FreeBSD generic HID gamepads use hgame(4) and special use Linux driver via `webcamd`.
To build GilRs, you will need pkg-config and libudev .pc file. On some distributions this file
is packaged in separate archive (e.g., `libudev-dev` in Debian, `libudev-devd` in FreeBSD).
Wasm
----
Wasm implementation uses stdweb, or wasm-bindgen with the wasm-bindgen feature.
For stdweb, you will need [cargo-web](https://github.com/koute/cargo-web) to build gilrs for
wasm32-unknown-unknown. For wasm-bindgen, you will need the wasm-bindgen cli or a tool like
[wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
Unlike other platforms, events are only generated when you call `Gilrs::next_event()`.
See [`./gilrs/examples/wasm/README.md`](./gilrs/examples/wasm/README.md) for running the examples using Wasm.
License
=======
This project is licensed under the terms of both the Apache License (Version 2.0) and the MIT
license. See LICENSE-APACHE and LICENSE-MIT for details.

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,57 @@
# SDL_GameControllerDB
A community sourced database of game controller mappings to be used with SDL2 and SDL3 Game Controller functionality.
# Usage
Download gamecontrollerdb.txt, place it in your app's directory and load it.
SDL2:
```c
SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
```
SDL3:
```c
SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
```
The database is compatible with SDL v2.0.10 and newer.
## Create New Mappings
A mapping looks like this:
```
030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
```
It includes controller GUID (`030000004c050000c405000000010000`), a name (`PS4 Controller`), button / axis mappings (`leftshoulder:b4`) and a platform (`platform:Mac OS X`).
Please make sure to check that the name is a good description of the controller. If relevant, include the controller's name and model number.
## Mapping Guide
![SDL Game Controller Mapping Guide](mapping_guide.png)
## Mapping Tools
There are a few different tools that let you create mappings.
### [SDL2 Gamepad Tool](http://www.generalarcade.com/gamepadtool/)
Third party cross-platform tool with GUI (Windows, macOS and Linux)
#### Note: While convenient, this tool has fallen out of date as SDL has amended and added new features for gamepad support (see issue [#478](https://github.com/gabomdq/SDL_GameControllerDB/issues/476)). As such, maps authored with this tool require greater scrutiny to ensure they will not break support for explicit mappings the SDL project provides.
### [SDL2 Gamepad Mapper](https://gitlab.com/ryochan7/sdl2-gamepad-mapper/-/releases)
Open source GUI app for authoring mappings. Builds available for Windows and Linux.
### [SDL](https://github.com/libsdl-org/SDL/releases/latest)
[testcontroller (SDL3)](https://github.com/libsdl-org/SDL/blob/main/test/testcontroller.c) and [controllermap (SDL2)](https://github.com/libsdl-org/SDL/blob/SDL2/test/controllermap.c) utilities are the official tools to create these mappings on all SDL supported platforms (Windows, Mac, Linux, iOS, Android, etc).
### [Steam](http://store.steampowered.com)
In Steam's Big Picture mode, configure your gamepad. Then look in `[steam_installation_directory]/config/config.vdf` in your Steam installation directory for the `SDL_GamepadBind` entry. It is one of the last entries, it will look something like this:
```
"SDL_GamepadBind" "030000004c050000c405000000010000,PS4 Controller,platform:Windows,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,"
```
## Resources
* [SDL2](http://www.libsdl.org)
* [SDL_GameControllerAddMappingsFromFile](http://wiki.libsdl.org/SDL_GameControllerAddMappingsFromFile)

View File

@@ -0,0 +1,26 @@
# SPDX-License-Identifier: Zlib
import difflib
import sys
CROSS_PLATFORM=False
cdict = {}
for i, l in enumerate(open("gamecontrollerdb.txt")):
l = l.strip()
if l.startswith("#") or not l:
continue
c = l.split(",")
key = tuple([c[0]]+[ce for ce in c[1:] if "platform:" in ce])
if CROSS_PLATFORM:
key = c[0]
if key in cdict:
print("Duplicate:", c[1], "at line", i + 1)
out = list(difflib.unified_diff(cdict[key], sorted(c), n=0))[3:]
out = [o for o in out if not o.startswith("@@")]
print("\t", " ".join(out))
if not CROSS_PLATFORM:
sys.exit(1)
cdict[key] = sorted(c)

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

79
vendor/gilrs/build.rs vendored Normal file
View File

@@ -0,0 +1,79 @@
//! This build script takes the gamecontrollerdb.txt from the SDL repo and removes any
//! mappings that aren't for the current platform and removes comments etc.
//!
//! This reduces the binary size fairly significantly compared to including mappings for every
//! platform.
//! Especially Wasm since it doesn't use SDL mappings and binary size is important.
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
#[cfg(windows)]
const PATH_SEPARATOR: &str = "backslash";
#[cfg(not(windows))]
const PATH_SEPARATOR: &str = "slash";
fn main() {
println!("cargo:rustc-check-cfg=cfg(path_separator, values(\"slash\",\"backslash\"))");
println!(r#"cargo:rustc-cfg=path_separator="{}""#, PATH_SEPARATOR);
let out_dir = env::var("OUT_DIR").unwrap();
let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let sdl_platform = "platform:".to_string()
+ match env::var("CARGO_CFG_TARGET_FAMILY").unwrap().as_str() {
"unix" => match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
"android" => "Android",
"macos" => "Mac OS X",
_ => "Linux",
},
"windows" => "Windows",
"wasm" => "Web",
_ => "Unknown",
};
let sdl_game_controller_db_path: PathBuf =
PathBuf::from_iter(vec!["SDL_GameControllerDB", "gamecontrollerdb.txt"]);
// Tell cargo to re-run this script only when SDL's gamecontrollerdb.txt changes.
println!(
"cargo:rerun-if-changed={}",
sdl_game_controller_db_path.to_string_lossy()
);
let mut new_file = File::create(Path::new(&out_dir).join("gamecontrollerdb.txt"))
.expect("failed to create gamecontrollerdb.txt for target");
let path = Path::new(&cargo_manifest_dir).join(sdl_game_controller_db_path);
let original_file = File::open(&path).unwrap_or_else(|_| {
panic!(
"Could not open gamecontrollerdb.txt {:?}. Did you forget to pull the \
`SDL_GameControllerDB` submodule?",
&path
)
});
let original_reader = BufReader::new(original_file);
original_reader
.lines()
.map(|x| match x {
Ok(x) => x,
Err(e) => panic!("Failed to read line from gamecontrollerdb.txt: {e}"),
})
.filter(|line| {
line.trim_end()
.trim_end_matches(',')
.ends_with(&sdl_platform)
})
.for_each(|line| {
let mut line = line;
line.push('\n');
new_file
.write_all(line.as_bytes())
.expect("Failed to write line to gamecontrollerdb.txt in OUT_DIR");
});
}

53
vendor/gilrs/examples/ev.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use gilrs::ev::filter::{Filter, Repeat};
use gilrs::GilrsBuilder;
use std::process;
fn main() {
env_logger::init();
let mut gilrs = match GilrsBuilder::new().set_update_state(false).build() {
Ok(g) => g,
Err(gilrs::Error::NotImplemented(g)) => {
eprintln!("Current platform is not supported");
g
}
Err(e) => {
eprintln!("Failed to create gilrs context: {}", e);
process::exit(-1);
}
};
let repeat_filter = Repeat::new();
loop {
while let Some(ev) = gilrs
.next_event_blocking(None)
.filter_ev(&repeat_filter, &mut gilrs)
{
gilrs.update(&ev);
println!("{:?}", ev);
}
if gilrs.counter() % 25 == 0 {
for (id, gamepad) in gilrs.gamepads() {
println!(
"Power info of gamepad {}({}): {:?}",
id,
gamepad.name(),
gamepad.power_info()
);
}
}
gilrs.inc();
}
}

47
vendor/gilrs/examples/ff.rs vendored Normal file
View File

@@ -0,0 +1,47 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use gilrs::ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks};
use gilrs::Gilrs;
use std::thread;
use std::time::Duration;
fn main() {
env_logger::init();
let mut gilrs = Gilrs::new().unwrap();
let support_ff = gilrs
.gamepads()
.filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None })
.collect::<Vec<_>>();
let duration = Ticks::from_ms(150);
let effect = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude: 60_000 },
scheduling: Replay {
play_for: duration,
with_delay: duration * 3,
..Default::default()
},
envelope: Default::default(),
})
.add_effect(BaseEffect {
kind: BaseEffectType::Weak { magnitude: 60_000 },
scheduling: Replay {
after: duration * 2,
play_for: duration,
with_delay: duration * 3,
},
..Default::default()
})
.gamepads(&support_ff)
.finish(&mut gilrs)
.unwrap();
effect.play().unwrap();
thread::sleep(Duration::from_secs(11));
}

201
vendor/gilrs/examples/ff_pos.rs vendored Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use gilrs::ff::{BaseEffect, BaseEffectType, DistanceModel, EffectBuilder};
use gilrs::{Axis, Button, EventType, Gilrs};
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
#[derive(Copy, Clone, PartialEq, Debug)]
enum Modify {
DistModel,
RefDistance,
RolloffFactor,
MaxDistance,
}
impl Modify {
fn next(&mut self) {
use crate::Modify::*;
*self = match *self {
DistModel => RefDistance,
RefDistance => RolloffFactor,
RolloffFactor => MaxDistance,
MaxDistance => DistModel,
};
print!("\x1b[2K\r{:?}", self);
io::stdout().flush().unwrap();
}
fn prev(&mut self) {
use crate::Modify::*;
*self = match *self {
DistModel => MaxDistance,
RefDistance => DistModel,
RolloffFactor => RefDistance,
MaxDistance => RolloffFactor,
};
print!("\x1b[2K\r{:?}", self);
io::stdout().flush().unwrap();
}
}
fn main() {
env_logger::init();
let mut gilrs = Gilrs::new().unwrap();
println!("Connected gamepads:");
let mut support_ff = Vec::new();
for (idx, gp) in gilrs.gamepads() {
let ff = gp.is_ff_supported();
println!(
"{}) {} ({})",
idx,
gp.name(),
if ff {
"Force feedback supported"
} else {
"Force feedback not supported"
}
);
if ff {
support_ff.push(idx);
}
}
println!("----------------------------------------");
println!(
"Use sticks to move listener. Triggers change properties of distance model. South/west \
button changes active property. Press east button on action pad to quit."
);
let pos1 = [-100.0, 0.0, 0.0];
let mut effect_builder = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude: 45_000 },
..Default::default()
})
.add_effect(BaseEffect {
kind: BaseEffectType::Weak { magnitude: 45_000 },
..Default::default()
})
.distance_model(DistanceModel::None)
.gamepads(&support_ff)
.clone();
let left_effect = effect_builder.position(pos1).finish(&mut gilrs).unwrap();
left_effect.play().unwrap();
println!("Playing one effects…");
println!("Position of effect sources: {:?}", pos1);
let mut listeners = support_ff
.iter()
.map(|&idx| (idx, [0.0, 0.0, 0.0]))
.collect::<Vec<_>>();
let mut ref_distance = 10.0;
let mut rolloff_factor = 0.5;
let mut max_distance = 100.0;
let mut modify = Modify::DistModel;
let mut model = 0usize;
'main: loop {
while let Some(event) = gilrs.next_event() {
match event.event {
EventType::ButtonReleased(Button::East, ..) => break 'main,
EventType::ButtonReleased(Button::South, ..) => modify.next(),
EventType::ButtonReleased(Button::West, ..) => modify.prev(),
EventType::ButtonReleased(Button::LeftTrigger, ..)
if modify == Modify::DistModel =>
{
model = model.wrapping_sub(1);
}
EventType::ButtonReleased(Button::RightTrigger, ..)
if modify == Modify::DistModel =>
{
model = model.wrapping_add(1);
}
_ => (),
}
}
for &mut (idx, ref mut pos) in &mut listeners {
let velocity = 0.5;
let gp = gilrs.gamepad(idx);
let (sx, sy) = (gp.value(Axis::LeftStickX), gp.value(Axis::LeftStickY));
if sx.abs() > 0.5 || sy.abs() > 0.5 {
if sx.abs() > 0.5 {
pos[0] += velocity * sx.signum();
}
if sy.abs() > 0.5 {
pos[1] += velocity * sy.signum();
}
gilrs.gamepad(idx).set_listener_position(*pos).unwrap();
let dist = ((pos[0] - pos1[0]).powi(2) + (pos[1] - pos1[1]).powi(2)).sqrt();
print!(
"\x1b[2K\rPosition of listener {:2} has changed: [{:6.1}, {:6.1}].Distance: \
{:.1}",
idx, pos[0], pos[1], dist
);
io::stdout().flush().unwrap();
}
let x = if gp.is_pressed(Button::LeftTrigger) {
-1.0
} else if gp.is_pressed(Button::RightTrigger) {
1.0
} else {
continue;
};
match modify {
Modify::RolloffFactor => rolloff_factor += x * velocity * 0.1,
Modify::RefDistance => ref_distance += x * velocity * 0.1,
Modify::MaxDistance => max_distance += x * velocity * 1.0,
Modify::DistModel => (), // DistanceModel handled in event loop
}
let model = match model % 4 {
0 => DistanceModel::None,
1 => DistanceModel::LinearClamped {
ref_distance,
rolloff_factor,
max_distance,
},
2 => DistanceModel::InverseClamped {
ref_distance,
rolloff_factor,
max_distance,
},
3 => DistanceModel::ExponentialClamped {
ref_distance,
rolloff_factor,
max_distance,
},
_ => unreachable!(),
};
match left_effect.set_distance_model(model) {
Ok(()) => print!("\x1b[2K\r{:?}", model),
Err(e) => print!("\x1b[2K\r{}", e),
}
io::stdout().flush().unwrap();
}
thread::sleep(Duration::from_millis(16));
}
}

60
vendor/gilrs/examples/gamepad_info.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
use gilrs::{Axis, Button, Gilrs};
use uuid::Uuid;
fn main() {
let gilrs = Gilrs::new().unwrap();
for (id, gamepad) in gilrs.gamepads() {
println!(
r#"Gamepad {id} ({name}):
Map name: {map_name:?}
Os name: {os_name}
UUID: {uuid}
Is connected: {is_connected}
Power info: {power_info:?}
Mapping source: {mapping_source:?}
Is ff supported: {ff}
Deadzone Left X: {dlx:?}
Deadzone Left Y: {dly:?}
Deadzone Right X: {drx:?}
Deadzone Right Y: {dry:?}
Deadzone Left Trigger: {dlt:?}
Deadzone Right Trigger: {drt:?}
Deadzone Left Trigger 2: {dlt2:?}
Deadzone Right Trigger 2: {drt2:?}
"#,
id = id,
name = gamepad.name(),
map_name = gamepad.map_name(),
os_name = gamepad.os_name(),
uuid = Uuid::from_bytes(gamepad.uuid()).as_hyphenated(),
is_connected = gamepad.is_connected(),
power_info = gamepad.power_info(),
mapping_source = gamepad.mapping_source(),
ff = gamepad.is_ff_supported(),
dlx = gamepad
.axis_code(Axis::LeftStickX)
.and_then(|code| gamepad.deadzone(code)),
dly = gamepad
.axis_code(Axis::LeftStickY)
.and_then(|code| gamepad.deadzone(code)),
drx = gamepad
.axis_code(Axis::RightStickX)
.and_then(|code| gamepad.deadzone(code)),
dry = gamepad
.axis_code(Axis::RightStickY)
.and_then(|code| gamepad.deadzone(code)),
dlt = gamepad
.button_code(Button::LeftTrigger)
.and_then(|code| gamepad.deadzone(code)),
drt = gamepad
.button_code(Button::RightTrigger)
.and_then(|code| gamepad.deadzone(code)),
dlt2 = gamepad
.button_code(Button::LeftTrigger2)
.and_then(|code| gamepad.deadzone(code)),
drt2 = gamepad
.button_code(Button::RightTrigger2)
.and_then(|code| gamepad.deadzone(code)),
);
}
}

329
vendor/gilrs/examples/gui.rs vendored Normal file
View File

@@ -0,0 +1,329 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use crate::egui::plot::{MarkerShape, PlotPoints, Points};
use crate::egui::RichText;
use eframe::egui;
use eframe::egui::Vec2;
use gilrs::ev::AxisOrBtn;
use gilrs::ff::{BaseEffect, BaseEffectType, Effect, EffectBuilder, Repeat, Ticks};
use gilrs::{Axis, GamepadId, Gilrs, GilrsBuilder};
use gilrs_core::PowerInfo;
use std::time::UNIX_EPOCH;
use uuid::Uuid;
struct MyEguiApp {
gilrs: Gilrs,
current_gamepad: Option<GamepadId>,
log_messages: [Option<String>; 300],
// These will be none if Force feedback isn't supported for this platform e.g. Wasm
ff_strong: Option<Effect>,
ff_weak: Option<Effect>,
}
impl Default for MyEguiApp {
fn default() -> Self {
#[cfg(target_arch = "wasm32")]
console_log::init().unwrap();
const INIT: Option<String> = None;
let mut gilrs = GilrsBuilder::new().set_update_state(false).build().unwrap();
let ff_strong = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude: 60_000 },
scheduling: Default::default(),
envelope: Default::default(),
})
.repeat(Repeat::For(Ticks::from_ms(100)))
.finish(&mut gilrs)
.ok();
let ff_weak = EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Weak { magnitude: 60_000 },
scheduling: Default::default(),
envelope: Default::default(),
})
.repeat(Repeat::For(Ticks::from_ms(100)))
.finish(&mut gilrs)
.ok();
Self {
gilrs,
current_gamepad: None,
log_messages: [INIT; 300],
ff_strong,
ff_weak,
}
}
}
impl MyEguiApp {
fn log(&mut self, message: String) {
self.log_messages[0..].rotate_right(1);
self.log_messages[0] = Some(message);
}
}
impl MyEguiApp {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
Self::default()
}
}
impl eframe::App for MyEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
while let Some(event) = self.gilrs.next_event() {
self.log(format!(
"{} : {} : {:?}",
event
.time
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis(),
event.id,
event.event
));
self.gilrs.update(&event);
if self.current_gamepad.is_none() {
self.current_gamepad = Some(event.id);
}
}
egui::SidePanel::left("side_panel").show(ctx, |ui| {
ui.heading("Controllers");
ui.separator();
for (id, gamepad) in self.gilrs.gamepads() {
if ui
.selectable_label(
self.current_gamepad == Some(id),
format!("{id}: {}", gamepad.name()),
)
.clicked()
{
self.current_gamepad = Some(id);
};
}
ui.allocate_space(ui.available_size());
});
egui::TopBottomPanel::bottom("log")
.resizable(true)
.default_height(200.0)
.show(ctx, |ui| {
ui.heading("Event Log");
egui::ScrollArea::vertical()
.max_height(ui.available_height())
.show(ui, |ui| {
for message in self.log_messages.iter().flatten() {
ui.label(message);
}
ui.allocate_space(ui.available_size());
});
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both().show(ui, |ui| {
if let Some(gamepad_id) = self.current_gamepad {
let gamepad = self.gilrs.gamepad(gamepad_id);
let gamepad_state = gamepad.state();
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.heading("Info");
egui::Grid::new("info_grid")
.striped(true)
.num_columns(2)
.show(ui, |ui| {
ui.label("Name");
ui.label(gamepad.name());
ui.end_row();
if let Some(vendor) = gamepad.vendor_id() {
ui.label("Vendor ID");
ui.label(format!("{vendor:04x}"));
ui.end_row();
}
if let Some(product) = gamepad.product_id() {
ui.label("Product ID");
ui.label(format!("{product:04x}"));
ui.end_row();
}
ui.label("Gilrs ID");
ui.label(gamepad.id().to_string());
ui.end_row();
if let Some(map_name) = gamepad.map_name() {
ui.label("Map Name");
ui.label(map_name);
ui.end_row();
}
ui.label("Map Source");
ui.label(format!("{:?}", gamepad.mapping_source()));
ui.end_row();
ui.label("Uuid");
let uuid = Uuid::from_bytes(gamepad.uuid()).to_string();
ui.horizontal(|ui| {
ui.label(&uuid);
if ui.button("Copy").clicked() {
ui.output().copied_text = uuid;
}
});
ui.end_row();
ui.label("Power");
ui.label(match gamepad.power_info() {
PowerInfo::Unknown => "Unknown".to_string(),
PowerInfo::Wired => "Wired".to_string(),
PowerInfo::Discharging(p) => format!("Discharging {p}"),
PowerInfo::Charging(p) => format!("Charging {p}"),
PowerInfo::Charged => "Charged".to_string(),
});
ui.end_row();
});
});
if gamepad.is_ff_supported() {
ui.vertical(|ui| {
ui.label("Force Feedback");
if let Some(ff_strong) = &self.ff_strong {
if ui.button("Play Strong").clicked() {
ff_strong.add_gamepad(&gamepad).unwrap();
ff_strong.play().unwrap();
}
}
if let Some(ff_weak) = &self.ff_weak {
if ui.button("Play Weak").clicked() {
ff_weak.add_gamepad(&gamepad).unwrap();
ff_weak.play().unwrap();
}
}
});
}
});
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.set_width(300.0);
ui.heading("Buttons");
for (code, button_data) in gamepad_state.buttons() {
let name = match gamepad.axis_or_btn_name(code) {
Some(AxisOrBtn::Btn(b)) => format!("{b:?}"),
_ => "Unknown".to_string(),
};
ui.add(
egui::widgets::ProgressBar::new(button_data.value()).text(
RichText::new(format!(
"{name:<14} {:<5} {:.4} {}",
button_data.is_pressed(),
button_data.value(),
code
))
.monospace(),
),
);
}
});
ui.vertical(|ui| {
ui.set_width(300.0);
ui.heading("Axes");
ui.horizontal(|ui| {
for (name, x, y) in [
("Left Stick", Axis::LeftStickX, Axis::LeftStickY),
("Right Stick", Axis::RightStickX, Axis::RightStickY),
] {
ui.vertical(|ui| {
ui.label(name);
let y_axis = gamepad
.axis_data(y)
.map(|a| a.value())
.unwrap_or_default()
as f64;
let x_axis = gamepad
.axis_data(x)
.map(|a| a.value())
.unwrap_or_default()
as f64;
egui::widgets::plot::Plot::new(format!("{name}_plot"))
.width(150.0)
.height(150.0)
.min_size(Vec2::splat(3.25))
.include_x(1.25)
.include_y(1.25)
.include_x(-1.25)
.include_y(-1.25)
.allow_drag(false)
.allow_zoom(false)
.allow_boxed_zoom(false)
.allow_scroll(false)
.show(ui, |plot_ui| {
plot_ui.points(
Points::new(PlotPoints::new(vec![[
x_axis, y_axis,
]]))
.shape(MarkerShape::Circle)
.radius(4.0),
);
});
});
}
});
for (code, axis_data) in gamepad_state.axes() {
let name = match gamepad.axis_or_btn_name(code) {
None => code.to_string(),
Some(AxisOrBtn::Btn(b)) => format!("{b:?}"),
Some(AxisOrBtn::Axis(a)) => format!("{a:?}"),
};
ui.add(
egui::widgets::ProgressBar::new(
(axis_data.value() * 0.5) + 0.5,
)
.text(
RichText::new(format!(
"{:+.4} {name:<15} {}",
axis_data.value(),
code
))
.monospace(),
),
);
}
});
});
} else {
ui.label("Press a button on a controller or select it from the left.");
}
ui.allocate_space(ui.available_size());
});
});
ctx.request_repaint();
}
}
#[cfg(not(target_arch = "wasm32"))]
fn main() {
env_logger::init();
let native_options = eframe::NativeOptions {
initial_window_size: Some(Vec2::new(1024.0, 768.0)),
..Default::default()
};
eframe::run_native(
"Gilrs Input Tester",
native_options,
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
);
}
#[cfg(target_arch = "wasm32")]
fn main() {
console_error_panic_hook::set_once();
let web_options = eframe::WebOptions::default();
eframe::start_web(
"canvas",
web_options,
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
)
.unwrap();
}

32
vendor/gilrs/examples/wasm/README.md vendored Normal file
View File

@@ -0,0 +1,32 @@
# Wasm Example
These are instructions for running the GUI example in your web browser using Wasm.
Currently only the GUI example is set up to run with Wasm.
### Ubuntu requirements
```bash
sudo apt install build-essential
sudo apt-get install libssl-dev pkg-config
```
### Setup
```pwsh
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
cargo install basic-http-server
```
### Build and Run
Run these from the workspace root.
```pwsh
cargo build --release --example gui --target wasm32-unknown-unknown
wasm-bindgen --out-name wasm_example --out-dir gilrs/examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/gui.wasm
basic-http-server gilrs/examples/wasm
```
Now open your web browser and navigate to http://127.0.0.1:4000

34
vendor/gilrs/examples/wasm/index.html vendored Normal file
View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Gilrs Example</title>
<style>
html, body {
margin: 0;
/* prevent scroll bar from showing */
overflow: hidden;
/*
Egui automatically resizes the canvas to fit it's parent element so ensures the canvas will
resize to always be fullscreen.
*/
width: 100%;
height: 100%;
}
canvas {
/*
Egui adds these to the canvas automatically, but not until it gets focus,
causing it to grow until you click on it. This prevents that.
*/
position: absolute;
top: 0;
}
</style>
</head>
<script type="module">
import init from './target/wasm_example.js'
init()
</script>
<canvas id="canvas"></canvas>
</html>

14
vendor/gilrs/examples/wasm/wasm_gui.ps1 vendored Normal file
View File

@@ -0,0 +1,14 @@
### This script can be used instead of the "Build and Run" step in `./gilrs/examples/wasm/README.md`.
### Useful for gilrs devs that want a single script to to point their IDE to for run configurations.
### Supports Powershell 5 and up on Windows or Linux
### Make sure to run the install steps from the readme first.
# Start at this script's path and go up three levels to the workspace root.
# Ensures a consistent path regardless of the working directory when you run the script.
$Path = $PSScriptRoot | Split-Path | Split-Path | Split-Path
$ProjectDir = Resolve-Path $Path
Set-Location $ProjectDir
cargo build --release --example gui --target wasm32-unknown-unknown
wasm-bindgen --out-name wasm_example --out-dir gilrs/examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/gui.wasm
basic-http-server gilrs/examples/wasm

17
vendor/gilrs/examples/wasm/wasm_gui.sh vendored Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
### This script can be used instead of the "Build and Run" step in `./gilrs/examples/wasm/README.md`.
### Useful for gilrs devs that want a single script to to point their IDE to for run configurations.
### Make sure to run the install steps from the readme first.
set -e
# Start at this script's path and go up three levels to the workspace root.
# Ensures a consistent path regardless of the working directory when you run the script.
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_DIR=$(dirname "$(dirname "$(dirname "$SCRIPT_DIR")")")
cd "$PROJECT_DIR" || exit
cargo build --release --example gui --target wasm32-unknown-unknown
wasm-bindgen --out-name wasm_example --out-dir gilrs/examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/gui.wasm
basic-http-server gilrs/examples/wasm

40
vendor/gilrs/src/constants.rs vendored Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
pub const BTN_UNKNOWN: u16 = 0;
pub const BTN_SOUTH: u16 = 1;
pub const BTN_EAST: u16 = 2;
pub const BTN_C: u16 = 3;
pub const BTN_NORTH: u16 = 4;
pub const BTN_WEST: u16 = 5;
pub const BTN_Z: u16 = 6;
pub const BTN_LT: u16 = 7;
pub const BTN_RT: u16 = 8;
pub const BTN_LT2: u16 = 9;
pub const BTN_RT2: u16 = 10;
pub const BTN_SELECT: u16 = 11;
pub const BTN_START: u16 = 12;
pub const BTN_MODE: u16 = 13;
pub const BTN_LTHUMB: u16 = 14;
pub const BTN_RTHUMB: u16 = 15;
pub const BTN_DPAD_UP: u16 = 16;
pub const BTN_DPAD_DOWN: u16 = 17;
pub const BTN_DPAD_LEFT: u16 = 18;
pub const BTN_DPAD_RIGHT: u16 = 19;
pub const AXIS_UNKNOWN: u16 = 0;
pub const AXIS_LSTICKX: u16 = 1;
pub const AXIS_LSTICKY: u16 = 2;
pub const AXIS_LEFTZ: u16 = 3;
pub const AXIS_RSTICKX: u16 = 4;
pub const AXIS_RSTICKY: u16 = 5;
pub const AXIS_RIGHTZ: u16 = 6;
pub const AXIS_DPADX: u16 = 7;
pub const AXIS_DPADY: u16 = 8;

547
vendor/gilrs/src/ev/filter.rs vendored Normal file
View File

@@ -0,0 +1,547 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Alter events in various ways.
//!
//! This modules contains "event filters" that can change, drop or create new events. To use them,
//! import `Filter` trait and call `filter()` function on `Option<Event>`. Because `filter` also
//! returns `Option<Event>` you can combine multiple filters by using `filter()` function on
//! returned event.
//!
//! Filters in this modules have public fields that can be used to configure their behaviour. You
//! can also create them with default values using `new()` method. If filter is not configurable,
//! it is implemented as function (for example `deadzone()`).
//!
//! # Example
//!
//! ```
//! use gilrs::{GilrsBuilder, Filter};
//! use gilrs::ev::filter::{Jitter, Repeat, deadzone};
//!
//! let mut gilrs = GilrsBuilder::new().with_default_filters(false).build().unwrap();
//! let jitter = Jitter { threshold: 0.02 };
//! let repeat = Repeat::new();
//!
//! // Event loop
//! loop {
//! while let Some(event) = gilrs
//! .next_event()
//! .filter_ev(&jitter, &mut gilrs)
//! .filter_ev(&deadzone, &mut gilrs)
//! .filter_ev(&repeat, &mut gilrs)
//! {
//! gilrs.update(&event);
//! println!("{:?}", event);
//! }
//! # break;
//! }
//! ```
//! # Implementing custom filters
//!
//! If you want to implement your own filters, you will have to implement `FilterFn` trait.
//! **Do not return `None` if you got `Some(event)`**. If you want to discard an event, uses
//! `EventType::Dropped`. Returning `None` means that there are no more events to process and
//! will end `while let` loop.
//!
//! ## Example
//!
//! Example implementations of filter that will drop all events with `Unknown` axis or button.
//!
//! ```
//! use gilrs::ev::filter::FilterFn;
//! use gilrs::{Gilrs, Event, EventType, Button, Axis, Filter};
//!
//! struct UnknownSlayer;
//!
//! impl FilterFn for UnknownSlayer {
//! fn filter(&self, ev: Option<Event>, _gilrs: &mut Gilrs) -> Option<Event> {
//! match ev {
//! Some(Event { event: EventType::ButtonPressed(Button::Unknown, ..), id, .. })
//! | Some(Event { event: EventType::ButtonReleased(Button::Unknown, ..), id, .. })
//! | Some(Event { event: EventType::AxisChanged(Axis::Unknown, ..), id, .. })
//! => Some(Event::new(id, EventType::Dropped)),
//! _ => ev,
//! }
//! }
//! }
//! ```
//!
//! `FilterFn` is also implemented for all `Fn(Option<Event>, &Gilrs) -> Option<Event>`, so above
//! example could be simplified to passing closure to `filter()` function.
use crate::ev::{Axis, AxisOrBtn, Button, Code, Event, EventType};
use crate::gamepad::{Gamepad, Gilrs};
use crate::utils;
use std::time::Duration;
/// Discard axis events that changed less than `threshold`.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Jitter {
pub threshold: f32,
}
impl Jitter {
/// Creates new `Repeat` filter with threshold set to 0.01.
pub fn new() -> Self {
Jitter { threshold: 0.01 }
}
}
impl Default for Jitter {
fn default() -> Self {
Self::new()
}
}
impl FilterFn for Jitter {
fn filter(&self, ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event> {
match ev {
Some(Event {
event: EventType::AxisChanged(_, val, axis),
id,
..
}) => match gilrs.gamepad(id).state().axis_data(axis) {
Some(data) if val != 0.0 && (val - data.value()).abs() < self.threshold => {
Some(Event::new(id, EventType::Dropped))
}
_ => ev,
},
_ => ev,
}
}
}
fn apply_deadzone(x: f32, y: f32, threshold: f32) -> (f32, f32) {
let magnitude = utils::clamp((x * x + y * y).sqrt(), 0.0, 1.0);
if magnitude <= threshold {
(0.0, 0.0)
} else {
let norm = ((magnitude - threshold) / (1.0 - threshold)) / magnitude;
(x * norm, y * norm)
}
}
fn deadzone_nonzero_axis_idx(axis: Axis) -> Option<usize> {
Some(match axis {
Axis::DPadX => 0,
Axis::DPadY => 1,
Axis::LeftStickX => 2,
Axis::LeftStickY => 3,
Axis::RightStickX => 4,
Axis::RightStickY => 5,
_ => {
return None;
}
})
}
/// Drops events in dead zone and remaps value to keep it in standard range.
pub fn deadzone(ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event> {
match ev {
Some(Event {
event: EventType::AxisChanged(axis, val, nec),
id,
time,
}) => {
let threshold = match gilrs.gamepad(id).deadzone(nec) {
Some(t) => t,
None => return ev,
};
if let Some((other_axis, other_code)) = axis
.second_axis()
.and_then(|axis| gilrs.gamepad(id).axis_code(axis).map(|code| (axis, code)))
{
let other_val = gilrs.gamepad(id).state().value(other_code);
let val = apply_deadzone(val, other_val, threshold);
// Since this is the second axis, deadzone_nonzero_axis_idx() will always returns something.
let other_axis_idx = deadzone_nonzero_axis_idx(other_axis).unwrap();
if val.0 == 0.
&& val.1 == 0.
&& gilrs.gamepads_data[id.0].have_sent_nonzero_for_axis[other_axis_idx]
&& gilrs.gamepad(id).state().value(other_code) != 0.
{
// Clear other axis that is now within the dead zone threshold.
gilrs.insert_event(Event {
id,
time,
event: EventType::AxisChanged(other_axis, 0., other_code),
});
gilrs.gamepads_data[id.0].have_sent_nonzero_for_axis[other_axis_idx] = false;
}
Some(if gilrs.gamepad(id).state().value(nec) == val.0 {
Event::new(id, EventType::Dropped)
} else {
if let Some(axis_idx) = deadzone_nonzero_axis_idx(axis) {
gilrs.gamepads_data[id.0].have_sent_nonzero_for_axis[axis_idx] =
val.0 != 0.;
}
Event {
id,
time,
event: EventType::AxisChanged(axis, val.0, nec),
}
})
} else {
let val = apply_deadzone(val, 0.0, threshold).0;
Some(if gilrs.gamepad(id).state().value(nec) == val {
Event::new(id, EventType::Dropped)
} else {
if let Some(axis_idx) = deadzone_nonzero_axis_idx(axis) {
gilrs.gamepads_data[id.0].have_sent_nonzero_for_axis[axis_idx] = val != 0.;
}
Event {
id,
time,
event: EventType::AxisChanged(axis, val, nec),
}
})
}
}
Some(Event {
event: EventType::ButtonChanged(btn, val, nec),
id,
time,
}) => {
let gp = &gilrs.gamepad(id);
let threshold = match gp.deadzone(nec) {
Some(t) => t,
None => return ev,
};
let val = apply_deadzone(val, 0.0, threshold).0;
Some(if gp.state().value(nec) == val {
Event::new(id, EventType::Dropped)
} else {
Event {
id,
time,
event: EventType::ButtonChanged(btn, val, nec),
}
})
}
_ => ev,
}
}
/// Maps axis dpad events to button dpad events.
///
/// This filter will do nothing if gamepad has dpad buttons (to prevent double events for same
/// element) and if standard `NativeEvCode` for dpads is used by some other buttons. It will always
/// try to map if SDL mappings contains mappings for all four hats.
pub fn axis_dpad_to_button(ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event> {
use gilrs_core::native_ev_codes as necs;
fn can_map(gp: &Gamepad<'_>) -> bool {
let hats_mapped = gp.mapping().hats_mapped();
if hats_mapped == 0b0000_1111 {
true
} else if hats_mapped == 0 {
gp.axis_or_btn_name(Code(necs::BTN_DPAD_RIGHT)).is_none()
&& gp.axis_or_btn_name(Code(necs::BTN_DPAD_LEFT)).is_none()
&& gp.axis_or_btn_name(Code(necs::BTN_DPAD_DOWN)).is_none()
&& gp.axis_or_btn_name(Code(necs::BTN_DPAD_UP)).is_none()
&& gp.button_code(Button::DPadRight).is_none()
} else {
// Not all hats are mapped so let's ignore it for now.
false
}
}
let ev = ev?;
let gamepad = gilrs.gamepad(ev.id);
if !can_map(&gamepad) {
return Some(ev);
}
let mut out_event = ev.drop();
match ev.event {
EventType::AxisChanged(Axis::DPadX, val, _) => {
let mut release_left = false;
let mut release_right = false;
if val == 1.0 {
// The axis value might change from left (-1.0) to right (1.0) immediately without
// us getting an additional event for the release at the center position (0.0).
release_left = gamepad.state().is_pressed(Code(necs::BTN_DPAD_LEFT));
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadRight,
1.0,
Code(necs::BTN_DPAD_RIGHT),
),
..ev
});
out_event = Event {
event: EventType::ButtonPressed(Button::DPadRight, Code(necs::BTN_DPAD_RIGHT)),
..ev
};
} else if val == -1.0 {
// The axis value might change from right (1.0) to left (-1.0) immediately without
// us getting an additional event for the release at the center position (0.0).
release_right = gamepad.state().is_pressed(Code(necs::BTN_DPAD_RIGHT));
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadLeft,
1.0,
Code(necs::BTN_DPAD_LEFT),
),
..ev
});
out_event = Event {
event: EventType::ButtonPressed(Button::DPadLeft, Code(necs::BTN_DPAD_LEFT)),
..ev
};
} else {
release_left = gamepad.state().is_pressed(Code(necs::BTN_DPAD_LEFT));
release_right = gamepad.state().is_pressed(Code(necs::BTN_DPAD_RIGHT));
}
if release_right {
if !out_event.is_dropped() {
gilrs.insert_event(out_event);
}
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadRight,
0.0,
Code(necs::BTN_DPAD_RIGHT),
),
..ev
});
out_event = Event {
event: EventType::ButtonReleased(Button::DPadRight, Code(necs::BTN_DPAD_RIGHT)),
..ev
};
}
if release_left {
if !out_event.is_dropped() {
gilrs.insert_event(out_event);
}
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadLeft,
0.0,
Code(necs::BTN_DPAD_LEFT),
),
..ev
});
out_event = Event {
event: EventType::ButtonReleased(Button::DPadLeft, Code(necs::BTN_DPAD_LEFT)),
..ev
};
}
Some(out_event)
}
EventType::AxisChanged(Axis::DPadY, val, _) => {
let mut release_up = false;
let mut release_down = false;
if val == 1.0 {
// The axis value might change from down (-1.0) to up (1.0) immediately without us
// getting an additional event for the release at the center position (0.0).
release_down = gamepad.state().is_pressed(Code(necs::BTN_DPAD_DOWN));
gilrs.insert_event(Event {
event: EventType::ButtonChanged(Button::DPadUp, 1.0, Code(necs::BTN_DPAD_UP)),
..ev
});
out_event = Event {
event: EventType::ButtonPressed(Button::DPadUp, Code(necs::BTN_DPAD_UP)),
..ev
};
} else if val == -1.0 {
// The axis value might change from up (1.0) to down (-1.0) immediately without us
// getting an additional event for the release at the center position (0.0).
release_up = gamepad.state().is_pressed(Code(necs::BTN_DPAD_UP));
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadDown,
1.0,
Code(necs::BTN_DPAD_DOWN),
),
..ev
});
out_event = Event {
event: EventType::ButtonPressed(Button::DPadDown, Code(necs::BTN_DPAD_DOWN)),
..ev
};
} else {
release_up = gamepad.state().is_pressed(Code(necs::BTN_DPAD_UP));
release_down = gamepad.state().is_pressed(Code(necs::BTN_DPAD_DOWN));
}
if release_up {
if !out_event.is_dropped() {
gilrs.insert_event(out_event);
}
gilrs.insert_event(Event {
event: EventType::ButtonChanged(Button::DPadUp, 0.0, Code(necs::BTN_DPAD_UP)),
..ev
});
out_event = Event {
event: EventType::ButtonReleased(Button::DPadUp, Code(necs::BTN_DPAD_UP)),
..ev
};
}
if release_down {
if !out_event.is_dropped() {
gilrs.insert_event(out_event);
}
gilrs.insert_event(Event {
event: EventType::ButtonChanged(
Button::DPadDown,
0.0,
Code(necs::BTN_DPAD_DOWN),
),
..ev
});
out_event = Event {
event: EventType::ButtonReleased(Button::DPadDown, Code(necs::BTN_DPAD_DOWN)),
..ev
};
}
Some(out_event)
}
_ => Some(ev),
}
}
/// Repeats pressed keys.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Repeat {
pub after: Duration,
pub every: Duration,
}
impl Repeat {
/// Creates new `Repeat` filter with `after` set to 500ms and `every` set to 30ms.
pub fn new() -> Self {
Repeat {
after: Duration::from_millis(500),
every: Duration::from_millis(30),
}
}
}
impl Default for Repeat {
fn default() -> Self {
Self::new()
}
}
impl FilterFn for Repeat {
fn filter(&self, ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event> {
match ev {
Some(ev) => Some(ev),
None => {
let now = utils::time_now();
for (id, gamepad) in gilrs.gamepads() {
for (nec, btn_data) in gamepad.state().buttons() {
match (
btn_data.is_pressed(),
btn_data.is_repeating(),
now.duration_since(btn_data.timestamp()),
) {
(true, false, Ok(dur)) if dur >= self.after => {
let btn_name = match gamepad.axis_or_btn_name(nec) {
Some(AxisOrBtn::Btn(b)) => b,
_ => Button::Unknown,
};
return Some(Event {
id,
event: EventType::ButtonRepeated(btn_name, nec),
time: btn_data.timestamp() + self.after,
});
}
(true, true, Ok(dur)) if dur >= self.every => {
let btn_name = match gamepad.axis_or_btn_name(nec) {
Some(AxisOrBtn::Btn(b)) => b,
_ => Button::Unknown,
};
return Some(Event {
id,
event: EventType::ButtonRepeated(btn_name, nec),
time: btn_data.timestamp() + self.every,
});
}
_ => (),
}
}
}
None
}
}
}
}
/// Allow filtering events.
///
/// See module level documentation for more info.
pub trait Filter {
fn filter_ev<F: FilterFn>(&self, filter: &F, gilrs: &mut Gilrs) -> Option<Event>;
}
/// Actual filter implementation.
///
/// See module level documentation for more info.
pub trait FilterFn {
fn filter(&self, ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event>;
}
impl<F> FilterFn for F
where
F: Fn(Option<Event>, &mut Gilrs) -> Option<Event>,
{
fn filter(&self, ev: Option<Event>, gilrs: &mut Gilrs) -> Option<Event> {
self(ev, gilrs)
}
}
impl Filter for Option<Event> {
fn filter_ev<F: FilterFn>(&self, filter: &F, gilrs: &mut Gilrs) -> Option<Event> {
let e = filter.filter(*self, gilrs);
debug_assert!(
!(self.is_some() && e.is_none()),
"Filter changed Some(event) into None. See ev::filter documentation for more info."
);
e
}
}
impl Filter for Event {
fn filter_ev<F: FilterFn>(&self, filter: &F, gilrs: &mut Gilrs) -> Option<Event> {
let e = filter.filter(Some(*self), gilrs);
debug_assert!(
e.is_some(),
"Filter changed Some(event) into None. See ev::filter documentation for more info."
);
e
}
}

266
vendor/gilrs/src/ev/mod.rs vendored Normal file
View File

@@ -0,0 +1,266 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Gamepad state and other event related functionality.
pub mod filter;
pub mod state;
use std::{
fmt::{Display, Formatter, Result as FmtResult},
time::SystemTime,
};
use crate::{constants::*, gamepad::GamepadId, utils};
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
/// Platform specific event code.
///
/// This type represents single gamepads's element like specific axis or button.
/// It can't be directly created, but you can get it from events or using
/// `Gamepad`'s methods [`button_code`](crate::Gamepad::button_code) and
/// [`axis_code`](crate::Gamepad::axis_code). If `serde-serialize` feature is
/// enabled, `Code` can be serialized and deserialized, but keep in mind that
/// layout **is** platform-specific. So it's not possible to serialize `Code` on
/// Linux and deserialize it on Windows. This also apply to `Display` implementation.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct Code(pub(crate) gilrs_core::EvCode);
impl Display for Code {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(f)
}
}
impl Code {
pub fn into_u32(&self) -> u32 {
self.0.into_u32()
}
}
/// Holds information about gamepad event.
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct Event {
/// Id of gamepad.
pub id: GamepadId,
/// Event's data.
pub event: EventType,
/// Time when event was emitted.
pub time: SystemTime,
}
impl Event {
/// Creates new event with current time.
pub fn new(id: GamepadId, event: EventType) -> Self {
Event {
id,
event,
time: utils::time_now(),
}
}
/// Returns `Event` with `EventType::Dropped`.
pub fn drop(mut self) -> Event {
self.event = EventType::Dropped;
self
}
/// Returns true if event is `Dropped` and should be ignored.
pub fn is_dropped(&self) -> bool {
self.event == EventType::Dropped
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[non_exhaustive]
/// Gamepad event.
pub enum EventType {
/// Some button on gamepad has been pressed.
ButtonPressed(Button, Code),
/// This event can be generated by [`ev::Repeat`](filter/struct.Repeat.html) event filter.
ButtonRepeated(Button, Code),
/// Previously pressed button has been released.
ButtonReleased(Button, Code),
/// Value of button has changed. Value can be in range [0.0, 1.0].
ButtonChanged(Button, f32, Code),
/// Value of axis has changed. Value can be in range [-1.0, 1.0].
AxisChanged(Axis, f32, Code),
/// Gamepad has been connected. If gamepad's UUID doesn't match one of disconnected gamepads,
/// newly connected gamepad will get new ID.
Connected,
/// Gamepad has been disconnected. Disconnected gamepad will not generate any new events.
Disconnected,
/// There was an `Event`, but it was dropped by one of filters. You should ignore it.
Dropped,
/// A force feedback effect has ran for its duration and stopped.
ForceFeedbackEffectCompleted,
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Gamepad's elements which state can be represented by value from 0.0 to 1.0.
///
/// ![Controller layout](https://gilrs-project.gitlab.io/gilrs/img/controller.svg)
pub enum Button {
// Action Pad
South = BTN_SOUTH,
East = BTN_EAST,
North = BTN_NORTH,
West = BTN_WEST,
C = BTN_C,
Z = BTN_Z,
// Triggers
LeftTrigger = BTN_LT,
LeftTrigger2 = BTN_LT2,
RightTrigger = BTN_RT,
RightTrigger2 = BTN_RT2,
// Menu Pad
Select = BTN_SELECT,
Start = BTN_START,
Mode = BTN_MODE,
// Sticks
LeftThumb = BTN_LTHUMB,
RightThumb = BTN_RTHUMB,
// D-Pad
DPadUp = BTN_DPAD_UP,
DPadDown = BTN_DPAD_DOWN,
DPadLeft = BTN_DPAD_LEFT,
DPadRight = BTN_DPAD_RIGHT,
#[default]
Unknown = BTN_UNKNOWN,
}
impl Button {
pub fn is_action(self) -> bool {
use crate::Button::*;
matches!(self, South | East | North | West | C | Z)
}
pub fn is_trigger(self) -> bool {
use crate::Button::*;
matches!(
self,
LeftTrigger | LeftTrigger2 | RightTrigger | RightTrigger2
)
}
pub fn is_menu(self) -> bool {
use crate::Button::*;
matches!(self, Select | Start | Mode)
}
pub fn is_stick(self) -> bool {
use crate::Button::*;
matches!(self, LeftThumb | RightThumb)
}
pub fn is_dpad(self) -> bool {
use crate::Button::*;
matches!(self, DPadUp | DPadDown | DPadLeft | DPadRight)
}
pub fn to_nec(self) -> Option<Code> {
use gilrs_core::native_ev_codes as necs;
match self {
Button::South => Some(necs::BTN_SOUTH),
Button::East => Some(necs::BTN_EAST),
Button::North => Some(necs::BTN_NORTH),
Button::West => Some(necs::BTN_WEST),
Button::C => Some(necs::BTN_C),
Button::Z => Some(necs::BTN_Z),
Button::LeftTrigger => Some(necs::BTN_LT),
Button::LeftTrigger2 => Some(necs::BTN_LT2),
Button::RightTrigger => Some(necs::BTN_RT),
Button::RightTrigger2 => Some(necs::BTN_RT2),
Button::Select => Some(necs::BTN_SELECT),
Button::Start => Some(necs::BTN_START),
Button::Mode => Some(necs::BTN_MODE),
Button::LeftThumb => Some(necs::BTN_LTHUMB),
Button::RightThumb => Some(necs::BTN_RTHUMB),
Button::DPadUp => Some(necs::BTN_DPAD_UP),
Button::DPadDown => Some(necs::BTN_DPAD_DOWN),
Button::DPadLeft => Some(necs::BTN_DPAD_LEFT),
Button::DPadRight => Some(necs::BTN_DPAD_RIGHT),
_ => None,
}
.map(Code)
}
}
#[repr(u16)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Gamepad's elements which state can be represented by value from -1.0 to 1.0.
///
/// ![Controller layout](https://gilrs-project.gitlab.io/gilrs/img/controller.svg)
pub enum Axis {
LeftStickX = AXIS_LSTICKX,
LeftStickY = AXIS_LSTICKY,
LeftZ = AXIS_LEFTZ,
RightStickX = AXIS_RSTICKX,
RightStickY = AXIS_RSTICKY,
RightZ = AXIS_RIGHTZ,
DPadX = AXIS_DPADX,
DPadY = AXIS_DPADY,
Unknown = AXIS_UNKNOWN,
}
impl Axis {
/// Returns true if axis is `LeftStickX`, `LeftStickY`, `RightStickX` or `RightStickY`.
pub fn is_stick(self) -> bool {
use crate::Axis::*;
matches!(self, LeftStickX | LeftStickY | RightStickX | RightStickY)
}
/// Returns the other axis from same element of gamepad, if any.
///
/// | input | output |
/// |-------------|-------------------|
/// |`LeftStickX` |`Some(LeftStickY)` |
/// |`LeftStickY` |`Some(LeftStickX)` |
/// |`RightStickX`|`Some(RightStickY)`|
/// |`RightStickY`|`Some(RightStickX)`|
/// |`DpadX` |`Some(DpadY)` |
/// |`DpadY` |`Some(DpadX)` |
/// | … |`None` |
pub fn second_axis(self) -> Option<Self> {
use crate::Axis::*;
match self {
LeftStickX => Some(LeftStickY),
LeftStickY => Some(LeftStickX),
RightStickX => Some(RightStickY),
RightStickY => Some(RightStickX),
DPadX => Some(DPadY),
DPadY => Some(DPadX),
_ => None,
}
}
}
/// Represents `Axis` or `Button`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub enum AxisOrBtn {
Axis(Axis),
Btn(Button),
}
impl AxisOrBtn {
pub(crate) fn is_button(&self) -> bool {
matches!(self, AxisOrBtn::Btn(_))
}
}

230
vendor/gilrs/src/ev/state.rs vendored Normal file
View File

@@ -0,0 +1,230 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use crate::ev::Code;
use fnv::FnvHashMap;
use std::collections::hash_map;
use std::iter::Iterator;
use std::time::SystemTime;
/// Cached gamepad state.
#[derive(Clone, Debug)]
pub struct GamepadState {
// Indexed by EvCode (nec)
buttons: FnvHashMap<Code, ButtonData>,
// Indexed by EvCode (nec)
axes: FnvHashMap<Code, AxisData>,
}
impl GamepadState {
pub(crate) fn new() -> Self {
GamepadState {
buttons: FnvHashMap::default(),
axes: FnvHashMap::default(),
}
}
/// Returns `true` if given button is pressed. Returns `false` if there is no information about
/// `btn` or it is not pressed.
pub fn is_pressed(&self, btn: Code) -> bool {
self.buttons
.get(&btn)
.map(|s| s.is_pressed())
.unwrap_or(false)
}
/// Returns value of `el` or 0.0 when there is no information about it. `el` can be either axis
/// or button.
pub fn value(&self, el: Code) -> f32 {
self.axes
.get(&el)
.map(|s| s.value())
.or_else(|| self.buttons.get(&el).map(|s| s.value()))
.unwrap_or(0.0)
}
/// Iterate over buttons data.
pub fn buttons(&self) -> ButtonDataIter<'_> {
ButtonDataIter(self.buttons.iter())
}
/// Iterate over axes data.
pub fn axes(&self) -> AxisDataIter<'_> {
AxisDataIter(self.axes.iter())
}
/// Returns button state and when it changed.
pub fn button_data(&self, btn: Code) -> Option<&ButtonData> {
self.buttons.get(&btn)
}
/// Returns axis state and when it changed.
pub fn axis_data(&self, axis: Code) -> Option<&AxisData> {
self.axes.get(&axis)
}
pub(crate) fn set_btn_pressed(
&mut self,
btn: Code,
pressed: bool,
counter: u64,
timestamp: SystemTime,
) {
let data = self.buttons.entry(btn).or_insert_with(|| {
ButtonData::new(
if pressed { 1.0 } else { 0.0 },
pressed,
false,
counter,
timestamp,
)
});
data.is_pressed = pressed;
data.is_repeating = false;
data.counter = counter;
data.last_event_ts = timestamp;
}
pub(crate) fn set_btn_repeating(&mut self, btn: Code, counter: u64, timestamp: SystemTime) {
let data = self
.buttons
.entry(btn)
.or_insert_with(|| ButtonData::new(1.0, true, true, counter, timestamp));
data.is_repeating = true;
data.counter = counter;
data.last_event_ts = timestamp;
}
pub(crate) fn set_btn_value(
&mut self,
btn: Code,
value: f32,
counter: u64,
timestamp: SystemTime,
) {
let data = self
.buttons
.entry(btn)
.or_insert_with(|| ButtonData::new(value, false, false, counter, timestamp));
data.value = value;
data.counter = counter;
data.last_event_ts = timestamp;
}
pub(crate) fn update_axis(&mut self, axis: Code, data: AxisData) {
self.axes.insert(axis, data);
}
}
/// Iterator over `ButtonData`.
pub struct ButtonDataIter<'a>(hash_map::Iter<'a, Code, ButtonData>);
/// Iterator over `AxisData`.
pub struct AxisDataIter<'a>(hash_map::Iter<'a, Code, AxisData>);
impl<'a> Iterator for ButtonDataIter<'a> {
type Item = (Code, &'a ButtonData);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(k, v)| (*k, v))
}
}
impl<'a> Iterator for AxisDataIter<'a> {
type Item = (Code, &'a AxisData);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(k, v)| (*k, v))
}
}
/// Information about button stored in `State`.
#[derive(Clone, Copy, Debug)]
pub struct ButtonData {
last_event_ts: SystemTime,
counter: u64,
value: f32,
is_pressed: bool,
is_repeating: bool,
}
impl ButtonData {
pub(crate) fn new(
value: f32,
pressed: bool,
repeating: bool,
counter: u64,
time: SystemTime,
) -> Self {
ButtonData {
last_event_ts: time,
counter,
value,
is_pressed: pressed,
is_repeating: repeating,
}
}
/// Returns `true` if button is pressed.
pub fn is_pressed(&self) -> bool {
self.is_pressed
}
/// Returns value of button.
pub fn value(&self) -> f32 {
self.value
}
/// Returns `true` if button is repeating.
pub fn is_repeating(&self) -> bool {
self.is_repeating
}
/// Returns value of counter when button state last changed.
pub fn counter(&self) -> u64 {
self.counter
}
/// Returns when button state last changed.
pub fn timestamp(&self) -> SystemTime {
self.last_event_ts
}
}
/// Information about axis stored in `State`.
#[derive(Clone, Copy, Debug)]
pub struct AxisData {
last_event_ts: SystemTime,
last_event_c: u64,
value: f32,
}
impl AxisData {
pub(crate) fn new(value: f32, counter: u64, time: SystemTime) -> Self {
AxisData {
last_event_ts: time,
last_event_c: counter,
value,
}
}
/// Returns value of axis.
pub fn value(&self) -> f32 {
self.value
}
/// Returns value of counter when axis value last changed.
pub fn counter(&self) -> u64 {
self.last_event_c
}
/// Returns when axis value last changed.
pub fn timestamp(&self) -> SystemTime {
self.last_event_ts
}
}

151
vendor/gilrs/src/ff/base_effect.rs vendored Normal file
View File

@@ -0,0 +1,151 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::ops::Mul;
use super::time::Ticks;
/// Kind of [`BaseEffect`](struct.BaseEffect.html).
///
/// Currently base effect support only xinput model of force feedback, which means that gamepad
/// have weak and strong motor.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum BaseEffectType {
Weak { magnitude: u16 },
Strong { magnitude: u16 },
}
impl BaseEffectType {
fn magnitude(&self) -> u16 {
match *self {
BaseEffectType::Weak { magnitude } => magnitude,
BaseEffectType::Strong { magnitude } => magnitude,
}
}
}
impl Mul<f32> for BaseEffectType {
type Output = BaseEffectType;
fn mul(self, rhs: f32) -> Self::Output {
let mg = (self.magnitude() as f32 * rhs) as u16;
match self {
BaseEffectType::Weak { .. } => BaseEffectType::Weak { magnitude: mg },
BaseEffectType::Strong { .. } => BaseEffectType::Strong { magnitude: mg },
}
}
}
impl Default for BaseEffectType {
fn default() -> Self {
BaseEffectType::Weak { magnitude: 0 }
}
}
/// Basic building block used to create more complex force feedback effects.
///
/// For each base effect you can specify it's type, for how long should it be played and it's
/// strength during playback.
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct BaseEffect {
/// Type of base effect.
pub kind: BaseEffectType,
/// Defines playback duration and delays between each repetition.
pub scheduling: Replay,
// TODO: maybe allow other f(t)?
/// Basic attenuation function.
pub envelope: Envelope,
}
impl BaseEffect {
/// Returns `Weak` or `Strong` after applying envelope.
pub(super) fn magnitude_at(&self, ticks: Ticks) -> BaseEffectType {
if let Some(wrapped) = self.scheduling.wrap(ticks) {
let att =
self.scheduling.at(wrapped) * self.envelope.at(wrapped, self.scheduling.play_for);
self.kind * att
} else {
self.kind * 0.0
}
}
}
// TODO: Image with "envelope"
#[derive(Copy, Clone, PartialEq, Debug, Default)]
/// Envelope shaped attenuation(time) function.
pub struct Envelope {
pub attack_length: Ticks,
pub attack_level: f32,
pub fade_length: Ticks,
pub fade_level: f32,
}
impl Envelope {
pub(super) fn at(&self, ticks: Ticks, dur: Ticks) -> f32 {
debug_assert!(self.fade_length < dur);
debug_assert!(self.attack_length + self.fade_length < dur);
if ticks < self.attack_length {
self.attack_level
+ ticks.0 as f32 * (1.0 - self.attack_level) / self.attack_length.0 as f32
} else if ticks + self.fade_length > dur {
1.0 + (ticks + self.fade_length - dur).0 as f32 * (self.fade_level - 1.0)
/ self.fade_length.0 as f32
} else {
1.0
}
}
}
/// Defines scheduling of the basic force feedback effect.
///
/// ```text
/// ____________ ____________ ____________
/// | | | | |
/// _______| |____________| |____________|
/// after play_for with_delay play_for with_delay play_for
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Replay {
/// Start playback `after` ticks after `Effect::play()` is called.
pub after: Ticks,
/// Playback duration.
pub play_for: Ticks,
/// If playback should be repeated delay it for `with_delay` ticks.
pub with_delay: Ticks,
}
impl Replay {
pub(super) fn at(&self, ticks: Ticks) -> f32 {
if ticks >= self.play_for {
0.0
} else {
1.0
}
}
/// Returns duration of effect calculated as `play_for + with_delay`.
pub fn dur(&self) -> Ticks {
self.play_for + self.with_delay
}
/// Returns `None` if effect hasn't started; or wrapped value
fn wrap(&self, ticks: Ticks) -> Option<Ticks> {
ticks.checked_sub(self.after).map(|t| t % self.dur())
}
}
impl Default for Replay {
fn default() -> Self {
Replay {
after: Ticks(0),
play_for: Ticks(1),
with_delay: Ticks(0),
}
}
}

378
vendor/gilrs/src/ff/effect_source.rs vendored Normal file
View File

@@ -0,0 +1,378 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::error::Error;
use std::ops::{AddAssign, Mul};
use std::{fmt, mem, u16};
use crate::{Event, EventType, GamepadId};
use super::base_effect::{BaseEffect, BaseEffectType};
use super::time::{Repeat, Ticks};
use vec_map::VecMap;
/// Specifies how distance between effect source and listener attenuates effect.
///
/// They are based on
/// [OpenAL Specification](http://openal.org/documentation/openal-1.1-specification.pdf) (chapter
/// 3.4), but the best way to see how they differ is to run `ff_pos` example.
///
/// Make sure that all parameters are ≥ 0. Additionally `Linear` and `LinearClamped` models don't
/// like if `ref_distance == max_distance` while others would prefer `ref_distance > 0`.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum DistanceModel {
/// Effect is not attenuated by distance.
#[default]
None,
/// Linear distance model.
Linear {
ref_distance: f32,
rolloff_factor: f32,
max_distance: f32,
},
/// Linear distance clamped model.
LinearClamped {
ref_distance: f32,
rolloff_factor: f32,
max_distance: f32,
},
/// Inverse distance model.
Inverse {
ref_distance: f32,
rolloff_factor: f32,
},
/// Inverse distance clamped model.
InverseClamped {
ref_distance: f32,
rolloff_factor: f32,
max_distance: f32,
},
/// Exponential distance model.
Exponential {
ref_distance: f32,
rolloff_factor: f32,
},
/// Exponential distance clamped model.
ExponentialClamped {
ref_distance: f32,
rolloff_factor: f32,
max_distance: f32,
},
}
impl DistanceModel {
fn attenuation(self, mut distance: f32) -> f32 {
// For now we will follow OpenAL[1] specification for distance models. See chapter 3.4 for
// more details.
//
// [1]: http://openal.org/documentation/openal-1.1-specification.pdf
match self {
DistanceModel::Linear {
ref_distance,
max_distance,
rolloff_factor,
} => {
distance = distance.min(max_distance);
1.0 - rolloff_factor * (distance - ref_distance) / (max_distance - ref_distance)
}
DistanceModel::LinearClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
distance = distance.max(ref_distance);
distance = distance.min(max_distance);
1.0 - rolloff_factor * (distance - ref_distance) / (max_distance - ref_distance)
}
DistanceModel::Inverse {
ref_distance,
rolloff_factor,
} => ref_distance / (ref_distance + rolloff_factor * (distance - ref_distance)),
DistanceModel::InverseClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
distance = distance.max(ref_distance);
distance = distance.min(max_distance);
ref_distance / (ref_distance + rolloff_factor * (distance - ref_distance))
}
DistanceModel::Exponential {
ref_distance,
rolloff_factor,
} => (distance / ref_distance).powf(-rolloff_factor),
DistanceModel::ExponentialClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
distance = distance.max(ref_distance);
distance = distance.min(max_distance);
(distance / ref_distance).powf(-rolloff_factor)
}
DistanceModel::None => 1.0,
}
}
pub(crate) fn validate(self) -> Result<(), DistanceModelError> {
let (ref_distance, rolloff_factor, max_distance) = match self {
DistanceModel::Inverse {
ref_distance,
rolloff_factor,
} => {
if ref_distance <= 0.0 {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, 0.0)
}
DistanceModel::InverseClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
if ref_distance <= 0.0 {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, max_distance)
}
DistanceModel::Linear {
ref_distance,
max_distance,
rolloff_factor,
} => {
if ref_distance == max_distance {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, max_distance)
}
DistanceModel::LinearClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
if ref_distance == max_distance {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, max_distance)
}
DistanceModel::Exponential {
ref_distance,
rolloff_factor,
} => {
if ref_distance <= 0.0 {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, 0.0)
}
DistanceModel::ExponentialClamped {
ref_distance,
max_distance,
rolloff_factor,
} => {
if ref_distance <= 0.0 {
return Err(DistanceModelError::InvalidModelParameter);
}
(ref_distance, rolloff_factor, max_distance)
}
DistanceModel::None => (0.0, 0.0, 0.0),
};
if ref_distance < 0.0 {
Err(DistanceModelError::InvalidReferenceDistance)
} else if rolloff_factor < 0.0 {
Err(DistanceModelError::InvalidRolloffFactor)
} else if max_distance < 0.0 {
Err(DistanceModelError::InvalidMaxDistance)
} else {
Ok(())
}
}
}
/// Error that can be returned when passing [`DistanceModel`](struct.DistanceModel.html) with
/// invalid value.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DistanceModelError {
/// Reference distance is < 0.
InvalidReferenceDistance,
/// Rolloff factor is < 0.
InvalidRolloffFactor,
/// Max distance is < 0.
InvalidMaxDistance,
/// Possible divide by zero
InvalidModelParameter,
}
impl Error for DistanceModelError {}
impl fmt::Display for DistanceModelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
DistanceModelError::InvalidReferenceDistance => "reference distance is < 0",
DistanceModelError::InvalidRolloffFactor => "rolloff factor is < 0",
DistanceModelError::InvalidMaxDistance => "max distance is < 0",
DistanceModelError::InvalidModelParameter => "possible divide by zero",
};
f.write_str(s)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(super) enum EffectState {
Playing { since: Ticks },
Stopped,
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct EffectSource {
base_effects: Vec<BaseEffect>,
// TODO: Use bitset
pub(super) devices: VecMap<()>,
pub(super) repeat: Repeat,
pub(super) distance_model: DistanceModel,
pub(super) position: [f32; 3],
pub(super) gain: f32,
pub(super) state: EffectState,
pub(super) completion_events: Vec<Event>,
}
impl EffectSource {
pub(super) fn new(
base_effects: Vec<BaseEffect>,
devices: VecMap<()>,
repeat: Repeat,
dist_model: DistanceModel,
position: [f32; 3],
gain: f32,
) -> Self {
EffectSource {
base_effects,
devices,
repeat,
distance_model: dist_model,
position,
gain,
state: EffectState::Stopped,
completion_events: vec![],
}
}
pub(super) fn combine_base_effects(&mut self, ticks: Ticks, actor_pos: [f32; 3]) -> Magnitude {
let ticks = match self.state {
EffectState::Playing { since } => {
debug_assert!(ticks >= since);
ticks - since
}
EffectState::Stopped => return Magnitude::zero(),
};
match self.repeat {
Repeat::For(max_dur) if ticks > max_dur => {
self.state = EffectState::Stopped;
self.devices.keys().for_each(|id| {
let event = Event::new(GamepadId(id), EventType::ForceFeedbackEffectCompleted);
self.completion_events.push(event);
});
}
_ => (),
}
let attenuation = self
.distance_model
.attenuation(self.position.distance(actor_pos))
* self.gain;
if attenuation < 0.05 {
return Magnitude::zero();
}
let mut final_magnitude = Magnitude::zero();
for effect in &self.base_effects {
match effect.magnitude_at(ticks) {
BaseEffectType::Strong { magnitude } => {
final_magnitude.strong = final_magnitude.strong.saturating_add(magnitude)
}
BaseEffectType::Weak { magnitude } => {
final_magnitude.weak = final_magnitude.weak.saturating_add(magnitude)
}
};
}
final_magnitude * attenuation
}
pub(super) fn flush_completion_events(&mut self) -> Vec<Event> {
mem::take(&mut self.completion_events)
}
}
/// (strong, weak) pair.
#[derive(Copy, Clone, Debug)]
pub(super) struct Magnitude {
pub strong: u16,
pub weak: u16,
}
impl Magnitude {
pub fn zero() -> Self {
Magnitude { strong: 0, weak: 0 }
}
}
impl Mul<f32> for Magnitude {
type Output = Magnitude;
fn mul(self, rhs: f32) -> Self::Output {
debug_assert!(rhs >= 0.0);
let strong = self.strong as f32 * rhs;
let strong = if strong > u16::MAX as f32 {
u16::MAX
} else {
strong as u16
};
let weak = self.weak as f32 * rhs;
let weak = if weak > u16::MAX as f32 {
u16::MAX
} else {
weak as u16
};
Magnitude { strong, weak }
}
}
impl AddAssign for Magnitude {
fn add_assign(&mut self, rhs: Magnitude) {
self.strong = self.strong.saturating_add(rhs.strong);
self.weak = self.weak.saturating_add(rhs.weak);
}
}
trait SliceVecExt {
type Base;
fn distance(self, from: Self) -> Self::Base;
}
impl SliceVecExt for [f32; 3] {
type Base = f32;
fn distance(self, from: Self) -> f32 {
((from[0] - self[0]).powi(2) + (from[1] - self[1]).powi(2) + (from[2] - self[2]).powi(2))
.sqrt()
}
}

458
vendor/gilrs/src/ff/mod.rs vendored Normal file
View File

@@ -0,0 +1,458 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
// This code is not used on wasm
#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
//! Force feedback module.
//!
//! To use force feedback, you have to create one or more [`Effect`s](struct.Effect.html). Each
//! `Effect` contains one or more [`BasicEffect`s](struct.BasicEffect.html) and parameters that
//! describe effect's source, like it's position, gain or used
//! [`DistanceModel`](enum.DistanceModel.html). Final strength of effect is based on saturating sum
//! (to `u16::MAX`) of all base effects and time from the start of playback, attenuation from
//! distance between effect source and listener (represented by gamepad) and effect's gain.
//!
//! See also [`Gilrs::set_listener_position()`](../struct.Gilrs.html#method.set_listener_position)
//! and [`Gamepad::is_ff_supported()`](../struct.Gamepad.html#method.is_ff_supported).
//!
//! # Example
//!
//! ```rust
//! use gilrs::Gilrs;
//! use gilrs::ff::{EffectBuilder, Replay, BaseEffect, BaseEffectType, Ticks};
//!
//! let mut gilrs = Gilrs::new().unwrap();
//! let support_ff = gilrs
//! .gamepads()
//! .filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None })
//! .collect::<Vec<_>>();
//!
//! let duration = Ticks::from_ms(150);
//! let effect = EffectBuilder::new()
//! .add_effect(BaseEffect {
//! kind: BaseEffectType::Strong { magnitude: 60_000 },
//! scheduling: Replay { play_for: duration, with_delay: duration * 3, ..Default::default() },
//! envelope: Default::default(),
//! })
//! .add_effect(BaseEffect {
//! kind: BaseEffectType::Weak { magnitude: 60_000 },
//! scheduling: Replay { after: duration * 2, play_for: duration, with_delay: duration * 3 },
//! ..Default::default()
//! })
//! .gamepads(&support_ff)
//! .finish(&mut gilrs).unwrap();
//!
//! effect.play().unwrap();
//! ```
//!
//! See [`examples/ff_pos.rs`](https://gitlab.com/gilrs-project/gilrs/blob/v0.11.0/examples/ff_pos.rs) for
//! more advanced example.
mod base_effect;
mod effect_source;
pub(crate) mod server;
mod time;
pub use self::base_effect::{BaseEffect, BaseEffectType, Envelope, Replay};
pub use self::effect_source::{DistanceModel, DistanceModelError};
#[allow(unused_imports)]
pub(crate) use self::time::TICK_DURATION;
pub use self::time::{Repeat, Ticks};
use std::error::Error as StdError;
use std::hash::{Hash, Hasher};
use std::sync::mpsc::{SendError, Sender};
use std::{f32, fmt};
use self::effect_source::EffectSource;
use crate::ff::server::Message;
use crate::gamepad::{Gamepad, GamepadId, Gilrs};
use crate::utils;
use vec_map::VecMap;
/// Handle to force feedback effect.
///
/// `Effect` represents force feedback effect that can be played on one or more gamepads. It uses a
/// form of reference counting, so it can be cheaply cloned. To create new `Effect` use
/// [`EffectBuilder`](struct.EffectBuilder.html).
///
/// All methods on can return `Error::SendFailed` although it shouldn't normally happen.
pub struct Effect {
id: usize,
tx: Sender<Message>,
}
impl PartialEq for Effect {
fn eq(&self, other: &Effect) -> bool {
self.id == other.id
}
}
impl Eq for Effect {}
impl Hash for Effect {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Clone for Effect {
fn clone(&self) -> Self {
let _ = self.tx.send(Message::HandleCloned { id: self.id });
Effect {
id: self.id,
tx: self.tx.clone(),
}
}
}
impl Drop for Effect {
fn drop(&mut self) {
let _ = self.tx.send(Message::HandleDropped { id: self.id });
}
}
impl Effect {
/// Plays effect on all associated gamepads.
pub fn play(&self) -> Result<(), Error> {
self.tx.send(Message::Play { id: self.id })?;
Ok(())
}
pub fn stop(&self) -> Result<(), Error> {
self.tx.send(Message::Stop { id: self.id })?;
Ok(())
}
/// Changes gamepads that are associated with effect. Effect will be only played on gamepads
/// from last call to this function.
///
/// # Errors
///
/// Returns `Error::Disconnected(id)` or `Error::FfNotSupported(id)` on first gamepad in `ids`
/// that is disconnected or doesn't support force feedback.
pub fn set_gamepads(&self, ids: &[GamepadId], gilrs: &Gilrs) -> Result<(), Error> {
let mut gamepads = VecMap::new();
for dev in ids.iter().cloned() {
if !gilrs
.connected_gamepad(dev)
.ok_or(Error::Disconnected(dev))?
.is_ff_supported()
{
return Err(Error::FfNotSupported(dev));
} else {
gamepads.insert(dev.0, ());
}
}
self.tx.send(Message::SetGamepads {
id: self.id,
gamepads,
})?;
Ok(())
}
/// Adds gamepad to the list of gamepads associated with effect.
///
/// # Errors
///
/// Returns `Error::Disconnected(id)` or `Error::FfNotSupported(id)` if gamepad is not connected
/// or does not support force feedback.
pub fn add_gamepad(&self, gamepad: &Gamepad<'_>) -> Result<(), Error> {
if !gamepad.is_connected() {
Err(Error::Disconnected(gamepad.id()))
} else if !gamepad.is_ff_supported() {
Err(Error::FfNotSupported(gamepad.id()))
} else {
self.tx.send(Message::AddGamepad {
id: self.id,
gamepad_id: gamepad.id(),
})?;
Ok(())
}
}
/// Changes what should happen to effect when it ends.
pub fn set_repeat(&self, repeat: Repeat) -> Result<(), Error> {
self.tx.send(Message::SetRepeat {
id: self.id,
repeat,
})?;
Ok(())
}
/// Changes distance model associated with effect.
///
/// # Errors
///
/// Returns `Error::InvalidDistanceModel` if `model` is not valid. See
/// [`DistanceModel`](enum.DistanceModelError.html) for details.
pub fn set_distance_model(&self, model: DistanceModel) -> Result<(), Error> {
model.validate()?;
self.tx
.send(Message::SetDistanceModel { id: self.id, model })?;
Ok(())
}
/// Changes position of the source of effect.
pub fn set_position<Vec3f: Into<[f32; 3]>>(&self, position: Vec3f) -> Result<(), Error> {
let position = position.into();
self.tx.send(Message::SetPosition {
id: self.id,
position,
})?;
Ok(())
}
/// Changes gain of the effect. `gain` will be clamped to \[0.0, f32::MAX\].
pub fn set_gain(&self, gain: f32) -> Result<(), Error> {
let gain = utils::clamp(gain, 0.0, f32::MAX);
self.tx.send(Message::SetGain { id: self.id, gain })?;
Ok(())
}
}
/// Creates new [`Effect`](struct.Effect.html).
#[derive(Clone, PartialEq, Debug)]
pub struct EffectBuilder {
base_effects: Vec<BaseEffect>,
devices: VecMap<()>,
repeat: Repeat,
dist_model: DistanceModel,
position: [f32; 3],
gain: f32,
}
impl EffectBuilder {
/// Creates new builder with following defaults: no gamepads, no base effects, repeat set to
/// infinitely, no distance model, position in (0.0, 0.0, 0.0) and gain 1.0. Use `finish()` to
/// create new effect.
pub fn new() -> Self {
EffectBuilder {
base_effects: Vec::new(),
devices: VecMap::new(),
repeat: Repeat::Infinitely,
dist_model: DistanceModel::None,
position: [0.0, 0.0, 0.0],
gain: 1.0,
}
}
/// Adds new [`BaseEffect`](struct.BaseEffect.html).
pub fn add_effect(&mut self, effect: BaseEffect) -> &mut Self {
self.base_effects.push(effect);
self
}
/// Changes gamepads that are associated with effect. Effect will be only played on gamepads
/// from last call to this function.
pub fn gamepads(&mut self, ids: &[GamepadId]) -> &mut Self {
for dev in ids {
self.devices.insert(dev.0, ());
}
self
}
/// Adds gamepad to the list of gamepads associated with effect.
pub fn add_gamepad(&mut self, gamepad: &Gamepad<'_>) -> &mut Self {
self.devices.insert(gamepad.id().0, ());
self
}
/// Changes what should happen to effect when it ends.
pub fn repeat(&mut self, repeat: Repeat) -> &mut Self {
self.repeat = repeat;
self
}
/// Changes distance model associated with effect.
pub fn distance_model(&mut self, model: DistanceModel) -> &mut Self {
self.dist_model = model;
self
}
/// Changes position of the source of effect.
pub fn position<Vec3f: Into<[f32; 3]>>(&mut self, position: Vec3f) -> &mut Self {
self.position = position.into();
self
}
/// Changes gain of the effect. `gain` will be clamped to \[0.0, f32::MAX\].
pub fn gain(&mut self, gain: f32) -> &mut Self {
self.gain = utils::clamp(gain, 0.0, f32::MAX);
self
}
/// Validates all parameters and creates new effect.
///
/// # Errors
///
/// Returns `Error::Disconnected(id)` or `Error::FfNotSupported(id)` on first gamepad in `ids`
/// that is disconnected or doesn't support force feedback.
///
/// Returns `Error::InvalidDistanceModel` if `model` is not valid. See
/// [`DistanceModel`](enum.DistanceModelError.html) for details.
pub fn finish(&mut self, gilrs: &mut Gilrs) -> Result<Effect, Error> {
for (dev, _) in &self.devices {
let dev = GamepadId(dev);
if !gilrs
.connected_gamepad(dev)
.ok_or(Error::Disconnected(dev))?
.is_ff_supported()
{
return Err(Error::FfNotSupported(dev));
}
}
self.dist_model.validate()?;
let effect = EffectSource::new(
self.base_effects.clone(),
self.devices.clone(),
self.repeat,
self.dist_model,
self.position,
self.gain,
);
let id = gilrs.next_ff_id();
let tx = gilrs.ff_sender();
tx.send(Message::Create {
id,
effect: Box::new(effect),
})?;
Ok(Effect { id, tx: tx.clone() })
}
}
impl Default for EffectBuilder {
fn default() -> Self {
Self::new()
}
}
/// Basic error type in force feedback module.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// Force feedback is not supported by device with this ID
FfNotSupported(GamepadId),
/// Device is not connected
Disconnected(GamepadId),
/// Distance model is invalid.
InvalidDistanceModel(DistanceModelError),
/// The other end of channel was dropped.
SendFailed,
/// Unexpected error has occurred
Other,
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::InvalidDistanceModel(m) => Some(m),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let sbuf;
let s = match self {
Error::FfNotSupported(id) => {
sbuf = format!(
"force feedback is not supported by device with id {}.",
id.0
);
sbuf.as_ref()
}
Error::Disconnected(id) => {
sbuf = format!("device with id {} is not connected.", id.0);
sbuf.as_ref()
}
Error::InvalidDistanceModel(_) => "distance model is invalid",
Error::SendFailed => "receiving end of a channel is disconnected.",
Error::Other => "unespected error has occurred.",
};
fmt.write_str(s)
}
}
impl<T> From<SendError<T>> for Error {
fn from(_: SendError<T>) -> Self {
Error::SendFailed
}
}
impl From<DistanceModelError> for Error {
fn from(f: DistanceModelError) -> Self {
Error::InvalidDistanceModel(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn envelope() {
let env = Envelope {
attack_length: Ticks(10),
attack_level: 0.2,
fade_length: Ticks(10),
fade_level: 0.2,
};
let dur = Ticks(40);
assert_eq!(env.at(Ticks(0), dur), 0.2);
assert_eq!(env.at(Ticks(5), dur), 0.6);
assert_eq!(env.at(Ticks(10), dur), 1.0);
assert_eq!(env.at(Ticks(20), dur), 1.0);
assert_eq!(env.at(Ticks(30), dur), 1.0);
assert_eq!(env.at(Ticks(35), dur), 0.6);
assert_eq!(env.at(Ticks(40), dur), 0.19999999);
}
#[test]
fn envelope_default() {
let env = Envelope::default();
let dur = Ticks(40);
assert_eq!(env.at(Ticks(0), dur), 1.0);
assert_eq!(env.at(Ticks(20), dur), 1.0);
assert_eq!(env.at(Ticks(40), dur), 1.0);
}
#[test]
fn replay() {
let replay = Replay {
after: Ticks(10),
play_for: Ticks(50),
with_delay: Ticks(20),
};
assert_eq!(replay.at(Ticks(0)), 1.0);
assert_eq!(replay.at(Ticks(9)), 1.0);
assert_eq!(replay.at(Ticks(10)), 1.0);
assert_eq!(replay.at(Ticks(30)), 1.0);
assert_eq!(replay.at(Ticks(59)), 0.0);
assert_eq!(replay.at(Ticks(60)), 0.0);
assert_eq!(replay.at(Ticks(70)), 0.0);
}
}

319
vendor/gilrs/src/ff/server.rs vendored Normal file
View File

@@ -0,0 +1,319 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::effect_source::{DistanceModel, EffectSource, EffectState, Magnitude};
use super::time::{Repeat, Ticks, TICK_DURATION};
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::{Duration, Instant};
use crate::gamepad::GamepadId;
use crate::Event;
use gilrs_core::FfDevice;
use vec_map::VecMap;
#[derive(Debug)]
pub(crate) enum Message {
Create {
id: usize,
effect: Box<EffectSource>,
},
HandleCloned {
id: usize,
},
HandleDropped {
id: usize,
},
Play {
id: usize,
},
Stop {
id: usize,
},
Open {
id: usize,
device: FfDevice,
},
Close {
id: usize,
},
SetListenerPosition {
id: usize,
position: [f32; 3],
},
SetGamepads {
id: usize,
gamepads: VecMap<()>,
},
AddGamepad {
id: usize,
gamepad_id: GamepadId,
},
SetRepeat {
id: usize,
repeat: Repeat,
},
SetDistanceModel {
id: usize,
model: DistanceModel,
},
SetPosition {
id: usize,
position: [f32; 3],
},
SetGain {
id: usize,
gain: f32,
},
}
pub(crate) enum FfMessage {
EffectCompleted { event: Event },
}
impl Message {
// Whether to use trace level logging or debug
fn use_trace_level(&self) -> bool {
use self::Message::*;
matches!(
self,
&SetListenerPosition { .. } | &HandleCloned { .. } | &HandleDropped { .. }
)
}
}
#[derive(Debug)]
struct Device {
inner: FfDevice,
position: [f32; 3],
}
struct Effect {
source: EffectSource,
/// Number of created effect's handles.
count: usize,
}
impl Effect {
fn inc(&mut self) -> usize {
self.count += 1;
self.count
}
fn dec(&mut self) -> usize {
self.count -= 1;
self.count
}
}
impl From<EffectSource> for Effect {
fn from(source: EffectSource) -> Self {
Effect { source, count: 1 }
}
}
impl Deref for Effect {
type Target = EffectSource;
fn deref(&self) -> &Self::Target {
&self.source
}
}
impl DerefMut for Effect {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.source
}
}
impl From<FfDevice> for Device {
fn from(inner: FfDevice) -> Self {
Device {
inner,
position: [0.0, 0.0, 0.0],
}
}
}
pub(crate) fn run(tx: Sender<FfMessage>, rx: Receiver<Message>) {
let mut effects = VecMap::<Effect>::new();
let mut devices = VecMap::<Device>::new();
let sleep_dur = Duration::from_millis(TICK_DURATION.into());
let mut tick = Ticks(0);
let mut completion_events = Vec::<Event>::new();
loop {
let t1 = Instant::now();
while let Ok(ev) = rx.try_recv() {
if ev.use_trace_level() {
trace!("New ff event: {:?}", ev);
} else {
debug!("New ff event: {:?}", ev);
}
match ev {
Message::Create { id, effect } => {
effects.insert(id, (*effect).into());
}
Message::Play { id } => {
if let Some(effect) = effects.get_mut(id) {
effect.source.state = EffectState::Playing { since: tick }
} else {
error!("{:?} with wrong ID", ev);
}
}
Message::Stop { id } => {
if let Some(effect) = effects.get_mut(id) {
effect.source.state = EffectState::Stopped
} else {
error!("{:?} with wrong ID", ev);
}
}
Message::Open { id, device } => {
devices.insert(id, device.into());
}
Message::Close { id } => {
devices.remove(id);
}
Message::SetListenerPosition { id, position } => {
if let Some(device) = devices.get_mut(id) {
device.position = position;
} else {
error!("{:?} with wrong ID", ev);
}
}
Message::HandleCloned { id } => {
if let Some(effect) = effects.get_mut(id) {
effect.inc();
} else {
error!("{:?} with wrong ID", ev);
}
}
Message::HandleDropped { id } => {
let mut drop = false;
if let Some(effect) = effects.get_mut(id) {
if effect.dec() == 0 {
drop = true;
}
} else {
error!("{:?} with wrong ID", ev);
}
if drop {
effects.remove(id);
}
}
Message::SetGamepads { id, gamepads } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.devices = gamepads;
} else {
error!("Invalid effect id {} when changing gamepads.", id);
}
}
Message::AddGamepad { id, gamepad_id } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.devices.insert(gamepad_id.0, ());
} else {
error!("Invalid effect id {} when changing gamepads.", id);
}
}
Message::SetRepeat { id, repeat } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.repeat = repeat;
} else {
error!("Invalid effect id {} when changing repeat mode.", id);
}
}
Message::SetDistanceModel { id, model } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.distance_model = model;
} else {
error!("Invalid effect id {} when changing distance model.", id);
}
}
Message::SetPosition { id, position } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.position = position;
} else {
error!("Invalid effect id {}.", id);
}
}
Message::SetGain { id, gain } => {
if let Some(eff) = effects.get_mut(id) {
eff.source.gain = gain;
} else {
error!("Invalid effect id {} when changing effect gain.", id);
}
}
}
}
combine_and_play(&mut effects, &mut devices, tick, &mut completion_events);
completion_events.iter().for_each(|ev| {
let _ = tx.send(FfMessage::EffectCompleted { event: *ev });
});
completion_events.clear();
let dur = Instant::now().duration_since(t1);
if dur > sleep_dur {
// TODO: Should we add dur - sleep_dur to next iteration's dur?
warn!(
"One iteration of a force feedback loop took more than {}ms!",
TICK_DURATION
);
} else {
thread::sleep(sleep_dur - dur);
}
tick.inc();
}
}
pub(crate) fn init() -> (Sender<Message>, Receiver<FfMessage>) {
let (tx, _rx) = mpsc::channel();
let (_tx2, rx2) = mpsc::channel();
// Wasm doesn't support threads and force feedback
#[cfg(not(target_arch = "wasm32"))]
std::thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || run(_tx2, _rx))
.expect("failed to spawn thread");
(tx, rx2)
}
fn combine_and_play(
effects: &mut VecMap<Effect>,
devices: &mut VecMap<Device>,
tick: Ticks,
completion_events: &mut Vec<Event>,
) {
for (dev_id, dev) in devices {
let mut magnitude = Magnitude::zero();
for (_, ref mut effect) in effects.iter_mut() {
if effect.devices.contains_key(dev_id) {
magnitude += effect.combine_base_effects(tick, dev.position);
completion_events.extend(effect.flush_completion_events());
}
}
trace!(
"({:?}) Setting ff state of {:?} to {:?}",
tick,
dev,
magnitude
);
dev.inner.set_ff_state(
magnitude.strong,
magnitude.weak,
Duration::from_millis(u64::from(TICK_DURATION) * 2),
);
}
}

114
vendor/gilrs/src/ff/time.rs vendored Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::ops::{Add, AddAssign, Mul, MulAssign, Rem, Sub, SubAssign};
use std::time::Duration;
use crate::utils;
pub(crate) const TICK_DURATION: u32 = 50;
/// Represents duration.
///
/// This type is only useful as input parameter for other functions in force feedback module. To
/// create it, use `from_ms()` method. Keep in mind that `Ticks` **is not precise** representation
/// of time.
///
/// # Example
///
/// ```rust
/// use gilrs::ff::Ticks;
/// use std::time::Duration;
///
/// let t1 = Ticks::from_ms(110);
/// let t2 = Ticks::from(Duration::from_millis(130));
///
/// /// `Ticks` is not precise.
/// assert_eq!(t1, t2);
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Ticks(pub(super) u32);
impl Ticks {
pub fn from_ms(dur: u32) -> Self {
Ticks(utils::ceil_div(dur, TICK_DURATION))
}
pub(super) fn inc(&mut self) {
self.0 += 1
}
pub(super) fn checked_sub(self, rhs: Ticks) -> Option<Ticks> {
self.0.checked_sub(rhs.0).map(Ticks)
}
}
impl From<Duration> for Ticks {
fn from(dur: Duration) -> Self {
Ticks::from_ms(dur.as_secs() as u32 * 1000 + dur.subsec_millis())
}
}
impl Add for Ticks {
type Output = Ticks;
fn add(self, rhs: Ticks) -> Self::Output {
Ticks(self.0 + rhs.0)
}
}
impl AddAssign for Ticks {
fn add_assign(&mut self, rhs: Ticks) {
self.0 += rhs.0
}
}
impl Sub for Ticks {
type Output = Ticks;
fn sub(self, rhs: Ticks) -> Self::Output {
Ticks(self.0 - rhs.0)
}
}
impl SubAssign for Ticks {
fn sub_assign(&mut self, rhs: Ticks) {
self.0 -= rhs.0
}
}
impl Mul<u32> for Ticks {
type Output = Ticks;
fn mul(self, rhs: u32) -> Self::Output {
Ticks(self.0 * rhs)
}
}
impl MulAssign<u32> for Ticks {
fn mul_assign(&mut self, rhs: u32) {
self.0 *= rhs;
}
}
impl Rem for Ticks {
type Output = Ticks;
fn rem(self, rhs: Ticks) -> Self::Output {
Ticks(self.0 % rhs.0)
}
}
/// Describes how long effect should be played.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Repeat {
/// Play effect until stop() is called.
#[default]
Infinitely,
/// Play effect for specified time.
For(Ticks),
}

1251
vendor/gilrs/src/gamepad.rs vendored Normal file

File diff suppressed because it is too large Load Diff

128
vendor/gilrs/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,128 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! GilRs - Game Input Library for Rust
//! ===================================
//!
//! GilRs abstract platform specific APIs to provide unified interfaces for working with gamepads.
//!
//! Main features:
//!
//! - Unified gamepad layout—buttons and axes are represented by familiar names
//! - Support for SDL2 mappings including `SDL_GAMECONTROLLERCONFIG` environment
//! variable which Steam uses
//! - Hotplugging—GilRs will try to assign new IDs for new gamepads and reuse same
//! ID for gamepads which reconnected
//! - Force feedback (rumble)
//! - Power information (is gamepad wired, current battery status)
//!
//! Example
//! -------
//!
//! ```
//! use gilrs::{Gilrs, Button, Event};
//!
//! let mut gilrs = Gilrs::new().unwrap();
//!
//! // Iterate over all connected gamepads
//! for (_id, gamepad) in gilrs.gamepads() {
//! println!("{} is {:?}", gamepad.name(), gamepad.power_info());
//! }
//!
//! let mut active_gamepad = None;
//!
//! loop {
//! // Examine new events
//! while let Some(Event { id, event, time, .. }) = gilrs.next_event() {
//! println!("{:?} New event from {}: {:?}", time, id, event);
//! active_gamepad = Some(id);
//! }
//!
//! // You can also use cached gamepad state
//! if let Some(gamepad) = active_gamepad.map(|id| gilrs.gamepad(id)) {
//! if gamepad.is_pressed(Button::South) {
//! println!("Button South is pressed (XBox - A, PS - X)");
//! }
//! }
//! # break;
//! }
//! ```
//!
//! Supported features
//! ------------------
//!
//! | | Input | Hotplugging | Force feedback |
//! |------------------|:-----:|:-----------:|:--------------:|
//! | Linux/BSD (evdev)| ✓ | ✓ | ✓ |
//! | Windows (XInput) | ✓ | ✓ | ✓ |
//! | OS X | ✓ | ✓ | ✕ |
//! | Wasm | ✓ | ✓ | n/a |
//! | Android | ✕ | ✕ | ✕ |
//!
//! Controller layout
//! -----------------
//!
//! ![Controller layout](https://gilrs-project.gitlab.io/gilrs/img/controller.svg)
//! [original image by nicefrog](http://opengameart.org/content/generic-gamepad-template)
//!
//! Mappings
//! --------
//!
//! GilRs use SDL-compatible controller mappings to fix on Linux legacy drivers that doesn't follow
//! [Linux Gamepad API](https://www.kernel.org/doc/Documentation/input/gamepad.txt) and to provide
//! unified button layout for platforms that doesn't make any guarantees about it. The main source
//! is [SDL_GameControllerDB](https://github.com/gabomdq/SDL_GameControllerDB), but library also
//! support loading mappings from environment variable `SDL_GAMECONTROLLERCONFIG` (which Steam
//! use).
//!
//! Cargo features
//! --------------
//!
//! - `serde-serialize` - enable deriving of serde's `Serialize` and `Deserialize` for
//! various types.
//!
//! Platform specific notes
//! ======================
//!
//! Linux/BSD (evdev)
//! -----
//!
//! With evdev, GilRs read (and write, in case of force feedback) directly from appropriate
//! `/dev/input/event*` file. This mean that user have to have read and write access to this file.
//! On most distros it shouldn't be a problem, but if it is, you will have to create udev rule.
//! On FreeBSD generic HID gamepads use hgame(4) and special use Linux driver via `webcamd`.
//!
//! To build GilRs, you will need pkg-config and libudev .pc file. On some distributions this file
//! is packaged in separate archive (e.g., `libudev-dev` in Debian, `libudev-devd` in FreeBSD).
//!
//! Wasm
//! ----
//!
//! Wasm implementation uses stdweb, or wasm-bindgen with the wasm-bindgen feature.
//! For stdweb, you will need [cargo-web](https://github.com/koute/cargo-web) to build gilrs for
//! wasm32-unknown-unknown. For wasm-bindgen, you will need the wasm-bindgen cli or a tool like
//! [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
//! Unlike other platforms, events are only generated when you call `Gilrs::next_event()`.
#[macro_use]
extern crate log;
mod constants;
mod gamepad;
mod mapping;
mod utils;
pub mod ev;
pub mod ff;
pub use crate::ev::filter::Filter;
pub use crate::ev::{Axis, Button, Event, EventType};
pub use crate::gamepad::{
ConnectedGamepadsIterator, Error, Gamepad, GamepadId, Gilrs, GilrsBuilder, MappingSource,
PowerInfo,
};
pub use crate::mapping::{MappingData as Mapping, MappingError};

731
vendor/gilrs/src/mapping/mod.rs vendored Normal file
View File

@@ -0,0 +1,731 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
#![cfg_attr(target_os = "windows", allow(dead_code))]
mod parser;
use crate::ev::{self, Axis, AxisOrBtn, Button};
use crate::utils::PATH_SEPARATOR;
use gilrs_core::native_ev_codes as nec;
use gilrs_core::EvCode;
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fmt::{Display, Formatter, Result as FmtResult, Write as _};
use fnv::FnvHashMap;
use uuid::Uuid;
use vec_map::VecMap;
use self::parser::{Error as ParserError, ErrorKind as ParserErrorKind, Parser, Token};
/// Platform name used by SDL mappings
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))]
const SDL_PLATFORM_NAME: &str = "Linux";
#[cfg(target_os = "macos")]
const SDL_PLATFORM_NAME: &str = "Mac OS X";
#[cfg(target_os = "windows")]
const SDL_PLATFORM_NAME: &str = "Windows";
#[cfg(all(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(target_os = "macos"),
not(target_os = "windows")
))]
const SDL_PLATFORM_NAME: &str = "Unknown";
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
/// Store mappings from one `EvCode` (`u16`) to another.
///
/// This struct is internal, `MappingData` is exported in public interface as `Mapping`.
pub struct Mapping {
mappings: FnvHashMap<EvCode, AxisOrBtn>,
name: String,
default: bool,
hats_mapped: u8,
}
impl Mapping {
pub fn new() -> Self {
Mapping {
mappings: FnvHashMap::default(),
name: String::new(),
default: false,
hats_mapped: 0,
}
}
pub fn default(gamepad: &gilrs_core::Gamepad) -> Self {
use self::Axis as Ax;
use self::AxisOrBtn::*;
macro_rules! fnv_map {
( $( $key:expr => $elem:expr ),* ) => {
{
let mut map = FnvHashMap::default();
$(
map.insert($key, $elem);
)*
map
}
};
}
let mut mappings = fnv_map![
nec::BTN_SOUTH => Btn(Button::South),
nec::BTN_EAST => Btn(Button::East),
nec::BTN_C => Btn(Button::C),
nec::BTN_NORTH => Btn(Button::North),
nec::BTN_WEST => Btn(Button::West),
nec::BTN_Z => Btn(Button::Z),
nec::BTN_LT => Btn(Button::LeftTrigger),
nec::BTN_RT => Btn(Button::RightTrigger),
nec::BTN_LT2 => Btn(Button::LeftTrigger2),
nec::BTN_RT2 => Btn(Button::RightTrigger2),
nec::BTN_SELECT => Btn(Button::Select),
nec::BTN_START => Btn(Button::Start),
nec::BTN_MODE => Btn(Button::Mode),
nec::BTN_LTHUMB => Btn(Button::LeftThumb),
nec::BTN_RTHUMB => Btn(Button::RightThumb),
nec::BTN_DPAD_UP => Btn(Button::DPadUp),
nec::BTN_DPAD_DOWN => Btn(Button::DPadDown),
nec::BTN_DPAD_LEFT => Btn(Button::DPadLeft),
nec::BTN_DPAD_RIGHT => Btn(Button::DPadRight),
nec::AXIS_LT => Btn(Button::LeftTrigger),
nec::AXIS_RT => Btn(Button::RightTrigger),
nec::AXIS_LT2 => Btn(Button::LeftTrigger2),
nec::AXIS_RT2 => Btn(Button::RightTrigger2),
nec::AXIS_LSTICKX => Axis(Ax::LeftStickX),
nec::AXIS_LSTICKY => Axis(Ax::LeftStickY),
nec::AXIS_LEFTZ => Axis(Ax::LeftZ),
nec::AXIS_RSTICKX => Axis(Ax::RightStickX),
nec::AXIS_RSTICKY => Axis(Ax::RightStickY),
nec::AXIS_RIGHTZ => Axis(Ax::RightZ),
nec::AXIS_DPADX => Axis(Ax::DPadX),
nec::AXIS_DPADY => Axis(Ax::DPadY)
];
// Remove all mappings that don't have corresponding element in gamepad. Partial fix to #83
let axes = [
nec::AXIS_DPADX,
nec::AXIS_DPADY,
nec::AXIS_LEFTZ,
nec::AXIS_LSTICKX,
nec::AXIS_LSTICKY,
nec::AXIS_RSTICKX,
nec::AXIS_RSTICKY,
nec::AXIS_LT,
nec::AXIS_LT2,
nec::AXIS_RT,
nec::AXIS_RT2,
nec::AXIS_RIGHTZ,
];
let btns = [
nec::BTN_SOUTH,
nec::BTN_NORTH,
nec::BTN_WEST,
nec::BTN_WEST,
nec::BTN_C,
nec::BTN_Z,
nec::BTN_LT,
nec::BTN_LT2,
nec::BTN_RT,
nec::BTN_RT2,
nec::BTN_SELECT,
nec::BTN_START,
nec::BTN_MODE,
nec::BTN_LTHUMB,
nec::BTN_RTHUMB,
nec::BTN_DPAD_DOWN,
nec::BTN_DPAD_LEFT,
nec::BTN_DPAD_RIGHT,
nec::BTN_DPAD_UP,
];
for axis in &axes {
if !gamepad.axes().contains(axis) {
mappings.remove(axis);
}
}
for btn in &btns {
if !gamepad.buttons().contains(btn) {
mappings.remove(btn);
}
}
Mapping {
mappings,
name: String::new(),
default: true,
hats_mapped: 0,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn from_data(
data: &MappingData,
buttons: &[EvCode],
axes: &[EvCode],
name: &str,
uuid: Uuid,
) -> Result<(Self, String), MappingError> {
use crate::constants::*;
if !Self::is_name_valid(name) {
return Err(MappingError::InvalidName);
}
let mut mappings = FnvHashMap::default();
let mut sdl_mappings = format!("{},{},", uuid.as_simple(), name);
{
let mut add_button = |ident, ev_code, mapped_btn| {
Self::add_button(
ident,
ev_code,
mapped_btn,
buttons,
&mut sdl_mappings,
&mut mappings,
)
};
for (button, &ev_code) in &data.buttons {
match button as u16 {
BTN_SOUTH => add_button("a", ev_code, Button::South)?,
BTN_EAST => add_button("b", ev_code, Button::East)?,
BTN_WEST => add_button("x", ev_code, Button::West)?,
BTN_NORTH => add_button("y", ev_code, Button::North)?,
BTN_LT => add_button("leftshoulder", ev_code, Button::LeftTrigger)?,
BTN_RT => add_button("rightshoulder", ev_code, Button::RightTrigger)?,
BTN_LT2 => add_button("lefttrigger", ev_code, Button::LeftTrigger2)?,
BTN_RT2 => add_button("righttrigger", ev_code, Button::RightTrigger2)?,
BTN_SELECT => add_button("back", ev_code, Button::Select)?,
BTN_START => add_button("start", ev_code, Button::Start)?,
BTN_MODE => add_button("guide", ev_code, Button::Mode)?,
BTN_LTHUMB => add_button("leftstick", ev_code, Button::LeftThumb)?,
BTN_RTHUMB => add_button("rightstick", ev_code, Button::RightThumb)?,
BTN_DPAD_UP => add_button("dpup", ev_code, Button::DPadUp)?,
BTN_DPAD_DOWN => add_button("dpdown", ev_code, Button::DPadDown)?,
BTN_DPAD_LEFT => add_button("dpleft", ev_code, Button::DPadLeft)?,
BTN_DPAD_RIGHT => add_button("dpright", ev_code, Button::DPadRight)?,
BTN_C => add_button("c", ev_code, Button::C)?,
BTN_Z => add_button("z", ev_code, Button::Z)?,
BTN_UNKNOWN => return Err(MappingError::UnknownElement),
_ => unreachable!(),
}
}
}
{
let mut add_axis = |ident, ev_code, mapped_axis| {
Self::add_axis(
ident,
ev_code,
mapped_axis,
axes,
&mut sdl_mappings,
&mut mappings,
)
};
for (axis, &ev_code) in &data.axes {
match axis as u16 {
AXIS_LSTICKX => add_axis("leftx", ev_code, Axis::LeftStickX)?,
AXIS_LSTICKY => add_axis("lefty", ev_code, Axis::LeftStickY)?,
AXIS_RSTICKX => add_axis("rightx", ev_code, Axis::RightStickX)?,
AXIS_RSTICKY => add_axis("righty", ev_code, Axis::RightStickY)?,
AXIS_LEFTZ => add_axis("leftz", ev_code, Axis::LeftZ)?,
AXIS_RIGHTZ => add_axis("rightz", ev_code, Axis::RightZ)?,
AXIS_UNKNOWN => return Err(MappingError::UnknownElement),
_ => unreachable!(),
}
}
}
let mapping = Mapping {
mappings,
name: name.to_owned(),
default: false,
hats_mapped: 0,
};
Ok((mapping, sdl_mappings))
}
pub fn parse_sdl_mapping(
line: &str,
buttons: &[EvCode],
axes: &[EvCode],
) -> Result<Self, ParseSdlMappingError> {
let mut mapping = Mapping::new();
let mut parser = Parser::new(line);
let mut uuid: Option<Uuid> = None;
while let Some(token) = parser.next_token() {
if let Err(ref e) = token {
if e.kind() == &ParserErrorKind::EmptyValue {
continue;
}
}
let token = token?;
match token {
Token::Platform(platform) => {
if platform != SDL_PLATFORM_NAME {
warn!("Mappings for different platform {}", platform);
}
}
Token::Uuid(v) => uuid = Some(v),
Token::Name(name) => mapping.name = name.to_owned(),
Token::AxisMapping { from, to, .. } => {
let axis = axes.get(from as usize).cloned();
if let Some(axis) = axis {
mapping.mappings.insert(axis, to);
} else {
warn!(
"SDL-mapping {} {}: Unknown axis a{}",
uuid.unwrap(),
mapping.name,
from
)
}
}
Token::ButtonMapping { from, to, .. } => {
let btn = buttons.get(from as usize).cloned();
if let Some(btn) = btn {
mapping.mappings.insert(btn, to);
} else {
warn!(
"SDL-mapping {} {}: Unknown button b{}",
uuid.unwrap(),
mapping.name,
from
)
}
}
Token::HatMapping {
hat, direction, to, ..
} => {
if hat != 0 {
warn!(
"Hat mappings are only supported for dpads (requested to map hat \
{}.{} to {:?}",
hat, direction, to
);
} else {
// We don't have anything like "hat" in gilrs, so let's jus assume that
// user want to map dpad axes.
//
// We have to add mappings for axes AND buttons, because axis_dpad_to_button
// filter may transform event to button event.
let (from_axis, from_btn) = match direction {
1 => (nec::AXIS_DPADY, nec::BTN_DPAD_UP),
4 => (nec::AXIS_DPADY, nec::BTN_DPAD_DOWN),
2 => (nec::AXIS_DPADX, nec::BTN_DPAD_RIGHT),
8 => (nec::AXIS_DPADX, nec::BTN_DPAD_LEFT),
0 => continue, // FIXME: I have no idea what 0 means here
_ => return Err(ParseSdlMappingError::UnknownHatDirection),
};
if to.is_button() {
match to {
AxisOrBtn::Btn(Button::DPadLeft | Button::DPadRight) => {
mapping
.mappings
.insert(from_axis, AxisOrBtn::Axis(Axis::DPadX));
}
AxisOrBtn::Btn(Button::DPadUp | Button::DPadDown) => {
mapping
.mappings
.insert(from_axis, AxisOrBtn::Axis(Axis::DPadY));
}
_ => (),
}
mapping.mappings.insert(from_btn, to);
} else {
mapping.mappings.insert(from_axis, to);
}
mapping.hats_mapped |= direction as u8;
}
}
}
}
Ok(mapping)
}
fn add_button(
ident: &str,
ev_code: EvCode,
mapped_btn: Button,
buttons: &[EvCode],
sdl_mappings: &mut String,
mappings: &mut FnvHashMap<EvCode, AxisOrBtn>,
) -> Result<(), MappingError> {
let n_btn = buttons
.iter()
.position(|&x| x == ev_code)
.ok_or(MappingError::InvalidCode(ev::Code(ev_code)))?;
let _ = write!(sdl_mappings, "{}:b{},", ident, n_btn);
mappings.insert(ev_code, AxisOrBtn::Btn(mapped_btn));
Ok(())
}
fn add_axis(
ident: &str,
ev_code: EvCode,
mapped_axis: Axis,
axes: &[EvCode],
sdl_mappings: &mut String,
mappings: &mut FnvHashMap<EvCode, AxisOrBtn>,
) -> Result<(), MappingError> {
let n_axis = axes
.iter()
.position(|&x| x == ev_code)
.ok_or(MappingError::InvalidCode(ev::Code(ev_code)))?;
let _ = write!(sdl_mappings, "{}:a{},", ident, n_axis);
mappings.insert(ev_code, AxisOrBtn::Axis(mapped_axis));
Ok(())
}
fn is_name_valid(name: &str) -> bool {
!name.chars().any(|x| x == ',')
}
pub fn map(&self, code: &EvCode) -> Option<AxisOrBtn> {
self.mappings.get(code).cloned()
}
pub fn map_rev(&self, el: &AxisOrBtn) -> Option<EvCode> {
self.mappings.iter().find(|x| x.1 == el).map(|x| *x.0)
}
pub fn is_default(&self) -> bool {
self.default
}
/// Return bit field with mapped hats. Only for mappings created from SDL format this function
/// can return non-zero value.
pub fn hats_mapped(&self) -> u8 {
self.hats_mapped
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseSdlMappingError {
UnknownHatDirection,
ParseError(ParserError),
}
impl From<ParserError> for ParseSdlMappingError {
fn from(f: ParserError) -> Self {
ParseSdlMappingError::ParseError(f)
}
}
impl Error for ParseSdlMappingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let ParseSdlMappingError::ParseError(ref err) = self {
Some(err)
} else {
None
}
}
}
impl Display for ParseSdlMappingError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
match self {
ParseSdlMappingError::UnknownHatDirection => {
fmt.write_str("hat direction wasn't 1, 2, 4 or 8")
}
ParseSdlMappingError::ParseError(_) => fmt.write_str("parsing error"),
}
}
}
#[derive(Debug)]
pub struct MappingDb {
mappings: HashMap<Uuid, String>,
}
impl MappingDb {
pub fn new() -> Self {
MappingDb {
mappings: HashMap::new(),
}
}
pub fn add_included_mappings(&mut self) {
self.insert(include_str!(concat!(
env!("OUT_DIR"),
PATH_SEPARATOR!(),
"gamecontrollerdb.txt"
)));
}
pub fn add_env_mappings(&mut self) {
if let Ok(mapping) = env::var("SDL_GAMECONTROLLERCONFIG") {
self.insert(&mapping);
}
}
pub fn insert(&mut self, s: &str) {
for mapping in s.lines() {
let pat = "platform:";
if let Some(offset) = mapping.find(pat).map(|o| o + pat.len()) {
let s = &mapping[offset..];
let end = s.find(',').unwrap_or(s.len());
if &s[..end] != SDL_PLATFORM_NAME {
continue;
}
}
mapping
.split(',')
.next()
.and_then(|s| Uuid::parse_str(s).ok())
.and_then(|uuid| self.mappings.insert(uuid, mapping.to_owned()));
}
}
pub fn get(&self, uuid: Uuid) -> Option<&str> {
self.mappings.get(&uuid).map(String::as_ref)
}
pub fn len(&self) -> usize {
self.mappings.len()
}
}
/// Stores data used to map gamepad buttons and axes.
///
/// After you add all mappings, use
/// [`Gamepad::set_mapping(…)`](struct.Gamepad.html#method.set_mapping) to change mapping of
/// existing gamepad.
///
/// See `examples/mapping.rs` for more detailed example.
#[derive(Debug, Clone, Default)]
// Re-exported as Mapping
pub struct MappingData {
buttons: VecMap<EvCode>,
axes: VecMap<EvCode>,
}
impl MappingData {
/// Creates new `Mapping`.
pub fn new() -> Self {
MappingData {
buttons: VecMap::with_capacity(18),
axes: VecMap::with_capacity(11),
}
}
/// Returns `EvCode` associated with button index.
pub fn button(&self, idx: Button) -> Option<ev::Code> {
self.buttons.get(idx as usize).cloned().map(ev::Code)
}
/// Returns `EvCode` associated with axis index.
pub fn axis(&self, idx: Axis) -> Option<ev::Code> {
self.axes.get(idx as usize).cloned().map(ev::Code)
}
/// Inserts new button mapping.
pub fn insert_btn(&mut self, from: ev::Code, to: Button) -> Option<ev::Code> {
self.buttons.insert(to as usize, from.0).map(ev::Code)
}
/// Inserts new axis mapping.
pub fn insert_axis(&mut self, from: ev::Code, to: Axis) -> Option<ev::Code> {
self.axes.insert(to as usize, from.0).map(ev::Code)
}
/// Removes button and returns associated `NativEvCode`.
pub fn remove_button(&mut self, idx: Button) -> Option<ev::Code> {
self.buttons.remove(idx as usize).map(ev::Code)
}
/// Removes axis and returns associated `NativEvCode`.
pub fn remove_axis(&mut self, idx: Axis) -> Option<ev::Code> {
self.axes.remove(idx as usize).map(ev::Code)
}
}
/// The error type for functions related to gamepad mapping.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MappingError {
/// Gamepad does not have element referenced by `EvCode`.
InvalidCode(ev::Code),
/// Name contains comma (',').
InvalidName,
/// This function is not implemented for current platform.
NotImplemented,
/// Gamepad is not connected.
NotConnected,
/// Same gamepad element is referenced by axis and button.
DuplicatedEntry,
/// `Mapping` with `Button::Unknown` or `Axis::Unknown`.
UnknownElement,
/// `Mapping` have button or axis that are not present in SDL2.
NotSdl2Compatible,
}
impl Error for MappingError {}
impl Display for MappingError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let sbuf;
let s = match self {
MappingError::InvalidCode(code) => {
sbuf = format!("gamepad does not have element with {}", code);
sbuf.as_ref()
}
MappingError::InvalidName => "name can not contain comma",
MappingError::NotImplemented => {
"current platform does not implement setting custom mappings"
}
MappingError::NotConnected => "gamepad is not connected",
MappingError::DuplicatedEntry => {
"same gamepad element is referenced by axis and button"
}
MappingError::UnknownElement => "Button::Unknown and Axis::Unknown are not allowed",
MappingError::NotSdl2Compatible => "one of buttons or axes is not compatible with SDL2",
};
f.write_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ev::{Axis, Button};
use gilrs_core::native_ev_codes as nec;
use gilrs_core::EvCode;
use uuid::Uuid;
// Do not include platform, mapping from (with UUID modified)
// https://github.com/gabomdq/SDL_GameControllerDB/blob/master/gamecontrollerdb.txt
const TEST_STR: &str = "03000000260900008888000000010001,GameCube {WiseGroup USB \
box},a:b0,b:b2,y:b3,x:b1,start:b7,rightshoulder:b6,dpup:h0.1,dpleft:\
h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,\
lefttrigger:a4,righttrigger:a5,";
const BUTTONS: [EvCode; 15] = [
nec::BTN_SOUTH,
nec::BTN_EAST,
nec::BTN_C,
nec::BTN_NORTH,
nec::BTN_WEST,
nec::BTN_Z,
nec::BTN_LT,
nec::BTN_RT,
nec::BTN_LT2,
nec::BTN_RT2,
nec::BTN_SELECT,
nec::BTN_START,
nec::BTN_MODE,
nec::BTN_LTHUMB,
nec::BTN_RTHUMB,
];
const AXES: [EvCode; 12] = [
nec::AXIS_LSTICKX,
nec::AXIS_LSTICKY,
nec::AXIS_LEFTZ,
nec::AXIS_RSTICKX,
nec::AXIS_RSTICKY,
nec::AXIS_RIGHTZ,
nec::AXIS_DPADX,
nec::AXIS_DPADY,
nec::AXIS_RT,
nec::AXIS_LT,
nec::AXIS_RT2,
nec::AXIS_LT2,
];
#[test]
fn mapping() {
Mapping::parse_sdl_mapping(TEST_STR, &BUTTONS, &AXES).unwrap();
}
#[test]
fn from_data() {
let uuid = Uuid::nil();
let name = "Best Gamepad";
let buttons = BUTTONS.iter().cloned().map(ev::Code).collect::<Vec<_>>();
let axes = AXES.iter().cloned().map(ev::Code).collect::<Vec<_>>();
let mut data = MappingData::new();
data.insert_axis(axes[0], Axis::LeftStickX);
data.insert_axis(axes[1], Axis::LeftStickY);
data.insert_axis(axes[2], Axis::LeftZ);
data.insert_axis(axes[3], Axis::RightStickX);
data.insert_axis(axes[4], Axis::RightStickY);
data.insert_axis(axes[5], Axis::RightZ);
data.insert_btn(buttons[0], Button::South);
data.insert_btn(buttons[1], Button::East);
data.insert_btn(buttons[3], Button::North);
data.insert_btn(buttons[4], Button::West);
data.insert_btn(buttons[5], Button::Select);
data.insert_btn(buttons[6], Button::Start);
data.insert_btn(buttons[7], Button::DPadDown);
data.insert_btn(buttons[8], Button::DPadLeft);
data.insert_btn(buttons[9], Button::RightThumb);
let (mappings, sdl_mappings) =
Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid).unwrap();
let sdl_mappings = Mapping::parse_sdl_mapping(&sdl_mappings, &BUTTONS, &AXES).unwrap();
assert_eq!(mappings, sdl_mappings);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, "Inval,id name", uuid);
assert_eq!(Err(MappingError::InvalidName), incorrect_mappings);
data.insert_btn(ev::Code(nec::BTN_DPAD_RIGHT), Button::DPadRight);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid);
assert_eq!(
Err(MappingError::InvalidCode(ev::Code(nec::BTN_DPAD_RIGHT))),
incorrect_mappings
);
data.insert_btn(ev::Code(BUTTONS[3]), Button::Unknown);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid);
assert_eq!(Err(MappingError::UnknownElement), incorrect_mappings);
}
#[test]
fn with_mappings() {
let mappings = format!(
"\nShould be ignored\nThis also should,be ignored\n\n{}",
TEST_STR
);
let mut db = MappingDb::new();
db.add_included_mappings();
db.insert(&mappings);
assert_eq!(
Some(TEST_STR),
db.get(Uuid::parse_str("03000000260900008888000000010001").unwrap())
);
}
}

421
vendor/gilrs/src/mapping/parser.rs vendored Normal file
View File

@@ -0,0 +1,421 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::error::Error as StdError;
use std::fmt::{self, Display};
use uuid::Uuid;
use crate::ev::{Axis, AxisOrBtn, Button};
// Must be sorted!
static AXES_SDL: [&str; 31] = [
"a",
"b",
"back",
"c",
"dpdown",
"dpleft",
"dpright",
"dpup",
"guide",
"leftshoulder",
"leftstick",
"lefttrigger",
"leftx",
"lefty",
"leftz",
"misc1",
"paddle1",
"paddle2",
"paddle3",
"paddle4",
"rightshoulder",
"rightstick",
"righttrigger",
"rightx",
"righty",
"rightz",
"start",
"touchpad",
"x",
"y",
"z",
];
static AXES: [AxisOrBtn; 31] = [
AxisOrBtn::Btn(Button::South),
AxisOrBtn::Btn(Button::East),
AxisOrBtn::Btn(Button::Select),
AxisOrBtn::Btn(Button::C),
AxisOrBtn::Btn(Button::DPadDown),
AxisOrBtn::Btn(Button::DPadLeft),
AxisOrBtn::Btn(Button::DPadRight),
AxisOrBtn::Btn(Button::DPadUp),
AxisOrBtn::Btn(Button::Mode),
AxisOrBtn::Btn(Button::LeftTrigger),
AxisOrBtn::Btn(Button::LeftThumb),
AxisOrBtn::Btn(Button::LeftTrigger2),
AxisOrBtn::Axis(Axis::LeftStickX),
AxisOrBtn::Axis(Axis::LeftStickY),
AxisOrBtn::Axis(Axis::LeftZ),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::RightTrigger),
AxisOrBtn::Btn(Button::RightThumb),
AxisOrBtn::Btn(Button::RightTrigger2),
AxisOrBtn::Axis(Axis::RightStickX),
AxisOrBtn::Axis(Axis::RightStickY),
AxisOrBtn::Axis(Axis::RightZ),
AxisOrBtn::Btn(Button::Start),
AxisOrBtn::Btn(Button::Unknown),
AxisOrBtn::Btn(Button::West),
AxisOrBtn::Btn(Button::North),
AxisOrBtn::Btn(Button::Z),
];
pub struct Parser<'a> {
data: &'a str,
pos: usize,
state: State,
}
impl<'a> Parser<'a> {
pub fn new(mapping: &'a str) -> Self {
Parser {
data: mapping,
pos: 0,
state: State::Uuid,
}
}
pub fn next_token(&mut self) -> Option<Result<Token<'_>, Error>> {
if self.pos >= self.data.len() {
None
} else {
Some(match self.state {
State::Uuid => self.parse_uuid(),
State::Name => self.parse_name(),
State::KeyVal => self.parse_key_val(),
State::Invalid => Err(Error::new(ErrorKind::InvalidParserState, self.pos)),
})
}
}
fn parse_uuid(&mut self) -> Result<Token<'_>, Error> {
let next_comma = self.next_comma_or_end();
let uuid_field = &self.data[self.pos..next_comma];
let uuid = if uuid_field == "xinput" {
Ok(Token::Uuid(Uuid::nil()))
} else {
Uuid::parse_str(uuid_field)
.map(Token::Uuid)
.map_err(|_| Error::new(ErrorKind::InvalidGuid, self.pos))
};
if uuid.is_err() {
self.state = State::Invalid;
} else if next_comma == self.data.len() {
self.state = State::Invalid;
return Err(Error::new(ErrorKind::UnexpectedEnd, self.pos));
} else {
self.state = State::Name;
self.pos = next_comma + 1;
}
uuid
}
fn parse_name(&mut self) -> Result<Token<'_>, Error> {
let next_comma = self.next_comma_or_end();
let name = &self.data[self.pos..next_comma];
self.state = State::KeyVal;
self.pos = next_comma + 1;
Ok(Token::Name(name))
}
fn parse_key_val(&mut self) -> Result<Token<'_>, Error> {
let next_comma = self.next_comma_or_end();
let pair = &self.data[self.pos..next_comma];
let pos = self.pos;
self.pos = next_comma + 1;
let mut split = pair.split(':');
let key = split
.next()
.ok_or_else(|| Error::new(ErrorKind::InvalidKeyValPair, pos))?;
let value = split
.next()
.ok_or_else(|| Error::new(ErrorKind::InvalidKeyValPair, pos))?;
if split.next().is_some() {
return Err(Error::new(ErrorKind::InvalidKeyValPair, pos));
}
if value.is_empty() {
return Err(Error::new(ErrorKind::EmptyValue, pos));
}
if key == "platform" {
return Ok(Token::Platform(value));
}
let mut input = AxisRange::Full;
let mut output = AxisRange::Full;
let mut inverted = false;
let mut is_axis = false;
let key = match key.get(0..1) {
Some("+") => {
output = AxisRange::UpperHalf;
&key[1..]
}
Some("-") => {
output = AxisRange::LowerHalf;
&key[1..]
}
_ => key,
};
let from = match value.get(0..1) {
Some("+") if value.get(1..2) == Some("a") => {
is_axis = true;
input = AxisRange::UpperHalf;
if value.get((value.len() - 1)..) == Some("~") {
inverted = true;
&value[2..(value.len() - 1)]
} else {
&value[2..]
}
}
Some("-") if value.get(1..2) == Some("a") => {
is_axis = true;
input = AxisRange::LowerHalf;
if value.get((value.len() - 1)..) == Some("~") {
inverted = true;
&value[2..(value.len() - 1)]
} else {
&value[2..]
}
}
Some("a") => {
is_axis = true;
if value.get((value.len() - 1)..) == Some("~") {
inverted = true;
&value[1..(value.len() - 1)]
} else {
&value[1..]
}
}
Some("b") => &value[1..],
Some("h") => {
let dot_idx = value
.find('.')
.ok_or_else(|| Error::new(ErrorKind::InvalidValue, pos))?;
let hat = value[1..dot_idx]
.parse()
.map_err(|_| Error::new(ErrorKind::InvalidValue, pos + 1))?;
let direction = value
.get((dot_idx + 1)..)
.and_then(|s| s.parse().ok())
.ok_or_else(|| Error::new(ErrorKind::InvalidValue, pos + dot_idx + 1))?;
let idx = AXES_SDL
.binary_search(&key)
.map_err(|_| Error::new(ErrorKind::UnknownButton, pos))?;
return Ok(Token::HatMapping {
hat,
direction,
to: AXES[idx],
output,
});
}
_ => return Err(Error::new(ErrorKind::InvalidValue, pos)),
}
.parse::<u16>()
.map_err(|_| Error::new(ErrorKind::InvalidValue, pos))?;
if is_axis {
let idx = AXES_SDL
.binary_search(&key)
.map_err(|_| Error::new(ErrorKind::UnknownAxis, pos))?;
Ok(Token::AxisMapping {
from,
to: AXES[idx],
input,
output,
inverted,
})
} else {
let idx = AXES_SDL
.binary_search(&key)
.map_err(|_| Error::new(ErrorKind::UnknownButton, pos))?;
Ok(Token::ButtonMapping {
from,
to: AXES[idx],
output,
})
}
}
fn next_comma_or_end(&self) -> usize {
self.data[self.pos..]
.find(',')
.map(|x| x + self.pos)
.unwrap_or_else(|| self.data.len())
}
}
#[derive(Debug)]
pub enum Token<'a> {
Uuid(Uuid),
Platform(&'a str),
Name(&'a str),
#[allow(dead_code)]
AxisMapping {
from: u16,
to: AxisOrBtn,
input: AxisRange,
output: AxisRange,
inverted: bool,
},
ButtonMapping {
from: u16,
to: AxisOrBtn,
#[allow(dead_code)]
output: AxisRange,
},
// This is just SDL representation, we will convert this to axis mapping later
HatMapping {
hat: u16,
// ?
direction: u16,
to: AxisOrBtn,
#[allow(dead_code)]
output: AxisRange,
},
}
#[repr(u8)]
#[derive(Debug)]
pub enum AxisRange {
LowerHalf,
UpperHalf,
Full,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum State {
Uuid,
Name,
KeyVal,
Invalid,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
pub(crate) position: usize,
kind: ErrorKind,
}
impl Error {
pub fn new(kind: ErrorKind, position: usize) -> Self {
Error { position, kind }
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
InvalidGuid,
InvalidKeyValPair,
InvalidValue,
EmptyValue,
UnknownAxis,
UnknownButton,
InvalidParserState,
UnexpectedEnd,
}
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self.kind {
ErrorKind::InvalidGuid => "GUID is invalid",
ErrorKind::InvalidKeyValPair => "expected key value pair",
ErrorKind::InvalidValue => "value is not valid",
ErrorKind::EmptyValue => "value is empty",
ErrorKind::UnknownAxis => "invalid axis name",
ErrorKind::UnknownButton => "invalid button name",
ErrorKind::InvalidParserState => "attempt to parse after unrecoverable error",
ErrorKind::UnexpectedEnd => "mapping does not have all required fields",
};
f.write_fmt(format_args!("{} at {}", s, self.position))
}
}
#[cfg(test)]
mod tests {
use crate::mapping::parser::{ErrorKind, Parser};
use crate::utils::PATH_SEPARATOR;
#[test]
fn test_all_sdl_mappings_for_parse_errors() {
let included_mappings = include_str!(concat!(
env!("OUT_DIR"),
PATH_SEPARATOR!(),
"gamecontrollerdb.txt"
))
.lines();
let mut errors = 0;
let mut index = 0;
for line in included_mappings {
let mut parser = Parser::new(line);
while let Some(token) = parser.next_token() {
if let Err(ref e) = token {
if e.kind() != &ErrorKind::EmptyValue {
errors += 1;
println!("{:?}", e);
println!(
"{}: {} (...) {}\n",
index,
line.chars().take(50).collect::<String>(),
line.chars().skip(e.position).take(15).collect::<String>()
);
if e.kind() == &ErrorKind::InvalidParserState {
break;
}
}
}
index += 1;
}
}
assert_eq!(errors, 0);
}
}

49
vendor/gilrs/src/utils.rs vendored Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
pub use gilrs_core::utils::*;
/// Like `(a: f32 / b).ceil()` but for integers.
pub fn ceil_div(a: u32, b: u32) -> u32 {
if a == 0 {
0
} else {
1 + ((a - 1) / b)
}
}
pub fn clamp(x: f32, min: f32, max: f32) -> f32 {
x.clamp(min, max)
}
#[cfg(path_separator = "backslash")]
macro_rules! PATH_SEPARATOR {
() => {
r"\"
};
}
#[cfg(path_separator = "slash")]
macro_rules! PATH_SEPARATOR {
() => {
r"/"
};
}
pub(crate) use PATH_SEPARATOR;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn t_clamp() {
assert_eq!(clamp(-1.0, 0.0, 1.0), 0.0);
assert_eq!(clamp(0.5, 0.0, 1.0), 0.5);
assert_eq!(clamp(2.0, 0.0, 1.0), 1.0);
}
}