Skip to content

Commit

Permalink
docs: Documented parameterised tap dance and mod morph
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick-Munnich committed Dec 19, 2024
1 parent d73530b commit 5ba0a64
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 56 deletions.
52 changes: 35 additions & 17 deletions docs/docs/config/behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,23 +233,28 @@ See the [mod-morph behavior](../keymaps/behaviors/mod-morph.md) documentation fo

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml)
Definition files:

Applies to: `compatible = "zmk,behavior-mod-morph"`
- [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml)
- [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml)

| Property | Type | Description |
| ---------------- | ------------- | --------------------------------------------------------------------------------- |
| `#binding-cells` | int | Must be `<0>` |
| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press |
| `mods` | int | A bit field of modifiers. The morph behavior is used if any of these are pressed. |
| Property | Type | Description |
| ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `compatible` | string | Mod-Morph variant, **must be _one_ of**:<ul><li>`"zmk,behavior-mod-morph-param"`</li><li>`"zmk,behavior-mod-morph"`</li></ul> |
| `#binding-cells` | int | Must be <ul><li>`<2>` if `compatible = "zmk,behavior-mod-morph-param"`</li><li>`<0>` if `compatible = "zmk,behavior-mod-morph"`</li></ul> |
| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press |
| `binding-params` | array | A list of two param assignment maps. Only applies to `compatible = "zmk,behavior-mod-morph-param"` |
| `mods` | int | A bit field of modifiers. The morph behavior is used if any of these are pressed. |

See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers.
The `binding-params` array should be constructed using the `BINDING_PARAM` macro.
See [here](../keymaps/behaviors/mod-morph.md#binding-parameters) for more details.

You can use the following nodes to tweak the default behaviors:

| Node | Behavior |
| -------- | ------------------------------------------------- |
| `&gresc` | [Grave escape](../keymaps/behaviors/mod-morph.md) |
| Node | Behavior |
| ----- | ------------------------------------------------------- |
| `&mm` | [Mod Morph Keypress](../keymaps/behaviors/mod-morph.md) |

## Sensor Rotation

Expand Down Expand Up @@ -327,15 +332,28 @@ See the [tap dance behavior](../keymaps/behaviors/tap-dance.mdx) documentation f

### Devicetree

Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml)
Definition files:

- [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance-param.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml)
- [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml)

| Property | Type | Description | Default |
| ----------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `compatible` | string | Tap-Dance variant, **must be _one_ of**:<ul><li>`"zmk,behavior-tap-dance-param"`</li><li>`"zmk,behavior-tap-dance"`</li></ul> | |
| `#binding-cells` | int | Must be <ul><li>`<2>` if `compatible = "zmk,behavior-tap-dance-param"`</li><li>`<0>` if `compatible = "zmk,behavior-tap-dance"`</li></ul> | |
| `bindings` | phandle array | A list of behaviors from which to select | |
| `binding-params` | array | A list of two param assignment maps. Only applies to `compatible = "zmk,behavior-tap-dance-param"` | |
| `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 |

Applies to: `compatible = "zmk,behavior-tap-dance"`
See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers.
The `binding-params` array should be constructed using the `BINDING_PARAM` macro.
See [here](../keymaps/behaviors/tap-dance.mdx#binding-params) for more details.

You can use the following nodes to tweak the default behaviors:

| Property | Type | Description | Default |
| ----------------- | ------------- | -------------------------------------------------------------------------------------------- | ------- |
| `#binding-cells` | int | Must be `<0>` | |
| `bindings` | phandle array | A list of behaviors from which to select | |
| `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 |
| Node | Behavior |
| ----- | -------------------------------------------------------- |
| `&td` | [Tap Dance Keypress](../keymaps/behaviors/tap-dance.mdx) |

## Two Axis Input

Expand Down
108 changes: 82 additions & 26 deletions docs/docs/keymaps/behaviors/mod-morph.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,41 @@ The mod-morph behavior invokes a different behavior depending on whether any of

## Mod-Morph

### Configuration
ZMK provides a simple build-in mod-morph behavior.
When pressed with one of `LEFT_SHIFT`, `LEFT_GUI`, `RIGHT_SHIFT`, `RIGHT_GUI` active, a [key press](key-press.md) will be triggered using the second parameter.
Otherwise, a key press with the first parameter will be triggered.

Below is an example of how to implement the mod-morph "Grave Escape". When assigned to a key, pressing the key on its own will send an
Escape keycode but pressing it while a shift or GUI modifier is held sends the grave `` ` `` keycode instead:
### Behavior Binding

