Skip to main content

rmk_q6_he_ansi/matrix/
layer_toggle.rs

1use embassy_stm32::{exti::ExtiInput, mode::Async};
2use embassy_time::{Duration, Timer};
3use rmk::{event::KeyboardEvent, macros::input_device};
4
5/// Matrix coordinates for a key position.
6#[derive(Copy, Clone)]
7pub struct MatrixPos {
8    /// Column index within the matrix.
9    pub col: u8,
10    /// Row index within the matrix.
11    pub row: u8,
12}
13
14/// Input device that toggles layers based on a switch position.
15#[input_device(publish = KeyboardEvent)]
16pub struct LayerToggle<'peripherals> {
17    /// Debounce duration applied to input level changes.
18    debounce: Duration,
19    /// Matrix position activated when the input is at a high logic level.
20    high_pos: MatrixPos,
21    /// Last observed logic level of the input pin.
22    last_level: Option<bool>,
23    /// Matrix position activated when the input is at a low logic level.
24    low_pos: MatrixPos,
25    /// Matrix position pending release after a level change.
26    pending_release: Option<MatrixPos>,
27    /// External interrupt input pin used to read the switch state.
28    pin: ExtiInput<'peripherals, Async>,
29}
30
31impl<'peripherals> LayerToggle<'peripherals> {
32    /// Emit an event if the level has changed.
33    fn maybe_emit_for_level(&mut self, new_level: bool) -> Option<KeyboardEvent> {
34        if self.last_level == Some(new_level) {
35            return None;
36        }
37        self.last_level = Some(new_level);
38        Some(self.queue_tap(self.pos_for_level(new_level)))
39    }
40
41    /// Create a new layer toggle with a provided debounce duration.
42    #[must_use]
43    pub const fn new(
44        pin: ExtiInput<'peripherals, Async>,
45        high_pos: MatrixPos,
46        low_pos: MatrixPos,
47        debounce: Duration,
48    ) -> Self {
49        Self { pin, high_pos, low_pos, last_level: None, pending_release: None, debounce }
50    }
51
52    /// Create a new layer toggle with the default debounce.
53    #[must_use]
54    pub const fn new_with_default_debounce(
55        pin: ExtiInput<'peripherals, Async>,
56        high_pos: MatrixPos,
57        low_pos: MatrixPos,
58    ) -> Self {
59        Self::new(pin, high_pos, low_pos, Duration::from_millis(15))
60    }
61
62    /// Select the matrix position for the provided level.
63    #[inline]
64    const fn pos_for_level(&self, level_high: bool) -> MatrixPos {
65        if level_high { self.high_pos } else { self.low_pos }
66    }
67
68    /// Queue a tap event for the provided position.
69    #[inline]
70    fn queue_tap(&mut self, pos: MatrixPos) -> KeyboardEvent {
71        self.pending_release = Some(pos);
72        KeyboardEvent::key(pos.row, pos.col, true)
73    }
74
75    /// Read the next layer-toggle `KeyboardEvent`.
76    async fn read_keyboard_event(&mut self) -> KeyboardEvent {
77        if let Some(pos) = self.pending_release.take() {
78            return KeyboardEvent::key(pos.row, pos.col, false);
79        }
80
81        if self.last_level.is_none() {
82            let level = self.pin.is_high();
83            self.last_level = Some(level);
84            return self.queue_tap(self.pos_for_level(level));
85        }
86
87        loop {
88            self.pin.wait_for_any_edge().await;
89            Timer::after(self.debounce).await;
90            let level = self.pin.is_high();
91
92            if let Some(evt) = self.maybe_emit_for_level(level) {
93                return evt;
94            }
95        }
96    }
97}