use crate::components::NavDirection;
const REPEAT_DELAY: f32 = 0.4;
const REPEAT_INTERVAL: f32 = 0.12;
const STICK_PRESS: f32 = 0.5;
const STICK_RELEASE: f32 = 0.35;
pub(crate) type DpadHeld = [bool; 4];
#[derive(Debug, Default)]
pub(crate) struct NavRepeat {
commanded: Option<NavDirection>,
until_repeat: f32,
stick_latched: Option<NavDirection>,
}
impl NavRepeat {
pub(crate) fn step(
&mut self,
dpad: DpadHeld,
stick: [f32; 2],
dt: f32,
) -> Option<NavDirection> {
self.stick_latched = self.stick_direction(stick);
let commanded = dpad_direction(dpad).or(self.stick_latched);
if commanded != self.commanded {
self.commanded = commanded;
self.until_repeat = REPEAT_DELAY;
return commanded;
}
let held = commanded?;
self.until_repeat -= dt;
if self.until_repeat <= 0.0 {
self.until_repeat += REPEAT_INTERVAL;
if self.until_repeat < 0.0 {
self.until_repeat = REPEAT_INTERVAL;
}
return Some(held);
}
None
}
fn stick_direction(&self, stick: [f32; 2]) -> Option<NavDirection> {
if let Some(dir) = self.stick_latched
&& component(stick, dir) > STICK_RELEASE
{
return Some(dir);
}
let (x, y) = (stick[0], stick[1]);
let dir = if y.abs() >= x.abs() {
if y > 0.0 {
NavDirection::Up
} else {
NavDirection::Down
}
} else if x > 0.0 {
NavDirection::Right
} else {
NavDirection::Left
};
(component(stick, dir) >= STICK_PRESS).then_some(dir)
}
}
fn component(stick: [f32; 2], dir: NavDirection) -> f32 {
match dir {
NavDirection::Up => stick[1],
NavDirection::Down => -stick[1],
NavDirection::Left => -stick[0],
NavDirection::Right => stick[0],
}
}
fn dpad_direction(dpad: DpadHeld) -> Option<NavDirection> {
const ORDER: [NavDirection; 4] = [
NavDirection::Up,
NavDirection::Down,
NavDirection::Left,
NavDirection::Right,
];
dpad.iter()
.zip(ORDER)
.find(|(held, _)| **held)
.map(|(_, dir)| dir)
}
#[cfg(test)]
mod tests {
use super::*;
const IDLE: DpadHeld = [false; 4];
const DOWN: DpadHeld = [false, true, false, false];
#[test]
fn press_pulses_once_and_repeats_after_the_delay() {
let mut nav = NavRepeat::default();
assert_eq!(nav.step(DOWN, [0.0, 0.0], 0.016), Some(NavDirection::Down));
assert_eq!(nav.step(DOWN, [0.0, 0.0], REPEAT_DELAY - 0.05), None);
assert_eq!(nav.step(DOWN, [0.0, 0.0], 0.1), Some(NavDirection::Down));
assert_eq!(nav.step(DOWN, [0.0, 0.0], REPEAT_INTERVAL / 2.0), None);
assert_eq!(
nav.step(DOWN, [0.0, 0.0], REPEAT_INTERVAL),
Some(NavDirection::Down)
);
}
#[test]
fn release_clears_and_a_new_press_pulses_immediately() {
let mut nav = NavRepeat::default();
nav.step(DOWN, [0.0, 0.0], 0.016);
assert_eq!(nav.step(IDLE, [0.0, 0.0], 0.016), None);
assert_eq!(nav.step(DOWN, [0.0, 0.0], 0.016), Some(NavDirection::Down));
}
#[test]
fn direction_change_pulses_without_waiting_for_the_delay() {
let mut nav = NavRepeat::default();
nav.step(DOWN, [0.0, 0.0], 0.016);
let up: DpadHeld = [true, false, false, false];
assert_eq!(nav.step(up, [0.0, 0.0], 0.016), Some(NavDirection::Up));
}
#[test]
fn stick_press_pulses_and_holds_through_hysteresis() {
let mut nav = NavRepeat::default();
assert_eq!(nav.step(IDLE, [0.0, -0.4], 0.016), None);
assert_eq!(nav.step(IDLE, [0.0, -0.6], 0.016), Some(NavDirection::Down));
assert_eq!(nav.step(IDLE, [0.0, -0.4], 0.016), None);
assert_eq!(
nav.step(IDLE, [0.0, -0.4], REPEAT_DELAY),
Some(NavDirection::Down)
);
assert_eq!(nav.step(IDLE, [0.0, -0.2], 0.016), None);
assert_eq!(nav.step(IDLE, [0.0, -0.9], 0.016), Some(NavDirection::Down));
}
#[test]
fn stick_picks_the_dominant_axis() {
let mut nav = NavRepeat::default();
assert_eq!(nav.step(IDLE, [0.8, 0.3], 0.016), Some(NavDirection::Right));
let mut nav = NavRepeat::default();
assert_eq!(nav.step(IDLE, [0.3, 0.8], 0.016), Some(NavDirection::Up));
}
#[test]
fn dpad_wins_over_the_stick() {
let mut nav = NavRepeat::default();
assert_eq!(nav.step(DOWN, [0.0, 0.9], 0.016), Some(NavDirection::Down));
}
#[test]
fn a_long_frame_emits_a_single_pulse() {
let mut nav = NavRepeat::default();
nav.step(DOWN, [0.0, 0.0], 0.016);
assert_eq!(nav.step(DOWN, [0.0, 0.0], 5.0), Some(NavDirection::Down));
assert_eq!(nav.step(DOWN, [0.0, 0.0], 0.016), None);
}
}