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")]
70 pub timeout: u16,
71
72 pub chords: Slice<ChordIndices<MAX_CHORD_SIZE>, MAX_CHORDS>,
74
75 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
95pub 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 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 fn default() -> Self {
118 Self::new()
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub struct ChordState<const MAX_CHORD_SIZE: usize> {
125 pub index: usize,
127 pub chord: ChordIndices<MAX_CHORD_SIZE>,
129 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 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 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#[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 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 pub fn reset(&mut self) {
237 *self = Self::from_config(self.config);
238 }
239
240 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 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 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(|&(_index, chord)| chord.has_index(keymap_index))
323 .filter(|&(_index, chord)| {
324 chords_indices_span.is_empty()
326 || chord.indices.iter().all(|&i| {
327 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 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 let span = self.pressed_chords_indices_span();
398 if span.contains(&keymap_index) {
399 self.ignore_idle_time = true;
401 } else {
402 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 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 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#[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 pub chords: Slice<(ChordId, R), MAX_OVERLAPPING_CHORD_SIZE>,
471 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 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 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 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 if let Some(ch_state) = ch_state {
584 let maybe_new_key_ref = match ch_state {
585 ChordResolution::Chord(resolved_chord_id) => {
586 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#[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 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 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 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#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
745pub enum Event {
746 ChordResolved(ChordResolution),
748
749 Timeout,
751}
752
753#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
755pub enum ChordResolution {
756 Chord(ChordId),
758 Passthrough,
760}
761
762#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
764pub enum PendingChordState {
765 Resolved(ChordResolution),
767 Pending(Option<ChordId>),
771}
772
773#[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 pressed_indices: heapless::Vec<u16, { MAX_CHORD_SIZE }>,
782 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 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 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 PendingChordState::Resolved(ChordResolution::Chord(*index as u8))
824 }
825 [] => {
826 PendingChordState::Resolved(ChordResolution::Passthrough)
829 }
830 satisfiable_chords => {
831 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 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 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 let maybe_satisfied_chord_id = self
868 .satisfied_chord()
869 .map(|chord_state| chord_state.index as u8);
870
871 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 if push_res.is_err() {
882 panic!();
883 }
884
885 self.possible_chords
887 .retain(|chord_state| chord_state.chord.has_index(pressed_keymap_index));
888
889 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 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 None => Some(ChordResolution::Passthrough),
924 }
925 } else {
926 None
927 }
928 }
929 _ => None,
930 }
931 }
932}
933
934#[derive(Debug, Clone, Copy, PartialEq)]
936pub struct KeyState;
937
938#[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 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 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 let timeout_ev = key::Event::key_event(keymap_index, Event::Timeout);
1134 let actual_resolution = pks.handle_event(keymap_index, timeout_ev);
1135
1136 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 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 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 let expected_resolution = Some(ChordResolution::Passthrough);
1156 assert_eq!(expected_resolution, actual_resolution);
1157 }
1158
1159 #[test]
1166 fn test_press_chorded_key_resolves_unambiguous_aux_state_as_chord() {
1167 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 let chord_press = input::Event::Press { keymap_index: 1 }.into();
1180 let actual_resolution = pks.handle_event(keymap_index, chord_press);
1181
1182 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 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 let chorded_key_release = input::Event::Release { keymap_index }.into();
1198 let actual_resolution = pks.handle_event(keymap_index, chorded_key_release);
1199
1200 let expected_resolution = Some(ChordResolution::Passthrough);
1202 assert_eq!(expected_resolution, actual_resolution);
1203 }
1204}