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                    // PRESSED KEY PATH: add Tap Hold item (0 = tap, 1 = hold)
110                    let tap_key_path = key_path.append_path_item(0);
111                    (
112                        key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key_path(
113                            tap_key_path,
114                        )),
115                        key::KeyEvents::no_events(),
116                    )
117                }
118            }
119            None => {
120                // Idle time not considered. Use pending tap-hold key state.
121                let (th_pks, sch_ev) = self.new_pending_key(th_ctx, key_path.clone());
122                let pk = key::PressedKeyResult::Pending(key_path, th_pks.into());
123                let pke = key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event());
124                (pk, pke)
125            }
126        }
127    }
128
129    fn new_pending_key(
130        &self,
131        context: &Context,
132        key_path: key::KeyPath,
133    ) -> (PendingKeyState, key::ScheduledEvent<Event>) {
134        let keymap_index: u16 = key_path.keymap_index();
135        let timeout_ev = Event::TapHoldTimeout;
136        (
137            PendingKeyState::new(),
138            key::ScheduledEvent::after(
139                context.config.timeout,
140                key::Event::key_event(keymap_index, timeout_ev),
141            ),
142        )
143    }
144}
145
146impl<
147        K: key::Key<
148            Context = crate::init::Context,
149            Event = crate::init::Event,
150            PendingKeyState = crate::init::PendingKeyState,
151            KeyState = crate::init::KeyState,
152        >,
153    > key::Key for Key<K>
154{
155    type Context = crate::init::Context;
156    type Event = crate::init::Event;
157    type PendingKeyState = crate::init::PendingKeyState;
158    type KeyState = crate::init::KeyState;
159
160    fn new_pressed_key(
161        &self,
162        context: &Self::Context,
163        key_path: key::KeyPath,
164    ) -> (
165        key::PressedKeyResult<Self::PendingKeyState, Self::KeyState>,
166        key::KeyEvents<Self::Event>,
167    ) {
168        self.new_pressed_key(context, key_path.clone())
169    }
170
171    fn handle_event(
172        &self,
173        pending_state: &mut Self::PendingKeyState,
174        context: &Self::Context,
175        key_path: key::KeyPath,
176        event: key::Event<Self::Event>,
177    ) -> (Option<key::NewPressedKey>, key::KeyEvents<Self::Event>) {
178        let keymap_index = key_path.keymap_index();
179        let th_pks_res: Result<&mut PendingKeyState, _> = pending_state.try_into();
180        if let Ok(th_pks) = th_pks_res {
181            if let Ok(th_ev) = event.try_into_key_event(|e| e.try_into()) {
182                let th_state = th_pks.handle_event(context.into(), keymap_index, th_ev);
183                if let Some(th_state) = th_state {
184                    let i = match th_state {
185                        key::tap_hold::TapHoldState::Tap => 0,
186                        key::tap_hold::TapHoldState::Hold => 1,
187                    };
188                    // PRESSED KEY PATH: add Tap Hold item (0 = tap, 1 = hold)
189                    let new_key_path = key_path.append_path_item(i);
190
191                    (
192                        Some(key::NewPressedKey::key_path(new_key_path)),
193                        key::KeyEvents::no_events(),
194                    )
195                } else {
196                    (None, key::KeyEvents::no_events())
197                }
198            } else {
199                (None, key::KeyEvents::no_events())
200            }
201        } else {
202            (None, key::KeyEvents::no_events())
203        }
204    }
205
206    fn lookup(
207        &self,
208        path: &[u16],
209    ) -> &dyn key::Key<
210        Context = Self::Context,
211        Event = Self::Event,
212        PendingKeyState = Self::PendingKeyState,
213        KeyState = Self::KeyState,
214    > {
215        match path {
216            [] => self,
217            // 0 = tap, 1 = hold
218            [0, path @ ..] => self.tap.lookup(path),
219            [1, path @ ..] => self.hold.lookup(path),
220            _ => panic!(),
221        }
222    }
223}
224
225/// Context for [Key].
226#[derive(Debug, Clone, Copy, PartialEq)]
227pub struct Context {
228    config: Config,
229}
230
231/// Default context.
232pub const DEFAULT_CONTEXT: Context = Context::from_config(DEFAULT_CONFIG);
233
234impl Context {
235    /// Constructs a context from the given config
236    pub const fn from_config(config: Config) -> Context {
237        Context { config }
238    }
239}
240
241/// The state of a tap-hold key.
242#[derive(Debug, Clone, Copy, PartialEq)]
243pub enum TapHoldState {
244    /// Resolved as tap.
245    Tap,
246    /// Resolved as hold.
247    Hold,
248}
249
250/// Events emitted by a tap-hold key.
251#[derive(Debug, Clone, Copy, PartialEq)]
252pub enum Event {
253    /// Event indicating the key has been held long enough to resolve as hold.
254    TapHoldTimeout,
255}
256
257/// The state of a pressed tap-hold key.
258#[derive(Debug, Clone, PartialEq)]
259pub struct PendingKeyState {
260    // For tracking 'tap' interruptions
261    other_pressed_keymap_index: Option<u16>,
262}
263
264impl PendingKeyState {
265    /// Constructs the initial pressed key state
266    fn new() -> PendingKeyState {
267        PendingKeyState {
268            other_pressed_keymap_index: None,
269        }
270    }
271
272    /// Compute whether the tap-hold key should resolve as tap or hold,
273    ///  given the tap hold config, the current state, and the key event.
274    fn hold_resolution(
275        &self,
276        interrupt_response: InterruptResponse,
277        keymap_index: u16,
278        event: key::Event<Event>,
279    ) -> Option<TapHoldState> {
280        match interrupt_response {
281            InterruptResponse::HoldOnKeyPress => {
282                match event {
283                    key::Event::Input(input::Event::Press { .. }) => {
284                        // TapHold: any interruption resolves pending TapHold as Hold.
285                        Some(TapHoldState::Hold)
286                    }
287                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
288                        if keymap_index == ki {
289                            // TapHold: not interrupted; resolved as tap.
290                            Some(TapHoldState::Tap)
291                        } else {
292                            None
293                        }
294                    }
295                    key::Event::Key {
296                        key_event: Event::TapHoldTimeout,
297                        ..
298                    } => {
299                        // Key held long enough to resolve as hold.
300                        Some(TapHoldState::Hold)
301                    }
302                    _ => None,
303                }
304            }
305            InterruptResponse::HoldOnKeyTap => {
306                match event {
307                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
308                        if keymap_index == ki {
309                            // TapHold: not interrupted; resolved as tap.
310                            Some(TapHoldState::Tap)
311                        } else if Some(ki) == self.other_pressed_keymap_index {
312                            // TapHold: interrupted by key tap (press + release); resolved as hold.
313                            Some(TapHoldState::Hold)
314                        } else {
315                            None
316                        }
317                    }
318                    key::Event::Key {
319                        key_event: Event::TapHoldTimeout,
320                        ..
321                    } => {
322                        // Key held long enough to resolve as hold.
323                        Some(TapHoldState::Hold)
324                    }
325                    _ => None,
326                }
327            }
328            InterruptResponse::Ignore => {
329                match event {
330                    key::Event::Input(input::Event::Release { keymap_index: ki }) => {
331                        if keymap_index == ki {
332                            // TapHold: not interrupted; resolved as tap.
333                            Some(TapHoldState::Tap)
334                        } else {
335                            None
336                        }
337                    }
338                    key::Event::Key {
339                        key_event: Event::TapHoldTimeout,
340                        ..
341                    } => {
342                        // Key held long enough to resolve as hold.
343                        Some(TapHoldState::Hold)
344                    }
345                    _ => None,
346                }
347            }
348        }
349    }
350
351    /// Returns at most 2 events
352    pub fn handle_event(
353        &mut self,
354        context: &Context,
355        keymap_index: u16,
356        event: key::Event<Event>,
357    ) -> Option<TapHoldState> {
358        // Check for interrupting taps
359        // (track other key press)
360        if let key::Event::Input(input::Event::Press { keymap_index: ki }) = event {
361            self.other_pressed_keymap_index = Some(ki);
362        }
363
364        // Resolve tap-hold state per the event.
365        let Context { config, .. } = context;
366        self.hold_resolution(config.interrupt_response, keymap_index, event)
367    }
368}