<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<p>I'm working on a new version of the behavior proposal after some
fruitful discussions here on this list, and a meeting with Andy.</p>
<p>In this meeting a few problems I was unable to address came to
light, and which I've now addressed in this post:</p>
<p>1) TextAreaBehavior is relying heavily on internal functions
provided by the corresponding Skin. These are all related to how
Text is visually laid out and actions that you can take that would
need this layout information of which the Control is unaware; the
simplest example is pressing the "HOME" or "END" key which needs
to move to the start or end of current line depending on its
visual bounds.</p>
<p>2) Having Behaviors as a separate concept didn't seem all that
useful.<br>
<br>
3) The potential for having a HUGE number of semantic events in
Skin -> Behavior communication<br>
</p>
<p>TextAreaBehavior <br>
==============</p>
<p>I think its fair to say that this is definitely one of the more
unique behaviors in JavaFX. Together with its Skin it is doing
things that I don't think any of the other skins do. The Skin for
example provides styleable CSS properties, and provides a lot of
call backs for the behavior. This latter part I've not seen in
any other skins (I did a quick search). I didn't investigate if
any other Skins are also providing CSS stylable properties.</p>
<p>Now I think this can mean that perhaps the TextArea control is
lacking some functions that it should be providing. TextArea is
aware that its content will be wrapped and split into lines, that
it has a view port, and provides ways to move the caret. What it
doesn't provide is more precise caret control. It doesn't seem
like a stretch to also provide for functions that can navigate the
caret to the start or end of the current line, or to the start or
end of the current visible page, etc.</p>
<p>Lacking that, we could also accept that perhaps a Behavior should
have some more awareness of a related Skin. In MVC, the
Controller (Behavior) has a reference to the View (Skin) and also
has some knowledge of how the View operates. A solution here can
be to have Skins with very specific needs implement an interface.
The Behavior can then be attuned to that interface, which would
allow a new Skin to be constructed which reuses a lot of an
existing Behavior without the requirement to subclass a specific
Skin. Note that the Behavior can fairly easily access the Skin
already through Control. A simple instanceof check will suffice
to see if it can expand its behavior to support visual operations.</p>
<p> void moveCaretToEndOfLine(TextArea control) { // called in
response to a KeyEvent<br>
if (control.getSkinnable() instanceof
VisuallyCaretAwareInterface x) {<br>
x.moveCaret(...);<br>
}<br>
// do nothing, wanted behavior not supported by Skin<br>
}<br>
<br>
Usefulness of Behavior Concept<br>
==========================<br>
</p>
<p>The point was made that creating a new Behavior is still very
limited in what it allows the user to change how a control
"feels". This is because the Skin does a lot of filtering when it
is passing things to the associated behavior. For example, the
SpinnerSkin will call the behavior when the spinner buttons are
pressed (with any mouse button(!!!)), but the Behavior can't limit
this to just the left mouse button (like Spinners on Windows do).
Other Skins will only call the Behavior for specific mouse
buttons, or for specific events. In effect, the Skin is already
dictating part of the behavior (either by omission or by
generalization) which should be part of the Behavior
impementation.</p>
<p>For example, if I want to create a SpinnerBehavior that would
react to the scroll wheel when it is hovering above the Spinner's
text field, I'm out of luck; the Skin is not calling the Behavior
for SCROLL events.<br>
</p>
<p>So perhaps we can leverage something here that is already public
knowledge: the Substructure of Controls. Spinner for example has
the substructure:</p>
<ul style="color: rgb(0, 0, 0); font-family: Helvetica, Arial,
sans-serif; font-size: 14px; font-style: normal;
font-variant-ligatures: normal; font-variant-caps: normal;
font-weight: 400; letter-spacing: normal; orphans: 2; text-align:
start; text-indent: 0px; text-transform: none; widows: 2;
word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space:
normal; text-decoration-thickness: initial; text-decoration-style:
initial; text-decoration-color: initial;">
<li>text-field — TextField</li>
<li>increment-arrow-button — StackPane
<ul>
<li>increment-arrow — Region</li>
</ul>
</li>
<li>decrement-arrow-button — StackPane
<ul>
<li>decrement-arrow — Region</li>
</ul>
</li>
</ul>
<p>The names here are part of the CSS public API and indicate
interesting parts of the spinner control.</p>
<p>What if we completely forbid the Skin from installing event
handlers, even on its children (or at least the ones that are part
of the substructure), and let those events bubble up for Behavior
to catch? An immediate problem would be how the Behavior would
know whether I pressed the mouse button on the UP or DOWN button,
or if I pressed the button on the Text Field portion. We could
look at the event's target, but even though you can see which
specific Node was targetted, the Behavior doesn't know what that
node means. So, instead we could ask the Skin this (or
alternatively, put this in a fixed Key on the Node in its
mappings, or use the CSS id field for this purpose):</p>
<p> interface Skin<T> {<br>
default String
determineSubstructureElement(Event<?> event) { return null;
}<br>
}</p>
The Skin will reply here with the publically known style class name
that it assigns to its substructure elements, or `null` if it
doesn't support this feature or if the Event passed in doesn't match
to any (public) substructure element or if the Event just didn't
belong to this Skins substructure at all.
<p>The Behavior can then use this "name" or "annotation" as an extra
piece of information to determine the desired action:</p>
<p> void mousePressed(MouseEvent event, Spinner control) {<br>
if (event.getButton() == MouseButton.PRIMARY) {<br>
control.requestFocus();<br>
<br>
switch
(control.getSkinnable().determineSubstructureElement(event)) {<br>
case "increment-arrow-button" ->
startSpinningUp();<br>
case "increment-arrow" ->
startSpinningUp();<br>
case "decrement-arrow-button" ->
startSpinningDown();<br>
case "decrement-arrow" ->
startSpinningDown();<br>
}<br>
}<br>
}<br>
</p>
<p>In this way Behaviors can support many more things than the Skin
initially thought of providing. You could have the Spinner
buttons only react on LMB, have the text field respond to SCROLL
events, have the spinner buttons react differently on long or
short presses, on drags or gestures, etc...</p>
<p>Note, the above suggested API fpr determineSubstructureElement
returns a String with the element name; it could also be a simple
object that is aware of the nested substructure, so that it can be
queried "did this Event involve the 'increment-arrow-button' or
any of its children?" more easily.<br>
</p>
<p>Semantic Events<br>
==============<br>
</p>
<p>Note that the above change sort of eliminates the need for
Semantic Events between Skins and Behavior, which I think will
severely cut down on how many Semantic Events will be needed. As
the events are now limited to specifying the communication needed
between Behavior and Control (or only an indirection between
Behavior and itself), they can be more directly related to state
changes. Instead of having 4 events for
START/STOP_SPINNING_UP/DOWN, we could just have a
SPINNER_VALUE_CHANGE event which is parameterized.</p>
<p>The value in still having the semantic events lies in:</p>
<p>- Being able to indirect several low-level sets of events to a
single high-level action, allowing the high-level function to be
remapped or accessed directly<br>
- Being able to intercept and block events using the normal event
system before they're acted upon, or even to generate a high level
event directly</p>
<p>Who does what?<br>
================</p>
<p>This changed a little bit from my earlier definitions, but I'll
adjust them here:</p>
# Controls (no changes)<br>
<p>- Controls refer to a Skin and Behavior<br>
- Controls define semantic events<br>
- Controls can generate semantic events (via mappings)<br>
- Controls never modify their own (user writable) state on their
own accord (the user is in full control)<br>
- Controls provide an override based key mapping system</p>
<p># Skins (stop acting on events)<br>
<br>
- Skins refer to a Control (legacy inheritance) but are limited in
their allowed interactions (unwritten rule since the beginning)<br>
- Skins never interprete any events, not even on their children,
with perhaps the one exception if the handling of the event only
changes Skin internal state<br>
- Skins never act upon semantic events<br>
- Skins provide information about their substructure, either on
the Node or when asked<br>
</p>
<p># Behaviors (also generates semantic events, and reacts on more
events)<br>
</p>
<p>- Behaviors refer to nothing<br>
- Control (and indirectly Skin) reference is received via
event handler and listeners only<br>
- Skins can be checked to see if they support special behavior
via an Interface<br>
- Behaviors define base key mappings<br>
- Controls refer to these, after first checking for any user
overrides<br>
- Base key mappings can be global and immutable, user
overrides (maintained in Control) are mutable<br>
- Behaviors generate semantic events in response to (combinations
of) low-level events (or via timers)<br>
- Behaviors can act upon events of any type (if unconsumed by the
user) that are targetted at (or bubble up to) the control
(enforced as that's the only place they can install handlers)<br>
- Behaviors are allowed to modify control state and their own
state</p>
<p>The above definitions seperate the Look & Feel part even
better than my earlier proposal, and makes Behaviors far more
powerful than before at only a slight cost to Skins. Skins now
truly only provide visuals, and never interprete events, while
Behaviors have more options to determine what events they are
interested in and how to respond to them.</p>
<p>The balance between the amount of code in Skins and Behaviors
will shift a bit. More event handling will move to Behaviors
(where it actually belongs), which may go a long way to also
making Skins easier to subclass or reimplement.<br>
</p>
<p>--John<br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
</body>
</html>