Skip to main content

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