diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 70dce9cb..16d83adc 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -12,6 +12,7 @@ If any of this does not make sense to you, or you have trouble updating your .in - [Bidirectional communication for plugins](#bidirectional-communication-for-plugins) - [Consistent timing](#consistent-timing) + [Breaking changes](#breaking-changes) + - [Layer system switched to activation-order](#layer-system-switched-to-activation-order) - [Deprecation of the HID facade](#deprecation-of-the-hid-facade) - [Implementation of type Key internally changed from C++ union to class](#implementation-of-type-key-internally-changed-from-union-to-class) - [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state) @@ -314,6 +315,26 @@ As a developer, one can continue using `millis()`, but migrating to `Kaleidoscop ## Breaking changes +### Layer system switched to activation order + +The layer system used to be index-ordered, meaning that we'd look keys up on +layers based on the _index_ of active layers. Kaleidoscope now uses activation +order, which looks up keys based on the order of layer activation. + +This means that the following functions are deprecated, and will be removed by **2020-09-16**: + +- `Layer.top()`, which used to return the topmost layer index. Use + `Layer.mostRecent()` instead, which returns the most recently activated layer. + Until removed, the old function will return the most recent layer. +- `Layer.deactivateTop()`, which used to return the topmost layer index. Use + `Layer.deactivateMostRecent()` instead. The old function will deactivate the + most recent layer. +- `Layer.getLayerState()`, which used to return a bitmap of the active layers. + With activation-order, a simple bitmap is not enough. For now, we still return + the bitmap, but without the ordering, it is almost useless. Use + `Layer.forEachActiveLayer()` to walk the active layers in order (from least + recent to most). + ### Deprecation of the HID facade With the new Device APIs it became possible to replace the HID facade (the `kaleidoscope::hid` family of functions) with a driver. As such, the old APIs are deprecated, and will be removed by **2020-09-16**. Please use `Kaleidoscope.hid()` instead. diff --git a/docs/layers.md b/docs/layers.md index e724275f..3e1c4d9e 100644 --- a/docs/layers.md +++ b/docs/layers.md @@ -21,6 +21,10 @@ will be active, without any stacking. If you want the layer switch to be active only while the key is held, like in the case of modifiers, the `ShiftToLayer(n)` method does just that. +While switching layers this way is similar to how modifiers work, there are +subtle differences. For a longer explanation, see +[later](#layers-transparency-and-how-lookup-works). + ## Layer theory First of all, the most important thing to remember is that layers are like a @@ -111,36 +115,28 @@ They're like overrides. Any layer you place on top of the existing stack, will override keys in the layers below. When you have multiple layers active, to figure out what a key does, the -firmware will first look at the key position on the topmost active layer, and -see if there's a non-transparent key there. If there is, it will use that. If -there isn't, it will start walking backwards on the stack of _active_ layers to -find the highest one with a non-transparent key. The first one it finds is whose -key it will use. If it finds none, then a transparent key will act like a blank -one, and do nothing. +firmware will first look at the key position on the most recently activated +layer, and see if there's a non-transparent key there. If there is, it will use +that. If there isn't, it will start walking backwards on the stack of _active_ +layers to find the highest one with a non-transparent key. The first one it +finds is whose key it will use. If it finds none, then a transparent key will +act like a blank one, and do nothing. It is important to note that transparent keys are looked up from active layers -only, from highest to lowest. Lets consider that we have three layers, 0, 1, -and 2. On a given position, we have a non-transparent key on layers 0 and 1, but -the same position is transparent on layer 2. If we have layer 0 and 2 active, -the key will be looked up from layer 0, because layer 2 is transparent. If we -activate layer 1 too, it will be looked up from there, since layer 1 is higher -in the stack than layer 0. +only, from most recently activated to least. Lets consider that we have three +layers, 0, 1, and 2. On a given position, we have a non-transparent key on +layers 0 and 1, but the same position is transparent on layer 2. If we have +layer 0 and 2 active, the key will be looked up from layer 0, because layer 2 is +transparent. If we activate layer 1 too, it will be looked up from there, since +layer 1 is higher in the stack than layer 0. In this case, since we activated +layer 1 most recently, layer 2 wouldn't even be looked at. As we just saw, another important factor is that layers are ordered by their -index, not by the order of activation. Whether you activate layer 1 or 2 first -doesn't matter. What matters is their index. In other words, when you have layer -0 and 2 active, then activate layer 1, that is like placing the layer 1 foil -between 0 and 2. - -A side-effect of this is that while you can activate a lower-indexed layer from -a higher index, the higher indexed one will still override the lower-indexed -one. As such, as a general rule of thumb, you want to order your layers in such -a way that this doesn't become a problem. - -This can be a little confusing at first, but it is easy to get used to with some -practice. Once mastered, layers are an incredibly powerful feature. - -Nevertheless, we have the `MoveToLayer(n)` method, where only one layer is ever -active. This way, we do not need to care about transparency, and we can freely -move from a higher layer to a lower one, because the higher one will get -disabled, and will not be able to shadow the lower-indexed one. +order of activation. Whether you activate layer 1 or 2 first, matters. Lets look +at another example: we have three layers, 0, 1, and 2. On a given position, we +have a non-transparent key on every layer. If we have just layer 0 active, it +will be looked up from there. If we activate layer 2, then the firmware will +look there first. If we activate layer 1 as well, then - since now layer 1 is +the most recently activated layer - the firmware will look the code up from +layer 1, without looking at layer 2. It would only look at layer 2 if the key +was transparent on layer 1. diff --git a/examples/Features/Layers/Layers.ino b/examples/Features/Layers/Layers.ino new file mode 100644 index 00000000..a1f749c4 --- /dev/null +++ b/examples/Features/Layers/Layers.ino @@ -0,0 +1,104 @@ +/* -*- mode: c++ -*- + * Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 +#include +#include + +enum { PRIMARY, NUMPAD, FUNCTION }; // layers + +// *INDENT-OFF* +KEYMAPS( + [PRIMARY] = KEYMAP_STACKED + (___, Key_1, Key_2, Key_3, Key_4, Key_5, XXX, + Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, + Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, + Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, + Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, + ShiftToLayer(FUNCTION), + + XXX, Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), + Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, + Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, + Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, + ShiftToLayer(FUNCTION)), + + [NUMPAD] = KEYMAP_STACKED + (___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + XXX, ___, Key_7, Key_8, Key_9, Key_KeypadSubtract, ___, + ___, ___, Key_4, Key_5, Key_6, Key_KeypadAdd, ___, + ___, Key_1, Key_2, Key_3, Key_Equals, ___, + ___, ___, Key_0, Key_Period, Key_KeypadMultiply, Key_KeypadDivide, Key_Enter, + ___, ___, ___, ___, + ___), + + [FUNCTION] = KEYMAP_STACKED + (ShiftToLayer(NUMPAD), Key_F1, Key_F2, Key_F3, Key_F4, Key_F5, Key_CapsLock, + Key_Tab, ___, Key_mouseUp, ___, Key_mouseBtnR, Key_mouseWarpEnd, Key_mouseWarpNE, + Key_Home, Key_mouseL, Key_mouseDn, Key_mouseR, Key_mouseBtnL, Key_mouseWarpNW, + Key_End, Key_PrintScreen, Key_Insert, ___, Key_mouseBtnM, Key_mouseWarpSW, Key_mouseWarpSE, + ___, Key_Delete, ___, ___, + ___, + + Consumer_ScanPreviousTrack, Key_F6, Key_F7, Key_F8, Key_F9, Key_F10, Key_F11, + Consumer_PlaySlashPause, Consumer_ScanNextTrack, Key_LeftCurlyBracket, Key_RightCurlyBracket, Key_LeftBracket, Key_RightBracket, Key_F12, + Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___, + Key_PcApplication, Consumer_Mute, Consumer_VolumeDecrement, Consumer_VolumeIncrement, ___, Key_Backslash, Key_Pipe, + ___, ___, Key_Enter, ___, + ___) +) +// *INDENT-OFF* + +namespace kaleidoscope { +class LayerDumper: public Plugin { + public: + LayerDumper() {} + + static void dumpLayerState(uint8_t index, uint8_t layer) { + Serial.print(index); + Serial.print(" -> "); + Serial.println(layer); + } + + EventHandlerResult onLayerChange() { + Serial.println("Active Layers:"); + Layer.forEachActiveLayer(&dumpLayerState); + Serial.println(); + return EventHandlerResult::OK; + } +}; + +} + +kaleidoscope::LayerDumper LayerDumper; + +KALEIDOSCOPE_INIT_PLUGINS(Focus, LayerDumper, MouseKeys); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index a3442b03..af283773 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -39,9 +39,11 @@ extern constexpr Key keymaps_linear[][kaleidoscope_internal::device.matrix_rows namespace kaleidoscope { uint32_t Layer_::layer_state_; -uint8_t Layer_::top_active_layer_; +uint8_t Layer_::active_layer_count_ = 1; +int8_t Layer_::active_layers_[31]; + Key Layer_::live_composite_keymap_[Runtime.device().numKeys()]; -uint8_t Layer_::active_layers_[Runtime.device().numKeys()]; +uint8_t Layer_::active_layer_keymap_[Runtime.device().numKeys()]; Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM; void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { @@ -57,12 +59,12 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { if (keyToggledOn(keyState)) activateNext(); else if (keyToggledOff(keyState)) - deactivateTop(); + deactivateMostRecent(); break; case KEYMAP_PREVIOUS: if (keyToggledOn(keyState)) - deactivateTop(); + deactivateMostRecent(); else if (keyToggledOff(keyState)) activateNext(); break; @@ -112,43 +114,29 @@ Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) { } void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) { - int8_t layer = active_layers_[key_addr.toInt()]; + int8_t layer = active_layer_keymap_[key_addr.toInt()]; live_composite_keymap_[key_addr.toInt()] = (*getKey)(layer, key_addr); } void Layer_::updateActiveLayers(void) { - memset(active_layers_, 0, Runtime.device().numKeys()); + memset(active_layer_keymap_, 0, Runtime.device().numKeys()); for (auto key_addr : KeyAddr::all()) { - int8_t layer = top_active_layer_; - - while (layer > 0) { + int8_t layer_index = active_layer_count_; + while (layer_index > 0) { + uint8_t layer = active_layers_[layer_index - 1]; if (Layer.isActive(layer)) { Key mappedKey = (*getKey)(layer, key_addr); if (mappedKey != Key_Transparent) { - active_layers_[key_addr.toInt()] = layer; + active_layer_keymap_[key_addr.toInt()] = layer; break; } } - layer--; + layer_index--; } } } -void Layer_::updateTopActiveLayer(void) { - // 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; - return; - } - } - // It's not possible to turn off the default layer (see - // updateActiveLayers()), so if no other layers are active: - top_active_layer_ = 0; -} - void Layer_::move(uint8_t layer) { // We do pretty much what activate() does, except we do everything // unconditionally, to make sure all parts of the firmware are aware of the @@ -159,8 +147,9 @@ void Layer_::move(uint8_t layer) { layer = 0; } bitSet(layer_state_, layer); + active_layer_count_ = 1; + active_layers_[0] = layer; - updateTopActiveLayer(); updateActiveLayers(); kaleidoscope::Hooks::onLayerChange(); @@ -179,11 +168,7 @@ void Layer_::activate(uint8_t layer) { // Otherwise, turn on its bit in layer_state_ bitSet(layer_state_, layer); - - // If the target layer is above the previous highest active layer, - // update top_active_layer_ - if (layer > top_active_layer_) - updateTopActiveLayer(); + active_layers_[active_layer_count_++] = layer; // Update the keymap cache (but not live_composite_keymap_; that gets // updated separately, when keys toggle on or off. See layers.h) @@ -201,10 +186,16 @@ void Layer_::deactivate(uint8_t layer) { // Turn off its bit in layer_state_ bitClear(layer_state_, layer); - // If the target layer was the previous highest active layer, - // update top_active_layer_ - if (layer == top_active_layer_) - updateTopActiveLayer(); + // Rearrange the activation order array... + uint8_t idx = 0; + for (uint8_t i = active_layer_count_; i > 0; i--) { + if (active_layers_[i] == layer) { + idx = i; + break; + } + } + memmove(&active_layers_[idx], &active_layers_[idx + 1], active_layer_count_ - idx); + active_layer_count_--; // Update the keymap cache (but not live_composite_keymap_; that gets // updated separately, when keys toggle on or off. See layers.h) @@ -218,11 +209,17 @@ boolean Layer_::isActive(uint8_t layer) { } void Layer_::activateNext(void) { - activate(top_active_layer_ + 1); + activate(active_layers_[active_layer_count_ - 1] + 1); } -void Layer_::deactivateTop(void) { - deactivate(top_active_layer_); +void Layer_::deactivateMostRecent(void) { + deactivate(active_layers_[active_layer_count_ - 1]); +} + +void Layer_::forEachActiveLayer(forEachHandler h) { + for (uint8_t i = 0; i < active_layer_count_; i++) { + (*h)(i, active_layers_[i]); + } } } diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index 61b08ea6..22ceb9cb 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -23,6 +23,7 @@ #include "kaleidoscope_internal/device.h" #include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h" #include "kaleidoscope_internal/shortname.h" +#include "kaleidoscope_internal/deprecations.h" #define START_KEYMAPS __NL__ \ constexpr Key keymaps_linear[][kaleidoscope_internal::device.matrix_rows * kaleidoscope_internal::device.matrix_columns] PROGMEM = { @@ -83,25 +84,31 @@ class Layer_ { return live_composite_keymap_[key_addr.toInt()]; } static Key lookupOnActiveLayer(KeyAddr key_addr) { - uint8_t layer = active_layers_[key_addr.toInt()]; + uint8_t layer = active_layer_keymap_[key_addr.toInt()]; return (*getKey)(layer, key_addr); } static uint8_t lookupActiveLayer(KeyAddr key_addr) { - return active_layers_[key_addr.toInt()]; + return active_layer_keymap_[key_addr.toInt()]; } static void activate(uint8_t layer); static void deactivate(uint8_t layer); static void activateNext(); - static void deactivateTop(); + static void deactivateTop() DEPRECATED(LAYER_DEACTIVATETOP) { + deactivateMostRecent(); + } + static void deactivateMostRecent(); static void move(uint8_t layer); - static uint8_t top(void) { - return top_active_layer_; + static uint8_t top(void) DEPRECATED(LAYER_TOP) { + return mostRecent(); + } + static uint8_t mostRecent() { + return active_layers_[active_layer_count_ - 1]; } static boolean isActive(uint8_t layer); - static uint32_t getLayerState(void) { + static uint32_t getLayerState(void) DEPRECATED(LAYER_GETLAYERSTATE) { return layer_state_; } @@ -118,14 +125,20 @@ class Layer_ { static void updateLiveCompositeKeymap(KeyAddr key_addr); static void updateActiveLayers(void); + private: + using forEachHandler = void(*)(uint8_t index, uint8_t layer); + + public: + static void forEachActiveLayer(forEachHandler h); + private: static uint32_t layer_state_; - static uint8_t top_active_layer_; + static uint8_t active_layer_count_; + static int8_t active_layers_[31]; static Key live_composite_keymap_[kaleidoscope_internal::device.numKeys()]; - static uint8_t active_layers_[kaleidoscope_internal::device.numKeys()]; + static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); - static void updateTopActiveLayer(void); }; } diff --git a/src/kaleidoscope/plugin/Colormap.cpp b/src/kaleidoscope/plugin/Colormap.cpp index b3e87ab3..157a0d02 100644 --- a/src/kaleidoscope/plugin/Colormap.cpp +++ b/src/kaleidoscope/plugin/Colormap.cpp @@ -40,7 +40,7 @@ void ColormapEffect::TransientLEDMode::onActivate(void) { if (!Runtime.has_leds) return; - parent_->top_layer_ = Layer.top(); + parent_->top_layer_ = Layer.mostRecent(); if (parent_->top_layer_ <= parent_->max_layers_) ::LEDPaletteTheme.updateHandler(parent_->map_base_, parent_->top_layer_); } diff --git a/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp b/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp index 3749a239..07fa56e6 100644 --- a/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp +++ b/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp @@ -59,10 +59,10 @@ EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, Key if (state_ == WAIT_FOR_KEY) { if (keyToggledOn(key_state)) { - update_position_ = Layer.top() * Runtime.device().numKeys() + key_addr.toInt(); + update_position_ = Layer.mostRecent() * Runtime.device().numKeys() + key_addr.toInt(); } if (keyToggledOff(key_state)) { - if ((uint16_t)(Layer.top() * Runtime.device().numKeys() + key_addr.toInt()) == update_position_) + if ((uint16_t)(Layer.mostRecent() * Runtime.device().numKeys() + key_addr.toInt()) == update_position_) nextState(); } return EventHandlerResult::EVENT_CONSUMED; @@ -70,10 +70,10 @@ EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, Key if (state_ == WAIT_FOR_SOURCE_KEY) { if (keyToggledOn(key_state)) { - new_key_ = Layer.getKeyFromPROGMEM(Layer.top(), key_addr); + new_key_ = Layer.getKeyFromPROGMEM(Layer.mostRecent(), key_addr); } if (keyToggledOff(key_state)) { - if (new_key_ == Layer.getKeyFromPROGMEM(Layer.top(), key_addr)) + if (new_key_ == Layer.getKeyFromPROGMEM(Layer.mostRecent(), key_addr)) nextState(); } return EventHandlerResult::EVENT_CONSUMED; diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp index 0cd7c081..b63cd0d6 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp @@ -36,7 +36,7 @@ void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) { cRGB LEDActiveLayerColorEffect::TransientLEDMode::getActiveColor() { cRGB color; - uint8_t top_layer = ::Layer.top(); + uint8_t top_layer = ::Layer.mostRecent(); color.r = pgm_read_byte(&(parent_->colormap_[top_layer].r)); color.g = pgm_read_byte(&(parent_->colormap_[top_layer].g)); diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index 9279c892..fdf87b95 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -33,6 +33,19 @@ "For further information and examples on how to do that, \n" __NL__ \ "please see UPGRADING.md" +#define _DEPRECATED_MESSAGE_LAYER_DEACTIVATETOP __NL__ \ + "`Layer.deactivateTop()` is deprecated.\n" __NL__ \ + "Please use `Layer.deactivateMostRecent()` instead." + +#define _DEPRECATED_MESSAGE_LAYER_TOP __NL__ \ + "`Layer.top()` is deprecated.\n" __NL__ \ + "Please use `Layer.mostRecent()` instead." + +#define _DEPRECATED_MESSAGE_LAYER_GETLAYERSTATE __NL__ \ + "`Layer.getLayerState()` is deprecated.\n" __NL__ \ + "Layers are now in activation-order, please use" __NL__ \ + "`Layer.forEachActiveLayer()` instead." + #define _DEPRECATED_MESSAGE_HID_FACADE __NL__ \ "The HID facade in the `kaleidoscope::hid` namespace is deprecated.\n" __NL__ \ "Please use `Kaleidoscope.hid()` instead."