<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hi list,<br>
    </p>
    <p>I've done another update to the proof of concept PR for
      Behaviors, this time focusing a bit on allowing behavior
      extension.  I added a ToggleButtonBehavior that is an extension of
      a ButtonBaseBehavior. I tweaked some generics to make this
      possible, and changed a few API's slightly.  A
      ToggleButtonBehavior looks like this (relevant parts only):</p>
    <div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
      <div style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#0000a0;font-weight:bold;">private</span><span style="color:#000000;"> </span><span style="color:#0000a0;font-weight:bold;">static</span><span style="color:#000000;"> </span><span style="color:#0000a0;font-weight:bold;">final</span><span style="color:#000000;"> KeyHandler<ToggleButton> </span><span style="background-color:#ffffff;padding:0px 0px 0px 2px;"><span style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><span style="color:#000000;"></span><span style="color:#0000c0;font-style:italic;font-weight:bold;">KEY_HANDLER</span></span></span><span style="color:#000000;">;</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#0000a0;font-weight:bold;">static</span><span style="color:#000000;"> {</span></p><p style="margin:0;"><span style="color:#000000;">        SimpleKeyBinder<ToggleButton> </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;"> = </span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;"> SimpleKeyBinder<>();</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;">.addBinding(</span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;"> KeyCodeCombination(KeyCode.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">UP</span><span style="color:#000000;">), </span><span style="color:#6a3e3e;">c</span><span style="color:#000000;"> -> </span><span style="color:#000000;font-style:italic;">traverse</span><span style="color:#000000;">(</span><span style="color:#6a3e3e;">c</span><span style="color:#000000;">, Direction.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">UP</span><span style="color:#000000;">));</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;">.addBinding(</span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;"> KeyCodeCombination(KeyCode.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">DOWN</span><span style="color:#000000;">), </span><span style="color:#6a3e3e;">c</span><span style="color:#000000;"> -> </span><span style="color:#000000;font-style:italic;">traverse</span><span style="color:#000000;">(</span><span style="color:#6a3e3e;">c</span><span style="color:#000000;">, Direction.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">DOWN</span><span style="color:#000000;">));</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;">.addBinding(</span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;"> KeyCodeCombination(KeyCode.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">RIGHT</span><span style="color:#000000;">), </span><span style="color:#6a3e3e;">c</span><span style="color:#000000;"> -> </span><span style="color:#000000;font-style:italic;">traverse</span><span style="color:#000000;">(</span><span style="color:#6a3e3e;">c</span><span style="color:#000000;">, Direction.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">RIGHT</span><span style="color:#000000;">));</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;">.addBinding(</span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;"> KeyCodeCombination(KeyCode.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">LEFT</span><span style="color:#000000;">), </span><span style="color:#6a3e3e;">c</span><span style="color:#000000;"> -> </span><span style="color:#000000;font-style:italic;">traverse</span><span style="color:#000000;">(</span><span style="color:#6a3e3e;">c</span><span style="color:#000000;">, Direction.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">LEFT</span><span style="color:#000000;">));</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#0000c0;font-style:italic;font-weight:bold;">KEY_HANDLER</span><span style="color:#000000;"> = </span><span style="color:#6a3e3e;">keyBinder</span><span style="color:#000000;">;</span></p><p style="margin:0;"><span style="color:#000000;">    }</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#646464;">@Override</span></p><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#0000a0;font-weight:bold;">public</span><span style="color:#000000;"> StateFactory<? </span><span style="color:#0000a0;font-weight:bold;">super</span><span style="color:#000000;"> ToggleButton> configure(BehaviorInstaller<? </span><span style="color:#0000a0;font-weight:bold;">extends</span><span style="color:#000000;"> ToggleButton> </span><span style="color:#6a3e3e;">installer</span><span style="color:#000000;">) {</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;">installer</span><span style="color:#000000;">.registerKeyHandler(</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">KEY_HANDLER</span><span style="color:#000000;">);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#7f0055;font-weight:bold;">return</span><span style="color:#000000;"> ButtonBaseBehavior.</span><span style="color:#000000;font-style:italic;">getInstance</span><span style="color:#000000;">().configure(</span><span style="color:#6a3e3e;">installer</span><span style="color:#000000;">);</span></p><p style="margin:0;"><span style="color:#000000;">    }</span></p></div>
    </div>
    <p>As you can see, all it takes to "inherit" the basic behavior from
      ButtonBase is to have it also configure the installer.  The
      ToggleButtonBehavior does not extend the ButtonBaseBehavior
      (although I think it could have, if you really wanted to).</p>
    <p>The PR is here: <a class="moz-txt-link-freetext" href="https://github.com/openjdk/jfx/pull/1265">https://github.com/openjdk/jfx/pull/1265</a></p>
    <p>There are some failing tests which I'll get around to fixing.
      Mostly tests which do nasty stuff like getting at internals like
      InputMap to do their verifications.  So far so good.<br>
    </p>
    <p>--John<br>
    </p>
    <div class="moz-cite-prefix">On 01/12/2023 20:56, John Hendrikx
      wrote:<br>
    </div>
    <blockquote type="cite"
      cite="mid:af848a3b-2348-d812-0527-e8a77fed0909@gmail.com">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <p>I've updated the proof of concept PR, and it has a number of
        changes to allow more reuse of the behaviors handlers and key
        bindings.  This has resulted in the Behavior installation
        process to become a bit more complex, but the actual use and the
        API's are still relatively simple.  For example,
        SpinnerBehavior's configuration method now looks like this:</p>
      <div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
        <div style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#646464;">@Override</span></p><p style="margin:0;"><span style="color:#000000;">    </span><span style="color:#0000a0;font-weight:bold;">public</span><span style="color:#000000;"> StateFactory<Spinner<?>> configure(BehaviorInstaller<Spinner<?>> </span><span style="color:#6a3e3e;background-color:#f0d8a8;">installer</span><span style="color:#000000;">) {</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;background-color:#d4d4d4;">installer</span><span style="color:#000000;">.setKeyHandler(</span><span style="background-color:#ffffff;padding:0px 0px 0px 2px;"><span style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><span style="color:#000000;"></span><span style="color:#0000c0;background-color:#d4d4d4;font-style:italic;font-weight:bold;">KEY_HANDLER</span></span></span><span style="color:#000000;">);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;background-color:#d4d4d4;">installer</span><span style="color:#000000;">.registerEventHandler(MouseEvent.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">MOUSE_PRESSED</span><span style="color:#000000;">, State::mousePressed);</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;background-color:#d4d4d4;">installer</span><span style="color:#000000;">.registerEventHandler(MouseEvent.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">MOUSE_RELEASED</span><span style="color:#000000;">, State::mouseReleased);</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;background-color:#d4d4d4;">installer</span><span style="color:#000000;">.registerEventHandler(AccessibleActionEvent.</span><span style="color:#0000c0;font-style:italic;font-weight:bold;">TRIGGERED</span><span style="color:#000000;">, State::accessibleActionTriggered);</span></p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#6a3e3e;background-color:#d4d4d4;">installer</span><span style="color:#000000;">.registerPropertyListener(Node::sceneProperty, State::sceneChanged);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;">        </span><span style="color:#7f0055;font-weight:bold;">return</span><span style="color:#000000;"> State::</span><span style="color:#0000a0;font-weight:bold;">new</span><span style="color:#000000;">;</span></p><p style="margin:0;"><span style="color:#000000;">    }</span></p></div>
      </div>
      <p>Changes:<br>
        - Renamed BehaviorContext to BehaviorInstaller to better reflect
        its purpose<br>
        - Behaviors now configure the BehaviorInstaller, and provide a
        factory for their State<br>
           - This simplifies all the internal methods of Behaviors, no
        need for a Control parameter everywhere anymore; State has a
        Control reference!<br>
           - Because state and control are now provided at the same
        time, the handlers can all be static and reusable<br>
        - Controls create a BehaviorInstaller, ask a Behavior to
        configure it, then ask the installer to install the handlers on
        the control, resulting in a Subscription to undo all changes
        later<br>
           - This process, although a bit complex, ensures that
        Behaviors are strictly limited in what changes they can apply to
        the control<br>
           - The process also externalizes the Behavior's state,
        allowing its handlers, listeners and key bindings to be reused
        accross controls<br>
        - Introduce a KeyHandler class, and a non-public helper
        SimpleKeyBinder; these can be a precursor for an InputMap system<br>
           - KeyHandler is fully static (or can be) to allow key binding
        reuse<br>
           - Control can later be made responsible for handling InputMap
        style overrides<br>
      </p>
      <p>Note: earlier it was said that behaviors can be stateless, and
        this is certainly true for the main Behavior type (which can be
        "set" on as many controls as you'd like).  However, even
        Behaviors without "state" still need the Control reference as a
        piece of state in order to do anything at all.  So, no installed
        behaviors are actually stateless, there is always at a minimum a
        control reference.  To better reflect this, the State classes
        for the two proof of concept behaviors now are constructed with
        a Control (to keep all the related state together), allowing for
        easy access to the control for all its methods.</p>
      <p>The PR is here: <a class="moz-txt-link-freetext"
          href="https://github.com/openjdk/jfx/pull/1265"
          moz-do-not-send="true">https://github.com/openjdk/jfx/pull/1265</a><br>
      </p>
      <p>--John<br>
      </p>
      <p><br>
      </p>
      <div class="moz-cite-prefix">On 30/11/2023 23:28, John Hendrikx
        wrote:<br>
      </div>
      <blockquote type="cite"
        cite="mid:2c5318b7-178e-b834-aa6a-2d63f56699da@gmail.com">
        <meta http-equiv="Content-Type" content="text/html;
          charset=UTF-8">
        <p>Thanks for having a look!<br>
        </p>
        <div class="moz-cite-prefix">On 30/11/2023 19:47, Andy Goryachev
          wrote:<br>
        </div>
        <blockquote type="cite"
cite="mid:DM5PR1001MB2172E7CED4ED6FD04AE0AA5FE582A@DM5PR1001MB2172.namprd10.prod.outlook.com">
          <meta http-equiv="Content-Type" content="text/html;
            charset=UTF-8">
          <meta name="Generator" content="Microsoft Word 15 (filtered
            medium)">
          <style>@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}@font-face
        {font-family:"Iosevka Fixed SS16";
        panose-1:2 0 5 9 3 0 0 0 0 4;}@font-face
        {font-family:"Times New Roman \(Body CS\)";
        panose-1:2 11 6 4 2 2 2 2 2 4;}p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        font-size:10.0pt;
        font-family:"Calibri",sans-serif;}a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}span.EmailStyle19
        {mso-style-type:personal-reply;
        font-family:"Iosevka Fixed SS16";
        color:windowtext;}.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;
        mso-ligatures:none;}div.WordSection1
        {page:WordSection1;}</style>
          <div class="WordSection1">
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16"">I think BehaviorContext looks suspiciously
                like a (skin) input map, so I would rather see that.  <o:p></o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16"">Invention of State class is a way to work
                around the stateless behavior which, though it adds (in
                my opinion) very little, might still be a feature that
                app developers want to exploit - at least in terms of
                saving on per-instance key maps.  So any alternative
                proposal should also support a stateless behavior.</span></p>
          </div>
        </blockquote>
        <p>I'm unsure what you are trying to say here.</p>
        <p>The State class is internal, and can be anything, and take
          any form.  If you only need to add a single EventHandler, you
          can also make that a class instead of a Lambda and have the
          state there. The State class, which can be named anything, is
          just a convenient way to share state amongst multiple handlers
          without having to construct each of them with special
          parameters, or having to put the state in the main class's
          fields (which would make it not reusable).<br>
        </p>
        <p>I do however see that I did not take reusability of the
          keymaps into account in this version, that will need to be
          adjusted -- I was more focused on keeping Behaviors isolated
          for now, not needing a Control until installed, and having 0
          references between Skins and Behaviors.</p>
        <p>I was hoping for more comments on how the seperation between
          Skin and Behavior is done in this PR.  I will attempt to
          adjust TextInputControl soon (and I will try a few subclasses
          of ButtonBehavior, to see how they can be built "on top" of
          another behavior) -- so far I've not found any real problems
          with this approach.<br>
        </p>
        <p>--John<br>
        </p>
        <blockquote type="cite"
