1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.traversal;
  27 
  28 import java.util.List;
  29 import java.util.Stack;
  30 import javafx.geometry.BoundingBox;
  31 import javafx.geometry.Bounds;
  32 import javafx.geometry.Point2D;
  33 import javafx.scene.Group;
  34 import javafx.scene.Node;
  35 import javafx.scene.Parent;
  36 import com.sun.javafx.Logging;
  37 import sun.util.logging.PlatformLogger;
  38 import sun.util.logging.PlatformLogger.Level;
  39 
  40 import static com.sun.javafx.scene.traversal.Direction.*;
  41 import java.util.function.Function;
  42 import javafx.geometry.Bounds;
  43 
  44 
  45 public class Hueristic2D implements Algorithm {
  46 
  47     PlatformLogger focusLogger;
  48 
  49     Hueristic2D() {
  50         focusLogger = Logging.getFocusLogger();
  51     }
  52 
  53     @Override
  54     public Node traverse(Node node, Direction dir, TraversalEngine engine) {
  55         Node newNode = null;
  56 
  57         cacheTraversal(node, dir, engine);
  58 
  59         if (focusLogger.isLoggable(Level.FINER)) {
  60             focusLogger.finer("old focus owner : "+node+", bounds : "+engine.getBounds(node));
  61         }
  62 
  63         if (NEXT.equals(dir)) {
  64             newNode = findNextFocusablePeer(node);
  65         }
  66         else if (PREVIOUS.equals(dir)) {
  67             newNode = (findPreviousFocusablePeer(node));
  68         }
  69         else if (UP.equals(dir) || DOWN.equals(dir) || LEFT.equals(dir) || RIGHT.equals(dir) ) {
  70             /*
  71             ** if there is a node top of stack then make sure it's traversable
  72             */
  73             if (reverseDirection == true && !traversalNodeStack.empty()) {
  74                 if (!traversalNodeStack.peek().isFocusTraversable()) {
  75                     traversalNodeStack.clear();
  76                 }
  77                 else {
  78                     newNode = traversalNodeStack.pop();
  79                 }
  80             }
  81 
  82             Bounds currentB = node.localToScene(node.getLayoutBounds());
  83 
  84             if (cacheStartTraversalNode != null) {
  85                 Bounds cachedB = cacheStartTraversalNode.localToScene(cacheStartTraversalNode.getLayoutBounds());
  86                 switch (dir) {
  87                 case UP:
  88                 case DOWN:
  89                     newNode = getNearestNodeUpOrDown(currentB, cachedB, engine, node, newNode, dir);
  90                     break;
  91                 case LEFT:
  92                 case RIGHT:
  93                     newNode = getNearestNodeLeftOrRight(currentB, cachedB, engine, node, newNode, dir);
  94                     break;
  95                 default:
  96                     break;
  97                 }
  98             }
  99         }
 100 
 101         if (focusLogger.isLoggable(Level.FINER)) {
 102             if (newNode != null) {
 103                 focusLogger.finer("new focus owner : "+newNode+", bounds : "+engine.getBounds(newNode));
 104             }
 105             else {
 106                 focusLogger.finer("no focus transfer");
 107             }
 108         }
 109 
 110         /*
 111         ** newNode will be null if there are no
 112         ** possible targets in the direction.
 113         ** don't cache null, there's no coming back from that!
 114         */
 115         if (newNode != null) {
 116             cacheLastTraversalNode = newNode;
 117             if (reverseDirection == false) {
 118                 traversalNodeStack.push(node);
 119             }
 120         }
 121         return newNode;
 122     }
 123 
 124     private Node findNextFocusablePeer(Node node) {
 125         Node startNode = node;
 126         Node newNode = null;
 127         List<Node> parentNodes = findPeers(startNode);
 128         if (parentNodes == null) {
 129             if (focusLogger.isLoggable(Level.FINER)) {
 130                 focusLogger.finer("can't find peers for a node without a parent");
 131             }
 132             return null;
 133         }
 134 
 135         int ourIndex = parentNodes.indexOf(startNode);
 136 
 137         if (ourIndex == -1) {
 138             if (focusLogger.isLoggable(Level.FINER)) {
 139                 focusLogger.finer("index not founds, no focus transfer");
 140             }
 141             return null;
 142         }
 143 
 144         newNode = findNextFocusableInList(parentNodes, ourIndex+1);
 145 
 146         /*
 147         ** we've reached the end of the peer nodes, and none have been selected,
 148         ** time to look at our parents peers.....
 149         */
 150         while (newNode == null && startNode != null) {
 151             List<Node> peerNodes;
 152             int parentIndex;
 153 
 154             Parent parent = startNode.getParent();
 155             if (parent != null) {
 156                 peerNodes = findPeers(parent);
 157                 if (peerNodes != null) {
 158                     parentIndex = peerNodes.indexOf(parent);
 159                     newNode = findNextFocusableInList(peerNodes, parentIndex+1);
 160                 }
 161             }
 162             startNode = parent;
 163         }
 164 
 165         if (newNode == null) {
 166             /*
 167             ** find the top-most parent which is not at it's end-of-list
 168             */
 169             Parent parent = null;
 170             Parent p1 = node.getParent();
 171             while (p1 != null) {
 172                 parent = p1;
 173                 p1 = p1.getParent();
 174             }
 175             parentNodes = parent.getChildrenUnmodifiable();
 176             newNode = findNextFocusableInList(parentNodes, 0);
 177         }
 178 
 179         return newNode;
 180     }
 181 
 182     private Node findNextFocusableInList(List<Node> nodeList, int startIndex) {
 183         Node newNode = null;
 184 
 185         for (int i = startIndex ; i < nodeList.size() ; i++) {
 186 
 187             Node nextNode = nodeList.get(i);
 188             if (nextNode.isFocusTraversable() == true && nextNode.isDisabled() == false && nextNode.impl_isTreeVisible() == true) {
 189                 newNode = nextNode;
 190                 break;
 191             }
 192             else if (nextNode instanceof javafx.scene.Parent) {
 193                 List<Node> nextNodesList = ((Parent)nextNode).getChildrenUnmodifiable();
 194                 if (nextNodesList.size() > 0) {
 195                     newNode = findNextFocusableInList(nextNodesList, 0);
 196                     if (newNode != null) {
 197                         break;
 198                     }
 199                 }
 200             }
 201         }
 202         return newNode;
 203     }
 204 
 205     private Node findPreviousFocusablePeer(Node node) {
 206         Node startNode = node;
 207         Node newNode = null;
 208         List<Node> parentNodes = findPeers(startNode);
 209 
 210         int ourIndex = parentNodes.indexOf(startNode);
 211 
 212         if (ourIndex == -1) {
 213             if (focusLogger.isLoggable(Level.FINER)) {
 214                 focusLogger.finer("index not founds, no focus transfer");
 215             }
 216             return null;
 217         }
 218 
 219         newNode = findPreviousFocusableInList(parentNodes, ourIndex-1);
 220 
 221         /*
 222         ** we've reached the end of the peer nodes, and none have been selected,
 223         ** time to look at our parents peers.....
 224         */
 225         while (newNode == null && startNode != null) {
 226             List<Node> peerNodes;
 227             int parentIndex;
 228 
 229             Parent parent = startNode.getParent();
 230             if (parent != null) {
 231                 peerNodes = findPeers(parent);
 232                 if (peerNodes != null) {
 233                     parentIndex = peerNodes.indexOf(parent);
 234                     newNode = findPreviousFocusableInList(peerNodes, parentIndex-1);
 235                 }
 236             }
 237             startNode = parent;
 238         }
 239 
 240         if (newNode == null) {
 241             /*
 242             ** find the top-most parent which is not at it's end-of-list
 243             */
 244             Parent parent = null;
 245             Parent p1 = node.getParent();
 246             while (p1 != null) {
 247                 parent = p1;
 248                 p1 = p1.getParent();
 249             }
 250             parentNodes = parent.getChildrenUnmodifiable();
 251             newNode = findPreviousFocusableInList(parentNodes, parentNodes.size()-1);
 252         }
 253 
 254         return newNode;
 255     }
 256 
 257 
 258     private Node findPreviousFocusableInList(List<Node> nodeList, int startIndex) {
 259         Node newNode = null;
 260 
 261         for (int i = startIndex ; i >= 0 ; i--) {
 262             Node prevNode = nodeList.get(i);
 263             if (prevNode.isFocusTraversable() == true && prevNode.isDisabled() == false && prevNode.impl_isTreeVisible() == true) {
 264                 newNode = prevNode;
 265                 break;
 266             }
 267             else if (prevNode instanceof javafx.scene.Parent) {
 268                 List<Node> prevNodesList = ((Parent)prevNode).getChildrenUnmodifiable();
 269                 if (prevNodesList.size() > 0) {
 270                     newNode = findPreviousFocusableInList(prevNodesList, prevNodesList.size()-1);
 271                     if (newNode != null) {
 272                         break;
 273                     }
 274                 }
 275             }
 276         }
 277         return newNode;
 278     }
 279 
 280     private List<Node> findPeers(Node node) {
 281         List<Node> parentNodes = null;
 282         Parent parent = node.getParent();
 283         /*
 284         ** check that we haven't hit the top-level
 285         */
 286         if (parent != null) {
 287             parentNodes = parent.getChildrenUnmodifiable();
 288         }
 289         return parentNodes;
 290     }
 291 
 292     private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) {
 293 
 294         final double cmin, cmax, tmin, tmax;
 295 
 296         if (dir == UP || dir == DOWN) {
 297             cmin = cur.getMinX();
 298             cmax = cur.getMaxX();
 299             tmin = tgt.getMinX();
 300             tmax = tgt.getMaxX();
 301         }
 302         else { // dir == LEFT || dir == RIGHT
 303             cmin = cur.getMinY();
 304             cmax = cur.getMaxY();
 305             tmin = tgt.getMinY();
 306             tmax = tgt.getMaxY();
 307         }
 308 
 309         return tmin <= cmax && tmax >= cmin;
 310     }
 311 
 312     /**
 313      * Compute the out-distance to the near edge of the target in the
 314      * traversal direction. Negative means the near edge is "behind".
 315      */
 316     private double outDistance(Direction dir, Bounds cur, Bounds tgt) {
 317 
 318         final double distance;
 319         if (dir == UP) {
 320             distance = cur.getMinY() - tgt.getMaxY();
 321         }
 322         else if (dir == DOWN) {
 323             distance = tgt.getMinY() - cur.getMaxY();
 324         }
 325         else if (dir == LEFT) {
 326             distance = cur.getMinX() - tgt.getMaxX();
 327         }
 328         else { // dir == RIGHT
 329             distance = tgt.getMinX() - cur.getMaxX();
 330         }
 331         return distance;
 332     }
 333 
 334     /**
 335      * Computes the side distance from current center to target center.
 336      * Always positive. This is only used for on-axis nodes.
 337      */
 338     private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) {
 339         final double cc; // current center
 340         final double tc; // target center
 341 
 342         if (dir == UP || dir == DOWN) {
 343             cc = cur.getMinX() + cur.getWidth() / 2.0f;
 344             tc = tgt.getMinX() + tgt.getWidth() / 2.0f;
 345         }
 346         else { // dir == LEFT || dir == RIGHT
 347             cc = cur.getMinY() + cur.getHeight() / 2.0f;
 348             tc = tgt.getMinY() + tgt.getHeight() / 2.0f;
 349         }
 350         return Math.abs(tc - cc);
 351     }
 352 
 353     /**
 354      * Computes the side distance between the closest corners of the current
 355      * and target. Always positive. This is only used for off-axis nodes.
 356      */
 357     private double cornerSideDistance(Direction dir, Bounds cur, Bounds tgt) {
 358 
 359         final double distance;
 360 
 361         if (dir == UP || dir == DOWN) {
 362             if (tgt.getMinX() > cur.getMaxX()) {
 363                 // on the right
 364                 distance = tgt.getMinX() - cur.getMaxX();
 365             }
 366             else {
 367                 // on the left
 368                 distance = cur.getMinX() - tgt.getMaxX();
 369             }
 370         }
 371         else { // dir == LEFT or dir == RIGHT
 372 
 373             if (tgt.getMinY() > cur.getMaxY()) {
 374                 // below
 375                 distance = tgt.getMinY() - cur.getMaxY();
 376             }
 377             else {
 378                 // above
 379                 distance = cur.getMinY() - tgt.getMaxY();
 380             }
 381         }
 382         return distance;
 383     }
 384 
 385     protected Node cacheStartTraversalNode = null;
 386     protected Direction cacheStartTraversalDirection = null;
 387     protected boolean reverseDirection = false;
 388     protected Node cacheLastTraversalNode = null;
 389     protected Stack<Node> traversalNodeStack = new Stack();
 390 
 391     private void cacheTraversal(Node node, Direction dir, TraversalEngine engine) {
 392         if (!traversalNodeStack.empty() && node != cacheLastTraversalNode) {
 393             /*
 394             ** we didn't get here by arrow key,
 395             ** dump the cache
 396             */
 397             traversalNodeStack.clear();
 398         }
 399         /*
 400         ** Next or Previous cancels the row caching
 401         */
 402         if (dir == Direction.NEXT || dir == Direction.PREVIOUS) {
 403             traversalNodeStack.clear();
 404             reverseDirection = false;
 405         } else {
 406             if (cacheStartTraversalNode == null || dir != cacheStartTraversalDirection) {
 407 
 408                 if ((dir == UP && cacheStartTraversalDirection == DOWN) ||
 409                     (dir == DOWN && cacheStartTraversalDirection == UP) ||
 410                     (dir == LEFT && cacheStartTraversalDirection == RIGHT) ||
 411                     (dir == RIGHT && cacheStartTraversalDirection == LEFT) && !traversalNodeStack.empty()) {
 412                     reverseDirection = true;
 413                 } else {
 414                     /*
 415                      ** if we don't have a row set, or the direction has changed, then
 416                     ** make the current node the row.
 417                     ** otherwise we are moving in the same direction as last time, so
 418                     ** we'll just leave it alone.
 419                     */
 420                     cacheStartTraversalNode = node;
 421                     cacheStartTraversalDirection = dir;
 422                     reverseDirection = false;
 423                     traversalNodeStack.clear();
 424                 }
 425             } else {
 426                 /*
 427                 ** we're going this way again!
 428                 */
 429                 reverseDirection = false;
 430             }
 431         }
 432     }
 433     
 434     private static final Function<Bounds, Double> BOUNDS_TOP_SIDE = new Function<Bounds, Double>() {
 435 
 436         @Override
 437         public Double apply(Bounds t) {
 438             return t.getMinY();
 439         }
 440     };
 441 
 442     private static final Function<Bounds, Double> BOUNDS_BOTTOM_SIDE = new Function<Bounds, Double>() {
 443 
 444         @Override
 445         public Double apply(Bounds t) {
 446             return t.getMaxY();
 447         }
 448     };
 449 
 450     protected Node getNearestNodeUpOrDown(Bounds currentB, Bounds originB, TraversalEngine engine, Node node, Node reversingNode, Direction dir) {
 451 
 452         List<Node> nodes = engine.getAllTargetNodes();
 453         
 454         Function<Bounds, Double> ySideInDirection = dir == DOWN ? BOUNDS_BOTTOM_SIDE : BOUNDS_TOP_SIDE;
 455         Function<Bounds, Double> ySideInOpositeDirection = dir == DOWN ? BOUNDS_TOP_SIDE : BOUNDS_BOTTOM_SIDE;
 456         
 457         Bounds biasedB = new BoundingBox(originB.getMinX(), currentB.getMinY(), originB.getWidth(), currentB.getHeight());
 458 
 459         Point2D currentMid2D = new Point2D(currentB.getMinX()+(currentB.getWidth()/2), currentB.getMinY());
 460         Point2D currenLeftCorner2D = new Point2D(currentB.getMinX(),ySideInDirection.apply(currentB));
 461         Point2D currentRightCorner2D = new Point2D(currentB.getMaxX(), ySideInDirection.apply(currentB));
 462 
 463         Point2D originLeftCorner2D = new Point2D(originB.getMinX(), ySideInDirection.apply(originB));
 464 
 465         TargetNode targetNode = new TargetNode();
 466         TargetNode nearestNodeCurrentSimple2D = null;
 467         TargetNode nearestNodeOriginSimple2D = null;
 468         TargetNode nearestNodeAverage = null;
 469         TargetNode nearestNodeOnOriginX = null;
 470         TargetNode nearestNodeOnCurrentX = null;
 471         TargetNode nearestNodeLeft = null;
 472         TargetNode nearestNodeAnythingAnywhere = null;
 473 
 474         if (nodes.size() > 0) {
 475             /*
 476              ** we've just changed direction, and have a node on stack.
 477              ** there is a strong preference for this node, just make sure
 478              ** it's not a bad choice, as sometimes we got here as a last-chance
 479              */
 480             if (reversingNode != null) {
 481                 return reversingNode;
 482             }
 483             
 484             for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) {
 485                 final Node n = nodes.get(nodeIndex);
 486 
 487                 Bounds targetBounds = n.localToScene(n.getLayoutBounds());
 488                 /*
 489                 ** check that the target node starts after we 
 490                 ** and the target node ends after we end
 491                 */
 492                 if (dir == UP ? (currentB.getMinY() > targetBounds.getMaxY()) :
 493                         currentB.getMaxY() < targetBounds.getMinY()) {
 494 
 495                     targetNode.node = n;
 496                     targetNode.bounds = targetBounds;
 497 
 498                     /*
 499                     ** closest biased : simple 2d
 500                     */
 501                     double outdB = outDistance(dir, biasedB, targetBounds);
 502 
 503                     if (isOnAxis(dir, biasedB, targetBounds)) {
 504                         targetNode.biased2DMetric = outdB + centerSideDistance(dir, biasedB, targetBounds) / 100;
 505                     }
 506                     else {
 507                         final double cosd = cornerSideDistance(dir, biasedB, targetBounds);
 508                         targetNode.biased2DMetric = 100000 + outdB*outdB + 9*cosd*cosd;
 509                     }
 510                     /*
 511                     ** closest current : simple 2d
 512                     */
 513                     double outdC = outDistance(dir, currentB, targetBounds);
 514 
 515                     if (isOnAxis(dir, currentB, targetBounds)) {
 516                         targetNode.current2DMetric = outdC + centerSideDistance(dir, currentB, targetBounds) / 100;
 517                     }
 518                     else {
 519                         final double cosd = cornerSideDistance(dir, currentB, targetBounds);
 520                         targetNode.current2DMetric = 100000 + outdC*outdC + 9*cosd*cosd;
 521                     }
 522 
 523                     targetNode.leftCornerDistance = currenLeftCorner2D.distance(targetBounds.getMinX(), ySideInOpositeDirection.apply(targetBounds));
 524                     targetNode.midDistance = currentMid2D.distance(targetBounds.getMinX()+(originB.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 525                     targetNode.rightCornerDistance = currentRightCorner2D.distance(originB.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 526 
 527                     double currentTopLeftToTargetMidDistance = currenLeftCorner2D.distance(targetBounds.getMinX()+(targetBounds.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 528                     double currentTopLeftToTargetBottomRightDistance = currenLeftCorner2D.distance(targetBounds.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 529                     double currentTopRightToTargetBottomLeftDistance = currentRightCorner2D.distance(targetBounds.getMinX(), ySideInOpositeDirection.apply(targetBounds));
 530                     double currentTopRightToTargetMidDistance = currentRightCorner2D.distance(targetBounds.getMinX()+(targetBounds.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 531                     double currentTopRightToTargetBottomRightDistance = currentRightCorner2D.distance(targetBounds.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 532                     double currentMidToTargetBottomLeftDistance = currentMid2D.distance(targetBounds.getMinX(), ySideInOpositeDirection.apply(targetBounds));
 533                     double currentMidToTargetMidDistance = currentMid2D.distance(targetBounds.getMinX()+(targetBounds.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 534                     double currentMidToTargetBottomRightDistance = currentMid2D.distance(targetBounds.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 535 
 536                     double biasTopLeftToTargetMidDistance = currenLeftCorner2D.distance(targetBounds.getMinX()+(originB.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 537                     double biasTopLeftToTargetBottomRightDistance = currenLeftCorner2D.distance(originB.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 538                     double biasTopRightToTargetMidDistance = currentRightCorner2D.distance(targetBounds.getMinX()+(originB.getWidth()/2), ySideInOpositeDirection.apply(targetBounds));
 539                     double biasMidToTargetBottomRightDistance = currentMid2D.distance(originB.getMaxX(), ySideInOpositeDirection.apply(targetBounds));
 540 
 541                     targetNode.averageDistance =
 542                         (targetNode.leftCornerDistance+biasTopLeftToTargetMidDistance+biasTopLeftToTargetBottomRightDistance+
 543                          currentTopRightToTargetBottomLeftDistance+targetNode.rightCornerDistance+biasTopRightToTargetMidDistance+targetNode.midDistance)/7;
 544 
 545                     targetNode.biasShortestDistance =
 546                         findMin(targetNode.leftCornerDistance, biasTopLeftToTargetMidDistance, biasTopLeftToTargetBottomRightDistance,
 547                                  currentTopRightToTargetBottomLeftDistance, biasTopRightToTargetMidDistance, targetNode.rightCornerDistance,
 548                                  currentMidToTargetBottomLeftDistance, targetNode.midDistance, biasMidToTargetBottomRightDistance);
 549 
 550                     targetNode.shortestDistance =
 551                         findMin(targetNode.leftCornerDistance, currentTopLeftToTargetMidDistance, currentTopLeftToTargetBottomRightDistance,
 552                                  currentTopRightToTargetBottomLeftDistance, currentTopRightToTargetMidDistance, currentTopRightToTargetBottomRightDistance,
 553                                  currentMidToTargetBottomLeftDistance, currentMidToTargetMidDistance, currentMidToTargetBottomRightDistance);
 554 
 555                     /*
 556                     ** closest biased : simple 2d
 557                     */
 558                     if (outdB >= 0.0) {
 559                         if (nearestNodeOriginSimple2D == null || targetNode.biased2DMetric < nearestNodeOriginSimple2D.biased2DMetric) {
 560 
 561                             if (nearestNodeOriginSimple2D == null) {
 562                                 nearestNodeOriginSimple2D = new TargetNode();
 563                             }
 564                             nearestNodeOriginSimple2D.copy(targetNode);
 565                         }
 566                     }
 567                     /*
 568                     ** closest current : simple 2d
 569                     */
 570                     if (outdC >= 0.0) {
 571                         if (nearestNodeCurrentSimple2D == null || targetNode.current2DMetric < nearestNodeCurrentSimple2D.current2DMetric) {
 572 
 573                             if (nearestNodeCurrentSimple2D == null) {
 574                                 nearestNodeCurrentSimple2D = new TargetNode();
 575                             }
 576                             nearestNodeCurrentSimple2D.copy(targetNode);
 577                         }
 578                     }
 579                     /*
 580                     ** on the Origin X
 581                     */
 582                     if ((originB.getMaxX() > targetBounds.getMinX()) && (targetBounds.getMaxX() > originB.getMinX())) {
 583                         if (nearestNodeOnOriginX == null || nearestNodeOnOriginX.biasShortestDistance > targetNode.biasShortestDistance) {
 584 
 585                             if (nearestNodeOnOriginX == null) {
 586                                 nearestNodeOnOriginX = new TargetNode();
 587                             }
 588                             nearestNodeOnOriginX.copy(targetNode);
 589                         }
 590                     }
 591                     /*
 592                     ** on the Current X
 593                     */
 594                     if ((currentB.getMaxX() > targetBounds.getMinX()) && (targetBounds.getMaxX() > currentB.getMinX())) {
 595                         if (nearestNodeOnCurrentX == null || nearestNodeOnCurrentX.biasShortestDistance > targetNode.biasShortestDistance) {
 596 
 597                             if (nearestNodeOnCurrentX == null) {
 598                                 nearestNodeOnCurrentX = new TargetNode();
 599                             }
 600                             nearestNodeOnCurrentX.copy(targetNode);
 601                         }
 602                     }
 603                     /*
 604                     ** Closest top left / bottom left corners.
 605                     */
 606                     if (nearestNodeLeft == null || nearestNodeLeft.leftCornerDistance > targetNode.leftCornerDistance) {
 607                         if (((originB.getMinX() >= currentB.getMinX()) && (targetBounds.getMinX() >= currentB.getMinX()))  ||
 608                             ((originB.getMinX() <= currentB.getMinX()) && (targetBounds.getMinX() <= currentB.getMinX()))) {
 609 
 610                             if (nearestNodeLeft == null) {
 611                                 nearestNodeLeft = new TargetNode();
 612                             }
 613                             nearestNodeLeft.copy(targetNode);
 614                         }
 615                     }
 616 
 617                     if (nearestNodeAverage == null || nearestNodeAverage.averageDistance > targetNode.averageDistance) {
 618                         if (((originB.getMinX() >= currentB.getMinX()) && (targetBounds.getMinX() >= currentB.getMinX()))  ||
 619                             ((originB.getMinX() <= currentB.getMinX()) && (targetBounds.getMinX() <= currentB.getMinX()))) {
 620 
 621                             if (nearestNodeAverage == null) {
 622                                 nearestNodeAverage = new TargetNode();
 623                             }
 624                             nearestNodeAverage.copy(targetNode);
 625                         }
 626                     }
 627 
 628                     if (nearestNodeAnythingAnywhere == null || nearestNodeAnythingAnywhere.shortestDistance > targetNode.shortestDistance) {
 629 
 630                         if (nearestNodeAnythingAnywhere == null) {
 631                             nearestNodeAnythingAnywhere = new TargetNode();
 632                         }
 633                         nearestNodeAnythingAnywhere.copy(targetNode);
 634                     }
 635                 }
 636             }
 637         }
 638         nodes.clear();
 639 
 640         if (nearestNodeOnOriginX != null) {
 641             nearestNodeOnOriginX.originLeftCornerDistance = originLeftCorner2D.distance(nearestNodeOnOriginX.bounds.getMinX(), ySideInOpositeDirection.apply(nearestNodeOnOriginX.bounds));
 642         }
 643 
 644         if (nearestNodeOnCurrentX != null) {
 645             nearestNodeOnCurrentX.originLeftCornerDistance = originLeftCorner2D.distance(nearestNodeOnCurrentX.bounds.getMinX(), ySideInOpositeDirection.apply(nearestNodeOnCurrentX.bounds));
 646         }
 647 
 648         if (nearestNodeAverage != null) {
 649             nearestNodeAverage.originLeftCornerDistance = originLeftCorner2D.distance(nearestNodeAverage.bounds.getMinX(), ySideInOpositeDirection.apply(nearestNodeAverage.bounds));
 650         }
 651 
 652         if (focusLogger.isLoggable(Level.FINER)) {
 653             if (nearestNodeOriginSimple2D != null) {
 654                 focusLogger.finer("nearestNodeOriginSimple2D.node : "+nearestNodeOriginSimple2D.node);
 655             }
 656             if (nearestNodeCurrentSimple2D != null) {
 657                 focusLogger.finer("nearestNodeCurrentSimple2D.node : "+nearestNodeCurrentSimple2D.node);
 658             }
 659             if (nearestNodeOnOriginX != null) {
 660                 focusLogger.finer("nearestNodeOnOriginX.node : "+nearestNodeOnOriginX.node);
 661             }
 662             if (nearestNodeOnCurrentX != null) {
 663                 focusLogger.finer("nearestNodeOnCurrentX.node : "+nearestNodeOnCurrentX.node);
 664             }
 665             if (nearestNodeAverage != null) {
 666                 focusLogger.finer("nearestNodeAverageUp.node : "+nearestNodeAverage.node);
 667             }
 668             if (nearestNodeLeft != null) {
 669                 focusLogger.finer("nearestNodeTopLeft.node : "+nearestNodeLeft.node);
 670             }
 671             if (nearestNodeAnythingAnywhere != null) {
 672                 focusLogger.finer("nearestNodeAnythingAnywhereUp.node : "+nearestNodeAnythingAnywhere.node);
 673             }
 674         }
 675 
 676         if (nearestNodeOnOriginX != null && nearestNodeOnOriginX.biasShortestDistance < Double.MAX_VALUE) {
 677             /*
 678             ** there's a preference, all else being equal, to return nearestNodeOnOriginX
 679             */
 680             if (nearestNodeOnCurrentX != null && nearestNodeOnOriginX.node == nearestNodeOnCurrentX.node
 681                     && ((nearestNodeAverage != null && nearestNodeOnOriginX.node == nearestNodeAverage.node)
 682                     || (nearestNodeOriginSimple2D != null && nearestNodeOnOriginX.node == nearestNodeOriginSimple2D.node)
 683                     || (nearestNodeLeft != null && nearestNodeOnOriginX.node == nearestNodeLeft.node)
 684                     || (nearestNodeAnythingAnywhere != null && nearestNodeOnOriginX.node == nearestNodeAnythingAnywhere.node))) {
 685                 return nearestNodeOnOriginX.node;
 686             }
 687             if (nearestNodeAverage != null && nearestNodeOnOriginX.node == nearestNodeAverage.node) {
 688                 return nearestNodeOnOriginX.node;
 689             }
 690 
 691             if (nearestNodeOnCurrentX != null && nearestNodeOnCurrentX.biasShortestDistance < Double.MAX_VALUE) {
 692                 if ((nearestNodeOnCurrentX.leftCornerDistance < nearestNodeOnOriginX.leftCornerDistance) &&
 693                     (nearestNodeOnCurrentX.originLeftCornerDistance < nearestNodeOnOriginX.originLeftCornerDistance) &&
 694                 (nearestNodeOnCurrentX.bounds.getMinX() - currenLeftCorner2D.getX()) < (nearestNodeOnOriginX.bounds.getMinX() - currenLeftCorner2D.getX())) {
 695 
 696                     return nearestNodeOnCurrentX.node;
 697                 } else if (nearestNodeAverage == null || nearestNodeOnOriginX.averageDistance < nearestNodeAverage.averageDistance) {
 698                     return nearestNodeOnOriginX.node;
 699                 }
 700             }
 701         } else {
 702             if (nearestNodeOnOriginX == null && nearestNodeOnCurrentX == null && nearestNodeCurrentSimple2D != null) {
 703                 if (nearestNodeAverage != null && nearestNodeLeft != null && (nearestNodeAverage.node == nearestNodeLeft.node && nearestNodeAverage.node == nearestNodeAnythingAnywhere.node)) {
 704                     return nearestNodeAverage.node;
 705                 }
 706                 return nearestNodeCurrentSimple2D.node;
 707             } else if (nearestNodeAverage != null && nearestNodeLeft != null && nearestNodeAnythingAnywhere != null
 708                     &&     nearestNodeAverage.biasShortestDistance == nearestNodeLeft.biasShortestDistance &&
 709                      nearestNodeAverage.biasShortestDistance == nearestNodeAnythingAnywhere.biasShortestDistance &&
 710                      nearestNodeAverage.biasShortestDistance < Double.MAX_VALUE) {
 711 
 712                 if (nearestNodeOnOriginX != null && nearestNodeOnOriginX.originLeftCornerDistance < nearestNodeAverage.originLeftCornerDistance) {
 713                     return nearestNodeOnOriginX.node;
 714                 } else {
 715                     return nearestNodeAverage.node;
 716                 }
 717             }
 718         }
 719 
 720         /*
 721         ** is the average closer?
 722         */
 723         if (nearestNodeAverage != null && (nearestNodeOnOriginX == null || (nearestNodeAverage.biasShortestDistance < nearestNodeOnOriginX.biasShortestDistance))) {
 724             /*
 725             ** but is one in the way
 726             */
 727             if (nearestNodeOnOriginX != null && (ySideInOpositeDirection.apply(nearestNodeOnOriginX.bounds) >= ySideInOpositeDirection.apply(nearestNodeAverage.bounds))) {
 728                 return nearestNodeOnOriginX.node;
 729             }
 730             if (nearestNodeOriginSimple2D != null) {
 731                 if (nearestNodeOriginSimple2D.current2DMetric <= nearestNodeAverage.current2DMetric) {
 732                     return nearestNodeOriginSimple2D.node;
 733                 }
 734                 if (ySideInOpositeDirection.apply(nearestNodeOriginSimple2D.bounds) >= ySideInOpositeDirection.apply(nearestNodeAverage.bounds)) {
 735                     return nearestNodeOriginSimple2D.node;
 736                 }
 737             }
 738             return nearestNodeAverage.node;
 739         }
 740 
 741         /*
 742         ** this is an odd one, in that is isn't the closest on current, or on the
 743         ** origin, but it looks better for most cases...
 744         */
 745         if ((nearestNodeCurrentSimple2D != null && nearestNodeOnCurrentX != null && nearestNodeAverage != null && nearestNodeLeft != null && nearestNodeAnythingAnywhere != null) &&
 746             (nearestNodeCurrentSimple2D.node == nearestNodeOnCurrentX.node) &&
 747             (nearestNodeCurrentSimple2D.node ==  nearestNodeAverage.node) &&
 748             (nearestNodeCurrentSimple2D.node == nearestNodeLeft.node) &&
 749             (nearestNodeCurrentSimple2D.node == nearestNodeAnythingAnywhere.node)) {
 750             return nearestNodeCurrentSimple2D.node;
 751         }
 752 
 753         if (nearestNodeOnOriginX != null && (nearestNodeOnCurrentX == null || (nearestNodeOnOriginX.rightCornerDistance < nearestNodeOnCurrentX.rightCornerDistance))) {
 754             return nearestNodeOnOriginX.node;
 755         }
 756         /*
 757         ** There isn't a clear winner, just go to the one nearest the current
 758          ** focus owner, or if invalid then try the other contenders.
 759          */
 760         if (nearestNodeOnOriginX != null) {
 761             return nearestNodeOnOriginX.node;
 762         } else if (nearestNodeOriginSimple2D != null) {
 763             return nearestNodeOriginSimple2D.node;
 764         } else if (nearestNodeOnCurrentX != null) {
 765             return nearestNodeOnCurrentX.node;
 766         } else if (nearestNodeAverage != null) {
 767             return nearestNodeAverage.node;
 768         } else if (nearestNodeLeft != null) {
 769             return nearestNodeLeft.node;
 770         } else if (nearestNodeAnythingAnywhere != null) {
 771             return nearestNodeAnythingAnywhere.node;
 772         }
 773         return null;
 774     }
 775     
 776     private static final Function<Bounds, Double> BOUNDS_LEFT_SIDE = new Function<Bounds, Double>() {
 777 
 778         @Override
 779         public Double apply(Bounds t) {
 780             return t.getMinX();
 781         }
 782     };
 783     
 784     private static final Function<Bounds, Double> BOUNDS_RIGHT_SIDE = new Function<Bounds, Double>() {
 785 
 786         @Override
 787         public Double apply(Bounds t) {
 788             return t.getMaxX();
 789         }
 790     };
 791 
 792     protected Node getNearestNodeLeftOrRight(Bounds currentB, Bounds originB, TraversalEngine engine, Node node, Node reversingNode, Direction dir) {
 793 
 794         List<Node> nodes = engine.getAllTargetNodes();
 795         
 796         Function<Bounds, Double> xSideInDirection = dir == LEFT ? BOUNDS_LEFT_SIDE : BOUNDS_RIGHT_SIDE;
 797         Function<Bounds, Double> xSideInOpositeDirection = dir == LEFT ? BOUNDS_RIGHT_SIDE : BOUNDS_LEFT_SIDE;
 798 
 799         Bounds biasedB = new BoundingBox(xSideInDirection.apply(currentB), originB.getMinY(), currentB.getWidth(), originB.getHeight());
 800 
 801         Point2D currentMid2D = new Point2D(xSideInDirection.apply(currentB), currentB.getMinY()+(currentB.getHeight()/2));
 802         Point2D currentTopCorner2D = new Point2D(xSideInDirection.apply(currentB), currentB.getMinY());
 803         Point2D currentBottomCorner2D = new Point2D(xSideInDirection.apply(currentB), currentB.getMaxY());
 804 
 805         Point2D originTopCorner2D = new Point2D(xSideInDirection.apply(originB), originB.getMinY());
 806 
 807         TargetNode targetNode = new TargetNode();
 808         TargetNode nearestNodeCurrentSimple2D = null;
 809         TargetNode nearestNodeOriginSimple2D = null;
 810         TargetNode nearestNodeAverage = null;
 811         TargetNode nearestNodeOnOriginY = null;
 812         TargetNode nearestNodeOnCurrentY = null;
 813         TargetNode nearestNodeTopLeft = null;
 814         TargetNode nearestNodeAnythingAnywhereLeft = null;
 815 
 816         if (nodes.size() > 0) {
 817             /*
 818              ** we've just changed direction, and have a node on stack.
 819              ** there is a strong preference for this node, just make sure
 820              ** it's not a bad choice, as sometimes we got here as a last-chance
 821              */
 822             if (reversingNode != null) {
 823                 return reversingNode;
 824             }
 825             
 826             for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) {
 827                 final Node n = nodes.get(nodeIndex);
 828 
 829                 Bounds targetBounds = n.localToScene(n.getLayoutBounds());
 830                 /*
 831                 ** check that the target node starts after we start
 832                 ** and the target node ends after we end 
 833                 */
 834                 if (dir == LEFT ? currentB.getMinX() >  targetBounds.getMinX() : 
 835                         currentB.getMaxX() < targetBounds.getMaxX()) {
 836 
 837                     targetNode.node = n;
 838                     targetNode.bounds = targetBounds;
 839 
 840                     /*
 841                     ** closest biased : simple 2d
 842                     */
 843                     double outdB = outDistance(dir, biasedB, targetBounds);
 844 
 845                     if (isOnAxis(dir, biasedB, targetBounds)) {
 846                         targetNode.biased2DMetric = outdB + centerSideDistance(dir, biasedB, targetBounds) / 100;
 847                     }
 848                     else {
 849                         final double cosd = cornerSideDistance(dir, biasedB, targetBounds);
 850                         targetNode.biased2DMetric = 100000 + outdB*outdB + 9*cosd*cosd;
 851                     }
 852                     /*
 853                     ** closest current : simple 2d
 854                     */
 855                     double outdC = outDistance(dir, currentB, targetBounds);
 856 
 857                     if (isOnAxis(dir, currentB, targetBounds)) {
 858                         targetNode.current2DMetric = outdC + centerSideDistance(dir, currentB, targetBounds) / 100;
 859                     }
 860                     else {
 861                         final double cosd = cornerSideDistance(dir, currentB, targetBounds);
 862                         targetNode.current2DMetric = 100000 + outdC*outdC + 9*cosd*cosd;
 863                     }
 864 
 865                     targetNode.topCornerDistance = currentTopCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY());
 866                     targetNode.midDistance = currentMid2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(originB.getHeight()/2));
 867                     targetNode.bottomCornerDistance = currentBottomCorner2D.distance(xSideInOpositeDirection.apply(originB), targetBounds.getMaxY());
 868 
 869                     double currentTopLeftToTargetBottomRightDistance = currentTopCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMaxY());
 870                     double currentTopLeftToTargetMidDistance = currentTopCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(targetBounds.getHeight()/2));
 871                     double currentBottomLeftToTargetTopRightDistance = currentBottomCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY());
 872                     double currentBottomLeftToTargetBottomRightDistance = currentBottomCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMaxY());
 873                     double currentBottomLeftToTargetMidDistance = currentBottomCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(targetBounds.getHeight()/2));
 874                     double currentMidToTargetTopRightDistance = currentMid2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY());
 875                     double currentMidToTargetBottomRightDistance = currentMid2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMaxY());
 876                     double currentMidToTargetMidDistance = currentMid2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(targetBounds.getHeight()/2));
 877 
 878                     double biasTopLeftToTargetBottomRightDistance = currentTopCorner2D.distance(xSideInOpositeDirection.apply(originB), targetBounds.getMaxY());
 879                     double biasTopLeftToTargetMidDistance = currentTopCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(originB.getHeight()/2));
 880                     double biasBottomLeftToTargetMidDistance = currentBottomCorner2D.distance(xSideInOpositeDirection.apply(targetBounds), targetBounds.getMinY()+(originB.getHeight()/2));
 881                     double biasMidToTargetBottomRightDistance = currentMid2D.distance(xSideInOpositeDirection.apply(originB), targetBounds.getMaxY());
 882 
 883                     targetNode.averageDistance =
 884                         (targetNode.topCornerDistance+biasTopLeftToTargetBottomRightDistance+biasTopLeftToTargetMidDistance+
 885                          currentBottomLeftToTargetTopRightDistance+targetNode.bottomCornerDistance+biasBottomLeftToTargetMidDistance)/7;
 886 
 887                     targetNode.biasShortestDistance =
 888                         findMin(targetNode.topCornerDistance, biasTopLeftToTargetBottomRightDistance, biasTopLeftToTargetMidDistance,
 889                                  currentBottomLeftToTargetTopRightDistance, targetNode.bottomCornerDistance, biasBottomLeftToTargetMidDistance,
 890                                  currentMidToTargetTopRightDistance, biasMidToTargetBottomRightDistance, targetNode.midDistance);
 891 
 892                     targetNode.shortestDistance =
 893                         findMin(targetNode.topCornerDistance, currentTopLeftToTargetBottomRightDistance, currentTopLeftToTargetMidDistance,
 894                                  currentBottomLeftToTargetTopRightDistance, currentBottomLeftToTargetBottomRightDistance, currentBottomLeftToTargetMidDistance,
 895                                  currentMidToTargetTopRightDistance, currentMidToTargetBottomRightDistance, currentMidToTargetMidDistance);
 896 
 897                     /*
 898                     ** closest biased : simple 2d
 899                     */
 900                     if (outdB >= 0.0) {
 901                         if (nearestNodeOriginSimple2D == null || targetNode.biased2DMetric < nearestNodeOriginSimple2D.biased2DMetric) {
 902 
 903                             if (nearestNodeOriginSimple2D == null) {
 904                                 nearestNodeOriginSimple2D = new TargetNode();
 905                             }
 906                             nearestNodeOriginSimple2D.copy(targetNode);
 907                         }
 908                     }
 909                     /*
 910                     ** closest current : simple 2d
 911                     */
 912                     if (outdC >= 0.0) {
 913                         if (nearestNodeCurrentSimple2D == null || targetNode.current2DMetric < nearestNodeCurrentSimple2D.current2DMetric) {
 914 
 915                             if (nearestNodeCurrentSimple2D == null) {
 916                                 nearestNodeCurrentSimple2D = new TargetNode();
 917                             }
 918                             nearestNodeCurrentSimple2D.copy(targetNode);
 919                         }
 920                     }
 921                     /*
 922                     ** on the Origin Y
 923                     */
 924                     if ((originB.getMaxY() > targetBounds.getMinY()) && (targetBounds.getMaxY() > originB.getMinY())) {
 925                         if (nearestNodeOnOriginY == null || nearestNodeOnOriginY.topCornerDistance > targetNode.topCornerDistance) {
 926 
 927                             if (nearestNodeOnOriginY == null) {
 928                                 nearestNodeOnOriginY = new TargetNode();
 929                             }
 930                             nearestNodeOnOriginY.copy(targetNode);
 931                         }
 932                     }
 933                     /*
 934                     ** on the Current Y
 935                     */
 936                     if ((currentB.getMaxY() > targetBounds.getMinY()) && (targetBounds.getMaxY() > currentB.getMinY())) {
 937                         if (nearestNodeOnCurrentY == null || nearestNodeOnCurrentY.topCornerDistance > targetNode.topCornerDistance) {
 938 
 939                             if (nearestNodeOnCurrentY == null) {
 940                                 nearestNodeOnCurrentY = new TargetNode();
 941                             }
 942                             nearestNodeOnCurrentY.copy(targetNode);
 943                         }
 944                     }
 945                     /*
 946                     ** Closest top left / top right corners.
 947                     */
 948                     if (nearestNodeTopLeft == null || nearestNodeTopLeft.topCornerDistance > targetNode.topCornerDistance) {
 949 
 950                         if (nearestNodeTopLeft == null) {
 951                             nearestNodeTopLeft = new TargetNode();
 952                         }
 953                         nearestNodeTopLeft.copy(targetNode);
 954                     }
 955 
 956                     if (nearestNodeAverage == null || nearestNodeAverage.averageDistance > targetNode.averageDistance) {
 957 
 958                         if (nearestNodeAverage == null) {
 959                             nearestNodeAverage = new TargetNode();
 960                         }
 961                         nearestNodeAverage.copy(targetNode);
 962                     }
 963 
 964                     if (nearestNodeAnythingAnywhereLeft == null || nearestNodeAnythingAnywhereLeft.shortestDistance > targetNode.shortestDistance) {
 965 
 966                         if (nearestNodeAnythingAnywhereLeft == null) {
 967                             nearestNodeAnythingAnywhereLeft = new TargetNode();
 968                         }
 969                         nearestNodeAnythingAnywhereLeft.copy(targetNode);
 970                     }
 971                 }
 972             }
 973         }
 974         nodes.clear();
 975 
 976         if (nearestNodeOnOriginY != null) {
 977             nearestNodeOnOriginY.originTopCornerDistance = originTopCorner2D.distance(xSideInOpositeDirection.apply(nearestNodeOnOriginY.bounds), nearestNodeOnOriginY.bounds.getMinY());
 978         }
 979         
 980         if (nearestNodeOnCurrentY != null) {
 981             nearestNodeOnCurrentY.originTopCornerDistance = originTopCorner2D.distance(xSideInOpositeDirection.apply(nearestNodeOnCurrentY.bounds), nearestNodeOnCurrentY.bounds.getMinY());
 982         }
 983         
 984         if (nearestNodeAverage != null) {
 985             nearestNodeAverage.originTopCornerDistance = originTopCorner2D.distance(xSideInOpositeDirection.apply(nearestNodeAverage.bounds), nearestNodeAverage.bounds.getMinY());
 986         }
 987 
 988         if (nearestNodeOnCurrentY == null && nearestNodeOnOriginY == null) {
 989             cacheStartTraversalNode = null;
 990             cacheStartTraversalDirection = null;
 991             reverseDirection = false;
 992             traversalNodeStack.clear();
 993         }
 994 
 995         if (focusLogger.isLoggable(Level.FINER)) {
 996             if (nearestNodeOriginSimple2D != null) {
 997                 focusLogger.finer("nearestNodeOriginSimple2D.node : "+nearestNodeOriginSimple2D.node);
 998             }
 999             if (nearestNodeCurrentSimple2D != null) {
1000                 focusLogger.finer("nearestNodeCurrentSimple2D.node : "+nearestNodeCurrentSimple2D.node);
1001             }
1002             if (nearestNodeOnOriginY != null) {
1003                 focusLogger.finer("nearestNodeOnOriginY.node : "+nearestNodeOnOriginY.node);
1004             }
1005             if (nearestNodeOnCurrentY != null) {
1006                 focusLogger.finer("nearestNodeOnCurrentY.node : "+nearestNodeOnCurrentY.node);
1007             }
1008             if (nearestNodeAverage != null) {
1009                 focusLogger.finer("nearestNodeAverageLeft.node : "+nearestNodeAverage.node);
1010             }
1011             if (nearestNodeTopLeft != null) {
1012                 focusLogger.finer("nearestNodeTopLeft.node : "+nearestNodeTopLeft.node);
1013             }
1014             if (nearestNodeAnythingAnywhereLeft != null) {
1015                 focusLogger.finer("nearestNodeAnythingAnywhereLeft.node : "+nearestNodeAnythingAnywhereLeft.node);
1016             }
1017         }
1018 
1019         if (nearestNodeOnOriginY != null && nearestNodeOnOriginY.biasShortestDistance < Double.MAX_VALUE) {
1020             /*
1021              ** there's a preference, all else being equal, to return nearestNodeOnOriginY
1022              */
1023             if (nearestNodeOnCurrentY != null && nearestNodeOnOriginY.node == nearestNodeOnCurrentY.node
1024                     && ((nearestNodeAverage != null && nearestNodeOnOriginY.node == nearestNodeAverage.node)
1025                     || (nearestNodeTopLeft != null && nearestNodeOnOriginY.node == nearestNodeTopLeft.node)
1026                     || (nearestNodeAnythingAnywhereLeft != null && nearestNodeOnOriginY.node == nearestNodeAnythingAnywhereLeft.node))) {
1027                 return nearestNodeOnOriginY.node;
1028             }
1029 
1030             if (nearestNodeAverage != null && nearestNodeOnOriginY.node == nearestNodeAverage.node) {
1031                 return nearestNodeOnOriginY.node;
1032             }
1033 
1034             if (nearestNodeOnCurrentY != null && nearestNodeOnCurrentY.biasShortestDistance < Double.MAX_VALUE) {
1035                 if ((nearestNodeOnCurrentY.bottomCornerDistance < nearestNodeOnOriginY.bottomCornerDistance)
1036                         && (nearestNodeOnCurrentY.originTopCornerDistance < nearestNodeOnOriginY.originTopCornerDistance)
1037                         && (nearestNodeOnCurrentY.bounds.getMinY() - currentTopCorner2D.getY()) < (nearestNodeOnOriginY.bounds.getMinY() - currentTopCorner2D.getY())) {
1038 
1039                     return nearestNodeOnCurrentY.node;
1040                 } else if (nearestNodeAverage == null || nearestNodeOnOriginY.averageDistance < nearestNodeAverage.averageDistance) {
1041                     return nearestNodeOnOriginY.node;
1042                 }
1043             }
1044         } else {
1045             if (nearestNodeOnOriginY == null && nearestNodeOnCurrentY == null && nearestNodeCurrentSimple2D != null) {
1046                 if (nearestNodeAverage != null && nearestNodeTopLeft != null
1047                         && nearestNodeAverage.node == nearestNodeTopLeft.node && nearestNodeAverage.node == nearestNodeAnythingAnywhereLeft.node) {
1048                     return nearestNodeAverage.node;
1049                 }
1050                 return nearestNodeCurrentSimple2D.node;
1051             } else if (nearestNodeAverage != null && nearestNodeTopLeft != null && nearestNodeAnythingAnywhereLeft != null
1052                     && nearestNodeAverage.biasShortestDistance == nearestNodeTopLeft.biasShortestDistance
1053                     && nearestNodeAverage.biasShortestDistance == nearestNodeAnythingAnywhereLeft.biasShortestDistance
1054                     && nearestNodeAverage.biasShortestDistance < Double.MAX_VALUE) {
1055 
1056                 if (nearestNodeOnOriginY != null && nearestNodeOnOriginY.originTopCornerDistance < nearestNodeAverage.originTopCornerDistance) {
1057                     return nearestNodeOnOriginY.node;
1058                 } else {
1059                     return nearestNodeAverage.node;
1060                 }
1061             }
1062         }
1063 
1064         /*
1065         ** is the average closer?
1066         */
1067         if (nearestNodeAverage != null && (nearestNodeOnOriginY == null || nearestNodeAverage.biasShortestDistance < nearestNodeOnOriginY.biasShortestDistance)) {
1068             /*
1069             ** but is one in the way
1070             */
1071             if (nearestNodeOnOriginY != null && (xSideInOpositeDirection.apply(nearestNodeOnOriginY.bounds) >= xSideInOpositeDirection.apply(nearestNodeAverage.bounds))) {
1072                 return nearestNodeOnOriginY.node;
1073             }
1074             /*
1075             ** maybe Origin is better than this?
1076             */
1077             if (nearestNodeOnOriginY != null && nearestNodeOnCurrentY != null && nearestNodeOnOriginY.biasShortestDistance < Double.MAX_VALUE && (nearestNodeOnOriginY.node == nearestNodeOnCurrentY.node)) {
1078                 return nearestNodeOnOriginY.node;
1079             }
1080 
1081             if (nearestNodeOnCurrentY != null && nearestNodeOnOriginY != null && nearestNodeOnCurrentY.biasShortestDistance < Double.MAX_VALUE && (nearestNodeOnCurrentY.biasShortestDistance < nearestNodeOnOriginY.biasShortestDistance)) {
1082                 return nearestNodeOnCurrentY.node;
1083             }
1084 
1085             if (nearestNodeOnOriginY != null && nearestNodeOnOriginY.biasShortestDistance < Double.MAX_VALUE && (nearestNodeOnOriginY.originTopCornerDistance < nearestNodeAverage.originTopCornerDistance)) {
1086                 return nearestNodeOnOriginY.node;
1087             }
1088             return nearestNodeAverage.node;
1089         }
1090 
1091 
1092         if (nearestNodeOnOriginY != null && nearestNodeOnCurrentY != null && nearestNodeOnOriginY.bottomCornerDistance < nearestNodeOnCurrentY.bottomCornerDistance) {
1093             return nearestNodeOnOriginY.node;
1094         }
1095 
1096         /*
1097         ** if any of the remaining match we'll take that
1098         */
1099         if (nearestNodeOnCurrentY != null && nearestNodeTopLeft != null && nearestNodeOnCurrentY.biasShortestDistance < Double.MAX_VALUE && (nearestNodeOnCurrentY.node == nearestNodeTopLeft.node)) {
1100             return nearestNodeOnCurrentY.node;
1101         }
1102         /*
1103         ** There isn't a clear winner, just go to the one nearest the current
1104         ** focus owner, or if invalid then try the other contenders.
1105         */
1106         if (nearestNodeOnOriginY != null) {
1107             return nearestNodeOnOriginY.node;
1108         } else if (nearestNodeOriginSimple2D != null) {
1109             return nearestNodeOriginSimple2D.node;
1110         } else if (nearestNodeOnCurrentY != null) {
1111             return nearestNodeOnCurrentY.node;
1112         } else if (nearestNodeAverage != null) {
1113             return nearestNodeAverage.node;
1114         } else if (nearestNodeTopLeft != null) {
1115             return nearestNodeTopLeft.node;
1116         } else if (nearestNodeAnythingAnywhereLeft != null) {
1117             return nearestNodeAnythingAnywhereLeft.node;
1118         }
1119         return null;
1120     }
1121 
1122     static final class TargetNode {
1123         Node node = null;
1124         Bounds bounds = null;
1125         double biased2DMetric = Double.MAX_VALUE;
1126         double current2DMetric = Double.MAX_VALUE;
1127 
1128         double leftCornerDistance = Double.MAX_VALUE;
1129         double midDistance = Double.MAX_VALUE;
1130         double rightCornerDistance = Double.MAX_VALUE;
1131         double topCornerDistance = Double.MAX_VALUE;
1132         double bottomCornerDistance = Double.MAX_VALUE;
1133 
1134         double shortestDistance = Double.MAX_VALUE;
1135         double biasShortestDistance = Double.MAX_VALUE;
1136         double averageDistance = Double.MAX_VALUE;
1137 
1138         double originLeftCornerDistance = Double.MAX_VALUE;
1139         double originTopCornerDistance = Double.MAX_VALUE;
1140 
1141         void copy(TargetNode source) {
1142             node = source.node;
1143             bounds = source.bounds;
1144             biased2DMetric = source.biased2DMetric;
1145             current2DMetric = source.current2DMetric;
1146 
1147             leftCornerDistance = source.leftCornerDistance;
1148             midDistance = source.midDistance;
1149             rightCornerDistance = source.rightCornerDistance;
1150 
1151             shortestDistance = source.shortestDistance;
1152             biasShortestDistance = source.biasShortestDistance;
1153             averageDistance = source.averageDistance;
1154 
1155             topCornerDistance = source.topCornerDistance;
1156             bottomCornerDistance = source.bottomCornerDistance;
1157             originLeftCornerDistance = source.originLeftCornerDistance;
1158             originTopCornerDistance = source.originTopCornerDistance;
1159         }
1160     }
1161 
1162     public static double findMin(double... values) {
1163 
1164         double minValue = Double.MAX_VALUE;
1165         
1166         for (int i = 0 ; i < values.length ; i++) {
1167             minValue = (minValue < values[i]) ? minValue : values[i];
1168         }
1169         return minValue;
1170     }
1171 }