- Reference: `&mm`
- Parameters: The keycode usage IDs from the usage page, e.g. `N4` or `A`

Example:

```dts
/ {
behaviors {
gresc: grave_escape {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
bindings = <&kp ESC>, <&kp GRAVE>;
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
};
};
};
&mm A B
```

Note that this specific mod-morph exists in ZMK by default using the binding `&gresc`.
## Custom Mod-Morph

### Behavior Binding
If you want to trigger other behaviors or morph based on other combinations of modifiers, you can create a new mod-morph behavior.

- Reference: `&gresc`
- Parameter: None

Example:
Below is an example of how to implement a mod-morph that selects which behavior to trigger based on whether a `CTRL` modifier is active.
When `CTRL` is not active, a `&kp` with the first parameter passed to the behavior is triggered.
Otherwise, the layer whose number corresponds to the second parameter passed to the behavior is triggered.

```dts
&gresc
/ {
behaviors {
mm_ctrl: mod_morph_control {
compatible = "zmk,behavior-mod-morph-param";
#binding-cells = <2>;
mods = <(MOD_LCTL|MOD_RCTL)>;
bindings = <&kp PLACEHOLDER>, <&mo PLACEHOLDER>;
binding-params = <BINDING_PARAM(1,0) BINDING_PARAM(2,0)>;
};
};
};
```

