Skip to main content

oxstack_input/
state.rs

1use crate::cardputer;
2use crate::key::{Action, Key, Modifiers};
3
4/// A change in one key's state, with the modifiers held *after* the change.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6#[cfg_attr(feature = "defmt", derive(defmt::Format))]
7pub struct KeyEvent {
8    /// The physical key.
9    pub key: Key,
10    /// `true` on press, `false` on release.
11    pub pressed: bool,
12    /// Modifiers held after this event was applied.
13    pub mods: Modifiers,
14}
15
16impl KeyEvent {
17    /// The action this event triggers: `None` for releases and for keys
18    /// that only act as modifiers.
19    ///
20    /// Fn takes precedence over Shift, matching the Cardputer's printed
21    /// orange legends. Ctrl, Alt and Opt don't change the resolved action;
22    /// read them from [`mods`](Self::mods) for shortcuts.
23    pub fn action(&self) -> Option<Action> {
24        if !self.pressed {
25            return None;
26        }
27        if self.mods.contains(Modifiers::FN)
28            && let Some(action) = cardputer::fn_layer(self.key)
29        {
30            return Some(action);
31        }
32        match self.key {
33            Key::Enter => Some(Action::Enter),
34            Key::Backspace => Some(Action::Backspace),
35            Key::Tab => Some(Action::Tab),
36            key => {
37                let (base, shifted) = key.legend()?;
38                let shift = self.mods.contains(Modifiers::SHIFT);
39                Some(Action::Char(if shift { shifted } else { base }))
40            }
41        }
42    }
43}
44
45/// Held-key tracker. One `u64` bit per [`Key`], plus the derived modifiers.
46#[derive(Clone, Copy, Debug, Default)]
47pub struct KeyboardState {
48    held: u64,
49    mods: Modifiers,
50}
51
52impl KeyboardState {
53    /// Nothing held.
54    pub const fn new() -> Self {
55        Self {
56            held: 0,
57            mods: Modifiers::NONE,
58        }
59    }
60
61    /// Records a press or release and returns the resulting event.
62    pub fn apply(&mut self, key: Key, pressed: bool) -> KeyEvent {
63        if pressed {
64            self.held |= key.bit();
65        } else {
66            self.held &= !key.bit();
67        }
68        if key.modifier().is_some() {
69            self.mods = self.derive_mods();
70        }
71        KeyEvent {
72            key,
73            pressed,
74            mods: self.mods,
75        }
76    }
77
78    /// Whether `key` is currently held.
79    pub const fn is_held(&self, key: Key) -> bool {
80        self.held & key.bit() != 0
81    }
82
83    /// Currently held modifiers.
84    pub const fn modifiers(&self) -> Modifiers {
85        self.mods
86    }
87
88    /// Forgets all held keys, e.g. after the scanner was reset.
89    pub fn clear(&mut self) {
90        *self = Self::new();
91    }
92
93    fn derive_mods(&self) -> Modifiers {
94        [Key::Shift, Key::Ctrl, Key::Alt, Key::Opt, Key::Fn]
95            .into_iter()
96            .filter(|k| self.is_held(*k))
97            .filter_map(Key::modifier)
98            .fold(Modifiers::NONE, Modifiers::union)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    fn press(s: &mut KeyboardState, k: Key) -> KeyEvent {
107        s.apply(k, true)
108    }
109
110    #[test]
111    fn plain_and_shifted_characters() {
112        let mut s = KeyboardState::new();
113        assert_eq!(press(&mut s, Key::A).action(), Some(Action::Char('a')));
114        press(&mut s, Key::Shift);
115        assert_eq!(press(&mut s, Key::A).action(), Some(Action::Char('A')));
116        assert_eq!(press(&mut s, Key::Num2).action(), Some(Action::Char('@')));
117        s.apply(Key::Shift, false);
118        assert_eq!(press(&mut s, Key::Num2).action(), Some(Action::Char('2')));
119    }
120
121    #[test]
122    fn fn_layer_beats_shift() {
123        let mut s = KeyboardState::new();
124        press(&mut s, Key::Shift);
125        press(&mut s, Key::Fn);
126        assert_eq!(press(&mut s, Key::Semicolon).action(), Some(Action::Up));
127        assert_eq!(press(&mut s, Key::Equal).action(), Some(Action::F(12)));
128        // Keys without an orange legend fall through to the normal layer.
129        assert_eq!(press(&mut s, Key::Q).action(), Some(Action::Char('Q')));
130    }
131
132    #[test]
133    fn modifiers_and_releases() {
134        let mut s = KeyboardState::new();
135        let ev = press(&mut s, Key::Ctrl);
136        assert_eq!(ev.action(), None, "modifier keys have no action");
137        let ev = press(&mut s, Key::C);
138        assert!(ev.mods.contains(Modifiers::CTRL));
139        assert_eq!(ev.action(), Some(Action::Char('c')));
140        assert_eq!(s.apply(Key::C, false).action(), None);
141        s.apply(Key::Ctrl, false);
142        assert!(s.modifiers().is_empty());
143        assert!(!s.is_held(Key::Ctrl));
144    }
145}