> If we want InputMaps

I think InputMap provides substantial value.  I also agree about addressing the 
Skin/Behavior split.  In fact, I see InputMap as an entity that ties the three 
parts together:

- Control provides a façade
- Skin provides the visual representation
- Behavior encapsulates event handlers
- InputMap maintains the pointers to the event handlers and resolves the 
priority issues between application and skin event handlers.

I've been trying to advance an InputMap implementation [0], which I think helps 
application, skin, and custom component developers alike, while also solving 
the priority issue and opening up the behavior customization a bit.

There are two POC - one is being incubated as a part of the rich text area in 
incubator.input module, the other is a partial implementation using a subset of 
somewhat complicated controls [1].

We went through a series of prolonged discussions.  Among the sticky points 
were:

- desire to have static behaviors, for the purpose of allowing the application 
to customize the behavior or all the controls of the given type I guess
- general dislike of having behavior classes inherit from a BaseBehavior 
convenience class

I am sure there were more, so I think we should continue.  I would like to see 
InputMap public API because it adds tremendous value without much cost.

What do you think?

-andy


References

[0] 
https://github.com/andy-goryachev-oracle/Test/blob/main/doc/InputMap/InputMapV3.md

[1] https://github.com/openjdk/jfx/pull/1495



From: John Hendrikx <[email protected]>
Date: Tuesday, January 13, 2026 at 13:44
To: Andy Goryachev <[email protected]>, Martin Fox 
<[email protected]>
Cc: OpenJFX <[email protected]>
Subject: Re: [External] : Re: Default event handlers


Hi,

The InputMap is a user of the event system.  It is basically a configurable 
event handler for a limited number of event types.  You could write one right 
now, expose it, and configure mappings, and in fact, to save on event handlers, 
this is exactly how many handlers already work.  Introducing it as an integral 
part of say Control immediately raises concerns; is InputMap going to be yet 
another event handler that users will have to fight when they install their 
own?  At most I see this as part of a public Behavior API, where the Behavior 
can install (default) handlers, that then delegate to an accessible input map 
for that Behavior.  Swing doesn't have a Control/Behavior/Skin split, but FX 
does, and I think we should remain committed to that instead of making some 
Swing/QT/Compose Frankenstein hybrid here by copying features without thinking 
about the bigger picture and how they really fit into FX.

Furthermore, the current private InputMap implementation is, to put it midly, 
insanely inefficient, and so I find it incredibly hard to justify using even 
parts of that design. Everything in it is Observable, but nobody will be able 
to monitor it all for changes as that would entail installing hundreds of 
handlers for the average TextField input map.  What's also inefficient is that 
this input map gets recreated for each control, leading to several kilobytes of 
overhead for every TextField in your application. There is just no reason to 
duplicate input maps like this as they're all going to be same 99.999% of the 
time.

If we want InputMaps, then I think we need to solve the Skin/Behavior split 
first -- this alone will be a huge win for FX, as reskinning then becomes 
trivial, finally fulfilling the promise that FX has made for easily reskinnable 
controls, as you no longer lose the default platform behavior.  Then with 
Behaviors also being replaceable, one can much easier allow for custom 
behaviors that expose fancy things like a fully customizable InputMap (although 
please, let's not make it observable, and let's de-duplicate them).

--John

On 13/01/2026 18:35, Andy Goryachev wrote:
I agree with Martin that this issue is mostly limited to Controls because of 
the skins.  What was the main objection to the InputMap idea I proposed?

I do want to ask Martin one thing though: what do you mean by "The only obvious 
gap in the public API is that there’s no way for a handler or filter to 
communicate with the dispatcher that invoked it."  Can you give an example?

Thanks!

-andy


From: openjfx-dev 
<[email protected]><mailto:[email protected]> on behalf 
of Martin Fox <[email protected]><mailto:[email protected]>
Date: Tuesday, January 13, 2026 at 09:00
To: John Hendrikx <[email protected]><mailto:[email protected]>
Cc: OpenJFX <[email protected]><mailto:[email protected]>
Subject: Re: [External] : Re: Default event handlers

I didn’t intend to re-open all of these debates. I just wanted to point out 
that JavaFX in general uses dispatchers to process events so there’s no 
existing concept of default handlers outside of Control. If Control had 
implemented InputMap using a dispatcher we probably wouldn’t be having this 
conversation.

I still believe this is a local problem for Control and it can craft its own 
solution. It doesn’t even have to involve handlers; look at how Scene and Menu 
handle accelerators. Whatever Control wants to do almost all of the tools are 
there. The only obvious gap in the public API is that there’s no way for a 
handler or filter to communicate with the dispatcher that invoked it. Rather 
than add a specialized bit like this PR does I thought it might be worth 
considering a more generalized solution (I can think of a few) but I’m actually 
fine with preventDefault() since it’s based on an existing standard.

Martin

On Jan 13, 2026, at 4:52 AM, John Hendrikx 
<[email protected]><mailto:[email protected]> wrote:



