diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 4e032f9dcfa..0880934e957 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -620,6 +620,7 @@ ifeq ($(strip $(VIA_ENABLE)), yes) DYNAMIC_KEYMAP_ENABLE := yes RAW_ENABLE := yes BOOTMAGIC_ENABLE := yes + TRI_LAYER_ENABLE := yes SRC += $(QUANTUM_DIR)/via.c OPT_DEFS += -DVIA_ENABLE endif diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 0d897bc1c82..5a1ef5c6f0a 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -39,6 +39,7 @@ GENERIC_FEATURES = \ VELOCIKEY \ WPM \ DYNAMIC_TAPPING_TERM \ + TRI_LAYER define HANDLE_GENERIC_FEATURE # $$(info "Processing: $1_ENABLE $2.c") diff --git a/builddefs/show_options.mk b/builddefs/show_options.mk index e9f7e7d0471..9723b45438a 100644 --- a/builddefs/show_options.mk +++ b/builddefs/show_options.mk @@ -84,7 +84,8 @@ OTHER_OPTION_NAMES = \ PROGRAMMABLE_BUTTON_ENABLE \ SECURE_ENABLE \ CAPS_WORD_ENABLE \ - AUTOCORRECT_ENABLE + AUTOCORRECT_ENABLE \ + TRI_LAYER_ENABLE define NAME_ECHO @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)" diff --git a/data/constants/keycodes/keycodes_0.0.2_quantum.hjson b/data/constants/keycodes/keycodes_0.0.2_quantum.hjson new file mode 100644 index 00000000000..960afa4e515 --- /dev/null +++ b/data/constants/keycodes/keycodes_0.0.2_quantum.hjson @@ -0,0 +1,18 @@ +{ + "keycodes": { + "0x7C77": { + "group": "quantum", + "key": "QK_TRI_LAYER_LOWER", + "aliases": [ + "TL_LOWR" + ] + }, + "0x7C78": { + "group": "quantum", + "key": "QK_TRI_LAYER_UPPER", + "aliases": [ + "TL_UPPR" + ] + } + } +} diff --git a/docs/_summary.md b/docs/_summary.md index d2d4bb9b326..b038f08a4dc 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -93,6 +93,7 @@ * [Swap Hands](feature_swap_hands.md) * [Tap Dance](feature_tap_dance.md) * [Tap-Hold Configuration](tap_hold.md) + * [Tri Layer](feature_tri_layer.md) * [Unicode](feature_unicode.md) * [Userspace](feature_userspace.md) * [WPM Calculation](feature_wpm.md) diff --git a/docs/feature_tri_layer.md b/docs/feature_tri_layer.md new file mode 100644 index 00000000000..aa6c87719ca --- /dev/null +++ b/docs/feature_tri_layer.md @@ -0,0 +1,48 @@ +# Tri Layers :id=tri-layers + +This enables support for the OLKB style "Tri Layer" keycodes. These function similar to the `MO` (momentary) function key, but if both the "Lower" and "Upper" keys are pressed, it activates a third "Adjust" layer. To enable this functionality, add this line to your `rules.mk`: + +```make +TRI_LAYER_ENABLE = yes +``` + +Note that the "upper", "lower" and "adjust" names don't have a particular significance, they are just used to identify and clarify the behavior. Layers are processed from highest numeric value to lowest, however the values are not required to be consecutive. + +For a detailed explanation of how the layer stack works, check out [Keymap Overview](keymap.md#keymap-and-layers). + +## Keycodes :id=keycodes + +| Keycode | Alias | Description | +|----------------------|-----------|---------------------------------------------------------------------------------------------------------| +| `QK_TRI_LAYER_LOWER` | `TL_LOWR` | Momentarily enables the "lower" layer. Enables the "adjust" layer if the "upper" layer is also enabled" | +| `QK_TRI_LAYER_UPPER` | `TL_UPPR` | Momentarily enables the "upper" layer. Enables the "adjust" layer if the "lower" layer is also enabled" | + +## Configuration + +To change the default values for the layers, you can change these defines, in your `config.h` + +| Config name | Default | Description | +|--------------------------|---------|------------------------------------------| +| `TRI_LAYER_LOWER_LAYER` | `1` | Sets the default for the "lower" layer. | +| `TRI_LAYER_UPPER_LAYER` | `2` | Sets the default for the "upper" layer. | +| `TRI_LAYER_ADJUST_LAYER` | `3` | Sets the default for the "adjust" layer. | + +Eg, if you wanted to set the "Adjust" layer to be layer 5, you'd add this to your `config.h`: + +```c +#define TRI_LAYER_ADJUST_LAYER 5 +``` + +## Functions + +| Function name | Description | +|----------------------------------------------|-------------------------------------------------| +| `set_tri_layer_lower_layer(layer)` | Changes the "lower" layer*. | +| `set_tri_layer_upper_layer(layer)` | Changes the "upper" layer*. | +| `set_tri_layer_adjust_layer(layer)` | Changes the "adjust" layer*. | +| `set_tri_layer_layers(lower, upper, adjust)` | Stes the "lower", "upper" and "adjust" layers*. | +| `get_tri_layer_lower_layer()` | Gets the current "lower" layer. | +| `get_tri_layer_upper_layer()` | Gets the current "upper" layer. | +| `get_tri_layer_adjust_layer()` | Gets the current "adjust" layer. | + +!> Note: these settings are not persisent, and will be reset to the default on power loss or power cycling of the controller. diff --git a/quantum/action_layer.c b/quantum/action_layer.c index 74e81379207..35f494df8f1 100644 --- a/quantum/action_layer.c +++ b/quantum/action_layer.c @@ -349,3 +349,13 @@ uint8_t layer_switch_get_layer(keypos_t key) { action_t layer_switch_get_action(keypos_t key) { return action_for_key(layer_switch_get_layer(key), key); } + +layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3) { + layer_state_t mask12 = ((layer_state_t)1 << layer1) | ((layer_state_t)1 << layer2); + layer_state_t mask3 = (layer_state_t)1 << layer3; + return (state & mask12) == mask12 ? (state | mask3) : (state & ~mask3); +} + +void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3) { + layer_state_set(update_tri_layer_state(layer_state, layer1, layer2, layer3)); +} diff --git a/quantum/action_layer.h b/quantum/action_layer.h index 3fe27265299..ff783bb3e7f 100644 --- a/quantum/action_layer.h +++ b/quantum/action_layer.h @@ -113,6 +113,25 @@ void layer_and(layer_state_t state); void layer_xor(layer_state_t state); layer_state_t layer_state_set_user(layer_state_t state); layer_state_t layer_state_set_kb(layer_state_t state); + +/** + * @brief Applies the tri layer to global layer state. Not be used in layer_state_set_(kb|user) functions. + * + * @param layer1 First layer to check for tri layer + * @param layer2 Second layer to check for tri layer + * @param layer3 Layer to activate if both other layers are enabled + */ +void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3); +/** + * @brief Applies the tri layer behavior to supplied layer bitmask, without using layer functions. + * + * @param state Original layer bitmask to check and modify + * @param layer1 First layer to check for tri layer + * @param layer2 Second layer to check for tri layer + * @param layer3 Layer to activate if both other layers are enabled + * @return layer_state_t returns a modified layer bitmask with tri layer modifications applied + */ +layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3); #else # define layer_state 0 @@ -131,6 +150,8 @@ layer_state_t layer_state_set_kb(layer_state_t state); # define layer_xor(state) (void)state # define layer_state_set_kb(state) (void)state # define layer_state_set_user(state) (void)state +# define update_tri_layer(layer1, layer2, layer3) +# define update_tri_layer_state(state, layer1, layer2, layer3) (void)state #endif /* pressed actions cache */ diff --git a/quantum/keycodes.h b/quantum/keycodes.h index f24ccf01b8f..4fa4306ed61 100644 --- a/quantum/keycodes.h +++ b/quantum/keycodes.h @@ -717,6 +717,8 @@ enum qk_keycode_defines { QK_AUTOCORRECT_ON = 0x7C74, QK_AUTOCORRECT_OFF = 0x7C75, QK_AUTOCORRECT_TOGGLE = 0x7C76, + QK_TRI_LAYER_LOWER = 0x7C77, + QK_TRI_LAYER_UPPER = 0x7C78, SAFE_RANGE = 0x7E00, // Alias @@ -1282,6 +1284,8 @@ enum qk_keycode_defines { AC_ON = QK_AUTOCORRECT_ON, AC_OFF = QK_AUTOCORRECT_OFF, AC_TOGG = QK_AUTOCORRECT_TOGGLE, + TL_LOWR = QK_TRI_LAYER_LOWER, + TL_UPPR = QK_TRI_LAYER_UPPER, }; // Range Helpers @@ -1333,4 +1337,4 @@ enum qk_keycode_defines { #define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31) #define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING) #define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE) -#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_AUTOCORRECT_TOGGLE) +#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_TRI_LAYER_UPPER) diff --git a/quantum/process_keycode/process_tri_layer.c b/quantum/process_keycode/process_tri_layer.c new file mode 100644 index 00000000000..1e681b9a1c2 --- /dev/null +++ b/quantum/process_keycode/process_tri_layer.c @@ -0,0 +1,30 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "process_tri_layer.h" +#include "tri_layer.h" +#include "action_layer.h" + +bool process_tri_layer(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case QK_TRI_LAYER_LOWER: + if (record->event.pressed) { + layer_on(get_tri_layer_lower_layer()); + update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer()); + } else { + layer_off(get_tri_layer_lower_layer()); + update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer()); + } + return false; + case QK_TRI_LAYER_UPPER: + if (record->event.pressed) { + layer_on(get_tri_layer_upper_layer()); + update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer()); + } else { + layer_off(get_tri_layer_upper_layer()); + update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer()); + } + return false; + } + return true; +} diff --git a/quantum/process_keycode/process_tri_layer.h b/quantum/process_keycode/process_tri_layer.h new file mode 100644 index 00000000000..9c4e3df1c2f --- /dev/null +++ b/quantum/process_keycode/process_tri_layer.h @@ -0,0 +1,16 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "action.h" + +/** + * @brief Handles tri layer behavior + * + * @param keycode the keycode + * @param record the key record structure + * @return true continue handling keycodes + * @return false stop handling keycodes + */ +bool process_tri_layer(uint16_t keycode, keyrecord_t *record); diff --git a/quantum/quantum.c b/quantum/quantum.c index 5554ecab327..0587f215fe3 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -342,6 +342,9 @@ bool process_record_quantum(keyrecord_t *record) { #endif #ifdef AUTOCORRECT_ENABLE process_autocorrect(keycode, record) && +#endif +#ifdef TRI_LAYER_ENABLE + process_tri_layer(keycode, record) && #endif true)) { return false; @@ -443,16 +446,6 @@ void set_single_persistent_default_layer(uint8_t default_layer) { default_layer_set((layer_state_t)1 << default_layer); } -layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3) { - layer_state_t mask12 = ((layer_state_t)1 << layer1) | ((layer_state_t)1 << layer2); - layer_state_t mask3 = (layer_state_t)1 << layer3; - return (state & mask12) == mask12 ? (state | mask3) : (state & ~mask3); -} - -void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3) { - layer_state_set(update_tri_layer_state(layer_state, layer1, layer2, layer3)); -} - //------------------------------------------------------------------------------ // Override these functions in your keymap file to play different tunes on // different events such as startup and bootloader jump diff --git a/quantum/quantum.h b/quantum/quantum.h index 615ec2382c5..708d325a321 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -240,9 +240,10 @@ extern layer_state_t layer_state; # include "process_autocorrect.h" #endif -// For tri-layer -void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3); -layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3); +#ifdef TRI_LAYER_ENABLE +# include "tri_layer.h" +# include "process_tri_layer.h" +#endif void set_single_persistent_default_layer(uint8_t default_layer); diff --git a/quantum/tri_layer.c b/quantum/tri_layer.c new file mode 100644 index 00000000000..a5e3f8cb477 --- /dev/null +++ b/quantum/tri_layer.c @@ -0,0 +1,39 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "tri_layer.h" +#include + +static uint8_t tri_layer_lower_layer = TRI_LAYER_LOWER_LAYER; +static uint8_t tri_layer_upper_layer = TRI_LAYER_UPPER_LAYER; +static uint8_t tri_layer_adjust_layer = TRI_LAYER_ADJUST_LAYER; + +void set_tri_layer_lower_layer(uint8_t layer) { + tri_layer_lower_layer = layer; +} + +void set_tri_layer_upper_layer(uint8_t layer) { + tri_layer_upper_layer = layer; +} + +void set_tri_layer_adjust_layer(uint8_t layer) { + tri_layer_adjust_layer = layer; +} + +void set_tri_layer_layers(uint8_t lower, uint8_t raise, uint8_t adjust) { + tri_layer_lower_layer = lower; + tri_layer_upper_layer = raise; + tri_layer_adjust_layer = adjust; +} + +uint8_t get_tri_layer_lower_layer(void) { + return tri_layer_lower_layer; +} + +uint8_t get_tri_layer_upper_layer(void) { + return tri_layer_upper_layer; +} + +uint8_t get_tri_layer_adjust_layer(void) { + return tri_layer_adjust_layer; +} diff --git a/quantum/tri_layer.h b/quantum/tri_layer.h new file mode 100644 index 00000000000..3341ebffb28 --- /dev/null +++ b/quantum/tri_layer.h @@ -0,0 +1,59 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#ifndef TRI_LAYER_LOWER_LAYER +# define TRI_LAYER_LOWER_LAYER 1 +#endif +#ifndef TRI_LAYER_UPPER_LAYER +# define TRI_LAYER_UPPER_LAYER 2 +#endif +#ifndef TRI_LAYER_ADJUST_LAYER +# define TRI_LAYER_ADJUST_LAYER 3 +#endif + +/** + * @brief Set the tri layer lower layer index + * + * @param layer + */ +void set_tri_layer_lower_layer(uint8_t layer); +/** + * @brief Set the tri layer upper layer index + * + * @param layer + */ +void set_tri_layer_upper_layer(uint8_t layer); +/** + * @brief Set the tri layer adjust layer index + * + * @param layer + */ +void set_tri_layer_adjust_layer(uint8_t layer); +/** + * @brief Set the tri layer indices + * + * @param lower + * @param upper + * @param adjust + */ +void set_tri_layer_layers(uint8_t lower, uint8_t upper, uint8_t adjust); +/** + * @brief Get the tri layer lower layer index + * + * @return uint8_t + */ +uint8_t get_tri_layer_lower_layer(void); +/** + * @brief Get the tri layer upper layer index + * + * @return uint8_t + */ +uint8_t get_tri_layer_upper_layer(void); +/** + * @brief Get the tri layer adjust layer index + * + * @return uint8_t + */ +uint8_t get_tri_layer_adjust_layer(void); diff --git a/tests/test_common/keycode_table.cpp b/tests/test_common/keycode_table.cpp index ab9c292e95d..337bc8fb5f5 100644 --- a/tests/test_common/keycode_table.cpp +++ b/tests/test_common/keycode_table.cpp @@ -659,5 +659,7 @@ std::map KEYCODE_ID_TABLE = { {QK_AUTOCORRECT_ON, "QK_AUTOCORRECT_ON"}, {QK_AUTOCORRECT_OFF, "QK_AUTOCORRECT_OFF"}, {QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"}, + {QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"}, + {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, {SAFE_RANGE, "SAFE_RANGE"}, }; diff --git a/tests/tri_layer/config.h b/tests/tri_layer/config.h new file mode 100644 index 00000000000..b68bf0c2d5a --- /dev/null +++ b/tests/tri_layer/config.h @@ -0,0 +1,6 @@ +// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "test_common.h" diff --git a/tests/tri_layer/test.mk b/tests/tri_layer/test.mk new file mode 100644 index 00000000000..50548c3e1c0 --- /dev/null +++ b/tests/tri_layer/test.mk @@ -0,0 +1,8 @@ +# Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +# SPDX-License-Identifier: GPL-2.0-or-later + +# -------------------------------------------------------------------------------- +# Keep this file, even if it is empty, as a marker that this folder contains tests +# -------------------------------------------------------------------------------- + +TRI_LAYER_ENABLE = yes diff --git a/tests/tri_layer/test_tri_layer.cpp b/tests/tri_layer/test_tri_layer.cpp new file mode 100644 index 00000000000..fffc124f4c6 --- /dev/null +++ b/tests/tri_layer/test_tri_layer.cpp @@ -0,0 +1,103 @@ +// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "test_common.hpp" + +using testing::_; +using testing::InSequence; + +class TriLayer : public TestFixture {}; + +TEST_F(TriLayer, TriLayerLowerTest) { + TestDriver driver; + KeymapKey lower_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_LOWER}; + + set_keymap({lower_layer_key, KeymapKey{1, 0, 0, KC_TRNS}}); + + /* Press Lower. */ + EXPECT_NO_REPORT(driver); + lower_layer_key.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + VERIFY_AND_CLEAR(driver); + + /* Release Lower. */ + EXPECT_NO_REPORT(driver); + lower_layer_key.release(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(TriLayer, TriLayerUpperTest) { + TestDriver driver; + KeymapKey upper_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_UPPER}; + + set_keymap({upper_layer_key, KeymapKey{2, 0, 0, KC_TRNS}}); + + /* Press Raise. */ + EXPECT_NO_REPORT(driver); + upper_layer_key.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + VERIFY_AND_CLEAR(driver); + + /* Release Raise. */ + EXPECT_NO_REPORT(driver); + upper_layer_key.release(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(TriLayer, TriLayerAdjustTest) { + TestDriver driver; + KeymapKey lower_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_LOWER}; + KeymapKey upper_layer_key = KeymapKey{0, 1, 0, QK_TRI_LAYER_UPPER}; + + set_keymap({ + upper_layer_key, + lower_layer_key, + KeymapKey{1, 0, 0, KC_TRNS}, + KeymapKey{1, 1, 0, KC_TRNS}, + KeymapKey{2, 0, 0, KC_TRNS}, + KeymapKey{2, 1, 0, KC_TRNS}, + KeymapKey{3, 0, 0, KC_TRNS}, + KeymapKey{3, 1, 0, KC_TRNS}, + }); + + /* Press Lower, then upper, and release upper and then lower. */ + EXPECT_NO_REPORT(driver); + lower_layer_key.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + + upper_layer_key.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_TRUE(layer_state_is(get_tri_layer_adjust_layer())); + + lower_layer_key.release(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + + upper_layer_key.release(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer())); + EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer())); + VERIFY_AND_CLEAR(driver); +}