Skip to main content

mirage_engine/input/
mod.rs

1//! Actions: what a player can do, and the controls bound to each one.
2//!
3//! A game names its own actions — `Jump`, `Walk`, `Aim` — in up to three
4//! vocabularies, one per kind: a button is pressed, released or down, an
5//! axis reads one number, an axis2 reads two. [`InputActions`] holds the
6//! three, and [`Game::InputActions`](crate::Game::InputActions) names the
7//! set; [`NoInputActions`] is the set of a game that reads no input, and
8//! [`Key`] is the prototype set that binds each key to itself. Every query
9//! — `down`, `pressed`, `released`, `axis`, `axis2` — takes an action of
10//! that set and no other.
11//!
12//! Each action declares its own default bindings, which may hold a key, a
13//! mouse button, a pad button, a stick or a composite of them. An axis or
14//! axis2 binding holds a deadzone —
15//! [`AxisBinding::DEFAULT_DEADZONE`](crate::AxisBinding::DEFAULT_DEADZONE)
16//! for a pad or joystick control, and zero for the rest — and a scale.
17//! A [`PointerDelta`] lane, a [`WheelDelta`] lane and
18//! [`Axis2Binding::pointer`](crate::Axis2Binding::pointer) read how far
19//! they moved through that scale and nothing clamps what they read; every
20//! other binding reads in its kind's own range.
21//!
22//! A game that rebinds reads [`bindings`](crate::FrameContext::bindings) for
23//! what an action is bound to now, and shows each binding's text.
24//! [`actuated_button`](crate::FrameContext::actuated_button),
25//! [`actuated_axis`](crate::FrameContext::actuated_axis) and
26//! [`actuated_axis2`](crate::FrameContext::actuated_axis2) then report
27//! whatever the player just moved, which
28//! [`rebind`](crate::FrameContext::rebind) takes. The engine keeps every
29//! rebind in the platform's own store, under a name made from the title
30//! [`Config::new`](crate::Config::new) was given.
31//!
32//! [`Cursor`] is what the pointer itself is drawn as, the closed set
33//! [`set_cursor`](crate::FrameContext::set_cursor) takes for one frame; a
34//! frame that sets none draws [`Cursor::Arrow`]. Where the UI sets a cursor
35//! of its own, the UI's is drawn instead. [`Cursor::Held`] draws no pointer
36//! and holds it in place, leaving a [`PointerDelta`] binding its
37//! movement to read and [`pointer`](crate::FrameContext::pointer) the place
38//! it was held at; the frame after it sets another cursor releases it.
39
40pub use action::{
41    InputAction, InputActions, InputAxis2Action, InputAxisAction, InputButtonAction,
42    NoInputActions, NoInputAxes, NoInputAxes2, NoInputButtons,
43};
44pub use binding::{
45    Axis2Binding, AxisBinding, ButtonAxis, ButtonAxis2, ButtonBinding, JoystickControl, Key,
46    MouseButton, Pad, PadAxis, PointerDelta, Stick, WheelDelta,
47};
48pub use cursor::Cursor;
49
50#[cfg(feature = "offscreen")]
51pub use state::Switch;
52
53pub(crate) use pad::Pads;
54pub(crate) use state::WheelRate;
55
56use core::num::NonZeroU32;
57use core::time::Duration;
58
59use winit::event::{DeviceEvent, WindowEvent};
60
61use crate::math::Vec2;
62use crate::platform::Store;
63use state::Devices;
64use table::{Rebound, Table};
65
66/// State behind the input API: every action's bindings, what the devices
67/// read, and where a rebind is kept.
68pub(crate) struct Input {
69    table: Table,
70    devices: Devices,
71    pads: Pads,
72    store: Store,
73    dirty: bool,
74    /// How long after a press a second one still counts as a double click.
75    double_click: Duration,
76}
77
78impl Input {
79    /// The input a run starts with: the game's actions at their declared
80    /// bindings, with whatever `store` kept of an earlier run over them.
81    pub(crate) fn new<A: InputActions>(pads: Pads, store: Store, double_click: Duration) -> Self {
82        Self {
83            table: Table::new::<A>(store.read().as_deref()),
84            devices: Devices::default(),
85            pads,
86            store,
87            dirty: false,
88            double_click,
89        }
90    }
91
92    pub(crate) fn see(&mut self, event: &WindowEvent) {
93        self.devices.see(event);
94    }
95
96    /// Takes what `event` reports of a device's own movement, which a held
97    /// pointer moves by and a pointer the window still places ignores.
98    pub(crate) fn see_device(&mut self, event: &DeviceEvent) {
99        self.devices.see_device(event);
100    }
101
102    /// Holds the pointer in place or releases it, by what the platform did
103    /// with it rather than by what the frame set.
104    pub(crate) fn hold_pointer(&mut self, held: bool) {
105        self.devices.hold_pointer(held);
106    }
107
108    /// Presses `control` or releases it in place of a window's event; only
109    /// a session calls this.
110    #[cfg(feature = "offscreen")]
111    pub(crate) fn press(&mut self, control: Switch, down: bool) {
112        self.devices.press(control, down);
113    }
114
115    /// Moves the pointer in place of a window's event; only a session calls
116    /// this.
117    #[cfg(feature = "offscreen")]
118    pub(crate) fn point_at(&mut self, at: Vec2) {
119        self.devices.point_at(at);
120    }
121
122    /// Moves one lane of the pointer in place of a window's event; only a
123    /// session calls this.
124    #[cfg(feature = "offscreen")]
125    pub(crate) fn move_pointer(&mut self, lane: PointerDelta, pixels: f32) {
126        self.devices.move_pointer(lane.moving(pixels));
127    }
128
129    /// Turns one lane of the wheel in place of a window's event; only a
130    /// session calls this.
131    #[cfg(feature = "offscreen")]
132    pub(crate) fn turn_wheel(&mut self, lane: WheelDelta, notches: f32) {
133        self.devices.turn_wheel(lane.turning(notches));
134    }
135
136    /// The `Shift`, `Control` and `Alt` keys held down as of now, as the
137    /// UI reads them, with `Control` as its command key.
138    ///
139    /// Only a session reads this: the keys a window holds down reach the
140    /// UI through `egui-winit`. A window on `macOS` takes its command key
141    /// from `Super`, which [`Key`] has no position for, so a session's
142    /// `Control` is what a command reads there.
143    #[cfg(all(feature = "ui", feature = "offscreen"))]
144    pub(crate) fn ui_modifiers(&self) -> egui::Modifiers {
145        let either = |left, right| {
146            self.devices.holds(Switch::Key(left)) || self.devices.holds(Switch::Key(right))
147        };
148        let ctrl = either(Key::LeftControl, Key::RightControl);
149
150        egui::Modifiers {
151            alt: either(Key::LeftAlt, Key::RightAlt),
152            ctrl,
153            shift: either(Key::LeftShift, Key::RightShift),
154            mac_cmd: false,
155            command: ctrl,
156        }
157    }
158
159    /// Where the pointer has been placed, ahead of the snapshot the game
160    /// reads, or `None` where nothing has placed it yet; only a session
161    /// reads this, for the press it passes to the UI.
162    #[cfg(all(feature = "ui", feature = "offscreen"))]
163    pub(crate) fn pointing_at(&self) -> Option<Vec2> {
164        self.devices.pointing_at()
165    }
166
167    /// Closes the snapshot that every tick, and the frame that follows
168    /// them, read from — at `at` on the run's own clock.
169    pub(crate) fn sample(&mut self, at: Duration) {
170        self.devices.sample(&mut self.pads, at, self.double_click);
171    }
172
173    /// Runs `tick` `count` times, on the edges since the last frame whose
174    /// ticks ran.
175    ///
176    /// Each call reads the same edges; no later tick reads them again.
177    pub(crate) fn ticks(&mut self, count: NonZeroU32, mut tick: impl FnMut(&Ticking<'_>)) {
178        let ticking = Ticking { input: self };
179        for _ in 0..count.get() {
180            tick(&ticking);
181        }
182    }
183
184    pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
185        self.devices.snapshot().down(self.table.resolve(action))
186    }
187
188    pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
189        self.devices.snapshot().pressed(self.table.resolve(action))
190    }
191
192    pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
193        self.devices.snapshot().released(self.table.resolve(action))
194    }
195
196    pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
197        match self.pressed(action) {
198            true => self.devices.clicks(self.table.resolve(action)),
199            false => 0,
200        }
201    }
202
203    pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
204        self.devices.snapshot().axis(self.table.resolve(action))
205    }
206
207    pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
208        self.devices.snapshot().axis2(self.table.resolve(action))
209    }
210
211    pub(crate) fn pointer(&self) -> Vec2 {
212        self.devices.snapshot().pointer()
213    }
214
215    pub(crate) fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding> {
216        self.table.resolve(action).to_vec()
217    }
218
219    /// Binds `action` to `bindings` and marks the store to be written where
220    /// that is not what the action already read through.
221    pub(crate) fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>) {
222        match self.table.rebind(action, bindings) {
223            Rebound::Changed => self.dirty = true,
224            Rebound::Unchanged => {}
225        }
226    }
227
228    /// Writes the bindings where a rebind changed one since the last call,
229    /// and nothing at all where none did.
230    pub(crate) fn flush(&mut self) {
231        if core::mem::take(&mut self.dirty) {
232            self.store.write(&self.table.written());
233        }
234    }
235
236    pub(crate) fn actuated_button(&self) -> Option<ButtonBinding> {
237        self.devices.snapshot().actuated_button()
238    }
239
240    pub(crate) fn actuated_axis(&self) -> Option<AxisBinding> {
241        self.devices.snapshot().actuated_axis()
242    }
243
244    pub(crate) fn actuated_axis2(&self) -> Option<Axis2Binding> {
245        self.devices.snapshot().actuated_axis2()
246    }
247
248    /// Whether `action` went down since the last frame whose ticks ran.
249    /// Only [`Ticking`] calls this.
250    fn pressed_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
251        self.devices.ticks().pressed(self.table.resolve(action))
252    }
253
254    /// Whether `action` came up since the last frame whose ticks ran. Only
255    /// [`Ticking`] calls this.
256    fn released_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
257        self.devices.ticks().released(self.table.resolve(action))
258    }
259
260    /// Presses in a row the press these ticks read is the last of. Only
261    /// [`Ticking`] calls this.
262    fn clicks_over_ticks<A: InputButtonAction>(&self, action: A) -> u32 {
263        match self.pressed_over_ticks(action) {
264            true => self.devices.clicks(self.table.resolve(action)),
265            false => 0,
266        }
267    }
268
269    /// Ends the ticks of one frame. Only dropping a [`Ticking`] calls this.
270    fn ticked(&mut self) {
271        self.devices.ticked();
272    }
273}
274
275/// The input the ticks of one frame read. [`Input::ticks`] is the only
276/// source of one.
277///
278/// Dropping it ends those ticks, so no later tick reads an edge these ticks
279/// already read.
280pub(crate) struct Ticking<'a> {
281    input: &'a mut Input,
282}
283
284impl Ticking<'_> {
285    pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
286        self.input.down(action)
287    }
288
289    pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
290        self.input.pressed_over_ticks(action)
291    }
292
293    pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
294        self.input.released_over_ticks(action)
295    }
296
297    pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
298        self.input.clicks_over_ticks(action)
299    }
300
301    pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
302        self.input.axis(action)
303    }
304
305    pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
306        self.input.axis2(action)
307    }
308
309    pub(crate) fn pointer(&self) -> Vec2 {
310        self.input.pointer()
311    }
312}
313
314impl Drop for Ticking<'_> {
315    fn drop(&mut self) {
316        self.input.ticked();
317    }
318}
319
320mod action;
321mod binding;
322mod cursor;
323mod pad;
324mod state;
325mod table;
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    use winit::event::{DeviceId, ElementState};
331
332    /// The interval these tests count a double click by.
333    const DOUBLE_CLICK: Duration = Duration::from_millis(400);
334
335    /// More than one tick in a frame, so a test can check what each tick
336    /// reads.
337    const TICKS: NonZeroU32 = match NonZeroU32::new(3) {
338        Some(ticks) => ticks,
339        None => NonZeroU32::MIN,
340    };
341
342    /// A run reading through the keyboard as its own vocabulary, keeping
343    /// nothing between runs.
344    fn input() -> Input {
345        Input::new::<Key>(Pads::silent(), Store::bindings(None), DOUBLE_CLICK)
346    }
347
348    /// The primary mouse button pressed, which is the one control a test can
349    /// press: `winit` keeps a keyboard event's platform field private, so no
350    /// other crate builds one.
351    fn click() -> WindowEvent {
352        WindowEvent::MouseInput {
353            device_id: DeviceId::dummy(),
354            state: ElementState::Pressed,
355            button: winit::event::MouseButton::Left,
356        }
357    }
358
359    /// What each of the `count` ticks of one frame reads for `action`.
360    fn pressed_per_tick(input: &mut Input, count: NonZeroU32, action: Key) -> Vec<bool> {
361        let mut read = Vec::new();
362        input.ticks(count, |ticking| read.push(ticking.pressed(action)));
363        read
364    }
365
366    #[test]
367    fn only_a_rebind_that_changed_something_leaves_the_store_to_write() {
368        let mut input = input();
369
370        input.rebind(Key::Space, vec![Key::Space.into()]);
371        assert!(!input.dirty, "what it read through already is no change");
372
373        input.rebind(Key::Space, vec![Key::Enter.into()]);
374        assert!(input.dirty);
375        input.flush();
376
377        assert!(!input.dirty, "so the store is written once, not per call");
378        assert_eq!(input.bindings(Key::Space), vec![Key::Enter.into()]);
379    }
380
381    #[test]
382    fn the_ticks_of_one_frame_read_an_edge_and_the_ticks_after_them_read_none() {
383        let mut input = input();
384        input.rebind(Key::Space, vec![MouseButton::Left.into()]);
385
386        input.see(&click());
387        input.sample(Duration::ZERO);
388        input.sample(Duration::ZERO);
389
390        assert_eq!(
391            pressed_per_tick(&mut input, TICKS, Key::Space),
392            [true; 3],
393            "every tick of the frame that reads the press reads it"
394        );
395
396        input.sample(Duration::ZERO);
397        assert_eq!(
398            pressed_per_tick(&mut input, TICKS, Key::Space),
399            [false; 3],
400            "and the ticks that follow read it no more"
401        );
402    }
403}