On-each-release tap dance function (#20255)
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
This commit is contained in:
parent
821db155f2
commit
2c70d38910
6 changed files with 159 additions and 7 deletions
|
@ -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_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(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(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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
_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) {
|
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);
|
_process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset);
|
||||||
del_weak_mods(action->state.weak_mods);
|
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);
|
process_tap_dance_action_on_each_tap(action);
|
||||||
active_td = action->state.finished ? 0 : keycode;
|
active_td = action->state.finished ? 0 : keycode;
|
||||||
} else {
|
} else {
|
||||||
|
process_tap_dance_action_on_each_release(action);
|
||||||
if (action->state.finished) {
|
if (action->state.finished) {
|
||||||
process_tap_dance_action_on_reset(action);
|
process_tap_dance_action_on_reset(action);
|
||||||
|
if (active_td == keycode) {
|
||||||
|
active_td = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ typedef struct {
|
||||||
tap_dance_user_fn_t on_each_tap;
|
tap_dance_user_fn_t on_each_tap;
|
||||||
tap_dance_user_fn_t on_dance_finished;
|
tap_dance_user_fn_t on_dance_finished;
|
||||||
tap_dance_user_fn_t on_reset;
|
tap_dance_user_fn_t on_reset;
|
||||||
|
tap_dance_user_fn_t on_each_release;
|
||||||
} fn;
|
} fn;
|
||||||
void *user_data;
|
void *user_data;
|
||||||
} tap_dance_action_t;
|
} tap_dance_action_t;
|
||||||
|
@ -56,19 +57,22 @@ typedef struct {
|
||||||
} tap_dance_dual_role_t;
|
} tap_dance_dual_role_t;
|
||||||
|
|
||||||
#define ACTION_TAP_DANCE_DOUBLE(kc1, kc2) \
|
#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) \
|
#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) \
|
#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) \
|
#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) \
|
#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(n) (QK_TAP_DANCE | TD_INDEX(n))
|
||||||
#define TD_INDEX(code) ((code)&0xFF)
|
#define TD_INDEX(code) ((code)&0xFF)
|
||||||
|
|
|
@ -187,13 +187,35 @@ void x_reset(tap_dance_state_t *state, void *user_data) {
|
||||||
xtap_state.state = TD_NONE;
|
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[] = {
|
tap_dance_action_t tap_dance_actions[] = {
|
||||||
[TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS),
|
[TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS),
|
||||||
[CT_EGG] = ACTION_TAP_DANCE_FN(dance_egg),
|
[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_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),
|
[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
|
// clang-format on
|
||||||
|
|
|
@ -26,6 +26,8 @@ enum {
|
||||||
CT_FLSH,
|
CT_FLSH,
|
||||||
CT_CLN,
|
CT_CLN,
|
||||||
X_CTL,
|
X_CTL,
|
||||||
|
TD_RELEASE,
|
||||||
|
TD_RELEASE_AND_FINISH,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -316,3 +316,119 @@ TEST_F(TapDance, QuadFunction) {
|
||||||
EXPECT_EMPTY_REPORT(driver);
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
run_one_scan_loop();
|
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();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue