Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene; 027 028import java.util.ArrayList; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032import javafx.beans.property.ObjectProperty; 033import javafx.beans.property.ReadOnlyBooleanProperty; 034import javafx.beans.property.ReadOnlyBooleanWrapper; 035import javafx.beans.property.SimpleObjectProperty; 036import javafx.collections.FXCollections; 037import javafx.collections.ListChangeListener.Change; 038import javafx.collections.ObservableList; 039import com.sun.javafx.Logging; 040import com.sun.javafx.TempState; 041import com.sun.javafx.Utils; 042import com.sun.javafx.collections.TrackableObservableList; 043import com.sun.javafx.collections.VetoableListDecorator; 044import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; 045import com.sun.javafx.css.Selector; 046import com.sun.javafx.css.StyleManager; 047import com.sun.javafx.geom.BaseBounds; 048import com.sun.javafx.geom.PickRay; 049import com.sun.javafx.geom.Point2D; 050import com.sun.javafx.geom.RectBounds; 051import com.sun.javafx.geom.transform.BaseTransform; 052import com.sun.javafx.geom.transform.NoninvertibleTransformException; 053import com.sun.javafx.jmx.MXNodeAlgorithm; 054import com.sun.javafx.jmx.MXNodeAlgorithmContext; 055import sun.util.logging.PlatformLogger; 056import com.sun.javafx.scene.CssFlags; 057import com.sun.javafx.scene.DirtyBits; 058import com.sun.javafx.scene.input.PickResultChooser; 059import com.sun.javafx.scene.traversal.TraversalEngine; 060import com.sun.javafx.sg.PGGroup; 061import com.sun.javafx.sg.PGNode; 062import com.sun.javafx.tk.Toolkit; 063 064import static com.sun.javafx.logging.PulseLogger.*; 065 066/** 067 * The base class for all nodes that have children in the scene graph. 068 * <p> 069 * This class handles all hierarchical scene graph operations, including adding/removing 070 * child nodes, marking branches dirty for layout and rendering, picking, 071 * bounds calculations, and executing the layout pass on each pulse. 072 * <p> 073 * There are two direct concrete Parent subclasses 074 * <ul> 075 * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li> 076 * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li> 077 * </ul> 078 * 079 */ 080public abstract class Parent extends Node { 081 // package private for testing 082 static final int DIRTY_CHILDREN_THRESHOLD = 10; 083 084 // If set to true, generate a warning message whenever adding a node to a 085 // parent if it is currently a child of another parent. 086 private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn"); 087 088 /** 089 * Threshold when it's worth to populate list of removed children. 090 */ 091 private static final int REMOVED_CHILDREN_THRESHOLD = 20; 092 093 /** 094 * Do not populate list of removed children when its number exceeds threshold, 095 * but mark whole parent dirty. 096 */ 097 private boolean removedChildrenExceedsThreshold = false; 098 099 /** 100 * @treatAsPrivate implementation detail 101 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 102 */ 103 @Deprecated 104 @Override public void impl_updatePG() { 105 super.impl_updatePG(); 106 107 if (Utils.assertionEnabled()) { 108 List<PGNode> pgnodes = getPGGroup().getChildren(); 109 if (pgnodes.size() != pgChildrenSize) { 110 java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]"); 111 } 112 } 113 114 if (impl_isDirty(DirtyBits.PARENT_CHILDREN)) { 115 PGGroup peer = getPGGroup(); 116 // Whether a permutation, or children having been added or 117 // removed, we'll want to clear out the PG side starting 118 // from startIdx. We know that everything up to but not 119 // including startIdx is identical between the FX and PG 120 // sides, so we only need to update the remaining portion. 121 peer.clearFrom(startIdx); 122 for (int idx = startIdx; idx < children.size(); idx++) { 123 peer.add(idx, children.get(idx).impl_getPGNode()); 124 } 125 if (removedChildrenExceedsThreshold) { 126 peer.markDirty(); 127 removedChildrenExceedsThreshold = false; 128 } else { 129 if (removed != null && !removed.isEmpty()) { 130 for(int i = 0; i < removed.size(); i++) { 131 peer.addToRemoved(removed.get(i).impl_getPGNode()); 132 } 133 } 134 } 135 if (removed != null) { 136 removed.clear(); 137 } 138 pgChildrenSize = children.size(); 139 startIdx = pgChildrenSize; 140 } 141 142 if (Utils.assertionEnabled()) validatePG(); 143 } 144 145 146 /*********************************************************************** 147 * Scenegraph Structure * 148 * * 149 * Functions and variables related to the scenegraph structure, * 150 * modifying the structure, and walking the structure. * 151 * * 152 **********************************************************************/ 153 154 // Used to check for duplicate nodes 155 private final Set<Node> childSet = new HashSet<Node>(); 156 157 // starting child index from which we need to send the children to the PGGroup 158 private int startIdx = 0; 159 160 // double of children in the PGGroup as of the last update 161 private int pgChildrenSize = 0; 162 163 void validatePG() { 164 boolean assertionFailed = false; 165 List<PGNode> pgnodes = getPGGroup().getChildren(); 166 if (pgnodes.size() != children.size()) { 167 java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]"); 168 assertionFailed = true; 169 } else { 170 for (int idx = 0; idx < children.size(); idx++) { 171 Node n = children.get(idx); 172 if (n.getParent() != this) { 173 java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent()); 174 assertionFailed = true; 175 } 176 if (n.impl_getPGNode() != pgnodes.get(idx)) { 177 java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]"); 178 assertionFailed = true; 179 } 180 } 181 } 182 if (assertionFailed) { 183 throw new java.lang.AssertionError("validation of PGGroup children failed"); 184 } 185 186 } 187 188 void printSeq(String prefix, List<Node> nodes) { 189 String str = prefix; 190 for (Node nn : nodes) { 191 str += nn + " "; 192 } 193 System.out.println(str); 194 } 195 196 /** 197 * Variable used to avoid executing the body of the on replace trigger on 198 * children. This is specifically used when we know that changes to the 199 * children is going to be valid so as to avoid all the scenegraph surgery 200 * validation routines in the trigger. 201 */ 202 private boolean ignoreChildrenTrigger = false; 203 204 // Variable used to indicate that the change to the children ObservableList is 205 // a simple permutation as the result of a toFront or toBack operation. 206 // We can avoid almost all of the processing of the on replace trigger in 207 // this case. 208 private boolean childrenTriggerPermutation = false; 209 210 //accumulates all removed nodes between pulses, for dirty area calculation. 211 private List<Node> removed; 212 213 /** 214 * A ObservableList of child {@code Node}s. 215 * <p> 216 * See the class documentation for {@link Node} for scene graph structure 217 * restrictions on setting a {@link Parent}'s children ObservableList. 218 * If these restrictions are violated by a change to the children ObservableList, 219 * the change is ignored and the previous value of the child ObservableList is 220 * restored. 221 * 222 * {@code <p>Throws AssignToBoundException} if the same node 223 * appears in two different bound ObservableList. 224 * 225 * @defaultValue empty 226 * @since JavaFX 1.3 227 */ 228 229 // set to true if either childRemoved or childAdded returns 230 // true. These functions will indicate whether the geom 231 // bounds for the parent have changed 232 private boolean geomChanged; 233 private boolean childrenModified; 234 private final ObservableList<Node> children = new VetoableListDecorator<Node>(new TrackableObservableList<Node>() { 235 236 237 protected void onChanged(Change<Node> c) { 238 // proceed with updating the scene graph 239 if (childrenModified) { 240 unmodifiableManagedChildren = null; 241 boolean relayout = false; 242 243 while (c.next()) { 244 int from = c.getFrom(); 245 int to = c.getTo(); 246 for (int i = from; i < to; ++i) { 247 Node n = children.get(i); 248 if (n.getParent() != null && n.getParent() != Parent.this) { 249 if (warnOnAutoMove) { 250 java.lang.System.err.println("WARNING added to a new parent without first removing it from its current"); 251 java.lang.System.err.println(" parent. It will be automatically removed from its current parent."); 252 java.lang.System.err.println(" node=" + n + " oldparent= " + n.getParent() + " newparent=" + this); 253 } 254 n.getParent().children.remove(n); 255 if (n.isManaged()) { 256 relayout = true; 257 } 258 if (warnOnAutoMove) { 259 Thread.dumpStack(); 260 } 261 } 262 } 263 264 List<Node> removed = c.getRemoved(); 265 int removedSize = removed.size(); 266 for (int i = 0; i < removedSize; ++i) { 267 if (removed.get(i).isManaged()) { 268 relayout = true; 269 } 270 } 271 272 // update the parent and scene for each new node 273 for (int i = from; i < to; ++i) { 274 Node node = children.get(i); 275 if (node.isManaged()) { 276 relayout = true; 277 } 278 node.setParent(Parent.this); 279 node.setScenes(getScene(), getSubScene()); 280 // assert !node.boundsChanged; 281 if (node.isVisible()) { 282 geomChanged = true; 283 childIncluded(node); 284 } 285 } 286 } 287 288 // check to see if the number of children exceeds 289 // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null. 290 // If so, then we need to create dirtyChildren and 291 // populate it. 292 if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) { 293 dirtyChildren = 294 new ArrayList<Node>(2 * DIRTY_CHILDREN_THRESHOLD); 295 // only bother populating children if geom has 296 // changed, otherwise there is no need 297 if (dirtyChildrenCount > 0) { 298 int size = children.size(); 299 for (int i = 0; i < size; ++i) { 300 Node ch = children.get(i); 301 if (ch.isVisible() && ch.boundsChanged) { 302 dirtyChildren.add(ch); 303 } 304 } 305 } 306 } 307 308 if (geomChanged) { 309 impl_geomChanged(); 310 } 311 312 // 313 // Note that the styles of a child do not affect the parent or 314 // its siblings. Thus, it is only necessary to reapply css to 315 // the Node just added and not to this parent and all of its 316 // children. So the following call to impl_reapplyCSS was moved 317 // to Node.parentProperty. The original comment and code were 318 // purposely left here as documentation should there be any 319 // question about how the code used to work and why the change 320 // was made. 321 // 322 // if children have changed then I need to reapply 323 // CSS from this node on down 324// impl_reapplyCSS(); 325 // 326 327 // request layout if a Group subclass has overridden doLayout OR 328 // if one of the new children needs layout, in which case need to ensure 329 // the needsLayout flag is set all the way to the root so the next layout 330 // pass will reach the child. 331 if (relayout) { 332 requestLayout(); 333 } 334 } 335 336 // Note the starting index at which we need to update the 337 // PGGroup on the next update, and mark the children dirty 338 c.reset(); 339 c.next(); 340 if (startIdx > c.getFrom()) { 341 startIdx = c.getFrom(); 342 } 343 344 impl_markDirty(DirtyBits.PARENT_CHILDREN); 345 } 346 347 }) { 348 @Override 349 protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) { 350 if (ignoreChildrenTrigger) { 351 return; 352 } 353 if (Parent.this.getScene() != null) { 354 // NOTE: this will throw IllegalStateException if we are on the wrong thread 355 Toolkit.getToolkit().checkFxUserThread(); 356 } 357 geomChanged = false; 358 359 long newLength = children.size() + newNodes.size(); 360 int removedLength = 0; 361 for (int i = 0; i < toBeRemoved.length; i += 2) { 362 removedLength += toBeRemoved[i + 1] - toBeRemoved[i]; 363 } 364 newLength -= removedLength; 365 366 // If the childrenTriggerPermutation flag is set, then we know it 367 // is a simple permutation and no further checking is needed. 368 if (childrenTriggerPermutation) { 369 childrenModified = false; 370 return; 371 } 372 373 // If the childrenTriggerPermutation flag is not set, then we will 374 // check to see whether any element in the ObservableList has changed, 375 // or whether the new ObservableList is a permutation on the existing 376 // ObservableList. Note that even if the childrenModified flag is false, 377 // we still have to check for duplicates. If it is a simple 378 // permutation, we can avoid checking for cycles or other parents. 379 childrenModified = true; 380 if (newLength == childSet.size()) { 381 childrenModified = false; 382 for (int i = newNodes.size() - 1; i >= 0; --i ) { 383 Node n = newNodes.get(i); 384 if (!childSet.contains(n)) { 385 childrenModified = true; 386 break; 387 } 388 } 389 } 390 391 // Enforce scene graph invariants, and check for structural errors. 392 // 393 // 1. If a child has been added to this parent more than once, 394 // then it is an error 395 // 396 // 2. If a child is a target of a clip, then it is an error. 397 // 398 // 3. If a node would cause a cycle, then it is an error. 399 // 400 // 4. If a node is null 401 // 402 // Note that if a node is the child of another parent, we will 403 // implicitly remove the node from its former Parent after first 404 // checking for errors. 405 406 // iterate over the nodes that were removed and remove them from 407 // the hash set. 408 for (int i = 0; i < toBeRemoved.length; i += 2) { 409 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) { 410 childSet.remove(children.get(j)); 411 } 412 } 413 414 try { 415 if (childrenModified) { 416 // check individual children before duplication test 417 // if done in this order, the exception is more specific 418 for (int i = newNodes.size() - 1; i >= 0; --i ) { 419 Node node = newNodes.get(i); 420 if (node == null) { 421 throw new NullPointerException( 422 constructExceptionMessage( 423 "child node is null", null)); 424 } 425 if (node.getClipParent() != null) { 426 throw new IllegalArgumentException( 427 constructExceptionMessage( 428 "node already used as a clip", node)); 429 } 430 if (wouldCreateCycle(Parent.this, node)) { 431 throw new IllegalArgumentException( 432 constructExceptionMessage( 433 "cycle detected", node)); 434 } 435 } 436 } 437 438 childSet.addAll(newNodes); 439 if (childSet.size() != newLength) { 440 throw new IllegalArgumentException( 441 constructExceptionMessage( 442 "duplicate children added", null)); 443 } 444 } catch (RuntimeException e) { 445 //Return children to it's original state 446 childSet.clear(); 447 childSet.addAll(children); 448 449 // rethrow 450 throw e; 451 } 452 453 // Done with error checking 454 455 if (!childrenModified) { 456 return; 457 } 458 459 // iterate over the nodes that were removed and clear their 460 // parent and scene. Add to them also to removed list for further 461 // dirty regions calculation. 462 if (removed == null) { 463 removed = new ArrayList<Node>(); 464 } 465 if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD) { 466 //do not populate too many children in removed list 467 removedChildrenExceedsThreshold = true; 468 } 469 for (int i = 0; i < toBeRemoved.length; i += 2) { 470 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) { 471 Node old = children.get(j); 472 if (dirtyChildren != null) { 473 dirtyChildren.remove(old); 474 } 475 if (old.isVisible()) { 476 geomChanged = true; 477 childExcluded(old); 478 } 479 if (old.getParent() == Parent.this) { 480 old.setParent(null); 481 old.setScenes(null, null); 482 } 483 if (!removedChildrenExceedsThreshold) { 484 removed.add(old); 485 } 486 } 487 } 488 } 489 490 private String constructExceptionMessage( 491 String cause, Node offendingNode) { 492 final StringBuilder sb = new StringBuilder("Children: "); 493 sb.append(cause); 494 sb.append(": parent = ").append(Parent.this); 495 if (offendingNode != null) { 496 sb.append(", node = ").append(offendingNode); 497 } 498 499 return sb.toString(); 500 } 501 }; 502 503 /** 504 * A constant reference to an unmodifiable view of the children, such that every time 505 * we ask for an unmodifiable list of children, we don't actually create a new 506 * collection and return it. The memory overhead is pretty lightweight compared 507 * to all the garbage we would otherwise generate. 508 */ 509 private final ObservableList<Node> unmodifiableChildren = 510 FXCollections.unmodifiableObservableList(children); 511 512 /** 513 * A cached reference to the unmodifiable managed children of this Parent. This is 514 * created whenever first asked for, and thrown away whenever children are added 515 * or removed or when their managed state changes. This could be written 516 * differently, such that this list is essentially a filtered copy of the 517 * main children, but that additional overhead might not be worth it. 518 */ 519 private List<Node> unmodifiableManagedChildren = null; 520 521 /** 522 * Gets the list of children of this {@code Parent}. 523 * 524 * <p> 525 * See the class documentation for {@link Node} for scene graph structure 526 * restrictions on setting a {@link Parent}'s children list. 527 * If these restrictions are violated by a change to the list of children, 528 * the change is ignored and the previous value of the children list is 529 * restored. An {@link IllegalArgumentException} is thrown in this case. 530 * 531 * <p> 532 * If this {@link Parent} node is attached to a {@link Scene}, then its 533 * list of children must only be modified on the JavaFX Application Thread. 534 * An {@link IllegalStateException} is thrown if this restriction is 535 * violated. 536 * 537 * <p> 538 * Note to subclasses: if you override this method, you must return from 539 * your implementation the result of calling this super method. The actual 540 * list instance returned from any getChildren() implementation must be 541 * the list owned and managed by this Parent. The only typical purpose 542 * for overriding this method is to promote the method to be public. 543 * 544 * @return the list of children of this {@code Parent}. 545 */ 546 protected ObservableList<Node> getChildren() { 547 return children; 548 } 549 550 /** 551 * Gets the list of children of this {@code Parent} as a read-only 552 * list. 553 * 554 * @return read-only access to this parent's children ObservableList 555 */ 556 @ReturnsUnmodifiableCollection 557 public ObservableList<Node> getChildrenUnmodifiable() { 558 return unmodifiableChildren; 559 } 560 561 /** 562 * Gets the list of all managed children of this {@code Parent}. 563 * 564 * @param <E> the type of the children nodes 565 * @return list of all managed children in this parent 566 */ 567 @ReturnsUnmodifiableCollection 568 protected <E extends Node> List<E> getManagedChildren() { 569 if (unmodifiableManagedChildren == null) { 570 unmodifiableManagedChildren = new ArrayList<Node>(); 571 for (int i=0, max=children.size(); i<max; i++) { 572 Node e = children.get(i); 573 if (e.isManaged()) { 574 unmodifiableManagedChildren.add(e); 575 } 576 } 577 } 578 return (List<E>)unmodifiableManagedChildren; 579 } 580 581 /** 582 * Called by Node whenever its managed state may have changed, this 583 * method will cause the view of managed children to be updated 584 * such that it properly includes or excludes this child. 585 */ 586 final void managedChildChanged() { 587 requestLayout(); 588 unmodifiableManagedChildren = null; 589 } 590 591 // implementation of Node.toFront function 592 final void impl_toFront(Node node) { 593 if (Utils.assertionEnabled()) { 594 if (!childSet.contains(node)) { 595 throw new java.lang.AssertionError( 596 "specified node is not in the list of children"); 597 } 598 } 599 600 if (children.get(children.size() - 1) != node) { 601 childrenTriggerPermutation = true; 602 try { 603 children.remove(node); 604 children.add(node); 605 } finally { 606 childrenTriggerPermutation = false; 607 } 608 } 609 } 610 611 // implementation of Node.toBack function 612 final void impl_toBack(Node node) { 613 if (Utils.assertionEnabled()) { 614 if (!childSet.contains(node)) { 615 throw new java.lang.AssertionError( 616 "specified node is not in the list of children"); 617 } 618 } 619 620 if (children.get(0) != node) { 621 childrenTriggerPermutation = true; 622 try { 623 children.remove(node); 624 children.add(0, node); 625 } finally { 626 childrenTriggerPermutation = false; 627 } 628 } 629 } 630 631 @Override 632 void scenesChanged(final Scene newScene, final SubScene newSubScene, 633 final Scene oldScene, final SubScene oldSubScene) { 634 for (int i=0; i<children.size(); i++) { 635 children.get(i).setScenes(newScene, newSubScene); 636 } 637 638 // If this node was in the old scene's dirty layout 639 // list, then remove it from that list so that it is 640 // not processed on the next pulse 641 final boolean awaitingLayout = isNeedsLayout(); 642 643 sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) || 644 (newScene != null && newScene.getRoot() == this); 645 layoutRoot = !isManaged() || sceneRoot; 646 647 if (awaitingLayout) { 648 boolean sceneChanged = oldScene != newScene; 649 if (oldScene != null && sceneChanged) { 650 oldScene.removeFromDirtyLayoutList(this); 651 } 652 // If this node is dirty and the new scene or subScene is not null 653 // then add this node to the new scene's dirty list 654 if (newScene != null && layoutRoot) { 655 if (newSubScene != null) { 656 newSubScene.setDirtyLayout(this); 657 } 658 if (sceneChanged) { 659 newScene.addToDirtyLayoutList(this); 660 } 661 } 662 } 663 } 664 665 @Override 666 void setDerivedDepthTest(boolean value) { 667 super.setDerivedDepthTest(value); 668 669 for (int i=0, max=children.size(); i<max; i++) { 670 final Node node = children.get(i); 671 node.computeDerivedDepthTest(); 672 } 673 } 674 675 /** 676 * @treatAsPrivate implementation detail 677 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 678 */ 679 @Deprecated 680 @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) { 681 682 double boundsDistance = impl_intersectsBounds(pickRay); 683 684 if (!Double.isNaN(boundsDistance)) { 685 for (int i = children.size()-1; i >= 0; i--) { 686 children.get(i).impl_pickNode(pickRay, result); 687 if (result.isClosed()) { 688 return; 689 } 690 } 691 692 if (isPickOnBounds()) { 693 result.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance)); 694 } 695 } 696 } 697 698 @Override boolean isConnected() { 699 return super.isConnected() || sceneRoot; 700 } 701 702 @Override public Node lookup(String selector) { 703 Node n = super.lookup(selector); 704 if (n == null) { 705 for (int i=0, max=children.size(); i<max; i++) { 706 final Node node = children.get(i); 707 n = node.lookup(selector); 708 if (n != null) return n; 709 } 710 } 711 return n; 712 } 713 714 /** 715 * Please Note: This method should never create the results set, 716 * let the Node class implementation do this! 717 */ 718 @Override List<Node> lookupAll(Selector selector, List<Node> results) { 719 results = super.lookupAll(selector, results); 720 for (int i=0, max=children.size(); i<max; i++) { 721 final Node node = children.get(i); 722 results = node.lookupAll(selector, results); 723 } 724 return results; 725 } 726 727 /** @treatAsPrivate implementation detail */ 728 private javafx.beans.property.ObjectProperty<TraversalEngine> impl_traversalEngine; 729 730 /** 731 * @treatAsPrivate implementation detail 732 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 733 */ 734 // SB-dependency: RT-21209 has been filed to track this 735 @Deprecated 736 public final void setImpl_traversalEngine(TraversalEngine value) { 737 impl_traversalEngineProperty().set(value); 738 } 739 740 /** 741 * @treatAsPrivate implementation detail 742 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 743 */ 744 @Deprecated 745 public final TraversalEngine getImpl_traversalEngine() { 746 return impl_traversalEngine == null ? null : impl_traversalEngine.get(); 747 } 748 749 /** 750 * @treatAsPrivate implementation detail 751 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 752 */ 753 @Deprecated 754 public final ObjectProperty<TraversalEngine> impl_traversalEngineProperty() { 755 if (impl_traversalEngine == null) { 756 impl_traversalEngine = 757 new SimpleObjectProperty<TraversalEngine>( 758 this, "impl_traversalEngine"); 759 } 760 return impl_traversalEngine; 761 } 762 763 /*********************************************************************** 764 * Layout * 765 * * 766 * Functions and variables related to the layout scheme used by * 767 * JavaFX. Includes both public and private API. * 768 * * 769 **********************************************************************/ 770 /** 771 * Indicates that this Node and its subnodes requires a layout pass on 772 * the next pulse. 773 */ 774 private ReadOnlyBooleanWrapper needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", true); 775 776 protected final void setNeedsLayout(boolean value) { 777 needsLayout.set(value); 778 } 779 780 public final boolean isNeedsLayout() { 781 return needsLayout.get(); 782 } 783 784 public final ReadOnlyBooleanProperty needsLayoutProperty() { 785 return needsLayout.getReadOnlyProperty(); 786 } 787 788 /** 789 * This package levelis used only by Node. It is set to true while 790 * the layout() function is processing and set to false on the conclusion. 791 * It is used by the Node to decide whether to perform CSS updates 792 * synchronously or asynchronously. 793 */ 794 boolean performingLayout = false; 795 796 private boolean sizeCacheClear = true; 797 private double prefWidthCache = -1; 798 private double prefHeightCache = -1; 799 private double minWidthCache = -1; 800 private double minHeightCache = -1; 801 802 /** 803 * Requests a layout pass to be performed before the next scene is 804 * rendered. This is batched up asynchronously to happen once per 805 * "pulse", or frame of animation. 806 * <p> 807 * If this parent is either a layout root or unmanaged, then it will be 808 * added directly to the scene's dirty layout list, otherwise requestLayout 809 * will be invoked on its parent. 810 */ 811 public void requestLayout() { 812 if (!isNeedsLayout()) { 813 prefWidthCache = -1; 814 prefHeightCache = -1; 815 minWidthCache = -1; 816 minHeightCache = -1; 817 PlatformLogger logger = Logging.getLayoutLogger(); 818 if (logger.isLoggable(PlatformLogger.FINER)) { 819 logger.finer(this.toString()); 820 } 821 822 setNeedsLayout(true); 823 if (layoutRoot) { 824 final Scene scene = getScene(); 825 final SubScene subScene = getSubScene(); 826 if (subScene != null) { 827 if (logger.isLoggable(PlatformLogger.FINER)) { 828 logger.finer(this.toString()+" layoutRoot added to SubScene dirty layout list"); 829 } 830 subScene.setDirtyLayout(this); 831 } 832 if (scene != null) { 833 if (logger.isLoggable(PlatformLogger.FINER)) { 834 logger.finer(this.toString()+" layoutRoot added to scene dirty layout list"); 835 } 836 scene.addToDirtyLayoutList(this); 837 } 838 } else { 839 final Parent parent = getParent(); 840 if (parent != null) { 841 parent.requestLayout(); 842 } 843 } 844 } else { 845 clearSizeCache(); 846 } 847 } 848 849 void clearSizeCache() { 850 if (sizeCacheClear) { 851 return; 852 } 853 sizeCacheClear = true; 854 prefWidthCache = -1; 855 prefHeightCache = -1; 856 minWidthCache = -1; 857 minHeightCache = -1; 858 if (!layoutRoot) { 859 final Parent parent = getParent(); 860 if (parent != null) { 861 parent.clearSizeCache(); 862 } 863 } 864 } 865 866 @Override public double prefWidth(double height) { 867 if (height == -1) { 868 if (prefWidthCache == -1) { 869 prefWidthCache = computePrefWidth(-1); 870 sizeCacheClear = false; 871 } 872 return prefWidthCache; 873 } else { 874 return computePrefWidth(height); 875 } 876 } 877 878 @Override public double prefHeight(double width) { 879 if (width == -1) { 880 if (prefHeightCache == -1) { 881 prefHeightCache = computePrefHeight(-1); 882 sizeCacheClear = false; 883 } 884 return prefHeightCache; 885 } else { 886 return computePrefHeight(width); 887 } 888 } 889 890 @Override public double minWidth(double height) { 891 if (height == -1) { 892 if (minWidthCache == -1) { 893 minWidthCache = computeMinWidth(-1); 894 sizeCacheClear = false; 895 } 896 return minWidthCache; 897 } else { 898 return computeMinWidth(height); 899 } 900 } 901 902 @Override public double minHeight(double width) { 903 if (width == -1) { 904 if (minHeightCache == -1) { 905 minHeightCache = computeMinHeight(-1); 906 sizeCacheClear = false; 907 } 908 return minHeightCache; 909 } else { 910 return computeMinHeight(width); 911 } 912 } 913 914 // PENDING_DOC_REVIEW 915 /** 916 * Calculates the preferred width of this {@code Parent}. The default 917 * implementation calculates this width as the width of the area occupied 918 * by its managed children when they are positioned at their 919 * current positions at their preferred widths. 920 * 921 * @param height the height that should be used if preferred width depends 922 * on it 923 * @return the calculated preferred width 924 */ 925 protected double computePrefWidth(double height) { 926 double minX = 0; 927 double maxX = 0; 928 for (int i=0, max=children.size(); i<max; i++) { 929 Node node = children.get(i); 930 if (node.isManaged()) { 931 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX(); 932 minX = Math.min(minX, x); 933 maxX = Math.max(maxX, x + boundedSize(node.prefWidth(-1), node.minWidth(-1), node.maxWidth(-1))); 934 } 935 } 936 return maxX - minX; 937 } 938 939 // PENDING_DOC_REVIEW 940 /** 941 * Calculates the preferred height of this {@code Parent}. The default 942 * implementation calculates this height as the height of the area occupied 943 * by its managed children when they are positioned at their current 944 * positions at their preferred heights. 945 * 946 * @param width the width that should be used if preferred height depends 947 * on it 948 * @return the calculated preferred height 949 */ 950 protected double computePrefHeight(double width) { 951 double minY = 0; 952 double maxY = 0; 953 for (int i=0, max=children.size(); i<max; i++) { 954 Node node = children.get(i); 955 if (node.isManaged()) { 956 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY(); 957 minY = Math.min(minY, y); 958 maxY = Math.max(maxY, y + boundedSize(node.prefHeight(-1), node.minHeight(-1), node.maxHeight(-1))); 959 } 960 } 961 return maxY - minY; 962 } 963 964 /** 965 * Calculates the minimum width of this {@code Parent}. The default 966 * implementation simply returns the pref width. 967 * 968 * @param height the height that should be used if min width depends 969 * on it 970 * @return the calculated min width 971 */ 972 protected double computeMinWidth(double height) { 973 return prefWidth(height); 974 } 975 976 // PENDING_DOC_REVIEW 977 /** 978 * Calculates the min height of this {@code Parent}. The default 979 * implementation simply returns the pref height; 980 * 981 * @param width the width that should be used if min height depends 982 * on it 983 * @return the calculated min height 984 */ 985 protected double computeMinHeight(double width) { 986 return prefHeight(width); 987 } 988 989 /** 990 * Calculates the baseline offset based on the first managed child. If there 991 * is no such child, returns {@link Node#getBaselineOffset()}. 992 * 993 * @return baseline offset 994 */ 995 @Override public double getBaselineOffset() { 996 for (int i=0, max=children.size(); i<max; i++) { 997 final Node child = children.get(i); 998 if (child.isManaged()) { 999 return child.getLayoutBounds().getMinY() + child.getLayoutY() + child.getBaselineOffset(); 1000 } 1001 } 1002 return super.getBaselineOffset(); 1003 } 1004 1005 /** 1006 * Executes a top-down layout pass on the scene graph under this parent. 1007 */ 1008 public final void layout() { 1009 if (isNeedsLayout()) { 1010 if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.fxIncrementCounter("Parent#layout() on dirty Node"); 1011 performingLayout = true; 1012 1013 PlatformLogger logger = Logging.getLayoutLogger(); 1014 if (logger.isLoggable(PlatformLogger.FINE)) { 1015 logger.fine(this+" size: "+ 1016 getLayoutBounds().getWidth()+" x "+getLayoutBounds().getHeight()); 1017 } 1018 1019 // layout the children in this parent. 1020 layoutChildren(); 1021 // clear flag before recursing down so requestLayout calls arn't swallowed 1022 setNeedsLayout(false); 1023 1024 // Perform layout on each child, hoping it has random access performance! 1025 for (int i=0, max=children.size(); i<max; i++) { 1026 final Node child = children.get(i); 1027 if (child instanceof Parent) { 1028 ((Parent) child).layout(); 1029 } 1030 } 1031 performingLayout = false; 1032 } else { 1033 if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.fxIncrementCounter("Parent#layout() on clean Node"); 1034 } 1035 } 1036 1037 /** 1038 * Invoked during the layout pass to layout the children in this 1039 * {@code Parent}. By default it will only set the size of managed, 1040 * resizable content to their preferred sizes and does not do any node 1041 * positioning. 1042 * <p> 1043 * Subclasses should override this function to layout content as needed. 1044 */ 1045 protected void layoutChildren() { 1046 for (int i=0, max=children.size(); i<max; i++) { 1047 final Node node = children.get(i); 1048 if (node.isResizable() && node.isManaged()) { 1049 node.autosize(); 1050 } 1051 } 1052 } 1053 1054 /** 1055 * This field is managed by the Scene, and set on any node which is the 1056 * root of a Scene. 1057 */ 1058 private boolean sceneRoot = false; 1059 1060 /** 1061 * Keeps track of whether this node is a layout root. This is updated 1062 * whenever the sceneRoot field changes, or whenever the managed 1063 * property changes. 1064 */ 1065 private boolean layoutRoot = false; 1066 @Override final void notifyManagedChanged() { 1067 layoutRoot = !isManaged() || sceneRoot; 1068 } 1069 1070 /*********************************************************************** 1071 * * 1072 * Stylesheet Handling * 1073 * * 1074 **********************************************************************/ 1075 1076 1077 /** 1078 * A ObservableList of string URLs linking to the stylesheets to use with this scene's 1079 * contents. For additional information about using CSS with the 1080 * scene graph, see the <a href="doc-files/cssref.html">CSS Reference 1081 * Guide</a>. 1082 */ 1083 private final ObservableList<String> stylesheets = new TrackableObservableList<String>() { 1084 @Override 1085 protected void onChanged(Change<String> c) { 1086 final Scene scene = getScene(); 1087 if (scene != null) { 1088 1089 // Notify the StyleManager if stylesheets change. This Parent's 1090 // styleManager will get recreated in impl_processCSS. 1091 StyleManager.getInstance().stylesheetsChanged(Parent.this, c); 1092 1093 // RT-9784 - if stylesheet is removed, reset styled properties to 1094 // their initial value. 1095 c.reset(); 1096 while(c.next()) { 1097 if (c.wasRemoved() == false) { 1098 continue; 1099 } 1100 break; // no point in resetting more than once... 1101 } 1102 1103 impl_reapplyCSS(); 1104 } 1105 } 1106 }; 1107 1108 /** 1109 * Gets an observable list of string URLs linking to the stylesheets to use 1110 * with this Parent's contents. For additional information about using CSS 1111 * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference 1112 * Guide</a>. 1113 * 1114 * @return the list of stylesheets to use with this Parent 1115 */ 1116 public final ObservableList<String> getStylesheets() { return stylesheets; } 1117 1118 /** 1119 * This method recurses up the parent chain until parent is null. As the 1120 * stack unwinds, if the Parent has stylesheets, they are added to the 1121 * list. 1122 * 1123 * It is possible to override this method to stop the recursion. This allows 1124 * a Parent to have a set of stylesheets distinct from its Parent. 1125 * 1126 * @treatAsPrivate implementation detail 1127 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1128 */ 1129 @Deprecated // SB-dependency: RT-21247 has been filed to track this 1130 public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() { 1131 1132 List<String> list = null; 1133 final Parent myParent = getParent(); 1134 if (myParent != null) { 1135 1136 // 1137 // recurse so that stylesheets of Parents closest to the root are 1138 // added to the list first. The ensures that declarations for 1139 // stylesheets further down the tree (closer to the leaf) have 1140 // a higer ordinal in the cascade. 1141 // 1142 list = myParent.impl_getAllParentStylesheets(); 1143 } 1144 1145 if (stylesheets != null && stylesheets.isEmpty() == false) { 1146 if (list == null) { 1147 list = new ArrayList<String>(stylesheets.size()); 1148 } 1149 for (int n=0,nMax=stylesheets.size(); n<nMax; n++) { 1150 list.add(stylesheets.get(n)); 1151 } 1152 } 1153 1154 return list; 1155 1156 } 1157 1158 /** 1159 * @treatAsPrivate implementation detail 1160 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1161 */ 1162 @Deprecated 1163 @Override protected void impl_processCSS() { 1164 1165 // Nothing to do... 1166 if (cssFlag == CssFlags.CLEAN) return; 1167 1168 // RT-29254 - If DIRTY_BRANCH, pass control to Node#processCSS. This avoids calling impl_processCSS on 1169 // this node and all of its children when css doesn't need updated, recalculated, or reapplied. 1170 if (cssFlag == CssFlags.DIRTY_BRANCH) { 1171 super.processCSS(); 1172 return; 1173 } 1174 // remember the flag we started with since super.impl_processCSS 1175 // resets it to CLEAN and we need it for setting children. 1176 CssFlags flag = cssFlag; 1177 1178 // Let the super implementation handle CSS for this node 1179 super.impl_processCSS(); 1180 1181 // For each child, process CSS 1182 for (int i=0, max=children.size(); i<max; i++) { 1183 final Node child = children.get(i); 1184 1185 // If the parent styles are being updated, recalculated or 1186 // reapplied, then make sure the children get the same treatment. 1187 // Unless the child is already more dirty than this parent (RT-29074). 1188 if(flag.compareTo(child.cssFlag) > 0) { 1189 child.cssFlag = flag; 1190 } 1191 child.impl_processCSS(); 1192 } 1193 } 1194 1195 /*********************************************************************** 1196 * Misc * 1197 * * 1198 * Initialization and other functions * 1199 * * 1200 **********************************************************************/ 1201 1202 1203 /** 1204 * Constructs a new {@code Parent}. 1205 */ 1206 protected Parent() { 1207 } 1208 1209 /** 1210 * @treatAsPrivate implementation detail 1211 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1212 */ 1213 @Deprecated 1214 protected @Override PGNode impl_createPGNode() { 1215 return Toolkit.getToolkit().createPGGroup(); 1216 } 1217 1218 PGGroup getPGGroup() { 1219 return ((PGGroup) impl_getPGNode()); 1220 } 1221 1222 @Override 1223 void nodeResolvedOrientationChanged() { 1224 for (int i = 0, max = children.size(); i < max; ++i) { 1225 children.get(i).parentResolvedOrientationInvalidated(); 1226 } 1227 } 1228 1229 /*************************************************************************** 1230 * * 1231 * Bounds Computations * 1232 * * 1233 * This code originated in GroupBoundsHelper (part of javafx-sg-common) * 1234 * but has been ported here to the FX side since we cannot rely on the PG * 1235 * side for computing the bounds (due to the decoupling of the two * 1236 * scenegraphs for threading and other purposes). * 1237 * * 1238 * Unfortunately, we cannot simply reuse GroupBoundsHelper without some * 1239 * major (and hacky) modification due to the fact that GroupBoundsHelper * 1240 * relies on PG state and we need to do similar things here that rely on * 1241 * core scenegraph state. Unfortunately, that means we made a port. * 1242 * * 1243 **************************************************************************/ 1244 1245 private BaseBounds tmp = new RectBounds(); 1246 1247 /** 1248 * The cached bounds for the Group. If the cachedBounds are invalid 1249 * then we have no history of what the bounds are, or were. 1250 */ 1251 private BaseBounds cachedBounds = new RectBounds(); 1252 1253 /** 1254 * Indicates that the cachedBounds is invalid (or old) and need to be recomputed. 1255 * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero, 1256 * then when we recompute the cachedBounds we can consider the 1257 * values in cachedBounds to represent the last valid bounds for the group. 1258 * This is useful for several fast paths. 1259 */ 1260 private boolean cachedBoundsInvalid; 1261 1262 /** 1263 * The number of dirty children which bounds haven't been incorporated 1264 * into the cached bounds yet. Can be used even when dirtyChildren is null. 1265 */ 1266 private int dirtyChildrenCount; 1267 1268 /** 1269 * This set is used to track all of the children of this group which are 1270 * dirty. It is only used in cases where the number of children is > some 1271 * value (currently 10). For very wide trees, this can provide a very 1272 * important speed boost. For the sake of memory consumption, this is 1273 * null unless the number of children ever crosses the threshold where 1274 * it will be activated. 1275 */ 1276 private ArrayList<Node> dirtyChildren; 1277 1278 private Node top; 1279 private Node left; 1280 private Node bottom; 1281 private Node right; 1282 private Node near; 1283 private Node far; 1284 1285 /** 1286 * @treatAsPrivate implementation detail 1287 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1288 */ 1289 @Deprecated 1290 @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 1291 // If we have no children, our bounds are invalid 1292 if (children.isEmpty()) { 1293 return bounds.makeEmpty(); 1294 } 1295 1296 if (tx.isTranslateOrIdentity()) { 1297 // this is a transform which is only doing translations, or nothing 1298 // at all (no scales, rotates, or shears) 1299 // so in this case we can easily use the cached bounds 1300 if (cachedBoundsInvalid) { 1301 recomputeBounds(); 1302 1303 if (dirtyChildren != null) { 1304 dirtyChildren.clear(); 1305 } 1306 cachedBoundsInvalid = false; 1307 dirtyChildrenCount = 0; 1308 } 1309 if (!tx.isIdentity()) { 1310 bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()), 1311 (float)(cachedBounds.getMinY() + tx.getMyt()), 1312 (float)(cachedBounds.getMinZ() + tx.getMzt()), 1313 (float)(cachedBounds.getMaxX() + tx.getMxt()), 1314 (float)(cachedBounds.getMaxY() + tx.getMyt()), 1315 (float)(cachedBounds.getMaxZ() + tx.getMzt())); 1316 } else { 1317 bounds = bounds.deriveWithNewBounds(cachedBounds); 1318 } 1319 1320 return bounds; 1321 } else { 1322 // there is a scale, shear, or rotation happening, so need to 1323 // do the full transform! 1324 double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE; 1325 double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE; 1326 boolean first = true; 1327 for (int i=0, max=children.size(); i<max; i++) { 1328 final Node node = children.get(i); 1329 if (node.isVisible()) { 1330 bounds = node.getTransformedBounds(bounds, tx); 1331 // if the bounds of the child are invalid, we don't want 1332 // to use those in the remaining computations. 1333 if (bounds.isEmpty()) continue; 1334 if (first) { 1335 minX = bounds.getMinX(); 1336 minY = bounds.getMinY(); 1337 minZ = bounds.getMinZ(); 1338 maxX = bounds.getMaxX(); 1339 maxY = bounds.getMaxY(); 1340 maxZ = bounds.getMaxZ(); 1341 first = false; 1342 } else { 1343 minX = Math.min(bounds.getMinX(), minX); 1344 minY = Math.min(bounds.getMinY(), minY); 1345 minZ = Math.min(bounds.getMinZ(), minZ); 1346 maxX = Math.max(bounds.getMaxX(), maxX); 1347 maxY = Math.max(bounds.getMaxY(), maxY); 1348 maxZ = Math.max(bounds.getMaxZ(), maxZ); 1349 } 1350 } 1351 } 1352 // if "first" is still true, then we didn't have any children with 1353 // non-empty bounds and thus we must return an empty bounds, 1354 // otherwise we have non-empty bounds so go for it. 1355 if (first) 1356 bounds.makeEmpty(); 1357 else 1358 bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ, 1359 (float)maxX, (float)maxY, (float)maxZ); 1360 1361 return bounds; 1362 } 1363 } 1364 1365 private void setChildDirty(final Node node, final boolean dirty) { 1366 if (node.boundsChanged == dirty) { 1367 return; 1368 } 1369 1370 node.boundsChanged = dirty; 1371 if (dirty) { 1372 if (dirtyChildren != null) { 1373 dirtyChildren.add(node); 1374 } 1375 ++dirtyChildrenCount; 1376 } else { 1377 if (dirtyChildren != null) { 1378 dirtyChildren.remove(node); 1379 } 1380 --dirtyChildrenCount; 1381 } 1382 } 1383 1384 private void childIncluded(final Node node) { 1385 // assert node.isVisible(); 1386 cachedBoundsInvalid = true; 1387 setChildDirty(node, true); 1388 } 1389 1390 // This is called when either the child is actually removed, OR IF IT IS 1391 // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be 1392 // cleared from the state which manages bounds. 1393 private void childExcluded(final Node node) { 1394 if (node == left) { 1395 left = null; 1396 cachedBoundsInvalid = true; 1397 } 1398 if (node == top) { 1399 top = null; 1400 cachedBoundsInvalid = true; 1401 } 1402 if (node == near) { 1403 near = null; 1404 cachedBoundsInvalid = true; 1405 } 1406 if (node == right) { 1407 right = null; 1408 cachedBoundsInvalid = true; 1409 } 1410 if (node == bottom) { 1411 bottom = null; 1412 cachedBoundsInvalid = true; 1413 } 1414 if (node == far) { 1415 far = null; 1416 cachedBoundsInvalid = true; 1417 } 1418 1419 setChildDirty(node, false); 1420 } 1421 1422 /** 1423 * Recomputes the bounds from scratch and saves the cached bounds. 1424 */ 1425 private void recomputeBounds() { 1426 // fast path for case of no children 1427 if (children.isEmpty()) { 1428 cachedBounds.makeEmpty(); 1429 return; 1430 } 1431 1432 // fast path for case of 1 child 1433 if (children.size() == 1) { 1434 Node node = children.get(0); 1435 node.boundsChanged = false; 1436 if (node.isVisible()) { 1437 cachedBounds = node.getTransformedBounds( 1438 cachedBounds, 1439 BaseTransform.IDENTITY_TRANSFORM); 1440 top = left = bottom = right = near = far = node; 1441 } else { 1442 cachedBounds.makeEmpty(); 1443 // no need to null edge nodes here, it was done in childExcluded 1444 // top = left = bottom = right = near = far = null; 1445 } 1446 return; 1447 } 1448 1449 if ((dirtyChildrenCount == 0) || 1450 !updateCachedBounds(dirtyChildren != null 1451 ? dirtyChildren : children, 1452 dirtyChildrenCount)) { 1453 // failed to update cached bounds, recreate them 1454 createCachedBounds(children); 1455 } 1456 } 1457 1458 private final int LEFT_INVALID = 1; 1459 private final int TOP_INVALID = 1 << 1; 1460 private final int NEAR_INVALID = 1 << 2; 1461 private final int RIGHT_INVALID = 1 << 3; 1462 private final int BOTTOM_INVALID = 1 << 4; 1463 private final int FAR_INVALID = 1 << 5; 1464 1465 private boolean updateCachedBounds(final List<Node> dirtyNodes, 1466 int remainingDirtyNodes) { 1467 // fast path for untransformed bounds calculation 1468 if (cachedBounds.isEmpty()) { 1469 createCachedBounds(dirtyNodes); 1470 return true; 1471 } 1472 1473 int invalidEdges = 0; 1474 1475 if ((left == null) || left.boundsChanged) { 1476 invalidEdges |= LEFT_INVALID; 1477 } 1478 if ((top == null) || top.boundsChanged) { 1479 invalidEdges |= TOP_INVALID; 1480 } 1481 if ((near == null) || near.boundsChanged) { 1482 invalidEdges |= NEAR_INVALID; 1483 } 1484 if ((right == null) || right.boundsChanged) { 1485 invalidEdges |= RIGHT_INVALID; 1486 } 1487 if ((bottom == null) || bottom.boundsChanged) { 1488 invalidEdges |= BOTTOM_INVALID; 1489 } 1490 if ((far == null) || far.boundsChanged) { 1491 invalidEdges |= FAR_INVALID; 1492 } 1493 1494 // These indicate the bounds of the Group as computed by this 1495 // function 1496 float minX = cachedBounds.getMinX(); 1497 float minY = cachedBounds.getMinY(); 1498 float minZ = cachedBounds.getMinZ(); 1499 float maxX = cachedBounds.getMaxX(); 1500 float maxY = cachedBounds.getMaxY(); 1501 float maxZ = cachedBounds.getMaxZ(); 1502 1503 // this checks the newly added nodes first, so if dirtyNodes is the 1504 // whole children list, we can end early 1505 for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) { 1506 final Node node = dirtyNodes.get(i); 1507 if (node.boundsChanged) { 1508 // assert node.isVisible(); 1509 node.boundsChanged = false; 1510 --remainingDirtyNodes; 1511 tmp = node.getTransformedBounds( 1512 tmp, BaseTransform.IDENTITY_TRANSFORM); 1513 if (!tmp.isEmpty()) { 1514 float tmpx = tmp.getMinX(); 1515 float tmpy = tmp.getMinY(); 1516 float tmpz = tmp.getMinZ(); 1517 float tmpx2 = tmp.getMaxX(); 1518 float tmpy2 = tmp.getMaxY(); 1519 float tmpz2 = tmp.getMaxZ(); 1520 1521 // If this node forms an edge, then we will set it to be the 1522 // node for this edge and update the min/max values 1523 if (tmpx <= minX) { 1524 minX = tmpx; 1525 left = node; 1526 invalidEdges &= ~LEFT_INVALID; 1527 } 1528 if (tmpy <= minY) { 1529 minY = tmpy; 1530 top = node; 1531 invalidEdges &= ~TOP_INVALID; 1532 } 1533 if (tmpz <= minZ) { 1534 minZ = tmpz; 1535 near = node; 1536 invalidEdges &= ~NEAR_INVALID; 1537 } 1538 if (tmpx2 >= maxX) { 1539 maxX = tmpx2; 1540 right = node; 1541 invalidEdges &= ~RIGHT_INVALID; 1542 } 1543 if (tmpy2 >= maxY) { 1544 maxY = tmpy2; 1545 bottom = node; 1546 invalidEdges &= ~BOTTOM_INVALID; 1547 } 1548 if (tmpz2 >= maxZ) { 1549 maxZ = tmpz2; 1550 far = node; 1551 invalidEdges &= ~FAR_INVALID; 1552 } 1553 } 1554 } 1555 } 1556 1557 if (invalidEdges != 0) { 1558 // failed to validate some edges 1559 return false; 1560 } 1561 1562 cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ, 1563 maxX, maxY, maxZ); 1564 return true; 1565 } 1566 1567 private void createCachedBounds(final List<Node> fromNodes) { 1568 // These indicate the bounds of the Group as computed by this function 1569 float minX, minY, minZ; 1570 float maxX, maxY, maxZ; 1571 1572 final int nodeCount = fromNodes.size(); 1573 int i; 1574 1575 // handle first visible non-empty node 1576 for (i = 0; i < nodeCount; ++i) { 1577 final Node node = fromNodes.get(i); 1578 node.boundsChanged = false; 1579 if (node.isVisible()) { 1580 tmp = node.getTransformedBounds( 1581 tmp, BaseTransform.IDENTITY_TRANSFORM); 1582 if (!tmp.isEmpty()) { 1583 left = top = near = right = bottom = far = node; 1584 break; 1585 } 1586 } 1587 } 1588 1589 if (i == nodeCount) { 1590 left = top = near = right = bottom = far = null; 1591 cachedBounds.makeEmpty(); 1592 return; 1593 } 1594 1595 minX = tmp.getMinX(); 1596 minY = tmp.getMinY(); 1597 minZ = tmp.getMinZ(); 1598 maxX = tmp.getMaxX(); 1599 maxY = tmp.getMaxY(); 1600 maxZ = tmp.getMaxZ(); 1601 1602 // handle remaining visible non-empty nodes 1603 for (++i; i < nodeCount; ++i) { 1604 final Node node = fromNodes.get(i); 1605 node.boundsChanged = false; 1606 if (node.isVisible()) { 1607 tmp = node.getTransformedBounds( 1608 tmp, BaseTransform.IDENTITY_TRANSFORM); 1609 if (!tmp.isEmpty()) { 1610 final float tmpx = tmp.getMinX(); 1611 final float tmpy = tmp.getMinY(); 1612 final float tmpz = tmp.getMinZ(); 1613 final float tmpx2 = tmp.getMaxX(); 1614 final float tmpy2 = tmp.getMaxY(); 1615 final float tmpz2 = tmp.getMaxZ(); 1616 1617 if (tmpx < minX) { minX = tmpx; left = node; } 1618 if (tmpy < minY) { minY = tmpy; top = node; } 1619 if (tmpz < minZ) { minZ = tmpz; near = node; } 1620 if (tmpx2 > maxX) { maxX = tmpx2; right = node; } 1621 if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; } 1622 if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; } 1623 } 1624 } 1625 } 1626 1627 cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ, 1628 maxX, maxY, maxZ); 1629 } 1630 1631 @Override void updateBounds() { 1632 for (int i=0, max=children.size(); i<max; i++) { 1633 children.get(i).updateBounds(); 1634 } 1635 super.updateBounds(); 1636 } 1637 1638 /** 1639 * Called by Node whenever its bounds have changed. 1640 */ 1641 void childBoundsChanged(Node node) { 1642 // assert node.isVisible(); 1643 1644 cachedBoundsInvalid = true; 1645 1646 // mark the node such that the parent knows that the child's bounds 1647 // are not in sync with this parent. In this way, when the bounds 1648 // need to be computed, we'll come back and figure out the new bounds 1649 // for all the children which have boundsChanged set to true 1650 setChildDirty(node, true); 1651 1652 // go ahead and indicate that the geom has changed for this parent, 1653 // even though once we figure it all out it may be that the bounds 1654 // have not changed 1655 impl_geomChanged(); 1656 } 1657 1658 /** 1659 * Called by node whenever the visibility of the node changes. 1660 */ 1661 void childVisibilityChanged(Node node) { 1662 if (node.isVisible()) { 1663 childIncluded(node); 1664 } else { 1665 childExcluded(node); 1666 } 1667 1668 impl_geomChanged(); 1669 } 1670 1671 /** 1672 * @treatAsPrivate implementation detail 1673 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1674 */ 1675 @Deprecated 1676 @Override 1677 protected boolean impl_computeContains(double localX, double localY) { 1678 final Point2D tempPt = TempState.getInstance().point; 1679 for (int i=0, max=children.size(); i<max; i++) { 1680 final Node node = children.get(i); 1681 tempPt.x = (float)localX; 1682 tempPt.y = (float)localY; 1683 try { 1684 node.parentToLocal(tempPt); 1685 } catch (NoninvertibleTransformException e) { 1686 continue; 1687 } 1688 if (node.contains(tempPt.x, tempPt.y)) { 1689 return true; 1690 } 1691 } 1692 return false; 1693 } 1694 1695 /** 1696 * @treatAsPrivate implementation detail 1697 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1698 */ 1699 @Deprecated 1700 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 1701 return alg.processContainerNode(this, ctx); 1702 } 1703}