pub use action::{
InputAction, InputActions, InputAxis2Action, InputAxisAction, InputButtonAction,
NoInputActions, NoInputAxes, NoInputAxes2, NoInputButtons,
};
pub use binding::{
Axis2Binding, AxisBinding, ButtonAxis, ButtonAxis2, ButtonBinding, JoystickControl, Key,
MouseButton, Pad, PadAxis, PointerDelta, Stick, WheelDelta,
};
pub use cursor::Cursor;
#[cfg(feature = "offscreen")]
pub use state::Switch;
pub(crate) use pad::Pads;
pub(crate) use state::{Controls, Devices, WheelRate};
use core::num::NonZeroU32;
use crate::math::Vec2;
use state::Ticks;
use table::{Rebound, Table};
pub(crate) struct Queries {
table: Table,
controls: Controls,
ticks: Ticks,
dirty: bool,
}
impl Queries {
pub(crate) fn new<A: InputActions>(kept: Option<&str>) -> Self {
Self {
table: Table::new::<A>(kept),
controls: Controls::default(),
ticks: Ticks::default(),
dirty: false,
}
}
pub(crate) fn take(&mut self, controls: Controls) {
self.ticks.fold(&controls);
self.controls = controls;
}
pub(crate) fn ticks(&mut self, count: NonZeroU32, mut tick: impl FnMut(&Ticking<'_>)) {
let ticking = Ticking { queries: self };
for _ in 0..count.get() {
tick(&ticking);
}
}
pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
self.controls.frame().down(self.table.resolve(action))
}
pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
self.controls.frame().pressed(self.table.resolve(action))
}
pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
self.controls.frame().released(self.table.resolve(action))
}
pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
match self.pressed(action) {
true => self.controls.clicks(self.table.resolve(action)),
false => 0,
}
}
pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
self.controls.frame().axis(self.table.resolve(action))
}
pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
self.controls.frame().axis2(self.table.resolve(action))
}
pub(crate) fn pointer(&self) -> Vec2 {
self.controls.frame().pointer()
}
pub(crate) fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding> {
self.table.resolve(action).to_vec()
}
pub(crate) fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>) {
match self.table.rebind(action, bindings) {
Rebound::Changed => self.dirty = true,
Rebound::Unchanged => {}
}
}
pub(crate) fn flush(&mut self) -> Option<String> {
core::mem::take(&mut self.dirty).then(|| self.table.written())
}
pub(crate) fn actuated_button(&self) -> Option<ButtonBinding> {
self.controls.frame().actuated_button()
}
pub(crate) fn actuated_axis(&self) -> Option<AxisBinding> {
self.controls.frame().actuated_axis()
}
pub(crate) fn actuated_axis2(&self) -> Option<Axis2Binding> {
self.controls.frame().actuated_axis2()
}
fn pressed_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
self.ticks.snapshot().pressed(self.table.resolve(action))
}
fn released_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
self.ticks.snapshot().released(self.table.resolve(action))
}
fn clicks_over_ticks<A: InputButtonAction>(&self, action: A) -> u32 {
match self.pressed_over_ticks(action) {
true => self.controls.clicks(self.table.resolve(action)),
false => 0,
}
}
fn ticked(&mut self) {
self.ticks.ticked();
}
}
pub(crate) struct Ticking<'a> {
queries: &'a mut Queries,
}
impl Ticking<'_> {
pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
self.queries.down(action)
}
pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
self.queries.pressed_over_ticks(action)
}
pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
self.queries.released_over_ticks(action)
}
pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
self.queries.clicks_over_ticks(action)
}
pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
self.queries.axis(action)
}
pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
self.queries.axis2(action)
}
pub(crate) fn pointer(&self) -> Vec2 {
self.queries.pointer()
}
}
impl Drop for Ticking<'_> {
fn drop(&mut self) {
self.queries.ticked();
}
}
mod action;
mod binding;
mod cursor;
mod pad;
mod state;
mod table;
#[cfg(test)]
mod tests {
use core::time::Duration;
use super::*;
use winit::event::{DeviceId, ElementState, WindowEvent};
const DOUBLE_CLICK: Duration = Duration::from_millis(400);
const TICKS: NonZeroU32 = match NonZeroU32::new(3) {
Some(ticks) => ticks,
None => NonZeroU32::MIN,
};
fn run() -> (Devices, Queries) {
(
Devices::new(Pads::silent(), DOUBLE_CLICK),
Queries::new::<Key>(None),
)
}
fn click() -> WindowEvent {
WindowEvent::MouseInput {
device_id: DeviceId::dummy(),
state: ElementState::Pressed,
button: winit::event::MouseButton::Left,
}
}
fn pressed_per_tick(queries: &mut Queries, count: NonZeroU32, action: Key) -> Vec<bool> {
let mut read = Vec::new();
queries.ticks(count, |ticking| read.push(ticking.pressed(action)));
read
}
#[test]
fn only_a_rebind_that_changed_something_leaves_the_store_to_write() {
let (_, mut queries) = run();
queries.rebind(Key::Space, vec![Key::Space.into()]);
assert!(!queries.dirty, "what it read through already is no change");
queries.rebind(Key::Space, vec![Key::Enter.into()]);
assert!(queries.dirty);
assert!(
queries.flush().is_some(),
"and the text to write is handed out"
);
assert!(
queries.flush().is_none(),
"so the store is written once, not per call"
);
assert_eq!(queries.bindings(Key::Space), vec![Key::Enter.into()]);
}
#[test]
fn the_queries_answer_from_the_controls_alone_and_count_clicks_across_them() {
let (mut devices, mut queries) = run();
queries.rebind(Key::Space, vec![MouseButton::Left.into()]);
devices.see(&click());
queries.take(devices.sample(Duration::ZERO));
let first = (queries.pressed(Key::Space), queries.clicks(Key::Space));
devices.press(MouseButton::Left.into(), false);
devices.sample(Duration::ZERO);
devices.press(MouseButton::Left.into(), true);
queries.take(devices.sample(Duration::from_millis(100)));
assert_eq!(
(queries.pressed(Key::Space), queries.clicks(Key::Space)),
(first.0, first.1 + 1),
"the same press read through the controls, one click further on"
);
assert!(
queries.pressed_over_ticks(Key::Space),
"and the ticks' edge crosses in the same controls"
);
}
#[test]
fn the_controls_are_plain_data_that_cross_between_threads() {
fn crosses<T: Send + Clone>() {}
crosses::<Controls>();
}
#[test]
fn the_ticks_of_one_frame_read_an_edge_and_the_ticks_after_them_read_none() {
let (mut devices, mut queries) = run();
queries.rebind(Key::Space, vec![MouseButton::Left.into()]);
devices.see(&click());
queries.take(devices.sample(Duration::ZERO));
queries.take(devices.sample(Duration::ZERO));
assert_eq!(
pressed_per_tick(&mut queries, TICKS, Key::Space),
[true; 3],
"every tick of the frame that reads the press reads it"
);
queries.take(devices.sample(Duration::ZERO));
assert_eq!(
pressed_per_tick(&mut queries, TICKS, Key::Space),
[false; 3],
"and the ticks that follow read it no more"
);
}
}