--------- Co-authored-by: xs5871 <60395129+xs5871@users.noreply.github.com>
This commit is contained in:
parent
adff02e88a
commit
878fe0deca
98
docs/en/combo_layers.md
Normal file
98
docs/en/combo_layers.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
## Combo Layers
|
||||||
|
|
||||||
|
Combo Layers is when you hold down 2 or more KC.MO() or KC.LM() keys at a time, and it goes to a defined layer.
|
||||||
|
|
||||||
|
By default combo layers is not activated. You can activate combo layers by adding this to your `main.py` file.
|
||||||
|
The combolayers NEEDS to be above the `keyboard.modules.append(Layers(combolayers))`
|
||||||
|
|
||||||
|
```python
|
||||||
|
combo_layers = {
|
||||||
|
(1, 2): 3,
|
||||||
|
}
|
||||||
|
keyboard.modules.append(Layers(combo_layers))
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above code, when layer 1 and 2 are held, layer 3 will activate. If you release 1 or 2 it will go to whatever key is still being held, if both are released it goes to the default (0) layer.
|
||||||
|
You should also notice that if you already have the layers Module activated, you can just add combolayers into `(Layers())`
|
||||||
|
|
||||||
|
You can add more, and even add more than 2 layers at a time.
|
||||||
|
|
||||||
|
```python
|
||||||
|
combo_layers = {
|
||||||
|
(1, 2): 3,
|
||||||
|
(1, 2, 3): 4,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
There can only be one combo layer active at a time and for overlapping matches
|
||||||
|
the first matching combo in `combo_layers` takes precedence.
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
layers = Layers()
|
||||||
|
layers.combo_layers = {
|
||||||
|
(1, 2, 3): 8,
|
||||||
|
(1, 2): 9,
|
||||||
|
}
|
||||||
|
keyboard.modules.append(Layers(combo_layers))
|
||||||
|
```
|
||||||
|
* If you activate layers 1 then 2, your active layer will be layer number 9.
|
||||||
|
* If you activate layers 1 then 2, then 3, your active layer will be layer
|
||||||
|
number 3 (because the layer combo `(1,2)` has been activated, but layer 3
|
||||||
|
stacks on top).
|
||||||
|
* deactivate 1: you're on layer 3
|
||||||
|
* deactivate 2: you're on layer 3
|
||||||
|
* deactivate 3: you're on layer 8
|
||||||
|
* If you activate layers 3 then 1, then 2, your active layer will be layer
|
||||||
|
number 8. Deativate layer
|
||||||
|
* deactivate any of 1/2/3: you're on layer 0
|
||||||
|
|
||||||
|
|
||||||
|
## Fully Working Example code
|
||||||
|
|
||||||
|
Below is an example of a fully working keypad that uses combo layers.
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("Starting")
|
||||||
|
|
||||||
|
import board
|
||||||
|
|
||||||
|
from kmk.kmk_keyboard import KMKKeyboard
|
||||||
|
from kmk.keys import KC
|
||||||
|
|
||||||
|
combo_layers = {
|
||||||
|
(1, 2): 3,
|
||||||
|
keyboard.modules.append(Layers(combo_layers))
|
||||||
|
|
||||||
|
|
||||||
|
keyboard = KMKKeyboard()
|
||||||
|
|
||||||
|
|
||||||
|
keyboard.keymap = [
|
||||||
|
[ #Default
|
||||||
|
KC.A, KC.B KC.C KC.D,
|
||||||
|
KC.E, KC.F KC.G KC.H,
|
||||||
|
KC.MO(1), KC.J, KC.K, KC.MO(2),
|
||||||
|
],
|
||||||
|
[ #Layer 1
|
||||||
|
KC.N1, KC.N2, KC.N3, KC.N4,
|
||||||
|
KC.N5, KC.N6, KC.N7, KC.8,
|
||||||
|
KC.MO(1), KC.N9, KC.N0, KC.MO(2),
|
||||||
|
],
|
||||||
|
[ #Layer 2
|
||||||
|
KC.EXLM, KC.AT, KC.HASH, KC.DLR,
|
||||||
|
KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR,
|
||||||
|
KC.MO(1), KC.LPRN, KC.RPRN, KC.MO(2),
|
||||||
|
],
|
||||||
|
[ #Layer 3
|
||||||
|
KC.F1, KC.F2, KC.F3, KC.F4,
|
||||||
|
KC.F5, KC.F6, KC.F7, KC.F8,
|
||||||
|
KC.MO(1) KC.F9, KC.F10, KC.MO(2)
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
keyboard.go()
|
||||||
|
```
|
@ -33,6 +33,11 @@ Some helpful guidelines to keep in mind as you design your layers:
|
|||||||
- Only reference higher-numbered layers from a given layer
|
- Only reference higher-numbered layers from a given layer
|
||||||
- Leave keys as `KC.TRNS` in higher layers when they would overlap with a layer-switch
|
- Leave keys as `KC.TRNS` in higher layers when they would overlap with a layer-switch
|
||||||
|
|
||||||
|
## Using Combo Layers
|
||||||
|
Combo Layers allow you to activate a corresponding layer based on the activation of 2 or more other layers.
|
||||||
|
The advantage of using Combo layers is that when you release one of the layer keys, it stays on whatever layer is still being held.
|
||||||
|
See [combo layers documentation](combolayers.md) for more information on it's function and to see examples.
|
||||||
|
|
||||||
### Using Multiple Base Layers
|
### Using Multiple Base Layers
|
||||||
In some cases, you may want to have more than one base layer (for instance you want to use
|
In some cases, you may want to have more than one base layer (for instance you want to use
|
||||||
both QWERTY and Dvorak layouts, or you have a custom gamepad that can switch between
|
both QWERTY and Dvorak layouts, or you have a custom gamepad that can switch between
|
||||||
@ -40,6 +45,7 @@ different games). In this case, best practice is to have these layers be the low
|
|||||||
defined first in your keymap. These layers are mutually-exclusive, so treat changing default
|
defined first in your keymap. These layers are mutually-exclusive, so treat changing default
|
||||||
layers with `KC.DF()` the same way that you would treat using `KC.TO()`
|
layers with `KC.DF()` the same way that you would treat using `KC.TO()`
|
||||||
|
|
||||||
|
|
||||||
## Example Code
|
## Example Code
|
||||||
For our example, let's take a simple 3x3 macropad with two layers as follows:
|
For our example, let's take a simple 3x3 macropad with two layers as follows:
|
||||||
|
|
||||||
|
@ -36,9 +36,15 @@ class LayerKeyMeta:
|
|||||||
class Layers(HoldTap):
|
class Layers(HoldTap):
|
||||||
'''Gives access to the keys used to enable the layer system'''
|
'''Gives access to the keys used to enable the layer system'''
|
||||||
|
|
||||||
def __init__(self):
|
_active_combo = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
combo_layers=None,
|
||||||
|
):
|
||||||
# Layers
|
# Layers
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.combo_layers = combo_layers
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator,
|
||||||
names=('MO',),
|
names=('MO',),
|
||||||
@ -46,9 +52,7 @@ class Layers(HoldTap):
|
|||||||
on_release=self._mo_released,
|
on_release=self._mo_released,
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('DF',), on_press=self._df_pressed
|
||||||
names=('DF',),
|
|
||||||
on_press=self._df_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator,
|
||||||
@ -57,14 +61,10 @@ class Layers(HoldTap):
|
|||||||
on_release=self._lm_released,
|
on_release=self._lm_released,
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('TG',), on_press=self._tg_pressed
|
||||||
names=('TG',),
|
|
||||||
on_press=self._tg_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed
|
||||||
names=('TO',),
|
|
||||||
on_press=self._to_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator_lt,
|
validator=layer_key_validator_lt,
|
||||||
@ -83,67 +83,102 @@ class Layers(HoldTap):
|
|||||||
'''
|
'''
|
||||||
Switches the default layer
|
Switches the default layer
|
||||||
'''
|
'''
|
||||||
keyboard.active_layers[-1] = key.meta.layer
|
self.activate_layer(keyboard, key.meta.layer, as_default=True)
|
||||||
self._print_debug(keyboard)
|
|
||||||
|
|
||||||
def _mo_pressed(self, key, keyboard, *args, **kwargs):
|
def _mo_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Momentarily activates layer, switches off when you let go
|
Momentarily activates layer, switches off when you let go
|
||||||
'''
|
'''
|
||||||
keyboard.active_layers.insert(0, key.meta.layer)
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
self._print_debug(keyboard)
|
|
||||||
|
|
||||||
@staticmethod
|
def _mo_released(self, key, keyboard, *args, **kwargs):
|
||||||
def _mo_released(key, keyboard, *args, **kwargs):
|
self.deactivate_layer(keyboard, key.meta.layer)
|
||||||
# remove the first instance of the target layer
|
|
||||||
# from the active list
|
|
||||||
# under almost all normal use cases, this will
|
|
||||||
# disable the layer (but preserve it if it was triggered
|
|
||||||
# as a default layer, etc.)
|
|
||||||
# this also resolves an issue where using DF() on a layer
|
|
||||||
# triggered by MO() and then defaulting to the MO()'s layer
|
|
||||||
# would result in no layers active
|
|
||||||
try:
|
|
||||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
|
||||||
del keyboard.active_layers[del_idx]
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
__class__._print_debug(__class__, keyboard)
|
|
||||||
|
|
||||||
def _lm_pressed(self, key, keyboard, *args, **kwargs):
|
def _lm_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
As MO(layer) but with mod active
|
As MO(layer) but with mod active
|
||||||
'''
|
'''
|
||||||
# Sets the timer start and acts like MO otherwise
|
keyboard.hid_pending = True
|
||||||
keyboard.add_key(key.meta.kc)
|
keyboard.keys_pressed.add(key.meta.kc)
|
||||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
|
|
||||||
def _lm_released(self, key, keyboard, *args, **kwargs):
|
def _lm_released(self, key, keyboard, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
As MO(layer) but with mod active
|
As MO(layer) but with mod active
|
||||||
'''
|
'''
|
||||||
keyboard.remove_key(key.meta.kc)
|
keyboard.hid_pending = True
|
||||||
self._mo_released(key, keyboard, *args, **kwargs)
|
keyboard.keys_pressed.discard(key.meta.kc)
|
||||||
|
self.deactivate_layer(keyboard, key.meta.layer)
|
||||||
|
|
||||||
def _tg_pressed(self, key, keyboard, *args, **kwargs):
|
def _tg_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Toggles the layer (enables it if not active, and vise versa)
|
Toggles the layer (enables it if not active, and vise versa)
|
||||||
'''
|
'''
|
||||||
# See mo_released for implementation details around this
|
# See mo_released for implementation details around this
|
||||||
try:
|
if key.meta.layer in keyboard.active_layers:
|
||||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
self.deactivate_layer(keyboard, key.meta.layer)
|
||||||
del keyboard.active_layers[del_idx]
|
else:
|
||||||
except ValueError:
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
keyboard.active_layers.insert(0, key.meta.layer)
|
|
||||||
|
|
||||||
def _to_pressed(self, key, keyboard, *args, **kwargs):
|
def _to_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Activates layer and deactivates all other layers
|
Activates layer and deactivates all other layers
|
||||||
'''
|
'''
|
||||||
|
self._active_combo = None
|
||||||
keyboard.active_layers.clear()
|
keyboard.active_layers.clear()
|
||||||
keyboard.active_layers.insert(0, key.meta.layer)
|
keyboard.active_layers.insert(0, key.meta.layer)
|
||||||
|
|
||||||
def _print_debug(self, keyboard):
|
def _print_debug(self, keyboard):
|
||||||
# debug(f'__getitem__ {key}')
|
|
||||||
if debug.enabled:
|
if debug.enabled:
|
||||||
debug(f'active_layers={keyboard.active_layers}')
|
debug(f'active_layers={keyboard.active_layers}')
|
||||||
|
|
||||||
|
def activate_layer(self, keyboard, layer, as_default=False):
|
||||||
|
if as_default:
|
||||||
|
keyboard.active_layers[-1] = layer
|
||||||
|
else:
|
||||||
|
keyboard.active_layers.insert(0, layer)
|
||||||
|
|
||||||
|
if self.combo_layers:
|
||||||
|
self._activate_combo_layer(keyboard)
|
||||||
|
|
||||||
|
self._print_debug(keyboard)
|
||||||
|
|
||||||
|
def deactivate_layer(self, keyboard, layer):
|
||||||
|
# Remove the first instance of the target layer from the active list
|
||||||
|
# under almost all normal use cases, this will disable the layer (but
|
||||||
|
# preserve it if it was triggered as a default layer, etc.).
|
||||||
|
# This also resolves an issue where using DF() on a layer
|
||||||
|
# triggered by MO() and then defaulting to the MO()'s layer
|
||||||
|
# would result in no layers active.
|
||||||
|
try:
|
||||||
|
del_idx = keyboard.active_layers.index(layer)
|
||||||
|
del keyboard.active_layers[del_idx]
|
||||||
|
except ValueError:
|
||||||
|
if debug.enabled:
|
||||||
|
debug(f'_mo_released: layer {layer} not active')
|
||||||
|
|
||||||
|
if self.combo_layers:
|
||||||
|
self._deactivate_combo_layer(keyboard, layer)
|
||||||
|
|
||||||
|
self._print_debug(keyboard)
|
||||||
|
|
||||||
|
def _activate_combo_layer(self, keyboard):
|
||||||
|
if self._active_combo:
|
||||||
|
return
|
||||||
|
|
||||||
|
for combo, result in self.combo_layers.items():
|
||||||
|
matching = True
|
||||||
|
for layer in combo:
|
||||||
|
if layer not in keyboard.active_layers:
|
||||||
|
matching = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if matching:
|
||||||
|
self._active_combo = combo
|
||||||
|
keyboard.active_layers.insert(0, result)
|
||||||
|
break
|
||||||
|
|
||||||
|
def _deactivate_combo_layer(self, keyboard, layer):
|
||||||
|
if self._active_combo and layer in self._active_combo:
|
||||||
|
keyboard.active_layers.remove(self.combo_layers[self._active_combo])
|
||||||
|
self._active_combo = None
|
||||||
|
Loading…
Reference in New Issue
Block a user