smart_keymap/key/
tap_hold.rs

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