I've run into some problems while trying to make event handlers on
skins only act when the event isn't consumed by user code. Consider a
button that is nested in some hierarchy:

scene
--> root
----> button

When the button receives a MOUSE_RELEASED event, it consumes the event
and fires off an ACTION event. Doing that in a default handler is
fine, but as as consequence, the MOUSE_RELEASED event won't be
immediately consumed. Instead, it bubbles up the entire hierarchy
before it is finally handled and consumed by the button in its default
handler after the dispatch chain has completed. This is clearly not
what we want, as it can potentially cause ancestors to act on the
event, too. (By the way, this also rules out an event system where
events are dispatched in prioritized capture/bubble phases.)

I think what we need is quite a bit simpler. Default event handlers
are still the way to go, but they shouldn't act across the entire
dispatch chain. Instead, they should only act locally on a single
event target: when a target receives an event, it first invokes its
regular handlers. Then, if the event is still eligible for default
handling, the default handlers are invoked. Skins and behaviors should
always use default handlers, never regular handlers.

In the example, the button would consume the MOUSE_RELEASED event in
its default handler, which will prevent it from bubbling up the
hierarchy. If user code adds an event handler to the button, the user
handler will always be invoked first and gets to decide whether to
consume the event (Event.consume()), prevent the default handler from
running (Event.preventDefault()), or let it continue to flow.

Using this simplified model, I've been able to switch over InputMap
and ListenerHelper (and therefore almost all controls) to use default
handling, and it seems to work pretty well.

Here is a PR with the implementation: https://github.com/openjdk/jfx/pull/2022

Reply via email to