[Core] Tri Layer Keys (#19795)

Co-authored-by: wilba <wilba@wilba.tech>
Co-authored-by: Pablo Martínez <58857054+elpekenin@users.noreply.github.com>
Co-authored-by: Joel Challis <git@zvecr.com>
Co-authored-by: Nick Brassel <nick@tzarc.org>
This commit is contained in:
Drashna Jaelre 2023-02-11 15:23:07 -08:00 committed by GitHub
parent 4002843797
commit fe02abc479
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 377 additions and 15 deletions

View file

@ -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

View file

@ -39,6 +39,7 @@ GENERIC_FEATURES = \
VELOCIKEY \
WPM \
DYNAMIC_TAPPING_TERM \
TRI_LAYER
define HANDLE_GENERIC_FEATURE
# $$(info "Processing: $1_ENABLE $2.c")

View file

@ -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)"

View file

@ -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"
]
}
}
}

View file

@ -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)

48
docs/feature_tri_layer.md Normal file
View file

@ -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.

View file

@ -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));
}

View file

@ -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 */

View file

@ -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)

View file

@ -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;
}

View file

@ -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);

View file

@ -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

View file

@ -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);

39
quantum/tri_layer.c Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2023 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
#include "tri_layer.h"
#include <stdint.h>
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;
}

59
quantum/tri_layer.h Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2023 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdint.h>
#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);

View file

@ -659,5 +659,7 @@ std::map<uint16_t, std::string> 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"},
};

6
tests/tri_layer/config.h Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "test_common.h"

8
tests/tri_layer/test.mk Normal file
View file

@ -0,0 +1,8 @@
# Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
# 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

View file

@ -0,0 +1,103 @@
// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// 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);
}