smart_keymap/key/
tap_hold.rs

1use core::fmt::Debug;
2use core::ops::Index;
3
4use serde::Deserialize;
5
6use crate::input;
7use crate::key;
8use crate::keymap;
9
10/// Reference for a tap_hold key.
11#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
12pub struct Ref(pub u8);
13
14/// A key with tap-hold functionality.
15#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
16pub struct Key<R> {
17    /// The 'tap' key.
18    pub tap: R,
19    /// The 'hold' key.
20    pub hold: R,
21}
22
23impl<R> Key<R> {
24    /// Constructs a new tap-hold key.
25    pub const fn new(tap: R, hold: R) -> Key<R> {
26        Key { tap, hold }
27    }
28}
29
30#[cfg(feature = "std")]
31impl<R: Default> Default for Key<R> {
32    fn default() -> Self {
33        Key {
34            tap: R::default(),
35            hold: R::default(),
36        }
37    }
38}
39
40/// How the tap hold key should respond to interruptions (input events from other keys).
41#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
42pub enum InterruptResponse {
43    /// The tap-hold key ignores other key presses/taps.
44    /// (Only resolves to hold on timeout).
45    Ignore,
46    /// The tap-hold key resolves as "hold" when interrupted by a key press.
47    HoldOnKeyPress,
48    /// The tap-hold key resolves as "hold" when interrupted by a key tap.
49    /// (Another key was pressed and released).
50    HoldOnKeyTap,
51}
52
53/// Configuration settings for tap hold keys.
54#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
55pub struct Config {
56    /// The timeout (in number of milliseconds) for a tap-hold key to resolve as hold.
57    #[serde(default = "default_timeout")]
58    pub timeout: u16,
59
60    /// How the tap-hold key should respond to interruptions.
61    #[serde(default = "default_interrupt_response")]
62    pub interrupt_response: InterruptResponse,
63
64    /// Amount of time (in milliseconds) the keymap must have been idle
65    ///  in order for tap hold to support 'hold' functionality.
66    ///
67    /// This reduces disruption from unexpected hold resolutions
68    ///  when typing quickly.
69    pub required_idle_time: Option<u16>,
70}
71
72/// The default timeout.
73pub const DEFAULT_TIMEOUT: u16 = 200;
74
75/// The default interrupt response.
76pub const DEFAULT_INTERRUPT_RESPONSE: InterruptResponse = InterruptResponse::Ignore;
77
78fn default_timeout() -> u16 {
79    DEFAULT_TIMEOUT
80}
81
82fn default_interrupt_response() -> InterruptResponse {
83    DEFAULT_INTERRUPT_RESPONSE
84}
85
86/// Default tap hold config.
87pub const DEFAULT_CONFIG: Config = Config {
88    timeout: DEFAULT_TIMEOUT,
89    interrupt_response: DEFAULT_INTERRUPT_RESPONSE,
90    required_idle_time: None,
91};
92
93impl Config {
94    /// Constructs a new default [Config].
95    pub const fn new() -> Self {
96        DEFAULT_CONFIG
97    }
98}
99
100impl Default for Config {
101    /// Returns the default context.
102    fn default() -> Self {
103        DEFAULT_CONFIG
104    }
105}
106
107/// Context for [Key].
108#[derive(Debug, Clone, Copy, PartialEq)]
109pub struct Context {
110    config: Config,
111    idle_time_ms: u32,
112}
113
114impl Context {
115    /// Constructs a context from the given config
116    pub const fn from_config(config: Config) -> Context {
117        Context {
118            config,
119            idle_time_ms: 0,
120        }
121    }
122
123    /// Updates the context with the given keymap context.
124    pub fn update_keymap_context(
125        &mut self,
126        keymap::KeymapContext { idle_time_ms, .. }: &keymap::KeymapContext,
127    ) {
128        self.idle_time_ms = *idle_time_ms;
129    }
130}
131
132/// The state of a tap-hold key.
133#[derive(Debug, Clone, Copy, PartialEq)]
134pub enum TapHoldState {
135    /// Resolved as tap.
136    Tap,
137    /// Resolved as hold.
138    Hold,
139}
140
141/// Events emitted by a tap-hold key.
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub enum Event {
144    /// Event indicating the key has been held long enough to resolve as hold.
145    TapHoldTimeout,
146}
147
148/// The state of a pressed tap-hold key.
149#[derive(Debug, Clone, PartialEq)]
150pub struct PendingKeyState {
151    // For tracking 'tap' interruptions
152    other_pressed_keymap_index: Option<u16>,
153}
154
155impl PendingKeyState {
156    /// Constructs the initial pressed key state
157    fn new() -> PendingKeyState {
158        PendingKeyState {
159            other_pressed_keymap_index: None,
160        }
161    }
162
163    /// Compute whether the tap-hold key should resolve as tap or hold,
164    ///  given the tap hold config, the current state, and the key event.
165    fn hold_resolution(
166        &self,
167        interrupt_response: InterruptResponse,
168        keymap_index: u16,
169        event: key::Event<Event>,
170    ) -> Option<TapHoldState> {
171        match interrupt_response {
172            InterruptResponse::HoldOnKeyPress => {
173                match event {
174                    key::Event::Input(input::Event::Press { .. }) => {
175                        // TapHold: any interruption resolves pending TapHold as Hold.
176                        Some(TapHoldState::Hold)
177                    }
178                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
179                        if keymap_index == ki {
180                            // TapHold: not interrupted; resolved as tap.
181                            Some(TapHoldState::Tap)
182                        } else {
183                            None
184                        }
185                    }
186                    key::Event::Key {
187                        key_event: Event::TapHoldTimeout,
188                        ..
189                    } => {
190                        // Key held long enough to resolve as hold.
191                        Some(TapHoldState::Hold)
192                    }
193                    _ => None,
194                }
195            }
196            InterruptResponse::HoldOnKeyTap => {
197                match event {
198                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
199                        if keymap_index == ki {
200                            // TapHold: not interrupted; resolved as tap.
201                            Some(TapHoldState::Tap)
202                        } else if Some(ki) == self.other_pressed_keymap_index {
203                            // TapHold: interrupted by key tap (press + release); resolved as hold.
204                            Some(TapHoldState::Hold)
205                        } else {
206                            None
207                        }
208                    }
209                    key::Event::Key {
210                        key_event: Event::TapHoldTimeout,
211                        ..
212                    } => {
213                        // Key held long enough to resolve as hold.
214                        Some(TapHoldState::Hold)
215                    }
216                    _ => None,
217                }
218            }
219            InterruptResponse::Ignore => {
220                match event {
221                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
222                        if keymap_index == ki {
223                            // TapHold: not interrupted; resolved as tap.
224                            Some(TapHoldState::Tap)
225                        } else {
226                            None
227                        }
228                    }
229                    key::Event::Key {
230                        key_event: Event::TapHoldTimeout,
231                        ..
232                    } => {
233                        // Key held long enough to resolve as hold.
234                        Some(TapHoldState::Hold)
235                    }
236                    _ => None,
237                }
238            }
239        }
240    }
241
242    /// Returns at most 2 events
243    pub fn handle_event(
244        &mut self,
245        context: &Context,
246        keymap_index: u16,
247        event: key::Event<Event>,
248    ) -> Option<TapHoldState> {
249        // Check for interrupting taps
250        // (track other key press)
251        if let key::Event::Input(input::Event::Press { keymap_index: ki }) = event {
252            self.other_pressed_keymap_index = Some(ki);
253        }
254
255        // Resolve tap-hold state per the event.
256        let Context { config, .. } = context;
257        self.hold_resolution(config.interrupt_response, keymap_index, event)
258    }
259}
260
261/// Key state for tap_hold keys. (Not used).
262#[derive(Debug, Clone, Copy, PartialEq)]
263pub struct KeyState;
264
265/// The [key::System] implementation for tap hold keys.
266#[derive(Debug, Clone, Copy, PartialEq)]
267pub struct System<R, Keys: Index<usize, Output = Key<R>>> {
268    keys: Keys,
269}
270
271impl<R, Keys: Index<usize, Output = Key<R>>> System<R, Keys> {
272    /// Constructs a new [System] with the given key data.
273    pub const fn new(key_data: Keys) -> Self {
274        Self { keys: key_data }
275    }
276
277    fn new_pending_key(
278        &self,
279        context: &Context,
280        keymap_index: u16,
281    ) -> (PendingKeyState, key::ScheduledEvent<Event>) {
282        let timeout_ev = Event::TapHoldTimeout;
283        (
284            PendingKeyState::new(),
285            key::ScheduledEvent::after(
286                context.config.timeout,
287                key::Event::key_event(keymap_index, timeout_ev),
288            ),
289        )
290    }
291}
292
293impl<R: Copy + Debug, Keys: Debug + Index<usize, Output = Key<R>>> key::System<R>
294    for System<R, Keys>
295{
296    type Ref = Ref;
297    type Context = Context;
298    type Event = Event;
299    type PendingKeyState = PendingKeyState;
300    type KeyState = KeyState;
301
302    fn new_pressed_key(
303        &self,
304        keymap_index: u16,
305        context: &Self::Context,
306        Ref(key_index): Ref,
307    ) -> (
308        key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
309        key::KeyEvents<Self::Event>,
310    ) {
311        match context.config.required_idle_time {
312            Some(required_idle_time) => {
313                if context.idle_time_ms >= required_idle_time as u32 {
314                    // Keymap has been idle long enough; use pending tap-hold key state.
315                    let (th_pks, sch_ev) = self.new_pending_key(context, keymap_index);
316                    let pk = key::PressedKeyResult::Pending(th_pks);
317                    let pke = key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event());
318                    (pk, pke)
319                } else {
320                    // Keymap has not been idle for long enough;
321                    // immediately resolve as tap.
322                    let Key {
323                        tap: tap_key_ref, ..
324                    } = self.keys[key_index as usize];
325                    (
326                        key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(tap_key_ref)),
327                        key::KeyEvents::no_events(),
328                    )
329                }
330            }
331            None => {
332                // Idle time not considered. Use pending tap-hold key state.
333                let (th_pks, sch_ev) = self.new_pending_key(context, keymap_index);
334                let pk = key::PressedKeyResult::Pending(th_pks);
335                let pke = key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event());
336                (pk, pke)
337            }
338        }
339    }
340
341    fn update_pending_state(
342        &self,
343        pending_state: &mut Self::PendingKeyState,
344        keymap_index: u16,
345        context: &Self::Context,
346        Ref(key_index): Ref,
347        event: key::Event<Self::Event>,
348    ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
349        let th_state = pending_state.handle_event(context, keymap_index, event);
350        if let Some(th_state) = th_state {
351            let Key { tap, hold } = self.keys[key_index as usize];
352            let new_key_ref = match th_state {
353                key::tap_hold::TapHoldState::Tap => tap,
354                key::tap_hold::TapHoldState::Hold => hold,
355            };
356
357            (
358                Some(key::NewPressedKey::key(new_key_ref)),
359                key::KeyEvents::no_events(),
360            )
361        } else {
362            (None, key::KeyEvents::no_events())
363        }
364    }
365
366    fn update_state(
367        &self,
368        _key_state: &mut Self::KeyState,
369        _ref: &Self::Ref,
370        _context: &Self::Context,
371        _keymap_index: u16,
372        _event: key::Event<Self::Event>,
373    ) -> key::KeyEvents<Self::Event> {
374        panic!() // tap_hold has no key state
375    }
376
377    fn key_output(
378        &self,
379        _key_ref: &Self::Ref,
380        _key_state: &Self::KeyState,
381    ) -> Option<key::KeyOutput> {
382        panic!() // tap_hold has no key state
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[test]
391    fn test_sizeof_ref() {
392        assert_eq!(1, core::mem::size_of::<Ref>());
393    }
394
395    #[test]
396    fn test_sizeof_event() {
397        assert_eq!(0, core::mem::size_of::<Event>());
398    }
399}