zmk icon indicating copy to clipboard operation
zmk copied to clipboard

feat: Parameterised Mod Morph and Tap Dance

Open nmunnich opened this issue 1 year ago • 7 comments

General idea is to have a binding-params array property which maps parameters input to the behavior to the behaviors found in the bindings array. Bit flags and some macros to tidy it all up.

Docs are included, I decided to substitute the references to the non-parameterised version with the parameterised one in the interest of "one way to do it". Current one was moved to a footnote.

In general I think the approach and user experience side of things is good. May add more tests. Unsure whether documentation needs further reworking or if the non-parameterised version should remain prominent.

PR check-list

  • [X] Branch has a clean commit history
  • [X] Additional tests are included, if changing behaviors/core code that is testable.
  • [X] Proper Copyright + License headers added to applicable files (Generally, we stick to "The ZMK Contributors" for copyrights to help avoid churn when files get edited)
  • [X] Pre-commit used to check formatting of files, commit messages, etc.

TODO:

  • [ ] Identify a solution to the hold-tap issue.
  • [ ] Add behavior metadata for Studio.

nmunnich avatar Dec 19 '24 00:12 nmunnich

* Forcing mod-morphs and tap-dances to always have two parameters prevents them from being used in hold-taps, or inside parametrized mod-morph/tap-dances. This is a big limitation, so it might be worth keeping around zero-param versions as the main variants.

You should be able to use them inside the others with appropriate 0s being passed in the right places. I'll add some more tests to be sure.

EDIT: parameterised nesting works fine, hold-tap is an issue that needs to be discussed to find the right resolution.

* These are a bit inconsistent w.r.t. parametrized macros, they are more flexible so we can't easily unify them. One thing we can do is switch to `PLACEHOLDER` instead of `MACRO_PLACEHOLDER` in the macro docs.

I think that both hold-tap and parameterised macros could be adjusted to use a binding-params, I just wanted to get the ones which aren't yet parameterised done before working on (understanding and) adjusting macro and hold-tap. Definitely agree on switching away from MACRO_PLACEHOLDER.

nmunnich avatar Jan 02 '25 07:01 nmunnich

Hi,

Is this every going to get merged? :D

From what I gather this feature would allow a user to define a generic behavior that accepts 2 symbols. The behavior would output symbol_1 without the modifier being held and symbol_2 if the opposite is true.

I think this is a very useful feature.

In the meantime is there any other solution to achieving the same behavior?

Could I just define a layer? Would that be sufficient?

Thank you in advance.

pnoulis avatar Sep 15 '25 00:09 pnoulis

In the meantime is there any other solution to achieving the same behavior?

this PR basically proposes a new syntax for the existing Mod-Morph so you can use that, see https://zmk.dev/docs/keymaps/behaviors/mod-morph

snoyer avatar Sep 15 '25 03:09 snoyer

In the meantime is there any other solution to achieving the same behavior?

this PR basically proposes a new syntax for the existing Mod-Morph so you can use that, see https://zmk.dev/docs/keymaps/behaviors/mod-morph

Thank you for your response.

The way currently things stand a User is allowed to define a static behavior.

Example: (from the docs)

bindings = <&kp B>, <kp C> -> When the symbol B is pressed output B. When the symbol B AND MODIFIER LCTL|RCTL is pressed output C.

      morph_BC: morph_BC {
            compatible = "zmk,behavior-mod-morph";
            #binding-cells = <0>;
            bindings = <&kp B>, <&kp C>;
            mods = <(MOD_LCTL|MOD_RCTL)>;
        };

Essentially this behavior is static, it cannot be parameterized.

Compare it with the next example:

behavior_shift: behavior_shift {
  compatible = "zmk,behavior-mod-morph";
  label = "behavior_shift";
  #binding-cells = <2>;
  bindings = <&kp>, <&kp>;
  mods = <(LSHIFT|RSHIFT)>;
};

Defines a behavior that accepts 2 arguments (2 symbols). bindings = <&kp>, <&kp>-> When symbol_1 is being pressed output symbol_1. When symbol_1 is being pressed AND modifier LSHIFT | RSHIFT output symbol_2.

This behavior is very different from the static ones.

How do you propose achieving the same result?

pnoulis avatar Sep 15 '25 06:09 pnoulis

My point was that, as far as I understand, this PR would let you do the following, with a single parameterized shift_morph behavior:

/ {
  keymap {
    default_layer {
      bindings = <
        &shift_morph A B    &shift_morph C D    &shift_morph E F
      >;
    };
  };
  behaviors {
    shift_morph: shift_morph {
      compatible = "zmk,behavior-mod-morph-param";
      #binding-cells = <2>;
      bindings = <&kp PLACEHOLDER>, <&kp PLACEHOLDER>;
      mods = <(MOD_LSFT|MOD_RSFT)>;
      binding-params = <BINDING_PARAM(1,0) BINDING_PARAM(2,0)>;
    };
  };
};

but in the meantime you could achieve functionally the same thing with multiple non-parameterized shift_morph_XY behaviors:

/ {
  keymap {
    default_layer {
      bindings = <
        &shift_morph_AB    &shift_morph_CD    &shift_morph_EF
      >;
    };
  };
  behaviors {
    shift_morph_AB: shift_morph_AB {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&kp A>, <&kp B>;
      mods = <(MOD_LSFT|MOD_RSFT)>;
    };
    shift_morph_CD: shift_morph_CD {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&kp C>, <&kp D>;
      mods = <(MOD_LSFT|MOD_RSFT)>;
    };
    shift_morph_EF: shift_morph_EF {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&kp E>, <&kp F>;
      mods = <(MOD_LSFT|MOD_RSFT)>;
    };
  };
};

sorry if that wasn't relevant to the question

snoyer avatar Sep 15 '25 07:09 snoyer

What @snoyer describes is indeed what this PR would let you do, and also how to work around it for the time being. However, I wouldn't consider this PR ready for usage yet - somewhere in the force pushing the tests failed, and I still need to fix both hold-tap and allow the parameterised behaviours to be customisable with ZMK Studio.

I will return to this PR eventually, but given the limited capacity for behaviour-focused PRs I will be waiting until #2717, #2848, and #2943 have been reviewed/merged first.

nmunnich avatar Sep 15 '25 07:09 nmunnich

Thank you both for your prompt and helpful responses!!!

pnoulis avatar Sep 15 '25 08:09 pnoulis