smart_keymap/key/
chorded.rs

1#![doc = include_str!("doc_de_chorded.md")]
2
3use core::fmt::Debug;
4
5use serde::Deserialize;
6
7use crate::{input, key};
8
9pub use crate::init::MAX_CHORDS;
10
11/// The maximum number of keys in a chord.
12const MAX_CHORD_SIZE: usize = 2;
13
14/// Chords are defined by an (unordered) set of indices into the keymap.
15#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "std", serde(untagged))]
17pub enum ChordIndices {
18    /// A chord from two keys.
19    Chord2(u16, u16),
20}
21
22impl ChordIndices {
23    /// Returns whether the given index is part of the chord.
24    pub fn has_index(&self, index: u16) -> bool {
25        match self {
26            ChordIndices::Chord2(i0, i1) => i0 == &index || i1 == &index,
27        }
28    }
29
30    /// Returns whether the chord is satisfied by the given indices.
31    pub fn is_satisfied_by(&self, indices: &[u16]) -> bool {
32        match self {
33            ChordIndices::Chord2(i0, i1) => indices.contains(i0) && indices.contains(i1),
34        }
35    }
36}
37
38/// Chord definitions.
39#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
40pub struct Config {
41    /// The timeout (in number of milliseconds) for a chorded key to resolve.
42    ///
43    /// (Resolves as passthrough key if no chord is satisfied).
44    #[serde(default = "default_timeout")]
45    pub timeout: u16,
46
47    /// The keymap chords.
48    #[serde(default = "default_chords")]
49    #[serde(deserialize_with = "deserialize_chords")]
50    pub chords: [Option<ChordIndices>; MAX_CHORDS],
51}
52
53fn default_timeout() -> u16 {
54    DEFAULT_CONFIG.timeout
55}
56
57fn default_chords() -> [Option<ChordIndices>; MAX_CHORDS] {
58    DEFAULT_CONFIG.chords
59}
60
61/// Deserialize chords for [Config].
62fn deserialize_chords<'de, D>(
63    deserializer: D,
64) -> Result<[Option<ChordIndices>; MAX_CHORDS], D::Error>
65where
66    D: serde::Deserializer<'de>,
67{
68    let mut v: heapless::Vec<Option<ChordIndices>, MAX_CHORDS> =
69        Deserialize::deserialize(deserializer)?;
70
71    while !v.is_full() {
72        v.push(None).unwrap();
73    }
74
75    v.into_array()
76        .map_err(|_| serde::de::Error::custom("unable to deserialize"))
77}
78
79/// Default config.
80pub const DEFAULT_CONFIG: Config = Config {
81    timeout: 200,
82    chords: [None; MAX_CHORDS],
83};
84
85impl Default for Config {
86    /// Returns the default context.
87    fn default() -> Self {
88        DEFAULT_CONFIG
89    }
90}
91
92/// Chord definitions.
93#[derive(Debug, Clone, Copy, PartialEq)]
94pub struct Context {
95    config: Config,
96
97    pressed_indices: [Option<u16>; MAX_CHORD_SIZE * MAX_CHORDS],
98}
99
100/// Default context.
101pub const DEFAULT_CONTEXT: Context = Context::from_config(DEFAULT_CONFIG);
102
103impl Context {
104    /// Constructs a context from the given config
105    pub const fn from_config(config: Config) -> Context {
106        let pressed_indices = [None; MAX_CHORD_SIZE * MAX_CHORDS];
107        Context {
108            config,
109            pressed_indices,
110        }
111    }
112
113    /// Returns the chord indices for the given pressed indices.
114    ///
115    /// The returned vec is empty if any of the indices are not part of a chord.
116    pub fn chords_for_indices(
117        &self,
118        indices: &[u16],
119    ) -> heapless::Vec<ChordIndices, { MAX_CHORDS }> {
120        self.config
121            .chords
122            .iter()
123            .filter_map(|&c| c)
124            .filter(|c| indices.iter().all(|&i| c.has_index(i)))
125            .collect()
126    }
127
128    // All the indices (including the given index) from chords which
129    //  include the given index.
130    //
131    // e.g. for chords {01, 12},
132    //  sibling_indices(0) -> [0, 1]
133    //  sibling_indices(1) -> [0, 1, 2]
134    fn sibling_indices(&self, index: u16) -> heapless::Vec<u16, { MAX_CHORD_SIZE * MAX_CHORDS }> {
135        let mut res: heapless::Vec<u16, { MAX_CHORD_SIZE * MAX_CHORDS }> = heapless::Vec::new();
136
137        let chords = self.chords_for_indices(&[index]);
138
139        chords.iter().for_each(|&ch| match ch {
140            ChordIndices::Chord2(i0, i1) => {
141                if let Err(pos) = res.binary_search(&i0) {
142                    res.insert(pos, i0).unwrap();
143                }
144                if let Err(pos) = res.binary_search(&i1) {
145                    res.insert(pos, i1).unwrap();
146                }
147            }
148        });
149
150        res
151    }
152
153    fn insert_pressed_index(&mut self, pos: usize, index: u16) {
154        if self.pressed_indices.is_empty() {
155            return;
156        }
157
158        let mut i = self.pressed_indices.len() - 1;
159        while i > pos {
160            self.pressed_indices[i] = self.pressed_indices[i - 1];
161            i -= 1;
162        }
163
164        self.pressed_indices[pos] = Some(index);
165    }
166
167    fn remove_pressed_index(&mut self, pos: usize) {
168        if self.pressed_indices.is_empty() {
169            return;
170        }
171
172        let mut i = pos;
173        while i < self.pressed_indices.len() - 1 {
174            self.pressed_indices[i] = self.pressed_indices[i + 1];
175            i += 1;
176        }
177
178        self.pressed_indices[self.pressed_indices.len() - 1] = None;
179    }
180
181    fn press_index(&mut self, index: u16) {
182        match self
183            .pressed_indices
184            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
185        {
186            Ok(_) => {}
187            Err(pos) => self.insert_pressed_index(pos, index),
188        }
189    }
190
191    fn release_index(&mut self, index: u16) {
192        if let Ok(pos) = self
193            .pressed_indices
194            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
195        {
196            self.remove_pressed_index(pos)
197        }
198    }
199
200    fn pressed_indices(&self) -> heapless::Vec<u16, { MAX_CHORD_SIZE * MAX_CHORDS }> {
201        self.pressed_indices.iter().filter_map(|&i| i).collect()
202    }
203
204    /// Updates the context for the given key event.
205    pub fn handle_event(&mut self, event: key::Event<Event>) {
206        match event {
207            key::Event::Input(input::Event::Press { keymap_index }) => {
208                self.press_index(keymap_index);
209            }
210            key::Event::Input(input::Event::Release { keymap_index }) => {
211                self.release_index(keymap_index);
212            }
213            key::Event::Key {
214                keymap_index,
215                key_event: Event::ChordResolved(ChordResolution::Passthrough),
216            } => self.release_index(keymap_index),
217            _ => {}
218        }
219    }
220}
221
222/// Primary Chorded key (with a passthrough key).
223///
224/// The primary key is the key with the lowest index in the chord,
225///  and has the key used for the resolved chord.
226#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
227pub struct Key<K> {
228    /// The chorded key
229    pub chord: K,
230    /// The passthrough key
231    pub passthrough: K,
232}
233
234impl<K: key::Key> Key<K>
235where
236    for<'c> &'c K::Context: Into<&'c Context>,
237    K::Event: TryInto<Event>,
238    K::Event: From<Event>,
239    K::PendingKeyState: From<PendingKeyState>,
240    K::KeyState: From<key::NoOpKeyState<K::Context, K::Event>>,
241{
242    /// Constructs new pressed key.
243    pub fn new_pressed_key(
244        &self,
245        context: &K::Context,
246        key_path: key::KeyPath,
247    ) -> (
248        key::PressedKeyResult<K::PendingKeyState, K::KeyState>,
249        key::KeyEvents<K::Event>,
250    ) {
251        let keymap_index: u16 = key_path[0];
252        let pks = PendingKeyState::new(context.into(), keymap_index);
253
254        let chord_resolution = pks.check_resolution(context.into());
255
256        if let Some(resolution) = chord_resolution {
257            let (i, key) = match resolution {
258                ChordResolution::Chord => (1, &self.chord),
259                ChordResolution::Passthrough => (0, &self.passthrough),
260            };
261
262            let (pkr, pke) = key.new_pressed_key(context, key_path);
263            // PRESSED KEY PATH: add Chord (0 = passthrough, 1 = chord)
264            (pkr.add_path_item(i), pke)
265        } else {
266            let pk = key::PressedKeyResult::Pending(key_path, pks.into());
267
268            let timeout_ev = Event::Timeout;
269            let ctx: &Context = context.into();
270            let sch_ev = key::ScheduledEvent::after(
271                ctx.config.timeout,
272                key::Event::key_event(keymap_index, timeout_ev),
273            );
274            let pke = key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event());
275
276            (pk, pke)
277        }
278    }
279}
280
281impl<K> Key<K> {
282    /// Constructs new chorded key.
283    pub const fn new(chord: K, passthrough: K) -> Self {
284        Key { chord, passthrough }
285    }
286}
287
288impl<
289        K: key::Key<
290            Context = crate::init::Context,
291            Event = crate::init::Event,
292            PendingKeyState = crate::init::PendingKeyState,
293            KeyState = crate::init::KeyState,
294        >,
295    > key::Key for Key<K>
296{
297    type Context = crate::init::Context;
298    type Event = crate::init::Event;
299    type PendingKeyState = crate::init::PendingKeyState;
300    type KeyState = crate::init::KeyState;
301
302    fn new_pressed_key(
303        &self,
304        context: &Self::Context,
305        key_path: key::KeyPath,
306    ) -> (
307        key::PressedKeyResult<Self::PendingKeyState, Self::KeyState>,
308        key::KeyEvents<Self::Event>,
309    ) {
310        self.new_pressed_key(context, key_path)
311    }
312
313    fn handle_event(
314        &self,
315        pending_state: &mut Self::PendingKeyState,
316        context: &Self::Context,
317        key_path: key::KeyPath,
318        event: key::Event<Self::Event>,
319    ) -> (
320        Option<key::PressedKeyResult<Self::PendingKeyState, Self::KeyState>>,
321        key::KeyEvents<Self::Event>,
322    ) {
323        let keymap_index: u16 = key_path[0];
324        let ch_pks_res: Result<&mut PendingKeyState, _> = pending_state.try_into();
325        if let Ok(ch_pks) = ch_pks_res {
326            if let Ok(ch_ev) = event.try_into_key_event(|e| e.try_into()) {
327                let ch_state = ch_pks.handle_event(context.into(), keymap_index, ch_ev);
328                if let Some(ch_state) = ch_state {
329                    let (i, nk) = match ch_state {
330                        key::chorded::ChordResolution::Chord => (1, &self.chord),
331                        key::chorded::ChordResolution::Passthrough => (0, &self.passthrough),
332                    };
333                    let (pkr, mut pke) = nk.new_pressed_key(context, key_path);
334                    // PRESSED KEY PATH: add Chord (0 = passthrough, 1 = chord)
335                    let pkr = pkr.add_path_item(i);
336
337                    let ch_r_ev = key::chorded::Event::ChordResolved(ch_state);
338                    let sch_ev = key::ScheduledEvent::immediate(key::Event::key_event(
339                        keymap_index,
340                        ch_r_ev.into(),
341                    ));
342                    pke.add_event(sch_ev);
343
344                    (Some(pkr), pke)
345                } else {
346                    (None, key::KeyEvents::no_events())
347                }
348            } else {
349                (None, key::KeyEvents::no_events())
350            }
351        } else {
352            (None, key::KeyEvents::no_events())
353        }
354    }
355
356    fn lookup(
357        &self,
358        path: &[u16],
359    ) -> &dyn key::Key<
360        Context = Self::Context,
361        Event = Self::Event,
362        PendingKeyState = Self::PendingKeyState,
363        KeyState = Self::KeyState,
364    > {
365        match path {
366            [] => self,
367            // 0 = passthrough, 1 = chord
368            [0, path @ ..] => self.passthrough.lookup(path),
369            [1, path @ ..] => self.chord.lookup(path),
370            _ => panic!(),
371        }
372    }
373}
374
375/// Auxiliary chorded key (with a passthrough key).
376///
377/// The auxiliary keys are chorded keys,
378///  but don't store the resolved chord key.
379/// (i.e. After te primary chorded key, the remaining keys
380///  in the chord are defined with auxiliary chorded keys).
381#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
382pub struct AuxiliaryKey<K> {
383    /// The passthrough key
384    pub passthrough: K,
385}
386
387impl<K: key::Key> AuxiliaryKey<K>
388where
389    for<'c> &'c K::Context: Into<&'c Context>,
390    K::Event: TryInto<Event>,
391    K::Event: From<Event>,
392    K::PendingKeyState: From<PendingKeyState>,
393    K::KeyState: From<key::NoOpKeyState<K::Context, K::Event>>,
394{
395    /// Constructs new pressed key.
396    pub fn new_pressed_key(
397        &self,
398        context: &K::Context,
399        key_path: key::KeyPath,
400    ) -> (
401        key::PressedKeyResult<K::PendingKeyState, K::KeyState>,
402        key::KeyEvents<K::Event>,
403    ) {
404        let keymap_index: u16 = key_path[0];
405        let pks = PendingKeyState::new(context.into(), keymap_index);
406
407        let chord_resolution = pks.check_resolution(context.into());
408
409        if let Some(resolution) = chord_resolution {
410            match resolution {
411                ChordResolution::Chord => {
412                    let pk = key::PressedKeyResult::Resolved(key::NoOpKeyState::new().into());
413                    let pke = key::KeyEvents::no_events();
414
415                    (pk, pke)
416                }
417                // n.b. no need to add to key path; chorded aux_key only nests the passthrough key.
418                ChordResolution::Passthrough => self.passthrough.new_pressed_key(context, key_path),
419            }
420        } else {
421            let pk = key::PressedKeyResult::Pending(key_path, pks.into());
422
423            let timeout_ev = Event::Timeout;
424            let ctx: &Context = context.into();
425            let sch_ev = key::ScheduledEvent::after(
426                ctx.config.timeout,
427                key::Event::key_event(keymap_index, timeout_ev),
428            );
429            let pke = key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event());
430
431            (pk, pke)
432        }
433    }
434}
435
436impl<K> AuxiliaryKey<K> {
437    /// Constructs new auxiliary chorded key.
438    pub const fn new(passthrough: K) -> Self {
439        AuxiliaryKey { passthrough }
440    }
441}
442
443impl<
444        K: key::Key<
445            Context = crate::init::Context,
446            Event = crate::init::Event,
447            PendingKeyState = crate::init::PendingKeyState,
448            KeyState = crate::init::KeyState,
449        >,
450    > key::Key for AuxiliaryKey<K>
451{
452    type Context = crate::init::Context;
453    type Event = crate::init::Event;
454    type PendingKeyState = crate::init::PendingKeyState;
455    type KeyState = crate::init::KeyState;
456
457    fn new_pressed_key(
458        &self,
459        context: &Self::Context,
460        key_path: key::KeyPath,
461    ) -> (
462        key::PressedKeyResult<Self::PendingKeyState, Self::KeyState>,
463        key::KeyEvents<Self::Event>,
464    ) {
465        self.new_pressed_key(context, key_path)
466    }
467
468    fn handle_event(
469        &self,
470        pending_state: &mut Self::PendingKeyState,
471        context: &Self::Context,
472        key_path: key::KeyPath,
473        event: key::Event<Self::Event>,
474    ) -> (
475        Option<key::PressedKeyResult<Self::PendingKeyState, Self::KeyState>>,
476        key::KeyEvents<Self::Event>,
477    ) {
478        let keymap_index = key_path[0];
479        let ch_pks_res: Result<&mut PendingKeyState, _> = pending_state.try_into();
480        if let Ok(ch_pks) = ch_pks_res {
481            if let Ok(ch_ev) = event.try_into_key_event(|e| e.try_into()) {
482                let ch_state = ch_pks.handle_event(context.into(), keymap_index, ch_ev);
483                if let Some(key::chorded::ChordResolution::Passthrough) = ch_state {
484                    let nk = &self.passthrough;
485                    let (pkr, mut pke) = nk.new_pressed_key(context, key_path);
486
487                    // n.b. no need to add to key path; chorded aux_key only nests the passthrough key.
488
489                    let ch_r_ev = key::chorded::Event::ChordResolved(
490                        key::chorded::ChordResolution::Passthrough,
491                    );
492                    let sch_ev = key::ScheduledEvent::immediate(key::Event::key_event(
493                        keymap_index,
494                        ch_r_ev.into(),
495                    ));
496                    pke.add_event(sch_ev);
497
498                    (Some(pkr), pke)
499                } else if let Some(key::chorded::ChordResolution::Chord) = ch_state {
500                    let ch_r_ev =
501                        key::chorded::Event::ChordResolved(key::chorded::ChordResolution::Chord);
502                    let pke =
503                        key::KeyEvents::event(key::Event::key_event(keymap_index, ch_r_ev.into()));
504
505                    (
506                        Some(key::PressedKeyResult::Resolved(
507                            key::NoOpKeyState::new().into(),
508                        )),
509                        pke,
510                    )
511                } else {
512                    (None, key::KeyEvents::no_events())
513                }
514            } else {
515                (None, key::KeyEvents::no_events())
516            }
517        } else {
518            (None, key::KeyEvents::no_events())
519        }
520    }
521
522    fn lookup(
523        &self,
524        path: &[u16],
525    ) -> &dyn key::Key<
526        Context = Self::Context,
527        Event = Self::Event,
528        PendingKeyState = Self::PendingKeyState,
529        KeyState = Self::KeyState,
530    > {
531        match path {
532            [] => self,
533            _ => self.passthrough.lookup(path),
534        }
535    }
536}
537
538/// Events for chorded keys.
539#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
540pub enum Event {
541    /// The chorded key was resolved.
542    ChordResolved(ChordResolution),
543
544    /// Timed out waiting for chord to be satisfied.
545    Timeout,
546}
547
548/// Whether enough keys have been pressed to satisfy a chord.
549///
550/// In the case of non-overlapping chords,
551///  a satisfied chord is a resolved chord.
552///
553/// In the case of overlapping chords,
554///  e.g. "chord 01" and "chord 012",
555///  pressed "01" is satisfies "chord 01".
556#[derive(Debug, Clone, Copy, PartialEq)]
557pub enum ChordSatisfaction {
558    /// Status where not enough keys have been pressed to satisfy a chord.
559    Unsatisfied,
560    /// Status where enough keys have been pressed to satisfy a chord.
561    Satisfied,
562}
563
564/// Whether the pressed key state has resolved to a chord or not.
565#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
566pub enum ChordResolution {
567    /// Resolved as chord.
568    Chord,
569    /// Resolved as passthrough key.
570    Passthrough,
571}
572
573/// State for pressed keys.
574#[derive(Debug, Clone, PartialEq)]
575pub struct PendingKeyState {
576    /// The keymap indices which have been pressed.
577    pressed_indices: heapless::Vec<u16, { MAX_CHORD_SIZE }>,
578    /// Whether the chord has been satisfied.
579    satisfaction: ChordSatisfaction,
580}
581
582impl PendingKeyState {
583    /// Constructs a new [PendingKeyState].
584    pub fn new(context: &Context, keymap_index: u16) -> Self {
585        let sibling_indices = context.sibling_indices(keymap_index);
586        let pressed_indices: heapless::Vec<u16, MAX_CHORD_SIZE> = context
587            .pressed_indices()
588            .iter()
589            .filter(|i| sibling_indices.contains(i))
590            .copied()
591            .collect();
592
593        Self {
594            pressed_indices,
595            satisfaction: ChordSatisfaction::Unsatisfied,
596        }
597    }
598
599    fn check_resolution(&self, context: &Context) -> Option<ChordResolution> {
600        let chords = context.chords_for_indices(self.pressed_indices.as_slice());
601        match chords.as_slice() {
602            [ch] if ch.is_satisfied_by(&self.pressed_indices) => {
603                // Only one chord is satisfied by pressed indices.
604                //
605                // This resolves the aux key.
606                Some(ChordResolution::Chord)
607            }
608            [] => {
609                // Otherwise, this key state resolves to "Passthrough",
610                //  since it has been interrupted by an unrelated key press.
611                Some(ChordResolution::Passthrough)
612            }
613            _ => {
614                // Overlapping chords.
615                None
616            }
617        }
618    }
619
620    /// Handle PKS for primary chorded key.
621    pub fn handle_event(
622        &mut self,
623        context: &Context,
624        keymap_index: u16,
625        event: key::Event<Event>,
626    ) -> Option<ChordResolution> {
627        match event {
628            key::Event::Key {
629                keymap_index: _ev_idx,
630                key_event: Event::Timeout,
631            } => {
632                // Timed out before chord unambiguously resolved.
633                //  So, the key behaves as the passthrough key.
634                Some(ChordResolution::Passthrough)
635            }
636            key::Event::Input(input::Event::Press {
637                keymap_index: pressed_keymap_index,
638            }) => {
639                // Another key was pressed.
640                // Check if the other key belongs to this key's chord indices,
641
642                let pos = self
643                    .pressed_indices
644                    .binary_search(&keymap_index)
645                    .unwrap_or_else(|e| e);
646
647                let push_res = self.pressed_indices.insert(pos, pressed_keymap_index);
648
649                // pressed_indices has capacity of MAX_CHORD_SIZE.
650                // pressed_indices will only be full without resolving
651                // if multiple chords with max chord size
652                //  having the same indices.
653                if push_res.is_err() {
654                    panic!();
655                }
656
657                self.check_resolution(context)
658            }
659            key::Event::Input(input::Event::Release {
660                keymap_index: released_keymap_index,
661            }) => {
662                if released_keymap_index == keymap_index {
663                    // This key state resolves to "Passthrough",
664                    //  since it has been released before resolving as chord.
665                    Some(ChordResolution::Passthrough)
666                } else {
667                    None
668                }
669            }
670            _ => None,
671        }
672    }
673}
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678
679    use key::composite;
680    use key::keyboard;
681
682    use key::Context as _;
683
684    #[test]
685    fn test_timeout_resolves_unsatisfied_aux_state_as_passthrough_key() {
686        // Assemble: an Auxilary chorded key, and its PKS.
687        let context = key::composite::Context::default();
688        let expected_key = keyboard::Key::new(0x04);
689        let _chorded_key = AuxiliaryKey {
690            passthrough: expected_key,
691        };
692        let keymap_index: u16 = 0;
693        let mut pks: PendingKeyState = PendingKeyState::new((&context).into(), keymap_index);
694
695        // Act: handle a timeout ev.
696        let timeout_ev = key::Event::key_event(keymap_index, Event::Timeout).into_key_event();
697        let actual_res = pks.handle_event((&context).into(), keymap_index, timeout_ev);
698
699        // Assert
700        let expected_res = Some(ChordResolution::Passthrough);
701        assert_eq!(expected_res, actual_res);
702    }
703
704    // #[test]
705    // fn test_timeout_resolves_satisfied_key_state_as_chord() {}
706
707    #[test]
708    fn test_press_non_chorded_key_resolves_aux_state_as_interrupted() {
709        // Assemble: an Auxilary chorded key, and its PKS.
710        let context = key::composite::Context::default();
711        let expected_key = keyboard::Key::new(0x04);
712        let _chorded_key = AuxiliaryKey {
713            passthrough: expected_key,
714        };
715        let keymap_index: u16 = 0;
716        let mut pks: PendingKeyState = PendingKeyState::new((&context).into(), keymap_index);
717
718        // Act: handle a key press, for an index that's not part of any chord.
719        let non_chord_press = input::Event::Press { keymap_index: 9 }.into();
720        let actual_res = pks.handle_event((&context).into(), keymap_index, non_chord_press);
721
722        // Assert
723        let expected_res = Some(ChordResolution::Passthrough);
724        assert_eq!(expected_res, actual_res);
725    }
726
727    // "unambiguous" in the sense that the chord
728    // is not overlapped by another chord.
729    // e.g. chord "01" is overlapped by chord "012",
730    //  and "pressed {0, 1}" would be 'ambiguous';
731    //  wheres "pressed {0, 1, 2}" would be 'unambiguous'.
732
733    #[test]
734    fn test_press_chorded_key_resolves_unambiguous_aux_state_as_chord() {
735        // Assemble: an Auxilary chorded key, and its PKS, with chord 01.
736        let mut context = key::composite::Context::from_config(composite::Config {
737            chorded: Config {
738                chords: [Some(ChordIndices::Chord2(0, 1)), None, None, None],
739                ..DEFAULT_CONFIG
740            },
741            ..composite::DEFAULT_CONFIG
742        });
743
744        let passthrough = keyboard::Key::new(0x04);
745        let _chorded_key = AuxiliaryKey { passthrough };
746        let keymap_index: u16 = 0;
747        context.handle_event(key::Event::Input(input::Event::Press { keymap_index: 0 }));
748        let mut pks: PendingKeyState = PendingKeyState::new((&context).into(), keymap_index);
749
750        // Act: handle a key press, for an index that completes (satisfies unambiguously) the chord.
751        let chord_press = input::Event::Press { keymap_index: 1 }.into();
752        let actual_res = pks.handle_event((&context).into(), keymap_index, chord_press);
753
754        // Assert: resolved aux key should have no events, should have (resolved) no output.
755        let expected_res = Some(ChordResolution::Chord);
756        assert_eq!(expected_res, actual_res);
757    }
758
759    // #[test]
760    // fn test_release_resolved_chord_state_releases_chord() {}
761
762    // This is better covered with an integration test.
763    // #[test]
764    // fn test_release_resolved_aux_passthrough_state_releases_passthrough_key() {}
765
766    #[test]
767    fn test_release_pending_aux_state_resolves_as_tapped_key() {
768        // Assemble: an Auxilary chorded key, and its PKS.
769        let context = key::composite::Context::default();
770        let expected_key = keyboard::Key::new(0x04);
771        let _chorded_key = AuxiliaryKey {
772            passthrough: expected_key,
773        };
774        let keymap_index: u16 = 0;
775        let mut pks: PendingKeyState = PendingKeyState::new((&context).into(), keymap_index);
776
777        // Act: handle a key press, for an index that's not part of any chord.
778        let chorded_key_release = input::Event::Release { keymap_index }.into();
779        let actual_res = pks.handle_event((&context).into(), keymap_index, chorded_key_release);
780
781        // Assert
782        let expected_res = Some(ChordResolution::Passthrough);
783        assert_eq!(expected_res, actual_res);
784    }
785}