1use super::{ActionMap, ActionMapError, ActionValue};
72use crate::Pattern;
73use crate::repeat::Held;
74
75pub const TAP_TIME: f32 = 0.25;
81
82pub const HOLD_TIME: f32 = 0.4;
87
88pub const DOUBLE_TAP_WINDOW: f32 = 0.25;
94
95fn is_duration(seconds: f32) -> bool {
98 seconds.is_finite() && seconds > 0.0
99}
100
101#[derive(Clone, Copy, Debug, PartialEq)]
106pub struct Tap {
107 time: f32,
108}
109
110impl Tap {
111 #[must_use]
114 pub fn new(time: f32) -> Option<Self> {
115 is_duration(time).then_some(Self { time })
116 }
117
118 #[must_use]
120 pub const fn time(self) -> f32 {
121 self.time
122 }
123}
124
125impl Default for Tap {
126 fn default() -> Self {
128 Self { time: TAP_TIME }
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq)]
137pub struct Hold {
138 time: f32,
139}
140
141impl Hold {
142 #[must_use]
145 pub fn new(time: f32) -> Option<Self> {
146 is_duration(time).then_some(Self { time })
147 }
148
149 #[must_use]
151 pub const fn time(self) -> f32 {
152 self.time
153 }
154}
155
156impl Default for Hold {
157 fn default() -> Self {
159 Self { time: HOLD_TIME }
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq)]
172pub struct DoubleTap {
173 tap_time: f32,
174 window: f32,
175 on_release: bool,
176}
177
178impl DoubleTap {
179 #[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 #[must_use]
194 pub const fn on_release(self) -> Self {
195 Self {
196 on_release: true,
197 ..self
198 }
199 }
200
201 #[must_use]
203 pub const fn fires_on_release(self) -> bool {
204 self.on_release
205 }
206
207 #[must_use]
210 pub const fn tap_time(self) -> f32 {
211 self.tap_time
212 }
213
214 #[must_use]
217 pub const fn window(self) -> f32 {
218 self.window
219 }
220}
221
222impl Default for DoubleTap {
223 fn default() -> Self {
226 Self {
227 tap_time: TAP_TIME,
228 window: DOUBLE_TAP_WINDOW,
229 on_release: false,
230 }
231 }
232}
233
234#[derive(Clone, Copy, Debug, Default)]
236enum Press {
237 #[default]
238 Up,
239 Down { at: f64, hold_fired: bool },
241 Spent,
244 Second { at: f64 },
248}
249
250#[derive(Clone, Copy, Debug)]
252struct Pending {
253 released_at: f64,
254 tap: bool,
256}
257
258#[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 pub(crate) emits: [Option<usize>; Pattern::ALL.len()],
276}
277
278impl PatternState {
279 pub(crate) const fn fired(&self) -> [bool; Pattern::ALL.len()] {
281 [self.tapped, self.hold_fired, self.double_tapped]
282 }
283
284 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 pub(crate) fn clear_fired(&mut self) {
296 self.tapped = false;
297 self.hold_fired = false;
298 self.double_tapped = false;
299 }
300
301 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 pub(crate) fn reset(&mut self) {
313 self.press = Press::Up;
314 self.pending = None;
315 self.clear_fired();
316 }
317
318 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 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 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 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 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 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 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 pub fn cancel_patterns(&mut self, name: &str) -> Result<(), ActionMapError> {
469 self.patterns_mut(name)?.cancel();
470 Ok(())
471 }
472
473 #[must_use]
475 pub fn tap(&self, name: &str) -> Option<Tap> {
476 self.patterns(name)?.tap
477 }
478
479 #[must_use]
481 pub fn hold(&self, name: &str) -> Option<Hold> {
482 self.patterns(name)?.hold
483 }
484
485 #[must_use]
487 pub fn double_tap(&self, name: &str) -> Option<DoubleTap> {
488 self.patterns(name)?.double_tap
489 }
490
491 #[must_use]
494 pub fn tapped(&self, name: &str) -> bool {
495 self.patterns(name).is_some_and(|patterns| patterns.tapped)
496 }
497
498 #[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 #[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 #[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 const TICK: f32 = 0.0625;
547
548 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 #[derive(Debug, Default, PartialEq)]
578 struct Fires {
579 tap: Vec<u32>,
580 hold: Vec<u32>,
581 double_tap: Vec<u32>,
582 }
583
584 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 assert_eq!(expected[..4], [0, 3, 6, 14]);
961 assert_eq!(repeat_pulses(&mut patterned), expected);
962 }
963
964 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}