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::WheelRate;
use core::num::NonZeroU32;
use core::time::Duration;
use winit::event::{DeviceEvent, WindowEvent};
use crate::math::Vec2;
use crate::platform::Store;
use state::Devices;
use table::{Rebound, Table};
pub(crate) struct Input {
table: Table,
devices: Devices,
pads: Pads,
store: Store,
dirty: bool,
double_click: Duration,
}
impl Input {
pub(crate) fn new<A: InputActions>(pads: Pads, store: Store, double_click: Duration) -> Self {
Self {
table: Table::new::<A>(store.read().as_deref()),
devices: Devices::default(),
pads,
store,
dirty: false,
double_click,
}
}
pub(crate) fn see(&mut self, event: &WindowEvent) {
self.devices.see(event);
}
pub(crate) fn see_device(&mut self, event: &DeviceEvent) {
self.devices.see_device(event);
}
pub(crate) fn hold_pointer(&mut self, held: bool) {
self.devices.hold_pointer(held);
}
#[cfg(feature = "offscreen")]
pub(crate) fn press(&mut self, control: Switch, down: bool) {
self.devices.press(control, down);
}
#[cfg(feature = "offscreen")]
pub(crate) fn point_at(&mut self, at: Vec2) {
self.devices.point_at(at);
}
#[cfg(feature = "offscreen")]
pub(crate) fn move_pointer(&mut self, lane: PointerDelta, pixels: f32) {
self.devices.move_pointer(lane.moving(pixels));
}
#[cfg(feature = "offscreen")]
pub(crate) fn turn_wheel(&mut self, lane: WheelDelta, notches: f32) {
self.devices.turn_wheel(lane.turning(notches));
}
#[cfg(all(feature = "ui", feature = "offscreen"))]
pub(crate) fn ui_modifiers(&self) -> egui::Modifiers {
let either = |left, right| {
self.devices.holds(Switch::Key(left)) || self.devices.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.devices.pointing_at()
}
pub(crate) fn sample(&mut self, at: Duration) {
self.devices.sample(&mut self.pads, at, self.double_click);
}
pub(crate) fn ticks(&mut self, count: NonZeroU32, mut tick: impl FnMut(&Ticking<'_>)) {
let ticking = Ticking { input: self };
for _ in 0..count.get() {
tick(&ticking);
}
}
pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
self.devices.snapshot().down(self.table.resolve(action))
}
pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
self.devices.snapshot().pressed(self.table.resolve(action))
}
pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
self.devices.snapshot().released(self.table.resolve(action))
}
pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
match self.pressed(action) {
true => self.devices.clicks(self.table.resolve(action)),
false => 0,
}
}
pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
self.devices.snapshot().axis(self.table.resolve(action))
}
pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
self.devices.snapshot().axis2(self.table.resolve(action))
}
pub(crate) fn pointer(&self) -> Vec2 {
self.devices.snapshot().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) {
if core::mem::take(&mut self.dirty) {
self.store.write(&self.table.written());
}
}
pub(crate) fn actuated_button(&self) -> Option<ButtonBinding> {
self.devices.snapshot().actuated_button()
}
pub(crate) fn actuated_axis(&self) -> Option<AxisBinding> {
self.devices.snapshot().actuated_axis()
}
pub(crate) fn actuated_axis2(&self) -> Option<Axis2Binding> {
self.devices.snapshot().actuated_axis2()
}
fn pressed_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
self.devices.ticks().pressed(self.table.resolve(action))
}
fn released_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
self.devices.ticks().released(self.table.resolve(action))
}
fn clicks_over_ticks<A: InputButtonAction>(&self, action: A) -> u32 {
match self.pressed_over_ticks(action) {
true => self.devices.clicks(self.table.resolve(action)),
false => 0,
}
}
fn ticked(&mut self) {
self.devices.ticked();
}
}
pub(crate) struct Ticking<'a> {
input: &'a mut Input,
}
impl Ticking<'_> {
pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
self.input.down(action)
}
pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
self.input.pressed_over_ticks(action)
}
pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
self.input.released_over_ticks(action)
}
pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
self.input.clicks_over_ticks(action)
}
pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
self.input.axis(action)
}
pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
self.input.axis2(action)
}
pub(crate) fn pointer(&self) -> Vec2 {
self.input.pointer()
}
}
impl Drop for Ticking<'_> {
fn drop(&mut self) {
self.input.ticked();
}
}
mod action;
mod binding;
mod cursor;
mod pad;
mod state;
mod table;
#[cfg(test)]
mod tests {
use super::*;
use winit::event::{DeviceId, ElementState};
const DOUBLE_CLICK: Duration = Duration::from_millis(400);
const TICKS: NonZeroU32 = match NonZeroU32::new(3) {
Some(ticks) => ticks,
None => NonZeroU32::MIN,
};
fn input() -> Input {
Input::new::<Key>(Pads::silent(), Store::bindings(None), DOUBLE_CLICK)
}
fn click() -> WindowEvent {
WindowEvent::MouseInput {
device_id: DeviceId::dummy(),
state: ElementState::Pressed,
button: winit::event::MouseButton::Left,
}
}
fn pressed_per_tick(input: &mut Input, count: NonZeroU32, action: Key) -> Vec<bool> {
let mut read = Vec::new();
input.ticks(count, |ticking| read.push(ticking.pressed(action)));
read
}
#[test]
fn only_a_rebind_that_changed_something_leaves_the_store_to_write() {
let mut input = input();
input.rebind(Key::Space, vec![Key::Space.into()]);
assert!(!input.dirty, "what it read through already is no change");
input.rebind(Key::Space, vec![Key::Enter.into()]);
assert!(input.dirty);
input.flush();
assert!(!input.dirty, "so the store is written once, not per call");
assert_eq!(input.bindings(Key::Space), vec![Key::Enter.into()]);
}
#[test]
fn the_ticks_of_one_frame_read_an_edge_and_the_ticks_after_them_read_none() {
let mut input = input();
input.rebind(Key::Space, vec![MouseButton::Left.into()]);
input.see(&click());
input.sample(Duration::ZERO);
input.sample(Duration::ZERO);
assert_eq!(
pressed_per_tick(&mut input, TICKS, Key::Space),
[true; 3],
"every tick of the frame that reads the press reads it"
);
input.sample(Duration::ZERO);
assert_eq!(
pressed_per_tick(&mut input, TICKS, Key::Space),
[false; 3],
"and the ticks that follow read it no more"
);
}
}