From 2c70d3891002337ef981a478b95c621f68dfd777 Mon Sep 17 00:00:00 2001 From: leep-frog <66687468+leep-frog@users.noreply.github.com> Date: Thu, 27 Jul 2023 00:04:35 -0400 Subject: [PATCH] On-each-release tap dance function (#20255) Co-authored-by: Sergey Vlasov --- docs/feature_tap_dance.md | 2 +- quantum/process_keycode/process_tap_dance.c | 8 ++ quantum/process_keycode/process_tap_dance.h | 14 ++- tests/tap_dance/examples.c | 24 +++- tests/tap_dance/examples.h | 2 + tests/tap_dance/test_examples.cpp | 116 ++++++++++++++++++++ 6 files changed, 159 insertions(+), 7 deletions(-) diff --git a/docs/feature_tap_dance.md b/docs/feature_tap_dance.md index b7d8a5528f..42ea233962 100644 --- a/docs/feature_tap_dance.md +++ b/docs/feature_tap_dance.md @@ -28,7 +28,7 @@ After this, you'll want to use the `tap_dance_actions` array to specify what act * `ACTION_TAP_DANCE_LAYER_TOGGLE(kc, layer)`: Sends the `kc` keycode when tapped once, or toggles the state of `layer`. (this functions like the `TG` layer keycode). * `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the final tap count of the tap dance action. * `ACTION_TAP_DANCE_FN_ADVANCED(on_each_tap_fn, on_dance_finished_fn, on_dance_reset_fn)`: Calls the first specified function - defined in the user keymap - on every tap, the second function when the dance action finishes (like the previous option), and the last function when the tap dance action resets. - +* `ACTION_TAP_DANCE_FN_ADVANCED_WITH_RELEASE(on_each_tap_fn, on_each_release_fn, on_dance_finished_fn, on_dance_reset_fn)`: This macro is identical to `ACTION_TAP_DANCE_FN_ADVANCED` with the addition of `on_each_release_fn` which is invoked every time the key for the tap dance is released. It is worth noting that `on_each_release_fn` will still be called even when the key is released after the dance finishes (e.g. if the key is released after being pressed and held for longer than the `TAPPING_TERM`). The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise. diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c index 1b2c6c8a44..b8a8d32f35 100644 --- a/quantum/process_keycode/process_tap_dance.c +++ b/quantum/process_keycode/process_tap_dance.c @@ -95,6 +95,10 @@ static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *acti _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_tap); } +static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action) { + _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_release); +} + static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action) { _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset); del_weak_mods(action->state.weak_mods); @@ -158,8 +162,12 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { process_tap_dance_action_on_each_tap(action); active_td = action->state.finished ? 0 : keycode; } else { + process_tap_dance_action_on_each_release(action); if (action->state.finished) { process_tap_dance_action_on_reset(action); + if (active_td == keycode) { + active_td = 0; + } } } diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h index 386f504619..2b114dabd3 100644 --- a/quantum/process_keycode/process_tap_dance.h +++ b/quantum/process_keycode/process_tap_dance.h @@ -40,6 +40,7 @@ typedef struct { tap_dance_user_fn_t on_each_tap; tap_dance_user_fn_t on_dance_finished; tap_dance_user_fn_t on_reset; + tap_dance_user_fn_t on_each_release; } fn; void *user_data; } tap_dance_action_t; @@ -56,19 +57,22 @@ typedef struct { } tap_dance_dual_role_t; #define ACTION_TAP_DANCE_DOUBLE(kc1, kc2) \ - { .fn = {tap_dance_pair_on_each_tap, tap_dance_pair_finished, tap_dance_pair_reset}, .user_data = (void *)&((tap_dance_pair_t){kc1, kc2}), } + { .fn = {tap_dance_pair_on_each_tap, tap_dance_pair_finished, tap_dance_pair_reset, NULL}, .user_data = (void *)&((tap_dance_pair_t){kc1, kc2}), } #define ACTION_TAP_DANCE_LAYER_MOVE(kc, layer) \ - { .fn = {tap_dance_dual_role_on_each_tap, tap_dance_dual_role_finished, tap_dance_dual_role_reset}, .user_data = (void *)&((tap_dance_dual_role_t){kc, layer, layer_move}), } + { .fn = {tap_dance_dual_role_on_each_tap, tap_dance_dual_role_finished, tap_dance_dual_role_reset, NULL}, .user_data = (void *)&((tap_dance_dual_role_t){kc, layer, layer_move}), } #define ACTION_TAP_DANCE_LAYER_TOGGLE(kc, layer) \ - { .fn = {NULL, tap_dance_dual_role_finished, tap_dance_dual_role_reset}, .user_data = (void *)&((tap_dance_dual_role_t){kc, layer, layer_invert}), } + { .fn = {NULL, tap_dance_dual_role_finished, tap_dance_dual_role_reset, NULL}, .user_data = (void *)&((tap_dance_dual_role_t){kc, layer, layer_invert}), } #define ACTION_TAP_DANCE_FN(user_fn) \ - { .fn = {NULL, user_fn, NULL}, .user_data = NULL, } + { .fn = {NULL, user_fn, NULL, NULL}, .user_data = NULL, } #define ACTION_TAP_DANCE_FN_ADVANCED(user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset) \ - { .fn = {user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset}, .user_data = NULL, } + { .fn = {user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset, NULL}, .user_data = NULL, } + +#define ACTION_TAP_DANCE_FN_ADVANCED_WITH_RELEASE(user_fn_on_each_tap, user_fn_on_each_release, user_fn_on_dance_finished, user_fn_on_dance_reset) \ + { .fn = {user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_dance_reset, user_fn_on_each_release}, .user_data = NULL, } #define TD(n) (QK_TAP_DANCE | TD_INDEX(n)) #define TD_INDEX(code) ((code)&0xFF) diff --git a/tests/tap_dance/examples.c b/tests/tap_dance/examples.c index af74388209..13086bbb4b 100644 --- a/tests/tap_dance/examples.c +++ b/tests/tap_dance/examples.c @@ -187,13 +187,35 @@ void x_reset(tap_dance_state_t *state, void *user_data) { xtap_state.state = TD_NONE; } +static void release_press(tap_dance_state_t *state, void *user_data) { + tap_code16(KC_P); +} + +static void release_unpress(tap_dance_state_t *state, void *user_data) { + tap_code16(KC_U); +} + +static void release_unpress_mark_finished(tap_dance_state_t *state, void *user_data) { + tap_code16(KC_U); + state->finished = true; +} + +static void release_finished(tap_dance_state_t *state, void *user_data) { + tap_code16(KC_F); +} + +static void release_reset(tap_dance_state_t *state, void *user_data) { + tap_code16(KC_R); +} tap_dance_action_t tap_dance_actions[] = { [TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS), [CT_EGG] = ACTION_TAP_DANCE_FN(dance_egg), [CT_FLSH] = ACTION_TAP_DANCE_FN_ADVANCED(dance_flsh_each, dance_flsh_finished, dance_flsh_reset), [CT_CLN] = ACTION_TAP_DANCE_TAP_HOLD(KC_COLN, KC_SCLN), - [X_CTL] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished, x_reset) + [X_CTL] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished, x_reset), + [TD_RELEASE] = ACTION_TAP_DANCE_FN_ADVANCED_WITH_RELEASE(release_press, release_unpress, release_finished, release_reset), + [TD_RELEASE_AND_FINISH] = ACTION_TAP_DANCE_FN_ADVANCED_WITH_RELEASE(release_press, release_unpress_mark_finished, release_finished, release_reset), }; // clang-format on diff --git a/tests/tap_dance/examples.h b/tests/tap_dance/examples.h index 2622af6b2f..6118188dd1 100644 --- a/tests/tap_dance/examples.h +++ b/tests/tap_dance/examples.h @@ -26,6 +26,8 @@ enum { CT_FLSH, CT_CLN, X_CTL, + TD_RELEASE, + TD_RELEASE_AND_FINISH, }; #ifdef __cplusplus diff --git a/tests/tap_dance/test_examples.cpp b/tests/tap_dance/test_examples.cpp index 6dabc45513..7858dab92b 100644 --- a/tests/tap_dance/test_examples.cpp +++ b/tests/tap_dance/test_examples.cpp @@ -316,3 +316,119 @@ TEST_F(TapDance, QuadFunction) { EXPECT_EMPTY_REPORT(driver); run_one_scan_loop(); } + +TEST_F(TapDance, DanceFnAdvancedWithRelease) { + TestDriver driver; + InSequence s; + auto key_rls = KeymapKey(0, 1, 0, TD(TD_RELEASE)); + + set_keymap({key_rls}); + + /* Single press and unpress */ + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_F)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_R)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + /* Double press and unpress */ + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_F)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_R)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + /* Unpress after tapping term has elapsed (key is registered as held) */ + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_F)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_R)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); +} + +TEST_F(TapDance, DanceFnAdvancedWithReleaseAndFinish) { + TestDriver driver; + InSequence s; + auto key_rls = KeymapKey(0, 1, 0, TD(TD_RELEASE_AND_FINISH)); + + set_keymap({key_rls}); + + /* Single press and unpress */ + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_R)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + // Verify the finished and/or reset functions aren't called + // after the tapping term elapses + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + /* Unpress after tapping term has elapsed (key is registered as held) */ + key_rls.press(); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_F)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + key_rls.release(); + EXPECT_REPORT(driver, (KC_U)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_R)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); +}