--------- Co-authored-by: xs5871 <60395129+xs5871@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user