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