### Mods
Expand All @@ -64,6 +70,37 @@ Example:
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
```

### Binding Parameters

The `binding-params` property determines how the parameters passed to the mod-morph behavior are passed on to the behaviors listed in `bindings`.
It is an array which always has two `BINDING_PARAM(arg1,arg2)` elements - the first corresponds to the first behavior listed in `bindings`, the second corresponding to the second. The number chosen for `argX` determines what the Xth parameter passed to the behavior will be:

- `1`: The Xth parameter passed will be the first parameter that the mod-morph behavior received
- `2`: The Xth parameter passed will be the second parameter that the mod-morph behavior received
- `0`: The Xth parameter will be that which is written in the `bindings` array.

Note that any parameters in `bindings` behaviors which are to be replaced should be set to `PLACEHOLDER`.

Examples:

```dts
binding-params = <BINDING_PARAM(1,2) BINDING_PARAM(0,0)>;
```

The first behavior receives both input parameters, the second receives neither.

```dts
binding-params = <BINDING_PARAM(2,0) BINDING_PARAM(1,0)>;
```

The first behavior receives the second input parameter as its first parameter, the second behavior receives the first input parameter as its first parameter.

```dts
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
```

Neither behavior receives any input parameter. Note that mod-morph always requires two parameters to be passed, so you will still need to write e.g. `&my_mm 0 0` in your keymap - the parameters passed will be ignored[^1].

### Advanced Configuration

#### `keep-mods`
Expand All @@ -76,9 +113,10 @@ For example, the following configuration morphs `LEFT_SHIFT` + `BACKSPACE` into
/ {
behaviors {
bspc_del: backspace_delete {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
compatible = "zmk,behavior-mod-morph-param";
#binding-cells = <2>;
bindings = <&kp BACKSPACE>, <&kp DELETE>;
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
mods = <(MOD_LSFT|MOD_RSFT)>;
keep-mods = <(MOD_RSFT)>;
};
Expand All @@ -97,15 +135,17 @@ As an example, consider the following two mod-morphs:
/ {
behaviors {
morph_BC: morph_BC {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
compatible = "zmk,behavior-mod-morph-param";
#binding-cells = <2>;
bindings = <&kp B>, <&kp C>;
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
mods = <(MOD_LCTL|MOD_RCTL)>;
};
morph_ABC: morph_ABC {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
compatible = "zmk,behavior-mod-morph-param";
#binding-cells = <2>;
bindings = <&kp A>, <&morph_BC>;
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
mods = <(MOD_LSFT|MOD_RSFT)>;
};
};
Expand All @@ -119,3 +159,19 @@ When you assign `&morph_ABC` to a key position and press it, it will output `A`
If the first modified key press sends the modifier along with the morphed keycode and [Karabiner-Elements](https://karabiner-elements.pqrs.org/) is running, disable the "Modify Events" toggle from Karabiner's "Devices" settings page for the keyboard running ZMK.

:::

[^1]: There exists a simplified version of mod-morph without any input parameters, `compatible="zmk,behavior-mod-morph"`:

```dts
/ {
behaviors {
bspc_del: backspace_delete {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
bindings = <&kp BACKSPACE>, <&kp DELETE>;
mods = <(MOD_LSFT|MOD_RSFT)>;
keep-mods = <(MOD_RSFT)>;
};
};
};
```
95 changes: 82 additions & 13 deletions docs/docs/keymaps/behaviors/tap-dance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,75 @@ import TabItem from "@theme/TabItem";

## Summary

A tap-dance key invokes a different behavior (e.g. `kp`) corresponding to how many times it is pressed. For example, you could configure a tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. The expandability of the number of [`bindings`](#bindings) attached to a particular tap-dance is a great way to add more functionality to a single key, especially for keyboards with a limited number of keys. Tap-dances are completely custom, so for every unique tap-dance key,a new tap-dance must be defined in your keymap's `behaviors`.
A tap-dance key invokes a different behavior (e.g. `kp`) corresponding to how many times it is pressed.
For example, you could configure a tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice.
The expandability of the number of [`bindings`](#bindings) attached to a particular tap-dance is a great way to add more functionality to a single key, especially for keyboards with a limited number of keys.
Tap-dances accept two parameters from your keymap, which you can map to the behaviors within as you please.

Tap-dances are designed to resolve immediately when interrupted by another keypress. Meaning, when a keybind is pressed other than any active tap-dances, the tap-dance will activate according to the current value of its counter before the interrupting keybind is registered.

### Configuration
## Keypress Tap Dance

#### `tapping-term-ms`
ZMK provides a simple two-tap tap dance for you to use. Press it once, and it will output a key press with the first parameter.
Press it twice within 200ms, and it will output a key press with the second parameter.

### Behavior Binding

- Reference: `&td`
- Parameters: The keycode usage IDs from the usage page, e.g. `N4` or `A`

Example:

```dts
&td A B
```

## Configuration

### `tapping-term-ms`

Defines the maximum elapsed time after the last tap-dance keybind press before a binding is selected from [`bindings`](#bindings). Default value is `200`ms.

#### `bindings`
### `bindings`

An array of one or more keybinds. This list can include [any ZMK keycode](../list-of-keycodes.mdx) and any listed ZMK behavior, like [hold-taps](hold-tap.mdx), or [sticky keys](sticky-key.md). The index of a keybind in the `bindings` array corresponds to the number of times the tap-dance binding is pressed. For example, in the basic tap-dance counter shown below, `&kp N2` is the second binding in the array of `bindings`: we then see an output of `2` when the `td0` binding is pressed twice.

The number of bindings in this array also determines the tap-dance's maximum number of keypresses. When a tap-dance reaches its maximum number of keypresses, it will immediately invoke the last behavior in its list of `bindings`, rather than waiting for [`tapping-term-ms`](#tapping-term-ms) to expire before the output is displayed.

### Example Usage
### `binding-params`

The `binding-params` property determines how the parameters passed to the tap-dance behavior are passed on to the behaviors listed in `bindings`.
It is an array of `BINDING_PARAM(arg1,arg2)` elements, one for each behavior in `bindings`.
The Nth `BINDING_PARAM(arg1,arg2)` corresponds to the Nth behavior listed in `bindings`.
The number chosen for `argX` determines what the Xth parameter passed to the behavior will be:

- `1`: The Xth parameter passed will be the first parameter that the tap-dance behavior received
- `2`: The Xth parameter passed will be the second parameter that the tap-dance behavior received
- `0`: The Xth parameter will be that which is written in the `bindings` array.

Note that any parameters in `bindings` behaviors which are to be replaced should be set to `PLACEHOLDER`.

Examples:

```dts
binding-params = <BINDING_PARAM(1,2) BINDING_PARAM(0,0)>;
```

The first behavior receives both input parameters, the second receives neither.

```dts
binding-params = <BINDING_PARAM(2,0) BINDING_PARAM(1,0) BINDING_PARAM(0,0)>;
```

The first behavior receives the second input parameter as its first parameter, the second behavior receives the first input parameter as its first parameter, the third receives neither.

```dts
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
```

No behavior receives any input parameter. Note that tap-dance always requires two parameters to be passed, so you will still need to write e.g. `&my_td 0 0` in your keymap - the parameters passed will be ignored[^1].

## Example Usage

<Tabs
defaultValue="basic"
Expand All @@ -44,10 +96,11 @@ This example configures a tap-dance named `td0` that outputs the number of times
/ {
behaviors {
td0: tap_dance_0 {
compatible = "zmk,behavior-tap-dance";
#binding-cells = <0>;
compatible = "zmk,behavior-tap-dance-param";
#binding-cells = <2>;
tapping-term-ms = <200>;
bindings = <&kp N1>, <&kp N2>, <&kp N3>;
binding-params = <BINDING_PARAM(0,0) BINDING_PARAM(0,0) BINDING_PARAM(0,0)>;
};
};
Expand All @@ -56,7 +109,7 @@ This example configures a tap-dance named `td0` that outputs the number of times
default_layer {
bindings = <
&td0
&td0 0 0
>;
};
};
Expand All @@ -75,7 +128,7 @@ Alphanumeric [`key press`](key-press.md) bindings, like those used for `td0`, wi

<TabItem value="advanced">

This example configures a mod-tap inside a tap-dance named `td_mt` that outputs `CAPSLOCK` on a single tap, `LSHIFT` on a single press and hold, and `LCTRL` when the tap-dance is pressed twice.
This example configures a mod-tap inside a tap-dance named `td_mt` that outputs its first parameter on a single tap, `LSHIFT` on a single press and hold, and its second parameter when the tap-dance is pressed twice.

```dts title="Advanced Tap-Dance Example: Nested Mod-Tap"
#include <behaviors.dtsi>
Expand All @@ -84,10 +137,11 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs
/ {
behaviors {
td_mt: tap_dance_mod_tap {
compatible = "zmk,behavior-tap-dance";
#binding-cells = <0>;
compatible = "zmk,behavior-tap-dance-param";
#binding-cells = <2>;
tapping-term-ms = <200>;
bindings = <&mt LSHIFT CAPSLOCK>, <&kp LCTRL>;
bindings = <&mt LSHIFT PLACEHOLDER>, <&kp PLACEHOLDER>;
binding-params = <BINDING_PARAM(0,1) BINDING_PARAM(2,0)>;
};
};
Expand All @@ -96,7 +150,7 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs
default_layer {
bindings = <
&td_mt
&td_mt CAPSLOCK LCTRL
>;
};
};
Expand All @@ -105,3 +159,18 @@ This example configures a mod-tap inside a tap-dance named `td_mt` that outputs

</TabItem>
</Tabs>

[^1]: There exists a simplified version of tap-dance without any input parameters, `compatible="zmk,behavior-tap-dance"`:

```dts
/ {
behaviors {
td0: tap_dance_0 {
compatible = "zmk,behavior-tap-dance";
#binding-cells = <0>;
tapping-term-ms = <200>;
bindings = <&kp N1>, <&kp N2>, <&kp N3>;
};
};
};
```

0 comments on commit 5ba0a64

Please sign in to comment.