Merge pull request #779 from keyboardio/layer/move-to-layer-key

layers: Add a MoveToLayer(n) key
pull/814/head
Jesse Vincent 5 years ago committed by GitHub
commit b71cfc99a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -20,7 +20,9 @@
#include "kaleidoscope_internal/deprecations.h" #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_0 0
#define KEYMAP_1 1 #define KEYMAP_1 1
@ -68,3 +70,10 @@ static const uint8_t LAYER_SHIFT_OFFSET = 42;
* value declared previously. * value declared previously.
*/ */
#define ShiftToLayer(n) Key(n + LAYER_SHIFT_OFFSET, KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP) #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)

@ -45,7 +45,11 @@ uint8_t Layer_::active_layers_[Runtime.device().numKeys()];
Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM; Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM;
void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { 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; uint8_t target = keymapEntry.getKeyCode() - LAYER_SHIFT_OFFSET;
switch (target) { switch (target) {
@ -146,8 +150,20 @@ void Layer_::updateTopActiveLayer(void) {
} }
void Layer_::move(uint8_t layer) { 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; layer_state_ = 0;
activate(layer);
if (layer >= layer_count) {
layer = 0;
}
bitSet(layer_state_, layer);
updateTopActiveLayer();
updateActiveLayers();
kaleidoscope::Hooks::onLayerChange();
} }
// Activate a given layer // Activate a given layer

Loading…
Cancel
Save