use core::time::Duration;
use winit::event::{DeviceEvent, MouseScrollDelta, TouchPhase, WindowEvent};
use winit::keyboard::PhysicalKey;
use crate::input::binding::{
Axis2Binding, Axis2Source, AxisBinding, AxisSource, ButtonBinding, JoystickControl, Key,
MouseButton, Pad, PadAxis, PointerDelta, Stick, WheelDelta,
};
use crate::input::pad::Pads;
use crate::math::Vec2;
use crate::platform::WHEEL_RATE;
const UNMAPPED: usize = 64;
const HELD: f32 = 0.5;
const ACTUATED: f32 = AxisBinding::DEFAULT_DEADZONE;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct WheelRate {
lines: f32,
pixels: f32,
}
impl WheelRate {
pub(crate) const DESKTOP: Self = Self::per_notch(1.0, 100.0);
pub(crate) const BROWSER: Self = Self::per_notch(3.0, 100.0);
const fn per_notch(lines: f32, pixels: f32) -> Self {
Self { lines, pixels }
}
fn notches(self, delta: MouseScrollDelta) -> Vec2 {
let (sideways, up, rate) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x, y, self.lines),
MouseScrollDelta::PixelDelta(pixels) => (pixels.x as f32, pixels.y as f32, self.pixels),
};
Vec2::new(-sideways, up) / rate
}
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn lines(self, turn: Vec2) -> Vec2 {
Vec2::new(-turn.x, turn.y) * self.lines
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct Controls {
frame: Snapshot,
tapped: Pressed,
clicking: Clicking,
}
impl Controls {
pub(crate) fn frame(&self) -> &Snapshot {
&self.frame
}
pub(crate) fn clicks(&self, bindings: &[ButtonBinding]) -> u32 {
self.clicking.clicks(bindings)
}
}
#[derive(Default)]
pub(crate) struct Ticks {
snapshot: Snapshot,
tapped: Pressed,
}
impl Ticks {
pub(crate) fn fold(&mut self, controls: &Controls) {
self.tapped.merge(controls.tapped);
self.snapshot.now = controls.frame.now.holding(self.tapped);
}
pub(crate) fn snapshot(&self) -> &Snapshot {
&self.snapshot
}
pub(crate) fn ticked(&mut self) {
self.snapshot.before = self.snapshot.now;
self.tapped = Pressed::default();
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct Snapshot {
now: Reading,
before: Reading,
}
impl Snapshot {
pub(crate) fn down(&self, bindings: &[ButtonBinding]) -> bool {
self.now.any(bindings)
}
pub(crate) fn pressed(&self, bindings: &[ButtonBinding]) -> bool {
self.now.any(bindings) && !self.before.any(bindings)
}
pub(crate) fn released(&self, bindings: &[ButtonBinding]) -> bool {
!self.now.any(bindings) && self.before.any(bindings)
}
pub(crate) fn axis(&self, bindings: &[AxisBinding]) -> f32 {
bindings.iter().fold(0.0, |most, binding| {
let value = self.now.axis(binding);
match value.abs() > most.abs() {
true => value,
false => most,
}
})
}
pub(crate) fn axis2(&self, bindings: &[Axis2Binding]) -> Vec2 {
bindings.iter().fold(Vec2::ZERO, |most, binding| {
let value = self.now.axis2(binding);
match value.length_squared() > most.length_squared() {
true => value,
false => most,
}
})
}
pub(crate) fn pointer(&self) -> Vec2 {
self.now.pointer
}
pub(crate) fn actuated_button(&self) -> Option<ButtonBinding> {
let keys = Key::ALL.iter().copied().map(ButtonBinding::Key);
let mouse = MouseButton::ALL.iter().copied().map(ButtonBinding::Mouse);
let pad = Pad::ALL.iter().copied().map(ButtonBinding::Pad);
let joystick = self
.now
.joystick
.iter()
.map(|(control, _)| ButtonBinding::Joystick(control));
keys.chain(mouse)
.chain(pad)
.chain(joystick)
.find(|&binding| self.now.held(binding) && !self.before.held(binding))
}
pub(crate) fn actuated_axis(&self) -> Option<AxisBinding> {
let pad = PadAxis::ALL.iter().copied().map(AxisBinding::pad);
let joystick = self
.now
.joystick_axes
.iter()
.map(|(control, _)| AxisBinding::joystick(control));
pad.chain(joystick).find(|binding| {
self.now.axis(binding).abs() > ACTUATED && self.before.axis(binding).abs() <= ACTUATED
})
}
pub(crate) fn actuated_axis2(&self) -> Option<Axis2Binding> {
Stick::ALL
.iter()
.copied()
.map(Axis2Binding::stick)
.find(|binding| {
self.now.axis2(binding).length() > ACTUATED
&& self.before.axis2(binding).length() <= ACTUATED
})
}
}
#[derive(Clone, Copy, Default)]
struct Clicking {
control: Option<ButtonBinding>,
at: Duration,
count: u32,
}
impl Clicking {
fn press(&mut self, pressed: Option<ButtonBinding>, at: Duration, within: Duration) {
let Some(control) = pressed else {
return;
};
let again = self.control == Some(control) && at.saturating_sub(self.at) <= within;
self.count = match again {
true => self.count + 1,
false => 1,
};
self.control = Some(control);
self.at = at;
}
fn clicks(&self, bindings: &[ButtonBinding]) -> u32 {
match self
.control
.is_some_and(|control| bindings.contains(&control))
{
true => self.count,
false => 0,
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct Reading {
pressed: Pressed,
pad: [bool; Pad::COUNT],
pad_axes: [f32; PadAxis::COUNT],
joystick: Unmapped,
joystick_axes: Unmapped,
pointer: Vec2,
pointer_delta: Vec2,
wheel: Vec2,
}
impl Reading {
fn holding(mut self, tapped: Pressed) -> Self {
self.pressed.merge(tapped);
self
}
pub(crate) fn press_pad(&mut self, button: Pad) {
self.pad[button.index()] = true;
}
pub(crate) fn push_pad(&mut self, axis: PadAxis, value: f32) {
let lane = &mut self.pad_axes[axis.index()];
if value.abs() > lane.abs() {
*lane = value.clamp(-1.0, 1.0);
}
}
pub(crate) fn press_joystick(&mut self, control: JoystickControl) {
self.joystick.push(control, 1.0);
}
pub(crate) fn push_joystick(&mut self, control: JoystickControl, value: f32) {
self.joystick_axes.push(control, value.clamp(-1.0, 1.0));
}
fn forget_pads(&mut self) {
self.pad = [false; Pad::COUNT];
self.pad_axes = [0.0; PadAxis::COUNT];
self.joystick = Unmapped::default();
self.joystick_axes = Unmapped::default();
}
fn any(&self, bindings: &[ButtonBinding]) -> bool {
bindings.iter().any(|&binding| self.held(binding))
}
fn held(&self, binding: ButtonBinding) -> bool {
match binding {
ButtonBinding::Key(key) => self.pressed.keys[key.index()],
ButtonBinding::Mouse(button) => self.pressed.mouse[button.index()],
ButtonBinding::Pad(button) => self.pad[button.index()],
ButtonBinding::Joystick(control) => self.joystick.value(control) > HELD,
}
}
fn axis(&self, binding: &AxisBinding) -> f32 {
let raw = match binding.source {
AxisSource::Pad(axis) => self.pad_axes[axis.index()],
AxisSource::Joystick(control) => self.joystick_axes.value(control),
AxisSource::Pointer(lane) => match lane {
PointerDelta::Sideways => self.pointer_delta.x,
PointerDelta::Up => self.pointer_delta.y,
},
AxisSource::Wheel(lane) => match lane {
WheelDelta::Sideways => self.wheel.x,
WheelDelta::Up => self.wheel.y,
},
AxisSource::Buttons { negative, positive } => {
weigh(self.held(positive)) - weigh(self.held(negative))
}
};
binding.resolve(raw)
}
fn axis2(&self, binding: &Axis2Binding) -> Vec2 {
let raw = match binding.source {
Axis2Source::Stick(stick) => {
let (x, y) = stick.lanes();
Vec2::new(self.pad_axes[x.index()], self.pad_axes[y.index()])
}
Axis2Source::Pointer => self.pointer_delta,
Axis2Source::Wheel => self.wheel,
Axis2Source::Buttons {
left,
right,
down,
up,
} => Vec2::new(
weigh(self.held(right)) - weigh(self.held(left)),
weigh(self.held(up)) - weigh(self.held(down)),
),
};
binding.resolve(raw)
}
}
impl Default for Reading {
fn default() -> Self {
Self {
pressed: Pressed::default(),
pad: [false; Pad::COUNT],
pad_axes: [0.0; PadAxis::COUNT],
joystick: Unmapped::default(),
joystick_axes: Unmapped::default(),
pointer: Vec2::ZERO,
pointer_delta: Vec2::ZERO,
wheel: Vec2::ZERO,
}
}
}
#[derive(Clone, Copy)]
struct Pressed {
keys: [bool; Key::COUNT],
mouse: [bool; MouseButton::COUNT],
}
impl Pressed {
#[cfg(all(feature = "ui", feature = "offscreen"))]
fn held(&self, control: Switch) -> bool {
match control {
Switch::Key(key) => self.keys[key.index()],
Switch::Mouse(button) => self.mouse[button.index()],
}
}
fn at(&mut self, control: Switch) -> &mut bool {
match control {
Switch::Key(key) => &mut self.keys[key.index()],
Switch::Mouse(button) => &mut self.mouse[button.index()],
}
}
fn merge(&mut self, other: Self) {
for (held, tapped) in self.keys.iter_mut().zip(other.keys) {
*held |= tapped;
}
for (held, tapped) in self.mouse.iter_mut().zip(other.mouse) {
*held |= tapped;
}
}
}
impl Default for Pressed {
fn default() -> Self {
Self {
keys: [false; Key::COUNT],
mouse: [false; MouseButton::COUNT],
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Switch {
Key(Key),
Mouse(MouseButton),
}
impl From<Key> for Switch {
fn from(key: Key) -> Self {
Self::Key(key)
}
}
impl From<MouseButton> for Switch {
fn from(button: MouseButton) -> Self {
Self::Mouse(button)
}
}
#[derive(Clone, Copy)]
struct Unmapped {
controls: [(JoystickControl, f32); UNMAPPED],
len: usize,
}
impl Unmapped {
fn push(&mut self, control: JoystickControl, value: f32) {
match self.controls[..self.len].binary_search_by_key(&control, |&(id, _)| id) {
Ok(at) if value.abs() > self.controls[at].1.abs() => self.controls[at].1 = value,
Ok(_) => {}
Err(at) if self.len < UNMAPPED => {
self.controls[at..=self.len].rotate_right(1);
self.controls[at] = (control, value);
self.len += 1;
}
Err(_) => {}
}
}
fn value(&self, control: JoystickControl) -> f32 {
match self.controls[..self.len].binary_search_by_key(&control, |&(id, _)| id) {
Ok(at) => self.controls[at].1,
Err(_) => 0.0,
}
}
fn iter(&self) -> impl Iterator<Item = (JoystickControl, f32)> + '_ {
self.controls[..self.len].iter().copied()
}
}
impl Default for Unmapped {
fn default() -> Self {
Self {
controls: [(JoystickControl::new(0), 0.0); UNMAPPED],
len: 0,
}
}
}
pub(crate) struct Devices {
pads: Pads,
double_click: Duration,
live: Reading,
tapped: Pressed,
tracked: Option<Vec2>,
held: bool,
touch: Option<u64>,
before: Reading,
clicking: Clicking,
}
impl Devices {
pub(crate) fn new(pads: Pads, double_click: Duration) -> Self {
Self {
pads,
double_click,
live: Reading::default(),
tapped: Pressed::default(),
tracked: None,
held: false,
touch: None,
before: Reading::default(),
clicking: Clicking::default(),
}
}
pub(crate) fn see(&mut self, event: &WindowEvent) {
match event {
WindowEvent::KeyboardInput { event, .. } => {
let PhysicalKey::Code(code) = event.physical_key else {
return;
};
let Some(key) = Key::from_code(code) else {
return;
};
self.press(Switch::Key(key), event.state.is_pressed());
}
WindowEvent::MouseInput { button, state, .. } => {
let Some(button) = MouseButton::from_winit(*button) else {
return;
};
self.press(Switch::Mouse(button), state.is_pressed());
}
WindowEvent::CursorMoved { position, .. } => {
self.point_at(Vec2::new(position.x as f32, position.y as f32));
}
WindowEvent::MouseWheel { delta, .. } => {
self.live.wheel += WHEEL_RATE.notches(*delta);
}
WindowEvent::Touch(touch) => self.touch(touch),
WindowEvent::Focused(false) => {
self.live.pressed = Pressed::default();
self.touch = None;
self.hold_pointer(false);
}
_ => {}
}
}
pub(crate) fn see_device(&mut self, event: &DeviceEvent) {
let DeviceEvent::MouseMotion { delta: (x, y) } = event else {
return;
};
if self.held {
self.move_pointer(Vec2::new(*x as f32, -(*y as f32)));
}
}
pub(crate) fn hold_pointer(&mut self, held: bool) {
if core::mem::replace(&mut self.held, held) != held && !held {
self.tracked = None;
}
}
pub(crate) fn sample(&mut self, at: Duration) -> Controls {
self.live.forget_pads();
self.pads.poll(&mut self.live);
let tapped = core::mem::take(&mut self.tapped);
let now = self.live.holding(tapped);
let frame = Snapshot {
before: core::mem::replace(&mut self.before, now),
now,
};
self.clicking
.press(frame.actuated_button(), at, self.double_click);
self.live.pointer_delta = Vec2::ZERO;
self.live.wheel = Vec2::ZERO;
Controls {
frame,
tapped,
clicking: self.clicking,
}
}
pub(crate) fn press(&mut self, control: Switch, down: bool) {
*self.live.pressed.at(control) = down;
if down {
*self.tapped.at(control) = true;
}
}
pub(crate) fn move_pointer(&mut self, pixels: Vec2) {
self.live.pointer_delta += pixels;
}
#[cfg(feature = "offscreen")]
pub(crate) fn turn_wheel(&mut self, notches: Vec2) {
self.live.wheel += notches;
}
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn holds(&self, control: Switch) -> bool {
self.live.pressed.held(control)
}
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn modifiers(&self) -> egui::Modifiers {
let either = |left, right| self.holds(Switch::Key(left)) || self.holds(Switch::Key(right));
let ctrl = either(Key::LeftControl, Key::RightControl);
egui::Modifiers {
alt: either(Key::LeftAlt, Key::RightAlt),
ctrl,
shift: either(Key::LeftShift, Key::RightShift),
mac_cmd: false,
command: ctrl,
}
}
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn pointing_at(&self) -> Option<Vec2> {
self.tracked
}
pub(crate) fn point_at(&mut self, position: Vec2) {
if self.held {
return;
}
if let Some(from) = self.tracked {
self.live.pointer_delta += Vec2::new(position.x - from.x, from.y - position.y);
}
self.tracked = Some(position);
self.live.pointer = position;
}
fn touch(&mut self, touch: &winit::event::Touch) {
let at = Vec2::new(touch.location.x as f32, touch.location.y as f32);
let primary = Switch::Mouse(MouseButton::Left);
match touch.phase {
TouchPhase::Started if self.touch.is_none() => {
self.touch = Some(touch.id);
self.tracked = None;
self.point_at(at);
self.press(primary, true);
}
TouchPhase::Moved if self.touch == Some(touch.id) => self.point_at(at),
TouchPhase::Ended | TouchPhase::Cancelled if self.touch == Some(touch.id) => {
self.touch = None;
self.press(primary, false);
}
_ => {}
}
}
}
fn weigh(held: bool) -> f32 {
match held {
true => 1.0,
false => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use winit::dpi::PhysicalPosition;
use winit::event::{DeviceId, ElementState, Touch};
use crate::input::binding::{ButtonAxis, ButtonAxis2};
use crate::platform::Platform;
const WITHIN: Duration = Duration::from_millis(400);
const JUMP: [ButtonBinding; 2] = [
ButtonBinding::Key(Key::Space),
ButtonBinding::Pad(Pad::South),
];
fn reading(fill: impl FnOnce(&mut Reading)) -> Reading {
let mut reading = Reading::default();
fill(&mut reading);
reading
}
fn snapshot(before: Reading, now: Reading) -> Snapshot {
Snapshot { now, before }
}
fn click(state: ElementState) -> WindowEvent {
WindowEvent::MouseInput {
device_id: DeviceId::dummy(),
state,
button: winit::event::MouseButton::Left,
}
}
fn cursor_at(x: f64, y: f64) -> WindowEvent {
WindowEvent::CursorMoved {
device_id: DeviceId::dummy(),
position: PhysicalPosition::new(x, y),
}
}
fn wheel(delta: MouseScrollDelta) -> WindowEvent {
WindowEvent::MouseWheel {
device_id: DeviceId::dummy(),
delta,
phase: TouchPhase::Moved,
}
}
fn rolled_away(lines: f32) -> MouseScrollDelta {
MouseScrollDelta::LineDelta(0.0, lines)
}
fn tilted_right(lines: f32) -> MouseScrollDelta {
MouseScrollDelta::LineDelta(-lines, 0.0)
}
fn scrolled_away(pixels: f64) -> MouseScrollDelta {
MouseScrollDelta::PixelDelta(PhysicalPosition::new(0.0, pixels))
}
fn motion(x: f64, y: f64) -> DeviceEvent {
DeviceEvent::MouseMotion { delta: (x, y) }
}
fn touch_at(phase: TouchPhase, id: u64, x: f64, y: f64) -> WindowEvent {
WindowEvent::Touch(Touch {
device_id: DeviceId::dummy(),
phase,
location: PhysicalPosition::new(x, y),
force: None,
id,
})
}
fn seen(devices: &mut Devices, events: &[WindowEvent]) -> Controls {
for event in events {
devices.see(event);
}
devices.sample(Duration::ZERO)
}
fn clicked(devices: &mut Devices, control: Switch, at: Duration) -> u32 {
devices.press(control, true);
let clicks = devices.sample(at).clicks(&[bound(control)]);
devices.press(control, false);
devices.sample(at);
clicks
}
fn bound(control: Switch) -> ButtonBinding {
match control {
Switch::Key(key) => ButtonBinding::Key(key),
Switch::Mouse(button) => ButtonBinding::Mouse(button),
}
}
#[test]
fn presses_of_one_control_within_the_interval_count_up_and_start_again_past_it() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let left = Switch::Mouse(MouseButton::Left);
assert_eq!(clicked(&mut devices, left, Duration::ZERO), 1, "one press");
assert_eq!(
clicked(&mut devices, left, WITHIN),
2,
"a second inside the interval is a double click"
);
assert_eq!(
clicked(&mut devices, left, WITHIN * 2 + Duration::from_millis(1)),
1,
"and one a millisecond past it starts a run of its own"
);
}
#[test]
fn a_control_pressed_beside_another_leaves_each_action_the_count_of_its_own() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let left = Switch::Mouse(MouseButton::Left);
let space = Switch::Key(Key::Space);
let selecting = [bound(left)];
let jumping = [bound(space)];
clicked(&mut devices, left, Duration::ZERO);
assert_eq!(
clicked(&mut devices, left, Duration::from_millis(100)),
2,
"the button has a count of two"
);
devices.press(left, true);
devices.press(space, true);
let sample = devices.sample(Duration::from_millis(200));
assert_eq!(sample.clicks(&jumping), 1, "the key counts its own press");
assert_eq!(
sample.clicks(&selecting),
0,
"and the button reads none of the key's count"
);
devices.press(left, false);
devices.press(space, false);
devices.sample(Duration::from_millis(250));
devices.press(left, true);
let sample = devices.sample(Duration::from_millis(300));
assert_eq!(
sample.clicks(&selecting),
1,
"the button counts its own press again"
);
assert_eq!(
sample.clicks(&jumping),
0,
"and the key reads none of the button's count"
);
}
#[test]
fn a_press_of_another_control_starts_the_count_again() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let left = Switch::Mouse(MouseButton::Left);
let right = Switch::Mouse(MouseButton::Right);
assert_eq!(clicked(&mut devices, left, Duration::ZERO), 1);
assert_eq!(clicked(&mut devices, left, Duration::from_millis(100)), 2);
assert_eq!(
clicked(&mut devices, right, Duration::from_millis(200)),
1,
"another control counts as one press, however soon it is pressed"
);
assert_eq!(
clicked(&mut devices, left, Duration::from_millis(300)),
1,
"and the one before it starts over too"
);
}
#[test]
fn an_edge_belongs_to_the_frame_the_control_changed_in() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let held = [ButtonBinding::Mouse(MouseButton::Left)];
let pressed = seen(&mut devices, &[click(ElementState::Pressed)]);
assert!(pressed.frame().down(&held) && pressed.frame().pressed(&held));
let still = seen(&mut devices, &[]);
assert!(still.frame().down(&held), "still held across frames");
assert!(!still.frame().pressed(&held), "the edge is spent");
let released = seen(&mut devices, &[click(ElementState::Released)]);
assert!(!released.frame().down(&held) && released.frame().released(&held));
assert!(!seen(&mut devices, &[]).frame().released(&held));
}
#[test]
fn a_frame_that_runs_no_ticks_keeps_its_edges_for_the_ticks_after_it() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let mut ticks = Ticks::default();
let held = [ButtonBinding::Mouse(MouseButton::Left)];
ticks.fold(&seen(&mut devices, &[click(ElementState::Pressed)]));
let over = seen(&mut devices, &[]);
ticks.fold(&over);
assert!(
!over.frame().pressed(&held),
"the frame that saw the press is over"
);
assert!(
ticks.snapshot().pressed(&held),
"and no tick has read it yet"
);
ticks.ticked();
assert!(
!ticks.snapshot().pressed(&held),
"the ticks that read it are the only ones that do"
);
ticks.fold(&seen(&mut devices, &[click(ElementState::Released)]));
ticks.fold(&seen(&mut devices, &[]));
assert!(
ticks.snapshot().released(&held),
"and coming up waits for the ticks the same way"
);
ticks.ticked();
ticks.fold(&seen(&mut devices, &[]));
assert!(!ticks.snapshot().released(&held));
}
#[test]
fn a_tap_made_while_no_ticks_ran_is_still_seen_by_the_ticks_after_it() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let mut ticks = Ticks::default();
let held = [ButtonBinding::Mouse(MouseButton::Left)];
ticks.fold(&seen(
&mut devices,
&[click(ElementState::Pressed), click(ElementState::Released)],
));
ticks.fold(&seen(&mut devices, &[]));
assert!(ticks.snapshot().pressed(&held), "the press is not lost");
ticks.ticked();
ticks.fold(&seen(&mut devices, &[]));
assert!(ticks.snapshot().released(&held), "and comes back up");
}
#[test]
fn a_tap_between_two_frames_is_still_seen_by_one_of_them() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let held = [ButtonBinding::Mouse(MouseButton::Left)];
let tapped = seen(
&mut devices,
&[click(ElementState::Pressed), click(ElementState::Released)],
);
assert!(tapped.frame().pressed(&held), "the press is not lost");
assert!(
seen(&mut devices, &[]).frame().released(&held),
"and comes back up"
);
}
#[test]
fn one_action_over_two_controls_takes_one_edge_at_a_time() {
let held = snapshot(
reading(|reading| reading.press_pad(Pad::South)),
reading(|reading| {
reading.press_pad(Pad::South);
reading.pressed.keys[Key::Space.index()] = true;
}),
);
assert!(held.down(&JUMP));
assert!(
!held.pressed(&JUMP),
"the second control joins an action already down"
);
}
#[test]
fn the_pointer_reports_where_it_is_and_how_far_it_moved() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let placed = seen(&mut devices, &[cursor_at(10.0, 20.0)]);
assert_eq!(placed.frame().pointer(), Vec2::new(10.0, 20.0));
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
let upward = [AxisBinding::pointer_delta(PointerDelta::Up)];
let moved = seen(&mut devices, &[cursor_at(14.0, 18.0)]);
assert_eq!(moved.frame().axis(&sideways), 4.0);
assert!(moved.frame().axis(&upward) > 0.0, "up the screen counts up");
let still = seen(&mut devices, &[]);
assert_eq!(still.frame().pointer(), Vec2::new(14.0, 18.0));
assert_eq!(still.frame().axis(&sideways), 0.0, "movement is spent");
}
#[test]
fn a_pointer_lane_reads_the_distance_it_moved_while_a_pad_lane_stops_at_its_own_end() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let look = [AxisBinding::pointer_delta(PointerDelta::Sideways).scale(0.01)];
seen(&mut devices, &[cursor_at(0.0, 0.0)]);
let moved = seen(&mut devices, &[cursor_at(500.0, 0.0)]);
assert_eq!(
moved.frame().axis(&look),
5.0,
"five hundred pixels at a hundredth each"
);
let pushed = snapshot(
Reading::default(),
reading(|reading| reading.push_pad(PadAxis::LeftX, 1.0)),
);
let lane = [AxisBinding::pad(PadAxis::LeftX).scale(4.0)];
assert_eq!(pushed.axis(&lane), 1.0, "and a pad lane reads one at most");
}
#[test]
fn an_action_bound_to_a_stick_and_the_pointer_reads_the_one_pushed_furthest() {
let bindings = [
Axis2Binding::stick(Stick::Left).deadzone(0.0),
Axis2Binding::pointer().scale(0.01),
];
let nudged = snapshot(
Reading::default(),
reading(|reading| {
reading.push_pad(PadAxis::LeftX, 0.5);
reading.pointer_delta = Vec2::new(20.0, 0.0);
}),
);
assert_eq!(
nudged.axis2(&bindings),
Vec2::new(0.5, 0.0),
"the stick, over a pointer barely moved"
);
let swept = snapshot(
Reading::default(),
reading(|reading| {
reading.push_pad(PadAxis::LeftX, 1.0);
reading.pointer_delta = Vec2::new(500.0, 0.0);
}),
);
assert_eq!(
swept.axis2(&bindings),
Vec2::new(5.0, 0.0),
"and the pointer, which reaches past the stick's own end"
);
}
#[test]
fn a_held_pointer_reads_the_movement_its_device_reports_as_a_window_pointer_reads_its_own() {
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
let upward = [AxisBinding::pointer_delta(PointerDelta::Up)];
let mut window = Devices::new(Pads::silent(), WITHIN);
seen(&mut window, &[cursor_at(10.0, 20.0)]);
let placed = seen(&mut window, &[cursor_at(10.5, 19.5)]);
let mut devices = Devices::new(Pads::silent(), WITHIN);
devices.hold_pointer(true);
devices.see_device(&motion(0.5, -0.5));
let held = devices.sample(Duration::ZERO);
assert_eq!(held.frame().axis(&sideways), placed.frame().axis(&sideways));
assert_eq!(held.frame().axis(&upward), placed.frame().axis(&upward));
assert!(held.frame().axis(&upward) > 0.0, "up the screen counts up");
}
#[test]
fn a_held_pointer_counts_its_movement_once_and_stays_where_it_was_held() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
seen(&mut devices, &[cursor_at(10.0, 20.0)]);
devices.hold_pointer(true);
devices.see_device(&motion(0.5, 0.0));
devices.see(&cursor_at(14.0, 20.0));
let held = devices.sample(Duration::ZERO);
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
assert_eq!(
held.frame().axis(&sideways),
0.5,
"the device's own movement, and not the window's report over it"
);
assert_eq!(
held.frame().pointer(),
Vec2::new(10.0, 20.0),
"and the place it was held at"
);
}
#[test]
fn a_released_pointer_measures_its_next_movement_from_where_the_window_reports_it() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
seen(&mut devices, &[cursor_at(10.0, 20.0)]);
devices.hold_pointer(true);
seen(&mut devices, &[]);
devices.hold_pointer(false);
let placed = seen(&mut devices, &[cursor_at(400.0, 20.0)]);
assert_eq!(
placed.frame().axis(&sideways),
0.0,
"however far the hold left it from there"
);
devices.hold_pointer(false);
let moved = seen(&mut devices, &[cursor_at(400.5, 20.0)]);
assert_eq!(
moved.frame().axis(&sideways),
0.5,
"and it moves from there, however often the release is set"
);
}
#[test]
fn a_window_that_loses_focus_loses_its_hold_on_the_pointer() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
seen(&mut devices, &[cursor_at(10.0, 20.0)]);
devices.hold_pointer(true);
seen(&mut devices, &[WindowEvent::Focused(false)]);
let placed = seen(&mut devices, &[cursor_at(400.0, 20.0)]);
assert_eq!(
placed.frame().pointer(),
Vec2::new(400.0, 20.0),
"the window places the pointer again"
);
let moved = seen(&mut devices, &[cursor_at(400.5, 20.0)]);
assert_eq!(
moved.frame().axis(&sideways),
0.5,
"and its movement reads through again"
);
}
#[test]
fn a_pointer_nothing_holds_reads_none_of_the_movement_its_device_reports() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
seen(&mut devices, &[cursor_at(10.0, 20.0)]);
devices.see_device(&motion(0.5, 0.0));
devices.see(&cursor_at(10.5, 20.0));
let moved = devices.sample(Duration::ZERO);
let sideways = [AxisBinding::pointer_delta(PointerDelta::Sideways)];
assert_eq!(moved.frame().axis(&sideways), 0.5, "the window's alone");
}
#[test]
fn the_first_touch_is_the_pointer_and_its_primary_button() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let held = [ButtonBinding::Mouse(MouseButton::Left)];
let touched = seen(
&mut devices,
&[touch_at(TouchPhase::Started, 1, 40.0, 60.0)],
);
assert_eq!(touched.frame().pointer(), Vec2::new(40.0, 60.0));
assert!(touched.frame().pressed(&held));
let moved = seen(
&mut devices,
&[
touch_at(TouchPhase::Started, 2, 0.0, 0.0),
touch_at(TouchPhase::Moved, 1, 44.0, 60.0),
],
);
assert_eq!(
moved.frame().pointer(),
Vec2::new(44.0, 60.0),
"a second touch is not the pointer"
);
let ended = seen(&mut devices, &[touch_at(TouchPhase::Ended, 1, 44.0, 60.0)]);
assert!(ended.frame().released(&held));
}
#[test]
fn a_window_that_loses_focus_cannot_leave_a_control_stuck() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
seen(&mut devices, &[click(ElementState::Pressed)]);
let unfocused = seen(&mut devices, &[WindowEvent::Focused(false)]);
let held = [ButtonBinding::Mouse(MouseButton::Left)];
assert!(!unfocused.frame().down(&held));
}
#[test]
fn two_buttons_make_an_axis_and_four_make_a_vector() {
let walking = reading(|reading| {
reading.pressed.keys[Key::D.index()] = true;
reading.pressed.keys[Key::W.index()] = true;
});
let snapshot = snapshot(Reading::default(), walking);
assert_eq!(
snapshot.axis(&[AxisBinding::from(ButtonAxis {
negative: Key::A,
positive: Key::D
})]),
1.0
);
assert_eq!(
snapshot.axis(&[AxisBinding::from(ButtonAxis {
negative: Key::D,
positive: Key::A
})]),
-1.0
);
let wasd = [Axis2Binding::from(ButtonAxis2 {
left: Key::A,
right: Key::D,
down: Key::S,
up: Key::W,
})];
let walk = snapshot.axis2(&wasd);
assert!((walk.length() - 1.0).abs() < 1e-6, "{walk} is one long");
assert!(walk.x > 0.0 && walk.y > 0.0, "{walk} points up and right");
}
#[test]
fn the_control_pushed_furthest_is_the_one_an_action_reads() {
let leaning = reading(|reading| {
reading.push_pad(PadAxis::LeftX, 0.5);
reading.pressed.keys[Key::A.index()] = true;
});
let snapshot = snapshot(Reading::default(), leaning);
let bindings = [
AxisBinding::pad(PadAxis::LeftX).deadzone(0.0),
AxisBinding::from(ButtonAxis {
negative: Key::A,
positive: Key::D,
}),
];
assert_eq!(snapshot.axis(&bindings), -1.0, "the key is pushed further");
assert_eq!(
snapshot.axis(&bindings[..1]),
0.5,
"on its own the stick is"
);
}
#[test]
fn a_pad_lane_reads_the_device_pushing_it_furthest() {
let both = reading(|reading| {
reading.push_pad(PadAxis::LeftX, 0.4);
reading.push_pad(PadAxis::LeftX, -0.9);
reading.push_pad(PadAxis::LeftX, 0.2);
reading.push_joystick(JoystickControl::new(3), 0.6);
reading.push_joystick(JoystickControl::new(3), 0.1);
});
assert_eq!(both.pad_axes[PadAxis::LeftX.index()], -0.9);
assert_eq!(both.joystick_axes.value(JoystickControl::new(3)), 0.6);
assert_eq!(
both.joystick_axes.value(JoystickControl::new(4)),
0.0,
"and nothing for the rest"
);
}
#[test]
fn capture_answers_with_the_control_the_player_just_moved() {
let quiet = Reading::default();
let pushed = reading(|reading| {
reading.press_pad(Pad::Start);
reading.push_pad(PadAxis::RightX, 0.8);
reading.push_joystick(JoystickControl::new(11), 0.9);
});
let snapshot = snapshot(quiet, pushed);
assert_eq!(
snapshot.actuated_button(),
Some(ButtonBinding::Pad(Pad::Start))
);
assert_eq!(
snapshot.actuated_axis(),
Some(AxisBinding::pad(PadAxis::RightX))
);
assert_eq!(
snapshot.actuated_axis2(),
Some(Axis2Binding::stick(Stick::Right))
);
let still = Snapshot {
before: pushed,
now: pushed,
};
assert_eq!(still.actuated_button(), None, "a held control is not new");
assert_eq!(still.actuated_axis(), None);
assert_eq!(still.actuated_axis2(), None);
}
#[test]
fn capture_is_deaf_to_a_control_that_has_barely_moved() {
let nudged = reading(|reading| reading.push_pad(PadAxis::LeftY, ACTUATED));
let snapshot = snapshot(Reading::default(), nudged);
assert_eq!(snapshot.actuated_axis(), None);
assert_eq!(snapshot.actuated_axis2(), None);
}
#[test]
fn one_notch_reads_as_one_however_the_platform_counts_a_turn() {
let browser = Platform::Browser.wheel_rate();
let desktop = Platform::Desktop.wheel_rate();
assert_eq!(
browser.notches(rolled_away(3.0)),
Vec2::new(0.0, 1.0),
"three lines are a notch in a browser"
);
assert_eq!(
browser.notches(scrolled_away(100.0)),
Vec2::new(0.0, 1.0),
"and so are 100 pixels"
);
assert_eq!(
desktop.notches(rolled_away(1.0)),
Vec2::new(0.0, 1.0),
"one line is a notch on the desktop"
);
assert_eq!(
desktop.notches(scrolled_away(100.0)),
Vec2::new(0.0, 1.0),
"and 100 pixels are one there too"
);
}
#[test]
fn a_roll_away_and_a_tilt_to_the_right_each_read_positive() {
let rate = Platform::Desktop.wheel_rate();
assert_eq!(rate.notches(rolled_away(1.0)), Vec2::new(0.0, 1.0));
assert_eq!(rate.notches(tilted_right(1.0)), Vec2::new(1.0, 0.0));
assert_eq!(
rate.notches(rolled_away(-1.0)),
Vec2::new(0.0, -1.0),
"and a roll toward the player reads the other way round"
);
}
#[test]
fn a_tilt_reaches_the_sideways_lane_and_a_roll_the_upward_one() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let sideways = [AxisBinding::wheel(WheelDelta::Sideways)];
let upward = [AxisBinding::wheel(WheelDelta::Up)];
let tilted = seen(&mut devices, &[wheel(tilted_right(1.0))]);
assert_eq!(tilted.frame().axis(&sideways), 1.0);
assert_eq!(tilted.frame().axis(&upward), 0.0, "and nothing rolled");
let rolled = seen(&mut devices, &[wheel(rolled_away(1.0))]);
assert_eq!(rolled.frame().axis(&upward), 1.0);
assert_eq!(rolled.frame().axis(&sideways), 0.0, "nothing tilted");
let still = seen(&mut devices, &[]);
assert_eq!(
still.frame().axis(&upward),
0.0,
"and a turn lasts one reading"
);
}
#[test]
fn the_wheel_reads_as_one_vector_with_the_tilt_as_x_and_the_roll_as_y() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
let wheel_vector = [Axis2Binding::wheel()];
let turned = seen(
&mut devices,
&[wheel(tilted_right(1.0)), wheel(rolled_away(3.0))],
);
assert_eq!(turned.frame().axis2(&wheel_vector), Vec2::new(1.0, 3.0));
assert_eq!(
turned.frame().actuated_axis2(),
None,
"and a turn that far is never captured"
);
let still = seen(&mut devices, &[]);
assert_eq!(
still.frame().axis2(&wheel_vector),
Vec2::ZERO,
"a turn lasts one reading"
);
}
#[test]
fn a_pointer_is_never_captured_however_far_it_is_moved() {
let mut devices = Devices::new(Pads::silent(), WITHIN);
seen(&mut devices, &[cursor_at(0.0, 0.0)]);
let moved = seen(&mut devices, &[cursor_at(400.0, 400.0)]);
assert_eq!(moved.frame().actuated_axis(), None);
assert_eq!(moved.frame().actuated_axis2(), None);
}
}