Skip to main content

rmk_q1_pro_iso/matrix/
layer_toggle.rs

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