smart_keymap/key/
chorded.rs

1use core::fmt::Debug;
2use core::marker::PhantomData;
3use core::ops::Index;
4
5use serde::Deserialize;
6
7use crate::{input, key, keymap, slice::Slice};
8
9/// Reference for a chorded key.
10#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
11pub enum Ref {
12    /// Ref for [Key].
13    Chorded(u8),
14    /// Ref for [AuxiliaryKey].
15    Auxiliary(u8),
16}
17
18/// A chord identifier.
19pub type ChordId = u8;
20
21/// Chords are defined by an (unordered) set of keymap indices into the keymap.
22#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
23#[serde(from = "heapless::Vec<u16, MAX_CHORD_SIZE>")]
24pub struct ChordIndices<const MAX_CHORD_SIZE: usize> {
25    /// A slice of keymap indices.
26    indices: Slice<u16, MAX_CHORD_SIZE>,
27}
28
29impl<const MAX_CHORD_SIZE: usize> ChordIndices<MAX_CHORD_SIZE> {
30    /// Constructs a new [ChordIndices] value from the given slice.
31    ///
32    /// The given slice must be less than `MAX_CHORD_SIZE` in length.
33    pub const fn from_slice(indices: &[u16]) -> ChordIndices<MAX_CHORD_SIZE> {
34        ChordIndices {
35            indices: Slice::from_slice(indices),
36        }
37    }
38
39    /// The chord indices as a slice.
40    pub const fn as_slice(&self) -> &[u16] {
41        self.indices.as_slice()
42    }
43
44    /// Whether the given index is part of the chord.
45    pub fn has_index(&self, index: u16) -> bool {
46        self.as_slice().contains(&index)
47    }
48
49    /// Whether the chord is satisfied by the given indices.
50    pub fn is_satisfied_by(&self, indices: &[u16]) -> bool {
51        self.as_slice().iter().all(|&i| indices.contains(&i))
52    }
53}
54
55impl<const MAX_CHORD_SIZE: usize> From<heapless::Vec<u16, MAX_CHORD_SIZE>>
56    for ChordIndices<MAX_CHORD_SIZE>
57{
58    fn from(v: heapless::Vec<u16, MAX_CHORD_SIZE>) -> Self {
59        ChordIndices::from_slice(&v)
60    }
61}
62
63/// Chord definitions.
64#[derive(Deserialize, Clone, Copy, PartialEq)]
65pub struct Config<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> {
66    /// The timeout (in number of milliseconds) for a chorded key to resolve.
67    ///
68    /// (Resolves as passthrough key if no chord is satisfied).
69    #[serde(default = "default_timeout")]
70    pub timeout: u16,
71
72    /// The keymap chords.
73    pub chords: Slice<ChordIndices<MAX_CHORD_SIZE>, MAX_CHORDS>,
74
75    /// Amount of time (in milliseconds) the keymap must have been idle
76    ///  in order for chorded key to activate.
77    ///
78    /// This reduces disruption from unexpected chord resolutions
79    ///  when typing quickly.
80    pub required_idle_time: Option<u16>,
81}
82
83impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> core::fmt::Debug
84    for Config<MAX_CHORDS, MAX_CHORD_SIZE>
85{
86    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87        f.debug_struct("Config")
88            .field("timeout", &self.timeout)
89            .field("chords", &self.chords.as_slice())
90            .field("required_idle_time", &self.required_idle_time)
91            .finish()
92    }
93}
94
95/// The default timeout.
96pub const DEFAULT_TIMEOUT: u16 = 200;
97
98const fn default_timeout() -> u16 {
99    DEFAULT_TIMEOUT
100}
101
102impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> Config<MAX_CHORDS, MAX_CHORD_SIZE> {
103    /// Constructs a new config.
104    pub const fn new() -> Self {
105        Self {
106            timeout: DEFAULT_TIMEOUT,
107            chords: Slice::from_slice(&[]),
108            required_idle_time: None,
109        }
110    }
111}
112
113impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> Default
114    for Config<MAX_CHORDS, MAX_CHORD_SIZE>
115{
116    /// Returns the default context.
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122/// State for a key chord.
123#[derive(Debug, Clone, PartialEq)]
124pub struct ChordState<const MAX_CHORD_SIZE: usize> {
125    /// The chord index in the chorded config.
126    pub index: usize,
127    /// The chord's indices.
128    pub chord: ChordIndices<MAX_CHORD_SIZE>,
129    /// Whether the chord is satisfied by the pressed indices.
130    pub is_satisfied: bool,
131}
132
133struct PressedIndicesDebugHelper<'a, const MAX_PRESSED_INDICES: usize> {
134    pressed_indices: &'a [Option<u16>; MAX_PRESSED_INDICES],
135}
136
137impl<const MAX_PRESSED_INDICES: usize> core::fmt::Debug
138    for PressedIndicesDebugHelper<'_, MAX_PRESSED_INDICES>
139{
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        // Reverse-find the last non-empty pressed index to avoid printing large arrays.
142        let last_non_empty_pi_pos = self
143            .pressed_indices
144            .iter()
145            .rposition(|pi| pi.is_some())
146            .map_or(0, |pos| pos + 1);
147        if last_non_empty_pi_pos < MAX_PRESSED_INDICES {
148            f.debug_list()
149                .entries(&self.pressed_indices[..last_non_empty_pi_pos])
150                .finish_non_exhaustive()
151        } else {
152            f.debug_list().entries(&self.pressed_indices[..]).finish()
153        }
154    }
155}
156
157struct PressedChordsDebugHelper<'a, const MAX_CHORDS: usize> {
158    pressed_chords: &'a [bool; MAX_CHORDS],
159}
160
161impl<const MAX_CHORDS: usize> core::fmt::Debug for PressedChordsDebugHelper<'_, MAX_CHORDS> {
162    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163        // Reverse-find the last true pressed chord to avoid printing large arrays.
164        let last_true_pc_pos = self
165            .pressed_chords
166            .iter()
167            .rposition(|&pc| pc)
168            .map_or(0, |pos| pos + 1);
169        if last_true_pc_pos < MAX_CHORDS {
170            f.debug_list()
171                .entries(&self.pressed_chords[..last_true_pc_pos])
172                .finish_non_exhaustive()
173        } else {
174            f.debug_list().entries(&self.pressed_chords[..]).finish()
175        }
176    }
177}
178
179/// Chord definitions.
180#[derive(Clone, Copy, PartialEq)]
181pub struct Context<
182    const MAX_CHORDS: usize,
183    const MAX_CHORD_SIZE: usize,
184    const MAX_PRESSED_INDICES: usize,
185> {
186    config: Config<MAX_CHORDS, MAX_CHORD_SIZE>,
187    pressed_indices: [Option<u16>; MAX_PRESSED_INDICES],
188    pressed_chords: [bool; MAX_CHORDS],
189    idle_time_ms: u32,
190    ignore_idle_time: bool,
191    latest_resolved_chord: Option<ChordId>,
192}
193
194impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize> Debug
195    for Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
196{
197    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198        f.debug_struct("Context")
199            .field("config", &self.config)
200            .field(
201                "pressed_indices",
202                &PressedIndicesDebugHelper {
203                    pressed_indices: &self.pressed_indices,
204                },
205            )
206            .field(
207                "pressed_chords",
208                &PressedChordsDebugHelper {
209                    pressed_chords: &self.pressed_chords,
210                },
211            )
212            .field("idle_time_ms", &self.idle_time_ms)
213            .field("ignore_idle_time", &self.ignore_idle_time)
214            .field("latest_resolved_chord", &self.latest_resolved_chord)
215            .finish()
216    }
217}
218
219impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
220    Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
221{
222    /// Constructs a context from the given config
223    pub const fn from_config(config: Config<MAX_CHORDS, MAX_CHORD_SIZE>) -> Self {
224        let pressed_indices = [None; MAX_PRESSED_INDICES];
225        Context {
226            config,
227            pressed_indices,
228            pressed_chords: [false; MAX_CHORDS],
229            idle_time_ms: 0,
230            ignore_idle_time: false,
231            latest_resolved_chord: None,
232        }
233    }
234
235    /// Updates the context with the given keymap context.
236    pub fn update_keymap_context(
237        &mut self,
238        keymap::KeymapContext { idle_time_ms, .. }: &keymap::KeymapContext,
239    ) {
240        self.idle_time_ms = *idle_time_ms;
241    }
242
243    fn sufficient_idle_time(&self) -> bool {
244        let sufficient_idle_time =
245            self.idle_time_ms >= self.config.required_idle_time.unwrap_or(0) as u32;
246
247        sufficient_idle_time || self.ignore_idle_time
248    }
249
250    fn pressed_chord_with_index(&self, keymap_index: u16) -> Option<ChordState<MAX_CHORD_SIZE>> {
251        self.pressed_chords
252            .iter()
253            .enumerate()
254            .filter_map(|(index, &is_pressed)| {
255                if is_pressed {
256                    Some(ChordState {
257                        index,
258                        chord: self.config.chords[index],
259                        is_satisfied: true,
260                    })
261                } else {
262                    None
263                }
264            })
265            .find(|ChordState { chord, .. }| chord.has_index(keymap_index))
266    }
267
268    // Span of indices of pressed chords.
269    fn pressed_chords_indices_span(&self) -> heapless::Vec<u16, MAX_PRESSED_INDICES> {
270        let mut res: heapless::Vec<u16, MAX_PRESSED_INDICES> = heapless::Vec::new();
271
272        let pressed_chords =
273            self.pressed_chords
274                .iter()
275                .enumerate()
276                .filter_map(|(index, &is_pressed)| {
277                    if is_pressed {
278                        Some(&self.config.chords[index])
279                    } else {
280                        None
281                    }
282                });
283
284        pressed_chords.for_each(|&chord| {
285            for &i in chord.as_slice() {
286                if let Err(pos) = res.binary_search(&i) {
287                    res.insert(pos, i).unwrap();
288                }
289            }
290        });
291
292        res
293    }
294
295    /// Returns the chords for the given keymap index.
296    ///
297    /// - If a chord with that index is resolved as active, return a vec with only that chord.
298    /// - Otherwise, return a vec with all the chords which include the keymap index
299    ///   and could be satisfied. (i.e. chords which do not overlap with resolved active chords).
300    pub fn chords_for_keymap_index(
301        &self,
302        keymap_index: u16,
303    ) -> heapless::Vec<ChordState<MAX_CHORD_SIZE>, { MAX_CHORDS }> {
304        match self.pressed_chord_with_index(keymap_index) {
305            Some(chord_state) => heapless::Vec::from_slice(&[chord_state]).unwrap(),
306            None => {
307                let chords_indices_span = self.pressed_chords_indices_span();
308                self.config
309                    .chords
310                    .iter()
311                    .enumerate()
312                    // filter: satisfiable chords
313                    .filter(|&(_index, chord)| chord.has_index(keymap_index))
314                    .filter(|&(_index, chord)| {
315                        // Filter out chords which overlap with resolved active chords.
316                        chords_indices_span.is_empty()
317                            || chord.indices.iter().all(|&i| {
318                                // The chord index is not part of the pressed chords indices span.
319                                chords_indices_span.binary_search(&i).is_err()
320                            })
321                    })
322                    .map(|(index, &chord)| ChordState {
323                        index,
324                        chord,
325                        is_satisfied: false,
326                    })
327                    .collect()
328            }
329        }
330    }
331
332    fn insert_pressed_index(&mut self, pos: usize, index: u16) {
333        if self.pressed_indices.is_empty() {
334            return;
335        }
336
337        let mut i = self.pressed_indices.len() - 1;
338        while i > pos {
339            self.pressed_indices[i] = self.pressed_indices[i - 1];
340            i -= 1;
341        }
342
343        self.pressed_indices[pos] = Some(index);
344    }
345
346    fn remove_pressed_index(&mut self, pos: usize) {
347        if self.pressed_indices.is_empty() {
348            return;
349        }
350
351        let mut i = pos;
352        while i < self.pressed_indices.len() - 1 {
353            self.pressed_indices[i] = self.pressed_indices[i + 1];
354            i += 1;
355        }
356
357        self.pressed_indices[self.pressed_indices.len() - 1] = None;
358    }
359
360    fn press_index(&mut self, index: u16) {
361        match self
362            .pressed_indices
363            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
364        {
365            Ok(_) => {}
366            Err(pos) => self.insert_pressed_index(pos, index),
367        }
368    }
369
370    fn release_index(&mut self, index: u16) {
371        if let Ok(pos) = self
372            .pressed_indices
373            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
374        {
375            self.remove_pressed_index(pos)
376        }
377    }
378
379    /// Updates the context for the given key event.
380    fn handle_event(&mut self, event: key::Event<Event>) {
381        match event {
382            key::Event::Input(input::Event::Press { keymap_index }) => {
383                self.press_index(keymap_index);
384
385                // Consider whether the key press supports
386                //  ignoring required idle time for a chorded key,
387                //  or supports quickly re-tapping a chorded key.
388                let span = self.pressed_chords_indices_span();
389                if span.contains(&keymap_index) {
390                    // Key presses of an active chord ignore required idle time.
391                    self.ignore_idle_time = true;
392                } else {
393                    // Otherwise, check against the latest resolved chord.
394                    if let Some(chord_id) = self.latest_resolved_chord {
395                        let chord_indices = self.config.chords[chord_id as usize];
396                        self.ignore_idle_time = chord_indices.has_index(keymap_index);
397                    } else {
398                        self.ignore_idle_time = false;
399
400                        // Chords not active, and this press was outside the latest active chord,
401                        //  so clear the latest resolved chord.
402                        self.latest_resolved_chord = None;
403                    }
404                }
405            }
406            key::Event::Input(input::Event::Release { keymap_index }) => {
407                self.release_index(keymap_index);
408
409                // Ensure every chord which includes this keymap index
410                //  is not marked as 'pressed'.
411                self.config
412                    .chords
413                    .iter()
414                    .enumerate()
415                    .for_each(|(chord_id, chord_indices)| {
416                        if chord_indices.has_index(keymap_index) {
417                            self.pressed_chords[chord_id] = false;
418                        }
419                    });
420            }
421            key::Event::Key {
422                keymap_index: _,
423                key_event: Event::ChordResolved(ChordResolution::Chord(chord_id)),
424            } => {
425                self.pressed_chords[chord_id as usize] = true;
426                self.latest_resolved_chord = Some(chord_id);
427            }
428            _ => {}
429        }
430    }
431}
432
433impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
434    key::Context for Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
435{
436    type Event = Event;
437
438    fn handle_event(&mut self, event: key::Event<Self::Event>) -> key::KeyEvents<Self::Event> {
439        self.handle_event(event);
440        key::KeyEvents::no_events()
441    }
442}
443
444/// Primary Chorded key (with a passthrough key).
445///
446/// The primary key is the key with the lowest index in the chord,
447///  and has the key used for the resolved chord.
448#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
449pub struct Key<
450    R: Copy,
451    const MAX_CHORDS: usize,
452    const MAX_CHORD_SIZE: usize,
453    const MAX_OVERLAPPING_CHORD_SIZE: usize,
454    const MAX_PRESSED_INDICES: usize,
455> {
456    /// The chorded key
457    pub chords: Slice<(ChordId, R), MAX_OVERLAPPING_CHORD_SIZE>,
458    /// The passthrough key
459    pub passthrough: R,
460    #[serde(default)]
461    marker: PhantomData<(
462        [(); MAX_CHORDS],
463        [(); MAX_CHORD_SIZE],
464        [(); MAX_PRESSED_INDICES],
465    )>,
466}
467
468impl<
469        R: Copy,
470        const MAX_CHORDS: usize,
471        const MAX_CHORD_SIZE: usize,
472        const MAX_OVERLAPPING_CHORD_SIZE: usize,
473        const MAX_PRESSED_INDICES: usize,
474    > Key<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_OVERLAPPING_CHORD_SIZE, MAX_PRESSED_INDICES>
475{
476    /// Constructs new pressed key.
477    pub fn new_pressed_key(
478        &self,
479        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
480        keymap_index: u16,
481    ) -> (
482        key::PressedKeyResult<
483            R,
484            PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
485            KeyState,
486        >,
487        key::KeyEvents<Event>,
488    ) {
489        let pks = PendingKeyState::new(context, keymap_index);
490
491        let chord_resolution = if context.sufficient_idle_time() {
492            pks.check_resolution()
493        } else {
494            PendingChordState::Resolved(ChordResolution::Passthrough)
495        };
496
497        if let PendingChordState::Resolved(resolution) = chord_resolution {
498            let maybe_new_key_ref = match resolution {
499                ChordResolution::Chord(resolved_chord_id) => {
500                    // Whether the resolved chord is associated with this key.
501                    // (i.e. the resolved chord's primary keymap index is this keymap index).
502                    if let Some(resolved_chord_indices) =
503                        context.config.chords.get(resolved_chord_id as usize)
504                    {
505                        if resolved_chord_indices.as_slice()[0] == keymap_index {
506                            if let Some((_, new_key_ref)) = self
507                                .chords
508                                .iter()
509                                .find(|(ch_id, _)| *ch_id == resolved_chord_id)
510                            {
511                                Some(*new_key_ref)
512                            } else {
513                                panic!("check_resolution has invalid chord id")
514                            }
515                        } else {
516                            None
517                        }
518                    } else {
519                        panic!("check_resolution has invalid chord id")
520                    }
521                }
522                ChordResolution::Passthrough => Some(self.passthrough),
523            };
524
525            if let Some(new_key_ref) = maybe_new_key_ref {
526                let pkr =
527                    key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
528                let pke = key::KeyEvents::no_events();
529
530                (pkr, pke)
531            } else {
532                let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
533                let pke = key::KeyEvents::no_events();
534                (pkr, pke)
535            }
536        } else {
537            let pkr = key::PressedKeyResult::Pending(pks);
538
539            let timeout_ev = Event::Timeout;
540            let sch_ev = key::ScheduledEvent::after(
541                context.config.timeout,
542                key::Event::key_event(keymap_index, timeout_ev),
543            );
544            let pke = key::KeyEvents::scheduled_event(sch_ev);
545
546            (pkr, pke)
547        }
548    }
549
550    /// Constructs new chorded key.
551    pub const fn new(chords: &[(ChordId, R)], passthrough: R) -> Self {
552        let chords = Slice::from_slice(chords);
553        Key {
554            chords,
555            passthrough,
556            marker: PhantomData,
557        }
558    }
559
560    fn update_pending_state(
561        &self,
562        pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
563        keymap_index: u16,
564        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
565        event: key::Event<Event>,
566    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
567        let ch_state = pending_state.handle_event(keymap_index, event);
568
569        // Whether handling the event resulted in a chord resolution.
570        if let Some(ch_state) = ch_state {
571            let maybe_new_key_ref = match ch_state {
572                ChordResolution::Chord(resolved_chord_id) => {
573                    // Whether the resolved chord is associated with this key.
574                    // (i.e. the resolved chord's primary keymap index is this keymap index).
575                    if let Some(resolved_chord_indices) =
576                        context.config.chords.get(resolved_chord_id as usize)
577                    {
578                        if resolved_chord_indices.as_slice()[0] == keymap_index {
579                            if let Some((_, key_ref)) = self
580                                .chords
581                                .iter()
582                                .find(|(ch_id, _)| *ch_id == resolved_chord_id)
583                            {
584                                Some(*key_ref)
585                            } else {
586                                panic!("event's chord resolution has invalid chord id")
587                            }
588                        } else {
589                            None
590                        }
591                    } else {
592                        panic!("event's chord resolution has invalid chord id")
593                    }
594                }
595                ChordResolution::Passthrough => Some(self.passthrough),
596            };
597
598            let ch_r_ev = Event::ChordResolved(ch_state);
599            let sch_ev =
600                key::ScheduledEvent::immediate(key::Event::key_event(keymap_index, ch_r_ev));
601
602            if let Some(new_key_ref) = maybe_new_key_ref {
603                let pke = key::KeyEvents::scheduled_event(sch_ev);
604
605                (Some(key::NewPressedKey::key(new_key_ref)), pke)
606            } else {
607                let pke = key::KeyEvents::scheduled_event(sch_ev);
608                (Some(key::NewPressedKey::no_op()), pke)
609            }
610        } else {
611            (None, key::KeyEvents::no_events())
612        }
613    }
614}
615
616/// Auxiliary chorded key (with a passthrough key).
617///
618/// The auxiliary keys are chorded keys,
619///  but don't store the resolved chord key.
620/// (i.e. After te primary chorded key, the remaining keys
621///  in the chord are defined with auxiliary chorded keys).
622#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
623pub struct AuxiliaryKey<
624    R,
625    const MAX_CHORDS: usize,
626    const MAX_CHORD_SIZE: usize,
627    const MAX_PRESSED_INDICES: usize,
628> {
629    /// The passthrough key
630    pub passthrough: R,
631    #[serde(default)]
632    marker: PhantomData<(
633        [(); MAX_CHORDS],
634        [(); MAX_CHORD_SIZE],
635        [(); MAX_PRESSED_INDICES],
636    )>,
637}
638
639impl<
640        R: Copy,
641        const MAX_CHORDS: usize,
642        const MAX_CHORD_SIZE: usize,
643        const MAX_PRESSED_INDICES: usize,
644    > AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
645{
646    /// Constructs new pressed key.
647    pub fn new_pressed_key(
648        &self,
649        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
650        keymap_index: u16,
651    ) -> (
652        key::PressedKeyResult<
653            R,
654            PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
655            KeyState,
656        >,
657        key::KeyEvents<Event>,
658    ) {
659        let pks = PendingKeyState::new(context, keymap_index);
660
661        let chord_resolution = if context.sufficient_idle_time() {
662            pks.check_resolution()
663        } else {
664            PendingChordState::Resolved(ChordResolution::Passthrough)
665        };
666
667        if let PendingChordState::Resolved(resolution) = chord_resolution {
668            match resolution {
669                ChordResolution::Chord(_resolved_chord_id) => {
670                    let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
671                    let pke = key::KeyEvents::no_events();
672
673                    (pkr, pke)
674                }
675                ChordResolution::Passthrough => {
676                    let new_key_ref = self.passthrough;
677                    let pkr =
678                        key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
679                    let pke = key::KeyEvents::no_events();
680                    (pkr, pke)
681                }
682            }
683        } else {
684            let pkr = key::PressedKeyResult::Pending(pks);
685
686            let timeout_ev = Event::Timeout;
687            let sch_ev = key::ScheduledEvent::after(
688                context.config.timeout,
689                key::Event::key_event(keymap_index, timeout_ev),
690            );
691            let pke = key::KeyEvents::scheduled_event(sch_ev);
692
693            (pkr, pke)
694        }
695    }
696
697    /// Constructs new auxiliary chorded key.
698    pub const fn new(passthrough: R) -> Self {
699        AuxiliaryKey {
700            passthrough,
701            marker: PhantomData,
702        }
703    }
704
705    fn update_pending_state(
706        &self,
707        pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
708        keymap_index: u16,
709        event: key::Event<Event>,
710    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
711        let ch_state = pending_state.handle_event(keymap_index, event);
712        if let Some(ChordResolution::Passthrough) = ch_state {
713            let ch_r_ev = Event::ChordResolved(ChordResolution::Passthrough);
714            let sch_ev =
715                key::ScheduledEvent::immediate(key::Event::key_event(keymap_index, ch_r_ev));
716            let pke = key::KeyEvents::scheduled_event(sch_ev);
717
718            (Some(key::NewPressedKey::key(self.passthrough)), pke)
719        } else if let Some(ChordResolution::Chord(resolved_chord_id)) = ch_state {
720            let ch_r_ev = Event::ChordResolved(ChordResolution::Chord(resolved_chord_id));
721            let pke = key::KeyEvents::event(key::Event::key_event(keymap_index, ch_r_ev));
722
723            (Some(key::NewPressedKey::no_op()), pke)
724        } else {
725            (None, key::KeyEvents::no_events())
726        }
727    }
728}
729
730/// Events for chorded keys.
731#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
732pub enum Event {
733    /// The chorded key was resolved.
734    ChordResolved(ChordResolution),
735
736    /// Timed out waiting for chord to be satisfied.
737    Timeout,
738}
739
740/// Whether the pressed key state has resolved to a chord or not.
741#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
742pub enum ChordResolution {
743    /// Resolved as chord.
744    Chord(ChordId),
745    /// Resolved as passthrough key.
746    Passthrough,
747}
748
749/// The resolution state of a chorded key.
750#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
751pub enum PendingChordState {
752    /// The key state is resolved (as chord or as passthrough).
753    Resolved(ChordResolution),
754    /// The key chord state is pending.
755    ///
756    /// The chord may be pending with the ID of a satisfied chord.
757    Pending(Option<ChordId>),
758}
759
760/// State for pressed keys.
761#[derive(Debug, Clone, PartialEq)]
762pub struct PendingKeyState<
763    const MAX_CHORDS: usize,
764    const MAX_CHORD_SIZE: usize,
765    const MAX_PRESSED_INDICES: usize,
766> {
767    /// The keymap indices which have been pressed while the key is pending.
768    pressed_indices: heapless::Vec<u16, { MAX_CHORD_SIZE }>,
769    /// The chords which this pending key could resolve to.
770    possible_chords: heapless::Vec<ChordState<MAX_CHORD_SIZE>, { MAX_CHORDS }>,
771    marker: PhantomData<[(); MAX_PRESSED_INDICES]>,
772}
773
774impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
775    PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
776{
777    /// Constructs a new [PendingKeyState].
778    pub fn new(
779        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
780        keymap_index: u16,
781    ) -> Self {
782        let pressed_indices = heapless::Vec::from_slice(&[keymap_index]).unwrap();
783        let possible_chords = context.chords_for_keymap_index(keymap_index);
784
785        Self {
786            pressed_indices,
787            possible_chords,
788            marker: PhantomData,
789        }
790    }
791
792    /// Finds the chord state amongst possible_chords which is satisfied (if it exists).
793    fn satisfied_chord(&self) -> Option<&ChordState<MAX_CHORD_SIZE>> {
794        self.possible_chords
795            .iter()
796            .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
797    }
798
799    fn check_resolution(&self) -> PendingChordState {
800        match self.possible_chords.as_slice() {
801            [ChordState {
802                index,
803                is_satisfied,
804                ..
805            }] if *is_satisfied => {
806                // Only one chord is satisfied by pressed indices.
807                //
808                // This resolves the chord.
809                PendingChordState::Resolved(ChordResolution::Chord(*index as u8))
810            }
811            [] => {
812                // Otherwise, this key state resolves to "Passthrough",
813                //  since it has been interrupted by an unrelated key press.
814                PendingChordState::Resolved(ChordResolution::Passthrough)
815            }
816            satisfiable_chords => {
817                // Overlapping chords.
818                PendingChordState::Pending(
819                    satisfiable_chords
820                        .iter()
821                        .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
822                        .map(|&ChordState { index, .. }| index as u8),
823                )
824            }
825        }
826    }
827
828    /// Handle PKS for primary chorded key.
829    pub fn handle_event(
830        &mut self,
831        keymap_index: u16,
832        event: key::Event<Event>,
833    ) -> Option<ChordResolution> {
834        match event {
835            key::Event::Key {
836                keymap_index: _ev_idx,
837                key_event: Event::Timeout,
838            } => {
839                // Timed out before chord unambiguously resolved.
840                let maybe_satisfied_chord_id = self
841                    .satisfied_chord()
842                    .map(|chord_state| chord_state.index as u8);
843                match maybe_satisfied_chord_id {
844                    Some(satisfied_chord_id) => Some(ChordResolution::Chord(satisfied_chord_id)),
845                    _ => Some(ChordResolution::Passthrough),
846                }
847            }
848            key::Event::Input(input::Event::Press {
849                keymap_index: pressed_keymap_index,
850            }) => {
851                // Another key was pressed.
852
853                let maybe_satisfied_chord_id = self
854                    .satisfied_chord()
855                    .map(|chord_state| chord_state.index as u8);
856
857                // Update pressed_indices.
858                let pos = self
859                    .pressed_indices
860                    .binary_search(&keymap_index)
861                    .unwrap_or_else(|e| e);
862                let push_res = self.pressed_indices.insert(pos, pressed_keymap_index);
863                // pressed_indices has capacity of MAX_CHORD_SIZE.
864                // pressed_indices will only be full without resolving
865                // if multiple chords with max chord size
866                //  having the same indices.
867                if push_res.is_err() {
868                    panic!();
869                }
870
871                // Chords only remain possible if they have the pressed keymap index.
872                self.possible_chords
873                    .retain(|chord_state| chord_state.chord.has_index(pressed_keymap_index));
874
875                // Re-evaluate the chord satisfaction states.
876                for chord in self.possible_chords.iter_mut() {
877                    chord.is_satisfied = chord.chord.is_satisfied_by(&self.pressed_indices);
878                }
879
880                let resolution = match self.check_resolution() {
881                    PendingChordState::Resolved(resolution) => Some(resolution),
882                    PendingChordState::Pending(_) => None,
883                };
884
885                // If the chord resolution is now passthrough (i.e. no chords satisfiable),
886                // then resolve the chord with the satisfied chord.
887                match (resolution, maybe_satisfied_chord_id) {
888                    (Some(ChordResolution::Passthrough), Some(satisfied_chord_id)) => {
889                        Some(ChordResolution::Chord(satisfied_chord_id))
890                    }
891                    _ => resolution,
892                }
893            }
894            key::Event::Input(input::Event::Release {
895                keymap_index: released_keymap_index,
896            }) => {
897                if released_keymap_index == keymap_index {
898                    let maybe_satisfied_chord_id = self
899                        .satisfied_chord()
900                        .map(|chord_state| chord_state.index as u8);
901
902                    match maybe_satisfied_chord_id {
903                        Some(satisfied_chord_id) => {
904                            Some(ChordResolution::Chord(satisfied_chord_id))
905                        }
906
907                        // This key state resolves to "Passthrough",
908                        //  since it has been released before any chord is satisfied.
909                        None => Some(ChordResolution::Passthrough),
910                    }
911                } else {
912                    None
913                }
914            }
915            _ => None,
916        }
917    }
918}
919
920/// Key state used by [System]. (Chorded keys do not have a key state).
921#[derive(Debug, Clone, Copy, PartialEq)]
922pub struct KeyState;
923
924/// The [key::System] implementation for the chorded key system.
925#[derive(Debug, Clone, Copy, PartialEq)]
926pub struct System<
927    R: Copy + Debug + PartialEq,
928    Keys: Index<
929        usize,
930        Output = Key<
931            R,
932            MAX_CHORDS,
933            MAX_CHORD_SIZE,
934            MAX_OVERLAPPING_CHORD_SIZE,
935            MAX_PRESSED_INDICES,
936        >,
937    >,
938    AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
939    const MAX_CHORDS: usize,
940    const MAX_CHORD_SIZE: usize,
941    const MAX_OVERLAPPING_CHORD_SIZE: usize,
942    const MAX_PRESSED_INDICES: usize,
943> {
944    keys: Keys,
945    auxiliary_keys: AuxiliaryKeys,
946}
947
948impl<
949        R: Copy + Debug + PartialEq,
950        Keys: Index<
951            usize,
952            Output = Key<
953                R,
954                MAX_CHORDS,
955                MAX_CHORD_SIZE,
956                MAX_OVERLAPPING_CHORD_SIZE,
957                MAX_PRESSED_INDICES,
958            >,
959        >,
960        AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
961        const MAX_CHORDS: usize,
962        const MAX_CHORD_SIZE: usize,
963        const MAX_OVERLAPPING_CHORD_SIZE: usize,
964        const MAX_PRESSED_INDICES: usize,
965    >
966    System<
967        R,
968        Keys,
969        AuxiliaryKeys,
970        MAX_CHORDS,
971        MAX_CHORD_SIZE,
972        MAX_OVERLAPPING_CHORD_SIZE,
973        MAX_PRESSED_INDICES,
974    >
975{
976    /// Constructs a new [System] with the given key data.
977    pub const fn new(keys: Keys, auxiliary_keys: AuxiliaryKeys) -> Self {
978        Self {
979            keys,
980            auxiliary_keys,
981        }
982    }
983}
984
985impl<
986        R: Copy + Debug + PartialEq,
987        Keys: Debug
988            + Index<
989                usize,
990                Output = Key<
991                    R,
992                    MAX_CHORDS,
993                    MAX_CHORD_SIZE,
994                    MAX_OVERLAPPING_CHORD_SIZE,
995                    MAX_PRESSED_INDICES,
996                >,
997            >,
998        AuxiliaryKeys: Debug
999            + Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
1000        const MAX_CHORDS: usize,
1001        const MAX_CHORD_SIZE: usize,
1002        const MAX_OVERLAPPING_CHORD_SIZE: usize,
1003        const MAX_PRESSED_INDICES: usize,
1004    > key::System<R>
1005    for System<
1006        R,
1007        Keys,
1008        AuxiliaryKeys,
1009        MAX_CHORDS,
1010        MAX_CHORD_SIZE,
1011        MAX_OVERLAPPING_CHORD_SIZE,
1012        MAX_PRESSED_INDICES,
1013    >
1014{
1015    type Ref = Ref;
1016    type Context = Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1017    type Event = Event;
1018    type PendingKeyState = PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1019    type KeyState = KeyState;
1020
1021    fn new_pressed_key(
1022        &self,
1023        keymap_index: u16,
1024        context: &Self::Context,
1025        key_ref: Ref,
1026    ) -> (
1027        key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
1028        key::KeyEvents<Self::Event>,
1029    ) {
1030        match key_ref {
1031            Ref::Chorded(i) => self.keys[i as usize].new_pressed_key(context, keymap_index),
1032            Ref::Auxiliary(i) => {
1033                self.auxiliary_keys[i as usize].new_pressed_key(context, keymap_index)
1034            }
1035        }
1036    }
1037
1038    fn update_pending_state(
1039        &self,
1040        pending_state: &mut Self::PendingKeyState,
1041        keymap_index: u16,
1042        context: &Self::Context,
1043        key_ref: Ref,
1044        event: key::Event<Self::Event>,
1045    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
1046        match key_ref {
1047            Ref::Chorded(i) => self.keys[i as usize].update_pending_state(
1048                pending_state,
1049                keymap_index,
1050                context,
1051                event,
1052            ),
1053            Ref::Auxiliary(i) => self.auxiliary_keys[i as usize].update_pending_state(
1054                pending_state,
1055                keymap_index,
1056                event,
1057            ),
1058        }
1059    }
1060
1061    fn update_state(
1062        &self,
1063        _key_state: &mut Self::KeyState,
1064        _key_ref: &Self::Ref,
1065        _context: &Self::Context,
1066        _keymap_index: u16,
1067        _event: key::Event<Self::Event>,
1068    ) -> key::KeyEvents<Self::Event> {
1069        panic!()
1070    }
1071
1072    fn key_output(
1073        &self,
1074        _key_ref: &Self::Ref,
1075        _key_state: &Self::KeyState,
1076    ) -> Option<key::KeyOutput> {
1077        panic!()
1078    }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083    use super::*;
1084
1085    use key::keyboard;
1086
1087    const MAX_CHORDS: usize = 4;
1088    const MAX_CHORD_SIZE: usize = 16;
1089    const MAX_PRESSED_INDICES: usize = MAX_CHORD_SIZE * 2;
1090
1091    const DEFAULT_CONTEXT: Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES> =
1092        Context::from_config(Config::new());
1093
1094    type AuxiliaryKey =
1095        super::AuxiliaryKey<keyboard::Ref, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1096    type PendingKeyState = super::PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1097
1098    #[test]
1099    fn test_sizeof_ref() {
1100        assert_eq!(2, core::mem::size_of::<Ref>());
1101    }
1102
1103    #[test]
1104    fn test_sizeof_event() {
1105        assert_eq!(2, core::mem::size_of::<Event>());
1106    }
1107
1108    #[test]
1109    fn test_timeout_resolves_unsatisfied_aux_state_as_passthrough_key() {
1110        // Assemble: an Auxilary chorded key, and its PKS.
1111        let context = DEFAULT_CONTEXT;
1112        let expected_ref = keyboard::Ref::KeyCode(0x04);
1113        let _chorded_key = AuxiliaryKey::new(expected_ref);
1114        let keymap_index: u16 = 0;
1115        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1116
1117        // Act: handle a timeout ev.
1118        let timeout_ev = key::Event::key_event(keymap_index, Event::Timeout);
1119        let actual_resolution = pks.handle_event(keymap_index, timeout_ev);
1120
1121        // Assert
1122        let expected_resolution = Some(ChordResolution::Passthrough);
1123        assert_eq!(expected_resolution, actual_resolution);
1124    }
1125
1126    #[test]
1127    fn test_press_non_chorded_key_resolves_aux_state_as_interrupted() {
1128        // Assemble: an Auxilary chorded key, and its PKS.
1129        let context = DEFAULT_CONTEXT;
1130        let expected_ref = keyboard::Ref::KeyCode(0x04);
1131        let _chorded_key = AuxiliaryKey::new(expected_ref);
1132        let keymap_index: u16 = 0;
1133        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1134
1135        // Act: handle a key press, for an index that's not part of any chord.
1136        let non_chord_press = input::Event::Press { keymap_index: 9 }.into();
1137        let actual_resolution = pks.handle_event(keymap_index, non_chord_press);
1138
1139        // Assert
1140        let expected_resolution = Some(ChordResolution::Passthrough);
1141        assert_eq!(expected_resolution, actual_resolution);
1142    }
1143
1144    // "unambiguous" in the sense that the chord
1145    // is not overlapped by another chord.
1146    // e.g. chord "01" is overlapped by chord "012",
1147    //  and "pressed {0, 1}" would be 'ambiguous';
1148    //  wheres "pressed {0, 1, 2}" would be 'unambiguous'.
1149
1150    #[test]
1151    fn test_press_chorded_key_resolves_unambiguous_aux_state_as_chord() {
1152        // Assemble: an Auxilary chorded key, and its PKS, with chord 01.
1153        let mut context = Context::from_config(Config {
1154            chords: Slice::from_slice(&[ChordIndices::from_slice(&[0, 1])]),
1155            ..Config::new()
1156        });
1157        let passthrough = keyboard::Ref::KeyCode(0x04);
1158        let _chorded_key = AuxiliaryKey::new(passthrough);
1159        let keymap_index: u16 = 0;
1160        context.handle_event(key::Event::Input(input::Event::Press { keymap_index: 0 }));
1161        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1162
1163        // Act: handle a key press, for an index that completes (satisfies unambiguously) the chord.
1164        let chord_press = input::Event::Press { keymap_index: 1 }.into();
1165        let actual_resolution = pks.handle_event(keymap_index, chord_press);
1166
1167        // Assert: resolved aux key should have no events, should have (resolved) no output.
1168        let expected_resolution = Some(ChordResolution::Chord(0));
1169        assert_eq!(expected_resolution, actual_resolution);
1170    }
1171
1172    #[test]
1173    fn test_release_pending_aux_state_resolves_as_tapped_key() {
1174        // Assemble: an Auxilary chorded key, and its PKS.
1175        let context = DEFAULT_CONTEXT;
1176        let expected_ref = keyboard::Ref::KeyCode(0x04);
1177        let _chorded_key = AuxiliaryKey::new(expected_ref);
1178        let keymap_index: u16 = 0;
1179        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1180
1181        // Act: handle a key press, for an index that's not part of any chord.
1182        let chorded_key_release = input::Event::Release { keymap_index }.into();
1183        let actual_resolution = pks.handle_event(keymap_index, chorded_key_release);
1184
1185        // Assert
1186        let expected_resolution = Some(ChordResolution::Passthrough);
1187        assert_eq!(expected_resolution, actual_resolution);
1188    }
1189}