implement multiple-choice for holdtap repeat

This commit is contained in:
xs5871 2022-10-14 15:20:55 +00:00 committed by Kyle Brown
parent 72735e0b5d
commit 178afdfeb1
3 changed files with 44 additions and 12 deletions

@ -31,7 +31,7 @@ keyboard.modules.append(modtap)
## Custom HoldTap Behavior ## Custom HoldTap Behavior
The full ModTap signature is as follows: The full ModTap signature is as follows:
```python ```python
KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=False) KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=HoldTapRepeat.NONE)
``` ```
* `prefer_hold`: decides which keycode the ModTap key resolves to when another * `prefer_hold`: decides which keycode the ModTap key resolves to when another
key is pressed before the timeout finishes. When `True` the hold keycode is key is pressed before the timeout finishes. When `True` the hold keycode is
@ -42,9 +42,10 @@ KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, r
* `tap_time`: length of the tap timeout in milliseconds. * `tap_time`: length of the tap timeout in milliseconds.
* `repeat`: decides how to interpret repeated presses if they happen within * `repeat`: decides how to interpret repeated presses if they happen within
`tap_time` after a release. `tap_time` after a release.
When `True` the repeated press sends the previous keycode, no matter * `TAP`: repeat tap action, if previous action was a tap.
how long the key remains pressed the second time. * `HOLD`: repeat hold action, if previous action was a hold.
This allows to hold the tap keycode, or repeatedly tap the hold keycode. * `ALL`: repeat all of the above.
When `False` repeated presses are independent. * `NONE`: no repeat action (default), everything works as expected.
The `HoldTapRepeat` enum must be imported from `kmk.modules.holdtap`.
Each of these parameters can be set for every ModTap key individually. Each of these parameters can be set for every ModTap key individually.

@ -15,6 +15,13 @@ class ActivationType:
REPEAT = const(4) REPEAT = const(4)
class HoldTapRepeat:
NONE = const(0)
TAP = const(1)
HOLD = const(2)
ALL = const(3)
class HoldTapKeyState: class HoldTapKeyState:
def __init__(self, timeout_key, *args, **kwargs): def __init__(self, timeout_key, *args, **kwargs):
self.timeout_key = timeout_key self.timeout_key = timeout_key
@ -31,7 +38,7 @@ class HoldTapKeyMeta:
prefer_hold=True, prefer_hold=True,
tap_interrupted=False, tap_interrupted=False,
tap_time=None, tap_time=None,
repeat=False, repeat=HoldTapRepeat.NONE,
): ):
self.tap = tap self.tap = tap
self.hold = hold self.hold = hold
@ -155,13 +162,17 @@ class HoldTap(Module):
state = self.key_states[key] state = self.key_states[key]
keyboard.cancel_timeout(state.timeout_key) keyboard.cancel_timeout(state.timeout_key)
repeat = key.meta.repeat & HoldTapRepeat.TAP
if state.activated == ActivationType.HOLD_TIMEOUT: if state.activated == ActivationType.HOLD_TIMEOUT:
# release hold # release hold
self.ht_deactivate_hold(key, keyboard, *args, **kwargs) self.ht_deactivate_hold(key, keyboard, *args, **kwargs)
repeat = key.meta.repeat & HoldTapRepeat.HOLD
elif state.activated == ActivationType.INTERRUPTED: elif state.activated == ActivationType.INTERRUPTED:
# release tap # release tap
self.ht_deactivate_on_interrupt(key, keyboard, *args, **kwargs) self.ht_deactivate_on_interrupt(key, keyboard, *args, **kwargs)
if key.meta.prefer_hold:
repeat = key.meta.repeat & HoldTapRepeat.HOLD
elif state.activated == ActivationType.PRESSED: elif state.activated == ActivationType.PRESSED:
# press and release tap because key released within tap time # press and release tap because key released within tap time
self.ht_activate_tap(key, keyboard, *args, **kwargs) self.ht_activate_tap(key, keyboard, *args, **kwargs)
@ -174,15 +185,16 @@ class HoldTap(Module):
self.ht_deactivate_tap(key, keyboard, *args, **kwargs) self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
# don't delete the key state right now in this case # don't delete the key state right now in this case
tap_time = 0 if repeat:
if key.meta.repeat:
if key.meta.tap_time is None: if key.meta.tap_time is None:
tap_time = self.tap_time tap_time = self.tap_time
else: else:
tap_time = key.meta.tap_time tap_time = key.meta.tap_time
state.timeout_key = keyboard.set_timeout( state.timeout_key = keyboard.set_timeout(
tap_time, lambda: self.key_states.pop(key) tap_time, lambda: self.key_states.pop(key)
) )
else:
del self.key_states[key]
return keyboard return keyboard

@ -1,6 +1,7 @@
import unittest import unittest
from kmk.keys import KC from kmk.keys import KC
from kmk.modules.holdtap import HoldTapRepeat
from kmk.modules.layers import Layers from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap from kmk.modules.modtap import ModTap
from kmk.modules.oneshot import OneShot from kmk.modules.oneshot import OneShot
@ -275,7 +276,13 @@ class TestHoldTap(unittest.TestCase):
def test_holdtap_repeat(self): def test_holdtap_repeat(self):
keyboard = KeyboardTest( keyboard = KeyboardTest(
[ModTap()], [ModTap()],
[[KC.MT(KC.A, KC.B, repeat=True, tap_time=50)]], [
[
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.ALL, tap_time=50),
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.TAP, tap_time=50),
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD, tap_time=50),
]
],
debug_enabled=False, debug_enabled=False,
) )
@ -331,6 +338,18 @@ class TestHoldTap(unittest.TestCase):
[{KC.A}, {}, {KC.B}, {}, {KC.A}, {}], [{KC.A}, {}, {KC.B}, {}, {KC.A}, {}],
) )
keyboard.test(
'tap repeat / no hold repeat ',
[(1, True), t_after, (1, False), (1, True), (1, False)],
[{KC.B}, {}, {KC.A}, {}],
)
keyboard.test(
'hold repeat / no tap repeat ',
[(2, True), (2, False), (2, True), t_after, (2, False)],
[{KC.A}, {}, {KC.B}, {}],
)
def test_oneshot(self): def test_oneshot(self):
keyboard = KeyboardTest( keyboard = KeyboardTest(
[Layers(), ModTap(), OneShot()], [Layers(), ModTap(), OneShot()],