use core::fmt::Debug;
use nalgebra::Vector2;
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use micromath::F32Ext;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum GamepadButton {
DpadUp = 0,
DpadDown = 1,
DpadLeft = 2,
DpadRight = 3,
ActionA = 4,
ActionB = 5,
ActionX = 6,
ActionY = 7,
Start = 8,
Select = 9,
LeftBumper = 10,
RightBumper = 11,
LeftTrigger = 12,
RightTrigger = 13,
LeftThumb = 14,
RightThumb = 15,
}
impl GamepadButton {
#[inline]
pub const fn mask(self) -> u16 {
1 << (self as u8)
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct ButtonInput {
current: u16,
previous: u16,
}
impl ButtonInput {
pub const fn new() -> Self {
Self {
current: 0,
previous: 0,
}
}
#[inline]
pub fn press(&mut self, button: GamepadButton) {
self.current |= button.mask();
}
#[inline]
pub fn release(&mut self, button: GamepadButton) {
self.current &= !button.mask();
}
#[inline]
pub fn set(&mut self, button: GamepadButton, pressed: bool) {
if pressed {
self.press(button);
} else {
self.release(button);
}
}
#[inline]
pub fn pressed(&self, button: GamepadButton) -> bool {
(self.current & button.mask()) != 0
}
#[inline]
pub fn just_pressed(&self, button: GamepadButton) -> bool {
let mask = button.mask();
(self.current & mask) != 0 && (self.previous & mask) == 0
}
#[inline]
pub fn just_released(&self, button: GamepadButton) -> bool {
let mask = button.mask();
(self.current & mask) == 0 && (self.previous & mask) != 0
}
#[inline]
pub const fn raw_current(&self) -> u16 {
self.current
}
#[inline]
pub fn update(&mut self) {
self.previous = self.current;
}
#[inline]
pub fn clear(&mut self) {
self.current = 0;
self.previous = 0;
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct VirtualAxis2D {
pub x: f32,
pub y: f32,
}
impl VirtualAxis2D {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn from_dpad(up: bool, down: bool, left: bool, right: bool) -> Self {
let x = match (left, right) {
(true, false) => -1.0,
(false, true) => 1.0,
_ => 0.0,
};
let y = match (down, up) {
(true, false) => -1.0,
(false, true) => 1.0,
_ => 0.0,
};
Self::new(x, y)
}
pub fn from_adc(raw_x: u16, raw_y: u16, center_x: u16, center_y: u16, max_val: u16) -> Self {
let half_range = (max_val / 2).max(1) as f32;
let x = ((raw_x as f32 - center_x as f32) / half_range).clamp(-1.0, 1.0);
let y = ((raw_y as f32 - center_y as f32) / half_range).clamp(-1.0, 1.0);
Self::new(x, y)
}
pub fn with_radial_deadzone(&self, threshold: f32) -> Self {
let threshold = threshold.clamp(0.0, 0.99);
let len_sq = self.x * self.x + self.y * self.y;
if len_sq <= threshold * threshold {
return Self::new(0.0, 0.0);
}
let len = len_sq.sqrt();
if len <= 1e-4 {
return Self::new(0.0, 0.0);
}
let normalized_factor = (len - threshold) / (1.0 - threshold);
let scale = (normalized_factor / len).min(1.0);
Self::new(self.x * scale, self.y * scale)
}
pub fn to_vector(&self) -> Vector2<f32> {
Vector2::new(self.x, self.y)
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct VirtualGamepad {
pub buttons: ButtonInput,
pub left_stick: VirtualAxis2D,
pub right_stick: VirtualAxis2D,
}
impl VirtualGamepad {
pub const fn new() -> Self {
Self {
buttons: ButtonInput::new(),
left_stick: VirtualAxis2D::new(0.0, 0.0),
right_stick: VirtualAxis2D::new(0.0, 0.0),
}
}
#[inline]
pub fn update(&mut self) {
self.buttons.update();
}
pub fn to_input_state(&self) -> InputState {
InputState {
forward: self.left_stick.y,
strafe: self.left_stick.x,
look_yaw: self.right_stick.x,
look_pitch: self.right_stick.y,
jump: self.buttons.pressed(GamepadButton::ActionA),
sprint: self.buttons.pressed(GamepadButton::RightBumper),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct DebounceFilter<const PINS: usize> {
states: [bool; PINS],
counters: [u8; PINS],
threshold: u8,
}
impl<const PINS: usize> DebounceFilter<PINS> {
pub const fn new(threshold: u8) -> Self {
Self {
states: [false; PINS],
counters: [0; PINS],
threshold,
}
}
pub fn update(&mut self, pin_idx: usize, raw_reading: bool) -> bool {
if pin_idx >= PINS {
return false;
}
if raw_reading != self.states[pin_idx] {
self.counters[pin_idx] = self.counters[pin_idx].saturating_add(1);
if self.counters[pin_idx] >= self.threshold {
self.states[pin_idx] = raw_reading;
self.counters[pin_idx] = 0;
}
} else {
self.counters[pin_idx] = 0;
}
self.states[pin_idx]
}
pub fn state(&self, pin_idx: usize) -> bool {
if pin_idx < PINS {
self.states[pin_idx]
} else {
false
}
}
}
#[derive(Clone, Copy, Default, Debug)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub struct InputState {
pub forward: f32,
pub strafe: f32,
pub look_yaw: f32,
pub look_pitch: f32,
pub jump: bool,
pub sprint: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_input_state_is_neutral() {
let input = InputState::default();
assert_eq!(input.forward, 0.0);
assert_eq!(input.strafe, 0.0);
assert_eq!(input.look_yaw, 0.0);
assert_eq!(input.look_pitch, 0.0);
assert!(!input.jump);
assert!(!input.sprint);
}
#[test]
fn input_state_can_store_full_analog_range() {
let input = InputState {
forward: 1.0,
strafe: -1.0,
look_yaw: 0.75,
look_pitch: -0.5,
jump: true,
sprint: true,
};
assert_eq!(input.forward, 1.0);
assert_eq!(input.strafe, -1.0);
assert_eq!(input.look_yaw, 0.75);
assert_eq!(input.look_pitch, -0.5);
assert!(input.jump);
assert!(input.sprint);
}
#[test]
fn test_button_input_edges() {
let mut buttons = ButtonInput::new();
assert!(!buttons.pressed(GamepadButton::ActionA));
assert!(!buttons.just_pressed(GamepadButton::ActionA));
buttons.press(GamepadButton::ActionA);
assert!(buttons.pressed(GamepadButton::ActionA));
assert!(buttons.just_pressed(GamepadButton::ActionA));
assert!(!buttons.just_released(GamepadButton::ActionA));
buttons.update();
assert!(buttons.pressed(GamepadButton::ActionA));
assert!(!buttons.just_pressed(GamepadButton::ActionA));
assert!(!buttons.just_released(GamepadButton::ActionA));
buttons.release(GamepadButton::ActionA);
assert!(!buttons.pressed(GamepadButton::ActionA));
assert!(!buttons.just_pressed(GamepadButton::ActionA));
assert!(buttons.just_released(GamepadButton::ActionA));
buttons.update();
assert!(!buttons.pressed(GamepadButton::ActionA));
assert!(!buttons.just_released(GamepadButton::ActionA));
}
#[test]
fn test_virtual_axis_radial_deadzone() {
let axis = VirtualAxis2D::new(0.05, 0.05);
let filtered = axis.with_radial_deadzone(0.15);
assert_eq!(filtered.x, 0.0);
assert_eq!(filtered.y, 0.0);
let axis_active = VirtualAxis2D::new(0.8, 0.0);
let filtered_active = axis_active.with_radial_deadzone(0.2);
assert!(filtered_active.x > 0.7);
assert_eq!(filtered_active.y, 0.0);
}
#[test]
fn test_debounce_filter() {
let mut debounce = DebounceFilter::<4>::new(3);
assert!(!debounce.state(0));
assert!(!debounce.update(0, true));
assert!(!debounce.update(0, true));
assert!(!debounce.update(0, false)); assert!(!debounce.state(0));
assert!(!debounce.update(0, true));
assert!(!debounce.update(0, true));
assert!(debounce.update(0, true)); assert!(debounce.state(0));
assert!(!debounce.update(9, true));
assert!(!debounce.state(9));
}
#[test]
fn test_button_set_clear_and_gamepad_state() {
let mut buttons = ButtonInput::new();
buttons.set(GamepadButton::ActionB, true);
assert!(buttons.pressed(GamepadButton::ActionB));
assert!(!buttons.pressed(GamepadButton::ActionA));
buttons.update();
buttons.clear();
assert!(!buttons.pressed(GamepadButton::ActionB));
assert!(!buttons.just_pressed(GamepadButton::ActionB));
let mut gp = VirtualGamepad::new();
gp.left_stick = VirtualAxis2D::new(0.5, -0.25);
gp.right_stick = VirtualAxis2D::new(0.75, 0.0);
gp.buttons.press(GamepadButton::ActionA);
gp.buttons.press(GamepadButton::RightBumper);
let state = gp.to_input_state();
assert_eq!(state.forward, -0.25);
assert_eq!(state.strafe, 0.5);
assert_eq!(state.look_yaw, 0.75);
assert_eq!(state.look_pitch, 0.0);
assert!(state.jump);
assert!(state.sprint);
gp.update();
assert!(gp.buttons.pressed(GamepadButton::ActionA));
}
#[test]
fn test_virtual_axis_dpad_adc_and_vector() {
assert_eq!(
VirtualAxis2D::from_dpad(true, false, false, false),
VirtualAxis2D::new(0.0, 1.0)
);
assert_eq!(
VirtualAxis2D::from_dpad(false, true, true, false),
VirtualAxis2D::new(-1.0, -1.0)
);
assert_eq!(
VirtualAxis2D::from_dpad(false, false, false, false),
VirtualAxis2D::new(0.0, 0.0)
);
let adc = VirtualAxis2D::from_adc(4095, 2048, 2048, 2048, 4096);
assert!(adc.x > 0.99);
assert!((adc.y).abs() < 1e-4);
let v = VirtualAxis2D::new(0.3, -0.4).to_vector();
assert_eq!(v.x, 0.3);
assert_eq!(v.y, -0.4);
let small = VirtualAxis2D::new(0.00001, 0.0).with_radial_deadzone(0.0);
assert_eq!(small, VirtualAxis2D::new(0.0, 0.0));
let _ = VirtualAxis2D::default();
}
}