Skip to main content

crcbl_input/
patterns.rs

1//! The tap, hold and double-tap patterns: what a press *was*, decided by how
2//! long it lasted and how soon the next one came.
3//!
4//! # On the tick clock
5//!
6//! Like [`Repeat`](crate::Repeat), every pattern is timed on the clock
7//! [`ActionMap::begin_tick`] advances, never on wall time: an edge that arrives
8//! between two ticks is stamped with the clock as the earlier one left it, so a
9//! scripted input sequence fires on the same ticks every run. A pattern firing
10//! is an edge — [`ActionMap::tapped`], [`ActionMap::hold_fired`] and
11//! [`ActionMap::double_tapped`] are true for the rest of the tick it fired on,
12//! and `begin_tick` clears them.
13//!
14//! # When each one fires
15//!
16//! - A **tap** fires on the release of a press that lasted at most
17//!   [`Tap::time`].
18//! - A **hold** fires once, on the first tick a press has lasted at least
19//!   [`Hold::time`]; [`ActionMap::hold_progress`] reports how far along it is.
20//! - A **double tap** fires on the second press, when the first lasted at most
21//!   [`DoubleTap::tap_time`] and the second went down at most
22//!   [`DoubleTap::window`] after the first came up. One made with
23//!   [`DoubleTap::on_release`] fires on the second press's release instead,
24//!   and only if that press too lasted at most [`DoubleTap::tap_time`]: two
25//!   taps, not a tap and a hold.
26//!
27//! **The window's end is inclusive.** A first tap stops waiting only once
28//! `elapsed - released_at > window`, checked before the tick's press is read,
29//! so a second press landing exactly [`DoubleTap::window`] after the release
30//! still finds the first tap waiting and completes the double; a tap that
31//! waits fires on the first tick past the window, never on a tick a second
32//! press could still have claimed.
33//!
34//! # One press is one gesture
35//!
36//! The patterns on an action share its presses, and each press means one thing:
37//!
38//! - **A press that fired its hold is not a tap**, however soon it is released.
39//!   A hold is checked at the start of each tick, before that tick's events, so
40//!   a press released on the very tick it reaches [`Hold::time`] is a hold.
41//! - **A press that completed a double tap is spent**: it is neither a tap nor
42//!   a hold, and cannot start another double tap. That holds for an
43//!   [`DoubleTap::on_release`] double too, from the moment the second press
44//!   goes down: the press is the double's whether or not its release fires it.
45//! - **With a double tap attached, a tap waits.** A tap that could still be the
46//!   first half of a double tap fires only once [`DoubleTap::window`] passes
47//!   with no second press, and not at all if one comes. That is the only way
48//!   one key can carry both a single and a double tap; a game that wants the
49//!   instant release reads [`ActionMap::just_released`] instead.
50//!
51//! # What cancels a pattern
52//!
53//! Letting go before [`Hold::time`] cancels the hold, and a press after the
54//! window lets the double tap lapse. Beyond those, anything that interrupts the
55//! action cancels whatever is in flight — the press in progress and any first
56//! tap waiting for its second: a context pushed or popped
57//! ([`ActionMap::push_context`], [`ActionMap::pop_context`]), held input
58//! withheld ([`ActionMap::suppress_held`],
59//! [`ActionMap::suppress_held_action`]), the action being disabled or rebound,
60//! a pattern attached or detached, or the game asking with
61//! [`ActionMap::cancel_patterns`]. A press that was down when it was cancelled
62//! is spent until it is released, so it fires nothing, even if it outlives the
63//! change.
64//!
65//! # What "down" means per kind
66//!
67//! What the repeat pattern calls held: a button while it is down, an axis while
68//! it is off zero. Unlike the repeat pattern, a change of an axis's direction is
69//! not a new press — the patterns time the press, not where it points.
70
71use super::{ActionMap, ActionMapError, ActionValue};
72use crate::Pattern;
73use crate::repeat::Held;
74
75/// The longest a press can last and still be a tap, in seconds.
76///
77/// Below [`HOLD_TIME`], so that with both patterns at their defaults a press
78/// between the two is neither: the player who let go at the last moment gets
79/// nothing rather than whichever of the two the frame rate picked.
80pub const TAP_TIME: f32 = 0.25;
81
82/// How long a press lasts before a hold fires, in seconds.
83///
84/// The `Hold(400, …)` the input plan's binding sketch used as its example:
85/// long enough that a deliberate tap never becomes a hold.
86pub const HOLD_TIME: f32 = 0.4;
87
88/// The longest gap between the first tap's release and the second press that
89/// still makes a double tap, in seconds.
90///
91/// Also how long a single tap waits when a double tap shares its action, so it
92/// is kept short: the whole of it is latency on every single tap.
93pub const DOUBLE_TAP_WINDOW: f32 = 0.25;
94
95/// Whether `seconds` can time a pattern: a zero or non-finite time would fire
96/// on every press, or never.
97fn is_duration(seconds: f32) -> bool {
98    seconds.is_finite() && seconds > 0.0
99}
100
101/// A tap: a press released within [`Tap::time`].
102///
103/// The field is private so the only taps that exist are ones the evaluator can
104/// time.
105#[derive(Clone, Copy, Debug, PartialEq)]
106pub struct Tap {
107    time: f32,
108}
109
110impl Tap {
111    /// A tap of at most `time` seconds, or `None` unless `time` is finite and
112    /// positive.
113    #[must_use]
114    pub fn new(time: f32) -> Option<Self> {
115        is_duration(time).then_some(Self { time })
116    }
117
118    /// The longest a press can last and still be a tap, in seconds.
119    #[must_use]
120    pub const fn time(self) -> f32 {
121        self.time
122    }
123}
124
125impl Default for Tap {
126    /// A tap of at most [`TAP_TIME`].
127    fn default() -> Self {
128        Self { time: TAP_TIME }
129    }
130}
131
132/// A hold: a press that lasts [`Hold::time`].
133///
134/// The field is private so the only holds that exist are ones the evaluator can
135/// time.
136#[derive(Clone, Copy, Debug, PartialEq)]
137pub struct Hold {
138    time: f32,
139}
140
141impl Hold {
142    /// A hold of `time` seconds, or `None` unless `time` is finite and
143    /// positive.
144    #[must_use]
145    pub fn new(time: f32) -> Option<Self> {
146        is_duration(time).then_some(Self { time })
147    }
148
149    /// How long a press lasts before the hold fires, in seconds.
150    #[must_use]
151    pub const fn time(self) -> f32 {
152        self.time
153    }
154}
155
156impl Default for Hold {
157    /// A hold of [`HOLD_TIME`].
158    fn default() -> Self {
159        Self { time: HOLD_TIME }
160    }
161}
162
163/// A double tap: two presses, the first no longer than
164/// [`DoubleTap::tap_time`], the second going down within
165/// [`DoubleTap::window`] of the first coming up.
166///
167/// Fires on the second press unless made with [`DoubleTap::on_release`].
168///
169/// The fields are private so the only double taps that exist are ones the
170/// evaluator can time.
171#[derive(Clone, Copy, Debug, PartialEq)]
172pub struct DoubleTap {
173    tap_time: f32,
174    window: f32,
175    on_release: bool,
176}
177
178impl DoubleTap {
179    /// A double tap that fires on the second press, or `None` unless both times
180    /// are finite and positive.
181    #[must_use]
182    pub fn new(tap_time: f32, window: f32) -> Option<Self> {
183        (is_duration(tap_time) && is_duration(window)).then_some(Self {
184            tap_time,
185            window,
186            on_release: false,
187        })
188    }
189
190    /// This double tap, firing on the second press's **release** instead of
191    /// on the press — and only if that press lasted at most
192    /// [`DoubleTap::tap_time`], as the first had to. See the module docs.
193    #[must_use]
194    pub const fn on_release(self) -> Self {
195        Self {
196            on_release: true,
197            ..self
198        }
199    }
200
201    /// Whether it fires on the second press's release rather than the press.
202    #[must_use]
203    pub const fn fires_on_release(self) -> bool {
204        self.on_release
205    }
206
207    /// The longest the first press can last, in seconds — and, for a double
208    /// tap made with [`DoubleTap::on_release`], the second.
209    #[must_use]
210    pub const fn tap_time(self) -> f32 {
211        self.tap_time
212    }
213
214    /// The longest gap between the first release and the second press, in
215    /// seconds.
216    #[must_use]
217    pub const fn window(self) -> f32 {
218        self.window
219    }
220}
221
222impl Default for DoubleTap {
223    /// A first press of at most [`TAP_TIME`], and a gap of at most
224    /// [`DOUBLE_TAP_WINDOW`], firing on the second press.
225    fn default() -> Self {
226        Self {
227            tap_time: TAP_TIME,
228            window: DOUBLE_TAP_WINDOW,
229            on_release: false,
230        }
231    }
232}
233
234/// Where the action's current press is.
235#[derive(Clone, Copy, Debug, Default)]
236enum Press {
237    #[default]
238    Up,
239    /// Down since the clock read `at`.
240    Down { at: f64, hold_fired: bool },
241    /// Down, but it can fire nothing more: it completed a double tap, or was
242    /// cancelled while down.
243    Spent,
244    /// Down since the clock read `at`, as the second press of a
245    /// [`DoubleTap::on_release`] double: spent for everything but that double,
246    /// which its release fires if it was short enough.
247    Second { at: f64 },
248}
249
250/// A first tap waiting for its second.
251#[derive(Clone, Copy, Debug)]
252struct Pending {
253    released_at: f64,
254    /// Whether it was also a [`Tap`], owed if no second press comes.
255    tap: bool,
256}
257
258/// A slot's tap, hold and double-tap patterns and where its presses are in
259/// them.
260///
261/// The press is tracked whether or not a pattern is attached, so attaching
262/// one only ever has to cancel, never to reconstruct.
263#[derive(Clone, Debug, Default)]
264pub(crate) struct PatternState {
265    tap: Option<Tap>,
266    hold: Option<Hold>,
267    double_tap: Option<DoubleTap>,
268    press: Press,
269    pending: Option<Pending>,
270    tapped: bool,
271    hold_fired: bool,
272    double_tapped: bool,
273    /// The slot each pattern emits when it fires, in [`Pattern::ALL`]'s order
274    /// — see `emit.rs`.
275    pub(crate) emits: [Option<usize>; Pattern::ALL.len()],
276}
277
278impl PatternState {
279    /// What has fired on this tick, in [`Pattern::ALL`]'s order.
280    pub(crate) const fn fired(&self) -> [bool; Pattern::ALL.len()] {
281        [self.tapped, self.hold_fired, self.double_tapped]
282    }
283
284    /// Queue the slot of every pattern that fired since `before` was read
285    /// from [`PatternState::fired`] and emits one.
286    pub(crate) fn queue_emits(&self, before: [bool; Pattern::ALL.len()], queue: &mut Vec<usize>) {
287        for ((was, now), target) in before.into_iter().zip(self.fired()).zip(self.emits) {
288            if now && !was {
289                queue.extend(target);
290            }
291        }
292    }
293
294    /// Clear what fired on the last tick — [`ActionMap::begin_tick`]'s share.
295    pub(crate) fn clear_fired(&mut self) {
296        self.tapped = false;
297        self.hold_fired = false;
298        self.double_tapped = false;
299    }
300
301    /// Drop everything in flight: the press in progress is spent until it is
302    /// released, and a first tap stops waiting. What already fired this tick
303    /// stays fired.
304    pub(crate) fn cancel(&mut self) {
305        if !matches!(self.press, Press::Up) {
306            self.press = Press::Spent;
307        }
308        self.pending = None;
309    }
310
311    /// Back to idle, as the action's value is: nothing down, nothing fired.
312    pub(crate) fn reset(&mut self) {
313        self.press = Press::Up;
314        self.pending = None;
315        self.clear_fired();
316    }
317
318    /// Advance the patterns against the slot's freshly resolved value.
319    pub(crate) fn update(&mut self, value: &ActionValue, elapsed: f64) {
320        if let (Some(pending), Some(double_tap)) = (self.pending, self.double_tap)
321            && elapsed - pending.released_at > f64::from(double_tap.window)
322        {
323            // No second press in time: the first stands alone.
324            self.pending = None;
325            self.tapped |= pending.tap;
326        }
327
328        let down = Held::of(value).is_some();
329        match (self.press, down) {
330            (Press::Up, true) => {
331                // `pending` is only ever still set inside its window, and only
332                // while a double tap is attached.
333                self.press = if self.pending.take().is_some() {
334                    if self.double_tap.is_some_and(DoubleTap::fires_on_release) {
335                        Press::Second { at: elapsed }
336                    } else {
337                        self.double_tapped = true;
338                        Press::Spent
339                    }
340                } else {
341                    Press::Down {
342                        at: elapsed,
343                        hold_fired: false,
344                    }
345                };
346            }
347            (
348                Press::Down {
349                    at,
350                    hold_fired: false,
351                },
352                true,
353            ) => {
354                if self
355                    .hold
356                    .is_some_and(|hold| elapsed - at >= f64::from(hold.time))
357                {
358                    self.hold_fired = true;
359                    self.press = Press::Down {
360                        at,
361                        hold_fired: true,
362                    };
363                }
364            }
365            (Press::Down { at, hold_fired }, false) => {
366                self.press = Press::Up;
367                if hold_fired {
368                    return;
369                }
370                let lasted = elapsed - at;
371                let tap = self.tap.is_some_and(|tap| lasted <= f64::from(tap.time));
372                let first_of_two = self
373                    .double_tap
374                    .is_some_and(|double_tap| lasted <= f64::from(double_tap.tap_time));
375                if first_of_two {
376                    self.pending = Some(Pending {
377                        released_at: elapsed,
378                        tap,
379                    });
380                } else {
381                    self.tapped |= tap;
382                }
383            }
384            (Press::Second { at }, false) => {
385                self.press = Press::Up;
386                self.double_tapped |= self
387                    .double_tap
388                    .is_some_and(|double_tap| elapsed - at <= f64::from(double_tap.tap_time));
389            }
390            (Press::Spent, false) => self.press = Press::Up,
391            (Press::Up, false)
392            | (Press::Down { .. } | Press::Spent | Press::Second { .. }, true) => {}
393        }
394    }
395
396    /// How far the press in progress is toward its hold, `0.0` to `1.0`.
397    fn hold_progress(&self, elapsed: f64) -> f32 {
398        match (self.press, self.hold) {
399            (
400                Press::Down {
401                    hold_fired: true, ..
402                },
403                _,
404            ) => 1.0,
405            (Press::Down { at, .. }, Some(hold)) => {
406                ((elapsed - at) / f64::from(hold.time)).min(1.0) as f32
407            }
408            _ => 0.0,
409        }
410    }
411}
412
413impl ActionMap {
414    /// Attach a tap to an action, or detach it with `None`.
415    ///
416    /// Attaching or detaching cancels whatever the action's patterns had in
417    /// flight, so a press already down when this is called fires nothing — see
418    /// the module docs.
419    ///
420    /// # Errors
421    /// [`ActionMapError::UnknownAction`] if nothing with that name is declared.
422    pub fn set_tap(&mut self, name: &str, tap: Option<Tap>) -> Result<(), ActionMapError> {
423        let patterns = self.patterns_mut(name)?;
424        patterns.tap = tap;
425        patterns.cancel();
426        Ok(())
427    }
428
429    /// Attach a hold to an action, or detach it with `None`. Cancels what was
430    /// in flight, as [`ActionMap::set_tap`] does.
431    ///
432    /// # Errors
433    /// [`ActionMapError::UnknownAction`] if nothing with that name is declared.
434    pub fn set_hold(&mut self, name: &str, hold: Option<Hold>) -> Result<(), ActionMapError> {
435        let patterns = self.patterns_mut(name)?;
436        patterns.hold = hold;
437        patterns.cancel();
438        Ok(())
439    }
440
441    /// Attach a double tap to an action, or detach it with `None`. Cancels what
442    /// was in flight, as [`ActionMap::set_tap`] does.
443    ///
444    /// # Errors
445    /// [`ActionMapError::UnknownAction`] if nothing with that name is declared.
446    pub fn set_double_tap(
447        &mut self,
448        name: &str,
449        double_tap: Option<DoubleTap>,
450    ) -> Result<(), ActionMapError> {
451        let patterns = self.patterns_mut(name)?;
452        patterns.double_tap = double_tap;
453        patterns.cancel();
454        Ok(())
455    }
456
457    /// Cancel whatever an action's patterns have in flight, exactly as a
458    /// context push does: the press in progress is spent until it is released,
459    /// and a first tap waiting for its second is dropped. The action's value is
460    /// untouched — a held button stays held — and with nothing in flight this
461    /// changes nothing.
462    ///
463    /// For a press the game decides was something else: a Z held while the
464    /// wheel turns was a Z+wheel, not the first half of a Z double tap.
465    ///
466    /// # Errors
467    /// [`ActionMapError::UnknownAction`] if nothing with that name is declared.
468    pub fn cancel_patterns(&mut self, name: &str) -> Result<(), ActionMapError> {
469        self.patterns_mut(name)?.cancel();
470        Ok(())
471    }
472
473    /// The tap attached to an action, if any.
474    #[must_use]
475    pub fn tap(&self, name: &str) -> Option<Tap> {
476        self.patterns(name)?.tap
477    }
478
479    /// The hold attached to an action, if any.
480    #[must_use]
481    pub fn hold(&self, name: &str) -> Option<Hold> {
482        self.patterns(name)?.hold
483    }
484
485    /// The double tap attached to an action, if any.
486    #[must_use]
487    pub fn double_tap(&self, name: &str) -> Option<DoubleTap> {
488        self.patterns(name)?.double_tap
489    }
490
491    /// Whether an action's tap fired on this tick. `false` for an action with
492    /// no tap, or none declared.
493    #[must_use]
494    pub fn tapped(&self, name: &str) -> bool {
495        self.patterns(name).is_some_and(|patterns| patterns.tapped)
496    }
497
498    /// Whether an action's hold fired on this tick. `false` for an action with
499    /// no hold, or none declared.
500    #[must_use]
501    pub fn hold_fired(&self, name: &str) -> bool {
502        self.patterns(name)
503            .is_some_and(|patterns| patterns.hold_fired)
504    }
505
506    /// Whether an action's double tap fired on this tick. `false` for an
507    /// action with no double tap, or none declared.
508    #[must_use]
509    pub fn double_tapped(&self, name: &str) -> bool {
510        self.patterns(name)
511            .is_some_and(|patterns| patterns.double_tapped)
512    }
513
514    /// How far the press in progress is toward the action's hold, from `0.0`
515    /// at the press to `1.0` once it fired — the fill of a hold-to-interact
516    /// ring. `0.0` while nothing is down, for a spent or cancelled press, for
517    /// an action with no hold, or none declared.
518    #[must_use]
519    pub fn hold_progress(&self, name: &str) -> f32 {
520        self.patterns(name)
521            .map_or(0.0, |patterns| patterns.hold_progress(self.elapsed))
522    }
523
524    fn patterns(&self, name: &str) -> Option<&PatternState> {
525        let &idx = self.name_to_idx.get(name)?;
526        Some(&self.slots[idx].patterns)
527    }
528
529    fn patterns_mut(&mut self, name: &str) -> Result<&mut PatternState, ActionMapError> {
530        let Some(&idx) = self.name_to_idx.get(name) else {
531            return Err(ActionMapError::UnknownAction(name.to_owned()));
532        };
533        Ok(&mut self.slots[idx].patterns)
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540    use crate::{ActionDecl, ActionKind, Binding, Repeat};
541    use crcbl_core::input::KeyCode;
542
543    /// Sixteen ticks a second: `0.0625` is exact in binary, so a clock summed
544    /// from it lands on every threshold below exactly, and a boundary test
545    /// checks the comparison rather than rounding.
546    const TICK: f32 = 0.0625;
547
548    /// A quarter second: four ticks.
549    const QUARTER: f32 = 0.25;
550
551    const KEY: KeyCode = KeyCode::Space;
552
553    fn map_with(attach: impl FnOnce(&mut ActionMap)) -> ActionMap {
554        let mut map = ActionMap::new();
555        map.declare(ActionDecl {
556            name: "act".to_owned(),
557            kind: ActionKind::Button,
558            bindings: vec![Binding::Key(KEY)],
559        });
560        attach(&mut map);
561        map
562    }
563
564    fn tap(time: f32) -> Option<Tap> {
565        Some(Tap::new(time).expect("a runnable tap"))
566    }
567
568    fn hold(time: f32) -> Option<Hold> {
569        Some(Hold::new(time).expect("a runnable hold"))
570    }
571
572    fn double_tap(tap_time: f32, window: f32) -> Option<DoubleTap> {
573        Some(DoubleTap::new(tap_time, window).expect("a runnable double tap"))
574    }
575
576    /// The ticks each pattern fired on.
577    #[derive(Debug, Default, PartialEq)]
578    struct Fires {
579        tap: Vec<u32>,
580        hold: Vec<u32>,
581        double_tap: Vec<u32>,
582    }
583
584    /// Run `ticks` ticks, pressing (`true`) or releasing the key on the ticks
585    /// `edges` names, just after that tick begins.
586    fn fires(map: &mut ActionMap, edges: &[(u32, bool)], ticks: u32) -> Fires {
587        let mut fires = Fires::default();
588        for tick in 0..ticks {
589            map.begin_tick(TICK);
590            for &(_, down) in edges.iter().filter(|(at, _)| *at == tick) {
591                map.key_event(KEY, down);
592            }
593            if map.tapped("act") {
594                fires.tap.push(tick);
595            }
596            if map.hold_fired("act") {
597                fires.hold.push(tick);
598            }
599            if map.double_tapped("act") {
600                fires.double_tap.push(tick);
601            }
602        }
603        fires
604    }
605
606    fn taps(tap: Vec<u32>) -> Fires {
607        Fires {
608            tap,
609            ..Fires::default()
610        }
611    }
612
613    /// **A tap fires on the release, up to and including its time.**
614    #[test]
615    fn a_tap_fires_on_a_release_within_its_time() {
616        for (released, expected) in [(3, vec![3]), (4, vec![4]), (5, vec![])] {
617            let mut map = map_with(|map| map.set_tap("act", tap(QUARTER)).expect("declared"));
618            assert_eq!(
619                fires(&mut map, &[(0, true), (released, false)], 8),
620                taps(expected),
621                "released after {released} ticks",
622            );
623        }
624    }
625
626    /// **A hold fires once, on the tick the press reaches its time**, and a
627    /// release before then cancels it.
628    #[test]
629    fn a_hold_fires_once_at_its_time_and_a_release_cancels_it() {
630        let mut map = map_with(|map| map.set_hold("act", hold(QUARTER)).expect("declared"));
631        let held = fires(&mut map, &[(0, true)], 10);
632        assert_eq!(
633            held,
634            Fires {
635                hold: vec![4],
636                ..Fires::default()
637            },
638        );
639
640        let mut map = map_with(|map| map.set_hold("act", hold(QUARTER)).expect("declared"));
641        let let_go = fires(&mut map, &[(0, true), (3, false)], 10);
642        assert_eq!(let_go, Fires::default(), "released one tick short");
643    }
644
645    /// The progress a hold-to-interact ring fills with: the fraction of the
646    /// hold time, `1.0` from the fire, and back to `0.0` on release.
647    #[test]
648    fn hold_progress_fills_to_one_and_empties_on_release() {
649        let mut map = map_with(|map| map.set_hold("act", hold(QUARTER)).expect("declared"));
650        map.begin_tick(TICK);
651        assert_eq!(map.hold_progress("act"), 0.0, "nothing down");
652        map.key_event(KEY, true);
653        let mut progress = vec![map.hold_progress("act")];
654        for _ in 0..5 {
655            map.begin_tick(TICK);
656            progress.push(map.hold_progress("act"));
657        }
658        assert_eq!(progress, [0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
659        map.key_event(KEY, false);
660        assert_eq!(map.hold_progress("act"), 0.0);
661        assert_eq!(map.hold_progress("nope"), 0.0);
662    }
663
664    /// **A press that fired its hold is not a tap**, even when the tap's time
665    /// is the longer of the two and the release comes inside it.
666    #[test]
667    fn a_press_that_held_does_not_tap() {
668        let mut map = map_with(|map| {
669            map.set_tap("act", tap(0.5)).expect("declared");
670            map.set_hold("act", hold(QUARTER)).expect("declared");
671        });
672        assert_eq!(
673            fires(&mut map, &[(0, true), (6, false)], 10),
674            Fires {
675                hold: vec![4],
676                ..Fires::default()
677            },
678            "released at 0.375 s, inside the tap's 0.5 s",
679        );
680    }
681
682    /// Released on the very tick the hold fires: the hold wins, since it is
683    /// checked as the tick begins.
684    #[test]
685    fn a_release_on_the_hold_tick_is_a_hold() {
686        let mut map = map_with(|map| {
687            map.set_tap("act", tap(QUARTER)).expect("declared");
688            map.set_hold("act", hold(QUARTER)).expect("declared");
689        });
690        assert_eq!(
691            fires(&mut map, &[(0, true), (4, false)], 8),
692            Fires {
693                hold: vec![4],
694                ..Fires::default()
695            },
696        );
697    }
698
699    /// Both patterns at their defaults: a short press taps, a long one holds,
700    /// and one between the two is neither.
701    #[test]
702    fn tap_and_hold_split_presses_by_length() {
703        for (released, expected) in [
704            (3, taps(vec![3])),
705            (5, Fires::default()),
706            (
707                8,
708                Fires {
709                    hold: vec![7],
710                    ..Fires::default()
711                },
712            ),
713        ] {
714            let mut map = map_with(|map| {
715                map.set_tap("act", Some(Tap::default())).expect("declared");
716                map.set_hold("act", Some(Hold::default()))
717                    .expect("declared");
718            });
719            assert_eq!(
720                fires(&mut map, &[(0, true), (released, false)], 12),
721                expected,
722                "released after {released} ticks",
723            );
724        }
725    }
726
727    /// **A double tap fires on the second press, up to and including the end
728    /// of its window**, and not after.
729    #[test]
730    fn a_double_tap_fires_on_a_second_press_inside_the_window() {
731        for (gap, expected) in [(3, vec![5]), (4, vec![6]), (5, vec![])] {
732            let mut map = map_with(|map| {
733                map.set_double_tap("act", double_tap(QUARTER, QUARTER))
734                    .expect("declared");
735            });
736            let second = 2 + gap;
737            let fired = fires(
738                &mut map,
739                &[(0, true), (2, false), (second, true), (second + 1, false)],
740                14,
741            );
742            assert_eq!(
743                fired,
744                Fires {
745                    double_tap: expected,
746                    ..Fires::default()
747                },
748                "second press {gap} ticks after the first release",
749            );
750        }
751    }
752
753    /// A first press longer than the double tap's tap time starts nothing.
754    #[test]
755    fn a_long_first_press_does_not_start_a_double_tap() {
756        let mut map = map_with(|map| {
757            map.set_double_tap("act", double_tap(QUARTER, QUARTER))
758                .expect("declared");
759        });
760        let fired = fires(&mut map, &[(0, true), (5, false), (6, true)], 10);
761        assert_eq!(fired, Fires::default());
762    }
763
764    /// **With a double tap attached, a single tap waits out the window**, and
765    /// a double tap swallows the tap it began with; the double's second press
766    /// is spent, so its release does not tap either.
767    #[test]
768    fn a_tap_waits_for_the_double_tap_window() {
769        let attach = |map: &mut ActionMap| {
770            map.set_tap("act", tap(QUARTER)).expect("declared");
771            map.set_double_tap("act", double_tap(QUARTER, QUARTER))
772                .expect("declared");
773        };
774
775        let mut map = map_with(attach);
776        let single = fires(&mut map, &[(0, true), (2, false)], 10);
777        assert_eq!(
778            single,
779            taps(vec![7]),
780            "released at tick 2, the window closes after tick 6",
781        );
782
783        let mut map = map_with(attach);
784        let double = fires(
785            &mut map,
786            &[(0, true), (2, false), (4, true), (5, false)],
787            12,
788        );
789        assert_eq!(
790            double,
791            Fires {
792                double_tap: vec![4],
793                ..Fires::default()
794            },
795        );
796    }
797
798    /// **Pushing a context over a held press cancels it**: the press lost to
799    /// the context is not a tap, and the hold never fires.
800    #[test]
801    fn a_context_push_cancels_the_press_in_progress() {
802        let mut map = map_with(|map| {
803            map.set_tap("act", tap(QUARTER)).expect("declared");
804            map.set_hold("act", hold(QUARTER)).expect("declared");
805        });
806        map.declare_in(
807            "menu",
808            ActionDecl {
809                name: "confirm".to_owned(),
810                kind: ActionKind::Button,
811                bindings: vec![Binding::Key(KEY)],
812            },
813        );
814        map.begin_tick(TICK);
815        map.key_event(KEY, true);
816        map.begin_tick(TICK);
817        map.push_context("menu").expect("declared");
818        assert!(!map.button_held("act"), "the menu took the key");
819        assert!(!map.tapped("act"), "a press taken away is not a tap");
820        for _ in 0..6 {
821            map.begin_tick(TICK);
822            assert!(!map.hold_fired("act"));
823        }
824    }
825
826    /// A context that takes nothing from the action still cancels its press:
827    /// the press outlives the change, spent, and fires neither tap nor hold.
828    #[test]
829    fn a_context_switch_spends_a_press_it_does_not_take() {
830        let mut map = map_with(|map| {
831            map.set_tap("act", tap(QUARTER)).expect("declared");
832            map.set_hold("act", hold(QUARTER)).expect("declared");
833        });
834        map.declare_in(
835            "menu",
836            ActionDecl {
837                name: "other".to_owned(),
838                kind: ActionKind::Button,
839                bindings: vec![Binding::Key(KeyCode::Enter)],
840            },
841        );
842        map.begin_tick(TICK);
843        map.key_event(KEY, true);
844        map.push_context("menu").expect("declared");
845        assert!(map.button_held("act"), "the key is still the action's");
846        let mut fired = false;
847        for _ in 0..6 {
848            map.begin_tick(TICK);
849            fired |= map.hold_fired("act");
850        }
851        map.key_event(KEY, false);
852        fired |= map.tapped("act");
853        assert!(!fired, "neither a hold nor a tap");
854    }
855
856    /// A first tap waiting for its second is dropped by a context switch.
857    #[test]
858    fn a_context_switch_drops_a_waiting_first_tap() {
859        let mut map = map_with(|map| {
860            map.set_double_tap("act", double_tap(QUARTER, QUARTER))
861                .expect("declared");
862        });
863        map.declare_in(
864            "menu",
865            ActionDecl {
866                name: "other".to_owned(),
867                kind: ActionKind::Button,
868                bindings: vec![Binding::Key(KeyCode::Enter)],
869            },
870        );
871        map.begin_tick(TICK);
872        map.key_event(KEY, true);
873        map.begin_tick(TICK);
874        map.key_event(KEY, false);
875        map.push_context("menu").expect("declared");
876        map.pop_context("menu").expect("on top");
877        map.begin_tick(TICK);
878        map.key_event(KEY, true);
879        assert!(!map.double_tapped("act"));
880    }
881
882    /// Disabling the action mid-press cancels it: a key let go while the
883    /// action was disabled is no tap once it is enabled again.
884    #[test]
885    fn disabling_cancels_the_press() {
886        let mut map = map_with(|map| map.set_tap("act", tap(QUARTER)).expect("declared"));
887        map.begin_tick(TICK);
888        map.key_event(KEY, true);
889        map.set_enabled("act", false);
890        map.begin_tick(TICK);
891        map.key_event(KEY, false);
892        map.set_enabled("act", true);
893        map.begin_tick(TICK);
894        assert!(!map.tapped("act"), "released while disabled");
895    }
896
897    /// Attaching to a held action does not fire on its release.
898    #[test]
899    fn attaching_mid_press_fires_nothing() {
900        let mut map = map_with(|_| {});
901        map.begin_tick(TICK);
902        map.key_event(KEY, true);
903        map.set_tap("act", tap(QUARTER)).expect("declared");
904        map.begin_tick(TICK);
905        map.key_event(KEY, false);
906        assert!(!map.tapped("act"));
907        assert_eq!(map.tap("act"), tap(QUARTER));
908        assert_eq!(map.hold("act"), None);
909        assert_eq!(map.double_tap("act"), None);
910        assert_eq!(
911            map.set_hold("nope", None),
912            Err(ActionMapError::UnknownAction("nope".to_owned())),
913        );
914    }
915
916    /// Every fire is an edge: gone on the next tick.
917    #[test]
918    fn a_fire_lasts_one_tick() {
919        let mut map = map_with(|map| map.set_tap("act", tap(QUARTER)).expect("declared"));
920        map.begin_tick(TICK);
921        map.key_event(KEY, true);
922        map.key_event(KEY, false);
923        assert!(map.tapped("act"));
924        map.begin_tick(TICK);
925        assert!(!map.tapped("act"));
926    }
927
928    /// The ticks a repeat on the action pulsed on, over a tap, a double tap
929    /// and a long hold.
930    fn repeat_pulses(map: &mut ActionMap) -> Vec<u32> {
931        let edges = [(0, true), (1, false), (3, true), (4, false), (6, true)];
932        let mut pulses = Vec::new();
933        for tick in 0..24 {
934            map.begin_tick(TICK);
935            for &(_, down) in edges.iter().filter(|(at, _)| *at == tick) {
936                map.key_event(KEY, down);
937            }
938            if map.repeated("act") {
939                pulses.push(tick);
940            }
941        }
942        pulses
943    }
944
945    /// **The repeat schedule is untouched by patterns on the same action.**
946    #[test]
947    fn patterns_leave_repeat_alone() {
948        let mut bare = map_with(|map| map.set_repeat("act", Some(Repeat::UI)).expect("declared"));
949        let mut patterned = map_with(|map| {
950            map.set_repeat("act", Some(Repeat::UI)).expect("declared");
951            map.set_tap("act", Some(Tap::default())).expect("declared");
952            map.set_hold("act", Some(Hold::default()))
953                .expect("declared");
954            map.set_double_tap("act", Some(DoubleTap::default()))
955                .expect("declared");
956        });
957        let expected = repeat_pulses(&mut bare);
958        // Each press pulses; the last is held from tick 6, so its first repeat
959        // is 0.5 s / 0.0625 s = 8 ticks later.
960        assert_eq!(expected[..4], [0, 3, 6, 14]);
961        assert_eq!(repeat_pulses(&mut patterned), expected);
962    }
963
964    /// **`cancel_patterns` with nothing in flight changes nothing**: a press
965    /// on the same tick taps as if it had never been called.
966    #[test]
967    fn cancel_patterns_with_nothing_in_flight_spends_nothing() {
968        let mut map = map_with(|map| map.set_tap("act", tap(QUARTER)).expect("declared"));
969        map.begin_tick(TICK);
970        map.cancel_patterns("act").expect("declared");
971        map.key_event(KEY, true);
972        map.begin_tick(TICK);
973        map.key_event(KEY, false);
974        assert!(map.tapped("act"));
975        assert_eq!(
976            map.cancel_patterns("nope"),
977            Err(ActionMapError::UnknownAction("nope".to_owned())),
978        );
979    }
980
981    /// **`cancel_patterns` drops a first tap waiting for its second**, as a
982    /// context switch does: the next press starts a new gesture instead.
983    #[test]
984    fn cancel_patterns_drops_a_waiting_first_tap() {
985        let mut map = map_with(|map| {
986            map.set_tap("act", tap(QUARTER)).expect("declared");
987            map.set_double_tap("act", double_tap(QUARTER, QUARTER))
988                .expect("declared");
989        });
990        map.begin_tick(TICK);
991        map.key_event(KEY, true);
992        map.begin_tick(TICK);
993        map.key_event(KEY, false);
994        map.cancel_patterns("act").expect("declared");
995        let fired = fires(&mut map, &[(0, true)], 8);
996        assert_eq!(
997            fired,
998            Fires::default(),
999            "neither the double nor the dropped tap fires"
1000        );
1001    }
1002
1003    /// **`cancel_patterns` spends the press in progress** until it is
1004    /// released: no hold, no tap, and the button itself stays held.
1005    #[test]
1006    fn cancel_patterns_spends_the_press_in_progress() {
1007        let mut map = map_with(|map| {
1008            map.set_tap("act", tap(QUARTER)).expect("declared");
1009            map.set_hold("act", hold(QUARTER)).expect("declared");
1010        });
1011        map.begin_tick(TICK);
1012        map.key_event(KEY, true);
1013        map.cancel_patterns("act").expect("declared");
1014        assert!(map.button_held("act"), "only the patterns are cancelled");
1015        let mut fired = false;
1016        for _ in 0..6 {
1017            map.begin_tick(TICK);
1018            fired |= map.hold_fired("act");
1019        }
1020        map.key_event(KEY, false);
1021        fired |= map.tapped("act");
1022        assert!(!fired, "neither a hold nor a tap");
1023        assert_eq!(fires(&mut map, &[(0, true), (1, false)], 2), taps(vec![1]));
1024    }
1025
1026    fn released_double(tap_time: f32, window: f32) -> Option<DoubleTap> {
1027        double_tap(tap_time, window).map(DoubleTap::on_release)
1028    }
1029
1030    fn doubles(double_tap: Vec<u32>) -> Fires {
1031        Fires {
1032            double_tap,
1033            ..Fires::default()
1034        }
1035    }
1036
1037    /// **An on-release double tap fires when the second press comes up**, not
1038    /// when it goes down, and keeps the window's inclusive end.
1039    #[test]
1040    fn an_on_release_double_tap_fires_on_the_second_release() {
1041        for (gap, expected) in [(3, vec![7]), (4, vec![8]), (5, vec![])] {
1042            let mut map = map_with(|map| {
1043                map.set_double_tap("act", released_double(QUARTER, QUARTER))
1044                    .expect("declared");
1045            });
1046            let second = 2 + gap;
1047            let fired = fires(
1048                &mut map,
1049                &[(0, true), (2, false), (second, true), (second + 2, false)],
1050                16,
1051            );
1052            assert_eq!(
1053                fired,
1054                doubles(expected),
1055                "second press {gap} ticks after the first release"
1056            );
1057        }
1058    }
1059
1060    /// **The second press must be a tap too**: up to and including the tap
1061    /// time it fires, one tick longer it is spent and fires nothing.
1062    #[test]
1063    fn an_on_release_double_tap_needs_a_short_second_press() {
1064        for (held, expected) in [(4, vec![7]), (5, vec![])] {
1065            let mut map = map_with(|map| {
1066                map.set_tap("act", tap(QUARTER)).expect("declared");
1067                map.set_hold("act", hold(QUARTER)).expect("declared");
1068                map.set_double_tap("act", released_double(QUARTER, QUARTER))
1069                    .expect("declared");
1070            });
1071            let fired = fires(
1072                &mut map,
1073                &[(0, true), (2, false), (3, true), (3 + held, false)],
1074                14,
1075            );
1076            assert_eq!(
1077                fired,
1078                doubles(expected),
1079                "second press held {held} ticks: never a hold or a tap either",
1080            );
1081        }
1082    }
1083
1084    /// Cancelled between its press and its release, an on-release double tap
1085    /// does not fire.
1086    #[test]
1087    fn a_cancelled_second_press_does_not_fire_on_release() {
1088        let mut map = map_with(|map| {
1089            map.set_double_tap("act", released_double(QUARTER, QUARTER))
1090                .expect("declared");
1091        });
1092        map.begin_tick(TICK);
1093        map.key_event(KEY, true);
1094        map.begin_tick(TICK);
1095        map.key_event(KEY, false);
1096        map.begin_tick(TICK);
1097        map.key_event(KEY, true);
1098        map.cancel_patterns("act").expect("declared");
1099        map.begin_tick(TICK);
1100        map.key_event(KEY, false);
1101        assert!(!map.double_tapped("act"));
1102    }
1103
1104    /// The default fires on the press, as it always did, and `on_release`
1105    /// changes when it fires and nothing else.
1106    #[test]
1107    fn a_double_tap_fires_on_the_press_unless_asked_otherwise() {
1108        let press = DoubleTap::new(QUARTER, 0.5).expect("a runnable double tap");
1109        assert!(!press.fires_on_release());
1110        assert!(!DoubleTap::default().fires_on_release());
1111        let release = press.on_release();
1112        assert!(release.fires_on_release());
1113        assert_eq!(
1114            (release.tap_time(), release.window()),
1115            (QUARTER, 0.5),
1116            "same times"
1117        );
1118        assert_ne!(press, release);
1119    }
1120
1121    /// A pattern that could not be timed is not constructible.
1122    #[test]
1123    fn only_timeable_patterns_exist() {
1124        for time in [0.0, -0.1, f32::NAN, f32::INFINITY] {
1125            assert_eq!(Tap::new(time), None, "{time}");
1126            assert_eq!(Hold::new(time), None, "{time}");
1127            assert_eq!(DoubleTap::new(time, QUARTER), None, "{time}");
1128            assert_eq!(DoubleTap::new(QUARTER, time), None, "{time}");
1129        }
1130        assert_eq!(Tap::new(TAP_TIME), Some(Tap::default()));
1131        assert_eq!(Hold::new(HOLD_TIME), Some(Hold::default()));
1132        assert_eq!(
1133            DoubleTap::new(TAP_TIME, DOUBLE_TAP_WINDOW),
1134            Some(DoubleTap::default()),
1135        );
1136    }
1137}