From 1c399ed11c16135b0732511bc8f6b51d8470eae4 Mon Sep 17 00:00:00 2001 From: Florian Fleissner Date: Sun, 3 Mar 2019 00:38:57 +0100 Subject: [PATCH] This PR enables logically grouping keys and apply layer switch and toggle operation to selected key groups only. Examples are provide that demonstrate grouping keys by keyboard hand sides, via condition expressions and with keymap-style color arrays. The basic idea that led to this PR was the question: How to switch one half of the keyboard to another layer while the other half should stay at the base layer. My intention was to switch via the thumb key of the Model01 one half of the keyboard to a modifier layer where all modifiers are supposed to reside on the home row. The other half of the keyboard should stay at the base layer. By means of that I intent to make typing modifier shortcuts with combined modifiers and keys more convenient. Of coures, this could already be done with the existing Kaleidoscope, e.g. by defining two individual additional layers whose keys for one half match those of the base layer and the other half the modifier layer. The redundancy is obvious. That's how the idea emerged to modity the layer class in a way that it stores not only one pair of `layer_state_` and `top_active_layer_` but several, each pair of those variables for a separate region of the keyboard. This makes it possible to make separate regions of the keyboard behave as individual keyboards in terms of their layer state. Lacking a better name, I called those keys assigned to such a region a "key group". Keys can be arbitrarily assigned to key groups. Layer switches/toggles can be defined to affect one or more key groups (see examples). By default only two key groups are defined. One for the left and one for the right hand side of the keyboard. * added header `src/Kaleidoscope-KeyGroups.h` * hardwares implement static constexpr bool isOnLeftHalf(uint8_t row, uint8_t col) convenience methods to test for the hand a key belongs to (one-handed keybords could just unconditionally return true) * added header `key_groups.h` * layers class tracks state of up to six key groups now * all public methods that affect the layer states now can be passed a key group flags to name the affected layers * method `extern uint8_t groupOfKey(uint8_t row, uint8_t col)` introduced that can be overridden from within the sketch * fixed Colormap plugin to work with multiple key groups * fixed plugin LED-ActiveLayerColor to work with multiple key groups * fixed plugin LED-ActiveModColor to work with multiple key groups * added TODO-comments in contexts (plugins EEPROM-KeymapOProgrmmer and NumPad) where layer-methods are used but no layer group information is available * added TODO-comments about the implementation of method `isOnLeftHalf` in some keyboards * examples were added to illustrate different use cases The PR will not break any user code or plugins. Only if keygroups are assigned to layer switch operations, the firmware behavior will differ from what it does currently. Changes will probably not noticeabley affect runtime performance. Stock firmware: Groth of program size from 25192 to 26992 bytes. Data usage grows from 1414 to 1459 bytes. Carefully check commit line changes containing TODOs and fix those. Especially concerning plugins EEPROM-KeymapOProgrmmer and NumPad. This PR comes with quite some changes and both program size and Data usage grow significantly. Nonetheless, this feature might be worth it. If Arduino eventually would introduce a global per-sketch configuration header (there's an ongoing discussion about this in the Arduino community) we could support several alternative implementations of the layer system, let the user select one and select a default one otherwise. As this is currently not possible, I chose to "upgrade" the existing layer class. Signed-off-by: noseglasses --- .../ArrayBasedMembership.ino | 113 ++++++++++++ .../ConditionalMembership.ino | 94 ++++++++++ .../Features/KeyGroups/Default/Default.ino | 88 +++++++++ src/Kaleidoscope-KeyGroups.h | 20 ++ src/kaleidoscope/Hardware.h | 7 + src/kaleidoscope/hardware/ez/ErgoDox.h | 4 + src/kaleidoscope/hardware/kbdfans/KBD4x.h | 3 + .../hardware/keyboardio/Model01.cpp | 1 - .../hardware/keyboardio/Model01.h | 8 + src/kaleidoscope/hardware/olkb/Planck.h | 3 + .../hardware/softhruf/Splitography.h | 3 + .../hardware/technomancy/Atreus.h | 3 + src/kaleidoscope/key_groups.h | 94 ++++++++++ src/kaleidoscope/layers.cpp | 173 ++++++++++++------ src/kaleidoscope/layers.h | 32 ++-- src/kaleidoscope/plugin/Colormap.cpp | 15 +- src/kaleidoscope/plugin/Colormap.h | 1 - .../plugin/EEPROM-Keymap-Programmer.cpp | 4 + .../plugin/LED-ActiveLayerColor.cpp | 21 ++- .../plugin/LED-ActiveLayerColor.h | 4 +- .../plugin/LED-ActiveModColor.cpp | 2 +- src/kaleidoscope/plugin/NumPad.cpp | 1 + 22 files changed, 611 insertions(+), 83 deletions(-) create mode 100644 examples/Features/KeyGroups/ArrayBasedMembership/ArrayBasedMembership.ino create mode 100644 examples/Features/KeyGroups/ConditionalMembership/ConditionalMembership.ino create mode 100644 examples/Features/KeyGroups/Default/Default.ino create mode 100644 src/Kaleidoscope-KeyGroups.h create mode 100644 src/kaleidoscope/key_groups.h diff --git a/examples/Features/KeyGroups/ArrayBasedMembership/ArrayBasedMembership.ino b/examples/Features/KeyGroups/ArrayBasedMembership/ArrayBasedMembership.ino new file mode 100644 index 0000000000..7682e462ca --- /dev/null +++ b/examples/Features/KeyGroups/ArrayBasedMembership/ArrayBasedMembership.ino @@ -0,0 +1,113 @@ +/* -*- mode: c++ -*- + * Basic -- A very basic Kaleidoscope example + * Copyright (C) 2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "Kaleidoscope.h" + +#include "Kaleidoscope-LED-ActiveLayerColor.h" + +// This example demonstrates how keys can be grouped by +// a decision function that relies on a keymap layer kind of array +// that stores the key group id of every individual key. +// +// Please note that the function groupOfKey(..) may only return values +// in the range [0;5]. + +enum { AMap, BMap }; + +/* *INDENT-OFF* */ +KEYMAPS( + [AMap] = KEYMAP_STACKED + ( + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_2, ShiftToLayer(BMap)), + + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_1, ShiftToLayer(BMap)) + ), + + [BMap] = KEYMAP_STACKED + ( + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___, + + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___ + ) +) +/* *INDENT-ON* */ + +KALEIDOSCOPE_INIT_PLUGINS( + LEDControl, + LEDActiveLayerColorEffect +) + +KEY_GROUP_IDS_STACKED( + 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, + 0, + + 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, + 0 +) + +uint8_t groupOfKey(uint8_t row, uint8_t col) { + return GROUP_OF_KEY(row, col); +} + +void setup() { + Kaleidoscope.setup(); + + static const cRGB layerColormap[] PROGMEM = { + CRGB(128, 0, 0), + CRGB(0, 128, 0) + }; + + LEDActiveLayerColorEffect.setColormap(layerColormap); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Features/KeyGroups/ConditionalMembership/ConditionalMembership.ino b/examples/Features/KeyGroups/ConditionalMembership/ConditionalMembership.ino new file mode 100644 index 0000000000..1a5df7f8ec --- /dev/null +++ b/examples/Features/KeyGroups/ConditionalMembership/ConditionalMembership.ino @@ -0,0 +1,94 @@ +/* -*- mode: c++ -*- + * Basic -- A very basic Kaleidoscope example + * Copyright (C) 2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "Kaleidoscope.h" + +#include "Kaleidoscope-LED-ActiveLayerColor.h" + +// This example demonstrates how keys can be grouped using conditionals +// in a decision function. +// +// Please note that the function groupOfKey(...) may only return values +// in the range [0;5]. + +enum { AMap, BMap }; + +/* *INDENT-OFF* */ +KEYMAPS( + [AMap] = KEYMAP_STACKED + ( + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_1, ShiftToLayer(BMap)), + + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_0 | KEY_GROUP_2, ShiftToLayer(BMap)) + ), + + [BMap] = KEYMAP_STACKED + ( + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___, + + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___ + ) +) +/* *INDENT-ON* */ + +KALEIDOSCOPE_INIT_PLUGINS( + LEDControl, + LEDActiveLayerColorEffect +) + +uint8_t groupOfKey(uint8_t row, uint8_t col) { + return row; +} + +void setup() { + Kaleidoscope.setup(); + + static const cRGB layerColormap[] PROGMEM = { + CRGB(128, 0, 0), + CRGB(0, 128, 0) + }; + + LEDActiveLayerColorEffect.setColormap(layerColormap); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Features/KeyGroups/Default/Default.ino b/examples/Features/KeyGroups/Default/Default.ino new file mode 100644 index 0000000000..57ba1238d7 --- /dev/null +++ b/examples/Features/KeyGroups/Default/Default.ino @@ -0,0 +1,88 @@ +/* -*- mode: c++ -*- + * Basic -- A very basic Kaleidoscope example + * Copyright (C) 2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "Kaleidoscope.h" + +#include "Kaleidoscope-LED-ActiveLayerColor.h" + +// This example demonstrates the default key grouping mechanism +// where the left hand is assigned to key group 0 (KEY_GROUP_LEFT_HAND), +// the right hand to key group 1 (KEY_GROUP_RIGHT_HAND). + +enum { AMap, BMap }; + +/* *INDENT-OFF* */ +KEYMAPS( + [AMap] = KEYMAP_STACKED + ( + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_RIGHT_HAND, ShiftToLayer(BMap)), + + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, Key_A, + + Key_A, Key_A, Key_A, Key_A, + KeyGroup(KEY_GROUP_LEFT_HAND, ShiftToLayer(BMap)) + ), + + [BMap] = KEYMAP_STACKED + ( + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___, + + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, Key_B, + + Key_B, Key_B, Key_B, Key_B, + ___ + ) +) +/* *INDENT-ON* */ + +KALEIDOSCOPE_INIT_PLUGINS( + LEDControl, + LEDActiveLayerColorEffect +) + +void setup() { + Kaleidoscope.setup(); + + static const cRGB layerColormap[] PROGMEM = { + CRGB(128, 0, 0), + CRGB(0, 128, 0) + }; + + LEDActiveLayerColorEffect.setColormap(layerColormap); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/src/Kaleidoscope-KeyGroups.h b/src/Kaleidoscope-KeyGroups.h new file mode 100644 index 0000000000..73497b7973 --- /dev/null +++ b/src/Kaleidoscope-KeyGroups.h @@ -0,0 +1,20 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-Hardware-EZ-ErgoDox -- ErgoDox hardware support for Kaleidoscope + * Copyright (C) 2018 Keyboard.io, Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "kaleidoscope/key_groups.h" diff --git a/src/kaleidoscope/Hardware.h b/src/kaleidoscope/Hardware.h index 35a303a4b7..f7bef6bd9d 100644 --- a/src/kaleidoscope/Hardware.h +++ b/src/kaleidoscope/Hardware.h @@ -50,11 +50,18 @@ typedef struct cRGB cRGB; namespace kaleidoscope { + /** Kaleidoscope Hardware base class. * Essential methods all hardware libraries must implement. */ class Hardware { public: + + // This class method can be overridden by hardware implementations. + constexpr static bool isOnLeftHalf(uint8_t /*row*/, uint8_t /*col*/) { + return true; + } + /** * @defgroup kaleidoscope_hardware_leds Kaleidoscope::Hardware/LEDs * @{ diff --git a/src/kaleidoscope/hardware/ez/ErgoDox.h b/src/kaleidoscope/hardware/ez/ErgoDox.h index 8724956067..ca17047e35 100644 --- a/src/kaleidoscope/hardware/ez/ErgoDox.h +++ b/src/kaleidoscope/hardware/ez/ErgoDox.h @@ -56,6 +56,10 @@ class ErgoDox : public kaleidoscope::Hardware { static constexpr byte matrix_rows = 14; static constexpr int8_t led_count = 0; + static constexpr bool isOnLeftHalf(uint8_t row, uint8_t /*col*/) { + return row < 7; + } + void scanMatrix(void); void readMatrix(void); void actOnMatrixScan(void); diff --git a/src/kaleidoscope/hardware/kbdfans/KBD4x.h b/src/kaleidoscope/hardware/kbdfans/KBD4x.h index 11951015d4..de914c42ab 100644 --- a/src/kaleidoscope/hardware/kbdfans/KBD4x.h +++ b/src/kaleidoscope/hardware/kbdfans/KBD4x.h @@ -67,6 +67,9 @@ class KBD4x: public kaleidoscope::hardware::ATMegaKeyboard { static constexpr int8_t led_count = 0; + // TODO: Implement + // static constexpr bool isOnLeftHalf(uint8_t row, uint8_t col); + void resetDevice(); }; diff --git a/src/kaleidoscope/hardware/keyboardio/Model01.cpp b/src/kaleidoscope/hardware/keyboardio/Model01.cpp index 0db6545843..7487eac396 100644 --- a/src/kaleidoscope/hardware/keyboardio/Model01.cpp +++ b/src/kaleidoscope/hardware/keyboardio/Model01.cpp @@ -244,7 +244,6 @@ void Model01::rebootBootloader() { // halves, with eight keys per logical row. constexpr byte HIGH_BIT = B10000000; -constexpr byte HAND_BIT = B00001000; constexpr byte ROW_BITS = B00110000; constexpr byte COL_BITS = B00000111; diff --git a/src/kaleidoscope/hardware/keyboardio/Model01.h b/src/kaleidoscope/hardware/keyboardio/Model01.h index 5457f87bd1..1494532f99 100644 --- a/src/kaleidoscope/hardware/keyboardio/Model01.h +++ b/src/kaleidoscope/hardware/keyboardio/Model01.h @@ -37,6 +37,14 @@ namespace keyboardio { class Model01 : public kaleidoscope::Hardware { public: + + static constexpr byte HAND_BIT = B00001000; + + static constexpr bool isOnLeftHalf(uint8_t /*row*/, uint8_t col) { + // If HAND_BIT is set, we are on the right hand side + return !(col & HAND_BIT); + } + Model01(void); static constexpr byte matrix_rows = 4; diff --git a/src/kaleidoscope/hardware/olkb/Planck.h b/src/kaleidoscope/hardware/olkb/Planck.h index e42eb15cd5..c331df9100 100644 --- a/src/kaleidoscope/hardware/olkb/Planck.h +++ b/src/kaleidoscope/hardware/olkb/Planck.h @@ -40,6 +40,9 @@ class Planck: public kaleidoscope::hardware::ATMegaKeyboard { COL_PIN_LIST({ PIN_F1, PIN_F0, PIN_B0, PIN_C7, PIN_F4, PIN_F5, PIN_F6, PIN_F7, PIN_D4, PIN_D6, PIN_B4, PIN_D7 }) ); + // TODO: Implement + // static constexpr bool isOnLeftHalf(uint8_t row, uint8_t col); + static constexpr int8_t led_count = 0; }; diff --git a/src/kaleidoscope/hardware/softhruf/Splitography.h b/src/kaleidoscope/hardware/softhruf/Splitography.h index 01d42b779e..98a11cbb9e 100644 --- a/src/kaleidoscope/hardware/softhruf/Splitography.h +++ b/src/kaleidoscope/hardware/softhruf/Splitography.h @@ -68,6 +68,9 @@ class Splitography: public kaleidoscope::hardware::ATMegaKeyboard { COL_PIN_LIST({ PIN_F0, PIN_F1, PIN_F4, PIN_F5, PIN_F6, PIN_F7, PIN_C7, PIN_C6, PIN_B6, PIN_B5, PIN_B4, PIN_D7 }) ); + // TODO: Implement + // static constexpr bool isOnLeftHalf(uint8_t row, uint8_t col); + static constexpr int8_t led_count = 0; }; diff --git a/src/kaleidoscope/hardware/technomancy/Atreus.h b/src/kaleidoscope/hardware/technomancy/Atreus.h index 00ce781b98..ac1c474d1e 100644 --- a/src/kaleidoscope/hardware/technomancy/Atreus.h +++ b/src/kaleidoscope/hardware/technomancy/Atreus.h @@ -67,6 +67,9 @@ class Atreus: public kaleidoscope::hardware::ATMegaKeyboard { void resetDevice(); + // TODO: Implement + // static constexpr bool isOnLeftHalf(uint8_t row, uint8_t col); + protected: }; diff --git a/src/kaleidoscope/key_groups.h b/src/kaleidoscope/key_groups.h new file mode 100644 index 0000000000..3bd490d137 --- /dev/null +++ b/src/kaleidoscope/key_groups.h @@ -0,0 +1,94 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "kaleidoscope/key_defs.h" + +static constexpr uint8_t encodeKeyGroupFlags(uint8_t key_group_flags) { + + // We use the unused bits in the key flags of layer change/toggle keycodes + // to code the key groups that are affected by a layer operation. + // Every bit thereby represents one keygroup. + // Unfortunately, the unused bits that are available dont form + // a contiguous part of the flags byte. + // + // The following bits of the key flags bitfield are already used: + // SYNTHETIC(B01000000) | SWITCH_TO_KEYMAP(B00000100) + // + // Thus, the following bits (those set to one) B10111011 can be used + // to code affected key groups. Every group is assigned one bit. + // Because of this, only a maximum of six key groups can be defined. + // If all flags are set to zero, this means the same as if all related + // flags are set to one. This is necessary to provide backwards compatibility + // for those layer toggle/switch commands that are not associated with + // key groups. Those commands then mean "all groups affected". + + return (key_group_flags & B00000011) + | ((key_group_flags & B00011100) << 1) + | ((key_group_flags & B00100000) << 2); +} + +static constexpr uint8_t decodeKeyGroupFlags(uint8_t coded_flags) { + + return (coded_flags & B00000011) + | ((coded_flags & B00111000) >> 1) + | ((coded_flags & B10000000) >> 2); +} + +inline static constexpr Key KeyGroup(uint8_t key_group_flags, Key k) { + return Key(k.keyCode, k.flags | encodeKeyGroupFlags(key_group_flags)); +} + +// Key group flags +static constexpr uint8_t KEY_GROUP_0 = B00000001; +static constexpr uint8_t KEY_GROUP_1 = B00000010; +static constexpr uint8_t KEY_GROUP_2 = B00000100; +static constexpr uint8_t KEY_GROUP_3 = B00001000; +static constexpr uint8_t KEY_GROUP_4 = B00010000; +static constexpr uint8_t KEY_GROUP_5 = B00100000; + +// The default configuration of key grouping is that group 0 is assigned +// to the left hand, group 1 to the right. We provide constants to be used in +// user sketches. +static constexpr uint8_t KEY_GROUP_LEFT_HAND = KEY_GROUP_0; +static constexpr uint8_t KEY_GROUP_RIGHT_HAND = KEY_GROUP_1; + +// This is also the default if no key group is assigned to a layer toggle +// keycode. +static constexpr uint8_t ALL_KEY_GROUPS = B00111111; + +namespace kaleidoscope { + +// The maximum number of possible key groups. This cannot be exceeded +// as there are not more than six unused bits in the key flags left to +// code the keygroup. +static constexpr uint8_t max_num_key_groups = 6; + +static constexpr bool isKeyGroupFlagSet(uint8_t key_group_flags, uint8_t flag_id) { + return key_group_flags & (B00000001 << flag_id); +} + +} // end kaleidoscope + +#define KEY_GROUP_IDS_STACKED(group_ids_per_key...) \ + const uint8_t key_groups[ROWS][COLS] PROGMEM = KEYMAP_STACKED(group_ids_per_key); + +#define KEY_GROUP_IDS(group_ids_per_key...) \ + const uint8_t key_groups[ROWS][COLS] PROGMEM = KEYMAP(group_ids_per_key); + +#define GROUP_OF_KEY(row, col) \ + pgm_read_byte(&(key_groups[row][col])) diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index e581703ec9..00f01210b3 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -27,29 +27,37 @@ uint8_t layer_count __attribute__((weak)) = MAX_LAYERS; namespace kaleidoscope { -uint32_t Layer_::layer_state_; -uint8_t Layer_::top_active_layer_; +uint32_t Layer_::layer_state_[kaleidoscope::max_num_key_groups] = { 0, 0, 0, 0, 0, 0 }; +uint8_t Layer_::top_active_layer_[kaleidoscope::max_num_key_groups] = { 0, 0, 0, 0, 0, 0 }; Key Layer_::live_composite_keymap_[ROWS][COLS]; uint8_t Layer_::active_layers_[ROWS][COLS]; Key(*Layer_::getKey)(uint8_t layer, byte row, byte col) = Layer.getKeyFromPROGMEM; void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { + + uint8_t key_group_flags = decodeKeyGroupFlags(keymapEntry.flags); + + if (key_group_flags == 0) { + key_group_flags = ALL_KEY_GROUPS; + } + if (keymapEntry.keyCode >= LAYER_SHIFT_OFFSET) { + uint8_t target = keymapEntry.keyCode - LAYER_SHIFT_OFFSET; switch (target) { case KEYMAP_NEXT: if (keyToggledOn(keyState)) - activateNext(); + activateNext(key_group_flags); else if (keyToggledOff(keyState)) - deactivateTop(); + deactivateTop(key_group_flags); break; case KEYMAP_PREVIOUS: if (keyToggledOn(keyState)) - deactivateTop(); + deactivateTop(key_group_flags); else if (keyToggledOff(keyState)) - activateNext(); + activateNext(key_group_flags); break; default: @@ -68,24 +76,24 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { * layer will toggle back on in the same cycle. */ if (keyIsPressed(keyState)) { - if (!Layer.isActive(target)) - activate(target); + if (!Layer.isActive(target, key_group_flags)) + activate(target, key_group_flags); } else if (keyToggledOff(keyState)) { - deactivate(target); + deactivate(target, key_group_flags); } break; } } else if (keyToggledOn(keyState)) { // switch keymap and stay there - if (Layer.isActive(keymapEntry.keyCode) && keymapEntry.keyCode) - deactivate(keymapEntry.keyCode); + if (Layer.isActive(keymapEntry.keyCode, key_group_flags) && keymapEntry.keyCode) + deactivate(keymapEntry.keyCode, key_group_flags); else - activate(keymapEntry.keyCode); + activate(keymapEntry.keyCode, key_group_flags); } } Key Layer_::eventHandler(Key mappedKey, byte row, byte col, uint8_t keyState) { - if (mappedKey.flags != (SYNTHETIC | SWITCH_TO_KEYMAP)) + if (!((mappedKey.flags & SYNTHETIC) && (mappedKey.flags & SWITCH_TO_KEYMAP))) return mappedKey; handleKeymapKeyswitchEvent(mappedKey, keyState); @@ -109,10 +117,13 @@ void Layer_::updateActiveLayers(void) { memset(active_layers_, 0, ROWS * COLS); for (byte row = 0; row < ROWS; row++) { for (byte col = 0; col < COLS; col++) { - int8_t layer = top_active_layer_; + + uint8_t key_group = groupOfKey(row, col); + + int8_t layer = top_active_layer_[key_group]; while (layer > 0) { - if (Layer.isActive(layer)) { + if (Layer.isActive(layer, key_group)) { Key mappedKey = (*getKey)(layer, row, col); if (mappedKey != Key_Transparent) { @@ -126,43 +137,67 @@ void Layer_::updateActiveLayers(void) { } } -void Layer_::updateTopActiveLayer(void) { +void Layer_::updateTopActiveLayer(uint8_t key_group) { + // If layer_count is set, start there, otherwise search from the // highest possible layer (MAX_LAYERS) for the top active layer for (byte i = (layer_count - 1); i > 0; i--) { - if (bitRead(layer_state_, i)) { - top_active_layer_ = i; + if (bitRead(layer_state_[key_group], i)) { + top_active_layer_[key_group] = i; return; } } // It's not possible to turn off the default layer (see // updateActiveLayers()), so if no other layers are active: - top_active_layer_ = 0; + top_active_layer_[key_group] = 0; } -void Layer_::move(uint8_t layer) { - layer_state_ = 0; - activate(layer); +void Layer_::move(uint8_t layer, uint8_t key_group_flags) { + + for (uint8_t key_group = 0; key_group < kaleidoscope::max_num_key_groups; ++key_group) { + + if (!isKeyGroupFlagSet(key_group_flags, key_group)) { + continue; + } + + layer_state_[key_group] = 0; + } + + activate(layer, key_group_flags); } // Activate a given layer -void Layer_::activate(uint8_t layer) { - // If we're trying to turn on a layer that doesn't exist, abort (but - // if the keymap wasn't defined using the KEYMAPS() macro, proceed anyway - if (layer >= layer_count) - return; +void Layer_::activate(uint8_t layer, uint8_t key_group_flags) { + + bool changes_applied = false; + + for (uint8_t key_group = 0; key_group < kaleidoscope::max_num_key_groups; ++key_group) { + + if (!isKeyGroupFlagSet(key_group_flags, key_group)) { + continue; + } + + // If we're trying to turn on a layer that doesn't exist, abort (but + // if the keymap wasn't defined using the KEYMAPS() macro, proceed anyway + if (layer >= layer_count) + return; + + // If the target layer was already on, return + if (isActive(layer, key_group_flags)) + continue; - // If the target layer was already on, return - if (isActive(layer)) - return; + changes_applied = true; - // Otherwise, turn on its bit in layer_state_ - bitSet(layer_state_, layer); + // Otherwise, turn on its bit in layer_state_ + bitSet(layer_state_[key_group], layer); - // If the target layer is above the previous highest active layer, - // update top_active_layer_ - if (layer > top_active_layer_) - updateTopActiveLayer(); + // If the target layer is above the previous highest active layer, + // update top_active_layer_ + if (layer > top_active_layer_[key_group]) + updateTopActiveLayer(key_group); + } + + if (!changes_applied) return; // Update the keymap cache (but not live_composite_keymap_; that gets // updated separately, when keys toggle on or off. See layers.h) @@ -172,18 +207,33 @@ void Layer_::activate(uint8_t layer) { } // Deactivate a given layer -void Layer_::deactivate(uint8_t layer) { - // If the target layer was already off, return - if (!bitRead(layer_state_, layer)) - return; - // Turn off its bit in layer_state_ - bitClear(layer_state_, layer); +void Layer_::deactivate(uint8_t layer, uint8_t key_group_flags) { + + bool changes_applied = false; + + for (uint8_t key_group = 0; key_group < kaleidoscope::max_num_key_groups; ++key_group) { + + if (!isKeyGroupFlagSet(key_group_flags, key_group)) { + continue; + } + + // If the target layer was already off, return + if (!bitRead(layer_state_[key_group], layer)) + continue; + + changes_applied = true; + + // Turn off its bit in layer_state_ + bitClear(layer_state_[key_group], layer); + + // If the target layer was the previous highest active layer, + // update top_active_layer_ + if (layer == top_active_layer_[key_group]) + updateTopActiveLayer(key_group); + } - // If the target layer was the previous highest active layer, - // update top_active_layer_ - if (layer == top_active_layer_) - updateTopActiveLayer(); + if (!changes_applied) return; // Update the keymap cache (but not live_composite_keymap_; that gets // updated separately, when keys toggle on or off. See layers.h) @@ -192,18 +242,37 @@ void Layer_::deactivate(uint8_t layer) { kaleidoscope::Hooks::onLayerChange(); } -boolean Layer_::isActive(uint8_t layer) { - return bitRead(layer_state_, layer); +boolean Layer_::isActive(uint8_t layer, uint8_t key_group) { + return bitRead(layer_state_[key_group], layer); } -void Layer_::activateNext(void) { - activate(top_active_layer_ + 1); +void Layer_::activateNext(uint8_t key_group_flags) { + + for (uint8_t key_group = 0; key_group < kaleidoscope::max_num_key_groups; ++key_group) { + + if (!isKeyGroupFlagSet(key_group_flags, key_group)) { + continue; + } + activate(top_active_layer_[key_group] + 1, key_group_flags); + } +} + +void Layer_::deactivateTop(uint8_t key_group_flags) { + + for (uint8_t key_group = 0; key_group < kaleidoscope::max_num_key_groups; ++key_group) { + + if (!isKeyGroupFlagSet(key_group_flags, key_group)) { + continue; + } + deactivate(top_active_layer_[key_group]); + } } -void Layer_::deactivateTop(void) { - deactivate(top_active_layer_); } +__attribute__((weak)) +uint8_t groupOfKey(uint8_t row, uint8_t col) { + return HARDWARE_IMPLEMENTATION::isOnLeftHalf(row, col) ? 0 : 1; } kaleidoscope::Layer_ Layer; diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index 587a5e5aa4..72ab68c69c 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -18,6 +18,7 @@ #include #include "kaleidoscope/key_defs.h" +#include "kaleidoscope/key_groups.h" #include KALEIDOSCOPE_HARDWARE_H extern const Key keymaps[][ROWS][COLS]; @@ -33,6 +34,7 @@ extern const Key keymaps[][ROWS][COLS]; extern uint8_t layer_count; namespace kaleidoscope { + class Layer_ { public: Layer_() {} @@ -77,19 +79,19 @@ class Layer_ { return active_layers_[row][col]; } - static void activate(uint8_t layer); - static void deactivate(uint8_t layer); - static void activateNext(); - static void deactivateTop(); - static void move(uint8_t layer); + static void activate(uint8_t layer, uint8_t key_group_flags = ALL_KEY_GROUPS); + static void deactivate(uint8_t layer, uint8_t key_group_flags = ALL_KEY_GROUPS); + static void activateNext(uint8_t key_group_flags = ALL_KEY_GROUPS); + static void deactivateTop(uint8_t key_group_flags = ALL_KEY_GROUPS); + static void move(uint8_t layer, uint8_t key_group_flags = ALL_KEY_GROUPS); - static uint8_t top(void) { - return top_active_layer_; + static uint8_t top(uint8_t key_group = 0) { + return top_active_layer_[key_group]; } - static boolean isActive(uint8_t layer); + static boolean isActive(uint8_t layer, uint8_t key_group = 0); - static uint32_t getLayerState(void) { - return layer_state_; + static uint32_t getLayerState(uint8_t key_group = 0) { + return layer_state_[key_group]; } static Key eventHandler(Key mappedKey, byte row, byte col, uint8_t keyState); @@ -102,14 +104,18 @@ class Layer_ { static void updateActiveLayers(void); private: - static uint32_t layer_state_; - static uint8_t top_active_layer_; + static uint32_t layer_state_[kaleidoscope::max_num_key_groups]; + static uint8_t top_active_layer_[kaleidoscope::max_num_key_groups]; static Key live_composite_keymap_[ROWS][COLS]; static uint8_t active_layers_[ROWS][COLS]; static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); - static void updateTopActiveLayer(void); + + static void updateTopActiveLayer(uint8_t key_group); }; + } +extern uint8_t groupOfKey(uint8_t row, uint8_t col); + extern kaleidoscope::Layer_ Layer; diff --git a/src/kaleidoscope/plugin/Colormap.cpp b/src/kaleidoscope/plugin/Colormap.cpp index ac7b3144b1..bb18670729 100644 --- a/src/kaleidoscope/plugin/Colormap.cpp +++ b/src/kaleidoscope/plugin/Colormap.cpp @@ -28,7 +28,6 @@ namespace plugin { uint16_t ColormapEffect::map_base_; uint8_t ColormapEffect::max_layers_; -uint8_t ColormapEffect::top_layer_; void ColormapEffect::max_layers(uint8_t max_) { if (map_base_ != 0) @@ -42,14 +41,18 @@ void ColormapEffect::onActivate(void) { if (!Kaleidoscope.has_leds) return; - top_layer_ = Layer.top(); - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.updateHandler(map_base_, top_layer_); + for (uint8_t row = 0; row < ROWS; ++row) { + for (uint8_t col = 0; col < COLS; ++col) { + refreshAt(row, col); + } + } } void ColormapEffect::refreshAt(byte row, byte col) { - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.refreshAt(map_base_, top_layer_, row, col); + uint8_t key_group = groupOfKey(row, col); + uint8_t top_layer = ::Layer.top(key_group); + if (top_layer <= max_layers_) + ::LEDPaletteTheme.refreshAt(map_base_, top_layer, row, col); } EventHandlerResult ColormapEffect::onLayerChange() { diff --git a/src/kaleidoscope/plugin/Colormap.h b/src/kaleidoscope/plugin/Colormap.h index 7837a8c354..6219b3bbc1 100644 --- a/src/kaleidoscope/plugin/Colormap.h +++ b/src/kaleidoscope/plugin/Colormap.h @@ -36,7 +36,6 @@ class ColormapEffect : public LEDMode { void refreshAt(byte row, byte col) final; private: - static uint8_t top_layer_; static uint8_t max_layers_; static uint16_t map_base_; }; diff --git a/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp b/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp index 956c6bb866..1c61f60a12 100644 --- a/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp +++ b/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp @@ -56,9 +56,11 @@ EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, byt if (state_ == WAIT_FOR_KEY) { if (keyToggledOn(key_state)) { + // TODO: Check use of Layer.top() in presence of multiple key groups update_position_ = Layer.top() * ROWS * COLS + row * COLS + col; } if (keyToggledOff(key_state)) { + // TODO: Check use of Layer.top() in presence of multiple key groups if ((uint16_t)(Layer.top() * ROWS * COLS + row * COLS + col) == update_position_) nextState(); } @@ -67,9 +69,11 @@ EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, byt if (state_ == WAIT_FOR_SOURCE_KEY) { if (keyToggledOn(key_state)) { + // TODO: Check use of Layer.top() in presence of multiple key groups new_key_ = Layer.getKeyFromPROGMEM(Layer.top(), row, col); } if (keyToggledOff(key_state)) { + // TODO: Check use of Layer.top() in presence of multiple key groups if (new_key_ == Layer.getKeyFromPROGMEM(Layer.top(), row, col)) nextState(); } diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp index ae7fd6185c..8136839d16 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp @@ -20,18 +20,16 @@ namespace kaleidoscope { namespace plugin { -cRGB LEDActiveLayerColorEffect::active_color_; +// cRGB LEDActiveLayerColorEffect::active_color_; const cRGB *LEDActiveLayerColorEffect::colormap_; void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) { colormap_ = colormap; } -cRGB LEDActiveLayerColorEffect::getActiveColor() { +cRGB LEDActiveLayerColorEffect::getActiveColor(uint8_t top_layer) { cRGB color; - uint8_t top_layer = ::Layer.top(); - color.r = pgm_read_byte(&(colormap_[top_layer].r)); color.g = pgm_read_byte(&(colormap_[top_layer].g)); color.b = pgm_read_byte(&(colormap_[top_layer].b)); @@ -42,13 +40,22 @@ cRGB LEDActiveLayerColorEffect::getActiveColor() { void LEDActiveLayerColorEffect::onActivate(void) { if (!Kaleidoscope.has_leds) return; + /* + active_color_ = getActiveColor(); + ::LEDControl.set_all_leds_to(active_color_);*/ - active_color_ = getActiveColor(); - ::LEDControl.set_all_leds_to(active_color_); + for (uint8_t row = 0; row < ROWS; ++row) { + for (uint8_t col = 0; col < COLS; ++col) { + refreshAt(row, col); + } + } } void LEDActiveLayerColorEffect::refreshAt(byte row, byte col) { - ::LEDControl.setCrgbAt(row, col, active_color_); + uint8_t key_group = groupOfKey(row, col); + uint8_t top_layer = ::Layer.top(key_group); + cRGB active_color = getActiveColor(top_layer); + ::LEDControl.setCrgbAt(row, col, active_color); } EventHandlerResult LEDActiveLayerColorEffect::onLayerChange() { diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h index 611b6b19f3..38f2738630 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h @@ -34,9 +34,9 @@ class LEDActiveLayerColorEffect : public LEDMode { private: static const cRGB *colormap_; - static cRGB active_color_; +// static cRGB active_color_; - static cRGB getActiveColor(); + static cRGB getActiveColor(uint8_t top_layer); }; } } diff --git a/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 8b49f20fa7..8b18b082ff 100644 --- a/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -80,7 +80,7 @@ EventHandlerResult ActiveModColorEffect::beforeReportingState() { if (layer >= LAYER_SHIFT_OFFSET) layer -= LAYER_SHIFT_OFFSET; - if (Layer.isActive(layer)) + if (Layer.isActive(layer, groupOfKey(r, c))) ::LEDControl.setCrgbAt(r, c, highlight_color); else ::LEDControl.refreshAt(r, c); diff --git a/src/kaleidoscope/plugin/NumPad.cpp b/src/kaleidoscope/plugin/NumPad.cpp index 64257af70f..d3a6fd6432 100644 --- a/src/kaleidoscope/plugin/NumPad.cpp +++ b/src/kaleidoscope/plugin/NumPad.cpp @@ -86,6 +86,7 @@ void NumPad::setKeyboardLEDColors(void) { } EventHandlerResult NumPad::afterEachCycle() { + // TODO: How could this work with more than one keygroup? if (!Layer.isActive(numPadLayer)) { cleanupNumlockState(); } else {