--------- 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
|
||||
- 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
|
||||
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
|
||||
@ -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
|
||||
layers with `KC.DF()` the same way that you would treat using `KC.TO()`
|
||||
|
||||
|
||||
## Example Code
|
||||
For our example, let's take a simple 3x3 macropad with two layers as follows:
|
||||
|
||||
|
@ -36,9 +36,15 @@ class LayerKeyMeta:
|
||||
class Layers(HoldTap):
|
||||
'''Gives access to the keys used to enable the layer system'''
|
||||
|
||||
def __init__(self):
|
||||
_active_combo = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
combo_layers=None,
|
||||
):
|
||||
# Layers
|
||||
super().__init__()
|
||||
self.combo_layers = combo_layers
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('MO',),
|
||||
@ -46,9 +52,7 @@ class Layers(HoldTap):
|
||||
on_release=self._mo_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('DF',),
|
||||
on_press=self._df_pressed,
|
||||
validator=layer_key_validator, names=('DF',), on_press=self._df_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
@ -57,14 +61,10 @@ class Layers(HoldTap):
|
||||
on_release=self._lm_released,
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('TG',),
|
||||
on_press=self._tg_pressed,
|
||||
validator=layer_key_validator, names=('TG',), on_press=self._tg_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('TO',),
|
||||
on_press=self._to_pressed,
|
||||
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed
|
||||
)
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator_lt,
|
||||
@ -83,67 +83,102 @@ class Layers(HoldTap):
|
||||
'''
|
||||
Switches the default layer
|
||||
'''
|
||||
keyboard.active_layers[-1] = key.meta.layer
|
||||
self._print_debug(keyboard)
|
||||
self.activate_layer(keyboard, key.meta.layer, as_default=True)
|
||||
|
||||
def _mo_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer, switches off when you let go
|
||||
'''
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
self._print_debug(keyboard)
|
||||
self.activate_layer(keyboard, key.meta.layer)
|
||||
|
||||
@staticmethod
|
||||
def _mo_released(key, keyboard, *args, **kwargs):
|
||||
# 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 _mo_released(self, key, keyboard, *args, **kwargs):
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _lm_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
keyboard.add_key(key.meta.kc)
|
||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
||||
keyboard.hid_pending = True
|
||||
keyboard.keys_pressed.add(key.meta.kc)
|
||||
self.activate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _lm_released(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
keyboard.remove_key(key.meta.kc)
|
||||
self._mo_released(key, keyboard, *args, **kwargs)
|
||||
keyboard.hid_pending = True
|
||||
keyboard.keys_pressed.discard(key.meta.kc)
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _tg_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Toggles the layer (enables it if not active, and vise versa)
|
||||
'''
|
||||
# See mo_released for implementation details around this
|
||||
try:
|
||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
||||
del keyboard.active_layers[del_idx]
|
||||
except ValueError:
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
if key.meta.layer in keyboard.active_layers:
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
else:
|
||||
self.activate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _to_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Activates layer and deactivates all other layers
|
||||
'''
|
||||
self._active_combo = None
|
||||
keyboard.active_layers.clear()
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
|
||||
def _print_debug(self, keyboard):
|
||||
# debug(f'__getitem__ {key}')
|
||||
if debug.enabled:
|
||||
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