Skip to main content

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