use gilrs::{Event as GamepadEvent, EventType as GamepadEventType, Gilrs};
#[cfg(not(target_arch = "wasm32"))]
use glutin::event::{ElementState, MouseScrollDelta, TouchPhase, WindowEvent};
use std::{
borrow::Cow,
cmp::Ordering,
collections::{HashMap, HashSet},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use typid::ID;
#[cfg(target_arch = "wasm32")]
use winit::event::{ElementState, MouseScrollDelta, TouchPhase, WindowEvent};
pub use gilrs::{Axis as GamepadAxis, Button as GamepadButton, GamepadId};
#[cfg(not(target_arch = "wasm32"))]
pub use glutin::event::{MouseButton, VirtualKeyCode};
#[cfg(target_arch = "wasm32")]
pub use winit::event::{MouseButton, VirtualKeyCode};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum InputConsume {
#[default]
None,
Hit,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VirtualAction {
KeyButton(VirtualKeyCode),
MouseButton(MouseButton),
Axis(u32),
GamepadButton(GamepadButton),
GamepadAxis(GamepadAxis),
Touch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VirtualAxis {
KeyButton(VirtualKeyCode),
MousePositionX,
MousePositionY,
MouseWheelX,
MouseWheelY,
MouseButton(MouseButton),
Axis(u32),
GamepadButton(GamepadButton),
GamepadAxis(GamepadAxis),
TouchX,
TouchY,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum InputAction {
#[default]
Idle,
Pressed,
Hold,
Released,
}
impl InputAction {
pub fn change(self, hold: bool) -> Self {
match (self, hold) {
(Self::Idle, true) | (Self::Released, true) => Self::Pressed,
(Self::Pressed, false) | (Self::Hold, false) => Self::Released,
_ => self,
}
}
pub fn update(self) -> Self {
match self {
Self::Pressed => Self::Hold,
Self::Released => Self::Idle,
_ => self,
}
}
pub fn is_idle(self) -> bool {
matches!(self, Self::Idle)
}
pub fn is_pressed(self) -> bool {
matches!(self, Self::Pressed)
}
pub fn is_hold(self) -> bool {
matches!(self, Self::Hold)
}
pub fn is_released(self) -> bool {
matches!(self, Self::Released)
}
pub fn is_up(self) -> bool {
matches!(self, Self::Idle | Self::Released)
}
pub fn is_down(self) -> bool {
matches!(self, Self::Pressed | Self::Hold)
}
pub fn is_changing(self) -> bool {
matches!(self, Self::Pressed | Self::Released)
}
pub fn is_continuing(self) -> bool {
matches!(self, Self::Idle | Self::Hold)
}
pub fn to_scalar(self, falsy: f32, truthy: f32) -> f32 {
if self.is_down() { truthy } else { falsy }
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct InputAxis(pub f32);
impl InputAxis {
pub fn threshold(self, value: f32) -> bool {
self.0 >= value
}
}
#[derive(Debug, Default, Clone)]
pub struct InputRef<T: Default>(Arc<RwLock<T>>);
impl<T: Default> InputRef<T> {
pub fn new(data: T) -> Self {
Self(Arc::new(RwLock::new(data)))
}
pub fn read(&'_ self) -> Option<RwLockReadGuard<'_, T>> {
self.0.read().ok()
}
pub fn write(&'_ self) -> Option<RwLockWriteGuard<'_, T>> {
self.0.write().ok()
}
pub fn get(&self) -> T
where
T: Clone,
{
self.read().map(|value| value.clone()).unwrap_or_default()
}
pub fn set(&self, value: T) {
if let Some(mut data) = self.write() {
*data = value;
}
}
}
pub type InputActionRef = InputRef<InputAction>;
pub type InputAxisRef = InputRef<InputAxis>;
pub type InputCharactersRef = InputRef<InputCharacters>;
pub type InputMappingRef = InputRef<InputMapping>;
#[derive(Debug, Default, Clone)]
pub enum InputActionOrAxisRef {
#[default]
None,
Action(InputActionRef),
Axis(InputAxisRef),
}
impl InputActionOrAxisRef {
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
pub fn get_scalar(&self, falsy: f32, truthy: f32) -> f32 {
match self {
Self::None => falsy,
Self::Action(action) => action.get().to_scalar(falsy, truthy),
Self::Axis(axis) => axis.get().0,
}
}
pub fn threshold(&self, value: f32) -> bool {
match self {
Self::None => false,
Self::Action(action) => action.get().is_down(),
Self::Axis(axis) => axis.get().threshold(value),
}
}
}
impl From<InputActionRef> for InputActionOrAxisRef {
fn from(value: InputActionRef) -> Self {
Self::Action(value)
}
}
impl From<InputAxisRef> for InputActionOrAxisRef {
fn from(value: InputAxisRef) -> Self {
Self::Axis(value)
}
}
pub struct InputCombinator<T> {
mapper: Box<dyn Fn() -> T + Send + Sync>,
}
impl<T: Default> Default for InputCombinator<T> {
fn default() -> Self {
Self::new(|| T::default())
}
}
impl<T> InputCombinator<T> {
pub fn new(mapper: impl Fn() -> T + Send + Sync + 'static) -> Self {
Self {
mapper: Box::new(mapper),
}
}
pub fn get(&self) -> T {
(self.mapper)()
}
}
#[derive(Default)]
pub struct CardinalInputCombinator(InputCombinator<[f32; 2]>);
impl CardinalInputCombinator {
pub fn new(
left: impl Into<InputActionOrAxisRef>,
right: impl Into<InputActionOrAxisRef>,
up: impl Into<InputActionOrAxisRef>,
down: impl Into<InputActionOrAxisRef>,
) -> Self {
let left = left.into();
let right = right.into();
let up = up.into();
let down = down.into();
Self(InputCombinator::new(move || {
let left = left.get_scalar(0.0, -1.0);
let right = right.get_scalar(0.0, 1.0);
let up = up.get_scalar(0.0, -1.0);
let down = down.get_scalar(0.0, 1.0);
[left + right, up + down]
}))
}
pub fn get(&self) -> [f32; 2] {
self.0.get()
}
}
#[derive(Default)]
pub struct DualInputCombinator(InputCombinator<f32>);
impl DualInputCombinator {
pub fn new(
negative: impl Into<InputActionOrAxisRef>,
positive: impl Into<InputActionOrAxisRef>,
) -> Self {
let negative = negative.into();
let positive = positive.into();
Self(InputCombinator::new(move || {
let negative = negative.get_scalar(0.0, -1.0);
let positive = positive.get_scalar(0.0, 1.0);
negative + positive
}))
}
pub fn get(&self) -> f32 {
self.0.get()
}
}
pub struct ArrayInputCombinator<const N: usize>(InputCombinator<[f32; N]>);
impl<const N: usize> Default for ArrayInputCombinator<N> {
fn default() -> Self {
Self(InputCombinator::new(|| [0.0; N]))
}
}
impl<const N: usize> ArrayInputCombinator<N> {
pub fn new(inputs: [impl Into<InputActionOrAxisRef>; N]) -> Self {
let items: [InputActionOrAxisRef; N] = inputs.map(|input| input.into());
Self(InputCombinator::new(move || {
std::array::from_fn(|index| items[index].get_scalar(0.0, 1.0))
}))
}
pub fn get(&self) -> [f32; N] {
self.0.get()
}
}
#[derive(Debug, Default, Clone)]
pub struct InputCharacters {
characters: String,
}
impl InputCharacters {
pub fn read(&self) -> &str {
&self.characters
}
pub fn write(&mut self) -> &mut String {
&mut self.characters
}
pub fn take(&mut self) -> String {
std::mem::take(&mut self.characters)
}
}
#[derive(Default, Clone)]
pub struct InputMapping {
pub actions: HashMap<VirtualAction, InputActionRef>,
pub axes: HashMap<VirtualAxis, InputAxisRef>,
pub consume: InputConsume,
pub layer: isize,
pub name: Cow<'static, str>,
pub gamepad: Option<GamepadId>,
pub validator: Option<Arc<dyn Fn() -> bool + Send + Sync>>,
}
impl InputMapping {
pub fn action(mut self, id: VirtualAction, action: InputActionRef) -> Self {
self.actions.insert(id, action);
self
}
pub fn axis(mut self, id: VirtualAxis, axis: InputAxisRef) -> Self {
self.axes.insert(id, axis);
self
}
pub fn consume(mut self, consume: InputConsume) -> Self {
self.consume = consume;
self
}
pub fn layer(mut self, value: isize) -> Self {
self.layer = value;
self
}
pub fn name(mut self, value: impl Into<Cow<'static, str>>) -> Self {
self.name = value.into();
self
}
pub fn gamepad(mut self, gamepad: GamepadId) -> Self {
self.gamepad = Some(gamepad);
self
}
pub fn validator(mut self, validator: impl Fn() -> bool + Send + Sync + 'static) -> Self {
self.validator = Some(Arc::new(validator));
self
}
}
impl From<InputMapping> for InputMappingRef {
fn from(value: InputMapping) -> Self {
Self::new(value)
}
}
impl std::fmt::Debug for InputMapping {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InputMapping")
.field("actions", &self.actions.keys().collect::<Vec<_>>())
.field("axes", &self.axes.keys().collect::<Vec<_>>())
.field("consume", &self.consume)
.field("layer", &self.layer)
.field("name", &self.name)
.field("gamepad", &self.gamepad)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct InputContext {
pub mouse_wheel_line_scale: f32,
pub touch_area_margin: [f64; 4],
mappings_stack: Vec<(ID<InputMapping>, InputMappingRef)>,
characters: InputCharactersRef,
gamepads: Option<Gilrs>,
active_touch: Option<u64>,
window_size: [f64; 2],
}
impl Default for InputContext {
fn default() -> Self {
Self {
mouse_wheel_line_scale: Self::default_mouse_wheel_line_scale(),
touch_area_margin: Self::default_touch_area_margin(),
mappings_stack: Default::default(),
characters: Default::default(),
gamepads: None,
active_touch: None,
window_size: Default::default(),
}
}
}
impl Clone for InputContext {
fn clone(&self) -> Self {
Self {
mouse_wheel_line_scale: self.mouse_wheel_line_scale,
touch_area_margin: self.touch_area_margin,
mappings_stack: self.mappings_stack.clone(),
characters: self.characters.clone(),
gamepads: None,
active_touch: self.active_touch,
window_size: self.window_size,
}
}
}
impl InputContext {
fn default_mouse_wheel_line_scale() -> f32 {
10.0
}
fn default_touch_area_margin() -> [f64; 4] {
[0.0, 0.0, 0.0, 0.0]
}
pub fn with_gamepads(mut self) -> Self {
self.gamepads = Gilrs::new().ok();
self
}
pub fn with_gamepads_custom(mut self, gamepads: Gilrs) -> Self {
self.gamepads = Some(gamepads);
self
}
pub fn gamepads(&self) -> Option<&Gilrs> {
self.gamepads.as_ref()
}
pub fn gamepads_mut(&mut self) -> Option<&mut Gilrs> {
self.gamepads.as_mut()
}
pub fn push_mapping(&mut self, mapping: impl Into<InputMappingRef>) -> ID<InputMapping> {
let mapping = mapping.into();
let id = ID::default();
let layer = mapping.read().unwrap().layer;
let index = self
.mappings_stack
.binary_search_by(|(_, mapping)| {
mapping
.read()
.unwrap()
.layer
.cmp(&layer)
.then(Ordering::Less)
})
.unwrap_or_else(|index| index);
self.mappings_stack.insert(index, (id, mapping));
id
}
pub fn pop_mapping(&mut self) -> Option<InputMappingRef> {
self.mappings_stack.pop().map(|(_, mapping)| mapping)
}
pub fn top_mapping(&self) -> Option<&InputMappingRef> {
self.mappings_stack.last().map(|(_, mapping)| mapping)
}
pub fn remove_mapping(&mut self, id: ID<InputMapping>) -> Option<InputMappingRef> {
self.mappings_stack
.iter()
.position(|(mid, _)| mid == &id)
.map(|index| self.mappings_stack.remove(index).1)
}
pub fn mapping(&'_ self, id: ID<InputMapping>) -> Option<RwLockReadGuard<'_, InputMapping>> {
self.mappings_stack
.iter()
.find(|(mid, _)| mid == &id)
.and_then(|(_, mapping)| mapping.read())
}
pub fn stack(&self) -> impl Iterator<Item = &InputMappingRef> {
self.mappings_stack.iter().map(|(_, mapping)| mapping)
}
pub fn characters(&self) -> InputCharactersRef {
self.characters.clone()
}
pub fn maintain(&mut self) {
for (_, mapping) in &mut self.mappings_stack {
if let Some(mut mapping) = mapping.write() {
for action in mapping.actions.values_mut() {
if let Some(mut action) = action.write() {
*action = action.update();
}
}
for (id, axis) in &mut mapping.axes {
if let VirtualAxis::MouseWheelX | VirtualAxis::MouseWheelY = id
&& let Some(mut axis) = axis.write()
{
axis.0 = 0.0;
}
}
}
}
let validity = self
.mappings_stack
.iter()
.filter(|(_, mapping)| {
mapping.read().is_some_and(|mapping| {
mapping
.validator
.as_ref()
.is_none_or(|validator| validator())
})
})
.map(|(id, _)| *id)
.collect::<HashSet<_>>();
if let Some(gamepads) = self.gamepads.as_mut() {
while let Some(GamepadEvent { id, event, .. }) = gamepads.next_event() {
match event {
GamepadEventType::ButtonPressed(info, ..) => {
for (mapping_id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(mapping_id) {
continue;
}
if let Some(mapping) = mapping.read() {
if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
continue;
}
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::GamepadButton(button) = id
&& *button == info
&& let Some(mut data) = data.write()
{
*data = data.change(true);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::GamepadButton(button) = id
&& *button == info
&& let Some(mut data) = data.write()
{
data.0 = 1.0;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
GamepadEventType::ButtonReleased(info, ..) => {
for (mapping_id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(mapping_id) {
continue;
}
if let Some(mapping) = mapping.read() {
if !mapping.gamepad.map(|gamepad| gamepad == id).unwrap_or(true) {
continue;
}
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::GamepadButton(button) = id
&& *button == info
&& let Some(mut data) = data.write()
{
*data = data.change(false);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::GamepadButton(button) = id
&& *button == info
&& let Some(mut data) = data.write()
{
data.0 = 0.0;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
GamepadEventType::AxisChanged(info, value, ..) => {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::GamepadAxis(axis) = id
&& *axis == info
&& let Some(mut data) = data.write()
{
*data = data.change(value > 0.5);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::GamepadAxis(axis) = id
&& *axis == info
&& let Some(mut data) = data.write()
{
data.0 = value;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
_ => {}
}
}
gamepads.inc();
}
}
pub fn on_event(&mut self, event: &WindowEvent) {
let validity = self
.mappings_stack
.iter()
.filter(|(_, mapping)| {
mapping.read().is_some_and(|mapping| {
mapping
.validator
.as_ref()
.is_none_or(|validator| validator())
})
})
.map(|(id, _)| *id)
.collect::<HashSet<_>>();
match event {
WindowEvent::Resized(size) => {
self.window_size = [size.width as _, size.height as _];
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
self.window_size = [new_inner_size.width as _, new_inner_size.height as _];
}
WindowEvent::ReceivedCharacter(character) => {
if let Some(mut characters) = self.characters.write() {
characters.characters.push(*character);
}
}
WindowEvent::KeyboardInput { input, .. } => {
if let Some(key) = input.virtual_keycode {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::KeyButton(button) = id
&& *button == key
&& let Some(mut data) = data.write()
{
*data = data.change(input.state == ElementState::Pressed);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::KeyButton(button) = id
&& *button == key
&& let Some(mut data) = data.write()
{
data.0 = if input.state == ElementState::Pressed {
1.0
} else {
0.0
};
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
}
WindowEvent::CursorMoved { position, .. } => {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.axes {
match id {
VirtualAxis::MousePositionX => {
if let Some(mut data) = data.write() {
data.0 = position.x as _;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
VirtualAxis::MousePositionY => {
if let Some(mut data) = data.write() {
data.0 = position.y as _;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
_ => {}
}
}
if consume {
break;
}
}
}
}
WindowEvent::MouseWheel { delta, .. } => {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.axes {
match id {
VirtualAxis::MouseWheelX => {
if let Some(mut data) = data.write() {
data.0 = match delta {
MouseScrollDelta::LineDelta(x, _) => *x,
MouseScrollDelta::PixelDelta(pos) => pos.x as _,
};
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
VirtualAxis::MouseWheelY => {
if let Some(mut data) = data.write() {
data.0 = match delta {
MouseScrollDelta::LineDelta(_, y) => *y,
MouseScrollDelta::PixelDelta(pos) => pos.y as _,
};
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
_ => {}
}
}
if consume {
break;
}
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::MouseButton(btn) = id
&& button == btn
&& let Some(mut data) = data.write()
{
*data = data.change(*state == ElementState::Pressed);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::MouseButton(btn) = id
&& button == btn
&& let Some(mut data) = data.write()
{
data.0 = if *state == ElementState::Pressed {
1.0
} else {
0.0
};
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
WindowEvent::AxisMotion { axis, value, .. } => {
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::Axis(index) = id
&& axis == index
&& let Some(mut data) = data.write()
{
*data = data.change(value.abs() > 0.5);
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
if let VirtualAxis::Axis(index) = id
&& axis == index
&& let Some(mut data) = data.write()
{
data.0 = *value as _;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
if consume {
break;
}
}
}
}
WindowEvent::Touch(touch) => {
if matches!(touch.phase, TouchPhase::Started | TouchPhase::Moved)
&& self.active_touch.is_none()
&& touch.location.x >= self.touch_area_margin[0]
&& touch.location.y >= self.touch_area_margin[1]
&& touch.location.x < self.window_size[0] - self.touch_area_margin[2]
&& touch.location.y < self.window_size[1] - self.touch_area_margin[3]
{
self.active_touch = Some(touch.id);
}
if let Some(active_touch) = self.active_touch
&& touch.id == active_touch
{
for (id, mapping) in self.mappings_stack.iter().rev() {
if !validity.contains(id) {
continue;
}
if let Some(mapping) = mapping.read() {
let mut consume = mapping.consume == InputConsume::All;
for (id, data) in &mapping.actions {
if let VirtualAction::Touch = id
&& let Some(mut data) = data.write()
{
*data = data.change(matches!(
touch.phase,
TouchPhase::Started | TouchPhase::Moved
));
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
for (id, data) in &mapping.axes {
match id {
VirtualAxis::TouchX => {
if let Some(mut data) = data.write() {
data.0 = touch.location.x as _;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
VirtualAxis::TouchY => {
if let Some(mut data) = data.write() {
data.0 = touch.location.y as _;
if mapping.consume == InputConsume::Hit {
consume = true;
}
}
}
_ => {}
}
}
if consume {
break;
}
}
}
if matches!(touch.phase, TouchPhase::Ended | TouchPhase::Cancelled) {
self.active_touch = None;
}
}
}
_ => {}
}
}
}
#[derive(Default)]
pub enum InputActionDetector {
#[default]
Listening,
Detected(VirtualAction),
}
impl InputActionDetector {
pub fn reset(&mut self) {
*self = Self::Listening;
}
pub fn try_consume(&mut self) -> Option<VirtualAction> {
if let Self::Detected(action) = self {
let action = *action;
*self = Self::Listening;
Some(action)
} else {
None
}
}
pub fn window_detect(&mut self, _context: &mut InputContext, event: &WindowEvent) {
if let Self::Listening = self {
match event {
WindowEvent::KeyboardInput { input, .. } => {
if let Some(action) = input.virtual_keycode.map(VirtualAction::KeyButton) {
*self = Self::Detected(action);
}
}
WindowEvent::MouseInput { button, .. } => {
*self = Self::Detected(VirtualAction::MouseButton(*button));
}
WindowEvent::AxisMotion { axis, .. } => {
*self = Self::Detected(VirtualAction::Axis(*axis));
}
WindowEvent::Touch(_) => *self = Self::Detected(VirtualAction::Touch),
_ => {}
}
}
}
pub fn gamepad_detect(&mut self, context: &mut InputContext, gamepad_id: Option<GamepadId>) {
if let Self::Listening = self
&& let Some(gamepads) = context.gamepads.as_mut()
{
while let Some(event) = gamepads.next_event() {
if let Some(id) = gamepad_id
&& id != event.id
{
continue;
}
match event.event {
GamepadEventType::ButtonPressed(info, ..) => {
*self = Self::Detected(VirtualAction::GamepadButton(info));
return;
}
GamepadEventType::AxisChanged(info, value, ..) => {
if value.abs() > 0.5 {
*self = Self::Detected(VirtualAction::GamepadAxis(info));
return;
}
}
_ => {}
}
}
}
}
}
#[derive(Default)]
pub enum InputAxisDetector {
#[default]
Listening,
TrackingMouse {
x: f64,
y: f64,
},
TrackingScroll {
x: f64,
y: f64,
},
TrackingTouch {
x: f64,
y: f64,
},
Detected(VirtualAxis),
}
impl InputAxisDetector {
pub fn reset(&mut self) {
*self = Self::Listening;
}
pub fn try_consume(&mut self) -> Option<VirtualAxis> {
if let Self::Detected(axis) = self {
let axis = *axis;
*self = Self::Listening;
Some(axis)
} else {
None
}
}
pub fn window_detect(&mut self, _context: &mut InputContext, event: &WindowEvent) {
match self {
Self::Listening => match event {
WindowEvent::KeyboardInput { input, .. } => {
if let Some(key) = input.virtual_keycode {
*self = Self::Detected(VirtualAxis::KeyButton(key));
}
}
WindowEvent::MouseInput { button, .. } => {
*self = Self::Detected(VirtualAxis::MouseButton(*button));
}
WindowEvent::CursorMoved { position, .. } => {
*self = Self::TrackingMouse {
x: position.x,
y: position.y,
};
}
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::LineDelta(x, y) => {
if x.abs() > y.abs() {
*self = Self::Detected(VirtualAxis::MouseWheelX);
} else if y.abs() > 0.0 {
*self = Self::Detected(VirtualAxis::MouseWheelY);
}
}
MouseScrollDelta::PixelDelta(pos) => {
*self = Self::TrackingScroll {
x: pos.x as _,
y: pos.y as _,
};
}
},
WindowEvent::AxisMotion { axis, .. } => {
*self = Self::Detected(VirtualAxis::Axis(*axis));
}
WindowEvent::Touch(touch) => {
*self = Self::TrackingTouch {
x: touch.location.x,
y: touch.location.y,
};
}
_ => {}
},
Self::TrackingMouse { x, y } => {
if let WindowEvent::CursorMoved { position, .. } = event {
let delta_x = position.x - *x;
let delta_y = position.y - *y;
if delta_x.abs() > delta_y.abs() {
*self = Self::Detected(VirtualAxis::MousePositionX);
} else if delta_y.abs() > 0.0 {
*self = Self::Detected(VirtualAxis::MousePositionY);
}
}
}
Self::TrackingScroll { x, y } => {
if let WindowEvent::MouseWheel { delta, .. } = event
&& let MouseScrollDelta::PixelDelta(pos) = delta
{
let delta_x = pos.x - *x;
let delta_y = pos.y - *y;
if delta_x.abs() > delta_y.abs() {
*self = Self::Detected(VirtualAxis::MouseWheelX);
} else if delta_y.abs() > 0.0 {
*self = Self::Detected(VirtualAxis::MouseWheelY);
}
}
}
Self::TrackingTouch { x, y } => {
if let WindowEvent::Touch(touch) = event {
let delta_x = touch.location.x - *x;
let delta_y = touch.location.y - *y;
if delta_x.abs() > delta_y.abs() {
*self = Self::Detected(VirtualAxis::TouchX);
} else if delta_y.abs() > 0.0 {
*self = Self::Detected(VirtualAxis::TouchY);
}
}
}
_ => {}
}
}
pub fn gamepad_detect(&mut self, context: &mut InputContext, gamepad_id: Option<GamepadId>) {
if let Some(gamepads) = context.gamepads.as_mut() {
while let Some(event) = gamepads.next_event() {
if let Some(id) = gamepad_id
&& id != event.id
{
continue;
}
match event.event {
GamepadEventType::ButtonPressed(info, ..) => {
*self = Self::Detected(VirtualAxis::GamepadButton(info));
}
GamepadEventType::AxisChanged(info, value, ..) => {
if value.abs() > 0.5 {
*self = Self::Detected(VirtualAxis::GamepadAxis(info));
}
}
_ => {}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack() {
let mut context = InputContext::default();
context.push_mapping(InputMapping::default().name("a").layer(0));
context.push_mapping(InputMapping::default().name("b").layer(0));
context.push_mapping(InputMapping::default().name("c").layer(0));
context.push_mapping(InputMapping::default().name("d").layer(-1));
context.push_mapping(InputMapping::default().name("e").layer(1));
context.push_mapping(InputMapping::default().name("f").layer(-1));
context.push_mapping(InputMapping::default().name("g").layer(1));
context.push_mapping(InputMapping::default().name("h").layer(-2));
context.push_mapping(InputMapping::default().name("i").layer(-2));
context.push_mapping(InputMapping::default().name("j").layer(2));
context.push_mapping(InputMapping::default().name("k").layer(2));
let provided = context
.stack()
.map(|mapping| {
let mapping = mapping.read().unwrap();
(mapping.name.as_ref().to_owned(), mapping.layer)
})
.collect::<Vec<_>>();
assert_eq!(
provided,
vec![
("h".to_owned(), -2),
("i".to_owned(), -2),
("d".to_owned(), -1),
("f".to_owned(), -1),
("a".to_owned(), 0),
("b".to_owned(), 0),
("c".to_owned(), 0),
("e".to_owned(), 1),
("g".to_owned(), 1),
("j".to_owned(), 2),
("k".to_owned(), 2),
]
);
}
}