Skip to main content

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                    let _ = res.insert(pos, i);
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) => {
306                let mut chords = heapless::Vec::new();
307                let _ = chords.push(chord_state);
308                chords
309            }
310            None => {
311                let chords_indices_span = self.pressed_chords_indices_span();
312                self.config
313                    .chords
314                    .iter()
315                    .enumerate()
316                    // filter: satisfiable chords
317                    .filter(|&(_index, chord)| chord.has_index(keymap_index))
318                    .filter(|&(_index, chord)| {
319                        // Filter out chords which overlap with resolved active chords.
320                        chords_indices_span.is_empty()
321                            || chord.indices.iter().all(|&i| {
322                                // The chord index is not part of the pressed chords indices span.
323                                chords_indices_span.binary_search(&i).is_err()
324                            })
325                    })
326                    .map(|(index, &chord)| ChordState {
327                        index,
328                        chord,
329                        is_satisfied: false,
330                    })
331                    .collect()
332            }
333        }
334    }
335
336    fn insert_pressed_index(&mut self, pos: usize, index: u16) {
337        if self.pressed_indices.is_empty() {
338            return;
339        }
340
341        let mut i = self.pressed_indices.len() - 1;
342        while i > pos {
343            self.pressed_indices[i] = self.pressed_indices[i - 1];
344            i -= 1;
345        }
346
347        self.pressed_indices[pos] = Some(index);
348    }
349
350    fn remove_pressed_index(&mut self, pos: usize) {
351        if self.pressed_indices.is_empty() {
352            return;
353        }
354
355        let mut i = pos;
356        while i < self.pressed_indices.len() - 1 {
357            self.pressed_indices[i] = self.pressed_indices[i + 1];
358            i += 1;
359        }
360
361        self.pressed_indices[self.pressed_indices.len() - 1] = None;
362    }
363
364    fn press_index(&mut self, index: u16) {
365        match self
366            .pressed_indices
367            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
368        {
369            Ok(_) => {}
370            Err(pos) => self.insert_pressed_index(pos, index),
371        }
372    }
373
374    fn release_index(&mut self, index: u16) {
375        if let Ok(pos) = self
376            .pressed_indices
377            .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
378        {
379            self.remove_pressed_index(pos)
380        }
381    }
382
383    /// Updates the context for the given key event.
384    fn handle_event(&mut self, event: key::Event<Event>) {
385        match event {
386            key::Event::Input(input::Event::Press { keymap_index }) => {
387                self.press_index(keymap_index);
388
389                // Consider whether the key press supports
390                //  ignoring required idle time for a chorded key,
391                //  or supports quickly re-tapping a chorded key.
392                let span = self.pressed_chords_indices_span();
393                if span.contains(&keymap_index) {
394                    // Key presses of an active chord ignore required idle time.
395                    self.ignore_idle_time = true;
396                } else {
397                    // Otherwise, check against the latest resolved chord.
398                    if let Some(chord_id) = self.latest_resolved_chord {
399                        let chord_indices = self.config.chords[chord_id as usize];
400                        self.ignore_idle_time = chord_indices.has_index(keymap_index);
401                    } else {
402                        self.ignore_idle_time = false;
403
404                        // Chords not active, and this press was outside the latest active chord,
405                        //  so clear the latest resolved chord.
406                        self.latest_resolved_chord = None;
407                    }
408                }
409            }
410            key::Event::Input(input::Event::Release { keymap_index }) => {
411                self.release_index(keymap_index);
412
413                // Ensure every chord which includes this keymap index
414                //  is not marked as 'pressed'.
415                self.config
416                    .chords
417                    .iter()
418                    .enumerate()
419                    .for_each(|(chord_id, chord_indices)| {
420                        if chord_indices.has_index(keymap_index) {
421                            self.pressed_chords[chord_id] = false;
422                        }
423                    });
424            }
425            key::Event::Key {
426                keymap_index: _,
427                key_event: Event::ChordResolved(ChordResolution::Chord(chord_id)),
428            } => {
429                self.pressed_chords[chord_id as usize] = true;
430                self.latest_resolved_chord = Some(chord_id);
431            }
432            _ => {}
433        }
434    }
435}
436
437impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
438    key::Context for Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
439{
440    type Event = Event;
441
442    fn handle_event(&mut self, event: key::Event<Self::Event>) -> key::KeyEvents<Self::Event> {
443        self.handle_event(event);
444        key::KeyEvents::no_events()
445    }
446}
447
448/// Primary Chorded key (with a passthrough key).
449///
450/// The primary key is the key with the lowest index in the chord,
451///  and has the key used for the resolved chord.
452#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
453pub struct Key<
454    R: Copy,
455    const MAX_CHORDS: usize,
456    const MAX_CHORD_SIZE: usize,
457    const MAX_OVERLAPPING_CHORD_SIZE: usize,
458    const MAX_PRESSED_INDICES: usize,
459> {
460    /// The chorded key
461    pub chords: Slice<(ChordId, R), MAX_OVERLAPPING_CHORD_SIZE>,
462    /// The passthrough key
463    pub passthrough: R,
464    #[serde(default)]
465    marker: PhantomData<(
466        [(); MAX_CHORDS],
467        [(); MAX_CHORD_SIZE],
468        [(); MAX_PRESSED_INDICES],
469    )>,
470}
471
472impl<
473        R: Copy,
474        const MAX_CHORDS: usize,
475        const MAX_CHORD_SIZE: usize,
476        const MAX_OVERLAPPING_CHORD_SIZE: usize,
477        const MAX_PRESSED_INDICES: usize,
478    > Key<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_OVERLAPPING_CHORD_SIZE, MAX_PRESSED_INDICES>
479{
480    /// Constructs new pressed key.
481    pub fn new_pressed_key(
482        &self,
483        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
484        keymap_index: u16,
485    ) -> (
486        key::PressedKeyResult<
487            R,
488            PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
489            KeyState,
490        >,
491        key::KeyEvents<Event>,
492    ) {
493        let pks = PendingKeyState::new(context, keymap_index);
494
495        let chord_resolution = if context.sufficient_idle_time() {
496            pks.check_resolution()
497        } else {
498            PendingChordState::Resolved(ChordResolution::Passthrough)
499        };
500
501        if let PendingChordState::Resolved(resolution) = chord_resolution {
502            let maybe_new_key_ref = match resolution {
503                ChordResolution::Chord(resolved_chord_id) => {
504                    // Whether the resolved chord is associated with this key.
505                    // (i.e. the resolved chord's primary keymap index is this keymap index).
506                    if let Some(resolved_chord_indices) =
507                        context.config.chords.get(resolved_chord_id as usize)
508                    {
509                        if resolved_chord_indices.as_slice()[0] == keymap_index {
510                            if let Some((_, new_key_ref)) = self
511                                .chords
512                                .iter()
513                                .find(|(ch_id, _)| *ch_id == resolved_chord_id)
514                            {
515                                Some(*new_key_ref)
516                            } else {
517                                panic!("check_resolution has invalid chord id")
518                            }
519                        } else {
520                            None
521                        }
522                    } else {
523                        panic!("check_resolution has invalid chord id")
524                    }
525                }
526                ChordResolution::Passthrough => Some(self.passthrough),
527            };
528
529            if let Some(new_key_ref) = maybe_new_key_ref {
530                let pkr =
531                    key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
532                let pke = key::KeyEvents::no_events();
533
534                (pkr, pke)
535            } else {
536                let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
537                let pke = key::KeyEvents::no_events();
538                (pkr, pke)
539            }
540        } else {
541            let pkr = key::PressedKeyResult::Pending(pks);
542
543            let timeout_ev = Event::Timeout;
544            let sch_ev = key::ScheduledEvent::after(
545                context.config.timeout,
546                key::Event::key_event(keymap_index, timeout_ev),
547            );
548            let pke = key::KeyEvents::scheduled_event(sch_ev);
549
550            (pkr, pke)
551        }
552    }
553
554    /// Constructs new chorded key.
555    pub const fn new(chords: &[(ChordId, R)], passthrough: R) -> Self {
556        let chords = Slice::from_slice(chords);
557        Key {
558            chords,
559            passthrough,
560            marker: PhantomData,
561        }
562    }
563
564    fn update_pending_state(
565        &self,
566        pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
567        keymap_index: u16,
568        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
569        event: key::Event<Event>,
570    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
571        let ch_state = pending_state.handle_event(keymap_index, event);
572
573        // Whether handling the event resulted in a chord resolution.
574        if let Some(ch_state) = ch_state {
575            let maybe_new_key_ref = match ch_state {
576                ChordResolution::Chord(resolved_chord_id) => {
577                    // Whether the resolved chord is associated with this key.
578                    // (i.e. the resolved chord's primary keymap index is this keymap index).
579                    if let Some(resolved_chord_indices) =
580                        context.config.chords.get(resolved_chord_id as usize)
581                    {
582                        if resolved_chord_indices.as_slice()[0] == keymap_index {
583                            if let Some((_, key_ref)) = self
584                                .chords
585                                .iter()
586                                .find(|(ch_id, _)| *ch_id == resolved_chord_id)
587                            {
588                                Some(*key_ref)
589                            } else {
590                                panic!("event's chord resolution has invalid chord id")
591                            }
592                        } else {
593                            None
594                        }
595                    } else {
596                        panic!("event's chord resolution has invalid chord id")
597                    }
598                }
599                ChordResolution::Passthrough => Some(self.passthrough),
600            };
601
602            let ch_r_ev = Event::ChordResolved(ch_state);
603            let sch_ev =
604                key::ScheduledEvent::immediate(key::Event::key_event(keymap_index, ch_r_ev));
605
606            if let Some(new_key_ref) = maybe_new_key_ref {
607                let pke = key::KeyEvents::scheduled_event(sch_ev);
608
609                (Some(key::NewPressedKey::key(new_key_ref)), pke)
610            } else {
611                let pke = key::KeyEvents::scheduled_event(sch_ev);
612                (Some(key::NewPressedKey::no_op()), pke)
613            }
614        } else {
615            (None, key::KeyEvents::no_events())
616        }
617    }
618}
619
620/// Auxiliary chorded key (with a passthrough key).
621///
622/// The auxiliary keys are chorded keys,
623///  but don't store the resolved chord key.
624/// (i.e. After te primary chorded key, the remaining keys
625///  in the chord are defined with auxiliary chorded keys).
626#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
627pub struct AuxiliaryKey<
628    R,
629    const MAX_CHORDS: usize,
630    const MAX_CHORD_SIZE: usize,
631    const MAX_PRESSED_INDICES: usize,
632> {
633    /// The passthrough key
634    pub passthrough: R,
635    #[serde(default)]
636    marker: PhantomData<(
637        [(); MAX_CHORDS],
638        [(); MAX_CHORD_SIZE],
639        [(); MAX_PRESSED_INDICES],
640    )>,
641}
642
643impl<
644        R: Copy,
645        const MAX_CHORDS: usize,
646        const MAX_CHORD_SIZE: usize,
647        const MAX_PRESSED_INDICES: usize,
648    > AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
649{
650    /// Constructs new pressed key.
651    pub fn new_pressed_key(
652        &self,
653        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
654        keymap_index: u16,
655    ) -> (
656        key::PressedKeyResult<
657            R,
658            PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
659            KeyState,
660        >,
661        key::KeyEvents<Event>,
662    ) {
663        let pks = PendingKeyState::new(context, keymap_index);
664
665        let chord_resolution = if context.sufficient_idle_time() {
666            pks.check_resolution()
667        } else {
668            PendingChordState::Resolved(ChordResolution::Passthrough)
669        };
670
671        if let PendingChordState::Resolved(resolution) = chord_resolution {
672            match resolution {
673                ChordResolution::Chord(_resolved_chord_id) => {
674                    let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
675                    let pke = key::KeyEvents::no_events();
676
677                    (pkr, pke)
678                }
679                ChordResolution::Passthrough => {
680                    let new_key_ref = self.passthrough;
681                    let pkr =
682                        key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
683                    let pke = key::KeyEvents::no_events();
684                    (pkr, pke)
685                }
686            }
687        } else {
688            let pkr = key::PressedKeyResult::Pending(pks);
689
690            let timeout_ev = Event::Timeout;
691            let sch_ev = key::ScheduledEvent::after(
692                context.config.timeout,
693                key::Event::key_event(keymap_index, timeout_ev),
694            );
695            let pke = key::KeyEvents::scheduled_event(sch_ev);
696
697            (pkr, pke)
698        }
699    }
700
701    /// Constructs new auxiliary chorded key.
702    pub const fn new(passthrough: R) -> Self {
703        AuxiliaryKey {
704            passthrough,
705            marker: PhantomData,
706        }
707    }
708
709    fn update_pending_state(
710        &self,
711        pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
712        keymap_index: u16,
713        event: key::Event<Event>,
714    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
715        let ch_state = pending_state.handle_event(keymap_index, event);
716        if let Some(ChordResolution::Passthrough) = ch_state {
717            let ch_r_ev = Event::ChordResolved(ChordResolution::Passthrough);
718            let sch_ev =
719                key::ScheduledEvent::immediate(key::Event::key_event(keymap_index, ch_r_ev));
720            let pke = key::KeyEvents::scheduled_event(sch_ev);
721
722            (Some(key::NewPressedKey::key(self.passthrough)), pke)
723        } else if let Some(ChordResolution::Chord(resolved_chord_id)) = ch_state {
724            let ch_r_ev = Event::ChordResolved(ChordResolution::Chord(resolved_chord_id));
725            let pke = key::KeyEvents::event(key::Event::key_event(keymap_index, ch_r_ev));
726
727            (Some(key::NewPressedKey::no_op()), pke)
728        } else {
729            (None, key::KeyEvents::no_events())
730        }
731    }
732}
733
734/// Events for chorded keys.
735#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
736pub enum Event {
737    /// The chorded key was resolved.
738    ChordResolved(ChordResolution),
739
740    /// Timed out waiting for chord to be satisfied.
741    Timeout,
742}
743
744/// Whether the pressed key state has resolved to a chord or not.
745#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
746pub enum ChordResolution {
747    /// Resolved as chord.
748    Chord(ChordId),
749    /// Resolved as passthrough key.
750    Passthrough,
751}
752
753/// The resolution state of a chorded key.
754#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
755pub enum PendingChordState {
756    /// The key state is resolved (as chord or as passthrough).
757    Resolved(ChordResolution),
758    /// The key chord state is pending.
759    ///
760    /// The chord may be pending with the ID of a satisfied chord.
761    Pending(Option<ChordId>),
762}
763
764/// State for pressed keys.
765#[derive(Debug, Clone, PartialEq)]
766pub struct PendingKeyState<
767    const MAX_CHORDS: usize,
768    const MAX_CHORD_SIZE: usize,
769    const MAX_PRESSED_INDICES: usize,
770> {
771    /// The keymap indices which have been pressed while the key is pending.
772    pressed_indices: heapless::Vec<u16, { MAX_CHORD_SIZE }>,
773    /// The chords which this pending key could resolve to.
774    possible_chords: heapless::Vec<ChordState<MAX_CHORD_SIZE>, { MAX_CHORDS }>,
775    marker: PhantomData<[(); MAX_PRESSED_INDICES]>,
776}
777
778impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
779    PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
780{
781    /// Constructs a new [PendingKeyState].
782    pub fn new(
783        context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
784        keymap_index: u16,
785    ) -> Self {
786        let mut pressed_indices = heapless::Vec::new();
787        let _ = pressed_indices.push(keymap_index);
788        let possible_chords = context.chords_for_keymap_index(keymap_index);
789
790        Self {
791            pressed_indices,
792            possible_chords,
793            marker: PhantomData,
794        }
795    }
796
797    /// Finds the chord state amongst possible_chords which is satisfied (if it exists).
798    fn satisfied_chord(&self) -> Option<&ChordState<MAX_CHORD_SIZE>> {
799        self.possible_chords
800            .iter()
801            .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
802    }
803
804    fn check_resolution(&self) -> PendingChordState {
805        match self.possible_chords.as_slice() {
806            [ChordState {
807                index,
808                is_satisfied,
809                ..
810            }] if *is_satisfied => {
811                // Only one chord is satisfied by pressed indices.
812                //
813                // This resolves the chord.
814                PendingChordState::Resolved(ChordResolution::Chord(*index as u8))
815            }
816            [] => {
817                // Otherwise, this key state resolves to "Passthrough",
818                //  since it has been interrupted by an unrelated key press.
819                PendingChordState::Resolved(ChordResolution::Passthrough)
820            }
821            satisfiable_chords => {
822                // Overlapping chords.
823                PendingChordState::Pending(
824                    satisfiable_chords
825                        .iter()
826                        .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
827                        .map(|&ChordState { index, .. }| index as u8),
828                )
829            }
830        }
831    }
832
833    /// Handle PKS for primary chorded key.
834    pub fn handle_event(
835        &mut self,
836        keymap_index: u16,
837        event: key::Event<Event>,
838    ) -> Option<ChordResolution> {
839        match event {
840            key::Event::Key {
841                keymap_index: _ev_idx,
842                key_event: Event::Timeout,
843            } => {
844                // Timed out before chord unambiguously resolved.
845                let maybe_satisfied_chord_id = self
846                    .satisfied_chord()
847                    .map(|chord_state| chord_state.index as u8);
848                match maybe_satisfied_chord_id {
849                    Some(satisfied_chord_id) => Some(ChordResolution::Chord(satisfied_chord_id)),
850                    _ => Some(ChordResolution::Passthrough),
851                }
852            }
853            key::Event::Input(input::Event::Press {
854                keymap_index: pressed_keymap_index,
855            }) => {
856                // Another key was pressed.
857
858                let maybe_satisfied_chord_id = self
859                    .satisfied_chord()
860                    .map(|chord_state| chord_state.index as u8);
861
862                // Update pressed_indices.
863                let pos = self
864                    .pressed_indices
865                    .binary_search(&keymap_index)
866                    .unwrap_or_else(|e| e);
867                let push_res = self.pressed_indices.insert(pos, pressed_keymap_index);
868                // pressed_indices has capacity of MAX_CHORD_SIZE.
869                // pressed_indices will only be full without resolving
870                // if multiple chords with max chord size
871                //  having the same indices.
872                if push_res.is_err() {
873                    panic!();
874                }
875
876                // Chords only remain possible if they have the pressed keymap index.
877                self.possible_chords
878                    .retain(|chord_state| chord_state.chord.has_index(pressed_keymap_index));
879
880                // Re-evaluate the chord satisfaction states.
881                for chord in self.possible_chords.iter_mut() {
882                    chord.is_satisfied = chord.chord.is_satisfied_by(&self.pressed_indices);
883                }
884
885                let resolution = match self.check_resolution() {
886                    PendingChordState::Resolved(resolution) => Some(resolution),
887                    PendingChordState::Pending(_) => None,
888                };
889
890                // If the chord resolution is now passthrough (i.e. no chords satisfiable),
891                // then resolve the chord with the satisfied chord.
892                match (resolution, maybe_satisfied_chord_id) {
893                    (Some(ChordResolution::Passthrough), Some(satisfied_chord_id)) => {
894                        Some(ChordResolution::Chord(satisfied_chord_id))
895                    }
896                    _ => resolution,
897                }
898            }
899            key::Event::Input(input::Event::Release {
900                keymap_index: released_keymap_index,
901            }) => {
902                if released_keymap_index == keymap_index {
903                    let maybe_satisfied_chord_id = self
904                        .satisfied_chord()
905                        .map(|chord_state| chord_state.index as u8);
906
907                    match maybe_satisfied_chord_id {
908                        Some(satisfied_chord_id) => {
909                            Some(ChordResolution::Chord(satisfied_chord_id))
910                        }
911
912                        // This key state resolves to "Passthrough",
913                        //  since it has been released before any chord is satisfied.
914                        None => Some(ChordResolution::Passthrough),
915                    }
916                } else {
917                    None
918                }
919            }
920            _ => None,
921        }
922    }
923}
924
925/// Key state used by [System]. (Chorded keys do not have a key state).
926#[derive(Debug, Clone, Copy, PartialEq)]
927pub struct KeyState;
928
929/// The [key::System] implementation for the chorded key system.
930#[derive(Debug, Clone, Copy, PartialEq)]
931pub struct System<
932    R: Copy + Debug + PartialEq,
933    Keys: Index<
934        usize,
935        Output = Key<
936            R,
937            MAX_CHORDS,
938            MAX_CHORD_SIZE,
939            MAX_OVERLAPPING_CHORD_SIZE,
940            MAX_PRESSED_INDICES,
941        >,
942    >,
943    AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
944    const MAX_CHORDS: usize,
945    const MAX_CHORD_SIZE: usize,
946    const MAX_OVERLAPPING_CHORD_SIZE: usize,
947    const MAX_PRESSED_INDICES: usize,
948> {
949    keys: Keys,
950    auxiliary_keys: AuxiliaryKeys,
951}
952
953impl<
954        R: Copy + Debug + PartialEq,
955        Keys: Index<
956            usize,
957            Output = Key<
958                R,
959                MAX_CHORDS,
960                MAX_CHORD_SIZE,
961                MAX_OVERLAPPING_CHORD_SIZE,
962                MAX_PRESSED_INDICES,
963            >,
964        >,
965        AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
966        const MAX_CHORDS: usize,
967        const MAX_CHORD_SIZE: usize,
968        const MAX_OVERLAPPING_CHORD_SIZE: usize,
969        const MAX_PRESSED_INDICES: usize,
970    >
971    System<
972        R,
973        Keys,
974        AuxiliaryKeys,
975        MAX_CHORDS,
976        MAX_CHORD_SIZE,
977        MAX_OVERLAPPING_CHORD_SIZE,
978        MAX_PRESSED_INDICES,
979    >
980{
981    /// Constructs a new [System] with the given key data.
982    pub const fn new(keys: Keys, auxiliary_keys: AuxiliaryKeys) -> Self {
983        Self {
984            keys,
985            auxiliary_keys,
986        }
987    }
988}
989
990impl<
991        R: Copy + Debug + PartialEq,
992        Keys: Debug
993            + Index<
994                usize,
995                Output = Key<
996                    R,
997                    MAX_CHORDS,
998                    MAX_CHORD_SIZE,
999                    MAX_OVERLAPPING_CHORD_SIZE,
1000                    MAX_PRESSED_INDICES,
1001                >,
1002            >,
1003        AuxiliaryKeys: Debug
1004            + Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
1005        const MAX_CHORDS: usize,
1006        const MAX_CHORD_SIZE: usize,
1007        const MAX_OVERLAPPING_CHORD_SIZE: usize,
1008        const MAX_PRESSED_INDICES: usize,
1009    > key::System<R>
1010    for System<
1011        R,
1012        Keys,
1013        AuxiliaryKeys,
1014        MAX_CHORDS,
1015        MAX_CHORD_SIZE,
1016        MAX_OVERLAPPING_CHORD_SIZE,
1017        MAX_PRESSED_INDICES,
1018    >
1019{
1020    type Ref = Ref;
1021    type Context = Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1022    type Event = Event;
1023    type PendingKeyState = PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1024    type KeyState = KeyState;
1025
1026    fn new_pressed_key(
1027        &self,
1028        keymap_index: u16,
1029        context: &Self::Context,
1030        key_ref: Ref,
1031    ) -> (
1032        key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
1033        key::KeyEvents<Self::Event>,
1034    ) {
1035        match key_ref {
1036            Ref::Chorded(i) => self.keys[i as usize].new_pressed_key(context, keymap_index),
1037            Ref::Auxiliary(i) => {
1038                self.auxiliary_keys[i as usize].new_pressed_key(context, keymap_index)
1039            }
1040        }
1041    }
1042
1043    fn update_pending_state(
1044        &self,
1045        pending_state: &mut Self::PendingKeyState,
1046        keymap_index: u16,
1047        context: &Self::Context,
1048        key_ref: Ref,
1049        event: key::Event<Self::Event>,
1050    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
1051        match key_ref {
1052            Ref::Chorded(i) => self.keys[i as usize].update_pending_state(
1053                pending_state,
1054                keymap_index,
1055                context,
1056                event,
1057            ),
1058            Ref::Auxiliary(i) => self.auxiliary_keys[i as usize].update_pending_state(
1059                pending_state,
1060                keymap_index,
1061                event,
1062            ),
1063        }
1064    }
1065
1066    fn update_state(
1067        &self,
1068        _key_state: &mut Self::KeyState,
1069        _key_ref: &Self::Ref,
1070        _context: &Self::Context,
1071        _keymap_index: u16,
1072        _event: key::Event<Self::Event>,
1073    ) -> key::KeyEvents<Self::Event> {
1074        panic!()
1075    }
1076
1077    fn key_output(
1078        &self,
1079        _key_ref: &Self::Ref,
1080        _key_state: &Self::KeyState,
1081    ) -> Option<key::KeyOutput> {
1082        panic!()
1083    }
1084}
1085
1086#[cfg(test)]
1087#[allow(clippy::unwrap_used, clippy::expect_used)]
1088mod tests {
1089    use super::*;
1090
1091    use key::keyboard;
1092
1093    const MAX_CHORDS: usize = 4;
1094    const MAX_CHORD_SIZE: usize = 16;
1095    const MAX_PRESSED_INDICES: usize = MAX_CHORD_SIZE * 2;
1096
1097    const DEFAULT_CONTEXT: Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES> =
1098        Context::from_config(Config::new());
1099
1100    type AuxiliaryKey =
1101        super::AuxiliaryKey<keyboard::Ref, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1102    type PendingKeyState = super::PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1103
1104    #[test]
1105    fn test_sizeof_ref() {
1106        assert_eq!(2, core::mem::size_of::<Ref>());
1107    }
1108
1109    #[test]
1110    fn test_sizeof_event() {
1111        assert_eq!(2, core::mem::size_of::<Event>());
1112    }
1113
1114    #[test]
1115    fn test_timeout_resolves_unsatisfied_aux_state_as_passthrough_key() {
1116        // Assemble: an Auxilary chorded key, and its PKS.
1117        let context = DEFAULT_CONTEXT;
1118        let expected_ref = keyboard::Ref::KeyCode(0x04);
1119        let _chorded_key = AuxiliaryKey::new(expected_ref);
1120        let keymap_index: u16 = 0;
1121        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1122
1123        // Act: handle a timeout ev.
1124        let timeout_ev = key::Event::key_event(keymap_index, Event::Timeout);
1125        let actual_resolution = pks.handle_event(keymap_index, timeout_ev);
1126
1127        // Assert
1128        let expected_resolution = Some(ChordResolution::Passthrough);
1129        assert_eq!(expected_resolution, actual_resolution);
1130    }
1131
1132    #[test]
1133    fn test_press_non_chorded_key_resolves_aux_state_as_interrupted() {
1134        // Assemble: an Auxilary chorded key, and its PKS.
1135        let context = DEFAULT_CONTEXT;
1136        let expected_ref = keyboard::Ref::KeyCode(0x04);
1137        let _chorded_key = AuxiliaryKey::new(expected_ref);
1138        let keymap_index: u16 = 0;
1139        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1140
1141        // Act: handle a key press, for an index that's not part of any chord.
1142        let non_chord_press = input::Event::Press { keymap_index: 9 }.into();
1143        let actual_resolution = pks.handle_event(keymap_index, non_chord_press);
1144
1145        // Assert
1146        let expected_resolution = Some(ChordResolution::Passthrough);
1147        assert_eq!(expected_resolution, actual_resolution);
1148    }
1149
1150    // "unambiguous" in the sense that the chord
1151    // is not overlapped by another chord.
1152    // e.g. chord "01" is overlapped by chord "012",
1153    //  and "pressed {0, 1}" would be 'ambiguous';
1154    //  wheres "pressed {0, 1, 2}" would be 'unambiguous'.
1155
1156    #[test]
1157    fn test_press_chorded_key_resolves_unambiguous_aux_state_as_chord() {
1158        // Assemble: an Auxilary chorded key, and its PKS, with chord 01.
1159        let mut context = Context::from_config(Config {
1160            chords: Slice::from_slice(&[ChordIndices::from_slice(&[0, 1])]),
1161            ..Config::new()
1162        });
1163        let passthrough = keyboard::Ref::KeyCode(0x04);
1164        let _chorded_key = AuxiliaryKey::new(passthrough);
1165        let keymap_index: u16 = 0;
1166        context.handle_event(key::Event::Input(input::Event::Press { keymap_index: 0 }));
1167        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1168
1169        // Act: handle a key press, for an index that completes (satisfies unambiguously) the chord.
1170        let chord_press = input::Event::Press { keymap_index: 1 }.into();
1171        let actual_resolution = pks.handle_event(keymap_index, chord_press);
1172
1173        // Assert: resolved aux key should have no events, should have (resolved) no output.
1174        let expected_resolution = Some(ChordResolution::Chord(0));
1175        assert_eq!(expected_resolution, actual_resolution);
1176    }
1177
1178    #[test]
1179    fn test_release_pending_aux_state_resolves_as_tapped_key() {
1180        // Assemble: an Auxilary chorded key, and its PKS.
1181        let context = DEFAULT_CONTEXT;
1182        let expected_ref = keyboard::Ref::KeyCode(0x04);
1183        let _chorded_key = AuxiliaryKey::new(expected_ref);
1184        let keymap_index: u16 = 0;
1185        let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1186
1187        // Act: handle a key press, for an index that's not part of any chord.
1188        let chorded_key_release = input::Event::Release { keymap_index }.into();
1189        let actual_resolution = pks.handle_event(keymap_index, chorded_key_release);
1190
1191        // Assert
1192        let expected_resolution = Some(ChordResolution::Passthrough);
1193        assert_eq!(expected_resolution, actual_resolution);
1194    }
1195}