Skip to main content

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