<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Andy,</p>
<p>As you're not responding to any of the suggestions or any of my
questions, but are only re-iterating points that I believe are not
going to be a benefit to the long term viability of FX, I see no
point in continuing the discussion further.</p>
<p>--John</p>
<div class="moz-cite-prefix">On 18/09/2024 01:09, Andy Goryachev
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:BL3PR10MB6185A510A3E46670575B4FBBE5612@BL3PR10MB6185.namprd10.prod.outlook.com">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="Generator"
content="Microsoft Word 15 (filtered medium)">
<style>@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}@font-face
{font-family:"Yu Gothic";
panose-1:2 11 4 0 0 0 0 0 0 0;}@font-face
{font-family:Aptos;
panose-1:2 11 0 4 2 2 2 2 2 4;}@font-face
{font-family:"Iosevka Fixed SS16";
panose-1:2 0 5 9 3 0 0 0 0 4;}@font-face
{font-family:"Times New Roman \(Body CS\)";
panose-1:2 11 6 4 2 2 2 2 2 4;}@font-face
{font-family:"\@Yu Gothic";
panose-1:2 11 4 0 0 0 0 0 0 0;}p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0in;
font-size:12.0pt;
font-family:"Aptos",sans-serif;}a:link, span.MsoHyperlink
{mso-style-priority:99;
color:#467886;
text-decoration:underline;}p.p1, li.p1, div.p1
{mso-style-name:p1;
mso-margin-top-alt:auto;
margin-right:0in;
mso-margin-bottom-alt:auto;
margin-left:0in;
font-size:12.0pt;
font-family:"Aptos",sans-serif;}span.outlook-search-highlight
{mso-style-name:outlook-search-highlight;}span.EmailStyle23
{mso-style-type:personal-reply;
font-family:"Iosevka Fixed SS16";
color:windowtext;}.MsoChpDefault
{mso-style-type:export-only;
font-size:10.0pt;
mso-ligatures:none;}div.WordSection1
{page:WordSection1;}</style>
<div class="WordSection1">
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Dear
John:<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">You
do bring a lot of good points, no doubt. And I do agree
with a lot of the suggestion, but I still want to emphasize
two points:<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">1.
The backward compatibility should not be dismissed that
easily. There is a number of existing applications out
there and we do not want to break them. Whether the
behavior is specified or not is irrelevant, we do not want
to cause mayhem from the customers and developers alike
whose keyboard navigation suddenly changed.<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">2. I
question the cost benefit analysis of the redesign idea.
While I agree with you that it might help with some unusual
cases, the overall benefit is rather limited. The benefit
of the proposed solution is, in my opinion, far greater: it
allows for custom traversal policies (a feature that has
been requested multiple times) and enables focus traversal
from custom components, something of a lesser value, but
still important. Exposing the existing APIs is a relatively
cheap solution that will give us two features at nearly zero
cost. On the other hand, I doubt that our team, or
yourself, are willing commit substantial development effort
to redesign the thing to use events. Which brings me to the
choice I mentioned earlier: realistically, we have a choice
of providing two requested features soon, or never.<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">I
would also encourage other members of the development
community to voice their opinion on the subject, perhaps
there is something else we can do to move forward.<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank
you
<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<div id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal" style="margin-bottom:12.0pt"><b><span
style="color:black">From:
</span></b><span style="color:black">John Hendrikx
<a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a><br>
<b>Date: </b>Saturday, September 14, 2024 at 09:41<br>
<b>To: </b>Andy Goryachev
<a class="moz-txt-link-rfc2396E" href="mailto:andy.goryachev@oracle.com"><andy.goryachev@oracle.com></a>,
<a class="moz-txt-link-abbreviated" href="mailto:openjfx-dev@openjdk.org">openjfx-dev@openjdk.org</a>
<a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
<b>Subject: </b>[External] : Re: Proposal: Focus
Traversal API<o:p></o:p></span></p>
</div>
<p>Hi Andy,<o:p></o:p></p>
<p>First let me say that when it comes to designing an
API, you really need to take the time to think the
solution through. The current internal solution was
probably kept internal for exactly that reason,
insufficient time to work out the kinks and look into
alternatives.<o:p></o:p></p>
<p>An API is almost impossible to change later, so the
general rule is that if you're not sure about an API,
then its better to have no API. This is why I think it
is important that we first look for what the API should
look like, then worry about how this can be fitted onto
JavaFX. Making concessions related to the current
implementation before having a clear idea of how the API
should preferably work is not part of that. You start
making concessions only when it turns out the preferred
design would encounter unresolvable problems in the
current implementation.<o:p></o:p></p>
<p>Since I think there is very little public API related
to focus traversal, nor is there any specification of
how it currently works, I think we have a lot of room to
maneuver. This is why I think we should first reach a
consensus on the API, then look how it can be fitted on
top of FX. Sometimes a well thought out API also is a
natural fit, and may be easier to migrate to than you
think.<o:p></o:p></p>
<div>
<p class="MsoNormal">On 14/09/2024 00:17, Andy Goryachev
wrote:<o:p></o:p></p>
</div>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Dear
John, Everyone:</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank
you for a thoughtful response! Some of the ideas
you described definitely deserve further
consideration. If I were to summarize:</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">1.
move the focus traversal logic away from the
components and into the Scene</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">2.
re-implement focus traversal through
TraversalEvents rather than responding directly to
KeyEvents</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">3.
(more) standard policies</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">4.
using CSS</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">(there
is of course more topics in your response, but let
me start with the 4 above)</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#1
</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">I
generally like this idea. In some sense it is
already how things work internally, but without
the ability to customize that (i.e. by introducing
custom traversal keys, or removing existing
ones). The downside is substantial: not only we'd
need to re-design the whole of the focus
traversal, but also rework the existing control's
behaviors. Did I mention the risk of regression,
given the absence of comprehensive behavioral
tests?</span><o:p></o:p></p>
</div>
</blockquote>
<p>There's two things here.<o:p></o:p></p>
<p>1. There is no need to re-design the whole focus
traversal. The old internal system can be gradually
replaced (it works by directly consuming KeyEvents after
all).<o:p></o:p></p>
<p>2. Regression. When nothing is specified, and the fact
that controls **ought** to work like other common
controls in different UI toolkits, is it a regression
when focus traversal works the same as those other
platforms, even if it may be a regression from the point
of view of FX? For example, a Spinner will currently
react to any mouse key, where as other common toolkits
only react to the left mouse button. Is it a regression
if FX is adjusted to also only react to the left mouse
button? It's not specified anywhere.<o:p></o:p></p>
<p>I think we have sufficient space to maneuver here as
long as we are not making focus traversal completely
different from how it commonly works in UI's.<o:p></o:p></p>
<p class="MsoNormal">Can there be regressions versus the
current (unspecified) implementation? Sure, there can
be. Is that necessarily bad? That depends. If the new
focus traversal works like it does on all other
toolkits, then no, it is more of a bug fix. Did we
break something with the new implementation? That's
always possible, but will then be fixed as soon as it is
reported.<br>
<br>
<o:p></o:p></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#2</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">This
may or may not be an integral part of #1.
Potentially, it allows for injection of events by
the application code, as well as simplifies
creation of complex custom controls. The latter
becomes possible with the original proposal, so
net benefit is limited to the first part, I think.</span><o:p></o:p></p>
</div>
</blockquote>
<p class="MsoNormal">I think TraversalEvents are quite
central to making this an API that will really stand the
test of time. It leverages the existing event system,
giving you all the power that comes with it. You did
not answer my question about the TraversalEvents in your
design. Why are the Events when they can't be
triggered, filtered or consumed?<br>
<br>
<o:p></o:p></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#3</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">One
obvious possibility is to enable creation of a
simple policy based on a list of Nodes. I must
mention one use case that is impossible to cover
with pre-defined policy is one where navigation
depends on some state. Such a policy must be
implemented programmatically. I think one
property should be sufficient - I am strongly
against adding two properties here.</span><o:p></o:p></p>
</div>
</blockquote>
<p>Programmatic escapes can always be achieved by
responding directly to a TraversalEvent. I think
however this should be a rare case, and standard
policies should really cover almost all use cases. It
may be a gap that should be investigated, and the API
adjusted for (usually the "exceptions" are well worth
looking into to see if with a tweak they can't become
"standard"). As for being "strongly against" having two
properties -- that's an odd stance to take without
motivating it. It could also be rolled into "one" where
the Policy is a record with the two values, but I think
we're getting ahead of ourselves here. First the API,
then the implementation.<o:p></o:p></p>
<p>I do think however there is great value in having the
Logical and Directional navigation split. Often you'll
only want to replace one of these with a custom policy
(or a different standard policy), so that the other
navigation method can be used to escape the control.
For example, a Toolbar could be tabbed in an out of
(using Logical navigation) while the Directional
navigation is cyclic (and thus can't be used to escape
the control's context).<o:p></o:p></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#4</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">The
idea of using CSS to specify traversal policy
seems wrong to me: the CSS defines the
presentation aspects (styles) rather than
behavioral ones. I know it is possible to set
custom skins and the corresponding behavior via
CSS, and we know why (skins define the
appearance), but we should not go beyond that, in
my opinion.</span><o:p></o:p></p>
</div>
</blockquote>
<p>I see no problem styling such properties. They're FX
properties, and it would be very convenient to style
them to globally alter how focus works, instead of
having to rely on, say, Builders or Factories for
controls where traversal policies can be applied. There
are also already properties that don't only influence
the look of controls. "-fx-skin" being the most obvious
one, but there is also "-fx-focus-traversable",
"-fx-context-menu-enabled", "-fx-block-increment",
"-fx-unit-increment", "-fx-pannable",
"-fx-initial-delay", "-fx-repeat-delay",
"-fx-collapsible", "-fx-show-delay",
"-fx-show-duration", "-fx-hide-delay", and probably
more. Aside from "-fx-skin" none of these properties
have a visual impact, but instead alter behavior.<o:p></o:p></p>
<p>Note: I'm not saying this needs to be there
immediately. I just want to make sure we're not closing
off this direction, as again, it would be a huge hassle
to do this programmatically. In "code" the only things
I usually do on my controls are the following:<o:p></o:p></p>
<p>- I define the container hierarchy (VBox, HBox, which
children go where)<br>
- I set a style name<br>
- I set anything that unfortunately cannot be CSS styled
(things like ALWAYS, SOMETIMES, NEVER grow policies,
Grid sizes, etc, things that are clearly "visual" but
still can't be styled).<o:p></o:p></p>
<p>All the rest I don't touch, or want to touch. Having
to select a traversal policy for every control of type X
I create is just cumbersome and unnecessary. There will
be a call then to set this "globally", and then there
will be the question, do we make something custom with
many limitations because it doesn't fit our conceptions
of what (FX) CSS is for (ie, not style, but
only *visual* style) or do we just expose these
properties as Styleable leveraging an existing powerful
system with almost zero effort?<o:p></o:p></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">--</span>
<o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">There
is one more aspect of the problem that I think we
should consider. The current proposal does not
change the implementation in any material way, nor
does it change the behavior, thus can be done
quickly. The benefit everyone gets from it is
ability to trigger focus traversal and to control
it via custom policies. Any other solution will
require resources and the bandwidth we currently
don't have, which means the
<i>probability</i> of it being added to FX is
virtually zero. Let me emphasize, I am not
against attempting to discuss or implement the
best possible solution, but we should be aware of
the limitations of the reality we live in.</span><o:p></o:p></p>
</div>
</blockquote>
<p>"Quickly" and API's are incompatible with each other.
There is nothing worse than exposing an API quickly,
which then becomes a burden on the system -- I think the
current CSS API is a prime example of where "quickly"
has gone wrong, costing us tremendous amounts of effort
to make even minor changes to it. <o:p></o:p></p>
<p>I urge you to ignore the current implementation, think
thoroughly how (in an ideal world) you would want such
an API to work (from a user perspective, not from an
implementor's perspective) and only then see how this
could be made to fit into JavaFX.<o:p></o:p></p>
<p>This is exactly what I did. I did not look at the
implementation, although I'm aware of some of it. I
looked at how I as a user of FX am building
applications, the struggles I have with it currently,
(with controls for example "eating" KeyEvents), and how
I would like to be able to adjust focus traversal. Do I
want to respond to "KeyCode.LEFT" or do I want to
respond to "TraversalEvent.LEFT"? Do I also need to
respond to "KeyCode.NUM_PAD_LEFT"? These things should
be abstracted, and preferably I should just be able to
choose from common navigation standards. And when I do
want to change such a standard, in 99% of the cases that
will be the case for all similar controls in my
application. How do I do such things currently if I
want to change something for all controls in my
application? I use CSS.<o:p></o:p></p>
<p>Also I think this can be implemented gradually. Here's
a potential plan:<o:p></o:p></p>
<p>1. Have Scene listen to unused KeyEvents and translate
them to TraversalEvents<o:p></o:p></p>
<p>Benefit: gives custom controls a way to respond to
keyboard based navigation in a platform agnostic way;
this probably already removes the biggest roadblock for
custom controls...<o:p></o:p></p>
<p>Public API: Limited to a new Event<o:p></o:p></p>
<p>2. Start converting existing controls to listen to
TraversalEvent instead of KeyEvent<br>
<br>
This hits a lot of controls, but should be relatively
easy to do, and it can be all kept internal for now. It
can be done in a few batches.<o:p></o:p></p>
<p>Benefit: for each control converted, user can now
programmatically trigger focus changes, and by
overriding things at Scene level can completely change
navigation keys<o:p></o:p></p>
<p>Public API: none<o:p></o:p></p>
<p>3. Implement a number of standard policies internally
(OPEN, CONFINED, CYCLIC, IGNORED)<o:p></o:p></p>
<p>Convert any controls that could use these as their
default, removing any custom logic if it happens to
match one of the defaults.<o:p></o:p></p>
<p>Benefit: less code to maintain and debug, gives us
experience with which policies make sense and where the
gaps are<o:p></o:p></p>
<p>Public API: none<o:p></o:p></p>
<p>Order: It is possible to do this before 2, and so some
of the control conversions could just consist of
removing their custom logic, and selecting a standard
policy.<o:p></o:p></p>
<p>4. Expose policy property/properties on Parent<o:p></o:p></p>
<p>Any controls that are not using a custom policy anymore
(of type IGNORED) can now be user adjusted. We don't
have to guarantee that each policy makes sense for each
control. Changing a default IGNORED policy to a standard
one will change the behavior (as intended) but it need
not be a "complete" behavior that users like. This is
not FX's problem, and can be improved upon later.<o:p></o:p></p>
<p>Benefit: users can now change policies on any existing
control, even ones with a custom policy; many of the
controls may support a switch between OPEN, CONFINED and
CYCLIC out of the box.<o:p></o:p></p>
<p class="MsoNormal">Public API: new properties on Parent<o:p></o:p></p>
<p>5. Perhaps expose some helpful tools to calculate the
"next" Node for a given traversal option.<br>
<br>
This can be done at any stage, and can be considered
completely separate. It is IMHO a relatively
low priority need.<br>
<br>
Benefit: less work for control implementors (although
they could just "copy" said code)<br>
<br>
Public API: Maybe some methods in Node, or some kind of
static helper.<o:p></o:p></p>
<p>6. CSS styleable properties<o:p></o:p></p>
<p>If we really want to give power to our users, and
impress them with a flexible focus traversal API, then
make these properties styleable.<o:p></o:p></p>
<p>Benefit: allow users to pick any control, and set is
policy globally or within a subset of controls (ie.
dialogs, popups, etc).<o:p></o:p></p>
<p>Public API: Nothing in Java, but document as CSS
properties<o:p></o:p></p>
<p>--John<o:p></o:p></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank
you,</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
<div id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal"
style="margin-bottom:12.0pt"><b><span
style="color:black">From:
</span></b><span style="color:black">openjfx-dev
<a
href="mailto:openjfx-dev-retn@openjdk.org" moz-do-not-send="true">
<openjfx-dev-retn@openjdk.org></a>
on behalf of John Hendrikx <a
href="mailto:john.hendrikx@gmail.com"
moz-do-not-send="true">
<john.hendrikx@gmail.com></a><br>
<b>Date: </b>Wednesday, September 11,
2024 at 19:05<br>
<b>To: </b><a
href="mailto:openjfx-dev@openjdk.org"
moz-do-not-send="true"
class="moz-txt-link-freetext">openjfx-dev@openjdk.org</a>
<a href="mailto:openjfx-dev@openjdk.org"
moz-do-not-send="true">
<openjfx-dev@openjdk.org></a><br>
<b>Subject: </b>Re: Proposal: Focus
Traversal API</span><o:p></o:p></p>
</div>
<p>Hi Andy / List,<o:p></o:p></p>
<p>I've given this some thought first, without
looking too much at the proposal.<o:p></o:p></p>
<p>In my view, focus traversal should be
implemented using events, and FX should
provide standard handling of these events
controlled with properties (potentially even
CSS stylable for easy mass changing of the
default navigation policy).<br>
<br>
## KeyEvent and TraversalEvent separation<o:p></o:p></p>
<p>I think the cleanest implementation would be
to implement a KeyEvent listener on Scene that
takes any unused KeyEvents, checks if they're
considered navigation keys, and converts these
keys to a new type of event, the
TraversalEvent. The TraversalEvent is then
fired at the original target. The
TraversalEvent is structured into Directional
and Logical sub types, and has leaf types
UP/DOWN/LEFT/RIGHT and NEXT/PREVIOUS. Scene
is the logical place to handle this as without
a Scene there is no focus owner, and so there
is no point in doing focus traversal.<o:p></o:p></p>
<p>This separation of KeyEvents into
TraversalEvents achieves the following:<o:p></o:p></p>
<p>- User can decide to act on **any** key, even
navigation keys, without the system
interfering by consuming keys early,
unexpectedly or even consuming these keys
without doing anything (sometimes keys get
consumed that don't actually change
focus...). The navigation keys have many
possible dual purposes, and robbing the user
of the opportunity to use them due to an
overzealous component interpreting them as
traversal keys is very annoying. Dual
purposes include for example cursor control in
TextField/TextArea, Scrollbars, etc. The user
should have the same control here as these FX
controls have.<br>
<br>
- Scene is interpreting the KeyEvents, and
this interpretation is now controllable. If I
want a Toolbar (or the whole application) to
react to WASD navigation keys, then installing
a KeyEvent handler at Scene level or at any
intermediate Parent level that converts WASD
to UP/LEFT/DOWN/RIGHT Traversal events would
affect this change easily.<br>
<br>
- The separation also allows to block Focus
Traversal only, without blocking the actual
Keys involved. If I want to stop a Toolbar
from reacting to LEFT/RIGHT, but I need those
keys higher up in the hierarchy, then I'm
screwed. With the separation, the key events
are unaffected, and I can block Toolbars from
reacting specifically to traversal events
only.<o:p></o:p></p>
<p>## Traversal Policy Properties on Parent<br>
<br>
I think FX should provide several policies out
of the box, based on common navigation
patterns. The goal here is to have policies
in place that cover all use cases in current
FX provided controls. This will provide a
good base that will cover probably all
realistic work loads that custom controls may
have. The goal is not to support every
esoteric form of navigation, instead an escape
hatch will be provided in the form of
disabling the standard navigation.<o:p></o:p></p>
<p>In order to achieve this, I think Parent
should get two new properties, which control
how it will react to Directional and Logical
navigation. These will have default values
that allow navigation to flow from Node to
Node within a Parent and from Parent to its
Parent when navigation options in a chosen
direction are exhausted within a Parent.
Custom controls like Combo boxes, Toolbars,
Button groups, etc, can change the default
provided by a Parent (similar to how some
controls change the mouse transparent flag
default).<o:p></o:p></p>
<p>These two properties should cover all
realistic needs, and IMHO should be considered
to be CSS stylable in the future to allow easy
changing of default policies of controls (ie.
want all Toolbars to react differently to
navigation keys, then just style the
appropriate property for all toolbars in one
go).<o:p></o:p></p>
<p>Parent will use these properties to install
an event handler that reacts to
TraversalEvents (not KeyEvents). This handler
can be fully disabled, or overridden (using
setOnTraversalEvent).<o:p></o:p></p>
<p>- logicalTraversalPolicy<br>
- directionalTraversalPolicy<o:p></o:p></p>
<p>The properties can be set with a value from a
TraversalPolicy enum. I would suggest the
following options:<o:p></o:p></p>
<p>- OPEN<o:p></o:p></p>
<p>This policy should be the default policy for
all Parents. It will act and consume a given
TraversalEvent only when there is a suitable
target within its hierarchy. If there is no
suitable target, or the target would remain
unchanged, the event is NOT consumed and left
to bubble up, allowing its parent(s) to act on
it instead.<o:p></o:p></p>
<p>- CONFINED<o:p></o:p></p>
<p>This policy consumes all TraversalEvents,
regardless of whether there is something to
navigate to or not. This policy is suitable
for controls that have some kind of
substructure that we don't want to
accidentally exit with either Directional or
Logical navigation. In most cases, you only
want to set one of the properties to CONFINED
as otherwise there would be no keyboard
supported way to exit your control. This is a
suitable policy for say button groups,
toolbars, comboboxes, etc.<o:p></o:p></p>
<p>- CYCLIC<o:p></o:p></p>
<p>Similar to CONFINED but instead of stopping
navigation at the controls logical boundaries,
the navigation wraps around to the logical
start. For example, when were positioned on
the right most button in a button group,
pressing RIGHT again would navigate to the
left most button.<br>
<br>
- IGNORED<o:p></o:p></p>
<p>This is similar to the mouseTransparent
property, and basically leaves the
TraversalEvent to bubble up. This policy
allows you to completely disable directional
and/or logical navigation for a control.
Useful if you want to install your own handler
(the escape hatch) but still want to keep
either the default directional or logical
navigation.<o:p></o:p></p>
<p>Possible other options for this enum could
include a version that consumes all
TraversalEvents (BLOCK) but I don't see a use
for it at the moment. There may also be
variants of CONFINED and CYCLIC that make an
exception for cases where there is only a
single choice available. A ButtonGroup for
example may want to react differently
depending on whether it has 0, 1 or more
buttons. Whether these should be enshrined
with a custom enum value, or perhaps a flag,
or just left up to a custom implementation is
something we'd need to decide still.<o:p></o:p></p>
<p>## Use Cases<o:p></o:p></p>
<p class="MsoNormal">1) User wants to change the
behavior of a control from its default to
something else (ie. a Control that is CYCLIC
can be changed to OPEN or CONFINED)
<o:p></o:p></p>
<p>Just call the setters with the appropriate
preferred policy. This could be done in CSS
for maximum convenience to enable a global
change of all similar controls.<o:p></o:p></p>
<p>2) User wants to act on Traversal events that
the standard policy leaves to bubble up<o:p></o:p></p>
<p>Just install a Traversal event handler either
on the control or on its parent (depending on
their needs). A potential action to an unused
Traversal event could be to close a
Dialog/Toast popup, or a custom behavior like
selecting the first/last item or next/previous
row (ie. if I press "RIGHT" and there is no
further right item, a user could decide to
have this select the first item again in the
current Row or the first item in the **next**
Row).<o:p></o:p></p>
<p>3) User wants to do crazy custom navigation<o:p></o:p></p>
<p>Set both policies to IGNORED, then install
your own event handler (or use the
setOnTraversalHandler to completely override
the handler). Now react on the Traversal
events, consuming them at will and changing
focus to whatever control you desire.<o:p></o:p></p>
<p>4) User wants to change what keys are
considered navigation keys<o:p></o:p></p>
<p>Install event handler on Scene (or any
intermediate Parent) for KeyEvents, interpret
WASD keys as UP/LEFT/DOWN/RIGHT and sent out a
corresponding Traversal event<o:p></o:p></p>
<p>5) User wants to use keys that are considered
navigation keys for their own purposes<o:p></o:p></p>
<p>Just install a KeyEvent handler as usual,
without having to worry that Skins/Controls
eat these events before you can get to them<br>
<br>
6) User wants to stop a control from reacting
to traversal events, without filtering
navigation keys completely<o:p></o:p></p>
<p>With the separation of unconsumed KeyEvents
into TraversalEvents, a user can now block
only the latter to achieve this goal without
having to blanket block certain KeyEvents.<o:p></o:p></p>
<p>-----<o:p></o:p></p>
<p>About the Proposal:<o:p></o:p></p>
<p>I think the Goals are fine as stated,
although I think we differ on what the
Traversal events signify.<o:p></o:p></p>
<p>I think CSS support should be considered a
possible future goal. The proposal should
therefore take into account that we may want
to offer this in the future.<o:p></o:p></p>
<p>Motivation looks okay.<o:p></o:p></p>
<p>> The focus traversal is provided by the
FocusTraversal class which offers static
methods for traversing focus in various
directions, determined by the
TraversalDirection enum.<o:p></o:p></p>
<p>I think these methods don't need to be
exposed with a good selection of standard
TraversalPolicy options. After all, there are
only so many ways that you can do a sensible
navigation action without confusing the user,
and therefore I think these policy options
will cover 99% of the use cases already. For
the left over 1% we could **consider**
providing these focus traversal functions as a
separate public API, but I would have them
return the Node they would suggest, and leave
the final decision to call requestFocus up to
the caller. Initially however I think there
is already more than enough power for custom
implementations to listen to Traversal events
and do their own custom navigation. If it is
not similar to one of the standard navigation
options, the traverseUp/Down functions won't
be of much use then anyway.<o:p></o:p></p>
<p>About your typical example:<o:p></o:p></p>
<p> Node from = ...<br>
switch (((KeyEvent)event).getCode()) {<br>
case UP:<br>
FocusTraversal.traverse(from,
TraversalDirection.UP, TraversalMethod.KEY);<br>
event.consume();<br>
break;<br>
case DOWN:<br>
// or use the convenience method<br>
FocusTraversal.traverseDown(from);<br>
event.consume();<br>
break;<br>
}<o:p></o:p></p>
<p>I think this is not a good way to deal with
events.<o:p></o:p></p>
<p>1) The event is consumed regardless of the
outcome of traverse. What if focus did not
change? Should the event be consumed?<o:p></o:p></p>
<p>2) This is consuming KeyEvents directly,
robbing the user of the opportunity to act on
keys considered "special" by FX.<o:p></o:p></p>
<p>3) This code is not only consuming KeyEvents
directly, but also deciding what keys are
navigation keys.<o:p></o:p></p>
<p>So I think this example code should be
different. However, first I expect that in
most cases, configuring a different traversal
policy on your Parent subclass will already be
sufficient in almost all cases (especially if
we look at FX current controls and see if the
suggested policies would cover those use
cases). So this code will almost never be
needed. However, in the event that you need
something even more specific, you may consider
handling Traversal events directly. In which
case the code should IMHO look something like
this:<o:p></o:p></p>
<p> Node from = ...<br>
<br>
Node result =
switch(traversalEvent.getEventType()) {<br>
case TraversalEvent.UP ->
FocusTraversals.findUp(from);<br>
case TraversalEvent.DOWN ->
FocusTraversals.findDown(from);<br>
// etc<br>
}<br>
<br>
if (result != null) {<br>
result.requestFocus();<br>
traversalEvent.consume();<br>
}<o:p></o:p></p>
<p>Note that the above code leaves the final
decision to call requestFocus up to the
caller. It also allows the caller to
distinguish between the case where there is no
suitable Node in the indicated direction and
act accordingly. <o:p></o:p></p>
<p>This allows it to NOT consume the event if it
prefers its Parent to handle it (if the
control doesn't want CYCLIC or CONFINED style
navigation). It also allows it to further
scrutinize the suggested Node, and if it
decides it does not like it (due to some
property or CSS style or whatever) it may
follow up with another findXXX call or some
other option to pick the Node it wants. It
also allows (in the case of no Node being
found) to pick its own preferred Node in those
cases. In other words, it is just far more
flexible.<o:p></o:p></p>
<p>I'm not sure yet where to place these static
helper methods (if we decide to expose them at
all initially), or even if they should be
static. Given that its first parameter is
always a Node, a non-static location for them
could simply be on Node itself, in which case
the calling convention would become "Node
result = from.findTraversableUp()" (suggested
name only)<o:p></o:p></p>
<p>> Focus traversals generate a new type of
event, encapsulated by the class
TraversalEvent which extends
javafx.event.Event, using the event type
TraversalEvent.NODE_TRAVERSED.<o:p></o:p></p>
<p>What is the point of this event? If you want
to know that focus changed, you can add a
listener to Scene.focusOwnerProperty. What
does it mean if I filter this event? What if
I consume it? I don't think this should be an
event at all, unless implemented as I
suggested above, where
consuming/filtering/bubbling can be used to
control how controls will react to navigation
events.<o:p></o:p></p>
<p>--John<o:p></o:p></p>
<p> <o:p></o:p></p>
<p> <o:p></o:p></p>
<p class="MsoNormal"
style="margin-bottom:12.0pt">On 03/09/2024
21:33, Andy Goryachev wrote:<o:p></o:p></p>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Dear
fellow developers:</span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">I'd
like to propose the public focus
traversal API:</span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="p1"
style="margin:0in;font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"><a
href="https://urldefense.com/v3/__https:/github.com/andy-goryachev-oracle/Test/blob/main/doc/FocusTraversal/FocusTraversal.md__;!!ACWV5N9M2RV99hQ!LnjDXwUbbEymf9b1gkZFia8vuewsVJy6_49It-IKw66U9mS78PjdIPotBpc7AXlSfY7N5xcRXsmcPQhOzavk4z9VkPv-$"
title="https://github.com/andy-goryachev-oracle/Test/blob/main/doc/FocusTraversal/FocusTraversal.md"
moz-do-not-send="true"><span
style="color:#0078D7">https://github.com/andy-goryachev-oracle/Test/blob/main/doc/<span
class="outlook-search-highlight">Focus</span>Traversal/<span
class="outlook-search-highlight">Focus</span>Traversal.md</span></a></span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Draft
PR:</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"><a
href="https://urldefense.com/v3/__https:/github.com/openjdk/jfx/pull/1555__;!!ACWV5N9M2RV99hQ!LnjDXwUbbEymf9b1gkZFia8vuewsVJy6_49It-IKw66U9mS78PjdIPotBpc7AXlSfY7N5xcRXsmcPQhOzavk49fH_P2p$"
moz-do-not-send="true">https://github.com/openjdk/jfx/pull/1555</a></span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Your
comments and suggestions will be warmly
accepted and appreciated.</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Thank
you</span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
<p class="MsoNormal"
style="font-variant-caps:normal;orphans:auto;text-align:start;widows:auto;word-spacing:0px">
<span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">-andy</span><o:p></o:p></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</blockquote>
</body>
</html>