Behavior API proof of concept PR

John Hendrikx john.hendrikx at gmail.com
Sat Dec 2 10:06:36 UTC 2023


Hi list,

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):

privatestaticfinalKeyHandler<ToggleButton> KEY_HANDLER;

static{

SimpleKeyBinder<ToggleButton> keyBinder= newSimpleKeyBinder<>();

keyBinder.addBinding(newKeyCodeCombination(KeyCode.UP), c-> traverse(c, 
Direction.UP));

keyBinder.addBinding(newKeyCodeCombination(KeyCode.DOWN), c-> 
traverse(c, Direction.DOWN));

keyBinder.addBinding(newKeyCodeCombination(KeyCode.RIGHT), c-> 
traverse(c, Direction.RIGHT));

keyBinder.addBinding(newKeyCodeCombination(KeyCode.LEFT), c-> 
traverse(c, Direction.LEFT));

KEY_HANDLER= keyBinder;

}

@Override

publicStateFactory<? superToggleButton> configure(BehaviorInstaller<? 
extendsToggleButton> installer) {

installer.registerKeyHandler(KEY_HANDLER);

returnButtonBaseBehavior.getInstance().configure(installer);

}

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).

The PR is here: https://github.com/openjdk/jfx/pull/1265

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.

--John

On 01/12/2023 20:56, John Hendrikx wrote:
>
> 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:
>
> @Override
>
> publicStateFactory<Spinner<?>> configure(BehaviorInstaller<Spinner<?>> 
> installer) {
>
> installer.setKeyHandler(KEY_HANDLER);
>
> installer.registerEventHandler(MouseEvent.MOUSE_PRESSED, 
> State::mousePressed);
>
> installer.registerEventHandler(MouseEvent.MOUSE_RELEASED, 
> State::mouseReleased);
>
> installer.registerEventHandler(AccessibleActionEvent.TRIGGERED, 
> State::accessibleActionTriggered);
>
> installer.registerPropertyListener(Node::sceneProperty, 
> State::sceneChanged);
>
> returnState::new;
>
> }
>
> Changes:
> - Renamed BehaviorContext to BehaviorInstaller to better reflect its 
> purpose
> - Behaviors now configure the BehaviorInstaller, and provide a factory 
> for their State
>    - This simplifies all the internal methods of Behaviors, no need 
> for a Control parameter everywhere anymore; State has a Control reference!
>    - Because state and control are now provided at the same time, the 
> handlers can all be static and reusable
> - 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
>    - This process, although a bit complex, ensures that Behaviors are 
> strictly limited in what changes they can apply to the control
>    - The process also externalizes the Behavior's state, allowing its 
> handlers, listeners and key bindings to be reused accross controls
> - Introduce a KeyHandler class, and a non-public helper 
> SimpleKeyBinder; these can be a precursor for an InputMap system
>    - KeyHandler is fully static (or can be) to allow key binding reuse
>    - Control can later be made responsible for handling InputMap style 
> overrides
>
> 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.
>
> The PR is here: https://github.com/openjdk/jfx/pull/1265
>
> --John
>
>
> On 30/11/2023 23:28, John Hendrikx wrote:
>>
>> Thanks for having a look!
>>
>> On 30/11/2023 19:47, Andy Goryachev wrote:
>>>
>>> I think BehaviorContext looks suspiciously like a (skin) input map, 
>>> so I would rather see that.
>>>
>>> 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.
>>>
>> I'm unsure what you are trying to say here.
>>
>> 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).
>>
>> 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.
>>
>> 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.
>>
>> --John
>>
>>> *From: *openjfx-dev <openjfx-dev-retn at openjdk.org> on behalf of John 
>>> Hendrikx <john.hendrikx at gmail.com>
>>> *Date: *Wednesday, November 29, 2023 at 14:54
>>> *To: *openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>
>>> *Subject: *Behavior API proof of concept PR
>>>
>>> For those still interested, I've updated
>>> https://github.com/openjdk/jfx/pull/1265 with a replaced ButtonBehavior
>>> and SpinnerBehavior, using concepts laid out in
>>> https://gist.github.com/hjohn/c7b1bf9d4a4770b1b3ae854b20fbaa94
>>>
>>> Any feedback is welcome, I think it's making progress, and the changes
>>> required seem quite doable.
>>>
>>> The ability for Behaviors to intercept any event from its Control or 
>>> its
>>> (named) children makes Behaviors a lot more powerful than earlier
>>> proposals, allowing for completely new interactions without requiring
>>> Skin changes.
>>>
>>> --John
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20231202/c85e2bdd/attachment-0001.htm>


More information about the openjfx-dev mailing list