Public Behavior API proposal

John Hendrikx john.hendrikx at gmail.com
Tue Nov 7 11:37:12 UTC 2023


Hi Michael,

Thanks a lot for this early feedback.

I think in light of it I will rework the proposal(s) to include semantic 
events right from the beginning.

More inline comments below.

--John

On 07/11/2023 08:09, Michael Strauß wrote:
> Hi John,
>
> I like that you clearly define the terms Control, Skin and Behavior,
> as well as their roles within the control architecture.
>
> However, I don't see how your proposal scales to non-trivial controls,
> and I agree with Andy that the Button example doesn't help (because a
> Button lacks substructure and provides only a single interaction).

I didn't go into too great detail due to some time constraints, but the 
API should scale to any kind of control as there is no requirement to 
make all interactions available immediately, and any interactions you do 
want to make available that don't bubble up to Control level 
automatically can be generated by the Skin with non-public events, or by 
leaving events to bubble up that can be distinguished by a tag on the 
source; the SpinnerSkin could have ActionEvents bubble up (although it 
doesn't work that way, and I wouldn't recommend using a public event 
type for this anyway).

> I'm missing your previous idea of using the event system for
> higher-level semantic events, because I think they're required to make
> this work. Here's how I see these parts working together:

I've attempted to keep the proposal small, and I had the impression 
there was some resistance against the semantic event idea, so I've left 
that part out for now, although hopefully left enough room to apply it 
later still.

> 1) A control is an opaque node in the scene graph, which defines the
> API for a particular interactive element. It also defines the
> interactions afforded by its implementation. For example, a Spinner
> will usually consist of a text field and two buttons, but a skin might
> choose to implement these components differently. The interactions
> afforded by a control are exposed as semantic events:
>
>      class SpinnerEvent extends Event {
>          static EventType<SpinnerEvent> COMMIT_TEXT;
>          static EventType<SpinnerEvent> START_INCREMENT;
>          static EventType<SpinnerEvent> STOP_INCREMENT;
>          static EventType<SpinnerEvent> START_DECREMENT;
>          static EventType<SpinnerEvent> STOP_DECREMENT;
>      }

I didn't examine the Spinner before, but I think these would be 
excellent events for a Spinner control.

> 2) Skins are responsible for generating semantic events, and sending
> those events to the control. Since we don't need those events to have
> a tunneling/bubbling behavior, we could have a flag on the event that
> indicates a "direct event", one that is dispatched directly to its
> target.
Performance tests could show if this is truly needed, many events are 
fired already to get to this point, and firing another should not be 
prohibitive. There may also be an option to use a different root instead 
(instead of Scene as the root, Control is used as root, so dispatching 
and bubbling is limited to the Control and its substructure; we should 
consider this carefully to be absolutely sure these events have no value 
outside the Control and its substructure).
> 3) Behaviors listen for semantic events on the control, and convert
> these events into state changes of the control. This part would
> probably be quite similar to some of the things that have already been
> proposed.

Yeah, the novelty here is that the Behavior would be also the one 
responding to the semantic events, and the Behavior would then be 
calling regular methods directly on the Control again (in my earlier 
idea, the Behavior also sent out semantic events and the control was the 
one responding to them).

For comparison, my earlier idea had:

1. Controls define semantic events
2. Controls respond to their own semantic events (by calling its own 
public methods, like `fire` or `increment(int)`)
3. Behaviors take standard events (KeyEvent, MouseEvent) and translates 
them to semantic events

With your adjustment this becomes:

1. Controls define semantic events

2. Behaviors install event handlers on the control

They listen for standard events (KeyEvent, MouseEvent) and translate 
them to semantic events. This indirection can be exposed as a key 
mapping system, where semantic events are the "Functions".

They also listen to semantic events (generated by Skins, or generated by 
itself) and modify control state accordingly (by calling public methods 
like `fire` or `increment(int)`).

3. Skins handle the visuals and are allowed to generate semantic events

Skins are not allowed to have event handlers at the control level, nor 
are they allowed to manipulate the control beyond installing listeners.  
For Behaviors I'm enforcing such restrictions by using the 
`BehaviorContext` during installing, but Skins being an older design 
lack such enforcement for now.  Skins are still alllowed to do anything 
they want with their nested controls (including manipulating them and 
installing event handlers).


> In this way, controls, skins, and behaviors would end up as loosely
> coupled parts. In particular, I don't see the value in making
> behaviors public API if they are so tightly coupled to skins that they
> end up as being basically implementation details.
>
> Andy:
>> Imagine a specific skin that has a Node that accepts a user input.  A scroll bar, a button, or a region with a some function.  Unless this element is proclaimed as must-have for any skin and codified via some new public API (MySkin.getSomeElement()), it is specific to that particular skin and that particular behavior.
> I think that's a very important observation. A skin can't just be
> anything it wants to be, it must be suitable for its control. So we
> need a place where we define the API and the interactions afforded by
> that control. In my opinion, this place is the Control. Its
> functionality is exposed via properties and methods, and its
> interactions are specified using semantic events.
Yeah, I think we're certainly in agreement here.
> Now skins are free to be implemented in any imaginable way, provided
> that they interact with the control using semantic events. This gives
> us very straightforward restrictions:
> * A skin can never add interactions that the control didn't specify.
> * If additional interactions are required, the control must be
> subclassed and the interactions must be specified by the control.
> Additionally, the behavior must be extended to account for the
> additional interactions.

Something I noticed is that there are Skins that have their own 
styleable CSS properties (TextInputControlSkin provides for example 
`-fx-text-fill`, `-fx-prompt-text-fill`, `-fx-display-caret`), but they 
are presented as properties of TextInputControl (parent class of 
TextArea and TextField) in the CSS documentation.  The CSS system allows 
for this, and will find these properties as if the belong to the 
control.  I have the feeling this shouldn't be done in this manner, 
especially when they're presented as being part of TextInputControl, 
when as soon as you reskin it those properties are lost.

However, I don't think that's counter to what you said above, but it is 
a bit surprising.

--John

>
>
>
> On Mon, Nov 6, 2023 at 4:50 AM John Hendrikx <john.hendrikx at gmail.com> wrote:
>> As promised,  a public Behavior API proposal.
>>
>> Summary:
>>
>> Introduce a new Behavior interface that can be set on a control to replace its current behavior. The new behavior can be fully custom or composed (or later subclassed) from a default behavior. Some default behaviors will be provided as part of this proposal, but not all.
>>
>> See here: https://gist.github.com/hjohn/293f3b0ec98562547d49832a2ce56fe7
>>
>> --John


More information about the openjfx-dev mailing list