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#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
11pub enum Ref {
12 Chorded(u8),
14 Auxiliary(u8),
16}
17
18pub type ChordId = u8;
20
21#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
23#[serde(from = "heapless::Vec<u16, MAX_CHORD_SIZE>")]
24pub struct ChordIndices<const MAX_CHORD_SIZE: usize> {
25 indices: Slice<u16, MAX_CHORD_SIZE>,
27}
28
29impl<const MAX_CHORD_SIZE: usize> ChordIndices<MAX_CHORD_SIZE> {
30 pub const fn from_slice(indices: &[u16]) -> ChordIndices<MAX_CHORD_SIZE> {
34 ChordIndices {
35 indices: Slice::from_slice(indices),
36 }
37 }
38
39 pub const fn as_slice(&self) -> &[u16] {
41 self.indices.as_slice()
42 }
43
44 pub fn has_index(&self, index: u16) -> bool {
46 self.as_slice().contains(&index)
47 }
48
49 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#[derive(Deserialize, Clone, Copy, PartialEq)]
65pub struct Config<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> {
66 #[serde(default = "default_timeout")]
74 pub timeout: u16,
75
76 pub chords: Slice<ChordIndices<MAX_CHORD_SIZE>, MAX_CHORDS>,
78
79 pub required_idle_time: Option<u16>,
85}
86
87impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> core::fmt::Debug
88 for Config<MAX_CHORDS, MAX_CHORD_SIZE>
89{
90 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91 f.debug_struct("Config")
92 .field("timeout", &self.timeout)
93 .field("chords", &self.chords.as_slice())
94 .field("required_idle_time", &self.required_idle_time)
95 .finish()
96 }
97}
98
99pub const DEFAULT_TIMEOUT: u16 = 200;
101
102const fn default_timeout() -> u16 {
103 DEFAULT_TIMEOUT
104}
105
106impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> Config<MAX_CHORDS, MAX_CHORD_SIZE> {
107 pub const fn new() -> Self {
109 Self {
110 timeout: DEFAULT_TIMEOUT,
111 chords: Slice::from_slice(&[]),
112 required_idle_time: None,
113 }
114 }
115}
116
117impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize> Default
118 for Config<MAX_CHORDS, MAX_CHORD_SIZE>
119{
120 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[derive(Debug, Clone, PartialEq)]
128pub struct ChordState<const MAX_CHORD_SIZE: usize> {
129 pub index: usize,
131 pub chord: ChordIndices<MAX_CHORD_SIZE>,
133 pub is_satisfied: bool,
135}
136
137struct PressedIndicesDebugHelper<'a, const MAX_PRESSED_INDICES: usize> {
138 pressed_indices: &'a [Option<u16>; MAX_PRESSED_INDICES],
139}
140
141impl<const MAX_PRESSED_INDICES: usize> core::fmt::Debug
142 for PressedIndicesDebugHelper<'_, MAX_PRESSED_INDICES>
143{
144 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145 let last_non_empty_pi_pos = self
147 .pressed_indices
148 .iter()
149 .rposition(|pi| pi.is_some())
150 .map_or(0, |pos| pos + 1);
151 if last_non_empty_pi_pos < MAX_PRESSED_INDICES {
152 f.debug_list()
153 .entries(&self.pressed_indices[..last_non_empty_pi_pos])
154 .finish_non_exhaustive()
155 } else {
156 f.debug_list().entries(&self.pressed_indices[..]).finish()
157 }
158 }
159}
160
161struct PressedChordsDebugHelper<'a, const MAX_CHORDS: usize> {
162 pressed_chords: &'a [bool; MAX_CHORDS],
163}
164
165impl<const MAX_CHORDS: usize> core::fmt::Debug for PressedChordsDebugHelper<'_, MAX_CHORDS> {
166 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
167 let last_true_pc_pos = self
169 .pressed_chords
170 .iter()
171 .rposition(|&pc| pc)
172 .map_or(0, |pos| pos + 1);
173 if last_true_pc_pos < MAX_CHORDS {
174 f.debug_list()
175 .entries(&self.pressed_chords[..last_true_pc_pos])
176 .finish_non_exhaustive()
177 } else {
178 f.debug_list().entries(&self.pressed_chords[..]).finish()
179 }
180 }
181}
182
183#[derive(Clone, Copy, PartialEq)]
185pub struct Context<
186 const MAX_CHORDS: usize,
187 const MAX_CHORD_SIZE: usize,
188 const MAX_PRESSED_INDICES: usize,
189> {
190 config: Config<MAX_CHORDS, MAX_CHORD_SIZE>,
191 pressed_indices: [Option<u16>; MAX_PRESSED_INDICES],
192 pressed_chords: [bool; MAX_CHORDS],
193 idle_time_ms: u32,
194 ignore_idle_time: bool,
195 latest_resolved_chord: Option<ChordId>,
196 activating_index: Option<u16>,
198}
199
200impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize> Debug
201 for Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
202{
203 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204 f.debug_struct("Context")
205 .field("config", &self.config)
206 .field(
207 "pressed_indices",
208 &PressedIndicesDebugHelper {
209 pressed_indices: &self.pressed_indices,
210 },
211 )
212 .field(
213 "pressed_chords",
214 &PressedChordsDebugHelper {
215 pressed_chords: &self.pressed_chords,
216 },
217 )
218 .field("idle_time_ms", &self.idle_time_ms)
219 .field("ignore_idle_time", &self.ignore_idle_time)
220 .field("latest_resolved_chord", &self.latest_resolved_chord)
221 .field("activating_index", &self.activating_index)
222 .finish()
223 }
224}
225
226impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
227 Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
228{
229 pub const fn from_config(config: Config<MAX_CHORDS, MAX_CHORD_SIZE>) -> Self {
231 let pressed_indices = [None; MAX_PRESSED_INDICES];
232 Context {
233 config,
234 pressed_indices,
235 pressed_chords: [false; MAX_CHORDS],
236 idle_time_ms: 0,
237 ignore_idle_time: false,
238 latest_resolved_chord: None,
239 activating_index: None,
240 }
241 }
242
243 pub fn reset(&mut self) {
245 *self = Self::from_config(self.config);
246 }
247
248 pub fn update_keymap_context(
250 &mut self,
251 keymap::KeymapContext { idle_time_ms, .. }: &keymap::KeymapContext,
252 ) {
253 self.idle_time_ms = *idle_time_ms;
254 }
255
256 pub fn is_activator(&self, keymap_index: u16) -> bool {
258 self.activating_index == Some(keymap_index)
259 }
260
261 fn sufficient_idle_time(&self) -> bool {
262 let sufficient_idle_time =
263 self.idle_time_ms >= self.config.required_idle_time.unwrap_or(0) as u32;
264
265 sufficient_idle_time || self.ignore_idle_time
266 }
267
268 fn pressed_chord_with_index(&self, keymap_index: u16) -> Option<ChordState<MAX_CHORD_SIZE>> {
269 self.pressed_chords
270 .iter()
271 .enumerate()
272 .filter_map(|(index, &is_pressed)| {
273 if is_pressed {
274 Some(ChordState {
275 index,
276 chord: self.config.chords[index],
277 is_satisfied: true,
278 })
279 } else {
280 None
281 }
282 })
283 .find(|ChordState { chord, .. }| chord.has_index(keymap_index))
284 }
285
286 fn pressed_chords_indices_span(&self) -> heapless::Vec<u16, MAX_PRESSED_INDICES> {
288 let mut res: heapless::Vec<u16, MAX_PRESSED_INDICES> = heapless::Vec::new();
289
290 let pressed_chords =
291 self.pressed_chords
292 .iter()
293 .enumerate()
294 .filter_map(|(index, &is_pressed)| {
295 if is_pressed {
296 Some(&self.config.chords[index])
297 } else {
298 None
299 }
300 });
301
302 pressed_chords.for_each(|&chord| {
303 for &i in chord.as_slice() {
304 if let Err(pos) = res.binary_search(&i) {
305 let _ = res.insert(pos, i);
306 }
307 }
308 });
309
310 res
311 }
312
313 pub fn chords_for_keymap_index(
319 &self,
320 keymap_index: u16,
321 ) -> heapless::Vec<ChordState<MAX_CHORD_SIZE>, { MAX_CHORDS }> {
322 match self.pressed_chord_with_index(keymap_index) {
323 Some(chord_state) => {
324 let mut chords = heapless::Vec::new();
325 let _ = chords.push(chord_state);
326 chords
327 }
328 None => {
329 let chords_indices_span = self.pressed_chords_indices_span();
330 self.config
331 .chords
332 .iter()
333 .enumerate()
334 .filter(|&(_index, chord)| chord.has_index(keymap_index))
336 .filter(|&(_index, chord)| {
337 chords_indices_span.is_empty()
339 || chord.indices.iter().all(|&i| {
340 chords_indices_span.binary_search(&i).is_err()
342 })
343 })
344 .map(|(index, &chord)| ChordState {
345 index,
346 chord,
347 is_satisfied: false,
348 })
349 .collect()
350 }
351 }
352 }
353
354 fn insert_pressed_index(&mut self, pos: usize, index: u16) {
355 if self.pressed_indices.is_empty() {
356 return;
357 }
358
359 let mut i = self.pressed_indices.len() - 1;
360 while i > pos {
361 self.pressed_indices[i] = self.pressed_indices[i - 1];
362 i -= 1;
363 }
364
365 self.pressed_indices[pos] = Some(index);
366 }
367
368 fn remove_pressed_index(&mut self, pos: usize) {
369 if self.pressed_indices.is_empty() {
370 return;
371 }
372
373 let mut i = pos;
374 while i < self.pressed_indices.len() - 1 {
375 self.pressed_indices[i] = self.pressed_indices[i + 1];
376 i += 1;
377 }
378
379 self.pressed_indices[self.pressed_indices.len() - 1] = None;
380 }
381
382 fn press_index(&mut self, index: u16) {
383 match self
384 .pressed_indices
385 .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
386 {
387 Ok(_) => {}
388 Err(pos) => self.insert_pressed_index(pos, index),
389 }
390 }
391
392 fn release_index(&mut self, index: u16) {
393 if let Ok(pos) = self
394 .pressed_indices
395 .binary_search_by_key(&index, |&k| k.unwrap_or(u16::MAX))
396 {
397 self.remove_pressed_index(pos)
398 }
399 }
400
401 fn handle_event(&mut self, event: key::Event<Event>) {
403 match event {
404 key::Event::Input(input::Event::Press { keymap_index }) => {
405 self.press_index(keymap_index);
406
407 let span = self.pressed_chords_indices_span();
411 if span.contains(&keymap_index) {
412 self.ignore_idle_time = true;
414 } else {
415 if let Some(chord_id) = self.latest_resolved_chord {
417 let chord_indices = self.config.chords[chord_id as usize];
418 self.ignore_idle_time = chord_indices.has_index(keymap_index);
419 } else {
420 self.ignore_idle_time = false;
421
422 self.latest_resolved_chord = None;
425 }
426 }
427 }
428 key::Event::Input(input::Event::Release { keymap_index }) => {
429 self.release_index(keymap_index);
430
431 self.config
434 .chords
435 .iter()
436 .enumerate()
437 .for_each(|(chord_id, chord_indices)| {
438 if chord_indices.has_index(keymap_index) {
439 self.pressed_chords[chord_id] = false;
440 }
441 });
442 self.activating_index = None;
443 }
444 key::Event::Key {
445 keymap_index: _,
446 key_event: Event::ChordResolved(ChordResolution::Chord(chord_id)),
447 } => {
448 self.pressed_chords[chord_id as usize] = true;
449 self.latest_resolved_chord = Some(chord_id);
450 }
451 key::Event::Key {
452 key_event: Event::ChordActivated { keymap_index },
453 ..
454 } => {
455 self.activating_index = Some(keymap_index);
456 }
457 _ => {}
458 }
459 }
460}
461
462impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
463 key::Context for Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
464{
465 type Event = Event;
466
467 fn handle_event(&mut self, event: key::Event<Self::Event>) -> key::KeyEvents<Self::Event> {
468 self.handle_event(event);
469 key::KeyEvents::no_events()
470 }
471
472 fn reset(&mut self) {
473 Context::reset(self);
474 }
475}
476
477#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
482pub struct Key<
483 R: Copy,
484 const MAX_CHORDS: usize,
485 const MAX_CHORD_SIZE: usize,
486 const MAX_OVERLAPPING_CHORD_SIZE: usize,
487 const MAX_PRESSED_INDICES: usize,
488> {
489 pub chords: Slice<(ChordId, R), MAX_OVERLAPPING_CHORD_SIZE>,
491 pub passthrough: R,
493 #[serde(default)]
494 marker: PhantomData<(
495 [(); MAX_CHORDS],
496 [(); MAX_CHORD_SIZE],
497 [(); MAX_PRESSED_INDICES],
498 )>,
499}
500
501impl<
502 R: Copy,
503 const MAX_CHORDS: usize,
504 const MAX_CHORD_SIZE: usize,
505 const MAX_OVERLAPPING_CHORD_SIZE: usize,
506 const MAX_PRESSED_INDICES: usize,
507 > Key<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_OVERLAPPING_CHORD_SIZE, MAX_PRESSED_INDICES>
508{
509 pub fn new_pressed_key(
511 &self,
512 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
513 keymap_index: u16,
514 lookup: impl Fn(ChordId) -> Option<R>,
515 ) -> (
516 key::PressedKeyResult<
517 R,
518 PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
519 KeyState,
520 >,
521 key::KeyEvents<Event>,
522 ) {
523 let pks = PendingKeyState::new(context, keymap_index);
524
525 let chord_resolution = if context.sufficient_idle_time() {
526 pks.check_resolution()
527 } else {
528 PendingChordState::Resolved(ChordResolution::Passthrough)
529 };
530
531 if let PendingChordState::Resolved(resolution) = chord_resolution {
532 let maybe_new_key_ref = match resolution {
533 ChordResolution::Chord(resolved_chord_id) => {
534 if context.is_activator(keymap_index) {
535 lookup(resolved_chord_id)
536 } else {
537 None
538 }
539 }
540 ChordResolution::Passthrough => Some(self.passthrough),
541 };
542
543 if let Some(new_key_ref) = maybe_new_key_ref {
544 let pkr =
545 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
546 let pke = key::KeyEvents::no_events();
547
548 (pkr, pke)
549 } else {
550 let pkr = key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp);
551 let pke = key::KeyEvents::no_events();
552 (pkr, pke)
553 }
554 } else {
555 let pkr = key::PressedKeyResult::Pending(pks);
556
557 let timeout_ev = Event::Timeout;
558 let sch_ev = key::ScheduledEvent::after(
559 context.config.timeout,
560 key::Event::key_event(keymap_index, timeout_ev),
561 );
562 let pke = key::KeyEvents::scheduled_event(sch_ev);
563
564 (pkr, pke)
565 }
566 }
567
568 pub const fn new(chords: &[(ChordId, R)], passthrough: R) -> Self {
570 let chords = Slice::from_slice(chords);
571 Key {
572 chords,
573 passthrough,
574 marker: PhantomData,
575 }
576 }
577
578 pub fn binding_for(&self, id: ChordId) -> Option<R> {
580 self.chords
581 .iter()
582 .find(|(ch_id, _)| *ch_id == id)
583 .map(|(_, r)| *r)
584 }
585
586 fn update_pending_state(
587 &self,
588 pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
589 keymap_index: u16,
590 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
591 event: key::Event<Event>,
592 lookup: impl Fn(ChordId) -> Option<R>,
593 ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
594 let ch_state = pending_state.handle_event(keymap_index, event);
595
596 if let Some(ch_state) = ch_state {
597 let (maybe_new_key_ref, pke) = resolved_chord_npk(
598 keymap_index,
599 context,
600 pending_state,
601 event,
602 ch_state,
603 &lookup,
604 Some(self.passthrough),
605 );
606 (maybe_new_key_ref, pke)
607 } else {
608 (None, key::KeyEvents::no_events())
609 }
610 }
611}
612
613#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
620pub struct AuxiliaryKey<
621 R,
622 const MAX_CHORDS: usize,
623 const MAX_CHORD_SIZE: usize,
624 const MAX_PRESSED_INDICES: usize,
625> {
626 pub passthrough: R,
628 #[serde(default)]
629 marker: PhantomData<(
630 [(); MAX_CHORDS],
631 [(); MAX_CHORD_SIZE],
632 [(); MAX_PRESSED_INDICES],
633 )>,
634}
635
636impl<
637 R: Copy,
638 const MAX_CHORDS: usize,
639 const MAX_CHORD_SIZE: usize,
640 const MAX_PRESSED_INDICES: usize,
641 > AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
642{
643 pub fn new_pressed_key(
645 &self,
646 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
647 keymap_index: u16,
648 lookup: impl Fn(ChordId) -> Option<R>,
649 ) -> (
650 key::PressedKeyResult<
651 R,
652 PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
653 KeyState,
654 >,
655 key::KeyEvents<Event>,
656 ) {
657 let pks = PendingKeyState::new(context, keymap_index);
658
659 let chord_resolution = if context.sufficient_idle_time() {
660 pks.check_resolution()
661 } else {
662 PendingChordState::Resolved(ChordResolution::Passthrough)
663 };
664
665 if let PendingChordState::Resolved(resolution) = chord_resolution {
666 match resolution {
667 ChordResolution::Chord(resolved_chord_id) => {
668 let pkr = if context.is_activator(keymap_index) {
669 match lookup(resolved_chord_id) {
670 Some(r) => {
671 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(r))
672 }
673 None => key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp),
674 }
675 } else {
676 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp)
677 };
678 let pke = key::KeyEvents::no_events();
679
680 (pkr, pke)
681 }
682 ChordResolution::Passthrough => {
683 let new_key_ref = self.passthrough;
684 let pkr =
685 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(new_key_ref));
686 let pke = key::KeyEvents::no_events();
687 (pkr, pke)
688 }
689 }
690 } else {
691 let pkr = key::PressedKeyResult::Pending(pks);
692
693 let timeout_ev = Event::Timeout;
694 let sch_ev = key::ScheduledEvent::after(
695 context.config.timeout,
696 key::Event::key_event(keymap_index, timeout_ev),
697 );
698 let pke = key::KeyEvents::scheduled_event(sch_ev);
699
700 (pkr, pke)
701 }
702 }
703
704 pub const fn new(passthrough: R) -> Self {
706 AuxiliaryKey {
707 passthrough,
708 marker: PhantomData,
709 }
710 }
711
712 fn update_pending_state(
713 &self,
714 pending_state: &mut PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
715 keymap_index: u16,
716 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
717 event: key::Event<Event>,
718 lookup: impl Fn(ChordId) -> Option<R>,
719 ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
720 let ch_state = pending_state.handle_event(keymap_index, event);
721 if let Some(ch_state) = ch_state {
722 resolved_chord_npk(
723 keymap_index,
724 context,
725 pending_state,
726 event,
727 ch_state,
728 &lookup,
729 Some(self.passthrough),
730 )
731 } else {
732 (None, key::KeyEvents::no_events())
733 }
734 }
735}
736
737#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
739pub enum Event {
740 ChordResolved(ChordResolution),
742
743 ChordActivated {
745 keymap_index: u16,
747 },
748
749 Timeout,
751}
752
753fn activator_index<
754 const MAX_CHORDS: usize,
755 const MAX_CHORD_SIZE: usize,
756 const MAX_PRESSED_INDICES: usize,
757>(
758 event: key::Event<Event>,
759 pending_state: &PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
760 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
761 resolving_index: u16,
762 chord_id: ChordId,
763) -> u16 {
764 let chord = context.config.chords.get(chord_id as usize);
765 let in_chord = |i: u16| chord.is_some_and(|c| c.has_index(i));
766 match event {
767 key::Event::Input(input::Event::Press { keymap_index }) if in_chord(keymap_index) => {
768 keymap_index
769 }
770 _ => pending_state
771 .last_foreign_press
772 .filter(|&i| in_chord(i))
773 .unwrap_or(resolving_index),
774 }
775}
776
777fn resolved_chord_npk<
778 R: Copy,
779 const MAX_CHORDS: usize,
780 const MAX_CHORD_SIZE: usize,
781 const MAX_PRESSED_INDICES: usize,
782>(
783 keymap_index: u16,
784 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
785 pending_state: &PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
786 event: key::Event<Event>,
787 ch_state: ChordResolution,
788 lookup: impl Fn(ChordId) -> Option<R>,
789 passthrough: Option<R>,
790) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Event>) {
791 let ch_r_ev = Event::ChordResolved(ch_state);
792 let mut pke = key::KeyEvents::event(key::Event::key_event(keymap_index, ch_r_ev));
793
794 let maybe_new_key_ref = match ch_state {
795 ChordResolution::Chord(chord_id) => {
796 let activator = activator_index(event, pending_state, context, keymap_index, chord_id);
797 pke.add_event(key::Event::key_event(
798 activator,
799 Event::ChordActivated {
800 keymap_index: activator,
801 },
802 ));
803 if keymap_index == activator {
804 lookup(chord_id)
805 } else {
806 None
807 }
808 }
809 ChordResolution::Passthrough => passthrough,
810 };
811
812 if let Some(new_key_ref) = maybe_new_key_ref {
813 (Some(key::NewPressedKey::key(new_key_ref)), pke)
814 } else {
815 (Some(key::NewPressedKey::no_op()), pke)
816 }
817}
818
819#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
821pub enum ChordResolution {
822 Chord(ChordId),
824 Passthrough,
826}
827
828#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
830pub enum PendingChordState {
831 Resolved(ChordResolution),
833 Pending(Option<ChordId>),
837}
838
839#[derive(Debug, Clone, PartialEq)]
841pub struct PendingKeyState<
842 const MAX_CHORDS: usize,
843 const MAX_CHORD_SIZE: usize,
844 const MAX_PRESSED_INDICES: usize,
845> {
846 pressed_indices: heapless::Vec<u16, { MAX_CHORD_SIZE }>,
848 possible_chords: heapless::Vec<ChordState<MAX_CHORD_SIZE>, { MAX_CHORDS }>,
850 last_foreign_press: Option<u16>,
852 marker: PhantomData<[(); MAX_PRESSED_INDICES]>,
853}
854
855impl<const MAX_CHORDS: usize, const MAX_CHORD_SIZE: usize, const MAX_PRESSED_INDICES: usize>
856 PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>
857{
858 pub fn new(
860 context: &Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>,
861 keymap_index: u16,
862 ) -> Self {
863 let mut pressed_indices = heapless::Vec::new();
864 let _ = pressed_indices.push(keymap_index);
865 let possible_chords = context.chords_for_keymap_index(keymap_index);
866
867 Self {
868 pressed_indices,
869 possible_chords,
870 last_foreign_press: None,
871 marker: PhantomData,
872 }
873 }
874
875 fn satisfied_chord(&self) -> Option<&ChordState<MAX_CHORD_SIZE>> {
877 self.possible_chords
878 .iter()
879 .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
880 }
881
882 fn check_resolution(&self) -> PendingChordState {
883 match self.possible_chords.as_slice() {
884 [ChordState {
885 index,
886 is_satisfied,
887 ..
888 }] if *is_satisfied => {
889 PendingChordState::Resolved(ChordResolution::Chord(*index as u8))
893 }
894 [] => {
895 PendingChordState::Resolved(ChordResolution::Passthrough)
898 }
899 satisfiable_chords => {
900 PendingChordState::Pending(
902 satisfiable_chords
903 .iter()
904 .find(|&ChordState { is_satisfied, .. }| *is_satisfied)
905 .map(|&ChordState { index, .. }| index as u8),
906 )
907 }
908 }
909 }
910
911 pub fn handle_event(
913 &mut self,
914 keymap_index: u16,
915 event: key::Event<Event>,
916 ) -> Option<ChordResolution> {
917 match event {
918 key::Event::Key {
919 keymap_index: _ev_idx,
920 key_event: Event::Timeout,
921 } => {
922 let maybe_satisfied_chord_id = self
924 .satisfied_chord()
925 .map(|chord_state| chord_state.index as u8);
926 match maybe_satisfied_chord_id {
927 Some(satisfied_chord_id) => Some(ChordResolution::Chord(satisfied_chord_id)),
928 _ => Some(ChordResolution::Passthrough),
929 }
930 }
931 key::Event::Input(input::Event::Press {
932 keymap_index: pressed_keymap_index,
933 }) => {
934 if self
935 .possible_chords
936 .iter()
937 .any(|c| c.chord.has_index(pressed_keymap_index))
938 {
939 self.last_foreign_press = Some(pressed_keymap_index);
940 }
941
942 let maybe_satisfied_chord_id = self
945 .satisfied_chord()
946 .map(|chord_state| chord_state.index as u8);
947
948 let pos = self
950 .pressed_indices
951 .binary_search(&keymap_index)
952 .unwrap_or_else(|e| e);
953 let push_res = self.pressed_indices.insert(pos, pressed_keymap_index);
954 if push_res.is_err() {
959 panic!();
960 }
961
962 self.possible_chords
964 .retain(|chord_state| chord_state.chord.has_index(pressed_keymap_index));
965
966 for chord in self.possible_chords.iter_mut() {
968 chord.is_satisfied = chord.chord.is_satisfied_by(&self.pressed_indices);
969 }
970
971 let resolution = match self.check_resolution() {
972 PendingChordState::Resolved(resolution) => Some(resolution),
973 PendingChordState::Pending(_) => None,
974 };
975
976 match (resolution, maybe_satisfied_chord_id) {
979 (Some(ChordResolution::Passthrough), Some(satisfied_chord_id)) => {
980 Some(ChordResolution::Chord(satisfied_chord_id))
981 }
982 _ => resolution,
983 }
984 }
985 key::Event::Input(input::Event::Release {
986 keymap_index: released_keymap_index,
987 }) => {
988 if released_keymap_index == keymap_index {
989 let maybe_satisfied_chord_id = self
990 .satisfied_chord()
991 .map(|chord_state| chord_state.index as u8);
992
993 match maybe_satisfied_chord_id {
994 Some(satisfied_chord_id) => {
995 Some(ChordResolution::Chord(satisfied_chord_id))
996 }
997
998 None => Some(ChordResolution::Passthrough),
1001 }
1002 } else {
1003 None
1004 }
1005 }
1006 _ => None,
1007 }
1008 }
1009}
1010
1011#[derive(Debug, Clone, Copy, PartialEq)]
1013pub struct KeyState;
1014
1015#[derive(Debug, Clone, Copy, PartialEq)]
1017pub struct System<
1018 R: Copy + Debug + PartialEq,
1019 Keys: Index<
1020 usize,
1021 Output = Key<
1022 R,
1023 MAX_CHORDS,
1024 MAX_CHORD_SIZE,
1025 MAX_OVERLAPPING_CHORD_SIZE,
1026 MAX_PRESSED_INDICES,
1027 >,
1028 >,
1029 AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
1030 const MAX_CHORDS: usize,
1031 const MAX_CHORD_SIZE: usize,
1032 const MAX_OVERLAPPING_CHORD_SIZE: usize,
1033 const MAX_PRESSED_INDICES: usize,
1034> {
1035 keys: Keys,
1036 auxiliary_keys: AuxiliaryKeys,
1037}
1038
1039impl<
1040 R: Copy + Debug + PartialEq,
1041 Keys: Index<
1042 usize,
1043 Output = Key<
1044 R,
1045 MAX_CHORDS,
1046 MAX_CHORD_SIZE,
1047 MAX_OVERLAPPING_CHORD_SIZE,
1048 MAX_PRESSED_INDICES,
1049 >,
1050 >,
1051 AuxiliaryKeys: Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
1052 const MAX_CHORDS: usize,
1053 const MAX_CHORD_SIZE: usize,
1054 const MAX_OVERLAPPING_CHORD_SIZE: usize,
1055 const MAX_PRESSED_INDICES: usize,
1056 >
1057 System<
1058 R,
1059 Keys,
1060 AuxiliaryKeys,
1061 MAX_CHORDS,
1062 MAX_CHORD_SIZE,
1063 MAX_OVERLAPPING_CHORD_SIZE,
1064 MAX_PRESSED_INDICES,
1065 >
1066{
1067 pub const fn new(keys: Keys, auxiliary_keys: AuxiliaryKeys) -> Self {
1069 Self {
1070 keys,
1071 auxiliary_keys,
1072 }
1073 }
1074
1075 fn binding_for(&self, id: ChordId) -> Option<R>
1076 where
1077 Keys: AsRef<
1078 [Key<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_OVERLAPPING_CHORD_SIZE, MAX_PRESSED_INDICES>],
1079 >,
1080 {
1081 self.keys.as_ref().iter().find_map(|k| k.binding_for(id))
1082 }
1083}
1084
1085impl<
1086 R: Copy + Debug + PartialEq,
1087 Keys: Debug
1088 + Index<
1089 usize,
1090 Output = Key<
1091 R,
1092 MAX_CHORDS,
1093 MAX_CHORD_SIZE,
1094 MAX_OVERLAPPING_CHORD_SIZE,
1095 MAX_PRESSED_INDICES,
1096 >,
1097 > + AsRef<
1098 [Key<
1099 R,
1100 MAX_CHORDS,
1101 MAX_CHORD_SIZE,
1102 MAX_OVERLAPPING_CHORD_SIZE,
1103 MAX_PRESSED_INDICES,
1104 >],
1105 >,
1106 AuxiliaryKeys: Debug
1107 + Index<usize, Output = AuxiliaryKey<R, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>>,
1108 const MAX_CHORDS: usize,
1109 const MAX_CHORD_SIZE: usize,
1110 const MAX_OVERLAPPING_CHORD_SIZE: usize,
1111 const MAX_PRESSED_INDICES: usize,
1112 > key::System<R>
1113 for System<
1114 R,
1115 Keys,
1116 AuxiliaryKeys,
1117 MAX_CHORDS,
1118 MAX_CHORD_SIZE,
1119 MAX_OVERLAPPING_CHORD_SIZE,
1120 MAX_PRESSED_INDICES,
1121 >
1122{
1123 type Ref = Ref;
1124 type Context = Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1125 type Event = Event;
1126 type PendingKeyState = PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1127 type KeyState = KeyState;
1128
1129 fn new_pressed_key(
1130 &self,
1131 keymap_index: u16,
1132 context: &Self::Context,
1133 key_ref: Ref,
1134 ) -> (
1135 key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
1136 key::KeyEvents<Self::Event>,
1137 ) {
1138 let lookup = |id| self.binding_for(id);
1139 match key_ref {
1140 Ref::Chorded(i) => self.keys[i as usize].new_pressed_key(context, keymap_index, lookup),
1141 Ref::Auxiliary(i) => {
1142 self.auxiliary_keys[i as usize].new_pressed_key(context, keymap_index, lookup)
1143 }
1144 }
1145 }
1146
1147 fn update_pending_state(
1148 &self,
1149 pending_state: &mut Self::PendingKeyState,
1150 keymap_index: u16,
1151 context: &Self::Context,
1152 key_ref: Ref,
1153 event: key::Event<Self::Event>,
1154 ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
1155 let lookup = |id| self.binding_for(id);
1156 match key_ref {
1157 Ref::Chorded(i) => self.keys[i as usize].update_pending_state(
1158 pending_state,
1159 keymap_index,
1160 context,
1161 event,
1162 lookup,
1163 ),
1164 Ref::Auxiliary(i) => self.auxiliary_keys[i as usize].update_pending_state(
1165 pending_state,
1166 keymap_index,
1167 context,
1168 event,
1169 lookup,
1170 ),
1171 }
1172 }
1173
1174 fn update_state(
1175 &self,
1176 _key_state: &mut Self::KeyState,
1177 _key_ref: &Self::Ref,
1178 _context: &Self::Context,
1179 _keymap_index: u16,
1180 _event: key::Event<Self::Event>,
1181 ) -> key::KeyEvents<Self::Event> {
1182 panic!()
1183 }
1184
1185 fn key_output(
1186 &self,
1187 _key_ref: &Self::Ref,
1188 _key_state: &Self::KeyState,
1189 ) -> Option<key::KeyOutput> {
1190 panic!()
1191 }
1192}
1193
1194#[cfg(test)]
1195#[allow(clippy::unwrap_used, clippy::expect_used)]
1196mod tests {
1197 use super::*;
1198
1199 use key::keyboard;
1200
1201 const MAX_CHORDS: usize = 4;
1202 const MAX_CHORD_SIZE: usize = 16;
1203 const MAX_PRESSED_INDICES: usize = MAX_CHORD_SIZE * 2;
1204
1205 const DEFAULT_CONTEXT: Context<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES> =
1206 Context::from_config(Config::new());
1207
1208 type AuxiliaryKey =
1209 super::AuxiliaryKey<keyboard::Ref, MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1210 type ChordedKey = super::Key<keyboard::Ref, MAX_CHORDS, MAX_CHORD_SIZE, 4, MAX_PRESSED_INDICES>;
1211 type PendingKeyState = super::PendingKeyState<MAX_CHORDS, MAX_CHORD_SIZE, MAX_PRESSED_INDICES>;
1212
1213 #[test]
1214 fn test_sizeof_ref() {
1215 assert_eq!(2, core::mem::size_of::<Ref>());
1216 }
1217
1218 #[test]
1219 fn test_sizeof_event() {
1220 assert_eq!(4, core::mem::size_of::<Event>());
1221 }
1222
1223 #[test]
1224 fn test_timeout_resolves_unsatisfied_aux_state_as_passthrough_key() {
1225 let context = DEFAULT_CONTEXT;
1227 let expected_ref = keyboard::Ref::KeyCode(0x04);
1228 let _chorded_key = AuxiliaryKey::new(expected_ref);
1229 let keymap_index: u16 = 0;
1230 let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1231
1232 let timeout_ev = key::Event::key_event(keymap_index, Event::Timeout);
1234 let actual_resolution = pks.handle_event(keymap_index, timeout_ev);
1235
1236 let expected_resolution = Some(ChordResolution::Passthrough);
1238 assert_eq!(expected_resolution, actual_resolution);
1239 }
1240
1241 #[test]
1242 fn test_press_non_chorded_key_resolves_aux_state_as_interrupted() {
1243 let context = DEFAULT_CONTEXT;
1245 let expected_ref = keyboard::Ref::KeyCode(0x04);
1246 let _chorded_key = AuxiliaryKey::new(expected_ref);
1247 let keymap_index: u16 = 0;
1248 let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1249
1250 let non_chord_press = input::Event::Press { keymap_index: 9 }.into();
1252 let actual_resolution = pks.handle_event(keymap_index, non_chord_press);
1253
1254 let expected_resolution = Some(ChordResolution::Passthrough);
1256 assert_eq!(expected_resolution, actual_resolution);
1257 }
1258
1259 #[test]
1266 fn test_press_chorded_key_resolves_unambiguous_aux_state_as_chord() {
1267 let mut context = Context::from_config(Config {
1269 chords: Slice::from_slice(&[ChordIndices::from_slice(&[0, 1])]),
1270 ..Config::new()
1271 });
1272 let passthrough = keyboard::Ref::KeyCode(0x04);
1273 let _chorded_key = AuxiliaryKey::new(passthrough);
1274 let keymap_index: u16 = 0;
1275 context.handle_event(key::Event::Input(input::Event::Press { keymap_index: 0 }));
1276 let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1277
1278 let chord_press = input::Event::Press { keymap_index: 1 }.into();
1280 let actual_resolution = pks.handle_event(keymap_index, chord_press);
1281
1282 let expected_resolution = Some(ChordResolution::Chord(0));
1284 assert_eq!(expected_resolution, actual_resolution);
1285 }
1286
1287 #[test]
1288 fn test_release_pending_aux_state_resolves_as_tapped_key() {
1289 let context = DEFAULT_CONTEXT;
1291 let expected_ref = keyboard::Ref::KeyCode(0x04);
1292 let _chorded_key = AuxiliaryKey::new(expected_ref);
1293 let keymap_index: u16 = 0;
1294 let mut pks: PendingKeyState = PendingKeyState::new(&context, keymap_index);
1295
1296 let chorded_key_release = input::Event::Release { keymap_index }.into();
1298 let actual_resolution = pks.handle_event(keymap_index, chorded_key_release);
1299
1300 let expected_resolution = Some(ChordResolution::Passthrough);
1302 assert_eq!(expected_resolution, actual_resolution);
1303 }
1304
1305 #[test]
1306 fn primary_pending_press_aux_emits_chord_activated_for_aux() {
1307 let context = Context::from_config(Config {
1309 chords: Slice::from_slice(&[ChordIndices::from_slice(&[0, 1])]),
1310 ..Config::new()
1311 });
1312 let key = ChordedKey::new(
1313 &[(0, keyboard::Ref::KeyCode(0x06))],
1314 keyboard::Ref::KeyCode(0x04),
1315 );
1316 let mut pks = PendingKeyState::new(&context, 0);
1317 let lookup = |id| key.binding_for(id);
1318
1319 let (maybe_npk, pke) = key.update_pending_state(
1321 &mut pks,
1322 0,
1323 &context,
1324 input::Event::Press { keymap_index: 1 }.into(),
1325 lookup,
1326 );
1327
1328 assert_eq!(maybe_npk, Some(key::NewPressedKey::no_op()));
1330 let activated = pke.into_iter().any(|sch_ev| {
1331 matches!(
1332 sch_ev.event,
1333 key::Event::Key {
1334 key_event: Event::ChordActivated { keymap_index: 1 },
1335 ..
1336 }
1337 )
1338 });
1339 assert!(activated);
1340 }
1341
1342 #[test]
1343 fn aux_pending_press_primary_emits_chord_activated_for_primary() {
1344 let context = Context::from_config(Config {
1346 chords: Slice::from_slice(&[ChordIndices::from_slice(&[0, 1])]),
1347 ..Config::new()
1348 });
1349 let key = AuxiliaryKey::new(keyboard::Ref::KeyCode(0x05));
1350 let mut pks = PendingKeyState::new(&context, 1);
1351 let lookup = |_id: ChordId| Some(keyboard::Ref::KeyCode(0x06));
1352
1353 let (maybe_npk, pke) = key.update_pending_state(
1355 &mut pks,
1356 1,
1357 &context,
1358 input::Event::Press { keymap_index: 0 }.into(),
1359 lookup,
1360 );
1361
1362 assert_eq!(maybe_npk, Some(key::NewPressedKey::no_op()));
1364 let activated = pke.into_iter().any(|sch_ev| {
1365 matches!(
1366 sch_ev.event,
1367 key::Event::Key {
1368 key_event: Event::ChordActivated { keymap_index: 0 },
1369 ..
1370 }
1371 )
1372 });
1373 assert!(activated);
1374 }
1375}