cite="mid:DM5PR1001MB2172E7CED4ED6FD04AE0AA5FE582A@DM5PR1001MB2172.namprd10.prod.outlook.com">
          <div class="WordSection1">
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p></o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p><o:p></o:p></span></p>
          </div>
        </blockquote>
        <blockquote type="cite"
cite="mid:DM5PR1001MB2172E7CED4ED6FD04AE0AA5FE582A@DM5PR1001MB2172.namprd10.prod.outlook.com">
          <div class="WordSection1">
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p></span></p>
            <p class="MsoNormal"><span
                style="font-size:11.0pt;font-family:"Iosevka Fixed
                SS16""><o:p> </o:p></span></p>
            <div id="mail-editor-reference-message-container">
              <div>
                <div style="border:none;border-top:solid #B5C4DF
                  1.0pt;padding:3.0pt 0in 0in 0in">
                  <p class="MsoNormal" style="margin-bottom:12.0pt"><b><span
                        style="font-size:12.0pt;color:black">From: </span></b><span
                      style="font-size:12.0pt;color:black">openjfx-dev <a
                        class="moz-txt-link-rfc2396E"
                        href="mailto:openjfx-dev-retn@openjdk.org"
                        moz-do-not-send="true"><openjfx-dev-retn@openjdk.org></a>
                      on behalf of John Hendrikx <a
                        class="moz-txt-link-rfc2396E"
                        href="mailto:john.hendrikx@gmail.com"
                        moz-do-not-send="true"><john.hendrikx@gmail.com></a><br>
                      <b>Date: </b>Wednesday, November 29, 2023 at
                      14:54<br>
                      <b>To: </b><a class="moz-txt-link-abbreviated
                        moz-txt-link-freetext"
                        href="mailto:openjfx-dev@openjdk.org"
                        moz-do-not-send="true">openjfx-dev@openjdk.org</a>
                      <a class="moz-txt-link-rfc2396E"
                        href="mailto:openjfx-dev@openjdk.org"
                        moz-do-not-send="true"><openjfx-dev@openjdk.org></a><br>
                      <b>Subject: </b>Behavior API proof of concept PR<o:p></o:p></span></p>
                </div>
                <div>
                  <p class="MsoNormal" style="margin-bottom:12.0pt"><span
                      style="font-size:11.0pt">For those still
                      interested, I've updated <br>
                      <a href="https://github.com/openjdk/jfx/pull/1265"
                        moz-do-not-send="true"
                        class="moz-txt-link-freetext">https://github.com/openjdk/jfx/pull/1265</a>
                      with a replaced ButtonBehavior <br>
                      and SpinnerBehavior, using concepts laid out in <br>
                      <a
                        href="https://gist.github.com/hjohn/c7b1bf9d4a4770b1b3ae854b20fbaa94"
                        moz-do-not-send="true"
                        class="moz-txt-link-freetext">https://gist.github.com/hjohn/c7b1bf9d4a4770b1b3ae854b20fbaa94</a><br>
                      <br>
                      Any feedback is welcome, I think it's making
                      progress, and the changes <br>
                      required seem quite doable.<br>
                      <br>
                      The ability for Behaviors to intercept any event
                      from its Control or its <br>
                      (named) children makes Behaviors a lot more
                      powerful than earlier <br>
                      proposals, allowing for completely new
                      interactions without requiring <br>
                      Skin changes.<br>
                      <br>
                      --John<o:p></o:p></span></p>
                </div>
              </div>
            </div>
          </div>
        </blockquote>
      </blockquote>
    </blockquote>
  </body>
</html>