From 5fcdd720cbd7f9eaf26e2d1ace0f44da21afe138 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 24 Jan 2020 17:01:05 +0100 Subject: [PATCH 1/3] Layer.move: Forcibly update all our caches `Layer.move()` relied on `activate()` to update the caches, but `activate()` worked under the assumption that we also `deactivate()` layers - which we did not in `move()`, as we directly modified the state. Since we directly modify the state, we can't rely on `activate()` either, and have to update the caches ourselves. Unlike `activate()`, we do this unconditionally, because there isn't a case where we do not want to update them. This makes `MoveToLayer()` play well with `Colormap`, and any other plugin that rely on cached layer information, such as the topmost active layer. Signed-off-by: Gergely Nagy --- src/kaleidoscope/layers.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index a9e0e153..1010c26a 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -146,8 +146,20 @@ void Layer_::updateTopActiveLayer(void) { } 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 + // layer change. layer_state_ = 0; - activate(layer); + + if (layer >= layer_count) { + layer = 0; + } + bitSet(layer_state_, layer); + + updateTopActiveLayer(); + updateActiveLayers(); + + kaleidoscope::Hooks::onLayerChange(); } // Activate a given layer From 2b0e90c5924838d94249d3f220c70ed40c8a4057 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 31 Dec 2019 13:24:40 +0100 Subject: [PATCH 2/3] layers: Add a MoveToLayer(n) key While `ShiftToLayer` and `LockLayer` activate the given layer, and keep all others active as well, `MoveToLayer` activates the given layer, and deactivates all others. This allows one to have discrete layers that stand on their own, without any other layers interfering. Basically, this is a different way to work with layers, a less powerful, but also simpler one. Fixes #564. Signed-off-by: Gergely Nagy --- src/kaleidoscope/key_defs_keymaps.h | 11 ++++++++++- src/kaleidoscope/layers.cpp | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/kaleidoscope/key_defs_keymaps.h b/src/kaleidoscope/key_defs_keymaps.h index aa341e15..21698eab 100644 --- a/src/kaleidoscope/key_defs_keymaps.h +++ b/src/kaleidoscope/key_defs_keymaps.h @@ -20,7 +20,9 @@ #include "kaleidoscope_internal/deprecations.h" -static const uint8_t LAYER_SHIFT_OFFSET = 42; +static const uint8_t LAYER_OP_OFFSET = 42; +static const uint8_t LAYER_SHIFT_OFFSET = LAYER_OP_OFFSET; +static const uint8_t LAYER_MOVE_OFFSET = LAYER_SHIFT_OFFSET + LAYER_OP_OFFSET;; #define KEYMAP_0 0 #define KEYMAP_1 1 @@ -68,3 +70,10 @@ static const uint8_t LAYER_SHIFT_OFFSET = 42; * value declared previously. */ #define ShiftToLayer(n) Key(n + LAYER_SHIFT_OFFSET, KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP) + +/** Move to layer `n`. + * + * Moving to a layer disables all other layers. Unlike locking and shifting to, + * this is a one-way operation. + */ +#define MoveToLayer(n) Key(n + LAYER_MOVE_OFFSET, KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP) diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index 1010c26a..a3442b03 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -45,7 +45,11 @@ uint8_t Layer_::active_layers_[Runtime.device().numKeys()]; Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM; void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { - if (keymapEntry.getKeyCode() >= LAYER_SHIFT_OFFSET) { + if (keymapEntry.getKeyCode() >= LAYER_MOVE_OFFSET) { + if (keyToggledOn(keyState)) { + move(keymapEntry.getKeyCode() - LAYER_MOVE_OFFSET); + } + } else if (keymapEntry.getKeyCode() >= LAYER_SHIFT_OFFSET) { uint8_t target = keymapEntry.getKeyCode() - LAYER_SHIFT_OFFSET; switch (target) { From 71522290ecec66e5a7be8ba8278ad31022850333 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Wed, 1 Jan 2020 12:54:28 +0100 Subject: [PATCH 3/3] doc: Add a bit of documentation about layers, and layer keys Signed-off-by: Gergely Nagy --- docs/layers.md | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/layers.md diff --git a/docs/layers.md b/docs/layers.md new file mode 100644 index 00000000..e724275f --- /dev/null +++ b/docs/layers.md @@ -0,0 +1,146 @@ +# Layers + +Layers are an integral part of Kaleidoscope, but a part that is perhaps harder +to master than many other things in the firmware. On these pages, we'll make an +attempt at explaining layers, what you can do with them, how, and a few common +use-cases. + +We'll start with a quick use-case guide, before diving deeper into explanations! + +## How do I...? + +### How do I switch to a layer, so I can type multiple keys from there? + +You can use `LockLayer(n)` or `MoveToLayer(n)`, depending on whether you want +other layers to be active at the same time or not. `LockLayer(n)` allows you to +build up a stack of layers, while with `MoveToLayer(n)` only the selected layer +will be active, without any stacking. + +### How do I do make layer switching act similar to modifiers? + +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. + +## Layer theory + +First of all, the most important thing to remember is that layers are like a +piece of foil, you can place many of them on top of each other, and see through +uncovered parts. In other words, you can have multiple layers all active at the +same time! As we'll see a few paragraphs later, this can be a very powerful +thing. + +To better explain how this works in practice, lets look at what layer-related +keys we can place on the keymap first. Armed with that knowledge, we'll then +explore a few use-cases. + +## Layer keys + +- `LockLayer(n)`: Locking a layer will activate it when the key toggles on, and + the layer will remain active until unlocked (with `UnlockLayer(n)`), even if + we release the layer key meanwhile. Think of it like a `Caps lock` or `Num + lock` key. +- `ShiftToLayer(n)`: Unlike `LockLayer`, this only activates the layer until the + key is held. Once the key is released, the layer deactivates. This behaviour + is very similar to that of modifiers. +- `MoveToLayer(n)`: Moving to a layer is very similar to locking it, the only + exception is that moving disables all other layers, so only the moved to layer + will be active. This allows us to have a less powerful, but simpler way of + dealing with layers, as we'll see below. +- `Key_KeymapNext_Momentary` / `Key_KeymapPrevious_Momentary`: These activate + the next or the previous layer, momentarily, like `ShiftToLayer(n)`. What it + considers `next`, is one layer higher than the currently highest active layer. + Similarly, `previous` is one below the currently highest active layer. + +## Use cases + +### Locked layers + +Locked layers are most useful when you'll want to spend more time on the target +layer. One such case is the numpad: when using it, we usually want to enter +longer numbers, or use the mathematical operator keys as well. Just imagine +hitting a layer lock key, and the right half of your keyboard turning into a +numpad! It's closer than the numpad on traditional full-size keyboards, thus +less hand movement is required! + +### Shifted layers + +There are many great examples for shifted layers, such as a symbols layer. Lets +say we have a number of often used symbols which we want easy access to, +preferably near the home row. For example, the various parentheses, brackets and +the like are often used in programming. Having them on the home row is +incredibly convenient. In most cases, we only need this layer for a very short +time, for a symbol or two. As such, locking the layer would be +counter-productive. Instead, we use a layer shift key, like if it was a +modifier. + +As a concrete example, lets imagine a small, ortholinear keyboard, like the +Planck. On the bottom row, on the right side of the space bar, we'd have a layer +shift key (lets call that `Fn` for now), that takes us to the symbol layer. On +the symbol layer, we'd have `{`, `}`, `[`, `]`, `(`, and `)` on the home row. To +input `{`, we'd press `Fn + d`, for example. This is still two presses, very +much like `Shift + [`, but the keys are more convenient, because we use stronger +fingers to press them. + +Another - and perhaps an even better - example would be a navigation layer, with +cursor keys laid over `WASD`. The reason why this would be a better example, is +because in this case, we often want to use modifiers along with the cursor keys, +such as `Shift` or `Control`. With a shifted layer, if we have transparent keys +at positions where the modifiers are on the base layer, we don't have to repeat +the modifier layout on the shifted layer! This makes it easier to experiment +with one's layout, because if we move modifiers, we only have to do that on one +layer. + +### Moving to layers + +Moving to a layer is very similar to locking one. The only difference is that +moving disables all other layers. This in turn, has a few consequences: to go +back to wherever we came from, we can't use `UnlockLayer(n)`, because no other +layers are active. We explicitly have to use _another_ `MoveToLayer(n)` key if +we want to move elsewhere. + +The major advantage of moving to a layer - as opposed to locking one - is the +cognitive load. With moving, there is no transparency. There is only one layer +active at any given time. It's a simpler concept to grasp. + +## Layers, transparency, and how lookup works + +The thing that confuses many people about layers is that they can have +transparency. What even is a transparent key? Remember the first paragraphs: +layers are like a foil. They're see-through, unless parts of it are obstructed. +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. + +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. + +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.