<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p><br>
</p>
<div class="moz-cite-prefix">On 24/09/2024 23:58, Andy Goryachev
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:BL3PR10MB61855645DC9827B4A60A302FE5682@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;}@font-face
{font-family:Optima-Regular;
panose-1:2 0 5 3 6 0 0 2 0 4;}p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0in;
font-size:10.0pt;
font-family:"Aptos",sans-serif;}a:link, span.MsoHyperlink
{mso-style-priority:99;
color:blue;
text-decoration:underline;}span.apple-converted-space
{mso-style-name:apple-converted-space;}span.outlook-search-highlight
{mso-style-name:outlook-search-highlight;}span.EmailStyle21
{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
Martin:<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 for a very thoughtful and constructive response! It
will take me some time to digest, but I do want to mention
some thoughts I had while listening to the discussion
earlier.<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.
TraversalEvent<o:p></o:p></span></p>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">You
are right, it appears there might be a few or no use cases
for this event as far as app code is concerned. Looking at
the code, it was a replacement for internal
TraversalEngine.TraverseListener utilized by several skins.
It might be possible to remove it and instead rely on
listening to Scene.focusOwnerProperty, at the expense of
complicating the skins. Is this a good idea? Will removing
the event make it more difficult to write a custom
component/skin?</span></p>
</div>
</blockquote>
I think it is worth looking into this. API to make things "easier"
to implement now will bite you later (see CSS and its binary CSS
load/save API that didn't really needed to be public...)<br>
<blockquote type="cite"
cite="mid:BL3PR10MB61855645DC9827B4A60A302FE5682@BL3PR10MB6185.namprd10.prod.outlook.com">
<div class="WordSection1">
<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>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">2.
The fact that some policies depend on the state of the
control basically negates the idea of standard set of
policies encoded in an enum.</span></p>
</div>
</blockquote>
<p>I really think exposing policies is a bit overkill, and should be
considered later if at all. Controls just want to be able to use
the standard traversal mechanism, which will not be such a pain to
get once <a class="moz-txt-link-freetext" href="https://bugs.openjdk.org/browse/JDK-8340852">https://bugs.openjdk.org/browse/JDK-8340852</a> is fixed (as
you can just let it bubble up). Controls that really want
something different to happen can take the extra steps of adding
KeyEvent handlers, consuming those only when they don't want the
standard navigation from Scene.</p>
<p>I don't think it should be a goal to have all the complex
navigation cases possible with FX controls be possible with a new
API. You can already do whatever navigation you want by finding
the Node you need and focusing it. The problem has always been
how to get the **default** navigation for your custom control
(which will cover the vast majority of cases already) -- this was
hard because events wouldn't bubble up to Scene properly, which
necessitated installing your own Key handlers, which then
necessitates having access to the standard focus changing code...
It is a cascade of problems for which we're now building a
"workaround", which IMHO is not really needed once the cause of
this problem is fixed.<br>
</p>
<blockquote type="cite"
cite="mid:BL3PR10MB61855645DC9827B4A60A302FE5682@BL3PR10MB6185.namprd10.prod.outlook.com">
<div class="WordSection1">
<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>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">3.
Controls may need to intercept traversal keys and fire off
the traversal, for example, TAB on a read-only TextArea
should traverse, but should insert the tab character into
the editable TextArea. (I would imagine this can be also
implemented by not consuming events if they effect no change
in the control, but that is a different story).</span></p>
</div>
</blockquote>
<p>That's really how it should be. Controls should preferably
always delegate so there is no navigation code all over the place.</p>
<p>--John<br>
</p>
<blockquote type="cite"
cite="mid:BL3PR10MB61855645DC9827B4A60A302FE5682@BL3PR10MB6185.namprd10.prod.outlook.com">
<div class="WordSection1">
<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>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">4.
It might be possible for controls like ToggleButton to set
its own traversal policy instead intercepting KeyEvents
(which is the current way of dealing with conditional
traversal). I would imagine this being done as a separate
enhancement though.<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>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thanks,<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>
<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="font-size:12.0pt;color:black">From:
</span></b><span
style="font-size:12.0pt;color:black">Martin Fox
<a class="moz-txt-link-rfc2396E" href="mailto:martin@martinfox.com"><martin@martinfox.com></a><br>
<b>Date: </b>Tuesday, September 24, 2024 at 13:30<br>
<b>To: </b>Andy Goryachev
<a class="moz-txt-link-rfc2396E" href="mailto:andy.goryachev@oracle.com"><andy.goryachev@oracle.com></a><br>
<b>Cc: </b>OpenJFX <a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
<b>Subject: </b>Re: [External] : Re: Proposal:
Focus Traversal API<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">Andy,<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">Glad
to finally see a proposal for opening up traversal.
Long overdue.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">There’s
a case in the current control set that this API
doesn’t cover. ToggleButtons intercept the arrow
keys so they can implement a cyclic traversal policy
among buttons in the ToggleGroup. They don’t assume
that their Parent will do this for them. To
reproduce this behavior this API would have to be
extended so it can first ask the focusOwner where to
traverse to before moving on to the Parent. (And
looking at the code I think there’s yet another bug
where events are consumed when they should not be.
Sigh).<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">The
proposal doesn’t provide any use cases for the
TraversalEvent. It also doesn’t specify the initial
target of the event or how it should be handled. I
had to look in the sources to see what it’s being
used for and that (a) it’s being fired at the new
focus owner and (b) it’s a bad idea to consume it.
It looks more like information you want to broadcast
to a set of listeners rather than a message to be
acted on. If nothing else the bounds should be
removed from this event since that’s a very specific
piece of info that only the ScrollPaneSkin wants. I
haven’t mapped out the details but I’m pretty sure
it can figure out the bounds itself.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">This
proposal should give some quick background on how
traversal key events are handled, specifically that
controls should let the key events bubble up to the
Scene to invoke traversal. And if the traversal keys
are always going to be consumed by the Scene that
should also be mentioned.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">There’s
been a few comments suggesting the traversal key
events should not be consumed if the focusOwner
doesn’t change. Unfortunately that would break
Popups which have an, um, interesting event
architecture. And I don’t want to introduce yet
another instance where events need to be consumed
under specific conditions or things break down (I’m
currently tracking down bugs where ESCAPE is
consumed when it shouldn’t be or not consumed when
it should be). We should figure out what these
clients need and ensure it’s delivered to them in a
reliable way. Perhaps when traversal ends we could
fire an event at the Scene with all the details?<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">The
sample code is problematic since we really don’t
want controls to kick off traversal themselves
unless they absolutely have to. That caveat should
probably precede the code. If you must provide some
sample code please make sure it checks the modifier
state on the KeyEvent. (There really should be a
central API for mapping a KeyEvent to a
TraversalDirection but that would be a different
proposal.)<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">If
we provide pre-packaged traversal policies we’ll
need to provide multiple variants of CYCLIC. A menu
bar cycles horizontally with the arrow keys and Tab
but not vertically. According to the w3c a toggle
button group should cycle with all the arrow keys
but not with the Tab key. And there’s probably one
or two more combinations in the current control set
and w3c accessibility guidelines.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">Martin<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt"><br>
<br>
<o:p></o:p></span></p>
<blockquote style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<p class="MsoNormal"><span style="font-size:12.0pt">On
Sep 24, 2024, at 11:57</span><span
style="font-size:12.0pt;font-family:"Arial",sans-serif"> </span><span
style="font-size:12.0pt">AM, Andy Goryachev
<a class="moz-txt-link-rfc2396E" href="mailto:andy.goryachev@oracle.com"><andy.goryachev@oracle.com></a> wrote:<o:p></o:p></span></p>
</div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
<div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">I
fully agree with Martin here.</span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy</span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
<div id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid windowtext 1.0pt;padding:3.0pt 0in 0in 0in;border-color:currentcolor currentcolor">
<p class="MsoNormal"
style="margin-bottom:12.0pt"><b><span
style="font-size:12.0pt">From:<span
class="apple-converted-space"> </span></span></b><span
style="font-size:12.0pt">Martin Fox
<a class="moz-txt-link-rfc2396E" href="mailto:martin@martinfox.com"><martin@martinfox.com></a><br>
<b>Date:<span
class="apple-converted-space"> </span></b>Wednesday,
September 18, 2024 at 11:49<br>
<b>To:<span
class="apple-converted-space"> </span></b>John
Hendrikx
<a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a><br>
<b>Cc:<span
class="apple-converted-space"> </span></b>Andy
Goryachev
<a class="moz-txt-link-rfc2396E" href="mailto:andy.goryachev@oracle.com"><andy.goryachev@oracle.com></a>,
OpenJFX
<a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
<b>Subject:<span
class="apple-converted-space"> </span></b>Re:
[External] : Re: Proposal: Focus
Traversal API</span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">John,</span><o:p></o:p></p>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">Sorry I
didn’t respond to this thread
earlier. I’ve been looking at the
code and bug database trying to work
backward to a problem statement. The
bug reports cited in the PR are
light on details so I’m trying to
come up with a more concrete set of
use cases to consider.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">Currently
I’m failing to see the benefit of a
traversal event model.</span><o:p></o:p></p>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div
id="mail-editor-reference-message-container">
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div
id="mail-editor-reference-message-container">
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- 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...). <o:p></o:p></span></p>
</div>
</blockquote>
</div>
</div>
</blockquote>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">It
doesn’t really matter whether
the control is capturing the key
event and invoking the traversal
engine directly or firing off a
TraversalEvent. The problem is
that the control is processing
the key event in the first
place. You’re talking about
something that happens before
traversal is even initiated.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">It’s
unfortunate that the code
snippet in the proposal shows a
control doing a direct mapping
from KeyCodes to traversal
calls. That’s not how the
current system is intended to
work. There’s already a
dispatcher at the Scene level
that processes KeyEvents and
invokes the traversal machinery
so controls should just let the
traversal keys bubble up. There
are exceptions to this
throughout the code which I
would like to get a better
handle on since I can’t tell if
they’re bugs or truly necessary
exceptions.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">Once
the system has determined that
it’s time for traversal it needs
to map from a KeyEvent to a
direction and type. I can see
where providing a centralized
API for doing this could be of
benefit but I’m not sure that
has anything to do with this PR
or the concept of
TraversalEvents. This is also a
decision made before traversal
is initiated.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">You
give some examples of
customizing traversal in various
ways. From what I can tell all
of them can be accomplished
using this PR with maybe one
exception that I’ll get to when
I respond to Andy’s proposal. In
other cases I can’t tell if a
TraversalEvent would provide a
cleaner implementation because I
don’t understand how they would
work. For example, one could
imagine a system where a
TraversalEvent is fired by the
Scene and expected to bubble
back up to the Scene which will
then utilize the existing
centralized engines to actually
perform the traversal. But one
could also imagine a system
where the Scene fires a
TraversalEvent and the nodes act
on them directly as it bubbles
up. I’m not sure what you have
in mind. It would be nice to see
some examples of how a
TraversalEvent would move
through the system in various
scenarios particularly when
traversing laterally.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Martin<o:p></o:p></span></p>
</div>
</div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">On
Sep 17, 2024, at 11:05</span><span
style="font-size:12.0pt;font-family:"Arial",sans-serif"> </span><span
style="font-size:12.0pt">PM,
John Hendrikx
<a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a>
wrote:</span><o:p></o:p></p>
</div>
</div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt"> </span><o:p></o:p></p>
</div>
<div>
<p
style="font-variant-caps:normal;text-align:start;word-spacing:0px"><span
style="font-size:10.5pt;font-family:Optima-Regular">Andy,<o:p></o:p></span></p>
<p
style="font-variant-caps:normal;text-align:start;word-spacing:0px"><span
style="font-size:10.5pt;font-family:Optima-Regular">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.<o:p></o:p></span></p>
<p
style="font-variant-caps:normal;text-align:start;word-spacing:0px"><span
style="font-size:10.5pt;font-family:Optima-Regular">--John<o:p></o:p></span></p>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:10.5pt;font-family:Optima-Regular">On 18/09/2024 01:09,
Andy Goryachev wrote:</span><o:p></o:p></p>
</div>
</div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt;font-variant-caps:normal;text-align:start;word-spacing:0px">
<div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Dear
John:</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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:</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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.</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank
you</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div
id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid windowtext 1.0pt;padding:3.0pt 0in 0in 0in;border-color:currentcolor">
<p class="MsoNormal"
style="margin-bottom:12.0pt"><b><span style="font-size:12.0pt">From:<span
class="apple-converted-space"> </span></span></b><span
style="font-size:12.0pt">John Hendrikx<span
class="apple-converted-space"> </span><a
href="mailto:john.hendrikx@gmail.com" moz-do-not-send="true"><span
style="color:#467886"><john.hendrikx@gmail.com></span></a><br>
<b>Date:<span
class="apple-converted-space"> </span></b>Saturday, September 14, 2024
at 09:41<br>
<b>To:<span
class="apple-converted-space"> </span></b>Andy Goryachev<span
class="apple-converted-space"> </span><a
href="mailto:andy.goryachev@oracle.com" moz-do-not-send="true"><span
style="color:#467886"><andy.goryachev@oracle.com></span></a>,<span
class="apple-converted-space"> </span><a
href="mailto:openjfx-dev@openjdk.org" moz-do-not-send="true"><span
style="color:#467886">openjfx-dev@openjdk.org</span></a><span
class="apple-converted-space"> </span><a
href="mailto:openjfx-dev@openjdk.org" moz-do-not-send="true"><span
style="color:#467886"><openjfx-dev@openjdk.org></span></a><br>
<b>Subject:<span
class="apple-converted-space"> </span></b>[External] : Re: Proposal:
Focus Traversal API</span><o:p></o:p></p>
</div>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Hi Andy,<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<div>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">On 14/09/2024 00:17, Andy Goryachev wrote:</span><o:p></o:p></p>
</div>
</div>
</div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Dear
John,
Everyone:</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">3.
(more)
standard
policies</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">4.
using CSS</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#1</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
</blockquote>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">There's two things
here.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<div>
<p class="MsoNormal"
style="margin-bottom:12.0pt"><span style="font-size:12.0pt">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.</span><o:p></o:p></p>
</div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#2</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
</blockquote>
<div>
<p class="MsoNormal"
style="margin-bottom:12.0pt"><span style="font-size:12.0pt">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?</span><o:p></o:p></p>
</div>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#3</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
</blockquote>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">#4</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
</blockquote>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- 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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">--</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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<span
class="apple-converted-space"> </span><i>probability</i><span
class="apple-converted-space"> </span>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>
</div>
</div>
</blockquote>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">"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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Also I think this
can be implemented
gradually. Here's a
potential plan:<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">1. Have Scene listen
to unused KeyEvents
and translate them to
TraversalEvents<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Public API: Limited
to a new Event<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Public API: none<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">3. Implement a
number of standard
policies internally
(OPEN, CONFINED,
CYCLIC, IGNORED)<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Benefit: less code
to maintain and debug,
gives us experience
with which policies
make sense and where
the gaps are<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Public API: none<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">4. Expose policy
property/properties on
Parent<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<div>
<div>
<p class="MsoNormal"><span
style="font-size:12.0pt">Public API: new properties on Parent</span><o:p></o:p></p>
</div>
</div>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">6. CSS styleable
properties<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Public API: Nothing
in Java, but document
as CSS properties<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">--John<o:p></o:p></span></p>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank
you,</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""> </span><o:p></o:p></p>
</div>
</div>
<div
id="mail-editor-reference-message-container">
<div>
<div>
<div
style="border:none;border-top:solid windowtext 1.0pt;padding:3.0pt 0in 0in 0in;border-color:currentcolor">
<p
class="MsoNormal" style="margin-bottom:12.0pt"><b><span
style="font-size:12.0pt">From:<span class="apple-converted-space"> </span></span></b><span
style="font-size:12.0pt">openjfx-dev<span class="apple-converted-space"> </span><a
href="mailto:openjfx-dev-retn@openjdk.org" moz-do-not-send="true"><span
style="color:#467886"><openjfx-dev-retn@openjdk.org></span></a><span
class="apple-converted-space"> </span>on behalf of John Hendrikx<span
class="apple-converted-space"> </span><a
href="mailto:john.hendrikx@gmail.com" moz-do-not-send="true"><span
style="color:#467886"><john.hendrikx@gmail.com></span></a><br>
<b>Date:<span
class="apple-converted-space"> </span></b>Wednesday, September 11, 2024
at 19:05<br>
<b>To:<span
class="apple-converted-space"> </span></b><a
href="mailto:openjfx-dev@openjdk.org" moz-do-not-send="true"><span
style="color:#467886">openjfx-dev@openjdk.org</span></a><span
class="apple-converted-space"> </span><a
href="mailto:openjfx-dev@openjdk.org" moz-do-not-send="true"><span
style="color:#467886"><openjfx-dev@openjdk.org></span></a><br>
<b>Subject:<span
class="apple-converted-space"> </span></b>Re: Proposal: Focus Traversal
API</span><o:p></o:p></p>
</div>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Hi Andy / List,<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">I've given this some
thought
first, without
looking too
much at the
proposal.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">This separation of
KeyEvents into
TraversalEvents achieves the following:<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- 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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">## 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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">-
logicalTraversalPolicy<br>
-
directionalTraversalPolicy<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">The properties can
be set with a
value from a
TraversalPolicy
enum. I would
suggest the
following
options:<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- OPEN<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- CONFINED<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">- CYCLIC<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">## Use Cases<o:p></o:p></span></p>
<div>
<div>
<p
class="MsoNormal"><span style="font-size:12.0pt">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)</span><o:p></o:p></p>
</div>
</div>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">2) User wants to act
on Traversal
events that
the standard
policy leaves
to bubble up<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">3) User wants to do
crazy custom
navigation<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">4) User wants to
change what
keys are
considered
navigation
keys<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">5) User wants to use
keys that are
considered
navigation
keys for their
own purposes<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">-----<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">About the Proposal:<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">I think the Goals
are fine as
stated,
although I
think we
differ on what
the Traversal
events
signify.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">Motivation looks
okay.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">> 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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">About your typical
example:<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular"> <span
class="apple-converted-space"> </span>Node from = ...<br>
<span
class="apple-converted-space"> </span>switch
(((KeyEvent)event).getCode())
{<br>
<span
class="apple-converted-space"> </span>case UP:<br>
<span
class="apple-converted-space"> </span>FocusTraversal.traverse(from,
TraversalDirection.UP,
TraversalMethod.KEY);<br>
<span
class="apple-converted-space"> </span>event.consume();<br>
<span
class="apple-converted-space"> </span>break;<br>
<span
class="apple-converted-space"> </span>case DOWN:<br>
<span
class="apple-converted-space"> </span>// or use the convenience method<br>
<span
class="apple-converted-space"> </span>FocusTraversal.traverseDown(from);<br>
<span
class="apple-converted-space"> </span>event.consume();<br>
<span
class="apple-converted-space"> </span>break;<br>
<span
class="apple-converted-space"> </span>}<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">I think this is not
a good way to
deal with
events.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">2) This is consuming
KeyEvents
directly,
robbing the
user of the
opportunity to
act on keys
considered
"special" by
FX.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">3) This code is not
only consuming
KeyEvents
directly, but
also deciding
what keys are
navigation
keys.<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular"> <span
class="apple-converted-space"> </span>Node from = ...<br>
<br>
<span
class="apple-converted-space"> </span>Node result =
switch(traversalEvent.getEventType())
{<br>
<span
class="apple-converted-space"> </span>case TraversalEvent.UP ->
FocusTraversals.findUp(from);<br>
<span
class="apple-converted-space"> </span>case TraversalEvent.DOWN ->
FocusTraversals.findDown(from);<br>
<span
class="apple-converted-space"> </span>// etc<br>
<span
class="apple-converted-space"> </span>}<br>
<br>
<span
class="apple-converted-space"> </span>if (result != null) {<br>
<span
class="apple-converted-space"> </span>result.requestFocus();<br>
<span
class="apple-converted-space"> </span>traversalEvent.consume();<br>
<span
class="apple-converted-space"> </span>}<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">> 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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">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></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular">--John<o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular"> <o:p></o:p></span></p>
<p><span
style="font-size:10.5pt;font-family:Optima-Regular"> <o:p></o:p></span></p>
<p
class="MsoNormal" style="margin-bottom:12.0pt"><span
style="font-size:12.0pt">On 03/09/2024 21:33, Andy Goryachev wrote:</span><o:p></o:p></p>
<blockquote
style="margin-top:5.0pt;margin-bottom:5.0pt">
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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/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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Draft
PR:</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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"><span style="color:#467886">https://github.com/openjdk/jfx/pull/1555</span></a></span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<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>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">Thank
you</span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121"> </span><o:p></o:p></p>
</div>
</div>
<div>
<div>
<p
class="MsoNormal"><span
style="font-size:11.0pt;font-family:"Iosevka Fixed SS16";color:#212121">-andy</span><o:p></o:p></p>
</div>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</blockquote>
</div>
<p class="MsoNormal"><span style="font-size:12.0pt"><o:p> </o:p></span></p>
</div>
</div>
</div>
</div>
</blockquote>
</body>
</html>