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 }