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