Looing for feedback: Behavior API Proposal V2

John Hendrikx john.hendrikx at gmail.com
Tue Nov 28 08:44:11 UTC 2023


On 27/11/2023 23:02, Andy Goryachev wrote:
>
> Dear John:
>
> Thank you for further development of this idea, especially defining 
> the separation between Control, Skin, and Behavior.
>
> Do I read correctly that semantic events are currently off the table?  
> Or, at least, either an implementation detail or some future enhancement?
>
I think for a first public Behavior API semantic events are not a 
requirement.  They were earlier a requirement because we envisioned 
Skins generating semantic events to communicate with Behaviors (in order 
to cut the Skin <-> Behavior dependency), but as that has been solved 
differently, these events are not in a first iteration.

Semantic events would still have value I think as an indirection between 
KeyBinding and its function (see document), unless we go the FunctionTag 
route here.

> I also have a few comments in regards to the strict rules e.g. “C. 
> never modifies its own publicly writable properties” and the reasoning 
> behind those, but that deserves a separate email.
>
Perhaps I worded this too strict.  The idea is simply that Controls 
should not make unexpected changes to their writable properties as the 
user must appear to be in control at all times. So changing a writable 
property in response to some (user) triggered action (like calling 
"Button#arm" results in the armed property changing) is fine.

> For now, I just want to note that BehaviorContext looks suspiciously 
> like an InputMap (a skin/behavior InputMap),
>
But it isn't an InputMap, I explained clearly what its purpose is, to 
isolate the changes that Behaviors makes so Control can remain in 
control, not only for when the event handlers are called (and at what 
priority) but also at uninstallation time.  Behaviors being a public API 
means that users can start doing crazy things, and it would serve us to 
as much as possible restrict those crazy things to a confined space, 
hence BehaviorContext offering limited options to interact with the 
Control during Behavior installation.

This is pretty standard practice when designing user facing API's: don't 
offer the user options they should not be using, as you'll regret it 
later when users inevitably don't follow the rules that you set out.  
Also remember that the Behavior implementor may be a different developer 
than the developer using a Control.  The developer using the Control 
should have some confidence that the act of installing a 3rd party 
Behavior will not adversely affect the Control, and that confidence can 
be severely boosted if the Behavior has limited access.

It indeed also happens to be a good place to offer KeyBinding based 
methods so Behaviors are easy to construct (and again so Control can 
track changes) does not make that an input map; it may *delegate* to 
some internal (or perhaps later public) input map though.

I've done a concession here to the API to allow for KeyBindings so that 
Behaviors can clearly separate a generic KEY_PRESSED handler (which you 
could never remap to do anything else) from things that might be 
remappable later.

> and the fact that you invent a State class indicates that, at least in 
> this example, we are dealing with a stateful behavior.  In other 
> words, why not have a BehaviorBase?
>
> So the only case where we might have a stateless behavior and thus 
> save a few bytes by using a singleton key map is where the control 
> either has no state, or the state is fully encapsulated within 
> control’s properties.  I agree we should support those (rare?) cases 
> should developers want it.
>
I think you sort of answered yourself here. However there is IMHO a far 
more compelling reason:

Ease of use. Behaviors can be constants like Colors and Borders. There 
are no checks and balances that need to be done when calling 
Control#setBehavior() -- it just always works, just like setting a color 
would (no ColorAlreadyInUseException :)).

A Behavior does not get "used up" when installed, instead it is 
"applied", like a Color.  The only reason for the complicated 
construction / install procedure of Skins (and a BehaviorBase solution) 
is an internal one: some state needed to be tracked, and unfortunately 
this internal reason has leaked to be part of public API.  From the 
outside it could and should have worked like a Color or Paint, fully 
reusable.

The intent never was to save a few bytes (at least not in this part).  
Of course I did recognize pretty early on that the current internal 
Behavior implementation is wasteful (duplicating about 25 kB in 
KeyBindings and mutable InputMaps per TextField instance which are 
exactly the same), and that the Control reference can be gleaned from 
the callbacks (so callbacks can also be fully deduplicated).  This last 
part I haven't yet fully incorporated, as it is a balance to strike 
between ease of use (getting a Control reference passed to the Behavior 
directly for easy access) and saving memory (making the callback 
reusable by extracting the Control from the Event).  Given that most 
handlers will be KeyBindings, which are lighter weight, it may not be 
worth it to deduplicate the few extra handlers that deal with the mouse 
or need special key handling.

> What I am getting at here is that if we provide an InputMap and a 
> SkinInputMap instead, then we can have the freedom to implement 
> stateful and stateless behaviors as well as provide key mapping 
> functionality as well as the prioritization of event handlers (if 
> registered via the input maps).
>
I don't quite understand how you would want to do this, and also, isn't 
a SkinInputMap a contradiction?  Skins are about visuals, input is about 
behavior.

The way I see it currently:

1. InputMaps would make sense to live at Control level; the user can 
examine it, and make overrides.  The API is limited enough to allow 
these InputMaps to be deduplicated.  I would not expose it as a 
MapProperty or anything like that, or offer any kind of 
bindings/listeners/interceptors, certainly not initially.

2. Behaviors provide standard mappings.  Going through BehaviorContext 
leaves all options open for Control to incorporate this into a "final" 
input map, taking user overrides into account.  By not allowing direct 
modification of the input map by Behaviors, Controls can also ensure 
they know exactly what the Behavior did so it can be cleanly uninstalled.

3. Event handler priority (or my alternative) should not be up to 
Behaviors. A Behavior always has the lowest priority, so that a user 
handler can always override what a behavior is doing. BehaviorContext 
indirection enforces this, as the Control decides how to "install" those 
handlers, and well behaved controls will install them with the lowest 
priority, and in such a way that they never conflict with what the user 
wants to do.

4. About event handler priorities as API: There is no real point in 
allowing users to install event handlers with a lower priority than 
Behavior handlers, as such handlers would have no use cases (they either 
always work or never work depending on the behavior, if you want it to 
always work, just install it at a higher priority, if you want it to 
never work... well, don't install it).  This is also the reason why I 
prefer not using handler priorities as part of the API -- I think it 
just complicates things for the user, and there is no compelling reason 
for it to ever work other than "Users first, Behaviors last".

Using handler priorities to solve "internal" ordering of handlers is not 
needed (we've lived without it so far); internal problems can be solved 
by installing handlers in the correct order, or not installing two 
different handlers for the same event in the first place.

--John
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20231128/554e37fb/attachment-0001.htm>


More information about the openjfx-dev mailing list