On 13/01/2026 00:43, Andy Goryachev wrote:

The reason I mentioned #2 is that it is somewhat relevant to the discussion, as 
in "why do we need to write custom dispatchers at all?"  There should be only 
two methods, in my opinion, one that dispatches an event that bubbles up (with 
filters and handlers), and one that sends an event to a single target Node and 
nothing else.  <rant>Somehow, Swing got the Events right - it manages to 
dispatch one (1) event in total, and the dispatching stops once the event is 
consumed.  The FX decided it needed to reinvent the wheel and leave multiple 
booby traps in the process.</rant>

Although I agree that how FX solved events is sub-optimal, there is a real need 
here to communicate to the EventHandler on which object it resides.  
EventHandler instances are expensive when you need to attach one to every Cell 
in a TableView, and so to re-use a single instance, you need to know which Cell 
the event applies to.  The source field (which is supposed to be constant) has 
been abused for this, making events non-constant requiring cloning before they 
can be dispatched to their final target.  This cloning then caused the 
"isConsumed" problem.  Perhaps we should just make the source field mutable as 
well, so the cloning isn't needed.

The solution to this problem at the time should not have been to modify events, 
but to have made event handlers be BiConsumers, with the Event **and** Node 
being passed to the callback (and a "convenience" method that delegates to the 
BiConsumer variant that accepts only Consumer<Event> -- we may be able to still 
do this...)

This isn't exactly rocket science, we should be able to figure something out.  
Maybe there is another option that will satisfy everyone?

I think the issue isn't so much in event dispatching, but in the Skin/Behavior 
system itself.  Skin/Behaviors in FX is like giving root access to every user 
on your system.  Sure it is convenient that everyone can do whatever they want, 
and as long as everyone behaves, everything works great.  However one malicious 
user can interfere with others or leave behind hooks that later come to bite 
you.

Controls are HOSTS for Skins and Behaviors.  Skins and Behaviors are clients.  
They should be restricted to a very specific subset of functionality that 
benefits the host and is predictable for the host:

- Skins get ownership of the children list of the Control; while a Skin is 
installed, the host should not be allowed to make modifications
- Skins can monitor properties for changes but this should never lead to a 
direct observable change on the main control that a subsequent installed 
listener may observe; in other words, listener order should be irrelevant for 
what the Skin does in order to share the listener infrastructure without 
interference.  Skins are free to directly take action on the children (which 
they own exclusively), just not on the main control; such actions should 
instead be deferred, usually by requesting a layout (this is usually already 
the case, but it is good to make this explicit so we can decide what a Skin is 
doing is a bug or not).
- Behaviors can react to events at the lowest precedence, and exclusively only 
take action when receiving an event; this means that blocking all events will 
automatically mean the Behavior no longer does anything, but also that 
selectively blocking events allows some control over Behaviors
- Behaviors can co-modify properties on the Control, but this should be clearly 
documented; controls are free to restrict this set (ie. a Behavior has no 
business modifying the "wrapText" property, or things like layout properties -- 
most often they do their work through pseudo class changes and modifying the 
value a control represents).

That should really be all that is needed for a functioning Skin/Behavior 
system; no need for root access.

Of course, root access to the Control is a ship that has sailed a long time 
ago; but that doesn't mean we can't introduce a client API for Skins/Behaviors. 
 All that really takes is passing an object to the Skin/Behavior when it is 
installed. This object is an interface with which the Skin/Behavior can do 
their work. Should they choose to not circumvent this API, and do all their 
work through this API, they can remove all their clean-up code, as the Control 
can now do this automatically.  This will greatly simplify skins, and remove a 
whole avenue of potential bugs.

All work done through this API can be monitored by the Control. The control can:
- Track what is installed (for later clean-up)
- Reject installation of listeners/handlers it doesn't want to expose
- Ensure that event handlers are installed at lowest precedence.  This can be 
kept internal, so many solutions are possible: separate lists, default event 
handlers (internal API), priorities, etc.

Everything you'd expect a host Control to be able to do, including forcefully 
removing all traces of a previously installed Skin, and disallowing it further 
access should it attempt to use the API again after a new Skin is installed. 
That's however not a requirement; all we'd need is that interface, and 
encourage Skins/Behaviors to use it.  Correctly behaved Skins/Behaviors then 
get all the benefits, and will stop interfering with user code.  This means of 
course modifications to existing skins, but it is mostly in their registration 
logic (which I think we modified like 5 times already).

The minimum API needed can be fairly small, and does not need to include 
accessors for every property and handler with some smart signatures.  For 
example:

    <T, P extends ReadOnlyProperty<T>> void addListener(Function<C, P> 
supplier, Consumer<T> subscriber)

Allows installation of a listener by doing:

    api.addListener(Slider::minProperty, v -> { ... });

In this way we can isolate and track what Skins/Behaviors are doing, ensure 
they don't interfere with user operations on the Control and also ensure 
guaranteed clean-up (if they refrain from accessing the Control directly).

--John


Reply via email to