1 /* 2 * Copyright (c) 2010, 2017, 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 javafx.scene; 27 28 29 import com.sun.javafx.geometry.BoundsUtils; 30 import javafx.application.Platform; 31 import javafx.beans.InvalidationListener; 32 import javafx.beans.Observable; 33 import javafx.beans.binding.BooleanExpression; 34 import javafx.beans.property.BooleanProperty; 35 import javafx.beans.property.BooleanPropertyBase; 36 import javafx.beans.property.DoubleProperty; 37 import javafx.beans.property.DoublePropertyBase; 38 import javafx.beans.property.IntegerProperty; 39 import javafx.beans.property.ObjectProperty; 40 import javafx.beans.property.ObjectPropertyBase; 41 import javafx.beans.property.ReadOnlyBooleanProperty; 42 import javafx.beans.property.ReadOnlyBooleanPropertyBase; 43 import javafx.beans.property.ReadOnlyBooleanWrapper; 44 import javafx.beans.property.ReadOnlyObjectProperty; 45 import javafx.beans.property.ReadOnlyObjectPropertyBase; 46 import javafx.beans.property.ReadOnlyObjectWrapper; 47 import javafx.beans.property.SimpleBooleanProperty; 48 import javafx.beans.property.SimpleObjectProperty; 49 import javafx.beans.property.StringProperty; 50 import javafx.beans.property.StringPropertyBase; 51 import javafx.beans.value.ChangeListener; 52 import javafx.collections.FXCollections; 53 import javafx.collections.ListChangeListener.Change; 54 import javafx.collections.ObservableList; 55 import javafx.collections.ObservableMap; 56 import javafx.collections.ObservableSet; 57 import javafx.css.CssMetaData; 58 import javafx.css.ParsedValue; 59 import javafx.css.PseudoClass; 60 import javafx.css.StyleConverter; 61 import javafx.css.Styleable; 62 import javafx.css.StyleableBooleanProperty; 63 import javafx.css.StyleableDoubleProperty; 64 import javafx.css.StyleableObjectProperty; 65 import javafx.css.StyleableProperty; 66 import javafx.event.Event; 67 import javafx.event.EventDispatchChain; 68 import javafx.event.EventDispatcher; 69 import javafx.event.EventHandler; 70 import javafx.event.EventTarget; 71 import javafx.event.EventType; 72 import javafx.geometry.BoundingBox; 73 import javafx.geometry.Bounds; 74 import javafx.geometry.NodeOrientation; 75 import javafx.geometry.Orientation; 76 import javafx.geometry.Point2D; 77 import javafx.geometry.Point3D; 78 import javafx.geometry.Rectangle2D; 79 import javafx.scene.effect.BlendMode; 80 import javafx.scene.effect.Effect; 81 import javafx.scene.image.WritableImage; 82 import javafx.scene.input.ContextMenuEvent; 83 import javafx.scene.input.DragEvent; 84 import javafx.scene.input.Dragboard; 85 import javafx.scene.input.InputEvent; 86 import javafx.scene.input.InputMethodEvent; 87 import javafx.scene.input.InputMethodRequests; 88 import javafx.scene.input.KeyEvent; 89 import javafx.scene.input.MouseDragEvent; 90 import javafx.scene.input.MouseEvent; 91 import javafx.scene.input.PickResult; 92 import javafx.scene.input.RotateEvent; 93 import javafx.scene.input.ScrollEvent; 94 import javafx.scene.input.SwipeEvent; 95 import javafx.scene.input.TouchEvent; 96 import javafx.scene.input.TransferMode; 97 import javafx.scene.input.ZoomEvent; 98 import javafx.scene.text.Font; 99 import javafx.scene.transform.Rotate; 100 import javafx.scene.transform.Transform; 101 import javafx.stage.Window; 102 import javafx.util.Callback; 103 import java.security.AccessControlContext; 104 105 import java.util.ArrayList; 106 import java.util.Collections; 107 import java.util.HashMap; 108 import java.util.LinkedList; 109 import java.util.List; 110 import java.util.Map; 111 import java.util.Set; 112 113 import com.sun.glass.ui.Accessible; 114 import com.sun.glass.ui.Application; 115 import com.sun.javafx.util.Logging; 116 import com.sun.javafx.util.TempState; 117 import com.sun.javafx.util.Utils; 118 import com.sun.javafx.beans.IDProperty; 119 import com.sun.javafx.beans.event.AbstractNotifyListener; 120 import com.sun.javafx.binding.ExpressionHelper; 121 import com.sun.javafx.collections.TrackableObservableList; 122 import com.sun.javafx.collections.UnmodifiableListSet; 123 import com.sun.javafx.css.PseudoClassState; 124 import javafx.css.Selector; 125 import javafx.css.Style; 126 import javafx.css.converter.BooleanConverter; 127 import javafx.css.converter.CursorConverter; 128 import javafx.css.converter.EffectConverter; 129 import javafx.css.converter.EnumConverter; 130 import javafx.css.converter.SizeConverter; 131 import com.sun.javafx.effect.EffectDirtyBits; 132 import com.sun.javafx.geom.BaseBounds; 133 import com.sun.javafx.geom.BoxBounds; 134 import com.sun.javafx.geom.PickRay; 135 import com.sun.javafx.geom.RectBounds; 136 import com.sun.javafx.geom.Vec3d; 137 import com.sun.javafx.geom.transform.Affine3D; 138 import com.sun.javafx.geom.transform.BaseTransform; 139 import com.sun.javafx.geom.transform.GeneralTransform3D; 140 import com.sun.javafx.geom.transform.NoninvertibleTransformException; 141 import com.sun.javafx.perf.PerformanceTracker; 142 import com.sun.javafx.scene.BoundsAccessor; 143 import com.sun.javafx.scene.CameraHelper; 144 import com.sun.javafx.scene.CssFlags; 145 import com.sun.javafx.scene.DirtyBits; 146 import com.sun.javafx.scene.EventHandlerProperties; 147 import com.sun.javafx.scene.LayoutFlags; 148 import com.sun.javafx.scene.NodeEventDispatcher; 149 import com.sun.javafx.scene.NodeHelper; 150 import com.sun.javafx.scene.SceneHelper; 151 import com.sun.javafx.scene.SceneUtils; 152 import com.sun.javafx.scene.input.PickResultChooser; 153 import com.sun.javafx.scene.transform.TransformHelper; 154 import com.sun.javafx.scene.transform.TransformUtils; 155 import com.sun.javafx.scene.traversal.Direction; 156 import com.sun.javafx.sg.prism.NGNode; 157 import com.sun.javafx.tk.Toolkit; 158 import com.sun.prism.impl.PrismSettings; 159 import com.sun.scenario.effect.EffectHelper; 160 161 import javafx.scene.shape.Shape3D; 162 import sun.util.logging.PlatformLogger; 163 import sun.util.logging.PlatformLogger.Level; 164 165 /** 166 * Base class for scene graph nodes. A scene graph is a set of tree data structures 167 * where every item has zero or one parent, and each item is either 168 * a "leaf" with zero sub-items or a "branch" with zero or more sub-items. 169 * <p> 170 * Each item in the scene graph is called a {@code Node}. Branch nodes are 171 * of type {@link Parent}, whose concrete subclasses are {@link Group}, 172 * {@link javafx.scene.layout.Region}, and {@link javafx.scene.control.Control}, 173 * or subclasses thereof. 174 * <p> 175 * Leaf nodes are classes such as 176 * {@link javafx.scene.shape.Rectangle}, {@link javafx.scene.text.Text}, 177 * {@link javafx.scene.image.ImageView}, {@link javafx.scene.media.MediaView}, 178 * or other such leaf classes which cannot have children. Only a single node within 179 * each scene graph tree will have no parent, which is referred to as the "root" node. 180 * <p> 181 * There may be several trees in the scene graph. Some trees may be part of 182 * a {@link Scene}, in which case they are eligible to be displayed. 183 * Other trees might not be part of any {@link Scene}. 184 * <p> 185 * A node may occur at most once anywhere in the scene graph. Specifically, 186 * a node must appear no more than once in all of the following: 187 * as the root node of a {@link Scene}, 188 * the children ObservableList of a {@link Parent}, 189 * or as the clip of a {@link Node}. 190 * <p> 191 * The scene graph must not have cycles. A cycle would exist if a node is 192 * an ancestor of itself in the tree, considering the {@link Group} content 193 * ObservableList, {@link Parent} children ObservableList, and {@link Node} clip relationships 194 * mentioned above. 195 * <p> 196 * If a program adds a child node to a Parent (including Group, Region, etc) 197 * and that node is already a child of a different Parent or the root of a Scene, 198 * the node is automatically (and silently) removed from its former parent. 199 * If a program attempts to modify the scene graph in any other way that violates 200 * the above rules, an exception is thrown, the modification attempt is ignored 201 * and the scene graph is restored to its previous state. 202 * <p> 203 * It is possible to rearrange the structure of the scene graph, for 204 * example, to move a subtree from one location in the scene graph to 205 * another. In order to do this, one would normally remove the subtree from 206 * its old location before inserting it at the new location. However, the 207 * subtree will be automatically removed as described above if the application 208 * doesn't explicitly remove it. 209 * <p> 210 * Node objects may be constructed and modified on any thread as long they are 211 * not yet attached to a {@link Scene} in a {@link Window} that is 212 * {@link Window#isShowing showing}. 213 * An application must attach nodes to such a Scene or modify them on the JavaFX 214 * Application Thread. 215 * 216 * <p> 217 * The JavaFX Application Thread is created as part of the startup process for 218 * the JavaFX runtime. See the {@link javafx.application.Application} class and 219 * the {@link Platform#startup(Runnable)} method for more information. 220 * </p> 221 * 222 * <p> 223 * An application should not extend the Node class directly. Doing so may lead to 224 * an UnsupportedOperationException being thrown. 225 * </p> 226 * 227 * <h3>String ID</h3> 228 * <p> 229 * Each node in the scene graph can be given a unique {@link #idProperty id}. This id is 230 * much like the "id" attribute of an HTML tag in that it is up to the designer 231 * and developer to ensure that the {@code id} is unique within the scene graph. 232 * A convenience function called {@link #lookup(String)} can be used to find 233 * a node with a unique id within the scene graph, or within a subtree of the 234 * scene graph. The id can also be used identify nodes for applying styles; see 235 * the CSS section below. 236 * 237 * <h4>Coordinate System</h4> 238 * <p> 239 * The {@code Node} class defines a traditional computer graphics "local" 240 * coordinate system in which the {@code x} axis increases to the right and the 241 * {@code y} axis increases downwards. The concrete node classes for shapes 242 * provide variables for defining the geometry and location of the shape 243 * within this local coordinate space. For example, 244 * {@link javafx.scene.shape.Rectangle} provides {@code x}, {@code y}, 245 * {@code width}, {@code height} variables while 246 * {@link javafx.scene.shape.Circle} provides {@code centerX}, {@code centerY}, 247 * and {@code radius}. 248 * <p> 249 * At the device pixel level, integer coordinates map onto the corners and 250 * cracks between the pixels and the centers of the pixels appear at the 251 * midpoints between integer pixel locations. Because all coordinate values 252 * are specified with floating point numbers, coordinates can precisely 253 * point to these corners (when the floating point values have exact integer 254 * values) or to any location on the pixel. For example, a coordinate of 255 * {@code (0.5, 0.5)} would point to the center of the upper left pixel on the 256 * {@code Stage}. Similarly, a rectangle at {@code (0, 0)} with dimensions 257 * of {@code 10} by {@code 10} would span from the upper left corner of the 258 * upper left pixel on the {@code Stage} to the lower right corner of the 259 * 10th pixel on the 10th scanline. The pixel center of the last pixel 260 * inside that rectangle would be at the coordinates {@code (9.5, 9.5)}. 261 * <p> 262 * In practice, most nodes have transformations applied to their coordinate 263 * system as mentioned below. As a result, the information above describing 264 * the alignment of device coordinates to the pixel grid is relative to 265 * the transformed coordinates, not the local coordinates of the nodes. 266 * The {@link javafx.scene.shape.Shape Shape} class describes some additional 267 * important context-specific information about coordinate mapping and how 268 * it can affect rendering. 269 * 270 * <h4>Transformations</h4> 271 * <p> 272 * Any {@code Node} can have transformations applied to it. These include 273 * translation, rotation, scaling, or shearing. 274 * <p> 275 * A <b>translation</b> transformation is one which shifts the origin of the 276 * node's coordinate space along either the x or y axis. For example, if you 277 * create a {@link javafx.scene.shape.Rectangle} which is drawn at the origin 278 * (x=0, y=0) and has a width of 100 and a height of 50, and then apply a 279 * {@link javafx.scene.transform.Translate} with a shift of 10 along the x axis 280 * (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain 281 * 100 points wide and 50 tall. Note that the origin was shifted, not the 282 * {@code x} variable of the rectangle. 283 * <p> 284 * A common node transform is a translation by an integer distance, most often 285 * used to lay out nodes on the stage. Such integer translations maintain the 286 * device pixel mapping so that local coordinates that are integers still 287 * map to the cracks between pixels. 288 * <p> 289 * A <b>rotation</b> transformation is one which rotates the coordinate space of 290 * the node about a specified "pivot" point, causing the node to appear rotated. 291 * For example, if you create a {@link javafx.scene.shape.Rectangle} which is 292 * drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and 293 * you apply a {@link javafx.scene.transform.Rotate} with a 90 degree rotation 294 * (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then 295 * the rectangle will be drawn as if its x and y were zero but its height was 296 * 100 and its width -30. That is, it is as if a pin is being stuck at the top 297 * left corner and the rectangle is rotating 90 degrees clockwise around that 298 * pin. If the pivot point is instead placed in the center of the rectangle 299 * (at point x=50, y=15) then the rectangle will instead appear to rotate about 300 * its center. 301 * <p> 302 * Note that as with all transformations, the x, y, width, and height variables 303 * of the rectangle (which remain relative to the local coordinate space) have 304 * not changed, but rather the transformation alters the entire coordinate space 305 * of the rectangle. 306 * <p> 307 * A <b>scaling</b> transformation causes a node to either appear larger or 308 * smaller depending on the scaling factor. Scaling alters the coordinate space 309 * of the node such that each unit of distance along the axis in local 310 * coordinates is multipled by the scale factor. As with rotation 311 * transformations, scaling transformations are applied about a "pivot" point. 312 * You can think of this as the point in the Node around which you "zoom". For 313 * example, if you create a {@link javafx.scene.shape.Rectangle} with a 314 * {@code strokeWidth} of 5, and a width and height of 50, and you apply a 315 * {@link javafx.scene.transform.Scale} with scale factors (x=2.0, y=2.0) and 316 * a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle 317 * (including the stroke) will double in size, growing to the right and 318 * downwards from the origin. 319 * <p> 320 * A <b>shearing</b> transformation, sometimes called a skew, effectively 321 * rotates one axis so that the x and y axes are no longer perpendicular. 322 * <p> 323 * Multiple transformations may be applied to a node by specifying an ordered 324 * chain of transforms. The order in which the transforms are applied is 325 * defined by the ObservableList specified in the {@link #getTransforms transforms} variable. 326 * 327 * <h3>Bounding Rectangles</h3> 328 * <p> 329 * Since every {@code Node} has transformations, every Node's geometric 330 * bounding rectangle can be described differently depending on whether 331 * transformations are accounted for or not. 332 * <p> 333 * Each {@code Node} has a read-only {@link #boundsInLocalProperty boundsInLocal} 334 * variable which specifies the bounding rectangle of the {@code Node} in 335 * untransformed local coordinates. {@code boundsInLocal} includes the 336 * Node's shape geometry, including any space required for a 337 * non-zero stroke that may fall outside the local position/size variables, 338 * and its {@link #clipProperty clip} and {@link #effectProperty effect} variables. 339 * <p> 340 * Each {@code Node} also has a read-only {@link #boundsInParentProperty boundsInParent} variable which 341 * specifies the bounding rectangle of the {@code Node} after all transformations 342 * have been applied, including those set in {@link #getTransforms transforms}, 343 * {@link #scaleXProperty scaleX}/{@link #scaleYProperty scaleY}, {@link #rotateProperty rotate}, 344 * {@link #translateXProperty translateX}/{@link #translateYProperty translateY}, and {@link #layoutXProperty layoutX}/{@link #layoutYProperty layoutY}. 345 * It is called "boundsInParent" because the rectangle will be relative to the 346 * parent's coordinate system. This is the 'visual' bounds of the node. 347 * <p> 348 * Finally, the {@link #layoutBoundsProperty layoutBounds} variable defines the rectangular bounds of 349 * the {@code Node} that should be used as the basis for layout calculations and 350 * may differ from the visual bounds of the node. For shapes, Text, and ImageView, 351 * layoutBounds by default includes only the shape geometry, including space required 352 * for a non-zero {@code strokeWidth}, but does <i>not</i> include the effect, 353 * clip, or any transforms. For resizable classes (Regions and Controls) 354 * layoutBounds will always map to {@code 0,0 width x height}. 355 * 356 * <p> The image shows a node without any transformation and its {@code boundsInLocal}: 357 * <p> <img src="doc-files/boundsLocal.png" alt="A sine wave shape enclosed by 358 * an axis-aligned rectangular bounds"> </p> 359 * If we rotate the image by 20 degrees we get following result: 360 * <p> <img src="doc-files/boundsParent.png" alt="An axis-aligned rectangular 361 * bounds that encloses the shape rotated by 20 degrees"> </p> 362 * The red rectangle represents {@code boundsInParent} in the 363 * coordinate space of the Node's parent. The {@code boundsInLocal} stays the same 364 * as in the first image, the green rectangle in this image represents {@code boundsInLocal} 365 * in the coordinate space of the Node. 366 * 367 * <p> The images show a filled and stroked rectangle and their bounds. The 368 * first rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]} 369 * has the following bounds bounds: {@code [x:10.0 y:10.0 width:100.0 height:100.0]}. 370 * 371 * The second rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]} 372 * has the following bounds: {@code [x:7.5 y:7.5 width:105 height:105]} 373 * (the stroke is centered by default, so only half of it is outside 374 * of the original bounds; it is also possible to create inside or outside 375 * stroke). 376 * 377 * Since neither of the rectangles has any transformation applied, 378 * {@code boundsInParent} and {@code boundsInLocal} are the same. </p> 379 * <p> <img src="doc-files/bounds.png" alt="The rectangles are enclosed by their 380 * respective bounds"> </p> 381 * 382 * 383 * <h3>CSS</h3> 384 * <p> 385 * The {@code Node} class contains {@code id}, {@code styleClass}, and 386 * {@code style} variables that are used in styling this node from 387 * CSS. The {@code id} and {@code styleClass} variables are used in 388 * CSS style sheets to identify nodes to which styles should be 389 * applied. The {@code style} variable contains style properties and 390 * values that are applied directly to this node. 391 * <p> 392 * For further information about CSS and how to apply CSS styles 393 * to nodes, see the <a href="doc-files/cssref.html">CSS Reference 394 * Guide</a>. 395 * @since JavaFX 2.0 396 */ 397 @IDProperty("id") 398 public abstract class Node implements EventTarget, Styleable { 399 400 /* 401 * Store the singleton instance of the NodeHelper subclass corresponding 402 * to the subclass of this instance of Node 403 */ 404 private NodeHelper nodeHelper = null; 405 406 static { 407 PerformanceTracker.logEvent("Node class loaded"); 408 409 // This is used by classes in different packages to get access to 410 // private and package private methods. 411 NodeHelper.setNodeAccessor(new NodeHelper.NodeAccessor() { 412 @Override 413 public NodeHelper getHelper(Node node) { 414 return node.nodeHelper; 415 } 416 417 @Override 418 public void setHelper(Node node, NodeHelper nodeHelper) { 419 node.nodeHelper = nodeHelper; 420 } 421 422 @Override 423 public void doMarkDirty(Node node, DirtyBits dirtyBit) { 424 node.doMarkDirty(dirtyBit); 425 } 426 427 @Override 428 public void doUpdatePeer(Node node) { 429 node.doUpdatePeer(); 430 } 431 432 @Override 433 public BaseTransform getLeafTransform(Node node) { 434 return node.getLeafTransform(); 435 } 436 437 @Override 438 public Bounds doComputeLayoutBounds(Node node) { 439 return node.doComputeLayoutBounds(); 440 } 441 442 @Override 443 public void doTransformsChanged(Node node) { 444 node.doTransformsChanged(); 445 } 446 447 @Override 448 public void doPickNodeLocal(Node node, PickRay localPickRay, 449 PickResultChooser result) { 450 node.doPickNodeLocal(localPickRay, result); 451 } 452 453 @Override 454 public boolean doComputeIntersects(Node node, PickRay pickRay, 455 PickResultChooser pickResult) { 456 return node.doComputeIntersects(pickRay, pickResult); 457 } 458 459 @Override 460 public void doGeomChanged(Node node) { 461 node.doGeomChanged(); 462 } 463 464 @Override 465 public void doNotifyLayoutBoundsChanged(Node node) { 466 node.doNotifyLayoutBoundsChanged(); 467 } 468 469 @Override 470 public void doProcessCSS(Node node) { 471 node.doProcessCSS(); 472 } 473 474 @Override 475 public boolean isDirty(Node node, DirtyBits dirtyBit) { 476 return node.isDirty(dirtyBit); 477 } 478 479 @Override 480 public boolean isDirtyEmpty(Node node) { 481 return node.isDirtyEmpty(); 482 } 483 484 @Override 485 public void syncPeer(Node node) { 486 node.syncPeer(); 487 } 488 489 @Override 490 public void layoutBoundsChanged(Node node) { 491 node.layoutBoundsChanged(); 492 } 493 494 @Override 495 public <P extends NGNode> P getPeer(Node node) { 496 return node.getPeer(); 497 } 498 499 @Override 500 public void setShowMnemonics(Node node, boolean value) { 501 node.setShowMnemonics(value); 502 } 503 504 @Override 505 public boolean isShowMnemonics(Node node) { 506 return node.isShowMnemonics(); 507 } 508 509 @Override 510 public BooleanProperty showMnemonicsProperty(Node node) { 511 return node.showMnemonicsProperty(); 512 } 513 514 @Override 515 public boolean traverse(Node node, Direction direction) { 516 return node.traverse(direction); 517 } 518 519 @Override 520 public double getPivotX(Node node) { 521 return node.getPivotX(); 522 } 523 524 @Override 525 public double getPivotY(Node node) { 526 return node.getPivotY(); 527 } 528 529 @Override 530 public double getPivotZ(Node node) { 531 return node.getPivotZ(); 532 } 533 534 @Override 535 public void pickNode(Node node,PickRay pickRay, 536 PickResultChooser result) { 537 node.pickNode(pickRay, result); 538 } 539 540 @Override 541 public boolean intersects(Node node, PickRay pickRay, 542 PickResultChooser pickResult) { 543 return node.intersects(pickRay, pickResult); 544 } 545 546 @Override 547 public double intersectsBounds(Node node, PickRay pickRay) { 548 return node.intersectsBounds(pickRay); 549 } 550 551 @Override 552 public void layoutNodeForPrinting(Node node) { 553 node.doCSSLayoutSyncForSnapshot(); 554 } 555 556 @Override 557 public boolean isDerivedDepthTest(Node node) { 558 return node.isDerivedDepthTest(); 559 } 560 561 @Override 562 public SubScene getSubScene(Node node) { 563 return node.getSubScene(); 564 } 565 566 @Override 567 public void setLabeledBy(Node node, Node labeledBy) { 568 node.labeledBy = labeledBy; 569 } 570 571 @Override 572 public Accessible getAccessible(Node node) { 573 return node.getAccessible(); 574 } 575 576 @Override 577 public void reapplyCSS(Node node) { 578 node.reapplyCSS(); 579 } 580 581 @Override 582 public boolean isTreeVisible(Node node) { 583 return node.isTreeVisible(); 584 } 585 586 @Override 587 public BooleanExpression treeVisibleProperty(Node node) { 588 return node.treeVisibleProperty(); 589 } 590 591 @Override 592 public boolean isTreeShowing(Node node) { 593 return node.isTreeShowing(); 594 } 595 596 @Override 597 public BooleanExpression treeShowingProperty(Node node) { 598 return node.treeShowingProperty(); 599 } 600 601 @Override 602 public List<Style> getMatchingStyles(CssMetaData cssMetaData, 603 Styleable styleable) { 604 return Node.getMatchingStyles(cssMetaData, styleable); 605 } 606 607 @Override 608 public Map<StyleableProperty<?>, List<Style>> findStyles(Node node, 609 Map<StyleableProperty<?>, List<Style>> styleMap) { 610 return node.findStyles(styleMap); 611 } 612 }); 613 } 614 615 /************************************************************************** 616 * * 617 * Methods and state for managing the dirty bits of a Node. The dirty * 618 * bits are flags used to keep track of what things are dirty on the * 619 * node and therefore need processing on the next pulse. Since the pulse * 620 * happens asynchronously to the change that made the node dirty (for * 621 * performance reasons), we need to keep track of what things have * 622 * changed. * 623 * * 624 *************************************************************************/ 625 626 /* 627 * Set of dirty bits that are set when state is invalidated and cleared by 628 * the updateState method, which is called from the synchronizer. 629 */ 630 private int dirtyBits; 631 632 /* 633 * Mark the specified bit as dirty, and add this node to the scene's dirty list. 634 * 635 * Note: This method MUST only be called via its accessor method. 636 */ 637 private void doMarkDirty(DirtyBits dirtyBit) { 638 if (isDirtyEmpty()) { 639 addToSceneDirtyList(); 640 } 641 642 dirtyBits |= dirtyBit.getMask(); 643 } 644 645 private void addToSceneDirtyList() { 646 Scene s = getScene(); 647 if (s != null) { 648 s.addToDirtyList(this); 649 if (getSubScene() != null) { 650 getSubScene().setDirty(this); 651 } 652 } 653 } 654 655 /* 656 * Test whether the specified dirty bit is set 657 */ 658 final boolean isDirty(DirtyBits dirtyBit) { 659 return (dirtyBits & dirtyBit.getMask()) != 0; 660 } 661 662 /* 663 * Clear the specified dirty bit 664 */ 665 final void clearDirty(DirtyBits dirtyBit) { 666 dirtyBits &= ~dirtyBit.getMask(); 667 } 668 669 /* 670 * Set all dirty bits 671 */ 672 private void setDirty() { 673 dirtyBits = ~0; 674 } 675 676 /* 677 * Clear all dirty bits 678 */ 679 private void clearDirty() { 680 dirtyBits = 0; 681 } 682 683 /* 684 * Test whether the set of dirty bits is empty 685 */ 686 final boolean isDirtyEmpty() { 687 return dirtyBits == 0; 688 } 689 690 /************************************************************************** 691 * * 692 * Methods for synchronizing state from this Node to its PG peer. This * 693 * should only *ever* be called during synchronization initialized as a * 694 * result of a pulse. Any attempt to synchronize at any other time may * 695 * cause rendering artifacts. * 696 * * 697 *************************************************************************/ 698 699 /* 700 * Called by the synchronizer to update the state and 701 * clear dirtybits of this node in the PG graph 702 */ 703 final void syncPeer() { 704 // Do not synchronize invisible nodes unless their visibility has changed 705 // or they have requested a forced synchronization 706 if (!isDirtyEmpty() && (treeVisible 707 || isDirty(DirtyBits.NODE_VISIBLE) 708 || isDirty(DirtyBits.NODE_FORCE_SYNC))) 709 { 710 NodeHelper.updatePeer(this); 711 clearDirty(); 712 } 713 } 714 715 /** 716 * A temporary rect used for computing bounds by the various bounds 717 * variables. This bounds starts life as a RectBounds, but may be promoted 718 * to a BoxBounds if there is a 3D transform mixed into its computation. 719 * These two fields were held in a thread local, but were then pulled 720 * out of it so that we could compute bounds before holding the 721 * synchronization lock. These objects have to be per-instance so 722 * that we can pass the right data down to the PG side later during 723 * synchronization (rather than statics as they were before). 724 */ 725 private BaseBounds _geomBounds = new RectBounds(0, 0, -1, -1); 726 private BaseBounds _txBounds = new RectBounds(0, 0, -1, -1); 727 728 private boolean pendingUpdateBounds = false; 729 730 // Happens before we hold the sync lock 731 void updateBounds() { 732 // Note: the clip must be handled before the visibility is checked. This is because the visiblity might be 733 // changing in the clip and it is going to be synchronized, so it needs to recompute the bounds. 734 Node n = getClip(); 735 if (n != null) { 736 n.updateBounds(); 737 } 738 739 // See syncPeer() 740 if (!treeVisible && !isDirty(DirtyBits.NODE_VISIBLE)) { 741 742 // Need to save the dirty bits since they will be cleared even for the 743 // case of short circuiting dirty bit processing. 744 if (isDirty(DirtyBits.NODE_TRANSFORM) 745 || isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS) 746 || isDirty(DirtyBits.NODE_BOUNDS)) { 747 pendingUpdateBounds = true; 748 } 749 750 return; 751 } 752 753 // Set transform and bounds dirty bits when this node becomes visible 754 if (pendingUpdateBounds) { 755 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM); 756 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS); 757 NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS); 758 759 pendingUpdateBounds = false; 760 } 761 762 if (isDirty(DirtyBits.NODE_TRANSFORM) || isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) { 763 if (isDirty(DirtyBits.NODE_TRANSFORM)) { 764 updateLocalToParentTransform(); 765 } 766 _txBounds = getTransformedBounds(_txBounds, 767 BaseTransform.IDENTITY_TRANSFORM); 768 } 769 770 if (isDirty(DirtyBits.NODE_BOUNDS)) { 771 _geomBounds = getGeomBounds(_geomBounds, 772 BaseTransform.IDENTITY_TRANSFORM); 773 } 774 775 } 776 777 /* 778 * This function is called during synchronization to update the state of the 779 * NG Node from the FX Node. Subclasses of Node should override this method 780 * and must call NodeHelper.updatePeer(this) 781 * 782 * Note: This method MUST only be called via its accessor method. 783 */ 784 private void doUpdatePeer() { 785 final NGNode peer = getPeer(); 786 787 // For debug / diagnostic purposes, we will copy across a name for this node down to 788 // the NG layer, where we can use the name to figure out what the NGNode represents. 789 // An alternative would be to have a back-reference from the NGNode back to the Node it 790 // is a peer to, however it was felt that this would make it too easy to communicate back 791 // to the Node and possibly violate thread invariants. But of course, we only need to do this 792 // if we're going to print the render graph (otherwise all the work we'd do to keep the name 793 // properly updated would be a waste). 794 if (PrismSettings.printRenderGraph && isDirty(DirtyBits.DEBUG)) { 795 final String id = getId(); 796 String className = getClass().getSimpleName(); 797 if (className.isEmpty()) { 798 className = getClass().getName(); 799 } 800 peer.setName(id == null ? className : id + "(" + className + ")"); 801 } 802 803 if (isDirty(DirtyBits.NODE_TRANSFORM)) { 804 peer.setTransformMatrix(localToParentTx); 805 } 806 807 if (isDirty(DirtyBits.NODE_VIEW_ORDER)) { 808 peer.setViewOrder(getViewOrder()); 809 } 810 811 if (isDirty(DirtyBits.NODE_BOUNDS)) { 812 peer.setContentBounds(_geomBounds); 813 } 814 815 if (isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) { 816 peer.setTransformedBounds(_txBounds, !isDirty(DirtyBits.NODE_BOUNDS)); 817 } 818 819 if (isDirty(DirtyBits.NODE_OPACITY)) { 820 peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1)); 821 } 822 823 if (isDirty(DirtyBits.NODE_CACHE)) { 824 peer.setCachedAsBitmap(isCache(), getCacheHint()); 825 } 826 827 if (isDirty(DirtyBits.NODE_CLIP)) { 828 peer.setClipNode(getClip() != null ? getClip().getPeer() : null); 829 } 830 831 if (isDirty(DirtyBits.EFFECT_EFFECT)) { 832 if (getEffect() != null) { 833 EffectHelper.sync(getEffect()); 834 peer.effectChanged(); 835 } 836 } 837 838 if (isDirty(DirtyBits.NODE_EFFECT)) { 839 peer.setEffect(getEffect() != null ? EffectHelper.getPeer(getEffect()) : null); 840 } 841 842 if (isDirty(DirtyBits.NODE_VISIBLE)) { 843 peer.setVisible(isVisible()); 844 } 845 846 if (isDirty(DirtyBits.NODE_DEPTH_TEST)) { 847 peer.setDepthTest(isDerivedDepthTest()); 848 } 849 850 if (isDirty(DirtyBits.NODE_BLENDMODE)) { 851 BlendMode mode = getBlendMode(); 852 peer.setNodeBlendMode((mode == null) 853 ? null 854 : EffectHelper.getToolkitBlendMode(mode)); 855 } 856 } 857 858 /************************************************************************* 859 * * 860 * * 861 * * 862 *************************************************************************/ 863 864 private static final Object USER_DATA_KEY = new Object(); 865 // A map containing a set of properties for this node 866 private ObservableMap<Object, Object> properties; 867 868 /** 869 * Returns an observable map of properties on this node for use primarily 870 * by application developers. 871 * 872 * @return an observable map of properties on this node for use primarily 873 * by application developers 874 */ 875 public final ObservableMap<Object, Object> getProperties() { 876 if (properties == null) { 877 properties = FXCollections.observableMap(new HashMap<Object, Object>()); 878 } 879 return properties; 880 } 881 882 /** 883 * Tests if Node has properties. 884 * @return true if node has properties. 885 */ 886 public boolean hasProperties() { 887 return properties != null && !properties.isEmpty(); 888 } 889 890 /** 891 * Convenience method for setting a single Object property that can be 892 * retrieved at a later date. This is functionally equivalent to calling 893 * the getProperties().put(Object key, Object value) method. This can later 894 * be retrieved by calling {@link Node#getUserData()}. 895 * 896 * @param value The value to be stored - this can later be retrieved by calling 897 * {@link Node#getUserData()}. 898 */ 899 public void setUserData(Object value) { 900 getProperties().put(USER_DATA_KEY, value); 901 } 902 903 /** 904 * Returns a previously set Object property, or null if no such property 905 * has been set using the {@link Node#setUserData(java.lang.Object)} method. 906 * 907 * @return The Object that was previously set, or null if no property 908 * has been set or if null was set. 909 */ 910 public Object getUserData() { 911 return getProperties().get(USER_DATA_KEY); 912 } 913 914 /************************************************************************** 915 * * 916 * 917 * * 918 *************************************************************************/ 919 920 /** 921 * The parent of this {@code Node}. If this {@code Node} has not been added 922 * to a scene graph, then parent will be null. 923 * 924 * @defaultValue null 925 */ 926 private ReadOnlyObjectWrapper<Parent> parent; 927 928 final void setParent(Parent value) { 929 parentPropertyImpl().set(value); 930 } 931 932 public final Parent getParent() { 933 return parent == null ? null : parent.get(); 934 } 935 936 public final ReadOnlyObjectProperty<Parent> parentProperty() { 937 return parentPropertyImpl().getReadOnlyProperty(); 938 } 939 940 private ReadOnlyObjectWrapper<Parent> parentPropertyImpl() { 941 if (parent == null) { 942 parent = new ReadOnlyObjectWrapper<Parent>() { 943 private Parent oldParent; 944 945 @Override 946 protected void invalidated() { 947 if (oldParent != null) { 948 oldParent.disabledProperty().removeListener(parentDisabledChangedListener); 949 oldParent.treeVisibleProperty().removeListener(parentTreeVisibleChangedListener); 950 if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) { 951 ((Node) oldParent).localToSceneTransformProperty().removeListener( 952 nodeTransformation.getLocalToSceneInvalidationListener()); 953 } 954 } 955 updateDisabled(); 956 computeDerivedDepthTest(); 957 final Parent newParent = get(); 958 if (newParent != null) { 959 newParent.disabledProperty().addListener(parentDisabledChangedListener); 960 newParent.treeVisibleProperty().addListener(parentTreeVisibleChangedListener); 961 if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) { 962 ((Node) newParent).localToSceneTransformProperty().addListener( 963 nodeTransformation.getLocalToSceneInvalidationListener()); 964 } 965 // 966 // if parent changed, then CSS needs to be reapplied so 967 // that this node will get the right styles. This used 968 // to be done from Parent.children's onChanged method. 969 // See the comments there, also. 970 // 971 reapplyCSS(); 972 } else { 973 // RT-31168: reset CssFlag to clean so css will be reapplied if the node is added back later. 974 // If flag is REAPPLY, then reapplyCSS() will just return and the call to 975 // notifyParentsOfInvalidatedCSS() will be skipped thus leaving the node un-styled. 976 cssFlag = CssFlags.CLEAN; 977 } 978 updateTreeVisible(true); 979 oldParent = newParent; 980 invalidateLocalToSceneTransform(); 981 parentResolvedOrientationInvalidated(); 982 notifyAccessibleAttributeChanged(AccessibleAttribute.PARENT); 983 } 984 985 @Override 986 public Object getBean() { 987 return Node.this; 988 } 989 990 @Override 991 public String getName() { 992 return "parent"; 993 } 994 }; 995 } 996 return parent; 997 } 998 999 private final InvalidationListener parentDisabledChangedListener = valueModel -> updateDisabled(); 1000 1001 private final InvalidationListener parentTreeVisibleChangedListener = valueModel -> updateTreeVisible(true); 1002 1003 private final ChangeListener<Boolean> windowShowingChangedListener 1004 = (win, oldVal, newVal) -> updateTreeShowing(); 1005 1006 private final ChangeListener<Window> sceneWindowChangedListener = (scene, oldWindow, newWindow) -> { 1007 // Replace the windowShowingListener and call updateTreeShowing() 1008 if (oldWindow != null) { 1009 oldWindow.showingProperty().removeListener(windowShowingChangedListener); 1010 } 1011 if (newWindow != null) { 1012 newWindow.showingProperty().addListener(windowShowingChangedListener); 1013 } 1014 updateTreeShowing(); 1015 }; 1016 1017 private SubScene subScene = null; 1018 1019 /** 1020 * The {@link Scene} that this {@code Node} is part of. If the Node is not 1021 * part of a scene, then this variable will be null. 1022 * 1023 * @defaultValue null 1024 */ 1025 private ReadOnlyObjectWrapperManualFire<Scene> scene = new ReadOnlyObjectWrapperManualFire<Scene>(); 1026 1027 private class ReadOnlyObjectWrapperManualFire<T> extends ReadOnlyObjectWrapper<T> { 1028 @Override 1029 public Object getBean() { 1030 return Node.this; 1031 } 1032 1033 @Override 1034 public String getName() { 1035 return "scene"; 1036 } 1037 1038 @Override 1039 protected void fireValueChangedEvent() { 1040 /* 1041 * Note: This method has been intentionally made into a no-op. In 1042 * order to override the default set behavior. By default calling 1043 * set(...) on a different scene will trigger: 1044 * - invalidated(); 1045 * - fireValueChangedEvent(); 1046 * Both of the above are no-ops, but are handled manually via 1047 * - Node.this.setScenes(...) 1048 * - Node.this.invalidatedScenes(...) 1049 * - forceValueChangedEvent() 1050 */ 1051 } 1052 1053 public void fireSuperValueChangedEvent() { 1054 super.fireValueChangedEvent(); 1055 } 1056 } 1057 1058 // reapplyCSS should be true for root elements when they are added, and is false for children 1059 // of the root element. This prevents CSS being reapplied recursively, as noted in JDK-8151756. 1060 private void invalidatedScenes(Scene oldScene, SubScene oldSubScene, boolean reapplyCSS) { 1061 Scene newScene = sceneProperty().get(); 1062 boolean sceneChanged = oldScene != newScene; 1063 SubScene newSubScene = subScene; 1064 1065 if (getClip() != null) { 1066 getClip().setScenes(newScene, newSubScene, reapplyCSS); 1067 } 1068 if (sceneChanged) { 1069 updateCanReceiveFocus(); 1070 if (isFocusTraversable()) { 1071 if (newScene != null) { 1072 newScene.initializeInternalEventDispatcher(); 1073 } 1074 } 1075 focusSetDirty(oldScene); 1076 focusSetDirty(newScene); 1077 } 1078 scenesChanged(newScene, newSubScene, oldScene, oldSubScene); 1079 1080 // isTreeShowing needs to take into account of Window's showing 1081 if (oldScene != null) { 1082 oldScene.windowProperty().removeListener(sceneWindowChangedListener); 1083 } 1084 if (newScene != null) { 1085 newScene.windowProperty().addListener(sceneWindowChangedListener); 1086 } 1087 updateTreeShowing(); 1088 1089 if (sceneChanged && reapplyCSS) reapplyCSS(); 1090 1091 if (sceneChanged && !isDirtyEmpty()) { 1092 //Note: no need to remove from scene's dirty list 1093 //Scene's is checking if the node's scene is correct 1094 /* TODO: looks like an existing bug when a node is moved from one 1095 * location to another, setScenes will be called twice by 1096 * Parent.VetoableListDecorator onProposedChange and onChanged 1097 * respectively. Removing the node and setting setScense(null,null) 1098 * then adding it back to potentially the same scene. Causing the 1099 * same node to being added twice to the same scene. 1100 */ 1101 addToSceneDirtyList(); 1102 } 1103 1104 if (newScene == null && peer != null) { 1105 peer.release(); 1106 } 1107 1108 if (oldScene != null) { 1109 oldScene.clearNodeMnemonics(this); 1110 } 1111 if (getParent() == null) { 1112 // if we are the root we need to handle scene change 1113 parentResolvedOrientationInvalidated(); 1114 } 1115 1116 if (sceneChanged) { scene.fireSuperValueChangedEvent(); } 1117 1118 /* Dispose the accessible peer, if any. If AT ever needs this node again 1119 * a new accessible peer is created. */ 1120 if (accessible != null) { 1121 /* Generally accessibility does not retain any state, therefore deleting objects 1122 * generally does not cause problems (AT just asks everything back). 1123 * The exception to this rule is when the object sends a notifications to the AT, 1124 * in which case it is expected to be around to answer request for the new values. 1125 * It is possible that a object is reparented (within the scene) in the middle of 1126 * this process. For example, when a tree item is expanded, the notification is 1127 * sent to the AT by the cell. But when the TreeView relayouts the cell can be 1128 * reparented before AT can query the relevant information about the expand event. 1129 * If the accessible was disposed, AT can't properly report the event. 1130 * 1131 * The fix is to defer the disposal of the accessible to the next pulse. 1132 * If at that time the node is placed back to the scene, then the accessible is hooked 1133 * to Node and AT requests are processed. Otherwise the accessible is disposed. 1134 */ 1135 if (oldScene != null && oldScene != newScene && newScene == null) { 1136 // Strictly speaking we need some type of accessible.thaw() at this point. 1137 oldScene.addAccessible(Node.this, accessible); 1138 } else { 1139 accessible.dispose(); 1140 } 1141 /* Always set to null to ensure this accessible is never on more than one 1142 * Scene#accMap at the same time (At lest not with the same accessible). 1143 */ 1144 accessible = null; 1145 } 1146 } 1147 1148 final void setScenes(Scene newScene, SubScene newSubScene, boolean reapplyCSS) { 1149 Scene oldScene = sceneProperty().get(); 1150 if (newScene != oldScene || newSubScene != subScene) { 1151 scene.set(newScene); 1152 SubScene oldSubScene = subScene; 1153 subScene = newSubScene; 1154 invalidatedScenes(oldScene, oldSubScene, reapplyCSS); 1155 if (this instanceof SubScene) { // TODO: find better solution 1156 SubScene thisSubScene = (SubScene)this; 1157 thisSubScene.getRoot().setScenes(newScene, thisSubScene, reapplyCSS); 1158 } 1159 } 1160 } 1161 1162 final SubScene getSubScene() { 1163 return subScene; 1164 } 1165 1166 public final Scene getScene() { 1167 return scene.get(); 1168 } 1169 1170 public final ReadOnlyObjectProperty<Scene> sceneProperty() { 1171 return scene.getReadOnlyProperty(); 1172 } 1173 1174 /** 1175 * Exists for Parent and LightBase 1176 */ 1177 void scenesChanged(final Scene newScene, final SubScene newSubScene, 1178 final Scene oldScene, final SubScene oldSubScene) { } 1179 1180 1181 /** 1182 * The id of this {@code Node}. This simple string identifier is useful for 1183 * finding a specific Node within the scene graph. While the id of a Node 1184 * should be unique within the scene graph, this uniqueness is not enforced. 1185 * This is analogous to the "id" attribute on an HTML element 1186 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>). 1187 * <p> 1188 * For example, if a Node is given the id of "myId", then the lookup method can 1189 * be used to find this node as follows: <code>scene.lookup("#myId");</code>. 1190 * </p> 1191 * 1192 * @defaultValue null 1193 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1194 */ 1195 private StringProperty id; 1196 1197 public final void setId(String value) { 1198 idProperty().set(value); 1199 } 1200 1201 //TODO: this is copied from the property in order to add the @return statement. 1202 // We should have a better, general solution without the need to copy it. 1203 /** 1204 * The id of this {@code Node}. This simple string identifier is useful for 1205 * finding a specific Node within the scene graph. While the id of a Node 1206 * should be unique within the scene graph, this uniqueness is not enforced. 1207 * This is analogous to the "id" attribute on an HTML element 1208 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>). 1209 * 1210 * @return the id assigned to this {@code Node} using the {@code setId} 1211 * method or {@code null}, if no id has been assigned. 1212 * @defaultValue null 1213 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a> 1214 */ 1215 public final String getId() { 1216 return id == null ? null : id.get(); 1217 } 1218 1219 public final StringProperty idProperty() { 1220 if (id == null) { 1221 id = new StringPropertyBase() { 1222 1223 @Override 1224 protected void invalidated() { 1225 reapplyCSS(); 1226 if (PrismSettings.printRenderGraph) { 1227 NodeHelper.markDirty(Node.this, DirtyBits.DEBUG); 1228 } 1229 } 1230 1231 @Override 1232 public Object getBean() { 1233 return Node.this; 1234 } 1235 1236 @Override 1237 public String getName() { 1238 return "id"; 1239 } 1240 }; 1241 } 1242 return id; 1243 } 1244 1245 /** 1246 * A list of String identifiers which can be used to logically group 1247 * Nodes, specifically for an external style engine. This variable is 1248 * analogous to the "class" attribute on an HTML element and, as such, 1249 * each element of the list is a style class to which this Node belongs. 1250 * 1251 * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a> 1252 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1253 * @defaultValue null 1254 */ 1255 private ObservableList<String> styleClass = new TrackableObservableList<String>() { 1256 @Override 1257 protected void onChanged(Change<String> c) { 1258 reapplyCSS(); 1259 } 1260 1261 @Override 1262 public String toString() { 1263 if (size() == 0) { 1264 return ""; 1265 } else if (size() == 1) { 1266 return get(0); 1267 } else { 1268 StringBuilder buf = new StringBuilder(); 1269 for (int i = 0; i < size(); i++) { 1270 buf.append(get(i)); 1271 if (i + 1 < size()) { 1272 buf.append(' '); 1273 } 1274 } 1275 return buf.toString(); 1276 } 1277 } 1278 }; 1279 1280 @Override 1281 public final ObservableList<String> getStyleClass() { 1282 return styleClass; 1283 } 1284 1285 /** 1286 * A string representation of the CSS style associated with this 1287 * specific {@code Node}. This is analogous to the "style" attribute of an 1288 * HTML element. Note that, like the HTML style attribute, this 1289 * variable contains style properties and values and not the 1290 * selector portion of a style rule. 1291 * @defaultValue empty string 1292 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>. 1293 */ 1294 private StringProperty style; 1295 1296 /** 1297 * A string representation of the CSS style associated with this 1298 * specific {@code Node}. This is analogous to the "style" attribute of an 1299 * HTML element. Note that, like the HTML style attribute, this 1300 * variable contains style properties and values and not the 1301 * selector portion of a style rule. 1302 * @param value The inline CSS style to use for this {@code Node}. 1303 * {@code null} is implicitly converted to an empty String. 1304 * @defaultValue empty string 1305 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a> 1306 */ 1307 public final void setStyle(String value) { 1308 styleProperty().set(value); 1309 } 1310 1311 // TODO: javadoc copied from property for the sole purpose of providing a return tag 1312 /** 1313 * A string representation of the CSS style associated with this 1314 * specific {@code Node}. This is analogous to the "style" attribute of an 1315 * HTML element. Note that, like the HTML style attribute, this 1316 * variable contains style properties and values and not the 1317 * selector portion of a style rule. 1318 * @defaultValue empty string 1319 * @return The inline CSS style associated with this {@code Node}. 1320 * If this {@code Node} does not have an inline style, 1321 * an empty String is returned. 1322 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a> 1323 */ 1324 public final String getStyle() { 1325 return style == null ? "" : style.get(); 1326 } 1327 1328 public final StringProperty styleProperty() { 1329 if (style == null) { 1330 style = new StringPropertyBase("") { 1331 1332 @Override public void set(String value) { 1333 // getStyle returns an empty string if the style property 1334 // is null. To be consistent, getStyle should also return 1335 // an empty string when the style property's value is null. 1336 super.set((value != null) ? value : ""); 1337 } 1338 1339 @Override 1340 protected void invalidated() { 1341 // If the style has changed, then styles of this node 1342 // and child nodes might be affected. 1343 reapplyCSS(); 1344 } 1345 1346 @Override 1347 public Object getBean() { 1348 return Node.this; 1349 } 1350 1351 @Override 1352 public String getName() { 1353 return "style"; 1354 } 1355 }; 1356 } 1357 return style; 1358 } 1359 1360 /** 1361 * Specifies whether this {@code Node} and any subnodes should be rendered 1362 * as part of the scene graph. A node may be visible and yet not be shown 1363 * in the rendered scene if, for instance, it is off the screen or obscured 1364 * by another Node. Invisible nodes never receive mouse events or 1365 * keyboard focus and never maintain keyboard focus when they become 1366 * invisible. 1367 * 1368 * @defaultValue true 1369 */ 1370 private BooleanProperty visible; 1371 1372 public final void setVisible(boolean value) { 1373 visibleProperty().set(value); 1374 } 1375 1376 public final boolean isVisible() { 1377 return visible == null ? true : visible.get(); 1378 } 1379 1380 public final BooleanProperty visibleProperty() { 1381 if (visible == null) { 1382 visible = new StyleableBooleanProperty(true) { 1383 boolean oldValue = true; 1384 @Override 1385 protected void invalidated() { 1386 if (oldValue != get()) { 1387 NodeHelper.markDirty(Node.this, DirtyBits.NODE_VISIBLE); 1388 NodeHelper.geomChanged(Node.this); 1389 updateTreeVisible(false); 1390 if (getParent() != null) { 1391 // notify the parent of the potential change in visibility 1392 // of this node, since visibility affects bounds of the 1393 // parent node 1394 getParent().childVisibilityChanged(Node.this); 1395 } 1396 oldValue = get(); 1397 } 1398 } 1399 1400 @Override 1401 public CssMetaData getCssMetaData() { 1402 return StyleableProperties.VISIBILITY; 1403 } 1404 1405 @Override 1406 public Object getBean() { 1407 return Node.this; 1408 } 1409 1410 @Override 1411 public String getName() { 1412 return "visible"; 1413 } 1414 }; 1415 } 1416 return visible; 1417 } 1418 1419 public final void setCursor(Cursor value) { 1420 cursorProperty().set(value); 1421 } 1422 1423 public final Cursor getCursor() { 1424 return (miscProperties == null) ? DEFAULT_CURSOR 1425 : miscProperties.getCursor(); 1426 } 1427 1428 /** 1429 * Defines the mouse cursor for this {@code Node} and subnodes. If null, 1430 * then the cursor of the first parent node with a non-null cursor will be 1431 * used. If no Node in the scene graph defines a cursor, then the cursor 1432 * of the {@code Scene} will be used. 1433 * 1434 * @return the mouse cursor for this {@code Node} and subnodes 1435 * @defaultValue null 1436 */ 1437 public final ObjectProperty<Cursor> cursorProperty() { 1438 return getMiscProperties().cursorProperty(); 1439 } 1440 1441 /** 1442 * Specifies how opaque (that is, solid) the {@code Node} appears. A Node 1443 * with 0% opacity is fully translucent. That is, while it is still 1444 * {@link #visibleProperty visible} and rendered, you generally won't be able to see it. The 1445 * exception to this rule is when the {@code Node} is combined with a 1446 * blending mode and blend effect in which case a translucent Node may still 1447 * have an impact in rendering. An opacity of 50% will render the node as 1448 * being 50% transparent. 1449 * <p> 1450 * A {@link #visibleProperty visible} node with any opacity setting still receives mouse 1451 * events and can receive keyboard focus. For example, if you want to have 1452 * a large invisible rectangle overlay all {@code Node}s in the scene graph 1453 * in order to intercept mouse events but not be visible to the user, you could 1454 * create a large {@code Rectangle} that had an opacity of 0%. 1455 * <p> 1456 * Opacity is specified as a value between 0 and 1. Values less than 0 are 1457 * treated as 0, values greater than 1 are treated as 1. 1458 * <p> 1459 * On some platforms ImageView might not support opacity variable. 1460 * 1461 * <p> 1462 * There is a known limitation of mixing opacity < 1.0 with a 3D Transform. 1463 * Opacity/Blending is essentially a 2D image operation. The result of 1464 * an opacity < 1.0 set on a {@link Group} node with 3D transformed children 1465 * will cause its children to be rendered in order without Z-buffering 1466 * applied between those children. 1467 * 1468 * @defaultValue 1.0 1469 */ 1470 private DoubleProperty opacity; 1471 1472 public final void setOpacity(double value) { 1473 opacityProperty().set(value); 1474 } 1475 public final double getOpacity() { 1476 return opacity == null ? 1 : opacity.get(); 1477 } 1478 1479 public final DoubleProperty opacityProperty() { 1480 if (opacity == null) { 1481 opacity = new StyleableDoubleProperty(1) { 1482 1483 @Override 1484 public void invalidated() { 1485 NodeHelper.markDirty(Node.this, DirtyBits.NODE_OPACITY); 1486 } 1487 1488 @Override 1489 public CssMetaData getCssMetaData() { 1490 return StyleableProperties.OPACITY; 1491 } 1492 1493 @Override 1494 public Object getBean() { 1495 return Node.this; 1496 } 1497 1498 @Override 1499 public String getName() { 1500 return "opacity"; 1501 } 1502 }; 1503 } 1504 return opacity; 1505 } 1506 1507 /** 1508 * The {@link javafx.scene.effect.BlendMode} used to blend this individual node 1509 * into the scene behind it. If this node happens to be a Group then all of the 1510 * children will be composited individually into a temporary buffer using their 1511 * own blend modes and then that temporary buffer will be composited into the 1512 * scene using the specified blend mode. 1513 * 1514 * A value of {@code null} is treated as pass-though this means no effect on a 1515 * parent such as a Group and the equivalent of SRC_OVER for a single Node. 1516 * 1517 * @defaultValue null 1518 */ 1519 private javafx.beans.property.ObjectProperty<BlendMode> blendMode; 1520 1521 public final void setBlendMode(BlendMode value) { 1522 blendModeProperty().set(value); 1523 } 1524 public final BlendMode getBlendMode() { 1525 return blendMode == null ? null : blendMode.get(); 1526 } 1527 1528 public final ObjectProperty<BlendMode> blendModeProperty() { 1529 if (blendMode == null) { 1530 blendMode = new StyleableObjectProperty<BlendMode>(null) { 1531 @Override public void invalidated() { 1532 NodeHelper.markDirty(Node.this, DirtyBits.NODE_BLENDMODE); 1533 } 1534 1535 @Override 1536 public CssMetaData getCssMetaData() { 1537 return StyleableProperties.BLEND_MODE; 1538 } 1539 1540 @Override 1541 public Object getBean() { 1542 return Node.this; 1543 } 1544 1545 @Override 1546 public String getName() { 1547 return "blendMode"; 1548 } 1549 }; 1550 } 1551 return blendMode; 1552 } 1553 1554 public final void setClip(Node value) { 1555 clipProperty().set(value); 1556 } 1557 1558 public final Node getClip() { 1559 return (miscProperties == null) ? DEFAULT_CLIP 1560 : miscProperties.getClip(); 1561 } 1562 1563 /** 1564 * Specifies a {@code Node} to use to define the the clipping shape for this 1565 * Node. This clipping Node is not a child of this {@code Node} in the scene 1566 * graph sense. Rather, it is used to define the clip for this {@code Node}. 1567 * <p> 1568 * For example, you can use an {@link javafx.scene.image.ImageView} Node as 1569 * a mask to represent the Clip. Or you could use one of the geometric shape 1570 * Nodes such as {@link javafx.scene.shape.Rectangle} or 1571 * {@link javafx.scene.shape.Circle}. Or you could use a 1572 * {@link javafx.scene.text.Text} node to represent the Clip. 1573 * <p> 1574 * See the class documentation for {@link Node} for scene graph structure 1575 * restrictions on setting the clip. If these restrictions are violated by 1576 * a change to the clip variable, the change is ignored and the 1577 * previous value of the clip variable is restored. 1578 * <p> 1579 * Note that this is a conditional feature. See 1580 * {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP} 1581 * for more information. 1582 * <p> 1583 * There is a known limitation of mixing Clip with a 3D Transform. 1584 * Clipping is essentially a 2D image operation. The result of 1585 * a Clip set on a {@link Group} node with 3D transformed children 1586 * will cause its children to be rendered in order without Z-buffering 1587 * applied between those children. 1588 * 1589 * @return the the clipping shape for this {@code Node} 1590 * @defaultValue null 1591 */ 1592 public final ObjectProperty<Node> clipProperty() { 1593 return getMiscProperties().clipProperty(); 1594 } 1595 1596 public final void setCache(boolean value) { 1597 cacheProperty().set(value); 1598 } 1599 1600 public final boolean isCache() { 1601 return (miscProperties == null) ? DEFAULT_CACHE 1602 : miscProperties.isCache(); 1603 } 1604 1605 /** 1606 * A performance hint to the system to indicate that this {@code Node} 1607 * should be cached as a bitmap. Rendering a bitmap representation of a node 1608 * will be faster than rendering primitives in many cases, especially in the 1609 * case of primitives with effects applied (such as a blur). However, it 1610 * also increases memory usage. This hint indicates whether that trade-off 1611 * (increased memory usage for increased performance) is worthwhile. Also 1612 * note that on some platforms such as GPU accelerated platforms there is 1613 * little benefit to caching Nodes as bitmaps when blurs and other effects 1614 * are used since they are very fast to render on the GPU. 1615 * 1616 * The {@link #cacheHintProperty} variable provides additional options for enabling 1617 * more aggressive bitmap caching. 1618 * 1619 * <p> 1620 * Caching may be disabled for any node that has a 3D transform on itself, 1621 * any of its ancestors, or any of its descendants. 1622 * 1623 * @return the hint to cache for this {@code Node} 1624 * @see #cacheHintProperty 1625 * @defaultValue false 1626 */ 1627 public final BooleanProperty cacheProperty() { 1628 return getMiscProperties().cacheProperty(); 1629 } 1630 1631 public final void setCacheHint(CacheHint value) { 1632 cacheHintProperty().set(value); 1633 } 1634 1635 public final CacheHint getCacheHint() { 1636 return (miscProperties == null) ? DEFAULT_CACHE_HINT 1637 : miscProperties.getCacheHint(); 1638 } 1639 1640 /** 1641 * Additional hint for controlling bitmap caching. 1642 * <p> 1643 * Under certain circumstances, such as animating nodes that are very 1644 * expensive to render, it is desirable to be able to perform 1645 * transformations on the node without having to regenerate the cached 1646 * bitmap. An option in such cases is to perform the transforms on the 1647 * cached bitmap itself. 1648 * <p> 1649 * This technique can provide a dramatic improvement to animation 1650 * performance, though may also result in a reduction in visual quality. 1651 * The {@code cacheHint} variable provides a hint to the system about how 1652 * and when that trade-off (visual quality for animation performance) is 1653 * acceptable. 1654 * <p> 1655 * It is possible to enable the cacheHint only at times when your node is 1656 * animating. In this way, expensive nodes can appear on screen with full 1657 * visual quality, yet still animate smoothly. 1658 * <p> 1659 * Example: 1660 * <pre>{@code 1661 expensiveNode.setCache(true); 1662 expensiveNode.setCacheHint(CacheHint.QUALITY); 1663 ... 1664 // Do an animation 1665 expensiveNode.setCacheHint(CacheHint.SPEED); 1666 new Timeline( 1667 new KeyFrame(Duration.seconds(2), 1668 new KeyValue(expensiveNode.scaleXProperty(), 2.0), 1669 new KeyValue(expensiveNode.scaleYProperty(), 2.0), 1670 new KeyValue(expensiveNode.rotateProperty(), 360), 1671 new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY) 1672 ) 1673 ).play(); 1674 }</pre> 1675 * 1676 * Note that {@code cacheHint} is only a hint to the system. Depending on 1677 * the details of the node or the transform, this hint may be ignored. 1678 * 1679 * <p> 1680 * If {@code Node.cache} is false, cacheHint is ignored. 1681 * Caching may be disabled for any node that has a 3D transform on itself, 1682 * any of its ancestors, or any of its descendants. 1683 * 1684 * @return the {@code CacheHint} for this {@code Node} 1685 * @see #cacheProperty 1686 * @defaultValue CacheHint.DEFAULT 1687 */ 1688 public final ObjectProperty<CacheHint> cacheHintProperty() { 1689 return getMiscProperties().cacheHintProperty(); 1690 } 1691 1692 public final void setEffect(Effect value) { 1693 effectProperty().set(value); 1694 } 1695 1696 public final Effect getEffect() { 1697 return (miscProperties == null) ? DEFAULT_EFFECT 1698 : miscProperties.getEffect(); 1699 } 1700 1701 /** 1702 * Specifies an effect to apply to this {@code Node}. 1703 * <p> 1704 * Note that this is a conditional feature. See 1705 * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT} 1706 * for more information. 1707 * 1708 * <p> 1709 * There is a known limitation of mixing Effect with a 3D Transform. Effect is 1710 * essentially a 2D image operation. The result of an Effect set on 1711 * a {@link Group} node with 3D transformed children will cause its children 1712 * to be rendered in order without Z-buffering applied between those 1713 * children. 1714 * 1715 * @return the effect for this {@code Node} 1716 * @defaultValue null 1717 */ 1718 public final ObjectProperty<Effect> effectProperty() { 1719 return getMiscProperties().effectProperty(); 1720 } 1721 1722 public final void setDepthTest(DepthTest value) { 1723 depthTestProperty().set(value); 1724 } 1725 1726 public final DepthTest getDepthTest() { 1727 return (miscProperties == null) ? DEFAULT_DEPTH_TEST 1728 : miscProperties.getDepthTest(); 1729 } 1730 1731 /** 1732 * Indicates whether depth testing is used when rendering this node. 1733 * If the depthTest flag is {@code DepthTest.DISABLE}, then depth testing 1734 * is disabled for this node. 1735 * If the depthTest flag is {@code DepthTest.ENABLE}, then depth testing 1736 * is enabled for this node. 1737 * If the depthTest flag is {@code DepthTest.INHERIT}, then depth testing 1738 * is enabled for this node if it is enabled for the parent node or the 1739 * parent node is null. 1740 * <p> 1741 * The depthTest flag is only used when the depthBuffer flag for 1742 * the {@link Scene} is true (meaning that the 1743 * {@link Scene} has an associated depth buffer) 1744 * <p> 1745 * Depth test comparison is only done among nodes with depthTest enabled. 1746 * A node with depthTest disabled does not read, test, or write the depth buffer, 1747 * that is to say its Z value will not be considered for depth testing 1748 * with other nodes. 1749 * <p> 1750 * Note that this is a conditional feature. See 1751 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 1752 * for more information. 1753 *<p> 1754 * See the constructor in Scene with depthBuffer as one of its input 1755 * arguments. 1756 * 1757 * @return the depth test setting for this {@code Node} 1758 * @see javafx.scene.Scene 1759 * @defaultValue INHERIT 1760 */ 1761 public final ObjectProperty<DepthTest> depthTestProperty() { 1762 return getMiscProperties().depthTestProperty(); 1763 } 1764 1765 /** 1766 * Recompute the derived depth test flag. This flag is true 1767 * if the depthTest flag for this node is true and the 1768 * depth test flag for each ancestor node is true. It is false 1769 * otherwise. Equivalently, the derived depth flag is true 1770 * if the depthTest flag for this node is true and the derivedDepthTest 1771 * flag for its parent is true. 1772 */ 1773 void computeDerivedDepthTest() { 1774 boolean newDDT; 1775 if (getDepthTest() == DepthTest.INHERIT) { 1776 if (getParent() != null) { 1777 newDDT = getParent().isDerivedDepthTest(); 1778 } else { 1779 newDDT = true; 1780 } 1781 } else if (getDepthTest() == DepthTest.ENABLE) { 1782 newDDT = true; 1783 } else { 1784 newDDT = false; 1785 } 1786 1787 if (isDerivedDepthTest() != newDDT) { 1788 NodeHelper.markDirty(this, DirtyBits.NODE_DEPTH_TEST); 1789 setDerivedDepthTest(newDDT); 1790 } 1791 } 1792 1793 // This is the derived depthTest value to pass to PG level 1794 private boolean derivedDepthTest = true; 1795 1796 void setDerivedDepthTest(boolean value) { 1797 derivedDepthTest = value; 1798 } 1799 1800 boolean isDerivedDepthTest() { 1801 return derivedDepthTest; 1802 } 1803 1804 public final void setDisable(boolean value) { 1805 disableProperty().set(value); 1806 } 1807 1808 public final boolean isDisable() { 1809 return (miscProperties == null) ? DEFAULT_DISABLE 1810 : miscProperties.isDisable(); 1811 } 1812 1813 /** 1814 * Defines the individual disabled state of this {@code Node}. Setting 1815 * {@code disable} to true will cause this {@code Node} and any subnodes to 1816 * become disabled. This property should be used only to set the disabled 1817 * state of a {@code Node}. For querying the disabled state of a 1818 * {@code Node}, the {@link #disabledProperty disabled} property should instead be used, 1819 * since it is possible that a {@code Node} was disabled as a result of an 1820 * ancestor being disabled even if the individual {@code disable} state on 1821 * this {@code Node} is {@code false}. 1822 * 1823 * @return the disabled state for this {@code Node} 1824 * @defaultValue false 1825 */ 1826 public final BooleanProperty disableProperty() { 1827 return getMiscProperties().disableProperty(); 1828 } 1829 1830 1831 // /** 1832 // * TODO document - null by default, could be non-null in subclasses (e.g. Control) 1833 // */ 1834 // public final ObjectProperty<InputMap<?>> inputMapProperty() { 1835 // if (inputMap == null) { 1836 // inputMap = new SimpleObjectProperty<InputMap<?>>(this, "inputMap") { 1837 // private InputMap<?> currentMap = get(); 1838 // @Override protected void invalidated() { 1839 // if (currentMap != null) { 1840 // currentMap.dispose(); 1841 // } 1842 // currentMap = get(); 1843 // } 1844 // }; 1845 // } 1846 // return inputMap; 1847 // } 1848 // public final void setInputMap(InputMap<?> value) { inputMapProperty().set(value); } 1849 // public final InputMap<?> getInputMap() { return inputMapProperty().getValue(); } 1850 // private ObjectProperty<InputMap<?>> inputMap; 1851 1852 1853 /************************************************************************** 1854 * * 1855 * 1856 * * 1857 *************************************************************************/ 1858 /** 1859 * Defines how the picking computation is done for this node when 1860 * triggered by a {@code MouseEvent} or a {@code contains} function call. 1861 * 1862 * If {@code pickOnBounds} is true, then picking is computed by 1863 * intersecting with the bounds of this node, else picking is computed 1864 * by intersecting with the geometric shape of this node. 1865 * 1866 * @defaultValue false 1867 */ 1868 private BooleanProperty pickOnBounds; 1869 1870 public final void setPickOnBounds(boolean value) { 1871 pickOnBoundsProperty().set(value); 1872 } 1873 1874 public final boolean isPickOnBounds() { 1875 return pickOnBounds == null ? false : pickOnBounds.get(); 1876 } 1877 1878 public final BooleanProperty pickOnBoundsProperty() { 1879 if (pickOnBounds == null) { 1880 pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds"); 1881 } 1882 return pickOnBounds; 1883 } 1884 1885 /** 1886 * Indicates whether or not this {@code Node} is disabled. A {@code Node} 1887 * will become disabled if {@link #disableProperty disable} is set to {@code true} on either 1888 * itself or one of its ancestors in the scene graph. 1889 * <p> 1890 * A disabled {@code Node} should render itself differently to indicate its 1891 * disabled state to the user. 1892 * Such disabled rendering is dependent on the implementation of the 1893 * {@code Node}. The shape classes contained in {@code javafx.scene.shape} 1894 * do not implement such rendering by default, therefore applications using 1895 * shapes for handling input must implement appropriate disabled rendering 1896 * themselves. The user-interface controls defined in 1897 * {@code javafx.scene.control} will implement disabled-sensitive rendering, 1898 * however. 1899 * <p> 1900 * A disabled {@code Node} does not receive mouse or key events. 1901 * 1902 * @defaultValue false 1903 */ 1904 private ReadOnlyBooleanWrapper disabled; 1905 1906 protected final void setDisabled(boolean value) { 1907 disabledPropertyImpl().set(value); 1908 } 1909 1910 public final boolean isDisabled() { 1911 return disabled == null ? false : disabled.get(); 1912 } 1913 1914 public final ReadOnlyBooleanProperty disabledProperty() { 1915 return disabledPropertyImpl().getReadOnlyProperty(); 1916 } 1917 1918 private ReadOnlyBooleanWrapper disabledPropertyImpl() { 1919 if (disabled == null) { 1920 disabled = new ReadOnlyBooleanWrapper() { 1921 1922 @Override 1923 protected void invalidated() { 1924 pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, get()); 1925 updateCanReceiveFocus(); 1926 focusSetDirty(getScene()); 1927 } 1928 1929 @Override 1930 public Object getBean() { 1931 return Node.this; 1932 } 1933 1934 @Override 1935 public String getName() { 1936 return "disabled"; 1937 } 1938 }; 1939 } 1940 return disabled; 1941 } 1942 1943 private void updateDisabled() { 1944 boolean isDisabled = isDisable(); 1945 if (!isDisabled) { 1946 isDisabled = getParent() != null ? getParent().isDisabled() : 1947 getSubScene() != null && getSubScene().isDisabled(); 1948 } 1949 setDisabled(isDisabled); 1950 if (this instanceof SubScene) { 1951 ((SubScene)this).getRoot().setDisabled(isDisabled); 1952 } 1953 } 1954 1955 /** 1956 * Finds this {@code Node}, or the first sub-node, based on the given CSS selector. 1957 * If this node is a {@code Parent}, then this function will traverse down 1958 * into the branch until it finds a match. If more than one sub-node matches the 1959 * specified selector, this function returns the first of them. 1960 * <p> 1961 * For example, if a Node is given the id of "myId", then the lookup method can 1962 * be used to find this node as follows: <code>scene.lookup("#myId");</code>. 1963 * </p> 1964 * 1965 * @param selector The css selector of the node to find 1966 * @return The first node, starting from this {@code Node}, which matches 1967 * the CSS {@code selector}, null if none is found. 1968 */ 1969 public Node lookup(String selector) { 1970 if (selector == null) return null; 1971 Selector s = Selector.createSelector(selector); 1972 return s != null && s.applies(this) ? this : null; 1973 } 1974 1975 /** 1976 * Finds all {@code Node}s, including this one and any children, which match 1977 * the given CSS selector. If no matches are found, an empty unmodifiable set is 1978 * returned. The set is explicitly unordered. 1979 * 1980 * @param selector The css selector of the nodes to find 1981 * @return All nodes, starting from and including this {@code Node}, which match 1982 * the CSS {@code selector}. The returned set is always unordered and 1983 * unmodifiable, and never null. 1984 */ 1985 public Set<Node> lookupAll(String selector) { 1986 final Selector s = Selector.createSelector(selector); 1987 final Set<Node> empty = Collections.emptySet(); 1988 if (s == null) return empty; 1989 List<Node> results = lookupAll(s, null); 1990 return results == null ? empty : new UnmodifiableListSet<Node>(results); 1991 } 1992 1993 /** 1994 * Used by Node and Parent for traversing the tree and adding all nodes which 1995 * match the given selector. 1996 * 1997 * @param selector The Selector. This will never be null. 1998 * @param results The results. This will never be null. 1999 */ 2000 List<Node> lookupAll(Selector selector, List<Node> results) { 2001 if (selector.applies(this)) { 2002 // Lazily create the set to reduce some trash. 2003 if (results == null) { 2004 results = new LinkedList<Node>(); 2005 } 2006 results.add(this); 2007 } 2008 return results; 2009 } 2010 2011 /** 2012 * Moves this {@code Node} to the back of its sibling nodes in terms of 2013 * z-order. This is accomplished by moving this {@code Node} to the 2014 * first position in its parent's {@code content} ObservableList. 2015 * This function has no effect if this {@code Node} is not part of a group. 2016 */ 2017 public void toBack() { 2018 if (getParent() != null) { 2019 getParent().toBack(this); 2020 } 2021 } 2022 2023 /** 2024 * Moves this {@code Node} to the front of its sibling nodes in terms of 2025 * z-order. This is accomplished by moving this {@code Node} to the 2026 * last position in its parent's {@code content} ObservableList. 2027 * This function has no effect if this {@code Node} is not part of a group. 2028 */ 2029 public void toFront() { 2030 if (getParent() != null) { 2031 getParent().toFront(this); 2032 } 2033 } 2034 2035 // TODO: need to verify whether this is OK to do starting from a node in 2036 // the scene graph other than the root. 2037 private void doCSSPass() { 2038 if (this.cssFlag != CssFlags.CLEAN) { 2039 // The dirty bit isn't checked but we must ensure it is cleared. 2040 // The cssFlag is set to clean in either Node.processCSS or 2041 // NodeHelper.processCSS 2042 2043 // Don't clear the dirty bit in case it will cause problems 2044 // with a full CSS pass on the scene. 2045 // TODO: is this the right thing to do? 2046 // this.clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 2047 2048 this.processCSS(); 2049 } 2050 } 2051 2052 /** 2053 * Recursive function for synchronizing a node and all descendents 2054 */ 2055 private static void syncAll(Node node) { 2056 node.syncPeer(); 2057 if (node instanceof Parent) { 2058 Parent p = (Parent) node; 2059 final int childrenCount = p.getChildren().size(); 2060 2061 for (int i = 0; i < childrenCount; i++) { 2062 Node n = p.getChildren().get(i); 2063 if (n != null) { 2064 syncAll(n); 2065 } 2066 } 2067 } 2068 if (node.getClip() != null) { 2069 syncAll(node.getClip()); 2070 } 2071 } 2072 2073 private void doLayoutPass() { 2074 if (this instanceof Parent) { 2075 // TODO: As an optimization we only need to layout those dirty 2076 // roots that are descendents of this node 2077 Parent p = (Parent)this; 2078 for (int i = 0; i < 3; i++) { 2079 p.layout(); 2080 } 2081 } 2082 } 2083 2084 private void doCSSLayoutSyncForSnapshot() { 2085 doCSSPass(); 2086 doLayoutPass(); 2087 updateBounds(); 2088 Scene.setAllowPGAccess(true); 2089 syncAll(this); 2090 Scene.setAllowPGAccess(false); 2091 } 2092 2093 private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) { 2094 if (getScene() != null) { 2095 getScene().doCSSLayoutSyncForSnapshot(this); 2096 } else { 2097 doCSSLayoutSyncForSnapshot(); 2098 } 2099 2100 BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM; 2101 if (params.getTransform() != null) { 2102 Affine3D tempTx = new Affine3D(); 2103 TransformHelper.apply(params.getTransform(), tempTx); 2104 transform = tempTx; 2105 } 2106 double x; 2107 double y; 2108 double w; 2109 double h; 2110 Rectangle2D viewport = params.getViewport(); 2111 if (viewport != null) { 2112 // Use the specified viewport 2113 x = viewport.getMinX(); 2114 y = viewport.getMinY(); 2115 w = viewport.getWidth(); 2116 h = viewport.getHeight(); 2117 } else { 2118 // Get the bounds in parent of this node, transformed by the 2119 // specified transform. 2120 BaseBounds tempBounds = TempState.getInstance().bounds; 2121 tempBounds = getTransformedBounds(tempBounds, transform); 2122 x = tempBounds.getMinX(); 2123 y = tempBounds.getMinY(); 2124 w = tempBounds.getWidth(); 2125 h = tempBounds.getHeight(); 2126 } 2127 WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h, 2128 this, transform, params.isDepthBufferInternal(), 2129 params.getFill(), params.getEffectiveCamera(), img); 2130 2131 return result; 2132 } 2133 2134 /** 2135 * Takes a snapshot of this node and returns the rendered image when 2136 * it is ready. 2137 * CSS and layout processing will be done for the node, and any of its 2138 * children, prior to rendering it. 2139 * The entire destination image is cleared to the fill {@code Paint} 2140 * specified by the SnapshotParameters. This node is then rendered to 2141 * the image. 2142 * If the viewport specified by the SnapshotParameters is null, the 2143 * upper-left pixel of the {@code boundsInParent} of this 2144 * node, after first applying the transform specified by the 2145 * SnapshotParameters, 2146 * is mapped to the upper-left pixel (0,0) in the image. 2147 * If a non-null viewport is specified, 2148 * the upper-left pixel of the viewport is mapped to upper-left pixel 2149 * (0,0) in the image. 2150 * In both cases, this mapping to (0,0) of the image is done with an integer 2151 * translation. The portion of the node that is outside of the rendered 2152 * image will be clipped by the image. 2153 * 2154 * <p> 2155 * When taking a snapshot of a scene that is being animated, either 2156 * explicitly by the application or implicitly (such as chart animation), 2157 * the snapshot will be rendered based on the state of the scene graph at 2158 * the moment the snapshot is taken and will not reflect any subsequent 2159 * animation changes. 2160 * </p> 2161 * 2162 * <p> 2163 * NOTE: In order for CSS and layout to function correctly, the node 2164 * must be part of a Scene (the Scene may be attached to a Stage, but need 2165 * not be). 2166 * </p> 2167 * 2168 * @param params the snapshot parameters containing attributes that 2169 * will control the rendering. If the SnapshotParameters object is null, 2170 * then the Scene's attributes will be used if this node is part of a scene, 2171 * or default attributes will be used if this node is not part of a scene. 2172 * 2173 * @param image the writable image that will be used to hold the rendered node. 2174 * It may be null in which case a new WritableImage will be constructed. 2175 * The new image is constructed using integer width and 2176 * height values that are derived either from the transformed bounds of this 2177 * Node or from the size of the viewport as specified in the 2178 * SnapShotParameters. These integer values are chosen such that the image 2179 * will wholly contain the bounds of this Node or the specified viewport. 2180 * If the image is non-null, the node will be rendered into the 2181 * existing image. 2182 * In this case, the width and height of the image determine the area 2183 * that is rendered instead of the width and height of the bounds or 2184 * viewport. 2185 * 2186 * @throws IllegalStateException if this method is called on a thread 2187 * other than the JavaFX Application Thread. 2188 * 2189 * @return the rendered image 2190 * @since JavaFX 2.2 2191 */ 2192 public WritableImage snapshot(SnapshotParameters params, WritableImage image) { 2193 Toolkit.getToolkit().checkFxUserThread(); 2194 2195 if (params == null) { 2196 params = new SnapshotParameters(); 2197 Scene s = getScene(); 2198 if (s != null) { 2199 params.setCamera(s.getEffectiveCamera()); 2200 params.setDepthBuffer(s.isDepthBufferInternal()); 2201 params.setFill(s.getFill()); 2202 } 2203 } 2204 2205 return doSnapshot(params, image); 2206 } 2207 2208 /** 2209 * Takes a snapshot of this node at the next frame and calls the 2210 * specified callback method when the image is ready. 2211 * CSS and layout processing will be done for the node, and any of its 2212 * children, prior to rendering it. 2213 * The entire destination image is cleared to the fill {@code Paint} 2214 * specified by the SnapshotParameters. This node is then rendered to 2215 * the image. 2216 * If the viewport specified by the SnapshotParameters is null, the 2217 * upper-left pixel of the {@code boundsInParent} of this 2218 * node, after first applying the transform specified by the 2219 * SnapshotParameters, 2220 * is mapped to the upper-left pixel (0,0) in the image. 2221 * If a non-null viewport is specified, 2222 * the upper-left pixel of the viewport is mapped to upper-left pixel 2223 * (0,0) in the image. 2224 * In both cases, this mapping to (0,0) of the image is done with an integer 2225 * translation. The portion of the node that is outside of the rendered 2226 * image will be clipped by the image. 2227 * 2228 * <p> 2229 * This is an asynchronous call, which means that other 2230 * events or animation might be processed before the node is rendered. 2231 * If any such events modify the node, or any of its children, that 2232 * modification will be reflected in the rendered image (just like it 2233 * will also be reflected in the frame rendered to the Stage, if this node 2234 * is part of a live scene graph). 2235 * </p> 2236 * 2237 * <p> 2238 * When taking a snapshot of a node that is being animated, either 2239 * explicitly by the application or implicitly (such as chart animation), 2240 * the snapshot will be rendered based on the state of the scene graph at 2241 * the moment the snapshot is taken and will not reflect any subsequent 2242 * animation changes. 2243 * </p> 2244 * 2245 * <p> 2246 * NOTE: In order for CSS and layout to function correctly, the node 2247 * must be part of a Scene (the Scene may be attached to a Stage, but need 2248 * not be). 2249 * </p> 2250 * 2251 * @param callback a class whose call method will be called when the image 2252 * is ready. The SnapshotResult that is passed into the call method of 2253 * the callback will contain the rendered image, the source node 2254 * that was rendered, and a copy of the SnapshotParameters. 2255 * The callback parameter must not be null. 2256 * 2257 * @param params the snapshot parameters containing attributes that 2258 * will control the rendering. If the SnapshotParameters object is null, 2259 * then the Scene's attributes will be used if this node is part of a scene, 2260 * or default attributes will be used if this node is not part of a scene. 2261 * 2262 * @param image the writable image that will be used to hold the rendered node. 2263 * It may be null in which case a new WritableImage will be constructed. 2264 * The new image is constructed using integer width and 2265 * height values that are derived either from the transformed bounds of this 2266 * Node or from the size of the viewport as specified in the 2267 * SnapShotParameters. These integer values are chosen such that the image 2268 * will wholly contain the bounds of this Node or the specified viewport. 2269 * If the image is non-null, the node will be rendered into the 2270 * existing image. 2271 * In this case, the width and height of the image determine the area 2272 * that is rendered instead of the width and height of the bounds or 2273 * viewport. 2274 * 2275 * @throws IllegalStateException if this method is called on a thread 2276 * other than the JavaFX Application Thread. 2277 * 2278 * @throws NullPointerException if the callback parameter is null. 2279 * @since JavaFX 2.2 2280 */ 2281 public void snapshot(Callback<SnapshotResult, Void> callback, 2282 SnapshotParameters params, WritableImage image) { 2283 2284 Toolkit.getToolkit().checkFxUserThread(); 2285 if (callback == null) { 2286 throw new NullPointerException("The callback must not be null"); 2287 } 2288 2289 if (params == null) { 2290 params = new SnapshotParameters(); 2291 Scene s = getScene(); 2292 if (s != null) { 2293 params.setCamera(s.getEffectiveCamera()); 2294 params.setDepthBuffer(s.isDepthBufferInternal()); 2295 params.setFill(s.getFill()); 2296 } 2297 } else { 2298 params = params.copy(); 2299 } 2300 2301 final SnapshotParameters theParams = params; 2302 final Callback<SnapshotResult, Void> theCallback = callback; 2303 final WritableImage theImage = image; 2304 2305 // Create a deferred runnable that will be run from a pulse listener 2306 // that is called after all of the scenes have been synced but before 2307 // any of them have been rendered. 2308 final Runnable snapshotRunnable = () -> { 2309 WritableImage img = doSnapshot(theParams, theImage); 2310 SnapshotResult result = new SnapshotResult(img, Node.this, theParams); 2311 // System.err.println("Calling snapshot callback"); 2312 try { 2313 Void v = theCallback.call(result); 2314 } catch (Throwable th) { 2315 System.err.println("Exception in snapshot callback"); 2316 th.printStackTrace(System.err); 2317 } 2318 }; 2319 2320 // System.err.println("Schedule a snapshot in the future"); 2321 Scene.addSnapshotRunnable(snapshotRunnable); 2322 } 2323 2324 /* ************************************************************************ 2325 * * 2326 * 2327 * * 2328 *************************************************************************/ 2329 2330 public final void setOnDragEntered( 2331 EventHandler<? super DragEvent> value) { 2332 onDragEnteredProperty().set(value); 2333 } 2334 2335 public final EventHandler<? super DragEvent> getOnDragEntered() { 2336 return (eventHandlerProperties == null) 2337 ? null : eventHandlerProperties.getOnDragEntered(); 2338 } 2339 2340 /** 2341 * Defines a function to be called when drag gesture 2342 * enters this {@code Node}. 2343 * @return the event handler that is called when drag gesture enters this 2344 * {@code Node} 2345 */ 2346 public final ObjectProperty<EventHandler<? super DragEvent>> 2347 onDragEnteredProperty() { 2348 return getEventHandlerProperties().onDragEnteredProperty(); 2349 } 2350 2351 public final void setOnDragExited( 2352 EventHandler<? super DragEvent> value) { 2353 onDragExitedProperty().set(value); 2354 } 2355 2356 public final EventHandler<? super DragEvent> getOnDragExited() { 2357 return (eventHandlerProperties == null) 2358 ? null : eventHandlerProperties.getOnDragExited(); 2359 } 2360 2361 /** 2362 * Defines a function to be called when drag gesture 2363 * exits this {@code Node}. 2364 * @return the event handler that is called when drag gesture exits this 2365 * {@code Node} 2366 */ 2367 public final ObjectProperty<EventHandler<? super DragEvent>> 2368 onDragExitedProperty() { 2369 return getEventHandlerProperties().onDragExitedProperty(); 2370 } 2371 2372 public final void setOnDragOver( 2373 EventHandler<? super DragEvent> value) { 2374 onDragOverProperty().set(value); 2375 } 2376 2377 public final EventHandler<? super DragEvent> getOnDragOver() { 2378 return (eventHandlerProperties == null) 2379 ? null : eventHandlerProperties.getOnDragOver(); 2380 } 2381 2382 /** 2383 * Defines a function to be called when drag gesture progresses within 2384 * this {@code Node}. 2385 * @return the event handler that is called when drag gesture progresses 2386 * within this {@code Node} 2387 */ 2388 public final ObjectProperty<EventHandler<? super DragEvent>> 2389 onDragOverProperty() { 2390 return getEventHandlerProperties().onDragOverProperty(); 2391 } 2392 2393 // Do we want DRAG_TRANSFER_MODE_CHANGED event? 2394 // public final void setOnDragTransferModeChanged( 2395 // EventHandler<? super DragEvent> value) { 2396 // onDragTransferModeChangedProperty().set(value); 2397 // } 2398 // 2399 // public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() { 2400 // return (eventHandlerProperties == null) 2401 // ? null : eventHandlerProperties.getOnDragTransferModeChanged(); 2402 // } 2403 // 2404 // /** 2405 // * Defines a function to be called this {@code Node} if it is a potential 2406 // * drag-and-drop target when the user takes action to change the intended 2407 // * {@code TransferMode}. 2408 // * The user can change the intended {@link TransferMode} by holding down 2409 // * or releasing key modifiers. 2410 // */ 2411 // public final ObjectProperty<EventHandler<? super DragEvent>> 2412 // onDragTransferModeChangedProperty() { 2413 // return getEventHandlerProperties().onDragTransferModeChangedProperty(); 2414 // } 2415 2416 public final void setOnDragDropped( 2417 EventHandler<? super DragEvent> value) { 2418 onDragDroppedProperty().set(value); 2419 } 2420 2421 public final EventHandler<? super DragEvent> getOnDragDropped() { 2422 return (eventHandlerProperties == null) 2423 ? null : eventHandlerProperties.getOnDragDropped(); 2424 } 2425 2426 /** 2427 * Defines a function to be called when the mouse button is released 2428 * on this {@code Node} during drag and drop gesture. Transfer of data from 2429 * the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should 2430 * happen in this function. 2431 * @return the event handler that is called when the mouse button is 2432 * released on this {@code Node} 2433 */ 2434 public final ObjectProperty<EventHandler<? super DragEvent>> 2435 onDragDroppedProperty() { 2436 return getEventHandlerProperties().onDragDroppedProperty(); 2437 } 2438 2439 public final void setOnDragDone( 2440 EventHandler<? super DragEvent> value) { 2441 onDragDoneProperty().set(value); 2442 } 2443 2444 public final EventHandler<? super DragEvent> getOnDragDone() { 2445 return (eventHandlerProperties == null) 2446 ? null : eventHandlerProperties.getOnDragDone(); 2447 } 2448 2449 /** 2450 * Defines a function to be called when this {@code Node} is a 2451 * drag and drop gesture source after its data has 2452 * been dropped on a drop target. The {@code transferMode} of the 2453 * event shows what just happened at the drop target. 2454 * If {@code transferMode} has the value {@code MOVE}, then the source can 2455 * clear out its data. Clearing the source's data gives the appropriate 2456 * appearance to a user that the data has been moved by the drag and drop 2457 * gesture. A {@code transferMode} that has the value {@code NONE} 2458 * indicates that no data was transferred during the drag and drop gesture. 2459 * @return the event handler that is called when this {@code Node} is a drag 2460 * and drop gesture source after its data has been dropped on a drop target 2461 */ 2462 public final ObjectProperty<EventHandler<? super DragEvent>> 2463 onDragDoneProperty() { 2464 return getEventHandlerProperties().onDragDoneProperty(); 2465 } 2466 2467 /** 2468 * Confirms a potential drag and drop gesture that is recognized over this 2469 * {@code Node}. 2470 * Can be called only from a DRAG_DETECTED event handler. The returned 2471 * {@link Dragboard} is used to transfer data during 2472 * the drag and drop gesture. Placing this {@code Node}'s data on the 2473 * {@link Dragboard} also identifies this {@code Node} as the source of 2474 * the drag and drop gesture. 2475 * More detail about drag and drop gestures is described in the overivew 2476 * of {@link DragEvent}. 2477 * 2478 * @see DragEvent 2479 * @param transferModes The supported {@code TransferMode}(s) of this {@code Node} 2480 * @return A {@code Dragboard} to place this {@code Node}'s data on 2481 * @throws IllegalStateException if drag and drop cannot be started at this 2482 * moment (it's called outside of {@code DRAG_DETECTED} event handling or 2483 * this node is not in scene). 2484 */ 2485 public Dragboard startDragAndDrop(TransferMode... transferModes) { 2486 if (getScene() != null) { 2487 return getScene().startDragAndDrop(this, transferModes); 2488 } 2489 2490 throw new IllegalStateException("Cannot start drag and drop on node " 2491 + "that is not in scene"); 2492 } 2493 2494 /** 2495 * Starts a full press-drag-release gesture with this node as gesture 2496 * source. This method can be called only from a {@code DRAG_DETECTED} mouse 2497 * event handler. More detail about dragging gestures can be found 2498 * in the overview of {@link MouseEvent} and {@link MouseDragEvent}. 2499 * 2500 * @see MouseEvent 2501 * @see MouseDragEvent 2502 * @throws IllegalStateException if the full press-drag-release gesture 2503 * cannot be started at this moment (it's called outside of 2504 * {@code DRAG_DETECTED} event handling or this node is not in scene). 2505 * @since JavaFX 2.1 2506 */ 2507 public void startFullDrag() { 2508 if (getScene() != null) { 2509 getScene().startFullDrag(this); 2510 return; 2511 } 2512 2513 throw new IllegalStateException("Cannot start full drag on node " 2514 + "that is not in scene"); 2515 } 2516 2517 //////////////////////////// 2518 // Private Implementation 2519 //////////////////////////// 2520 2521 /** 2522 * If this Node is being used as the clip of another Node, that other node 2523 * is referred to as the clipParent. If the boundsInParent of this Node 2524 * changes, it must update the clipParent's bounds as well. 2525 */ 2526 private Node clipParent; 2527 // Use a getter function instead of giving clipParent package access, 2528 // so that clipParent doesn't get turned into a Location. 2529 final Node getClipParent() { 2530 return clipParent; 2531 } 2532 2533 /** 2534 * Determines whether this node is connected anywhere in the scene graph. 2535 */ 2536 boolean isConnected() { 2537 // don't need to check scene, because if scene is non-null 2538 // parent must also be non-null 2539 return getParent() != null || clipParent != null; 2540 } 2541 2542 /** 2543 * Tests whether creating a parent-child relationship between these 2544 * nodes would cause a cycle. The parent relationship includes not only 2545 * the "real" parent (child of Group) but also the clipParent. 2546 */ 2547 boolean wouldCreateCycle(Node parent, Node child) { 2548 if (child != null && child.getClip() == null && (!(child instanceof Parent))) { 2549 return false; 2550 } 2551 2552 Node n = parent; 2553 while (n != child) { 2554 if (n.getParent() != null) { 2555 n = n.getParent(); 2556 } else if (n.getSubScene() != null) { 2557 n = n.getSubScene(); 2558 } else if (n.clipParent != null) { 2559 n = n.clipParent; 2560 } else { 2561 return false; 2562 } 2563 } 2564 return true; 2565 } 2566 2567 /** 2568 * The peer node created by the graphics Toolkit/Pipeline implementation 2569 */ 2570 private NGNode peer; 2571 2572 @SuppressWarnings("CallToPrintStackTrace") 2573 <P extends NGNode> P getPeer() { 2574 if (Utils.assertionEnabled()) { 2575 // Assertion checking code 2576 if (getScene() != null && !Scene.isPGAccessAllowed()) { 2577 java.lang.System.err.println(); 2578 java.lang.System.err.println("*** unexpected PG access"); 2579 java.lang.Thread.dumpStack(); 2580 } 2581 } 2582 2583 if (peer == null) { 2584 //if (PerformanceTracker.isLoggingEnabled()) { 2585 // PerformanceTracker.logEvent("Creating NGNode for [{this}, id=\"{id}\"]"); 2586 //} 2587 peer = NodeHelper.createPeer(this); 2588 //if (PerformanceTracker.isLoggingEnabled()) { 2589 // PerformanceTracker.logEvent("NGNode created"); 2590 //} 2591 } 2592 return (P) peer; 2593 } 2594 2595 /*************************************************************************** 2596 * * 2597 * Initialization * 2598 * * 2599 * To Note limit the number of bounds computations and improve startup * 2600 * performance. * 2601 * * 2602 **************************************************************************/ 2603 2604 /** 2605 * Creates a new instance of Node. 2606 */ 2607 protected Node() { 2608 //if (PerformanceTracker.isLoggingEnabled()) { 2609 // PerformanceTracker.logEvent("Node.init for [{this}, id=\"{id}\"]"); 2610 //} 2611 setDirty(); 2612 updateTreeVisible(false); 2613 //if (PerformanceTracker.isLoggingEnabled()) { 2614 // PerformanceTracker.logEvent("Node.postinit " + 2615 // "for [{this}, id=\"{id}\"] finished"); 2616 //} 2617 } 2618 2619 /*************************************************************************** 2620 * * 2621 * Layout related APIs. * 2622 * * 2623 **************************************************************************/ 2624 /** 2625 * Defines whether or not this node's layout will be managed by it's parent. 2626 * If the node is managed, it's parent will factor the node's geometry 2627 * into its own preferred size and {@link #layoutBoundsProperty layoutBounds} 2628 * calculations and will lay it 2629 * out during the scene's layout pass. If a managed node's layoutBounds 2630 * changes, it will automatically trigger relayout up the scene-graph 2631 * to the nearest layout root (which is typically the scene's root node). 2632 * <p> 2633 * If the node is unmanaged, its parent will ignore the child in both preferred 2634 * size computations and layout. Changes in layoutBounds will not trigger 2635 * relayout above it. If an unmanaged node is of type {@link javafx.scene.Parent Parent}, 2636 * it will act as a "layout root", meaning that calls to {@link Parent#requestLayout()} 2637 * beneath it will cause only the branch rooted by the node to be relayed out, 2638 * thereby isolating layout changes to that root and below. It's the application's 2639 * responsibility to set the size and position of an unmanaged node. 2640 * <p> 2641 * By default all nodes are managed. 2642 * </p> 2643 * 2644 * @see #isResizable() 2645 * @see #layoutBoundsProperty() 2646 * @see Parent#requestLayout() 2647 * 2648 */ 2649 private BooleanProperty managed; 2650 2651 public final void setManaged(boolean value) { 2652 managedProperty().set(value); 2653 } 2654 2655 public final boolean isManaged() { 2656 return managed == null ? true : managed.get(); 2657 } 2658 2659 public final BooleanProperty managedProperty() { 2660 if (managed == null) { 2661 managed = new BooleanPropertyBase(true) { 2662 2663 @Override 2664 protected void invalidated() { 2665 final Parent parent = getParent(); 2666 if (parent != null) { 2667 parent.managedChildChanged(); 2668 } 2669 notifyManagedChanged(); 2670 } 2671 2672 @Override 2673 public Object getBean() { 2674 return Node.this; 2675 } 2676 2677 @Override 2678 public String getName() { 2679 return "managed"; 2680 } 2681 2682 }; 2683 } 2684 return managed; 2685 } 2686 2687 /** 2688 * Called whenever the "managed" flag has changed. This is only 2689 * used by Parent as an optimization to keep track of whether a 2690 * Parent node is a layout root or not. 2691 */ 2692 void notifyManagedChanged() { } 2693 2694 /** 2695 * Defines the x coordinate of the translation that is added to this {@code Node}'s 2696 * transform for the purpose of layout. The value should be computed as the 2697 * offset required to adjust the position of the node from its current 2698 * {@link #layoutBoundsProperty() layoutBounds minX} position (which might not be 0) to the desired location. 2699 * 2700 * <p>For example, if {@code textnode} should be positioned at {@code finalX} 2701 * <pre>{@code 2702 * textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX()); 2703 * }</pre> 2704 * <p> 2705 * Failure to subtract {@code layoutBounds minX} may result in misplacement 2706 * of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the 2707 * correct computation and should generally be used over setting layoutX directly. 2708 * <p> 2709 * The node's final translation will be computed as {@code layoutX} + {@link #translateXProperty translateX}, 2710 * where {@code layoutX} establishes the node's stable position 2711 * and {@code translateX} optionally makes dynamic adjustments to that 2712 * position. 2713 * <p> 2714 * If the node is managed and has a {@link javafx.scene.layout.Region} 2715 * as its parent, then the layout region will set {@code layoutX} according to its 2716 * own layout policy. If the node is unmanaged or parented by a {@link Group}, 2717 * then the application may set {@code layoutX} directly to position it. 2718 * 2719 * @see #relocate(double, double) 2720 * @see #layoutBoundsProperty() 2721 * 2722 */ 2723 private DoubleProperty layoutX; 2724 2725 public final void setLayoutX(double value) { 2726 layoutXProperty().set(value); 2727 } 2728 2729 public final double getLayoutX() { 2730 return layoutX == null ? 0.0 : layoutX.get(); 2731 } 2732 2733 public final DoubleProperty layoutXProperty() { 2734 if (layoutX == null) { 2735 layoutX = new DoublePropertyBase(0.0) { 2736 2737 @Override 2738 protected void invalidated() { 2739 NodeHelper.transformsChanged(Node.this); 2740 final Parent p = getParent(); 2741 2742 // Propagate layout if this change isn't triggered by its parent 2743 if (p != null && !p.isCurrentLayoutChild(Node.this)) { 2744 if (isManaged()) { 2745 // Force its parent to fix the layout since it is a managed child. 2746 p.requestLayout(true); 2747 } else { 2748 // Parent size changed, parent's parent might need to re-layout 2749 p.clearSizeCache(); 2750 p.requestParentLayout(); 2751 } 2752 } 2753 } 2754 2755 @Override 2756 public Object getBean() { 2757 return Node.this; 2758 } 2759 2760 @Override 2761 public String getName() { 2762 return "layoutX"; 2763 } 2764 }; 2765 } 2766 return layoutX; 2767 } 2768 2769 /** 2770 * Defines the y coordinate of the translation that is added to this {@code Node}'s 2771 * transform for the purpose of layout. The value should be computed as the 2772 * offset required to adjust the position of the node from its current 2773 * {@link #layoutBoundsProperty() layoutBounds minY} position (which might not be 0) to the desired location. 2774 * 2775 * <p>For example, if {@code textnode} should be positioned at {@code finalY} 2776 * <pre>{@code 2777 * textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY()); 2778 * }</pre> 2779 * <p> 2780 * Failure to subtract {@code layoutBounds minY} may result in misplacement 2781 * of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the 2782 * correct computation and should generally be used over setting layoutY directly. 2783 * <p> 2784 * The node's final translation will be computed as {@code layoutY} + {@link #translateYProperty translateY}, 2785 * where {@code layoutY} establishes the node's stable position 2786 * and {@code translateY} optionally makes dynamic adjustments to that 2787 * position. 2788 * <p> 2789 * If the node is managed and has a {@link javafx.scene.layout.Region} 2790 * as its parent, then the region will set {@code layoutY} according to its 2791 * own layout policy. If the node is unmanaged or parented by a {@link Group}, 2792 * then the application may set {@code layoutY} directly to position it. 2793 * 2794 * @see #relocate(double, double) 2795 * @see #layoutBoundsProperty() 2796 */ 2797 private DoubleProperty layoutY; 2798 2799 public final void setLayoutY(double value) { 2800 layoutYProperty().set(value); 2801 } 2802 2803 public final double getLayoutY() { 2804 return layoutY == null ? 0.0 : layoutY.get(); 2805 } 2806 2807 public final DoubleProperty layoutYProperty() { 2808 if (layoutY == null) { 2809 layoutY = new DoublePropertyBase(0.0) { 2810 2811 @Override 2812 protected void invalidated() { 2813 NodeHelper.transformsChanged(Node.this); 2814 final Parent p = getParent(); 2815 2816 // Propagate layout if this change isn't triggered by its parent 2817 if (p != null && !p.isCurrentLayoutChild(Node.this)) { 2818 if (isManaged()) { 2819 // Force its parent to fix the layout since it is a managed child. 2820 p.requestLayout(true); 2821 } else { 2822 // Parent size changed, parent's parent might need to re-layout 2823 p.clearSizeCache(); 2824 p.requestParentLayout(); 2825 } 2826 } 2827 } 2828 2829 @Override 2830 public Object getBean() { 2831 return Node.this; 2832 } 2833 2834 @Override 2835 public String getName() { 2836 return "layoutY"; 2837 } 2838 2839 }; 2840 } 2841 return layoutY; 2842 } 2843 2844 /** 2845 * Sets the node's layoutX and layoutY translation properties in order to 2846 * relocate this node to the x,y location in the parent. 2847 * <p> 2848 * This method does not alter translateX or translateY, which if also set 2849 * will be added to layoutX and layoutY, adjusting the final location by 2850 * corresponding amounts. 2851 * 2852 * @param x the target x coordinate location 2853 * @param y the target y coordinate location 2854 */ 2855 public void relocate(double x, double y) { 2856 setLayoutX(x - getLayoutBounds().getMinX()); 2857 setLayoutY(y - getLayoutBounds().getMinY()); 2858 2859 PlatformLogger logger = Logging.getLayoutLogger(); 2860 if (logger.isLoggable(Level.FINER)) { 2861 logger.finer(this.toString()+" moved to ("+x+","+y+")"); 2862 } 2863 } 2864 2865 /** 2866 * Indicates whether this node is a type which can be resized by its parent. 2867 * If this method returns true, then the parent will resize the node (ideally 2868 * within its size range) by calling node.resize(width,height) during the 2869 * layout pass. All Regions, Controls, and WebView are resizable classes 2870 * which depend on their parents resizing them during layout once all sizing 2871 * and CSS styling information has been applied. 2872 * <p> 2873 * If this method returns false, then the parent cannot resize it during 2874 * layout (resize() is a no-op) and it should return its layoutBounds for 2875 * minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not 2876 * resizable and hence depend on the application to establish their sizing 2877 * by setting appropriate properties (e.g. width/height for Rectangle, 2878 * text on Text, and so on). Non-resizable nodes may still be relocated 2879 * during layout. 2880 * 2881 * @see #getContentBias() 2882 * @see #minWidth(double) 2883 * @see #minHeight(double) 2884 * @see #prefWidth(double) 2885 * @see #prefHeight(double) 2886 * @see #maxWidth(double) 2887 * @see #maxHeight(double) 2888 * @see #resize(double, double) 2889 * @see #getLayoutBounds() 2890 * 2891 * @return whether or not this node type can be resized by its parent during layout 2892 */ 2893 public boolean isResizable() { 2894 return false; 2895 } 2896 2897 /** 2898 * Returns the orientation of a node's resizing bias for layout purposes. 2899 * If the node type has no bias, returns null. If the node is resizable and 2900 * it's height depends on its width, returns HORIZONTAL, else if its width 2901 * depends on its height, returns VERTICAL. 2902 * <p> 2903 * Resizable subclasses should override this method to return an 2904 * appropriate value. 2905 * 2906 * @see #isResizable() 2907 * @see #minWidth(double) 2908 * @see #minHeight(double) 2909 * @see #prefWidth(double) 2910 * @see #prefHeight(double) 2911 * @see #maxWidth(double) 2912 * @see #maxHeight(double) 2913 * 2914 * @return orientation of width/height dependency or null if there is none 2915 */ 2916 public Orientation getContentBias() { 2917 return null; 2918 } 2919 2920 /** 2921 * Returns the node's minimum width for use in layout calculations. 2922 * If the node is resizable, its parent should not resize its width any 2923 * smaller than this value. If the node is not resizable, returns its 2924 * layoutBounds width. 2925 * <p> 2926 * Layout code which calls this method should first check the content-bias 2927 * of the node. If the node has a vertical content-bias, then callers 2928 * should pass in a height value that the minimum width should be based on. 2929 * If the node has either a horizontal or null content-bias, then the caller 2930 * should pass in -1. 2931 * <p> 2932 * Node subclasses with a vertical content-bias should honor the height 2933 * parameter whether -1 or a positive value. All other subclasses may ignore 2934 * the height parameter (which will likely be -1). 2935 * <p> 2936 * If Node's {@link #maxWidth(double)} is lower than this number, 2937 * {@code minWidth} takes precedence. This means the Node should never be resized below {@code minWidth}. 2938 * <p> 2939 * @see #isResizable() 2940 * @see #getContentBias() 2941 * 2942 * @param height the height that should be used if minimum width depends on it 2943 * @return the minimum width that the node should be resized to during layout. 2944 * The result will never be NaN, nor will it ever be negative. 2945 */ 2946 public double minWidth(double height) { 2947 return prefWidth(height); 2948 } 2949 2950 /** 2951 * Returns the node's minimum height for use in layout calculations. 2952 * If the node is resizable, its parent should not resize its height any 2953 * smaller than this value. If the node is not resizable, returns its 2954 * layoutBounds height. 2955 * <p> 2956 * Layout code which calls this method should first check the content-bias 2957 * of the node. If the node has a horizontal content-bias, then callers 2958 * should pass in a width value that the minimum height should be based on. 2959 * If the node has either a vertical or null content-bias, then the caller 2960 * should pass in -1. 2961 * <p> 2962 * Node subclasses with a horizontal content-bias should honor the width 2963 * parameter whether -1 or a positive value. All other subclasses may ignore 2964 * the width parameter (which will likely be -1). 2965 * <p> 2966 * If Node's {@link #maxHeight(double)} is lower than this number, 2967 * {@code minHeight} takes precedence. This means the Node should never be resized below {@code minHeight}. 2968 * <p> 2969 * @see #isResizable() 2970 * @see #getContentBias() 2971 * 2972 * @param width the width that should be used if minimum height depends on it 2973 * @return the minimum height that the node should be resized to during layout 2974 * The result will never be NaN, nor will it ever be negative. 2975 */ 2976 public double minHeight(double width) { 2977 return prefHeight(width); 2978 } 2979 2980 /** 2981 * Returns the node's preferred width for use in layout calculations. 2982 * If the node is resizable, its parent should treat this value as the 2983 * node's ideal width within its range. If the node is not resizable, 2984 * just returns its layoutBounds width, which should be treated as the rigid 2985 * width of the node. 2986 * <p> 2987 * Layout code which calls this method should first check the content-bias 2988 * of the node. If the node has a vertical content-bias, then callers 2989 * should pass in a height value that the preferred width should be based on. 2990 * If the node has either a horizontal or null content-bias, then the caller 2991 * should pass in -1. 2992 * <p> 2993 * Node subclasses with a vertical content-bias should honor the height 2994 * parameter whether -1 or a positive value. All other subclasses may ignore 2995 * the height parameter (which will likely be -1). 2996 * <p> 2997 * @see #isResizable() 2998 * @see #getContentBias() 2999 * @see #autosize() 3000 * 3001 * @param height the height that should be used if preferred width depends on it 3002 * @return the preferred width that the node should be resized to during layout 3003 * The result will never be NaN, nor will it ever be negative. 3004 */ 3005 public double prefWidth(double height) { 3006 final double result = getLayoutBounds().getWidth(); 3007 return Double.isNaN(result) || result < 0 ? 0 : result; 3008 } 3009 3010 /** 3011 * Returns the node's preferred height for use in layout calculations. 3012 * If the node is resizable, its parent should treat this value as the 3013 * node's ideal height within its range. If the node is not resizable, 3014 * just returns its layoutBounds height, which should be treated as the rigid 3015 * height of the node. 3016 * <p> 3017 * Layout code which calls this method should first check the content-bias 3018 * of the node. If the node has a horizontal content-bias, then callers 3019 * should pass in a width value that the preferred height should be based on. 3020 * If the node has either a vertical or null content-bias, then the caller 3021 * should pass in -1. 3022 * <p> 3023 * Node subclasses with a horizontal content-bias should honor the height 3024 * parameter whether -1 or a positive value. All other subclasses may ignore 3025 * the height parameter (which will likely be -1). 3026 * <p> 3027 * @see #getContentBias() 3028 * @see #autosize() 3029 * 3030 * @param width the width that should be used if preferred height depends on it 3031 * @return the preferred height that the node should be resized to during layout 3032 * The result will never be NaN, nor will it ever be negative. 3033 */ 3034 public double prefHeight(double width) { 3035 final double result = getLayoutBounds().getHeight(); 3036 return Double.isNaN(result) || result < 0 ? 0 : result; 3037 } 3038 3039 /** 3040 * Returns the node's maximum width for use in layout calculations. 3041 * If the node is resizable, its parent should not resize its width any 3042 * larger than this value. A value of Double.MAX_VALUE indicates the 3043 * parent may expand the node's width beyond its preferred without limits. 3044 * <p> 3045 * If the node is not resizable, returns its layoutBounds width. 3046 * <p> 3047 * Layout code which calls this method should first check the content-bias 3048 * of the node. If the node has a vertical content-bias, then callers 3049 * should pass in a height value that the maximum width should be based on. 3050 * If the node has either a horizontal or null content-bias, then the caller 3051 * should pass in -1. 3052 * <p> 3053 * Node subclasses with a vertical content-bias should honor the height 3054 * parameter whether -1 or a positive value. All other subclasses may ignore 3055 * the height parameter (which will likely be -1). 3056 * <p> 3057 * If Node's {@link #minWidth(double)} is greater, it should take precedence 3058 * over the {@code maxWidth}. This means the Node should never be resized below {@code minWidth}. 3059 * <p> 3060 * @see #isResizable() 3061 * @see #getContentBias() 3062 * 3063 * @param height the height that should be used if maximum width depends on it 3064 * @return the maximum width that the node should be resized to during layout 3065 * The result will never be NaN, nor will it ever be negative. 3066 */ 3067 public double maxWidth(double height) { 3068 return prefWidth(height); 3069 } 3070 3071 /** 3072 * Returns the node's maximum height for use in layout calculations. 3073 * If the node is resizable, its parent should not resize its height any 3074 * larger than this value. A value of Double.MAX_VALUE indicates the 3075 * parent may expand the node's height beyond its preferred without limits. 3076 * <p> 3077 * If the node is not resizable, returns its layoutBounds height. 3078 * <p> 3079 * Layout code which calls this method should first check the content-bias 3080 * of the node. If the node has a horizontal content-bias, then callers 3081 * should pass in a width value that the maximum height should be based on. 3082 * If the node has either a vertical or null content-bias, then the caller 3083 * should pass in -1. 3084 * <p> 3085 * Node subclasses with a horizontal content-bias should honor the width 3086 * parameter whether -1 or a positive value. All other subclasses may ignore 3087 * the width parameter (which will likely be -1). 3088 * <p> 3089 * If Node's {@link #minHeight(double)} is greater, it should take precedence 3090 * over the {@code maxHeight}. This means the Node should never be resized below {@code minHeight}. 3091 * <p> 3092 * @see #isResizable() 3093 * @see #getContentBias() 3094 * 3095 * @param width the width that should be used if maximum height depends on it 3096 * @return the maximum height that the node should be resized to during layout 3097 * The result will never be NaN, nor will it ever be negative. 3098 */ 3099 public double maxHeight(double width) { 3100 return prefHeight(width); 3101 } 3102 3103 /** 3104 * If the node is resizable, will set its layout bounds to the specified 3105 * width and height. If the node is not resizable, this method is a no-op. 3106 * <p> 3107 * This method should generally only be called by parent nodes from their 3108 * layoutChildren() methods. All Parent classes will automatically resize 3109 * resizable children, so resizing done directly by the application will be 3110 * overridden by the node's parent, unless the child is unmanaged. 3111 * <p> 3112 * Parents are responsible for ensuring the width and height values fall 3113 * within the resizable node's preferred range. The autosize() method may 3114 * be used if the parent just needs to resize the node to its preferred size. 3115 * 3116 * <p> 3117 * @see #isResizable() 3118 * @see #getContentBias() 3119 * @see #autosize() 3120 * @see #minWidth(double) 3121 * @see #minHeight(double) 3122 * @see #prefWidth(double) 3123 * @see #prefHeight(double) 3124 * @see #maxWidth(double) 3125 * @see #maxHeight(double) 3126 * @see #getLayoutBounds() 3127 * 3128 * @param width the target layout bounds width 3129 * @param height the target layout bounds height 3130 */ 3131 public void resize(double width, double height) { 3132 } 3133 3134 /** 3135 * If the node is resizable, will set its layout bounds to its current preferred 3136 * width and height. If the node is not resizable, this method is a no-op. 3137 * <p> 3138 * This method automatically queries the node's content-bias and if it's 3139 * horizontal, will pass in the node's preferred width to get the preferred 3140 * height; if vertical, will pass in the node's preferred height to get the width, 3141 * and if null, will compute the preferred width/height independently. 3142 * </p> 3143 * 3144 * @see #isResizable() 3145 * @see #getContentBias() 3146 * 3147 */ 3148 public final void autosize() { 3149 if (isResizable()) { 3150 Orientation contentBias = getContentBias(); 3151 double w, h; 3152 if (contentBias == null) { 3153 w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1)); 3154 h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1)); 3155 } else if (contentBias == Orientation.HORIZONTAL) { 3156 w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1)); 3157 h = boundedSize(prefHeight(w), minHeight(w), maxHeight(w)); 3158 } else { // bias == VERTICAL 3159 h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1)); 3160 w = boundedSize(prefWidth(h), minWidth(h), maxWidth(h)); 3161 } 3162 resize(w,h); 3163 } 3164 } 3165 3166 double boundedSize(double value, double min, double max) { 3167 // if max < value, return max 3168 // if min > value, return min 3169 // if min > max, return min 3170 return Math.min(Math.max(value, min), Math.max(min,max)); 3171 } 3172 3173 /** 3174 * If the node is resizable, will set its layout bounds to the specified 3175 * width and height. If the node is not resizable, the resize step is skipped. 3176 * <p> 3177 * Once the node has been resized (if resizable) then sets the node's layoutX 3178 * and layoutY translation properties in order to relocate it to x,y in the 3179 * parent's coordinate space. 3180 * <p> 3181 * This method should generally only be called by parent nodes from their 3182 * layoutChildren() methods. All Parent classes will automatically resize 3183 * resizable children, so resizing done directly by the application will be 3184 * overridden by the node's parent, unless the child is unmanaged. 3185 * <p> 3186 * Parents are responsible for ensuring the width and height values fall 3187 * within the resizable node's preferred range. The autosize() and relocate() 3188 * methods may be used if the parent just needs to resize the node to its 3189 * preferred size and reposition it. 3190 * <p> 3191 * @see #isResizable() 3192 * @see #getContentBias() 3193 * @see #autosize() 3194 * @see #minWidth(double) 3195 * @see #minHeight(double) 3196 * @see #prefWidth(double) 3197 * @see #prefHeight(double) 3198 * @see #maxWidth(double) 3199 * @see #maxHeight(double) 3200 * 3201 * @param x the target x coordinate location 3202 * @param y the target y coordinate location 3203 * @param width the target layout bounds width 3204 * @param height the target layout bounds height 3205 * 3206 */ 3207 public void resizeRelocate(double x, double y, double width, double height) { 3208 resize(width, height); 3209 relocate(x,y); 3210 } 3211 3212 /** 3213 * This is a special value that might be returned by {@link #getBaselineOffset()}. 3214 * This means that the Parent (layout Pane) of this Node should use the height of this Node as a baseline. 3215 */ 3216 public static final double BASELINE_OFFSET_SAME_AS_HEIGHT = Double.NEGATIVE_INFINITY; 3217 3218 /** 3219 * The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location 3220 * that should be used when this node is being vertically aligned by baseline with 3221 * other nodes. By default this returns {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} for resizable Nodes 3222 * and layoutBounds height for non-resizable. Subclasses 3223 * which contain text should override this method to return their actual text baseline offset. 3224 * 3225 * @return offset of text baseline from layoutBounds.minY for non-resizable Nodes or {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} otherwise 3226 */ 3227 public double getBaselineOffset() { 3228 if (isResizable()) { 3229 return BASELINE_OFFSET_SAME_AS_HEIGHT; 3230 } else { 3231 return getLayoutBounds().getHeight(); 3232 } 3233 } 3234 3235 /** 3236 * Returns the area of this {@code Node} projected onto the 3237 * physical screen in pixel units. 3238 * @return the area of this {@code Node} projected onto the physical screen 3239 * @since JavaFX 8.0 3240 */ 3241 public double computeAreaInScreen() { 3242 return doComputeAreaInScreen(); 3243 } 3244 3245 /* 3246 * Help application or utility to implement LOD support by returning the 3247 * projected area of a Node in pixel unit. The projected area is not clipped. 3248 * 3249 * For perspective camera, this method first exams node's bounds against 3250 * camera's clipping plane to cut off those out of viewing frustrum. After 3251 * computing areaInScreen, it applys a tight viewing frustrum check using 3252 * canonical view volume. 3253 * 3254 * The result of areaInScreen comes from the product of 3255 * (projViewTx x localToSceneTransform x localBounds). 3256 * 3257 * Returns 0 for those fall outside viewing frustrum. 3258 */ 3259 private double doComputeAreaInScreen() { 3260 Scene tmpScene = getScene(); 3261 if (tmpScene != null) { 3262 Bounds bounds = getBoundsInLocal(); 3263 Camera camera = tmpScene.getEffectiveCamera(); 3264 boolean isPerspective = camera instanceof PerspectiveCamera ? true : false; 3265 Transform localToSceneTx = getLocalToSceneTransform(); 3266 Affine3D tempTx = TempState.getInstance().tempTx; 3267 BaseBounds localBounds = new BoxBounds((float) bounds.getMinX(), 3268 (float) bounds.getMinY(), 3269 (float) bounds.getMinZ(), 3270 (float) bounds.getMaxX(), 3271 (float) bounds.getMaxY(), 3272 (float) bounds.getMaxZ()); 3273 3274 // NOTE: Viewing frustrum check on camera's clipping plane is now only 3275 // for perspective camera. 3276 // TODO: Need to hook up parallel camera's nearClip and farClip. 3277 if (isPerspective) { 3278 Transform cameraL2STx = camera.getLocalToSceneTransform(); 3279 3280 // If camera transform only contains translate, compare in scene 3281 // coordinate. Otherwise, compare in camera coordinate. 3282 if (cameraL2STx.getMxx() == 1.0 3283 && cameraL2STx.getMxy() == 0.0 3284 && cameraL2STx.getMxz() == 0.0 3285 && cameraL2STx.getMyx() == 0.0 3286 && cameraL2STx.getMyy() == 1.0 3287 && cameraL2STx.getMyz() == 0.0 3288 && cameraL2STx.getMzx() == 0.0 3289 && cameraL2STx.getMzy() == 0.0 3290 && cameraL2STx.getMzz() == 1.0) { 3291 3292 double minZ, maxZ; 3293 3294 // If node transform only contains translate, only convert 3295 // minZ and maxZ to scene coordinate. Otherwise, convert 3296 // node bounds to scene coordinate. 3297 if (localToSceneTx.getMxx() == 1.0 3298 && localToSceneTx.getMxy() == 0.0 3299 && localToSceneTx.getMxz() == 0.0 3300 && localToSceneTx.getMyx() == 0.0 3301 && localToSceneTx.getMyy() == 1.0 3302 && localToSceneTx.getMyz() == 0.0 3303 && localToSceneTx.getMzx() == 0.0 3304 && localToSceneTx.getMzy() == 0.0 3305 && localToSceneTx.getMzz() == 1.0) { 3306 3307 Vec3d tempV3D = TempState.getInstance().vec3d; 3308 tempV3D.set(0, 0, bounds.getMinZ()); 3309 localToScene(tempV3D); 3310 minZ = tempV3D.z; 3311 3312 tempV3D.set(0, 0, bounds.getMaxZ()); 3313 localToScene(tempV3D); 3314 maxZ = tempV3D.z; 3315 } else { 3316 Bounds nodeInSceneBounds = localToScene(bounds); 3317 minZ = nodeInSceneBounds.getMinZ(); 3318 maxZ = nodeInSceneBounds.getMaxZ(); 3319 } 3320 3321 if (minZ > camera.getFarClipInScene() 3322 || maxZ < camera.getNearClipInScene()) { 3323 return 0; 3324 } 3325 3326 } else { 3327 BaseBounds nodeInCameraBounds = new BoxBounds(); 3328 3329 // We need to set tempTx to identity since it is a recycled transform. 3330 // This is because TransformHelper.apply() is a matrix concatenation operation. 3331 tempTx.setToIdentity(); 3332 TransformHelper.apply(localToSceneTx, tempTx); 3333 3334 // Convert node from local coordinate to camera coordinate 3335 tempTx.preConcatenate(camera.getSceneToLocalTransform()); 3336 tempTx.transform(localBounds, nodeInCameraBounds); 3337 3338 // Compare in camera coornidate 3339 if (nodeInCameraBounds.getMinZ() > camera.getFarClip() 3340 || nodeInCameraBounds.getMaxZ() < camera.getNearClip()) { 3341 return 0; 3342 } 3343 } 3344 } 3345 3346 GeneralTransform3D projViewTx = TempState.getInstance().projViewTx; 3347 projViewTx.set(camera.getProjViewTransform()); 3348 3349 // We need to set tempTx to identity since it is a recycled transform. 3350 // This is because TransformHelper.apply() is a matrix concatenation operation. 3351 tempTx.setToIdentity(); 3352 TransformHelper.apply(localToSceneTx, tempTx); 3353 3354 // The product of projViewTx * localToSceneTransform 3355 GeneralTransform3D tx = projViewTx.mul(tempTx); 3356 3357 // Transform localBounds to projected bounds 3358 localBounds = tx.transform(localBounds, localBounds); 3359 double area = localBounds.getWidth() * localBounds.getHeight(); 3360 3361 // Use canonical view volume to check whether object is outside the 3362 // viewing frustrum 3363 if (isPerspective) { 3364 localBounds.intersectWith(-1, -1, 0, 1, 1, 1); 3365 area = (localBounds.getWidth() < 0 || localBounds.getHeight() < 0) ? 0 : area; 3366 } 3367 return area * (camera.getViewWidth() / 2 * camera.getViewHeight() / 2); 3368 } 3369 return 0; 3370 } 3371 3372 /* ************************************************************************* 3373 * * 3374 * Bounds related APIs * 3375 * * 3376 **************************************************************************/ 3377 3378 public final Bounds getBoundsInParent() { 3379 return boundsInParentProperty().get(); 3380 } 3381 3382 /** 3383 * The rectangular bounds of this {@code Node} which include its transforms. 3384 * {@code boundsInParent} is calculated by 3385 * taking the local bounds (defined by {@link #boundsInLocalProperty boundsInLocal}) and applying 3386 * the transform created by setting the following additional variables 3387 * <ol> 3388 * <li>{@link #getTransforms transforms} ObservableList</li> 3389 * <li>{@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}</li> 3390 * <li>{@link #rotateProperty rotate}</li> 3391 * <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li> 3392 * <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY}</li> 3393 * </ol> 3394 * <p> 3395 * The resulting bounds will be conceptually in the coordinate space of the 3396 * {@code Node}'s parent, however the node need not have a parent to calculate 3397 * these bounds. 3398 * <p> 3399 * Note that this method does not take the node's visibility into account; 3400 * the computation is based on the geometry of this {@code Node} only. 3401 * <p> 3402 * This property will always have a non-null value. 3403 * <p> 3404 * Note that boundsInParent is automatically recomputed whenever the 3405 * geometry of a node changes, or when any of the following the change: 3406 * transforms ObservableList, translateX, translateY, layoutX, layoutY, 3407 * scaleX, scaleY, or the rotate variable. For this reason, it is an error 3408 * to bind any of these values in a node to an expression that depends upon 3409 * this variable. For example, the x or y variables of a shape, or 3410 * translateX, translateY should never be bound to boundsInParent 3411 * for the purpose of positioning the node. 3412 * @return the boundsInParent for this {@code Node} 3413 */ 3414 public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() { 3415 return getMiscProperties().boundsInParentProperty(); 3416 } 3417 3418 private void invalidateBoundsInParent() { 3419 if (miscProperties != null) { 3420 miscProperties.invalidateBoundsInParent(); 3421 } 3422 } 3423 3424 public final Bounds getBoundsInLocal() { 3425 return boundsInLocalProperty().get(); 3426 } 3427 3428 /** 3429 * The rectangular bounds of this {@code Node} in the node's 3430 * untransformed local coordinate space. For nodes that extend 3431 * {@link javafx.scene.shape.Shape}, the local bounds will also include 3432 * space required for a non-zero stroke that may fall outside the shape's 3433 * geometry that is defined by position and size attributes. 3434 * The local bounds will also include any clipping set with {@link #clipProperty clip} 3435 * as well as effects set with {@link #effectProperty effect}. 3436 * 3437 * <p> 3438 * Note that this method does not take the node's visibility into account; 3439 * the computation is based on the geometry of this {@code Node} only. 3440 * <p> 3441 * This property will always have a non-null value. 3442 * <p> 3443 * Note that boundsInLocal is automatically recomputed whenever the 3444 * geometry of a node changes. For this reason, it is an error to bind any 3445 * of these values in a node to an expression that depends upon this variable. 3446 * For example, the x or y variables of a shape should never be bound 3447 * to boundsInLocal for the purpose of positioning the node. 3448 * @return the boundsInLocal for this {@code Node} 3449 */ 3450 public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() { 3451 return getMiscProperties().boundsInLocalProperty(); 3452 } 3453 3454 private void invalidateBoundsInLocal() { 3455 if (miscProperties != null) { 3456 miscProperties.invalidateBoundsInLocal(); 3457 } 3458 } 3459 3460 /** 3461 * The rectangular bounds that should be used for layout calculations for 3462 * this node. {@code layoutBounds} may differ from the visual bounds 3463 * of the node and is computed differently depending on the node type. 3464 * <p> 3465 * If the node type is resizable ({@link javafx.scene.layout.Region Region}, 3466 * {@link javafx.scene.control.Control Control}, or {@link javafx.scene.web.WebView WebView}) 3467 * then the layoutBounds will always be {@code 0,0 width x height}. 3468 * If the node type is not resizable ({@link javafx.scene.shape.Shape Shape}, 3469 * {@link javafx.scene.text.Text Text}, or {@link Group}), then the layoutBounds 3470 * are computed based on the node's geometric properties and does not include the 3471 * node's clip, effect, or transforms. See individual class documentation 3472 * for details. 3473 * <p> 3474 * Note that the {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}, {@link #translateXProperty translateX}, and 3475 * {@link #translateYProperty translateY} variables are not included in the layoutBounds. 3476 * This is important because layout code must first determine the current 3477 * size and location of the node (using layoutBounds) and then set 3478 * {@code layoutX} and {@code layoutY} to adjust the translation of the 3479 * node so that it will have the desired layout position. 3480 * <p> 3481 * Because the computation of layoutBounds is often tied to a node's 3482 * geometric variables, it is an error to bind any such variables to an 3483 * expression that depends upon {@code layoutBounds}. For example, the 3484 * x or y variables of a shape should never be bound to layoutBounds 3485 * for the purpose of positioning the node. 3486 * <p> 3487 * The layoutBounds will never be null. 3488 * 3489 */ 3490 private LazyBoundsProperty layoutBounds = new LazyBoundsProperty() { 3491 @Override 3492 protected Bounds computeBounds() { 3493 return NodeHelper.computeLayoutBounds(Node.this); 3494 } 3495 3496 @Override 3497 public Object getBean() { 3498 return Node.this; 3499 } 3500 3501 @Override 3502 public String getName() { 3503 return "layoutBounds"; 3504 } 3505 }; 3506 3507 public final Bounds getLayoutBounds() { 3508 return layoutBoundsProperty().get(); 3509 } 3510 3511 public final ReadOnlyObjectProperty<Bounds> layoutBoundsProperty() { 3512 return layoutBounds; 3513 } 3514 3515 /* 3516 * Bounds And Transforms Computation 3517 * 3518 * This section of the code is responsible for computing and caching 3519 * various bounds and transforms. For optimal performance and minimal 3520 * recomputation of bounds (which can be quite expensive), we cache 3521 * values on two different levels. We expose two public immutable 3522 * Bounds boundsInParent objects and boundsInLocal. Because they are 3523 * immutable and because they may change quite frequently (especially 3524 * in the case of a Parent whose children are animated), it is 3525 * important that the system does not rely on these variables, because 3526 * doing so would produce a large amount of garbage. Rather, these 3527 * variables are provided solely for the convenience of application 3528 * developers and, being lazily bound, should generally be created at 3529 * most once per frame. 3530 * 3531 * The second level of caching are within local Bounds2D variables. 3532 * These variables, txBounds and geomBounds, are mutable and as such 3533 * can be cached and updated as frequently as necessary without creating 3534 * excessive garbage. However, since the computation of bounds is still 3535 * expensive, it is desirable to cache both the geometric bounds and 3536 * the "complete" transformed bounds (essentially, boundsInParent). 3537 * Cached txBounds is particularly useful when computing the geometric 3538 * bounds of a Parent since it would not require complete or partial 3539 * recomputation of each child. 3540 * 3541 * Finally, we cache the complete transform for this node which converts 3542 * its coord system from local to parent coords. This is useful both for 3543 * minimizing bounds recomputations in the case of the geometry having 3544 * changed but the transform not having changed, and also because the tx 3545 * is required for several different computations (for example, it must 3546 * be computed once during state synchronization with the PG peer, and 3547 * must also be computed when the pivot point changes, and also when 3548 * deriving the txBounds of the Node). 3549 * 3550 * As with any caching system, a subtle and non-trivial amount of code 3551 * is devoted to invalidating the bounds / transforms at appropriate 3552 * times and in appropriate places to make sure bounds / transforms 3553 * are recomputed at all necessary times. 3554 * 3555 * There are three computeXXX functions. One is for computing the 3556 * boundsInParent, the second for computing boundsInLocal, and the 3557 * third for computing the default layout bounds (which, by default, 3558 * is based on the geometric bounds). These functions are all prefixed 3559 * with "compute" because they create and return new immutable 3560 * Bounds objects. 3561 * 3562 * There are three getXXXBounds functions. One is for returning the 3563 * complete transformed bounds. The second is for returning the 3564 * local bounds. The last is for returning the geometric bounds. These 3565 * functions are all prefixed with "get" because they may well return 3566 * a cached value, or may actually compute the bounds if necessary. These 3567 * functions all have the same signature. They take a Bounds2D and 3568 * BaseTransform, and return a Bounds2D (the same as they took). These 3569 * functions essentially populate the supplied bounds2D with the 3570 * appropriate bounds information, leveraging cached bounds if possible. 3571 * 3572 * There is a single NodeHelper.computeGeomBoundsImpl function which is abstract. 3573 * This must be implemented in each subclass, and is responsible for 3574 * computing the actual geometric bounds for the Node. For example, Parent 3575 * is written such that this function is the union of the transformed 3576 * bounds of each child. Rectangle is written such that this takes into 3577 * account the size and stroke. Text is written such that it is computed 3578 * based on the actual glyphs. 3579 * 3580 * There are two updateXXX functions, updateGeomBounds and updateTxBounds. 3581 * These functions are for ensuring that geomBounds and txBounds are 3582 * valid. They only execute in the case of the cached value being invalid, 3583 * so the function call is very cheap in cases where the cached bounds 3584 * values are still valid. 3585 */ 3586 3587 /** 3588 * An affine transform that holds the computed local-to-parent transform. 3589 * This is the concatenation of all transforms in this node, including all 3590 * of the convenience transforms. 3591 */ 3592 private BaseTransform localToParentTx = BaseTransform.IDENTITY_TRANSFORM; 3593 3594 /** 3595 * This flag is used to indicate that localToParentTx is dirty and needs 3596 * to be recomputed. 3597 */ 3598 private boolean transformDirty = true; 3599 3600 /** 3601 * The cached transformed bounds. This is never null, but is frequently set 3602 * to be invalid whenever the bounds for the node have changed. These are 3603 * "complete" bounds, that is, with transforms and effect and clip applied. 3604 * Note that this is equivalent to boundsInParent 3605 */ 3606 private BaseBounds txBounds = new RectBounds(); 3607 3608 /** 3609 * The cached bounds. This is never null, but is frequently set to be 3610 * invalid whenever the bounds for the node have changed. These are the 3611 * "content" bounds, that is, without transforms or effects applied. 3612 */ 3613 private BaseBounds geomBounds = new RectBounds(); 3614 3615 /** 3616 * The cached local bounds (without transforms, with clip and effects). 3617 * If there is neither clip nor effect 3618 * local bounds are equal to geom bounds, so in this case we don't keep 3619 * the extra instance and set null to this variable. 3620 */ 3621 private BaseBounds localBounds = null; 3622 3623 /** 3624 * This special flag is used only by Parent to flag whether or not 3625 * the *parent* has processed the fact that bounds have changed for this 3626 * child Node. We need some way of flagging this on a per-node basis to 3627 * enable the significant performance optimizations and fast paths that 3628 * are in the Parent code. 3629 * <p> 3630 * To reduce confusion, although this variable is defined on Node, it 3631 * really belongs to the Parent of the node and should *only* be modified 3632 * by the parent. 3633 */ 3634 boolean boundsChanged; 3635 3636 /* 3637 * Returns geometric bounds, but may be over-ridden by a subclass. 3638 */ 3639 private Bounds doComputeLayoutBounds() { 3640 BaseBounds tempBounds = TempState.getInstance().bounds; 3641 tempBounds = getGeomBounds(tempBounds, 3642 BaseTransform.IDENTITY_TRANSFORM); 3643 return new BoundingBox(tempBounds.getMinX(), 3644 tempBounds.getMinY(), 3645 tempBounds.getMinZ(), 3646 tempBounds.getWidth(), 3647 tempBounds.getHeight(), 3648 tempBounds.getDepth()); 3649 } 3650 3651 /* 3652 * Subclasses may customize the layoutBounds by means of overriding the 3653 * NodeHelper.computeLayoutBoundsImpl method. If the layout bounds need to be 3654 * recomputed, the subclass must notify the Node implementation of this 3655 * fact so that appropriate notifications and internal state can be 3656 * kept in sync. Subclasses must call NodeHelper.layoutBoundsChanged to 3657 * let Node know that the layout bounds are invalid and need to be 3658 * recomputed. 3659 */ 3660 final void layoutBoundsChanged() { 3661 if (!layoutBounds.valid) { 3662 return; 3663 } 3664 layoutBounds.invalidate(); 3665 if ((nodeTransformation != null && nodeTransformation.hasScaleOrRotate()) || hasMirroring()) { 3666 // if either the scale or rotate convenience variables are used, 3667 // then we need a valid pivot point. Since the layoutBounds 3668 // affects the pivot we need to invalidate the transform 3669 NodeHelper.transformsChanged(this); 3670 } 3671 } 3672 3673 /** 3674 * Loads the given bounds object with the transformed bounds relative to, 3675 * and based on, the given transform. That is, this is the local bounds 3676 * with the local-to-parent transform applied. 3677 * 3678 * We *never* pass null in as a bounds. This method will 3679 * NOT take a null bounds object. The returned value may be 3680 * the same bounds object passed in, or it may be a new object. 3681 * The reason for this object promotion is in the case of needing 3682 * to promote from a RectBounds to a BoxBounds (3D). 3683 */ 3684 BaseBounds getTransformedBounds(BaseBounds bounds, BaseTransform tx) { 3685 updateLocalToParentTransform(); 3686 if (tx.isTranslateOrIdentity()) { 3687 updateTxBounds(); 3688 bounds = bounds.deriveWithNewBounds(txBounds); 3689 if (!tx.isIdentity()) { 3690 final double translateX = tx.getMxt(); 3691 final double translateY = tx.getMyt(); 3692 final double translateZ = tx.getMzt(); 3693 bounds = bounds.deriveWithNewBounds( 3694 (float) (bounds.getMinX() + translateX), 3695 (float) (bounds.getMinY() + translateY), 3696 (float) (bounds.getMinZ() + translateZ), 3697 (float) (bounds.getMaxX() + translateX), 3698 (float) (bounds.getMaxY() + translateY), 3699 (float) (bounds.getMaxZ() + translateZ)); 3700 } 3701 return bounds; 3702 } else if (localToParentTx.isIdentity()) { 3703 return getLocalBounds(bounds, tx); 3704 } else { 3705 double mxx = tx.getMxx(); 3706 double mxy = tx.getMxy(); 3707 double mxz = tx.getMxz(); 3708 double mxt = tx.getMxt(); 3709 double myx = tx.getMyx(); 3710 double myy = tx.getMyy(); 3711 double myz = tx.getMyz(); 3712 double myt = tx.getMyt(); 3713 double mzx = tx.getMzx(); 3714 double mzy = tx.getMzy(); 3715 double mzz = tx.getMzz(); 3716 double mzt = tx.getMzt(); 3717 BaseTransform boundsTx = tx.deriveWithConcatenation(localToParentTx); 3718 bounds = getLocalBounds(bounds, boundsTx); 3719 if (boundsTx == tx) { 3720 tx.restoreTransform(mxx, mxy, mxz, mxt, 3721 myx, myy, myz, myt, 3722 mzx, mzy, mzz, mzt); 3723 } 3724 return bounds; 3725 } 3726 } 3727 3728 /** 3729 * Loads the given bounds object with the local bounds relative to, 3730 * and based on, the given transform. That is, these are the geometric 3731 * bounds + clip and effect. 3732 * 3733 * We *never* pass null in as a bounds. This method will 3734 * NOT take a null bounds object. The returned value may be 3735 * the same bounds object passed in, or it may be a new object. 3736 * The reason for this object promotion is in the case of needing 3737 * to promote from a RectBounds to a BoxBounds (3D). 3738 */ 3739 BaseBounds getLocalBounds(BaseBounds bounds, BaseTransform tx) { 3740 if (getEffect() == null && getClip() == null) { 3741 return getGeomBounds(bounds, tx); 3742 } 3743 3744 if (tx.isTranslateOrIdentity()) { 3745 // we can take a fast path since we know tx is either a simple 3746 // translation or is identity 3747 updateLocalBounds(); 3748 bounds = bounds.deriveWithNewBounds(localBounds); 3749 if (!tx.isIdentity()) { 3750 double translateX = tx.getMxt(); 3751 double translateY = tx.getMyt(); 3752 double translateZ = tx.getMzt(); 3753 bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX), 3754 (float) (bounds.getMinY() + translateY), 3755 (float) (bounds.getMinZ() + translateZ), 3756 (float) (bounds.getMaxX() + translateX), 3757 (float) (bounds.getMaxY() + translateY), 3758 (float) (bounds.getMaxZ() + translateZ)); 3759 } 3760 return bounds; 3761 } else if (tx.is2D() 3762 && (tx.getType() 3763 & ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION 3764 | BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) { 3765 // this is a non-uniform scale / non-quadrant rotate / skew transform 3766 return computeLocalBounds(bounds, tx); 3767 } else { 3768 // 3D transformations and 3769 // selected 2D transformations (unifrom transform, flip, quadrant rotation). 3770 // These 2D transformation will yield tight bounds when applied on the pre-computed 3771 // geomBounds 3772 // Note: Transforming the local bounds into a 3D space will yield a bounds 3773 // that isn't as tight as transforming its geometry and compute it bounds. 3774 updateLocalBounds(); 3775 return tx.transform(localBounds, bounds); 3776 } 3777 } 3778 3779 /** 3780 * Loads the given bounds object with the geometric bounds relative to, 3781 * and based on, the given transform. 3782 * 3783 * We *never* pass null in as a bounds. This method will 3784 * NOT take a null bounds object. The returned value may be 3785 * the same bounds object passed in, or it may be a new object. 3786 * The reason for this object promotion is in the case of needing 3787 * to promote from a RectBounds to a BoxBounds (3D). 3788 */ 3789 BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx) { 3790 if (tx.isTranslateOrIdentity()) { 3791 // we can take a fast path since we know tx is either a simple 3792 // translation or is identity 3793 updateGeomBounds(); 3794 bounds = bounds.deriveWithNewBounds(geomBounds); 3795 if (!tx.isIdentity()) { 3796 double translateX = tx.getMxt(); 3797 double translateY = tx.getMyt(); 3798 double translateZ = tx.getMzt(); 3799 bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX), 3800 (float) (bounds.getMinY() + translateY), 3801 (float) (bounds.getMinZ() + translateZ), 3802 (float) (bounds.getMaxX() + translateX), 3803 (float) (bounds.getMaxY() + translateY), 3804 (float) (bounds.getMaxZ() + translateZ)); 3805 } 3806 return bounds; 3807 } else if (tx.is2D() 3808 && (tx.getType() 3809 & ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION 3810 | BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) { 3811 // this is a non-uniform scale / non-quadrant rotate / skew transform 3812 return NodeHelper.computeGeomBounds(this, bounds, tx); 3813 } else { 3814 // 3D transformations and 3815 // selected 2D transformations (unifrom transform, flip, quadrant rotation). 3816 // These 2D transformation will yield tight bounds when applied on the pre-computed 3817 // geomBounds 3818 // Note: Transforming the local geomBounds into a 3D space will yield a bounds 3819 // that isn't as tight as transforming its geometry and compute it bounds. 3820 updateGeomBounds(); 3821 return tx.transform(geomBounds, bounds); 3822 } 3823 } 3824 3825 /** 3826 * If necessary, recomputes the cached geom bounds. If the bounds are not 3827 * invalid, then this method is a no-op. 3828 */ 3829 void updateGeomBounds() { 3830 if (geomBoundsInvalid) { 3831 geomBounds = NodeHelper.computeGeomBounds(this, geomBounds, BaseTransform.IDENTITY_TRANSFORM); 3832 geomBoundsInvalid = false; 3833 } 3834 } 3835 3836 /** 3837 * Computes the local bounds of this Node. 3838 */ 3839 private BaseBounds computeLocalBounds(BaseBounds bounds, BaseTransform tx) { 3840 // We either get the bounds of the effect (if it isn't null) 3841 // or we get the geom bounds (if effect is null). We will then 3842 // intersect this with the clip. 3843 if (getEffect() != null) { 3844 BaseBounds b = EffectHelper.getBounds(getEffect(), bounds, tx, this, boundsAccessor); 3845 bounds = bounds.deriveWithNewBounds(b); 3846 } else { 3847 bounds = getGeomBounds(bounds, tx); 3848 } 3849 // intersect with the clip. Take care with "bounds" as it may 3850 // actually be TEMP_BOUNDS, so we save off state 3851 if (getClip() != null 3852 // FIXME: All 3D picking is currently ignored by rendering. 3853 // Until this is fixed or defined differently (RT-28510), 3854 // we follow this behavior. 3855 && !(this instanceof Shape3D) && !(getClip() instanceof Shape3D)) { 3856 double x1 = bounds.getMinX(); 3857 double y1 = bounds.getMinY(); 3858 double x2 = bounds.getMaxX(); 3859 double y2 = bounds.getMaxY(); 3860 double z1 = bounds.getMinZ(); 3861 double z2 = bounds.getMaxZ(); 3862 bounds = getClip().getTransformedBounds(bounds, tx); 3863 bounds.intersectWith((float)x1, (float)y1, (float)z1, 3864 (float)x2, (float)y2, (float)z2); 3865 } 3866 return bounds; 3867 } 3868 3869 3870 /** 3871 * If necessary, recomputes the cached local bounds. If the bounds are not 3872 * invalid, then this method is a no-op. 3873 */ 3874 private void updateLocalBounds() { 3875 if (localBoundsInvalid) { 3876 if (getClip() != null || getEffect() != null) { 3877 localBounds = computeLocalBounds( 3878 localBounds == null ? new RectBounds() : localBounds, 3879 BaseTransform.IDENTITY_TRANSFORM); 3880 } else { 3881 localBounds = null; 3882 } 3883 localBoundsInvalid = false; 3884 } 3885 } 3886 3887 /** 3888 * If necessary, recomputes the cached transformed bounds. 3889 * If the cached transformed bounds are not invalid, then 3890 * this method is a no-op. 3891 */ 3892 void updateTxBounds() { 3893 if (txBoundsInvalid) { 3894 updateLocalToParentTransform(); 3895 txBounds = getLocalBounds(txBounds, localToParentTx); 3896 txBoundsInvalid = false; 3897 } 3898 } 3899 3900 /* 3901 * Bounds Invalidation And Notification 3902 * 3903 * The goal of this section is to efficiently propagate bounds 3904 * invalidation through the scenegraph while also being semantically 3905 * correct. 3906 * 3907 * The code path for invalidation of layout bounds is somewhat confusing 3908 * primarily due to performance enhancements and the desire to reduce the 3909 * number of requestLayout() calls that are performed when layout bounds 3910 * change. Before diving into layout bounds, I will first describe how 3911 * normal bounds invalidation occurs. 3912 * 3913 * When a node's geometry changes (for example, if the width of a 3914 * Rectangle is changed) then the Node must call NodeHelper.geomChanged(). 3915 * Invoking this function will eventually clear all cached bounds and 3916 * notify to each parent up the tree that their bounds may have changed. 3917 * 3918 * After invalidating geomBounds (and after kicking off layout bounds 3919 * notification), NodeHelper.geomChanged calls localBoundsChanged(). It should 3920 * be noted that NodeHelper.geomChanged should only be called when the geometry 3921 * of the node has changed such that it may result in the geom bounds 3922 * actually changing. 3923 * 3924 * localBoundsChanged() simply invalidates boundsInLocal and then calls 3925 * transformedBoundsChanged(). 3926 * 3927 * transformedBoundsChanged() is responsible for invalidating 3928 * boundsInParent and txBounds. If the Node is not visible, then there is 3929 * no need to notify the parent of the bounds change because the parent's 3930 * bounds do not include invisible nodes. If the node is visible, then 3931 * it must tell the parent that this child node's bounds have changed. 3932 * It is up to the parent to eventually invoke its own NodeHelper.geomChanged 3933 * function. If instead of a parent this node has a clipParent, then the 3934 * clipParent's localBoundsChanged() is called instead. 3935 * 3936 * There are a few other ways in which we enter the invalidate steps 3937 * beyond just the geometry changes. If the visibility of a Node changes, 3938 * its own bounds are not affected but its parent's bounds are. So a 3939 * special call to parent.childVisibilityChanged is made so the parent 3940 * can react accordingly. 3941 * 3942 * If a transform is changed (layoutX, layoutY, rotate, transforms, etc) 3943 * then the transform must be invalidated. When a transform is invalidated, 3944 * it must also invalidate the txBounds by invoking 3945 * transformedBoundsChanged, which will in turn notify the parent as 3946 * before. 3947 * 3948 * If an effect is changed or replaced then the local bounds must be 3949 * invalidated, as well as the transformedBounds and the parent notified 3950 * of the change in bounds. 3951 * 3952 * layoutBound is somewhat unique in that it can be redefined in 3953 * subclasses. By default, the layoutBounds is the geomBounds, and so 3954 * whenever the geomBounds() function is called the layoutBounds 3955 * must be invalidated. However in subclasses, especially Resizables, 3956 * the layout bounds may not be defined to be the same as the geometric 3957 * bounds. This is both useful and provides a very nice performance 3958 * optimization for regions and controls. In this case, subclasses 3959 * need some way to interpose themselves such that a call to 3960 * NodeHelper.geomChanged() *does not* invalidate the layout bounds. 3961 * 3962 * This interposition happens by providing the 3963 * NodeHelper.notifyLayoutBoundsChanged function. The default implementation 3964 * simply invalidates boundsInLocal. Subclasses (such as Region and 3965 * Control) can override this function so that it does not invalidate 3966 * the layout bounds. 3967 * 3968 * An on invalidate trigger on layoutBounds handles kicking off the rest 3969 * of the invalidate process for layoutBounds. Because the layout bounds 3970 * define the pivot point, if scaleX, scaleY, or rotate contain 3971 * non-identity values then whenever the layoutBounds change the 3972 * transformed bounds also change. Finally, if this node's parent is 3973 * a Region and if the Node is being managed by the Region, then 3974 * we must call requestLayout on the Region whenever the layout bounds 3975 * have changed. 3976 */ 3977 3978 /* 3979 * Invoked by subclasses whenever their geometric bounds have changed. 3980 * Because the default layout bounds is based on the node geometry, this 3981 * function will invoke NodeHelper.notifyLayoutBoundsChanged. The default 3982 * implementation of NodeHelper.notifyLayoutBoundsChanged() will simply invalidate 3983 * layoutBounds. Resizable subclasses will want to override this function 3984 * in most cases to be a no-op. 3985 * 3986 * This function will also invalidate the cached geom bounds, and then 3987 * invoke localBoundsChanged() which will eventually end up invoking a 3988 * chain of functions up the tree to ensure that each parent of this 3989 * Node is notified that its bounds may have also changed. 3990 * 3991 * This function should be treated as though it were final. It is not 3992 * intended to be overridden by subclasses. 3993 * 3994 * Note: This method MUST only be called via its accessor method. 3995 */ 3996 private void doGeomChanged() { 3997 if (geomBoundsInvalid) { 3998 // GeomBoundsInvalid is false when node geometry changed and 3999 // the untransformed node bounds haven't been recalculated yet. 4000 // Most of the time, the recalculation of layout and transformed 4001 // node bounds don't require validation of untransformed bounds 4002 // and so we can not skip the following notifications. 4003 NodeHelper.notifyLayoutBoundsChanged(this); 4004 transformedBoundsChanged(); 4005 return; 4006 } 4007 geomBounds.makeEmpty(); 4008 geomBoundsInvalid = true; 4009 NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS); 4010 NodeHelper.notifyLayoutBoundsChanged(this); 4011 localBoundsChanged(); 4012 } 4013 4014 private boolean geomBoundsInvalid = true; 4015 private boolean localBoundsInvalid = true; 4016 private boolean txBoundsInvalid = true; 4017 4018 /** 4019 * Responds to changes in the local bounds by invalidating boundsInLocal 4020 * and notifying this node that its transformed bounds have changed. 4021 */ 4022 void localBoundsChanged() { 4023 localBoundsInvalid = true; 4024 invalidateBoundsInLocal(); 4025 transformedBoundsChanged(); 4026 } 4027 4028 /** 4029 * Responds to changes in the transformed bounds by invalidating txBounds 4030 * and boundsInParent. If this Node is not visible, then we have no need 4031 * to walk further up the tree but can instead simply invalidate state. 4032 * Otherwise, this function will notify parents (either the parent or the 4033 * clipParent) that this child Node's bounds have changed. 4034 */ 4035 void transformedBoundsChanged() { 4036 if (!txBoundsInvalid) { 4037 txBounds.makeEmpty(); 4038 txBoundsInvalid = true; 4039 invalidateBoundsInParent(); 4040 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS); 4041 } 4042 if (isVisible()) { 4043 notifyParentOfBoundsChange(); 4044 } 4045 } 4046 4047 /* 4048 * Invoked by geomChanged(). Since layoutBounds is by default based 4049 * on the geometric bounds, the default implementation of this function will 4050 * invalidate the layoutBounds. Resizable Node subclasses generally base 4051 * layoutBounds on the width/height instead of the geometric bounds, and so 4052 * will generally want to override this function to be a no-op. 4053 * 4054 * Note: This method MUST only be called via its accessor method. 4055 */ 4056 private void doNotifyLayoutBoundsChanged() { 4057 layoutBoundsChanged(); 4058 // notify the parent 4059 // Group instanceof check a little hoaky, but it allows us to disable 4060 // unnecessary layout for the case of a non-resizable within a group 4061 Parent p = getParent(); 4062 4063 // Need to propagate layout if parent isn't part of performing layout 4064 if (isManaged() && (p != null) && !(p instanceof Group && !isResizable()) 4065 && !p.isPerformingLayout()) { 4066 // Force its parent to fix the layout since it is a managed child. 4067 p.requestLayout(true); 4068 } 4069 } 4070 4071 /** 4072 * Notifies both the real parent and the clip parent (if they exist) that 4073 * the bounds of the child has changed. Note that since FX doesn't throw 4074 * NPE's, things actually are faster if we don't check twice for Null 4075 * (we check once, the compiler checks again) 4076 */ 4077 void notifyParentOfBoundsChange() { 4078 // let the parent know which node has changed and the parent will 4079 // deal with marking itself invalid correctly 4080 Parent p = getParent(); 4081 if (p != null) { 4082 p.childBoundsChanged(this); 4083 } 4084 // since the clip is used to compute the local bounds (and not the 4085 // geom bounds), we just need to notify that local bounds on the 4086 // clip parent have changed 4087 if (clipParent != null) { 4088 clipParent.localBoundsChanged(); 4089 } 4090 } 4091 4092 /*************************************************************************** 4093 * * 4094 * Geometry and coordinate system related APIs. For example, methods * 4095 * related to containment, intersection, coordinate space conversion, etc. * 4096 * * 4097 **************************************************************************/ 4098 4099 /** 4100 * Returns {@code true} if the given point (specified in the local 4101 * coordinate space of this {@code Node}) is contained within the shape of 4102 * this {@code Node}. Note that this method does not take visibility into 4103 * account; the test is based on the geometry of this {@code Node} only. 4104 * @param localX the x coordinate of the point in Node's space 4105 * @param localY the y coordinate of the point in Node's space 4106 * @return the result of contains for this {@code Node} 4107 */ 4108 public boolean contains(double localX, double localY) { 4109 if (containsBounds(localX, localY)) { 4110 return (isPickOnBounds() || NodeHelper.computeContains(this, localX, localY)); 4111 } 4112 return false; 4113 } 4114 4115 /* 4116 * This method only does the contains check based on the bounds, clip and 4117 * effect of this node, excluding its shape (or geometry). 4118 * 4119 * Returns true if the given point (specified in the local 4120 * coordinate space of this {@code Node}) is contained within the bounds, 4121 * clip and effect of this node. 4122 */ 4123 private boolean containsBounds(double localX, double localY) { 4124 final TempState tempState = TempState.getInstance(); 4125 BaseBounds tempBounds = tempState.bounds; 4126 4127 // first, we do a quick test to see if the point is contained in 4128 // our local bounds. If so, then we will go the next step and check 4129 // the clip, effect, and geometry for containment. 4130 tempBounds = getLocalBounds(tempBounds, 4131 BaseTransform.IDENTITY_TRANSFORM); 4132 if (tempBounds.contains((float)localX, (float)localY)) { 4133 // if the clip is defined, then check it for containment, being 4134 // sure to convert from this node's local coordinate system 4135 // to the local coordinate system of the clip node 4136 if (getClip() != null) { 4137 tempState.point.x = (float)localX; 4138 tempState.point.y = (float)localY; 4139 try { 4140 getClip().parentToLocal(tempState.point); 4141 } catch (NoninvertibleTransformException e) { 4142 return false; 4143 } 4144 if (!getClip().contains(tempState.point.x, tempState.point.y)) { 4145 return false; 4146 } 4147 } 4148 return true; 4149 } 4150 return false; 4151 } 4152 4153 /** 4154 * Returns {@code true} if the given point (specified in the local 4155 * coordinate space of this {@code Node}) is contained within the shape of 4156 * this {@code Node}. Note that this method does not take visibility into 4157 * account; the test is based on the geometry of this {@code Node} only. 4158 * @param localPoint the 2D point in Node's space 4159 * @return the result of contains for this {@code Node} 4160 */ 4161 public boolean contains(Point2D localPoint) { 4162 return contains(localPoint.getX(), localPoint.getY()); 4163 } 4164 4165 /** 4166 * Returns {@code true} if the given rectangle (specified in the local 4167 * coordinate space of this {@code Node}) intersects the shape of this 4168 * {@code Node}. Note that this method does not take visibility into 4169 * account; the test is based on the geometry of this {@code Node} only. 4170 * The default behavior of this function is simply to check if the 4171 * given coordinates intersect with the local bounds. 4172 * @param localX the x coordinate of a rectangle in Node's space 4173 * @param localY the y coordinate of a rectangle in Node's space 4174 * @param localWidth the width of a rectangle in Node's space 4175 * @param localHeight the height of a rectangle in Node's space 4176 * @return the result of intersects for this {@code Node} 4177 */ 4178 public boolean intersects(double localX, double localY, double localWidth, double localHeight) { 4179 BaseBounds tempBounds = TempState.getInstance().bounds; 4180 tempBounds = getLocalBounds(tempBounds, 4181 BaseTransform.IDENTITY_TRANSFORM); 4182 return tempBounds.intersects((float)localX, 4183 (float)localY, 4184 (float)localWidth, 4185 (float)localHeight); 4186 } 4187 4188 /** 4189 * Returns {@code true} if the given bounds (specified in the local 4190 * coordinate space of this {@code Node}) intersects the shape of this 4191 * {@code Node}. Note that this method does not take visibility into 4192 * account; the test is based on the geometry of this {@code Node} only. 4193 * The default behavior of this function is simply to check if the 4194 * given coordinates intersect with the local bounds. 4195 * @param localBounds the bounds 4196 * @return the result of intersects for this {@code Node} 4197 */ 4198 public boolean intersects(Bounds localBounds) { 4199 return intersects(localBounds.getMinX(), localBounds.getMinY(), localBounds.getWidth(), localBounds.getHeight()); 4200 } 4201 4202 /** 4203 * Transforms a point from the coordinate space of the {@link javafx.stage.Screen} 4204 * into the local coordinate space of this {@code Node}. 4205 * @param screenX x coordinate of a point on a Screen 4206 * @param screenY y coordinate of a point on a Screen 4207 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4208 * Null is also returned if the transformation from local to Scene is not invertible. 4209 * @since JavaFX 8.0 4210 */ 4211 public Point2D screenToLocal(double screenX, double screenY) { 4212 Scene scene = getScene(); 4213 if (scene == null) return null; 4214 Window window = scene.getWindow(); 4215 if (window == null) return null; 4216 4217 final com.sun.javafx.geom.Point2D tempPt = 4218 TempState.getInstance().point; 4219 4220 tempPt.setLocation((float)(screenX - scene.getX() - window.getX()), 4221 (float)(screenY - scene.getY() - window.getY())); 4222 4223 final SubScene subScene = getSubScene(); 4224 if (subScene != null) { 4225 final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene, 4226 new Point2D(tempPt.x, tempPt.y)); 4227 if (ssCoord == null) { 4228 return null; 4229 } 4230 tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY()); 4231 } 4232 4233 final Point3D ppIntersect = 4234 scene.getEffectiveCamera().pickProjectPlane(tempPt.x, tempPt.y); 4235 tempPt.setLocation((float) ppIntersect.getX(), (float) ppIntersect.getY()); 4236 4237 try { 4238 sceneToLocal(tempPt); 4239 } catch (NoninvertibleTransformException e) { 4240 return null; 4241 } 4242 return new Point2D(tempPt.x, tempPt.y); 4243 } 4244 4245 /** 4246 * Transforms a point from the coordinate space of the {@link javafx.stage.Screen} 4247 * into the local coordinate space of this {@code Node}. 4248 * @param screenPoint a point on a Screen 4249 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4250 * Null is also returned if the transformation from local to Scene is not invertible. 4251 * @since JavaFX 8.0 4252 */ 4253 public Point2D screenToLocal(Point2D screenPoint) { 4254 return screenToLocal(screenPoint.getX(), screenPoint.getY()); 4255 } 4256 4257 /** 4258 * Transforms a rectangle from the coordinate space of the 4259 * {@link javafx.stage.Screen} into the local coordinate space of this 4260 * {@code Node}. Returns reasonable result only in 2D space. 4261 * @param screenBounds bounds on a Screen 4262 * @return bounds in the local Node'space or null if Node is not in a {@link Window}. 4263 * Null is also returned if the transformation from local to Scene is not invertible. 4264 * @since JavaFX 8.0 4265 */ 4266 public Bounds screenToLocal(Bounds screenBounds) { 4267 final Point2D p1 = screenToLocal(screenBounds.getMinX(), screenBounds.getMinY()); 4268 final Point2D p2 = screenToLocal(screenBounds.getMinX(), screenBounds.getMaxY()); 4269 final Point2D p3 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMinY()); 4270 final Point2D p4 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMaxY()); 4271 4272 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4273 } 4274 4275 4276 /** 4277 * Transforms a point from the coordinate space of the scene 4278 * into the local coordinate space of this {@code Node}. 4279 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the arguments are in {@link Scene} coordinates 4280 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4281 * {@link #sceneToLocal(double, double)} 4282 * @param x the x coordinate 4283 * @param y the y coordinate 4284 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4285 * @return local coordinates of the point 4286 * @since JavaFX 8u40 4287 */ 4288 public Point2D sceneToLocal(double x, double y, boolean rootScene) { 4289 if (!rootScene) { 4290 return sceneToLocal(x, y); 4291 } 4292 final com.sun.javafx.geom.Point2D tempPt = 4293 TempState.getInstance().point; 4294 4295 tempPt.setLocation((float)(x), (float)y); 4296 4297 final SubScene subScene = getSubScene(); 4298 if (subScene != null) { 4299 final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene, 4300 new Point2D(tempPt.x, tempPt.y)); 4301 if (ssCoord == null) { 4302 return null; 4303 } 4304 tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY()); 4305 } 4306 4307 try { 4308 sceneToLocal(tempPt); 4309 return new Point2D(tempPt.x, tempPt.y); 4310 } catch (NoninvertibleTransformException e) { 4311 return null; 4312 } 4313 } 4314 4315 /** 4316 * Transforms a point from the coordinate space of the scene 4317 * into the local coordinate space of this {@code Node}. 4318 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the arguments are in {@link Scene} coordinates 4319 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4320 * {@link #sceneToLocal(javafx.geometry.Point2D)} 4321 * @param point the point 4322 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4323 * @return local coordinates of the point 4324 * @since JavaFX 8u40 4325 */ 4326 public Point2D sceneToLocal(Point2D point, boolean rootScene) { 4327 return sceneToLocal(point.getX(), point.getY(), rootScene); 4328 } 4329 4330 /** 4331 * Transforms a bounds from the coordinate space of the scene 4332 * into the local coordinate space of this {@code Node}. 4333 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the arguments are in {@link Scene} coordinates 4334 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4335 * {@link #sceneToLocal(javafx.geometry.Bounds)}. 4336 * <p> 4337 * Since 3D bounds cannot be converted with {@code rootScene} set to {@code true}, trying to convert 3D bounds will yield {@code null}. 4338 * </p> 4339 * @param bounds the bounds 4340 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4341 * @return local coordinates of the bounds 4342 * @since JavaFX 8u40 4343 */ 4344 public Bounds sceneToLocal(Bounds bounds, boolean rootScene) { 4345 if (!rootScene) { 4346 return sceneToLocal(bounds); 4347 } 4348 if (bounds.getMinZ() != 0 || bounds.getMaxZ() != 0) { 4349 return null; 4350 } 4351 final Point2D p1 = sceneToLocal(bounds.getMinX(), bounds.getMinY(), true); 4352 final Point2D p2 = sceneToLocal(bounds.getMinX(), bounds.getMaxY(), true); 4353 final Point2D p3 = sceneToLocal(bounds.getMaxX(), bounds.getMinY(), true); 4354 final Point2D p4 = sceneToLocal(bounds.getMaxX(), bounds.getMaxY(), true); 4355 4356 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4357 } 4358 4359 /** 4360 * Transforms a point from the coordinate space of the scene 4361 * into the local coordinate space of this {@code Node}. 4362 * 4363 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates, 4364 * not that of {@link javafx.scene.Scene}. 4365 * 4366 * @param sceneX x coordinate of a point on a Scene 4367 * @param sceneY y coordinate of a point on a Scene 4368 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4369 * Null is also returned if the transformation from local to Scene is not invertible. 4370 */ 4371 public Point2D sceneToLocal(double sceneX, double sceneY) { 4372 final com.sun.javafx.geom.Point2D tempPt = 4373 TempState.getInstance().point; 4374 tempPt.setLocation((float)sceneX, (float)sceneY); 4375 try { 4376 sceneToLocal(tempPt); 4377 } catch (NoninvertibleTransformException e) { 4378 return null; 4379 } 4380 return new Point2D(tempPt.x, tempPt.y); 4381 } 4382 4383 /** 4384 * Transforms a point from the coordinate space of the scene 4385 * into the local coordinate space of this {@code Node}. 4386 * 4387 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates, 4388 * not that of {@link javafx.scene.Scene}. 4389 * 4390 * @param scenePoint a point on a Scene 4391 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4392 * Null is also returned if the transformation from local to Scene is not invertible. 4393 */ 4394 public Point2D sceneToLocal(Point2D scenePoint) { 4395 return sceneToLocal(scenePoint.getX(), scenePoint.getY()); 4396 } 4397 4398 /** 4399 * Transforms a point from the coordinate space of the scene 4400 * into the local coordinate space of this {@code Node}. 4401 * 4402 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates, 4403 * not that of {@link javafx.scene.Scene}. 4404 * 4405 * @param scenePoint a point on a Scene 4406 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4407 * Null is also returned if the transformation from local to Scene is not invertible. 4408 * @since JavaFX 8.0 4409 */ 4410 public Point3D sceneToLocal(Point3D scenePoint) { 4411 return sceneToLocal(scenePoint.getX(), scenePoint.getY(), scenePoint.getZ()); 4412 } 4413 4414 /** 4415 * Transforms a point from the coordinate space of the scene 4416 * into the local coordinate space of this {@code Node}. 4417 * 4418 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates, 4419 * not that of {@link javafx.scene.Scene}. 4420 * 4421 * @param sceneX x coordinate of a point on a Scene 4422 * @param sceneY y coordinate of a point on a Scene 4423 * @param sceneZ z coordinate of a point on a Scene 4424 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}. 4425 * Null is also returned if the transformation from local to Scene is not invertible. 4426 * @since JavaFX 8.0 4427 */ 4428 public Point3D sceneToLocal(double sceneX, double sceneY, double sceneZ) { 4429 try { 4430 return sceneToLocal0(sceneX, sceneY, sceneZ); 4431 } catch (NoninvertibleTransformException ex) { 4432 return null; 4433 } 4434 } 4435 4436 /** 4437 * Internal method to transform a point from scene to local coordinates. 4438 */ 4439 private Point3D sceneToLocal0(double x, double y, double z) throws NoninvertibleTransformException { 4440 final com.sun.javafx.geom.Vec3d tempV3D = 4441 TempState.getInstance().vec3d; 4442 tempV3D.set(x, y, z); 4443 sceneToLocal(tempV3D); 4444 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z); 4445 } 4446 4447 /** 4448 * Transforms a rectangle from the coordinate space of the 4449 * scene into the local coordinate space of this 4450 * {@code Node}. 4451 * 4452 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates, 4453 * not that of {@link javafx.scene.Scene}. 4454 * 4455 * @param sceneBounds bounds on a Scene 4456 * @return bounds in the local Node'space or null if Node is not in a {@link Window}. 4457 * Null is also returned if the transformation from local to Scene is not invertible. 4458 */ 4459 public Bounds sceneToLocal(Bounds sceneBounds) { 4460 // Do a quick update of localToParentTransform so that we can determine 4461 // if this tx is 2D transform 4462 updateLocalToParentTransform(); 4463 if (localToParentTx.is2D() && (sceneBounds.getMinZ() == 0) && (sceneBounds.getMaxZ() == 0)) { 4464 Point2D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY()); 4465 Point2D p2 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY()); 4466 Point2D p3 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY()); 4467 Point2D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY()); 4468 4469 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4470 } 4471 try { 4472 Point3D p1 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMinZ()); 4473 Point3D p2 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMaxZ()); 4474 Point3D p3 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMinZ()); 4475 Point3D p4 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ()); 4476 Point3D p5 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMinZ()); 4477 Point3D p6 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ()); 4478 Point3D p7 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMinZ()); 4479 Point3D p8 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMaxZ()); 4480 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4481 } catch (NoninvertibleTransformException e) { 4482 return null; 4483 } 4484 } 4485 4486 /** 4487 * Transforms a point from the local coordinate space of this {@code Node} 4488 * into the coordinate space of its {@link javafx.stage.Screen}. 4489 * @param localX x coordinate of a point in Node's space 4490 * @param localY y coordinate of a point in Node's space 4491 * @return screen coordinates of the point or null if Node is not in a {@link Window} 4492 * @since JavaFX 8.0 4493 */ 4494 public Point2D localToScreen(double localX, double localY) { 4495 return localToScreen(localX, localY, 0.0); 4496 } 4497 4498 /** 4499 * Transforms a point from the local coordinate space of this {@code Node} 4500 * into the coordinate space of its {@link javafx.stage.Screen}. 4501 * @param localPoint a point in Node's space 4502 * @return screen coordinates of the point or null if Node is not in a {@link Window} 4503 * @since JavaFX 8.0 4504 */ 4505 public Point2D localToScreen(Point2D localPoint) { 4506 return localToScreen(localPoint.getX(), localPoint.getY()); 4507 } 4508 4509 /** 4510 * Transforms a point from the local coordinate space of this {@code Node} 4511 * into the coordinate space of its {@link javafx.stage.Screen}. 4512 * @param localX x coordinate of a point in Node's space 4513 * @param localY y coordinate of a point in Node's space 4514 * @param localZ z coordinate of a point in Node's space 4515 * @return screen coordinates of the point or null if Node is not in a {@link Window} 4516 * @since JavaFX 8.0 4517 */ 4518 public Point2D localToScreen(double localX, double localY, double localZ) { 4519 Scene scene = getScene(); 4520 if (scene == null) return null; 4521 Window window = scene.getWindow(); 4522 if (window == null) return null; 4523 4524 Point3D pt = localToScene(localX, localY, localZ); 4525 final SubScene subScene = getSubScene(); 4526 if (subScene != null) { 4527 pt = SceneUtils.subSceneToScene(subScene, pt); 4528 } 4529 final Point2D projection = CameraHelper.project( 4530 SceneHelper.getEffectiveCamera(getScene()), pt); 4531 4532 return new Point2D(projection.getX() + scene.getX() + window.getX(), 4533 projection.getY() + scene.getY() + window.getY()); 4534 } 4535 4536 /** 4537 * Transforms a point from the local coordinate space of this {@code Node} 4538 * into the coordinate space of its {@link javafx.stage.Screen}. 4539 * @param localPoint a point in Node's space 4540 * @return screen coordinates of the point or null if Node is not in a {@link Window} 4541 * @since JavaFX 8.0 4542 */ 4543 public Point2D localToScreen(Point3D localPoint) { 4544 return localToScreen(localPoint.getX(), localPoint.getY(), localPoint.getZ()); 4545 } 4546 4547 /** 4548 * Transforms a bounds from the local coordinate space of this 4549 * {@code Node} into the coordinate space of its {@link javafx.stage.Screen}. 4550 * @param localBounds bounds in Node's space 4551 * @return the bounds in screen coordinates or null if Node is not in a {@link Window} 4552 * @since JavaFX 8.0 4553 */ 4554 public Bounds localToScreen(Bounds localBounds) { 4555 final Point2D p1 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ()); 4556 final Point2D p2 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ()); 4557 final Point2D p3 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ()); 4558 final Point2D p4 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4559 final Point2D p5 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ()); 4560 final Point2D p6 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4561 final Point2D p7 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ()); 4562 final Point2D p8 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ()); 4563 4564 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4565 } 4566 4567 /** 4568 * Transforms a point from the local coordinate space of this {@code Node} 4569 * into the coordinate space of its scene. 4570 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates, 4571 * not that of {@link javafx.scene.Scene}. 4572 * @param localX x coordinate of a point in Node's space 4573 * @param localY y coordinate of a point in Node's space 4574 * @return scene coordinates of the point or null if Node is not in a {@link Window} 4575 */ 4576 public Point2D localToScene(double localX, double localY) { 4577 final com.sun.javafx.geom.Point2D tempPt = 4578 TempState.getInstance().point; 4579 tempPt.setLocation((float)localX, (float)localY); 4580 localToScene(tempPt); 4581 return new Point2D(tempPt.x, tempPt.y); 4582 } 4583 4584 /** 4585 * Transforms a point from the local coordinate space of this {@code Node} 4586 * into the coordinate space of its scene. 4587 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates, 4588 * not that of {@link javafx.scene.Scene}. 4589 * @param localPoint a point in Node's space 4590 * @return scene coordinates of the point or null if Node is not in a {@link Window} 4591 */ 4592 public Point2D localToScene(Point2D localPoint) { 4593 return localToScene(localPoint.getX(), localPoint.getY()); 4594 } 4595 4596 /** 4597 * Transforms a point from the local coordinate space of this {@code Node} 4598 * into the coordinate space of its scene. 4599 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates, 4600 * not that of {@link javafx.scene.Scene}. 4601 * @param localPoint a 3D point in Node's space 4602 * @return the transformed 3D point in Scene's space 4603 * @see #localToScene(javafx.geometry.Point3D, boolean) 4604 * @since JavaFX 8.0 4605 */ 4606 public Point3D localToScene(Point3D localPoint) { 4607 return localToScene(localPoint.getX(), localPoint.getY(), localPoint.getZ()); 4608 } 4609 4610 /** 4611 * Transforms a point from the local coordinate space of this {@code Node} 4612 * into the coordinate space of its scene. 4613 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates, 4614 * not that of {@link javafx.scene.Scene}. 4615 * @param x the x coordinate of a point in Node's space 4616 * @param y the y coordinate of a point in Node's space 4617 * @param z the z coordinate of a point in Node's space 4618 * @return the transformed 3D point in Scene's space 4619 * @see #localToScene(double, double, double, boolean) 4620 * @since JavaFX 8.0 4621 */ 4622 public Point3D localToScene(double x, double y, double z) { 4623 final com.sun.javafx.geom.Vec3d tempV3D = 4624 TempState.getInstance().vec3d; 4625 tempV3D.set(x, y, z); 4626 localToScene(tempV3D); 4627 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z); 4628 } 4629 4630 /** 4631 * Transforms a point from the local coordinate space of this {@code Node} 4632 * into the coordinate space of its scene. 4633 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the result point is in {@link Scene} coordinates 4634 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4635 * {@link #localToScene(javafx.geometry.Point3D)} 4636 * 4637 * @param localPoint the point in local coordinates 4638 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4639 * @return transformed point 4640 * 4641 * @see #localToScene(javafx.geometry.Point3D) 4642 * @since JavaFX 8u40 4643 */ 4644 public Point3D localToScene(Point3D localPoint, boolean rootScene) { 4645 Point3D pt = localToScene(localPoint); 4646 if (rootScene) { 4647 final SubScene subScene = getSubScene(); 4648 if (subScene != null) { 4649 pt = SceneUtils.subSceneToScene(subScene, pt); 4650 } 4651 } 4652 return pt; 4653 } 4654 4655 /** 4656 * Transforms a point from the local coordinate space of this {@code Node} 4657 * into the coordinate space of its scene. 4658 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the result point is in {@link Scene} coordinates 4659 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4660 * {@link #localToScene(double, double, double)} 4661 * 4662 * @param x the x coordinate of the point in local coordinates 4663 * @param y the y coordinate of the point in local coordinates 4664 * @param z the z coordinate of the point in local coordinates 4665 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4666 * @return transformed point 4667 * 4668 * @see #localToScene(double, double, double) 4669 * @since JavaFX 8u40 4670 */ 4671 public Point3D localToScene(double x, double y, double z, boolean rootScene) { 4672 return localToScene(new Point3D(x, y, z), rootScene); 4673 } 4674 4675 /** 4676 * Transforms a point from the local coordinate space of this {@code Node} 4677 * into the coordinate space of its scene. 4678 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the result point is in {@link Scene} coordinates 4679 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4680 * {@link #localToScene(javafx.geometry.Point2D)} 4681 * 4682 * @param localPoint the point in local coordinates 4683 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4684 * @return transformed point 4685 * 4686 * @see #localToScene(javafx.geometry.Point2D) 4687 * @since JavaFX 8u40 4688 */ 4689 public Point2D localToScene(Point2D localPoint, boolean rootScene) { 4690 if (!rootScene) { 4691 return localToScene(localPoint); 4692 } 4693 Point3D pt = localToScene(localPoint.getX(), localPoint.getY(), 0, rootScene); 4694 return new Point2D(pt.getX(), pt.getY()); 4695 } 4696 4697 /** 4698 * Transforms a point from the local coordinate space of this {@code Node} 4699 * into the coordinate space of its scene. 4700 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the result point is in {@link Scene} coordinates 4701 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4702 * {@link #localToScene(double, double)} 4703 * 4704 * @param x the x coordinate of the point in local coordinates 4705 * @param y the y coordinate of the point in local coordinates 4706 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4707 * @return transformed point 4708 * 4709 * @see #localToScene(double, double) 4710 * @since JavaFX 8u40 4711 */ 4712 public Point2D localToScene(double x, double y, boolean rootScene) { 4713 return localToScene(new Point2D(x, y), rootScene); 4714 } 4715 4716 /** 4717 * Transforms a bounds from the local coordinate space of this {@code Node} 4718 * into the coordinate space of its scene. 4719 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the result bounds are in {@link Scene} coordinates 4720 * of the Node returned by {@link #getScene()}. Othwerwise, the subscene coordinates are used, which is equivalent to calling 4721 * {@link #localToScene(javafx.geometry.Bounds)} 4722 * 4723 * @param localBounds the bounds in local coordinates 4724 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene 4725 * @return transformed bounds 4726 * 4727 * @see #localToScene(javafx.geometry.Bounds) 4728 * @since JavaFX 8u40 4729 */ 4730 public Bounds localToScene(Bounds localBounds, boolean rootScene) { 4731 if (!rootScene) { 4732 return localToScene(localBounds); 4733 } 4734 Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ(), true); 4735 Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ(), true); 4736 Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ(), true); 4737 Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ(), true); 4738 Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ(), true); 4739 Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ(), true); 4740 Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ(), true); 4741 Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ(), true); 4742 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4743 } 4744 4745 /** 4746 * Transforms a bounds from the local coordinate space of this 4747 * {@code Node} into the coordinate space of its scene. 4748 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates, 4749 * not that of {@link javafx.scene.Scene}. 4750 * @param localBounds bounds in Node's space 4751 * @return the bounds in the scene coordinates or null if Node is not in a {@link Window} 4752 * @see #localToScene(javafx.geometry.Bounds, boolean) 4753 */ 4754 public Bounds localToScene(Bounds localBounds) { 4755 // Do a quick update of localToParentTransform so that we can determine 4756 // if this tx is 2D transform 4757 updateLocalToParentTransform(); 4758 if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) { 4759 Point2D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY()); 4760 Point2D p2 = localToScene(localBounds.getMaxX(), localBounds.getMinY()); 4761 Point2D p3 = localToScene(localBounds.getMaxX(), localBounds.getMaxY()); 4762 Point2D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY()); 4763 4764 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4765 } 4766 Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ()); 4767 Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ()); 4768 Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ()); 4769 Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4770 Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ()); 4771 Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4772 Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ()); 4773 Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ()); 4774 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4775 4776 } 4777 4778 /** 4779 * Transforms a point from the coordinate space of the parent into the 4780 * local coordinate space of this {@code Node}. 4781 * @param parentX the x coordinate in Parent's space 4782 * @param parentY the y coordinate in Parent's space 4783 * @return the transformed 2D point in Node's space 4784 */ 4785 public Point2D parentToLocal(double parentX, double parentY) { 4786 final com.sun.javafx.geom.Point2D tempPt = 4787 TempState.getInstance().point; 4788 tempPt.setLocation((float)parentX, (float)parentY); 4789 try { 4790 parentToLocal(tempPt); 4791 } catch (NoninvertibleTransformException e) { 4792 return null; 4793 } 4794 return new Point2D(tempPt.x, tempPt.y); 4795 } 4796 4797 /** 4798 * Transforms a point from the coordinate space of the parent into the 4799 * local coordinate space of this {@code Node}. 4800 * @param parentPoint the 2D point in Parent's space 4801 * @return the transformed 2D point in Node's space 4802 */ 4803 public Point2D parentToLocal(Point2D parentPoint) { 4804 return parentToLocal(parentPoint.getX(), parentPoint.getY()); 4805 } 4806 4807 /** 4808 * Transforms a point from the coordinate space of the parent into the 4809 * local coordinate space of this {@code Node}. 4810 * @param parentPoint parentPoint the 3D point in Parent's space 4811 * @return the transformed 3D point in Node's space 4812 * @since JavaFX 8.0 4813 */ 4814 public Point3D parentToLocal(Point3D parentPoint) { 4815 return parentToLocal(parentPoint.getX(), parentPoint.getY(), parentPoint.getZ()); 4816 } 4817 4818 /** 4819 * Transforms a point from the coordinate space of the parent into the 4820 * local coordinate space of this {@code Node}. 4821 * @param parentX the x coordinate in Parent's space 4822 * @param parentY the y coordinate in Parent's space 4823 * @param parentZ the z coordinate in Parent's space 4824 * @return the transformed 3D point in Node's space 4825 * @since JavaFX 8.0 4826 */ 4827 public Point3D parentToLocal(double parentX, double parentY, double parentZ) { 4828 final com.sun.javafx.geom.Vec3d tempV3D = 4829 TempState.getInstance().vec3d; 4830 tempV3D.set(parentX, parentY, parentZ); 4831 try { 4832 parentToLocal(tempV3D); 4833 } catch (NoninvertibleTransformException e) { 4834 return null; 4835 } 4836 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z); 4837 } 4838 4839 /** 4840 * Transforms a rectangle from the coordinate space of the parent into the 4841 * local coordinate space of this {@code Node}. 4842 * @param parentBounds the bounds in Parent's space 4843 * @return the transformed bounds in Node's space 4844 */ 4845 public Bounds parentToLocal(Bounds parentBounds) { 4846 // Do a quick update of localToParentTransform so that we can determine 4847 // if this tx is 2D transform 4848 updateLocalToParentTransform(); 4849 if (localToParentTx.is2D() && (parentBounds.getMinZ() == 0) && (parentBounds.getMaxZ() == 0)) { 4850 Point2D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY()); 4851 Point2D p2 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY()); 4852 Point2D p3 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY()); 4853 Point2D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY()); 4854 4855 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4856 } 4857 Point3D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMinZ()); 4858 Point3D p2 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMaxZ()); 4859 Point3D p3 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMinZ()); 4860 Point3D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMaxZ()); 4861 Point3D p5 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMinZ()); 4862 Point3D p6 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMaxZ()); 4863 Point3D p7 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMinZ()); 4864 Point3D p8 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMaxZ()); 4865 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4866 } 4867 4868 /** 4869 * Transforms a point from the local coordinate space of this {@code Node} 4870 * into the coordinate space of its parent. 4871 * @param localX the x coordinate of the point in Node's space 4872 * @param localY the y coordinate of the point in Node's space 4873 * @return the transformed 2D point in Parent's space 4874 */ 4875 public Point2D localToParent(double localX, double localY) { 4876 final com.sun.javafx.geom.Point2D tempPt = 4877 TempState.getInstance().point; 4878 tempPt.setLocation((float)localX, (float)localY); 4879 localToParent(tempPt); 4880 return new Point2D(tempPt.x, tempPt.y); 4881 } 4882 4883 /** 4884 * Transforms a point from the local coordinate space of this {@code Node} 4885 * into the coordinate space of its parent. 4886 * @param localPoint the 2D point in Node's space 4887 * @return the transformed 2D point in Parent's space 4888 */ 4889 public Point2D localToParent(Point2D localPoint) { 4890 return localToParent(localPoint.getX(), localPoint.getY()); 4891 } 4892 4893 /** 4894 * Transforms a point from the local coordinate space of this {@code Node} 4895 * into the coordinate space of its parent. 4896 * @param localPoint the 3D point in Node's space 4897 * @return the transformed 3D point in Parent's space 4898 * @since JavaFX 8.0 4899 */ 4900 public Point3D localToParent(Point3D localPoint) { 4901 return localToParent(localPoint.getX(), localPoint.getY(), localPoint.getZ()); 4902 } 4903 4904 /** 4905 * Transforms a point from the local coordinate space of this {@code Node} 4906 * into the coordinate space of its parent. 4907 * @param x the x coordinate of the point in Node's space 4908 * @param y the y coordinate of the point in Node's space 4909 * @param z the z coordinate of the point in Node's space 4910 * @return the transformed 3D point in Parent's space 4911 * @since JavaFX 8.0 4912 */ 4913 public Point3D localToParent(double x, double y, double z) { 4914 final com.sun.javafx.geom.Vec3d tempV3D = 4915 TempState.getInstance().vec3d; 4916 tempV3D.set(x, y, z); 4917 localToParent(tempV3D); 4918 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z); 4919 } 4920 4921 /** 4922 * Transforms a bounds from the local coordinate space of this 4923 * {@code Node} into the coordinate space of its parent. 4924 * @param localBounds the bounds in Node's space 4925 * @return the transformed bounds in Parent's space 4926 */ 4927 public Bounds localToParent(Bounds localBounds) { 4928 // Do a quick update of localToParentTransform so that we can determine 4929 // if this tx is 2D transform 4930 updateLocalToParentTransform(); 4931 if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) { 4932 Point2D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY()); 4933 Point2D p2 = localToParent(localBounds.getMaxX(), localBounds.getMinY()); 4934 Point2D p3 = localToParent(localBounds.getMaxX(), localBounds.getMaxY()); 4935 Point2D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY()); 4936 4937 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 4938 } 4939 Point3D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ()); 4940 Point3D p2 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ()); 4941 Point3D p3 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ()); 4942 Point3D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4943 Point3D p5 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ()); 4944 Point3D p6 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ()); 4945 Point3D p7 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ()); 4946 Point3D p8 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ()); 4947 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 4948 } 4949 4950 /** 4951 * Copy the localToParent transform into specified transform. 4952 */ 4953 BaseTransform getLocalToParentTransform(BaseTransform tx) { 4954 updateLocalToParentTransform(); 4955 tx.setTransform(localToParentTx); 4956 return tx; 4957 } 4958 4959 /* 4960 * Currently used only by PathTransition 4961 */ 4962 final BaseTransform getLeafTransform() { 4963 return getLocalToParentTransform(TempState.getInstance().leafTx); 4964 } 4965 4966 /* 4967 * Invoked whenever the transforms[] ObservableList changes, or by the transforms 4968 * in that ObservableList whenever they are changed. 4969 * 4970 * Note: This method MUST only be called via its accessor method. 4971 */ 4972 private void doTransformsChanged() { 4973 if (!transformDirty) { 4974 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM); 4975 transformDirty = true; 4976 transformedBoundsChanged(); 4977 } 4978 invalidateLocalToParentTransform(); 4979 invalidateLocalToSceneTransform(); 4980 } 4981 4982 final double getPivotX() { 4983 final Bounds bounds = getLayoutBounds(); 4984 return bounds.getMinX() + bounds.getWidth()/2; 4985 } 4986 4987 final double getPivotY() { 4988 final Bounds bounds = getLayoutBounds(); 4989 return bounds.getMinY() + bounds.getHeight()/2; 4990 } 4991 4992 final double getPivotZ() { 4993 final Bounds bounds = getLayoutBounds(); 4994 return bounds.getMinZ() + bounds.getDepth()/2; 4995 } 4996 4997 /** 4998 * This helper function will update the transform matrix on the peer based 4999 * on the "complete" transform for this node. 5000 */ 5001 void updateLocalToParentTransform() { 5002 if (transformDirty) { 5003 localToParentTx.setToIdentity(); 5004 5005 boolean mirror = false; 5006 double mirroringCenter = 0; 5007 if (hasMirroring()) { 5008 final Scene sceneValue = getScene(); 5009 if ((sceneValue != null) && (sceneValue.getRoot() == this)) { 5010 // handle scene mirroring in this branch 5011 // (must be the last transformation) 5012 mirroringCenter = sceneValue.getWidth() / 2; 5013 if (mirroringCenter == 0.0) { 5014 mirroringCenter = getPivotX(); 5015 } 5016 5017 localToParentTx = localToParentTx.deriveWithTranslation( 5018 mirroringCenter, 0.0); 5019 localToParentTx = localToParentTx.deriveWithScale( 5020 -1.0, 1.0, 1.0); 5021 localToParentTx = localToParentTx.deriveWithTranslation( 5022 -mirroringCenter, 0.0); 5023 } else { 5024 // mirror later 5025 mirror = true; 5026 mirroringCenter = getPivotX(); 5027 } 5028 } 5029 5030 if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) { 5031 // recompute pivotX, pivotY and pivotZ 5032 double pivotX = getPivotX(); 5033 double pivotY = getPivotY(); 5034 double pivotZ = getPivotZ(); 5035 5036 localToParentTx = localToParentTx.deriveWithTranslation( 5037 getTranslateX() + getLayoutX() + pivotX, 5038 getTranslateY() + getLayoutY() + pivotY, 5039 getTranslateZ() + pivotZ); 5040 localToParentTx = localToParentTx.deriveWithRotation( 5041 Math.toRadians(getRotate()), getRotationAxis().getX(), 5042 getRotationAxis().getY(), getRotationAxis().getZ()); 5043 localToParentTx = localToParentTx.deriveWithScale( 5044 getScaleX(), getScaleY(), getScaleZ()); 5045 localToParentTx = localToParentTx.deriveWithTranslation( 5046 -pivotX, -pivotY, -pivotZ); 5047 } else { 5048 localToParentTx = localToParentTx.deriveWithTranslation( 5049 getTranslateX() + getLayoutX(), 5050 getTranslateY() + getLayoutY(), 5051 getTranslateZ()); 5052 } 5053 5054 if (hasTransforms()) { 5055 for (Transform t : getTransforms()) { 5056 localToParentTx = TransformHelper.derive(t, localToParentTx); 5057 } 5058 } 5059 5060 // Check to see whether the node requires mirroring 5061 if (mirror) { 5062 localToParentTx = localToParentTx.deriveWithTranslation( 5063 mirroringCenter, 0); 5064 localToParentTx = localToParentTx.deriveWithScale( 5065 -1.0, 1.0, 1.0); 5066 localToParentTx = localToParentTx.deriveWithTranslation( 5067 -mirroringCenter, 0); 5068 } 5069 5070 transformDirty = false; 5071 } 5072 } 5073 5074 /** 5075 * Transforms in place the specified point from parent coords to local 5076 * coords. Made package private for the sake of testing. 5077 */ 5078 void parentToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException { 5079 updateLocalToParentTransform(); 5080 localToParentTx.inverseTransform(pt, pt); 5081 } 5082 5083 void parentToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException { 5084 updateLocalToParentTransform(); 5085 localToParentTx.inverseTransform(pt, pt); 5086 } 5087 5088 void sceneToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException { 5089 if (getParent() != null) { 5090 getParent().sceneToLocal(pt); 5091 } 5092 parentToLocal(pt); 5093 } 5094 5095 void sceneToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException { 5096 if (getParent() != null) { 5097 getParent().sceneToLocal(pt); 5098 } 5099 parentToLocal(pt); 5100 } 5101 5102 void localToScene(com.sun.javafx.geom.Point2D pt) { 5103 localToParent(pt); 5104 if (getParent() != null) { 5105 getParent().localToScene(pt); 5106 } 5107 } 5108 5109 void localToScene(com.sun.javafx.geom.Vec3d pt) { 5110 localToParent(pt); 5111 if (getParent() != null) { 5112 getParent().localToScene(pt); 5113 } 5114 } 5115 5116 /*************************************************************************** 5117 * * 5118 * Mouse event related APIs * 5119 * * 5120 **************************************************************************/ 5121 5122 /** 5123 * Transforms in place the specified point from local coords to parent 5124 * coords. Made package private for the sake of testing. 5125 */ 5126 void localToParent(com.sun.javafx.geom.Point2D pt) { 5127 updateLocalToParentTransform(); 5128 localToParentTx.transform(pt, pt); 5129 } 5130 5131 void localToParent(com.sun.javafx.geom.Vec3d pt) { 5132 updateLocalToParentTransform(); 5133 localToParentTx.transform(pt, pt); 5134 } 5135 5136 /* 5137 * Finds a top-most child node that contains the given local coordinates. 5138 * 5139 * The result argument is used for storing the picking result. 5140 * 5141 * Note: This method MUST only be called via its accessor method. 5142 */ 5143 private void doPickNodeLocal(PickRay localPickRay, PickResultChooser result) { 5144 intersects(localPickRay, result); 5145 } 5146 5147 /* 5148 * Finds a top-most child node that intersects the given ray. 5149 * 5150 * The result argument is used for storing the picking result. 5151 */ 5152 final void pickNode(PickRay pickRay, PickResultChooser result) { 5153 5154 // In some conditions we can omit picking this node or subgraph 5155 if (!isVisible() || isDisable() || isMouseTransparent()) { 5156 return; 5157 } 5158 5159 final Vec3d o = pickRay.getOriginNoClone(); 5160 final double ox = o.x; 5161 final double oy = o.y; 5162 final double oz = o.z; 5163 final Vec3d d = pickRay.getDirectionNoClone(); 5164 final double dx = d.x; 5165 final double dy = d.y; 5166 final double dz = d.z; 5167 5168 updateLocalToParentTransform(); 5169 try { 5170 localToParentTx.inverseTransform(o, o); 5171 localToParentTx.inverseDeltaTransform(d, d); 5172 5173 // Delegate to a function which can be overridden by subclasses which 5174 // actually does the pick. The implementation is markedly different 5175 // for leaf nodes vs. parent nodes vs. region nodes. 5176 NodeHelper.pickNodeLocal(this, pickRay, result); 5177 } catch (NoninvertibleTransformException e) { 5178 // in this case we just don't pick anything 5179 } 5180 5181 pickRay.setOrigin(ox, oy, oz); 5182 pickRay.setDirection(dx, dy, dz); 5183 } 5184 5185 /* 5186 * Returns {@code true} if the given ray (start, dir), specified in the 5187 * local coordinate space of this {@code Node}, intersects the 5188 * shape of this {@code Node}. Note that this method does not take visibility 5189 * into account; the test is based on the geometry of this {@code Node} only. 5190 * <p> 5191 * The pickResult is updated if the found intersection is closer than 5192 * the currently held one. 5193 * <p> 5194 * Note that this is a conditional feature. See 5195 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 5196 * for more information. 5197 */ 5198 final boolean intersects(PickRay pickRay, PickResultChooser pickResult) { 5199 double boundsDistance = intersectsBounds(pickRay); 5200 if (!Double.isNaN(boundsDistance)) { 5201 if (isPickOnBounds()) { 5202 if (pickResult != null) { 5203 pickResult.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance)); 5204 } 5205 return true; 5206 } else { 5207 return NodeHelper.computeIntersects(this, pickRay, pickResult); 5208 } 5209 } 5210 return false; 5211 } 5212 5213 /* 5214 * Computes the intersection of the pickRay with this node. 5215 * The pickResult argument is updated if the found intersection 5216 * is closer than the passed one. On the other hand, the return value 5217 * specifies whether the intersection exists, regardless of its comparison 5218 * with the given pickResult. 5219 */ 5220 private boolean doComputeIntersects(PickRay pickRay, PickResultChooser pickResult) { 5221 double origZ = pickRay.getOriginNoClone().z; 5222 double dirZ = pickRay.getDirectionNoClone().z; 5223 // Handle the case where pickRay is almost parallel to the Z-plane 5224 if (almostZero(dirZ)) { 5225 return false; 5226 } 5227 double t = -origZ / dirZ; 5228 if (t < pickRay.getNearClip() || t > pickRay.getFarClip()) { 5229 return false; 5230 } 5231 double x = pickRay.getOriginNoClone().x + (pickRay.getDirectionNoClone().x * t); 5232 double y = pickRay.getOriginNoClone().y + (pickRay.getDirectionNoClone().y * t); 5233 5234 if (contains((float) x, (float) y)) { 5235 if (pickResult != null) { 5236 pickResult.offer(this, t, PickResultChooser.computePoint(pickRay, t)); 5237 } 5238 return true; 5239 } 5240 return false; 5241 } 5242 5243 /* 5244 * Computes the intersection of the pickRay with the bounds of this node. 5245 * The return value is the distance between the camera and the intersection 5246 * point, measured in pickRay direction magnitudes. If there is 5247 * no intersection, it returns NaN. 5248 * 5249 * @param pickRay The pick ray 5250 * @return Distance of the intersection point, a NaN if there 5251 * is no intersection 5252 */ 5253 final double intersectsBounds(PickRay pickRay) { 5254 5255 final Vec3d dir = pickRay.getDirectionNoClone(); 5256 double tmin, tmax; 5257 5258 final Vec3d origin = pickRay.getOriginNoClone(); 5259 final double originX = origin.x; 5260 final double originY = origin.y; 5261 final double originZ = origin.z; 5262 5263 final TempState tempState = TempState.getInstance(); 5264 BaseBounds tempBounds = tempState.bounds; 5265 5266 tempBounds = getLocalBounds(tempBounds, 5267 BaseTransform.IDENTITY_TRANSFORM); 5268 5269 if (dir.x == 0.0 && dir.y == 0.0) { 5270 // fast path for the usual 2D picking 5271 5272 if (dir.z == 0.0) { 5273 return Double.NaN; 5274 } 5275 5276 if (originX < tempBounds.getMinX() || 5277 originX > tempBounds.getMaxX() || 5278 originY < tempBounds.getMinY() || 5279 originY > tempBounds.getMaxY()) { 5280 return Double.NaN; 5281 } 5282 5283 final double invDirZ = 1.0 / dir.z; 5284 final boolean signZ = invDirZ < 0.0; 5285 5286 final double minZ = tempBounds.getMinZ(); 5287 final double maxZ = tempBounds.getMaxZ(); 5288 tmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ; 5289 tmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ; 5290 5291 } else if (tempBounds.getDepth() == 0.0) { 5292 // fast path for 3D picking of 2D bounds 5293 5294 if (almostZero(dir.z)) { 5295 return Double.NaN; 5296 } 5297 5298 final double t = (tempBounds.getMinZ() - originZ) / dir.z; 5299 final double x = originX + (dir.x * t); 5300 final double y = originY + (dir.y * t); 5301 5302 if (x < tempBounds.getMinX() || 5303 x > tempBounds.getMaxX() || 5304 y < tempBounds.getMinY() || 5305 y > tempBounds.getMaxY()) { 5306 return Double.NaN; 5307 } 5308 5309 tmin = tmax = t; 5310 5311 } else { 5312 5313 final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x); 5314 final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y); 5315 final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z); 5316 final boolean signX = invDirX < 0.0; 5317 final boolean signY = invDirY < 0.0; 5318 final boolean signZ = invDirZ < 0.0; 5319 final double minX = tempBounds.getMinX(); 5320 final double minY = tempBounds.getMinY(); 5321 final double maxX = tempBounds.getMaxX(); 5322 final double maxY = tempBounds.getMaxY(); 5323 5324 tmin = Double.NEGATIVE_INFINITY; 5325 tmax = Double.POSITIVE_INFINITY; 5326 if (Double.isInfinite(invDirX)) { 5327 if (minX <= originX && maxX >= originX) { 5328 // move on, we are inside for the whole length 5329 } else { 5330 return Double.NaN; 5331 } 5332 } else { 5333 tmin = ((signX ? maxX : minX) - originX) * invDirX; 5334 tmax = ((signX ? minX : maxX) - originX) * invDirX; 5335 } 5336 5337 if (Double.isInfinite(invDirY)) { 5338 if (minY <= originY && maxY >= originY) { 5339 // move on, we are inside for the whole length 5340 } else { 5341 return Double.NaN; 5342 } 5343 } else { 5344 final double tymin = ((signY ? maxY : minY) - originY) * invDirY; 5345 final double tymax = ((signY ? minY : maxY) - originY) * invDirY; 5346 5347 if ((tmin > tymax) || (tymin > tmax)) { 5348 return Double.NaN; 5349 } 5350 if (tymin > tmin) { 5351 tmin = tymin; 5352 } 5353 if (tymax < tmax) { 5354 tmax = tymax; 5355 } 5356 } 5357 5358 final double minZ = tempBounds.getMinZ(); 5359 final double maxZ = tempBounds.getMaxZ(); 5360 if (Double.isInfinite(invDirZ)) { 5361 if (minZ <= originZ && maxZ >= originZ) { 5362 // move on, we are inside for the whole length 5363 } else { 5364 return Double.NaN; 5365 } 5366 } else { 5367 final double tzmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ; 5368 final double tzmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ; 5369 5370 if ((tmin > tzmax) || (tzmin > tmax)) { 5371 return Double.NaN; 5372 } 5373 if (tzmin > tmin) { 5374 tmin = tzmin; 5375 } 5376 if (tzmax < tmax) { 5377 tmax = tzmax; 5378 } 5379 } 5380 } 5381 5382 // For clip we use following semantics: pick the node normally 5383 // if there is an intersection with the clip node. We don't consider 5384 // clip node distance. 5385 Node clip = getClip(); 5386 if (clip != null 5387 // FIXME: All 3D picking is currently ignored by rendering. 5388 // Until this is fixed or defined differently (RT-28510), 5389 // we follow this behavior. 5390 && !(this instanceof Shape3D) && !(clip instanceof Shape3D)) { 5391 final double dirX = dir.x; 5392 final double dirY = dir.y; 5393 final double dirZ = dir.z; 5394 5395 clip.updateLocalToParentTransform(); 5396 5397 boolean hitClip = true; 5398 try { 5399 clip.localToParentTx.inverseTransform(origin, origin); 5400 clip.localToParentTx.inverseDeltaTransform(dir, dir); 5401 } catch (NoninvertibleTransformException e) { 5402 hitClip = false; 5403 } 5404 hitClip = hitClip && clip.intersects(pickRay, null); 5405 pickRay.setOrigin(originX, originY, originZ); 5406 pickRay.setDirection(dirX, dirY, dirZ); 5407 5408 if (!hitClip) { 5409 return Double.NaN; 5410 } 5411 } 5412 5413 if (Double.isInfinite(tmin) || Double.isNaN(tmin)) { 5414 // We've got a nonsense pick ray or bounds. 5415 return Double.NaN; 5416 } 5417 5418 final double minDistance = pickRay.getNearClip(); 5419 final double maxDistance = pickRay.getFarClip(); 5420 if (tmin < minDistance) { 5421 if (tmax >= minDistance) { 5422 // we are inside bounds 5423 return 0.0; 5424 } else { 5425 return Double.NaN; 5426 } 5427 } else if (tmin > maxDistance) { 5428 return Double.NaN; 5429 } 5430 5431 return tmin; 5432 } 5433 5434 5435 // Good to find a home for commonly use util. code such as EPS. 5436 // and almostZero. This code currently defined in multiple places, 5437 // such as Affine3D and GeneralTransform3D. 5438 private static final double EPSILON_ABSOLUTE = 1.0e-5; 5439 5440 static boolean almostZero(double a) { 5441 return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE)); 5442 } 5443 5444 /*************************************************************************** 5445 * * 5446 * viewOrder property handling * 5447 * * 5448 **************************************************************************/ 5449 5450 /** 5451 * Defines the rendering and picking order of this {@code Node} within its 5452 * parent. 5453 * <p> 5454 * This property is used to alter the rendering and picking order of a node 5455 * within its parent without reordering the parent's {@code children} list. 5456 * For example, this can be used as a more efficient way to implement 5457 * transparency sorting. To do this, an application can assign the viewOrder 5458 * value of each node to the computed distance between that node and the 5459 * viewer. 5460 * </p> 5461 * <p> 5462 * The parent will traverse its {@code children} in decreasing 5463 * {@code viewOrder} order. This means that a child with a lower 5464 * {@code viewOrder} will be in front of a child with a higher 5465 * {@code viewOrder}. If two children have the same {@code viewOrder}, the 5466 * parent will traverse them in the order they appear in the parent's 5467 * {@code children} list. 5468 * </p> 5469 * <p> 5470 * However, {@code viewOrder} does not alter the layout and focus traversal 5471 * order of this Node within its parent. A parent always traverses its 5472 * {@code children} list in order when doing layout or focus traversal. 5473 * </p> 5474 * 5475 * @return the view order for this {@code Node} 5476 * @defaultValue 0.0 5477 * 5478 * @since 9 5479 */ 5480 public final DoubleProperty viewOrderProperty() { 5481 return getMiscProperties().viewOrderProperty(); 5482 } 5483 5484 public final void setViewOrder(double value) { 5485 viewOrderProperty().set(value); 5486 } 5487 5488 public final double getViewOrder() { 5489 return (miscProperties == null) ? DEFAULT_VIEW_ORDER 5490 : miscProperties.getViewOrder(); 5491 } 5492 5493 /*************************************************************************** 5494 * * 5495 * Transformations * 5496 * * 5497 **************************************************************************/ 5498 /** 5499 * Defines the ObservableList of {@link javafx.scene.transform.Transform} objects 5500 * to be applied to this {@code Node}. This ObservableList of transforms is applied 5501 * before {@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #scaleXProperty scaleX}, and 5502 * {@link #scaleYProperty scaleY}, {@link #rotateProperty rotate} transforms. 5503 * 5504 * @return the transforms for this {@code Node} 5505 * @defaultValue empty 5506 */ 5507 public final ObservableList<Transform> getTransforms() { 5508 return transformsProperty(); 5509 } 5510 5511 private ObservableList<Transform> transformsProperty() { 5512 return getNodeTransformation().getTransforms(); 5513 } 5514 5515 public final void setTranslateX(double value) { 5516 translateXProperty().set(value); 5517 } 5518 5519 public final double getTranslateX() { 5520 return (nodeTransformation == null) 5521 ? DEFAULT_TRANSLATE_X 5522 : nodeTransformation.getTranslateX(); 5523 } 5524 5525 /** 5526 * Defines the x coordinate of the translation that is added to this {@code Node}'s 5527 * transform. 5528 * <p> 5529 * The node's final translation will be computed as {@link #layoutXProperty layoutX} + {@code translateX}, 5530 * where {@code layoutX} establishes the node's stable position and {@code translateX} 5531 * optionally makes dynamic adjustments to that position. 5532 *<p> 5533 * This variable can be used to alter the location of a node without disturbing 5534 * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location. 5535 * 5536 * @return the translateX for this {@code Node} 5537 * @defaultValue 0 5538 */ 5539 public final DoubleProperty translateXProperty() { 5540 return getNodeTransformation().translateXProperty(); 5541 } 5542 5543 public final void setTranslateY(double value) { 5544 translateYProperty().set(value); 5545 } 5546 5547 public final double getTranslateY() { 5548 return (nodeTransformation == null) 5549 ? DEFAULT_TRANSLATE_Y 5550 : nodeTransformation.getTranslateY(); 5551 } 5552 5553 /** 5554 * Defines the y coordinate of the translation that is added to this {@code Node}'s 5555 * transform. 5556 * <p> 5557 * The node's final translation will be computed as {@link #layoutYProperty layoutY} + {@code translateY}, 5558 * where {@code layoutY} establishes the node's stable position and {@code translateY} 5559 * optionally makes dynamic adjustments to that position. 5560 *<p> 5561 * This variable can be used to alter the location of a node without disturbing 5562 * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location. 5563 * 5564 * @return the translateY for this {@code Node} 5565 * @defaultValue 0 5566 */ 5567 public final DoubleProperty translateYProperty() { 5568 return getNodeTransformation().translateYProperty(); 5569 } 5570 5571 public final void setTranslateZ(double value) { 5572 translateZProperty().set(value); 5573 } 5574 5575 public final double getTranslateZ() { 5576 return (nodeTransformation == null) 5577 ? DEFAULT_TRANSLATE_Z 5578 : nodeTransformation.getTranslateZ(); 5579 } 5580 5581 /** 5582 * Defines the Z coordinate of the translation that is added to the 5583 * transformed coordinates of this {@code Node}. This value will be added 5584 * to any translation defined by the {@code transforms} ObservableList and 5585 * {@code layoutZ}. 5586 *<p> 5587 * This variable can be used to alter the location of a Node without 5588 * disturbing its layout bounds, which makes it useful for animating a 5589 * node's location. 5590 * <p> 5591 * Note that this is a conditional feature. See 5592 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 5593 * for more information. 5594 * 5595 * @return the translateZ for this {@code Node} 5596 * @defaultValue 0 5597 */ 5598 public final DoubleProperty translateZProperty() { 5599 return getNodeTransformation().translateZProperty(); 5600 } 5601 5602 public final void setScaleX(double value) { 5603 scaleXProperty().set(value); 5604 } 5605 5606 public final double getScaleX() { 5607 return (nodeTransformation == null) ? DEFAULT_SCALE_X 5608 : nodeTransformation.getScaleX(); 5609 } 5610 5611 /** 5612 * Defines the factor by which coordinates are scaled about the center of the 5613 * object along the X axis of this {@code Node}. This is used to stretch or 5614 * animate the node either manually or by using an animation. 5615 * <p> 5616 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by 5617 * default, which makes it ideal for scaling the entire node after 5618 * all effects and transforms have been taken into account. 5619 * <p> 5620 * The pivot point about which the scale occurs is the center of the 5621 * untransformed {@link #layoutBoundsProperty layoutBounds}. 5622 * 5623 * @return the scaleX for this {@code Node} 5624 * @defaultValue 1.0 5625 */ 5626 public final DoubleProperty scaleXProperty() { 5627 return getNodeTransformation().scaleXProperty(); 5628 } 5629 5630 public final void setScaleY(double value) { 5631 scaleYProperty().set(value); 5632 } 5633 5634 public final double getScaleY() { 5635 return (nodeTransformation == null) ? DEFAULT_SCALE_Y 5636 : nodeTransformation.getScaleY(); 5637 } 5638 5639 /** 5640 * Defines the factor by which coordinates are scaled about the center of the 5641 * object along the Y axis of this {@code Node}. This is used to stretch or 5642 * animate the node either manually or by using an animation. 5643 * <p> 5644 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by 5645 * default, which makes it ideal for scaling the entire node after 5646 * all effects and transforms have been taken into account. 5647 * <p> 5648 * The pivot point about which the scale occurs is the center of the 5649 * untransformed {@link #layoutBoundsProperty layoutBounds}. 5650 * 5651 * @return the scaleY for this {@code Node} 5652 * @defaultValue 1.0 5653 */ 5654 public final DoubleProperty scaleYProperty() { 5655 return getNodeTransformation().scaleYProperty(); 5656 } 5657 5658 public final void setScaleZ(double value) { 5659 scaleZProperty().set(value); 5660 } 5661 5662 public final double getScaleZ() { 5663 return (nodeTransformation == null) ? DEFAULT_SCALE_Z 5664 : nodeTransformation.getScaleZ(); 5665 } 5666 5667 /** 5668 * Defines the factor by which coordinates are scaled about the center of the 5669 * object along the Z axis of this {@code Node}. This is used to stretch or 5670 * animate the node either manually or by using an animation. 5671 * <p> 5672 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by 5673 * default, which makes it ideal for scaling the entire node after 5674 * all effects and transforms have been taken into account. 5675 * <p> 5676 * The pivot point about which the scale occurs is the center of the 5677 * rectangular bounds formed by taking {@link #boundsInLocalProperty boundsInLocal} and applying 5678 * all the transforms in the {@link #getTransforms transforms} ObservableList. 5679 * <p> 5680 * Note that this is a conditional feature. See 5681 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 5682 * for more information. 5683 * 5684 * @return the scaleZ for this {@code Node} 5685 * @defaultValue 1.0 5686 */ 5687 public final DoubleProperty scaleZProperty() { 5688 return getNodeTransformation().scaleZProperty(); 5689 } 5690 5691 public final void setRotate(double value) { 5692 rotateProperty().set(value); 5693 } 5694 5695 public final double getRotate() { 5696 return (nodeTransformation == null) ? DEFAULT_ROTATE 5697 : nodeTransformation.getRotate(); 5698 } 5699 5700 /** 5701 * Defines the angle of rotation about the {@code Node}'s center, measured in 5702 * degrees. This is used to rotate the {@code Node}. 5703 * <p> 5704 * This rotation factor is not included in {@link #layoutBoundsProperty layoutBounds} by 5705 * default, which makes it ideal for rotating the entire node after 5706 * all effects and transforms have been taken into account. 5707 * <p> 5708 * The pivot point about which the rotation occurs is the center of the 5709 * untransformed {@link #layoutBoundsProperty layoutBounds}. 5710 * <p> 5711 * Note that because the pivot point is computed as the center of this 5712 * {@code Node}'s layout bounds, any change to the layout bounds will cause 5713 * the pivot point to change, which can move the object. For a leaf node, 5714 * any change to the geometry will cause the layout bounds to change. 5715 * For a group node, any change to any of its children, including a 5716 * change in a child's geometry, clip, effect, position, orientation, or 5717 * scale, will cause the group's layout bounds to change. If this movement 5718 * of the pivot point is not 5719 * desired, applications should instead use the Node's {@link #getTransforms transforms} 5720 * ObservableList, and add a {@link javafx.scene.transform.Rotate} transform, 5721 * which has a user-specifiable pivot point. 5722 * 5723 * @return the rotate for this {@code Node} 5724 * @defaultValue 0.0 5725 */ 5726 public final DoubleProperty rotateProperty() { 5727 return getNodeTransformation().rotateProperty(); 5728 } 5729 5730 public final void setRotationAxis(Point3D value) { 5731 rotationAxisProperty().set(value); 5732 } 5733 5734 public final Point3D getRotationAxis() { 5735 return (nodeTransformation == null) 5736 ? DEFAULT_ROTATION_AXIS 5737 : nodeTransformation.getRotationAxis(); 5738 } 5739 5740 /** 5741 * Defines the axis of rotation of this {@code Node}. 5742 * <p> 5743 * Note that this is a conditional feature. See 5744 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} 5745 * for more information. 5746 * 5747 * @return the rotationAxis for this {@code Node} 5748 * @defaultValue Rotate.Z_AXIS 5749 */ 5750 public final ObjectProperty<Point3D> rotationAxisProperty() { 5751 return getNodeTransformation().rotationAxisProperty(); 5752 } 5753 5754 /** 5755 * An affine transform that holds the computed local-to-parent transform. 5756 * This is the concatenation of all transforms in this node, including all 5757 * of the convenience transforms. 5758 * @return the localToParent transform for this {@code Node} 5759 * @since JavaFX 2.2 5760 */ 5761 public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() { 5762 return getNodeTransformation().localToParentTransformProperty(); 5763 } 5764 5765 private void invalidateLocalToParentTransform() { 5766 if (nodeTransformation != null) { 5767 nodeTransformation.invalidateLocalToParentTransform(); 5768 } 5769 } 5770 5771 public final Transform getLocalToParentTransform() { 5772 return localToParentTransformProperty().get(); 5773 } 5774 5775 /** 5776 * An affine transform that holds the computed local-to-scene transform. 5777 * This is the concatenation of all transforms in this node's parents and 5778 * in this node, including all of the convenience transforms up to the root. 5779 * If this node is in a {@link javafx.scene.SubScene}, this property represents 5780 * transforms up to the subscene, not the root scene. 5781 * 5782 * <p> 5783 * Note that when you register a listener or a binding to this property, 5784 * it needs to listen for invalidation on all its parents to the root node. 5785 * This means that registering a listener on this 5786 * property on many nodes may negatively affect performance of 5787 * transformation changes in their common parents. 5788 * </p> 5789 * 5790 * @return the localToScene transform for this {@code Node} 5791 * @since JavaFX 2.2 5792 */ 5793 public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() { 5794 return getNodeTransformation().localToSceneTransformProperty(); 5795 } 5796 5797 private void invalidateLocalToSceneTransform() { 5798 if (nodeTransformation != null) { 5799 nodeTransformation.invalidateLocalToSceneTransform(); 5800 } 5801 } 5802 5803 public final Transform getLocalToSceneTransform() { 5804 return localToSceneTransformProperty().get(); 5805 } 5806 5807 private NodeTransformation nodeTransformation; 5808 5809 private NodeTransformation getNodeTransformation() { 5810 if (nodeTransformation == null) { 5811 nodeTransformation = new NodeTransformation(); 5812 } 5813 5814 return nodeTransformation; 5815 } 5816 5817 private boolean hasTransforms() { 5818 return (nodeTransformation != null) 5819 && nodeTransformation.hasTransforms(); 5820 } 5821 5822 // for tests only 5823 Transform getCurrentLocalToSceneTransformState() { 5824 if (nodeTransformation == null || 5825 nodeTransformation.localToSceneTransform == null) { 5826 return null; 5827 } 5828 5829 return nodeTransformation.localToSceneTransform.transform; 5830 } 5831 5832 private static final double DEFAULT_TRANSLATE_X = 0; 5833 private static final double DEFAULT_TRANSLATE_Y = 0; 5834 private static final double DEFAULT_TRANSLATE_Z = 0; 5835 private static final double DEFAULT_SCALE_X = 1; 5836 private static final double DEFAULT_SCALE_Y = 1; 5837 private static final double DEFAULT_SCALE_Z = 1; 5838 private static final double DEFAULT_ROTATE = 0; 5839 private static final Point3D DEFAULT_ROTATION_AXIS = Rotate.Z_AXIS; 5840 5841 private final class NodeTransformation { 5842 private DoubleProperty translateX; 5843 private DoubleProperty translateY; 5844 private DoubleProperty translateZ; 5845 private DoubleProperty scaleX; 5846 private DoubleProperty scaleY; 5847 private DoubleProperty scaleZ; 5848 private DoubleProperty rotate; 5849 private ObjectProperty<Point3D> rotationAxis; 5850 private ObservableList<Transform> transforms; 5851 private LazyTransformProperty localToParentTransform; 5852 private LazyTransformProperty localToSceneTransform; 5853 private int listenerReasons = 0; 5854 private InvalidationListener localToSceneInvLstnr; 5855 5856 private InvalidationListener getLocalToSceneInvalidationListener() { 5857 if (localToSceneInvLstnr == null) { 5858 localToSceneInvLstnr = observable -> invalidateLocalToSceneTransform(); 5859 } 5860 return localToSceneInvLstnr; 5861 } 5862 5863 public void incListenerReasons() { 5864 if (listenerReasons == 0) { 5865 Node n = Node.this.getParent(); 5866 if (n != null) { 5867 n.localToSceneTransformProperty().addListener( 5868 getLocalToSceneInvalidationListener()); 5869 } 5870 } 5871 listenerReasons++; 5872 } 5873 5874 public void decListenerReasons() { 5875 listenerReasons--; 5876 if (listenerReasons == 0) { 5877 Node n = Node.this.getParent(); 5878 if (n != null) { 5879 n.localToSceneTransformProperty().removeListener( 5880 getLocalToSceneInvalidationListener()); 5881 } 5882 if (localToSceneTransform != null) { 5883 localToSceneTransform.validityUnknown(); 5884 } 5885 } 5886 } 5887 5888 public final Transform getLocalToParentTransform() { 5889 return localToParentTransformProperty().get(); 5890 } 5891 5892 public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() { 5893 if (localToParentTransform == null) { 5894 localToParentTransform = new LazyTransformProperty() { 5895 @Override 5896 protected Transform computeTransform(Transform reuse) { 5897 updateLocalToParentTransform(); 5898 return TransformUtils.immutableTransform(reuse, 5899 localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(), 5900 localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(), 5901 localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt()); 5902 } 5903 5904 @Override 5905 protected boolean validityKnown() { 5906 return true; 5907 } 5908 5909 @Override 5910 protected int computeValidity() { 5911 return valid; 5912 } 5913 5914 @Override 5915 public Object getBean() { 5916 return Node.this; 5917 } 5918 5919 @Override 5920 public String getName() { 5921 return "localToParentTransform"; 5922 } 5923 }; 5924 } 5925 5926 return localToParentTransform; 5927 } 5928 5929 public void invalidateLocalToParentTransform() { 5930 if (localToParentTransform != null) { 5931 localToParentTransform.invalidate(); 5932 } 5933 } 5934 5935 public final Transform getLocalToSceneTransform() { 5936 return localToSceneTransformProperty().get(); 5937 } 5938 5939 class LocalToSceneTransformProperty extends LazyTransformProperty { 5940 // need this to track number of listeners 5941 private List localToSceneListeners; 5942 // stamps to watch for parent changes when the listeners 5943 // are not present 5944 private long stamp, parentStamp; 5945 5946 @Override 5947 protected Transform computeTransform(Transform reuse) { 5948 stamp++; 5949 updateLocalToParentTransform(); 5950 5951 Node parentNode = Node.this.getParent(); 5952 if (parentNode != null) { 5953 final LocalToSceneTransformProperty parentProperty = 5954 (LocalToSceneTransformProperty) parentNode.localToSceneTransformProperty(); 5955 final Transform parentTransform = parentProperty.getInternalValue(); 5956 5957 parentStamp = parentProperty.stamp; 5958 5959 return TransformUtils.immutableTransform(reuse, 5960 parentTransform, 5961 ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue()); 5962 } else { 5963 return TransformUtils.immutableTransform(reuse, 5964 ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue()); 5965 } 5966 } 5967 5968 @Override 5969 public Object getBean() { 5970 return Node.this; 5971 } 5972 5973 @Override 5974 public String getName() { 5975 return "localToSceneTransform"; 5976 } 5977 5978 @Override 5979 protected boolean validityKnown() { 5980 return listenerReasons > 0; 5981 } 5982 5983 @Override 5984 protected int computeValidity() { 5985 if (valid != VALIDITY_UNKNOWN) { 5986 return valid; 5987 } 5988 5989 Node n = (Node) getBean(); 5990 Node parent = n.getParent(); 5991 5992 if (parent != null) { 5993 final LocalToSceneTransformProperty parentProperty = 5994 (LocalToSceneTransformProperty) parent.localToSceneTransformProperty(); 5995 5996 if (parentStamp != parentProperty.stamp) { 5997 valid = INVALID; 5998 return INVALID; 5999 } 6000 6001 int parentValid = parentProperty.computeValidity(); 6002 if (parentValid == INVALID) { 6003 valid = INVALID; 6004 } 6005 return parentValid; 6006 } 6007 6008 // Validity unknown for root means it is valid 6009 return VALID; 6010 } 6011 6012 @Override 6013 public void addListener(InvalidationListener listener) { 6014 incListenerReasons(); 6015 if (localToSceneListeners == null) { 6016 localToSceneListeners = new LinkedList<Object>(); 6017 } 6018 localToSceneListeners.add(listener); 6019 super.addListener(listener); 6020 } 6021 6022 @Override 6023 public void addListener(ChangeListener<? super Transform> listener) { 6024 incListenerReasons(); 6025 if (localToSceneListeners == null) { 6026 localToSceneListeners = new LinkedList<Object>(); 6027 } 6028 localToSceneListeners.add(listener); 6029 super.addListener(listener); 6030 } 6031 6032 @Override 6033 public void removeListener(InvalidationListener listener) { 6034 if (localToSceneListeners != null && 6035 localToSceneListeners.remove(listener)) { 6036 decListenerReasons(); 6037 } 6038 super.removeListener(listener); 6039 } 6040 6041 @Override 6042 public void removeListener(ChangeListener<? super Transform> listener) { 6043 if (localToSceneListeners != null && 6044 localToSceneListeners.remove(listener)) { 6045 decListenerReasons(); 6046 } 6047 super.removeListener(listener); 6048 } 6049 } 6050 6051 public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() { 6052 if (localToSceneTransform == null) { 6053 localToSceneTransform = new LocalToSceneTransformProperty(); 6054 } 6055 6056 return localToSceneTransform; 6057 } 6058 6059 public void invalidateLocalToSceneTransform() { 6060 if (localToSceneTransform != null) { 6061 localToSceneTransform.invalidate(); 6062 } 6063 } 6064 6065 public double getTranslateX() { 6066 return (translateX == null) ? DEFAULT_TRANSLATE_X 6067 : translateX.get(); 6068 } 6069 6070 public final DoubleProperty translateXProperty() { 6071 if (translateX == null) { 6072 translateX = new StyleableDoubleProperty(DEFAULT_TRANSLATE_X) { 6073 @Override 6074 public void invalidated() { 6075 NodeHelper.transformsChanged(Node.this); 6076 } 6077 6078 @Override 6079 public CssMetaData getCssMetaData() { 6080 return StyleableProperties.TRANSLATE_X; 6081 } 6082 6083 @Override 6084 public Object getBean() { 6085 return Node.this; 6086 } 6087 6088 @Override 6089 public String getName() { 6090 return "translateX"; 6091 } 6092 }; 6093 } 6094 return translateX; 6095 } 6096 6097 public double getTranslateY() { 6098 return (translateY == null) ? DEFAULT_TRANSLATE_Y : translateY.get(); 6099 } 6100 6101 public final DoubleProperty translateYProperty() { 6102 if (translateY == null) { 6103 translateY = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Y) { 6104 @Override 6105 public void invalidated() { 6106 NodeHelper.transformsChanged(Node.this); 6107 } 6108 6109 @Override 6110 public CssMetaData getCssMetaData() { 6111 return StyleableProperties.TRANSLATE_Y; 6112 } 6113 6114 @Override 6115 public Object getBean() { 6116 return Node.this; 6117 } 6118 6119 @Override 6120 public String getName() { 6121 return "translateY"; 6122 } 6123 }; 6124 } 6125 return translateY; 6126 } 6127 6128 public double getTranslateZ() { 6129 return (translateZ == null) ? DEFAULT_TRANSLATE_Z : translateZ.get(); 6130 } 6131 6132 public final DoubleProperty translateZProperty() { 6133 if (translateZ == null) { 6134 translateZ = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Z) { 6135 @Override 6136 public void invalidated() { 6137 NodeHelper.transformsChanged(Node.this); 6138 } 6139 6140 @Override 6141 public CssMetaData getCssMetaData() { 6142 return StyleableProperties.TRANSLATE_Z; 6143 } 6144 6145 @Override 6146 public Object getBean() { 6147 return Node.this; 6148 } 6149 6150 @Override 6151 public String getName() { 6152 return "translateZ"; 6153 } 6154 }; 6155 } 6156 return translateZ; 6157 } 6158 6159 public double getScaleX() { 6160 return (scaleX == null) ? DEFAULT_SCALE_X : scaleX.get(); 6161 } 6162 6163 public final DoubleProperty scaleXProperty() { 6164 if (scaleX == null) { 6165 scaleX = new StyleableDoubleProperty(DEFAULT_SCALE_X) { 6166 @Override 6167 public void invalidated() { 6168 NodeHelper.transformsChanged(Node.this); 6169 } 6170 6171 @Override 6172 public CssMetaData getCssMetaData() { 6173 return StyleableProperties.SCALE_X; 6174 } 6175 6176 @Override 6177 public Object getBean() { 6178 return Node.this; 6179 } 6180 6181 @Override 6182 public String getName() { 6183 return "scaleX"; 6184 } 6185 }; 6186 } 6187 return scaleX; 6188 } 6189 6190 public double getScaleY() { 6191 return (scaleY == null) ? DEFAULT_SCALE_Y : scaleY.get(); 6192 } 6193 6194 public final DoubleProperty scaleYProperty() { 6195 if (scaleY == null) { 6196 scaleY = new StyleableDoubleProperty(DEFAULT_SCALE_Y) { 6197 @Override 6198 public void invalidated() { 6199 NodeHelper.transformsChanged(Node.this); 6200 } 6201 6202 @Override 6203 public CssMetaData getCssMetaData() { 6204 return StyleableProperties.SCALE_Y; 6205 } 6206 6207 @Override 6208 public Object getBean() { 6209 return Node.this; 6210 } 6211 6212 @Override 6213 public String getName() { 6214 return "scaleY"; 6215 } 6216 }; 6217 } 6218 return scaleY; 6219 } 6220 6221 public double getScaleZ() { 6222 return (scaleZ == null) ? DEFAULT_SCALE_Z : scaleZ.get(); 6223 } 6224 6225 public final DoubleProperty scaleZProperty() { 6226 if (scaleZ == null) { 6227 scaleZ = new StyleableDoubleProperty(DEFAULT_SCALE_Z) { 6228 @Override 6229 public void invalidated() { 6230 NodeHelper.transformsChanged(Node.this); 6231 } 6232 6233 @Override 6234 public CssMetaData getCssMetaData() { 6235 return StyleableProperties.SCALE_Z; 6236 } 6237 6238 @Override 6239 public Object getBean() { 6240 return Node.this; 6241 } 6242 6243 @Override 6244 public String getName() { 6245 return "scaleZ"; 6246 } 6247 }; 6248 } 6249 return scaleZ; 6250 } 6251 6252 public double getRotate() { 6253 return (rotate == null) ? DEFAULT_ROTATE : rotate.get(); 6254 } 6255 6256 public final DoubleProperty rotateProperty() { 6257 if (rotate == null) { 6258 rotate = new StyleableDoubleProperty(DEFAULT_ROTATE) { 6259 @Override 6260 public void invalidated() { 6261 NodeHelper.transformsChanged(Node.this); 6262 } 6263 6264 @Override 6265 public CssMetaData getCssMetaData() { 6266 return StyleableProperties.ROTATE; 6267 } 6268 6269 @Override 6270 public Object getBean() { 6271 return Node.this; 6272 } 6273 6274 @Override 6275 public String getName() { 6276 return "rotate"; 6277 } 6278 }; 6279 } 6280 return rotate; 6281 } 6282 6283 public Point3D getRotationAxis() { 6284 return (rotationAxis == null) ? DEFAULT_ROTATION_AXIS 6285 : rotationAxis.get(); 6286 } 6287 6288 public final ObjectProperty<Point3D> rotationAxisProperty() { 6289 if (rotationAxis == null) { 6290 rotationAxis = new ObjectPropertyBase<Point3D>( 6291 DEFAULT_ROTATION_AXIS) { 6292 @Override 6293 protected void invalidated() { 6294 NodeHelper.transformsChanged(Node.this); 6295 } 6296 6297 @Override 6298 public Object getBean() { 6299 return Node.this; 6300 } 6301 6302 @Override 6303 public String getName() { 6304 return "rotationAxis"; 6305 } 6306 }; 6307 } 6308 return rotationAxis; 6309 } 6310 6311 public ObservableList<Transform> getTransforms() { 6312 if (transforms == null) { 6313 transforms = new TrackableObservableList<Transform>() { 6314 @Override 6315 protected void onChanged(Change<Transform> c) { 6316 while (c.next()) { 6317 for (Transform t : c.getRemoved()) { 6318 TransformHelper.remove(t, Node.this); 6319 } 6320 for (Transform t : c.getAddedSubList()) { 6321 TransformHelper.add(t, Node.this); 6322 } 6323 } 6324 6325 NodeHelper.transformsChanged(Node.this); 6326 } 6327 }; 6328 } 6329 6330 return transforms; 6331 } 6332 6333 public boolean canSetTranslateX() { 6334 return (translateX == null) || !translateX.isBound(); 6335 } 6336 6337 public boolean canSetTranslateY() { 6338 return (translateY == null) || !translateY.isBound(); 6339 } 6340 6341 public boolean canSetTranslateZ() { 6342 return (translateZ == null) || !translateZ.isBound(); 6343 } 6344 6345 public boolean canSetScaleX() { 6346 return (scaleX == null) || !scaleX.isBound(); 6347 } 6348 6349 public boolean canSetScaleY() { 6350 return (scaleY == null) || !scaleY.isBound(); 6351 } 6352 6353 public boolean canSetScaleZ() { 6354 return (scaleZ == null) || !scaleZ.isBound(); 6355 } 6356 6357 public boolean canSetRotate() { 6358 return (rotate == null) || !rotate.isBound(); 6359 } 6360 6361 public boolean hasTransforms() { 6362 return (transforms != null && !transforms.isEmpty()); 6363 } 6364 6365 public boolean hasScaleOrRotate() { 6366 if (scaleX != null && scaleX.get() != DEFAULT_SCALE_X) { 6367 return true; 6368 } 6369 if (scaleY != null && scaleY.get() != DEFAULT_SCALE_Y) { 6370 return true; 6371 } 6372 if (scaleZ != null && scaleZ.get() != DEFAULT_SCALE_Z) { 6373 return true; 6374 } 6375 if (rotate != null && rotate.get() != DEFAULT_ROTATE) { 6376 return true; 6377 } 6378 return false; 6379 } 6380 6381 } 6382 6383 //////////////////////////// 6384 // Private Implementation 6385 //////////////////////////// 6386 6387 /*************************************************************************** 6388 * * 6389 * Event Handler Properties * 6390 * * 6391 **************************************************************************/ 6392 6393 private EventHandlerProperties eventHandlerProperties; 6394 6395 private EventHandlerProperties getEventHandlerProperties() { 6396 if (eventHandlerProperties == null) { 6397 eventHandlerProperties = 6398 new EventHandlerProperties( 6399 getInternalEventDispatcher().getEventHandlerManager(), 6400 this); 6401 } 6402 6403 return eventHandlerProperties; 6404 } 6405 6406 /*************************************************************************** 6407 * * 6408 * Component Orientation Properties * 6409 * * 6410 **************************************************************************/ 6411 6412 private ObjectProperty<NodeOrientation> nodeOrientation; 6413 private EffectiveOrientationProperty effectiveNodeOrientationProperty; 6414 6415 private static final byte EFFECTIVE_ORIENTATION_LTR = 0; 6416 private static final byte EFFECTIVE_ORIENTATION_RTL = 1; 6417 private static final byte EFFECTIVE_ORIENTATION_MASK = 1; 6418 private static final byte AUTOMATIC_ORIENTATION_LTR = 0; 6419 private static final byte AUTOMATIC_ORIENTATION_RTL = 2; 6420 private static final byte AUTOMATIC_ORIENTATION_MASK = 2; 6421 6422 private byte resolvedNodeOrientation = 6423 EFFECTIVE_ORIENTATION_LTR | AUTOMATIC_ORIENTATION_LTR; 6424 6425 public final void setNodeOrientation(NodeOrientation orientation) { 6426 nodeOrientationProperty().set(orientation); 6427 } 6428 6429 public final NodeOrientation getNodeOrientation() { 6430 return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get(); 6431 } 6432 /** 6433 * Property holding NodeOrientation. 6434 * <p> 6435 * Node orientation describes the flow of visual data within a node. 6436 * In the English speaking world, visual data normally flows from 6437 * left-to-right. In an Arabic or Hebrew world, visual data flows 6438 * from right-to-left. This is consistent with the reading order 6439 * of text in both worlds. The default value is left-to-right. 6440 * </p> 6441 * 6442 * @return NodeOrientation 6443 * @since JavaFX 8.0 6444 */ 6445 public final ObjectProperty<NodeOrientation> nodeOrientationProperty() { 6446 if (nodeOrientation == null) { 6447 nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) { 6448 @Override 6449 protected void invalidated() { 6450 nodeResolvedOrientationInvalidated(); 6451 } 6452 6453 @Override 6454 public Object getBean() { 6455 return Node.this; 6456 } 6457 6458 @Override 6459 public String getName() { 6460 return "nodeOrientation"; 6461 } 6462 6463 @Override 6464 public CssMetaData getCssMetaData() { 6465 //TODO - not supported 6466 throw new UnsupportedOperationException("Not supported yet."); 6467 } 6468 6469 }; 6470 } 6471 return nodeOrientation; 6472 } 6473 6474 public final NodeOrientation getEffectiveNodeOrientation() { 6475 return (getEffectiveOrientation(resolvedNodeOrientation) 6476 == EFFECTIVE_ORIENTATION_LTR) 6477 ? NodeOrientation.LEFT_TO_RIGHT 6478 : NodeOrientation.RIGHT_TO_LEFT; 6479 } 6480 6481 /** 6482 * The effective orientation of a node resolves the inheritance of 6483 * node orientation, returning either left-to-right or right-to-left. 6484 * @return the node orientation for this {@code Node} 6485 * @since JavaFX 8.0 6486 */ 6487 public final ReadOnlyObjectProperty<NodeOrientation> 6488 effectiveNodeOrientationProperty() { 6489 if (effectiveNodeOrientationProperty == null) { 6490 effectiveNodeOrientationProperty = 6491 new EffectiveOrientationProperty(); 6492 } 6493 6494 return effectiveNodeOrientationProperty; 6495 } 6496 6497 /** 6498 * Determines whether a node should be mirrored when node orientation 6499 * is right-to-left. 6500 * <p> 6501 * When a node is mirrored, the origin is automatically moved to the 6502 * top right corner causing the node to layout children and draw from 6503 * right to left using a mirroring transformation. Some nodes may wish 6504 * to draw from right to left without using a transformation. These 6505 * nodes will will answer {@code false} and implement right-to-left 6506 * orientation without using the automatic transformation. 6507 * </p> 6508 * @return true if this {@code Node} should be mirrored 6509 * @since JavaFX 8.0 6510 */ 6511 public boolean usesMirroring() { 6512 return true; 6513 } 6514 6515 final void parentResolvedOrientationInvalidated() { 6516 if (getNodeOrientation() == NodeOrientation.INHERIT) { 6517 nodeResolvedOrientationInvalidated(); 6518 } else { 6519 // mirroring changed 6520 NodeHelper.transformsChanged(this); 6521 } 6522 } 6523 6524 final void nodeResolvedOrientationInvalidated() { 6525 final byte oldResolvedNodeOrientation = 6526 resolvedNodeOrientation; 6527 6528 resolvedNodeOrientation = 6529 (byte) (calcEffectiveNodeOrientation() 6530 | calcAutomaticNodeOrientation()); 6531 6532 if ((effectiveNodeOrientationProperty != null) 6533 && (getEffectiveOrientation(resolvedNodeOrientation) 6534 != getEffectiveOrientation( 6535 oldResolvedNodeOrientation))) { 6536 effectiveNodeOrientationProperty.invalidate(); 6537 } 6538 6539 // mirroring changed 6540 NodeHelper.transformsChanged(this); 6541 6542 if (resolvedNodeOrientation != oldResolvedNodeOrientation) { 6543 nodeResolvedOrientationChanged(); 6544 } 6545 } 6546 6547 void nodeResolvedOrientationChanged() { 6548 // overriden in Parent 6549 } 6550 6551 private Node getMirroringOrientationParent() { 6552 Node parentValue = getParent(); 6553 while (parentValue != null) { 6554 if (parentValue.usesMirroring()) { 6555 return parentValue; 6556 } 6557 parentValue = parentValue.getParent(); 6558 } 6559 6560 final Node subSceneValue = getSubScene(); 6561 if (subSceneValue != null) { 6562 return subSceneValue; 6563 } 6564 6565 return null; 6566 } 6567 6568 private Node getOrientationParent() { 6569 final Node parentValue = getParent(); 6570 if (parentValue != null) { 6571 return parentValue; 6572 } 6573 6574 final Node subSceneValue = getSubScene(); 6575 if (subSceneValue != null) { 6576 return subSceneValue; 6577 } 6578 6579 return null; 6580 } 6581 6582 private byte calcEffectiveNodeOrientation() { 6583 final NodeOrientation nodeOrientationValue = getNodeOrientation(); 6584 if (nodeOrientationValue != NodeOrientation.INHERIT) { 6585 return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT) 6586 ? EFFECTIVE_ORIENTATION_LTR 6587 : EFFECTIVE_ORIENTATION_RTL; 6588 } 6589 6590 final Node parentValue = getOrientationParent(); 6591 if (parentValue != null) { 6592 return getEffectiveOrientation(parentValue.resolvedNodeOrientation); 6593 } 6594 6595 final Scene sceneValue = getScene(); 6596 if (sceneValue != null) { 6597 return (sceneValue.getEffectiveNodeOrientation() 6598 == NodeOrientation.LEFT_TO_RIGHT) 6599 ? EFFECTIVE_ORIENTATION_LTR 6600 : EFFECTIVE_ORIENTATION_RTL; 6601 } 6602 6603 return EFFECTIVE_ORIENTATION_LTR; 6604 } 6605 6606 private byte calcAutomaticNodeOrientation() { 6607 if (!usesMirroring()) { 6608 return AUTOMATIC_ORIENTATION_LTR; 6609 } 6610 6611 final NodeOrientation nodeOrientationValue = getNodeOrientation(); 6612 if (nodeOrientationValue != NodeOrientation.INHERIT) { 6613 return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT) 6614 ? AUTOMATIC_ORIENTATION_LTR 6615 : AUTOMATIC_ORIENTATION_RTL; 6616 } 6617 6618 final Node parentValue = getMirroringOrientationParent(); 6619 if (parentValue != null) { 6620 // automatic node orientation is inherited 6621 return getAutomaticOrientation(parentValue.resolvedNodeOrientation); 6622 } 6623 6624 final Scene sceneValue = getScene(); 6625 if (sceneValue != null) { 6626 return (sceneValue.getEffectiveNodeOrientation() 6627 == NodeOrientation.LEFT_TO_RIGHT) 6628 ? AUTOMATIC_ORIENTATION_LTR 6629 : AUTOMATIC_ORIENTATION_RTL; 6630 } 6631 6632 return AUTOMATIC_ORIENTATION_LTR; 6633 } 6634 6635 // Return true if the node needs to be mirrored. 6636 // A node has mirroring if the orientation differs from the parent 6637 // package private for testing 6638 final boolean hasMirroring() { 6639 final Node parentValue = getOrientationParent(); 6640 6641 final byte thisOrientation = 6642 getAutomaticOrientation(resolvedNodeOrientation); 6643 final byte parentOrientation = 6644 (parentValue != null) 6645 ? getAutomaticOrientation( 6646 parentValue.resolvedNodeOrientation) 6647 : AUTOMATIC_ORIENTATION_LTR; 6648 6649 return thisOrientation != parentOrientation; 6650 } 6651 6652 private static byte getEffectiveOrientation( 6653 final byte resolvedNodeOrientation) { 6654 return (byte) (resolvedNodeOrientation & EFFECTIVE_ORIENTATION_MASK); 6655 } 6656 6657 private static byte getAutomaticOrientation( 6658 final byte resolvedNodeOrientation) { 6659 return (byte) (resolvedNodeOrientation & AUTOMATIC_ORIENTATION_MASK); 6660 } 6661 6662 private final class EffectiveOrientationProperty 6663 extends ReadOnlyObjectPropertyBase<NodeOrientation> { 6664 @Override 6665 public NodeOrientation get() { 6666 return getEffectiveNodeOrientation(); 6667 } 6668 6669 @Override 6670 public Object getBean() { 6671 return Node.this; 6672 } 6673 6674 @Override 6675 public String getName() { 6676 return "effectiveNodeOrientation"; 6677 } 6678 6679 public void invalidate() { 6680 fireValueChangedEvent(); 6681 } 6682 } 6683 6684 /*************************************************************************** 6685 * * 6686 * Misc Seldom Used Properties * 6687 * * 6688 **************************************************************************/ 6689 6690 private MiscProperties miscProperties; 6691 6692 private MiscProperties getMiscProperties() { 6693 if (miscProperties == null) { 6694 miscProperties = new MiscProperties(); 6695 } 6696 6697 return miscProperties; 6698 } 6699 6700 private static final double DEFAULT_VIEW_ORDER = 0; 6701 private static final boolean DEFAULT_CACHE = false; 6702 private static final CacheHint DEFAULT_CACHE_HINT = CacheHint.DEFAULT; 6703 private static final Node DEFAULT_CLIP = null; 6704 private static final Cursor DEFAULT_CURSOR = null; 6705 private static final DepthTest DEFAULT_DEPTH_TEST = DepthTest.INHERIT; 6706 private static final boolean DEFAULT_DISABLE = false; 6707 private static final Effect DEFAULT_EFFECT = null; 6708 private static final InputMethodRequests DEFAULT_INPUT_METHOD_REQUESTS = 6709 null; 6710 private static final boolean DEFAULT_MOUSE_TRANSPARENT = false; 6711 6712 private final class MiscProperties { 6713 private LazyBoundsProperty boundsInParent; 6714 private LazyBoundsProperty boundsInLocal; 6715 private BooleanProperty cache; 6716 private ObjectProperty<CacheHint> cacheHint; 6717 private ObjectProperty<Node> clip; 6718 private ObjectProperty<Cursor> cursor; 6719 private ObjectProperty<DepthTest> depthTest; 6720 private BooleanProperty disable; 6721 private ObjectProperty<Effect> effect; 6722 private ObjectProperty<InputMethodRequests> inputMethodRequests; 6723 private BooleanProperty mouseTransparent; 6724 private DoubleProperty viewOrder; 6725 6726 public double getViewOrder() { 6727 return (viewOrder == null) ? DEFAULT_VIEW_ORDER : viewOrder.get(); 6728 } 6729 6730 public final DoubleProperty viewOrderProperty() { 6731 if (viewOrder == null) { 6732 viewOrder = new StyleableDoubleProperty(DEFAULT_VIEW_ORDER) { 6733 @Override 6734 public void invalidated() { 6735 Parent p = getParent(); 6736 if (p != null) { 6737 // Parent will be responsible to update sorted children list 6738 p.markViewOrderChildrenDirty(); 6739 } 6740 NodeHelper.markDirty(Node.this, DirtyBits.NODE_VIEW_ORDER); 6741 } 6742 6743 @Override 6744 public CssMetaData getCssMetaData() { 6745 return StyleableProperties.VIEW_ORDER; 6746 } 6747 6748 @Override 6749 public Object getBean() { 6750 return Node.this; 6751 } 6752 6753 @Override 6754 public String getName() { 6755 return "viewOrder"; 6756 } 6757 }; 6758 } 6759 return viewOrder; 6760 } 6761 6762 public final Bounds getBoundsInParent() { 6763 return boundsInParentProperty().get(); 6764 } 6765 6766 public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() { 6767 if (boundsInParent == null) { 6768 boundsInParent = new LazyBoundsProperty() { 6769 /** 6770 * Computes the bounds including the clip, effects, and all 6771 * transforms. This function is essentially how to compute 6772 * the boundsInParent. Optimizations are made to compute as 6773 * little as possible and create as little trash as 6774 * possible. 6775 */ 6776 @Override 6777 protected Bounds computeBounds() { 6778 BaseBounds tempBounds = TempState.getInstance().bounds; 6779 tempBounds = getTransformedBounds( 6780 tempBounds, 6781 BaseTransform.IDENTITY_TRANSFORM); 6782 return new BoundingBox(tempBounds.getMinX(), 6783 tempBounds.getMinY(), 6784 tempBounds.getMinZ(), 6785 tempBounds.getWidth(), 6786 tempBounds.getHeight(), 6787 tempBounds.getDepth()); 6788 } 6789 6790 @Override 6791 public Object getBean() { 6792 return Node.this; 6793 } 6794 6795 @Override 6796 public String getName() { 6797 return "boundsInParent"; 6798 } 6799 }; 6800 } 6801 6802 return boundsInParent; 6803 } 6804 6805 public void invalidateBoundsInParent() { 6806 if (boundsInParent != null) { 6807 boundsInParent.invalidate(); 6808 } 6809 } 6810 6811 public final Bounds getBoundsInLocal() { 6812 return boundsInLocalProperty().get(); 6813 } 6814 6815 public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() { 6816 if (boundsInLocal == null) { 6817 boundsInLocal = new LazyBoundsProperty() { 6818 @Override 6819 protected Bounds computeBounds() { 6820 BaseBounds tempBounds = TempState.getInstance().bounds; 6821 tempBounds = getLocalBounds( 6822 tempBounds, 6823 BaseTransform.IDENTITY_TRANSFORM); 6824 return new BoundingBox(tempBounds.getMinX(), 6825 tempBounds.getMinY(), 6826 tempBounds.getMinZ(), 6827 tempBounds.getWidth(), 6828 tempBounds.getHeight(), 6829 tempBounds.getDepth()); 6830 } 6831 6832 @Override 6833 public Object getBean() { 6834 return Node.this; 6835 } 6836 6837 @Override 6838 public String getName() { 6839 return "boundsInLocal"; 6840 } 6841 }; 6842 } 6843 6844 return boundsInLocal; 6845 } 6846 6847 public void invalidateBoundsInLocal() { 6848 if (boundsInLocal != null) { 6849 boundsInLocal.invalidate(); 6850 } 6851 } 6852 6853 public final boolean isCache() { 6854 return (cache == null) ? DEFAULT_CACHE 6855 : cache.get(); 6856 } 6857 6858 public final BooleanProperty cacheProperty() { 6859 if (cache == null) { 6860 cache = new BooleanPropertyBase(DEFAULT_CACHE) { 6861 @Override 6862 protected void invalidated() { 6863 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE); 6864 } 6865 6866 @Override 6867 public Object getBean() { 6868 return Node.this; 6869 } 6870 6871 @Override 6872 public String getName() { 6873 return "cache"; 6874 } 6875 }; 6876 } 6877 return cache; 6878 } 6879 6880 public final CacheHint getCacheHint() { 6881 return (cacheHint == null) ? DEFAULT_CACHE_HINT 6882 : cacheHint.get(); 6883 } 6884 6885 public final ObjectProperty<CacheHint> cacheHintProperty() { 6886 if (cacheHint == null) { 6887 cacheHint = new ObjectPropertyBase<CacheHint>(DEFAULT_CACHE_HINT) { 6888 @Override 6889 protected void invalidated() { 6890 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE); 6891 } 6892 6893 @Override 6894 public Object getBean() { 6895 return Node.this; 6896 } 6897 6898 @Override 6899 public String getName() { 6900 return "cacheHint"; 6901 } 6902 }; 6903 } 6904 return cacheHint; 6905 } 6906 6907 public final Node getClip() { 6908 return (clip == null) ? DEFAULT_CLIP : clip.get(); 6909 } 6910 6911 public final ObjectProperty<Node> clipProperty() { 6912 if (clip == null) { 6913 clip = new ObjectPropertyBase<Node>(DEFAULT_CLIP) { 6914 6915 //temp variables used when clip was invalid to rollback to 6916 // last value 6917 private Node oldClip; 6918 6919 @Override 6920 protected void invalidated() { 6921 final Node newClip = get(); 6922 if ((newClip != null) 6923 && ((newClip.isConnected() 6924 && newClip.clipParent != Node.this) 6925 || wouldCreateCycle(Node.this, 6926 newClip))) { 6927 // Assigning this node to clip is illegal. 6928 // Roll back to the previous state and throw an 6929 // exception. 6930 final String cause = 6931 newClip.isConnected() 6932 && (newClip.clipParent != Node.this) 6933 ? "node already connected" 6934 : "cycle detected"; 6935 6936 if (isBound()) { 6937 unbind(); 6938 set(oldClip); 6939 throw new IllegalArgumentException( 6940 "Node's clip set to incorrect value " 6941 + " through binding" 6942 + " (" + cause + ", node = " 6943 + Node.this + ", clip = " 6944 + clip + ")." 6945 + " Binding has been removed."); 6946 } else { 6947 set(oldClip); 6948 throw new IllegalArgumentException( 6949 "Node's clip set to incorrect value" 6950 + " (" + cause + ", node = " 6951 + Node.this + ", clip = " 6952 + clip + ")."); 6953 } 6954 } else { 6955 if (oldClip != null) { 6956 oldClip.clipParent = null; 6957 oldClip.setScenes(null, null, /* reapplyCSS */ false); 6958 oldClip.updateTreeVisible(false); 6959 } 6960 6961 if (newClip != null) { 6962 newClip.clipParent = Node.this; 6963 newClip.setScenes(getScene(), getSubScene(), /* reapplyCSS */ false); 6964 newClip.updateTreeVisible(true); 6965 } 6966 6967 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CLIP); 6968 6969 // the local bounds have (probably) changed 6970 localBoundsChanged(); 6971 6972 oldClip = newClip; 6973 } 6974 } 6975 6976 @Override 6977 public Object getBean() { 6978 return Node.this; 6979 } 6980 6981 @Override 6982 public String getName() { 6983 return "clip"; 6984 } 6985 }; 6986 } 6987 return clip; 6988 } 6989 6990 public final Cursor getCursor() { 6991 return (cursor == null) ? DEFAULT_CURSOR : cursor.get(); 6992 } 6993 6994 public final ObjectProperty<Cursor> cursorProperty() { 6995 if (cursor == null) { 6996 cursor = new StyleableObjectProperty<Cursor>(DEFAULT_CURSOR) { 6997 6998 @Override 6999 protected void invalidated() { 7000 final Scene sceneValue = getScene(); 7001 if (sceneValue != null) { 7002 sceneValue.markCursorDirty(); 7003 } 7004 } 7005 7006 @Override 7007 public CssMetaData getCssMetaData() { 7008 return StyleableProperties.CURSOR; 7009 } 7010 7011 @Override 7012 public Object getBean() { 7013 return Node.this; 7014 } 7015 7016 @Override 7017 public String getName() { 7018 return "cursor"; 7019 } 7020 7021 }; 7022 } 7023 return cursor; 7024 } 7025 7026 public final DepthTest getDepthTest() { 7027 return (depthTest == null) ? DEFAULT_DEPTH_TEST 7028 : depthTest.get(); 7029 } 7030 7031 public final ObjectProperty<DepthTest> depthTestProperty() { 7032 if (depthTest == null) { 7033 depthTest = new ObjectPropertyBase<DepthTest>(DEFAULT_DEPTH_TEST) { 7034 @Override protected void invalidated() { 7035 computeDerivedDepthTest(); 7036 } 7037 7038 @Override 7039 public Object getBean() { 7040 return Node.this; 7041 } 7042 7043 @Override 7044 public String getName() { 7045 return "depthTest"; 7046 } 7047 }; 7048 } 7049 return depthTest; 7050 } 7051 7052 public final boolean isDisable() { 7053 return (disable == null) ? DEFAULT_DISABLE : disable.get(); 7054 } 7055 7056 public final BooleanProperty disableProperty() { 7057 if (disable == null) { 7058 disable = new BooleanPropertyBase(DEFAULT_DISABLE) { 7059 @Override 7060 protected void invalidated() { 7061 updateDisabled(); 7062 } 7063 7064 @Override 7065 public Object getBean() { 7066 return Node.this; 7067 } 7068 7069 @Override 7070 public String getName() { 7071 return "disable"; 7072 } 7073 }; 7074 } 7075 return disable; 7076 } 7077 7078 public final Effect getEffect() { 7079 return (effect == null) ? DEFAULT_EFFECT : effect.get(); 7080 } 7081 7082 public final ObjectProperty<Effect> effectProperty() { 7083 if (effect == null) { 7084 effect = new StyleableObjectProperty<Effect>(DEFAULT_EFFECT) { 7085 private Effect oldEffect = null; 7086 private int oldBits; 7087 7088 private final AbstractNotifyListener effectChangeListener = 7089 new AbstractNotifyListener() { 7090 7091 @Override 7092 public void invalidated(Observable valueModel) { 7093 int newBits = ((IntegerProperty) valueModel).get(); 7094 int changedBits = newBits ^ oldBits; 7095 oldBits = newBits; 7096 if (EffectDirtyBits.isSet( 7097 changedBits, 7098 EffectDirtyBits.EFFECT_DIRTY) 7099 && EffectDirtyBits.isSet( 7100 newBits, 7101 EffectDirtyBits.EFFECT_DIRTY)) { 7102 NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT); 7103 } 7104 if (EffectDirtyBits.isSet( 7105 changedBits, 7106 EffectDirtyBits.BOUNDS_CHANGED)) { 7107 localBoundsChanged(); 7108 } 7109 } 7110 }; 7111 7112 @Override 7113 protected void invalidated() { 7114 Effect _effect = get(); 7115 if (oldEffect != null) { 7116 EffectHelper.effectDirtyProperty(oldEffect).removeListener( 7117 effectChangeListener.getWeakListener()); 7118 } 7119 oldEffect = _effect; 7120 if (_effect != null) { 7121 EffectHelper.effectDirtyProperty(_effect) 7122 .addListener( 7123 effectChangeListener.getWeakListener()); 7124 if (EffectHelper.isEffectDirty(_effect)) { 7125 NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT); 7126 } 7127 oldBits = EffectHelper.effectDirtyProperty(_effect).get(); 7128 } 7129 7130 NodeHelper.markDirty(Node.this, DirtyBits.NODE_EFFECT); 7131 // bounds may have changed regardeless whether 7132 // the dirty flag on efffect is set 7133 localBoundsChanged(); 7134 } 7135 7136 @Override 7137 public CssMetaData getCssMetaData() { 7138 return StyleableProperties.EFFECT; 7139 } 7140 7141 @Override 7142 public Object getBean() { 7143 return Node.this; 7144 } 7145 7146 @Override 7147 public String getName() { 7148 return "effect"; 7149 } 7150 }; 7151 } 7152 return effect; 7153 } 7154 7155 public final InputMethodRequests getInputMethodRequests() { 7156 return (inputMethodRequests == null) ? DEFAULT_INPUT_METHOD_REQUESTS 7157 : inputMethodRequests.get(); 7158 } 7159 7160 public ObjectProperty<InputMethodRequests> 7161 inputMethodRequestsProperty() { 7162 if (inputMethodRequests == null) { 7163 inputMethodRequests = 7164 new SimpleObjectProperty<InputMethodRequests>( 7165 Node.this, 7166 "inputMethodRequests", 7167 DEFAULT_INPUT_METHOD_REQUESTS); 7168 } 7169 return inputMethodRequests; 7170 } 7171 7172 public final boolean isMouseTransparent() { 7173 return (mouseTransparent == null) ? DEFAULT_MOUSE_TRANSPARENT 7174 : mouseTransparent.get(); 7175 } 7176 7177 public final BooleanProperty mouseTransparentProperty() { 7178 if (mouseTransparent == null) { 7179 mouseTransparent = 7180 new SimpleBooleanProperty( 7181 Node.this, 7182 "mouseTransparent", 7183 DEFAULT_MOUSE_TRANSPARENT); 7184 } 7185 return mouseTransparent; 7186 } 7187 7188 public boolean canSetCursor() { 7189 return (cursor == null) || !cursor.isBound(); 7190 } 7191 7192 public boolean canSetEffect() { 7193 return (effect == null) || !effect.isBound(); 7194 } 7195 } 7196 7197 /* ************************************************************************* 7198 * * 7199 * Mouse Handling * 7200 * * 7201 **************************************************************************/ 7202 7203 public final void setMouseTransparent(boolean value) { 7204 mouseTransparentProperty().set(value); 7205 } 7206 7207 public final boolean isMouseTransparent() { 7208 return (miscProperties == null) ? DEFAULT_MOUSE_TRANSPARENT 7209 : miscProperties.isMouseTransparent(); 7210 } 7211 7212 /** 7213 * If {@code true}, this node (together with all its children) is completely 7214 * transparent to mouse events. When choosing target for mouse event, nodes 7215 * with {@code mouseTransparent} set to {@code true} and their subtrees 7216 * won't be taken into account. 7217 * @return is this {@code Node} (together with all its children) is completely 7218 * transparent to mouse events. 7219 */ 7220 public final BooleanProperty mouseTransparentProperty() { 7221 return getMiscProperties().mouseTransparentProperty(); 7222 } 7223 7224 /** 7225 * Whether or not this {@code Node} is being hovered over. Typically this is 7226 * due to the mouse being over the node, though it could be due to a pen 7227 * hovering on a graphics tablet or other form of input. 7228 * 7229 * <p>Note that current implementation of hover relies on mouse enter and 7230 * exit events to determine whether this Node is in the hover state; this 7231 * means that this feature is currently supported only on systems that 7232 * have a mouse. Future implementations may provide alternative means of 7233 * supporting hover. 7234 * 7235 * @defaultValue false 7236 */ 7237 private ReadOnlyBooleanWrapper hover; 7238 7239 protected final void setHover(boolean value) { 7240 hoverPropertyImpl().set(value); 7241 } 7242 7243 public final boolean isHover() { 7244 return hover == null ? false : hover.get(); 7245 } 7246 7247 public final ReadOnlyBooleanProperty hoverProperty() { 7248 return hoverPropertyImpl().getReadOnlyProperty(); 7249 } 7250 7251 private ReadOnlyBooleanWrapper hoverPropertyImpl() { 7252 if (hover == null) { 7253 hover = new ReadOnlyBooleanWrapper() { 7254 7255 @Override 7256 protected void invalidated() { 7257 PlatformLogger logger = Logging.getInputLogger(); 7258 if (logger.isLoggable(Level.FINER)) { 7259 logger.finer(this + " hover=" + get()); 7260 } 7261 pseudoClassStateChanged(HOVER_PSEUDOCLASS_STATE, get()); 7262 } 7263 7264 @Override 7265 public Object getBean() { 7266 return Node.this; 7267 } 7268 7269 @Override 7270 public String getName() { 7271 return "hover"; 7272 } 7273 }; 7274 } 7275 return hover; 7276 } 7277 7278 /** 7279 * Whether or not the {@code Node} is pressed. Typically this is true when 7280 * the primary mouse button is down, though subclasses may define other 7281 * mouse button state or key state to cause the node to be "pressed". 7282 * 7283 * @defaultValue false 7284 */ 7285 private ReadOnlyBooleanWrapper pressed; 7286 7287 protected final void setPressed(boolean value) { 7288 pressedPropertyImpl().set(value); 7289 } 7290 7291 public final boolean isPressed() { 7292 return pressed == null ? false : pressed.get(); 7293 } 7294 7295 public final ReadOnlyBooleanProperty pressedProperty() { 7296 return pressedPropertyImpl().getReadOnlyProperty(); 7297 } 7298 7299 private ReadOnlyBooleanWrapper pressedPropertyImpl() { 7300 if (pressed == null) { 7301 pressed = new ReadOnlyBooleanWrapper() { 7302 7303 @Override 7304 protected void invalidated() { 7305 PlatformLogger logger = Logging.getInputLogger(); 7306 if (logger.isLoggable(Level.FINER)) { 7307 logger.finer(this + " pressed=" + get()); 7308 } 7309 pseudoClassStateChanged(PRESSED_PSEUDOCLASS_STATE, get()); 7310 } 7311 7312 @Override 7313 public Object getBean() { 7314 return Node.this; 7315 } 7316 7317 @Override 7318 public String getName() { 7319 return "pressed"; 7320 } 7321 }; 7322 } 7323 return pressed; 7324 } 7325 7326 public final void setOnContextMenuRequested( 7327 EventHandler<? super ContextMenuEvent> value) { 7328 onContextMenuRequestedProperty().set(value); 7329 } 7330 7331 public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() { 7332 return (eventHandlerProperties == null) 7333 ? null : eventHandlerProperties.onContextMenuRequested(); 7334 } 7335 7336 /** 7337 * Defines a function to be called when a context menu 7338 * has been requested on this {@code Node}. 7339 * @return the event handler that is called when a context menu has been 7340 * requested on this {@code Node} 7341 * @since JavaFX 2.1 7342 */ 7343 public final ObjectProperty<EventHandler<? super ContextMenuEvent>> 7344 onContextMenuRequestedProperty() { 7345 return getEventHandlerProperties().onContextMenuRequestedProperty(); 7346 } 7347 7348 public final void setOnMouseClicked( 7349 EventHandler<? super MouseEvent> value) { 7350 onMouseClickedProperty().set(value); 7351 } 7352 7353 public final EventHandler<? super MouseEvent> getOnMouseClicked() { 7354 return (eventHandlerProperties == null) 7355 ? null : eventHandlerProperties.getOnMouseClicked(); 7356 } 7357 7358 /** 7359 * Defines a function to be called when a mouse button has been clicked 7360 * (pressed and released) on this {@code Node}. 7361 * @return the event handler that is called when a mouse button has been 7362 * clicked (pressed and released) on this {@code Node} 7363 */ 7364 public final ObjectProperty<EventHandler<? super MouseEvent>> 7365 onMouseClickedProperty() { 7366 return getEventHandlerProperties().onMouseClickedProperty(); 7367 } 7368 7369 public final void setOnMouseDragged( 7370 EventHandler<? super MouseEvent> value) { 7371 onMouseDraggedProperty().set(value); 7372 } 7373 7374 public final EventHandler<? super MouseEvent> getOnMouseDragged() { 7375 return (eventHandlerProperties == null) 7376 ? null : eventHandlerProperties.getOnMouseDragged(); 7377 } 7378 7379 /** 7380 * Defines a function to be called when a mouse button is pressed 7381 * on this {@code Node} and then dragged. 7382 * @return the event handler that is called when a mouse button is pressed 7383 * on this {@code Node} and then dragged 7384 */ 7385 public final ObjectProperty<EventHandler<? super MouseEvent>> 7386 onMouseDraggedProperty() { 7387 return getEventHandlerProperties().onMouseDraggedProperty(); 7388 } 7389 7390 public final void setOnMouseEntered( 7391 EventHandler<? super MouseEvent> value) { 7392 onMouseEnteredProperty().set(value); 7393 } 7394 7395 public final EventHandler<? super MouseEvent> getOnMouseEntered() { 7396 return (eventHandlerProperties == null) 7397 ? null : eventHandlerProperties.getOnMouseEntered(); 7398 } 7399 7400 /** 7401 * Defines a function to be called when the mouse enters this {@code Node}. 7402 * @return the event handler that is called when a mouse enters this 7403 * {@code Node} 7404 */ 7405 public final ObjectProperty<EventHandler<? super MouseEvent>> 7406 onMouseEnteredProperty() { 7407 return getEventHandlerProperties().onMouseEnteredProperty(); 7408 } 7409 7410 public final void setOnMouseExited( 7411 EventHandler<? super MouseEvent> value) { 7412 onMouseExitedProperty().set(value); 7413 } 7414 7415 public final EventHandler<? super MouseEvent> getOnMouseExited() { 7416 return (eventHandlerProperties == null) 7417 ? null : eventHandlerProperties.getOnMouseExited(); 7418 } 7419 7420 /** 7421 * Defines a function to be called when the mouse exits this {@code Node}. 7422 * @return the event handler that is called when a mouse exits this 7423 * {@code Node} 7424 */ 7425 public final ObjectProperty<EventHandler<? super MouseEvent>> 7426 onMouseExitedProperty() { 7427 return getEventHandlerProperties().onMouseExitedProperty(); 7428 } 7429 7430 public final void setOnMouseMoved( 7431 EventHandler<? super MouseEvent> value) { 7432 onMouseMovedProperty().set(value); 7433 } 7434 7435 public final EventHandler<? super MouseEvent> getOnMouseMoved() { 7436 return (eventHandlerProperties == null) 7437 ? null : eventHandlerProperties.getOnMouseMoved(); 7438 } 7439 7440 /** 7441 * Defines a function to be called when mouse cursor moves within 7442 * this {@code Node} but no buttons have been pushed. 7443 * @return the event handler that is called when a mouse cursor moves 7444 * within this {@code Node} but no buttons have been pushed 7445 */ 7446 public final ObjectProperty<EventHandler<? super MouseEvent>> 7447 onMouseMovedProperty() { 7448 return getEventHandlerProperties().onMouseMovedProperty(); 7449 } 7450 7451 public final void setOnMousePressed( 7452 EventHandler<? super MouseEvent> value) { 7453 onMousePressedProperty().set(value); 7454 } 7455 7456 public final EventHandler<? super MouseEvent> getOnMousePressed() { 7457 return (eventHandlerProperties == null) 7458 ? null : eventHandlerProperties.getOnMousePressed(); 7459 } 7460 7461 /** 7462 * Defines a function to be called when a mouse button 7463 * has been pressed on this {@code Node}. 7464 * @return the event handler that is called when a mouse button has been 7465 * pressed on this {@code Node} 7466 */ 7467 public final ObjectProperty<EventHandler<? super MouseEvent>> 7468 onMousePressedProperty() { 7469 return getEventHandlerProperties().onMousePressedProperty(); 7470 } 7471 7472 public final void setOnMouseReleased( 7473 EventHandler<? super MouseEvent> value) { 7474 onMouseReleasedProperty().set(value); 7475 } 7476 7477 public final EventHandler<? super MouseEvent> getOnMouseReleased() { 7478 return (eventHandlerProperties == null) 7479 ? null : eventHandlerProperties.getOnMouseReleased(); 7480 } 7481 7482 /** 7483 * Defines a function to be called when a mouse button 7484 * has been released on this {@code Node}. 7485 * @return the event handler that is called when a mouse button has been 7486 * released on this {@code Node} 7487 */ 7488 public final ObjectProperty<EventHandler<? super MouseEvent>> 7489 onMouseReleasedProperty() { 7490 return getEventHandlerProperties().onMouseReleasedProperty(); 7491 } 7492 7493 public final void setOnDragDetected( 7494 EventHandler<? super MouseEvent> value) { 7495 onDragDetectedProperty().set(value); 7496 } 7497 7498 public final EventHandler<? super MouseEvent> getOnDragDetected() { 7499 return (eventHandlerProperties == null) 7500 ? null : eventHandlerProperties.getOnDragDetected(); 7501 } 7502 7503 /** 7504 * Defines a function to be called when drag gesture has been 7505 * detected. This is the right place to start drag and drop operation. 7506 * @return the event handler that is called when drag gesture has been 7507 * detected 7508 */ 7509 public final ObjectProperty<EventHandler<? super MouseEvent>> 7510 onDragDetectedProperty() { 7511 return getEventHandlerProperties().onDragDetectedProperty(); 7512 } 7513 7514 public final void setOnMouseDragOver( 7515 EventHandler<? super MouseDragEvent> value) { 7516 onMouseDragOverProperty().set(value); 7517 } 7518 7519 public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() { 7520 return (eventHandlerProperties == null) 7521 ? null : eventHandlerProperties.getOnMouseDragOver(); 7522 } 7523 7524 /** 7525 * Defines a function to be called when a full press-drag-release gesture 7526 * progresses within this {@code Node}. 7527 * @return the event handler that is called when a full press-drag-release 7528 * gesture progresses within this {@code Node} 7529 * @since JavaFX 2.1 7530 */ 7531 public final ObjectProperty<EventHandler<? super MouseDragEvent>> 7532 onMouseDragOverProperty() { 7533 return getEventHandlerProperties().onMouseDragOverProperty(); 7534 } 7535 7536 public final void setOnMouseDragReleased( 7537 EventHandler<? super MouseDragEvent> value) { 7538 onMouseDragReleasedProperty().set(value); 7539 } 7540 7541 public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() { 7542 return (eventHandlerProperties == null) 7543 ? null : eventHandlerProperties.getOnMouseDragReleased(); 7544 } 7545 7546 /** 7547 * Defines a function to be called when a full press-drag-release gesture 7548 * ends (by releasing mouse button) within this {@code Node}. 7549 * @return the event handler that is called when a full press-drag-release 7550 * gesture ends (by releasing mouse button) within this {@code Node} 7551 * @since JavaFX 2.1 7552 */ 7553 public final ObjectProperty<EventHandler<? super MouseDragEvent>> 7554 onMouseDragReleasedProperty() { 7555 return getEventHandlerProperties().onMouseDragReleasedProperty(); 7556 } 7557 7558 public final void setOnMouseDragEntered( 7559 EventHandler<? super MouseDragEvent> value) { 7560 onMouseDragEnteredProperty().set(value); 7561 } 7562 7563 public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() { 7564 return (eventHandlerProperties == null) 7565 ? null : eventHandlerProperties.getOnMouseDragEntered(); 7566 } 7567 7568 /** 7569 * Defines a function to be called when a full press-drag-release gesture 7570 * enters this {@code Node}. 7571 * @return the event handler that is called when a full press-drag-release 7572 * gesture enters this {@code Node} 7573 * @since JavaFX 2.1 7574 */ 7575 public final ObjectProperty<EventHandler<? super MouseDragEvent>> 7576 onMouseDragEnteredProperty() { 7577 return getEventHandlerProperties().onMouseDragEnteredProperty(); 7578 } 7579 7580 public final void setOnMouseDragExited( 7581 EventHandler<? super MouseDragEvent> value) { 7582 onMouseDragExitedProperty().set(value); 7583 } 7584 7585 public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() { 7586 return (eventHandlerProperties == null) 7587 ? null : eventHandlerProperties.getOnMouseDragExited(); 7588 } 7589 7590 /** 7591 * Defines a function to be called when a full press-drag-release gesture 7592 * leaves this {@code Node}. 7593 * @return the event handler that is called when a full press-drag-release 7594 * gesture leaves this {@code Node} 7595 * @since JavaFX 2.1 7596 */ 7597 public final ObjectProperty<EventHandler<? super MouseDragEvent>> 7598 onMouseDragExitedProperty() { 7599 return getEventHandlerProperties().onMouseDragExitedProperty(); 7600 } 7601 7602 7603 /* ************************************************************************* 7604 * * 7605 * Gestures Handling * 7606 * * 7607 **************************************************************************/ 7608 7609 public final void setOnScrollStarted( 7610 EventHandler<? super ScrollEvent> value) { 7611 onScrollStartedProperty().set(value); 7612 } 7613 7614 public final EventHandler<? super ScrollEvent> getOnScrollStarted() { 7615 return (eventHandlerProperties == null) 7616 ? null : eventHandlerProperties.getOnScrollStarted(); 7617 } 7618 7619 /** 7620 * Defines a function to be called when a scrolling gesture is detected. 7621 * @return the event handler that is called when a scrolling gesture is 7622 * detected 7623 * @since JavaFX 2.2 7624 */ 7625 public final ObjectProperty<EventHandler<? super ScrollEvent>> 7626 onScrollStartedProperty() { 7627 return getEventHandlerProperties().onScrollStartedProperty(); 7628 } 7629 7630 public final void setOnScroll( 7631 EventHandler<? super ScrollEvent> value) { 7632 onScrollProperty().set(value); 7633 } 7634 7635 public final EventHandler<? super ScrollEvent> getOnScroll() { 7636 return (eventHandlerProperties == null) 7637 ? null : eventHandlerProperties.getOnScroll(); 7638 } 7639 7640 /** 7641 * Defines a function to be called when user performs a scrolling action. 7642 * @return the event handler that is called when user performs a scrolling 7643 * action 7644 */ 7645 public final ObjectProperty<EventHandler<? super ScrollEvent>> 7646 onScrollProperty() { 7647 return getEventHandlerProperties().onScrollProperty(); 7648 } 7649 7650 public final void setOnScrollFinished( 7651 EventHandler<? super ScrollEvent> value) { 7652 onScrollFinishedProperty().set(value); 7653 } 7654 7655 public final EventHandler<? super ScrollEvent> getOnScrollFinished() { 7656 return (eventHandlerProperties == null) 7657 ? null : eventHandlerProperties.getOnScrollFinished(); 7658 } 7659 7660 /** 7661 * Defines a function to be called when a scrolling gesture ends. 7662 * @return the event handler that is called when a scrolling gesture ends 7663 * @since JavaFX 2.2 7664 */ 7665 public final ObjectProperty<EventHandler<? super ScrollEvent>> 7666 onScrollFinishedProperty() { 7667 return getEventHandlerProperties().onScrollFinishedProperty(); 7668 } 7669 7670 public final void setOnRotationStarted( 7671 EventHandler<? super RotateEvent> value) { 7672 onRotationStartedProperty().set(value); 7673 } 7674 7675 public final EventHandler<? super RotateEvent> getOnRotationStarted() { 7676 return (eventHandlerProperties == null) 7677 ? null : eventHandlerProperties.getOnRotationStarted(); 7678 } 7679 7680 /** 7681 * Defines a function to be called when a rotation gesture is detected. 7682 * @return the event handler that is called when a rotation gesture is 7683 * detected 7684 * @since JavaFX 2.2 7685 */ 7686 public final ObjectProperty<EventHandler<? super RotateEvent>> 7687 onRotationStartedProperty() { 7688 return getEventHandlerProperties().onRotationStartedProperty(); 7689 } 7690 7691 public final void setOnRotate( 7692 EventHandler<? super RotateEvent> value) { 7693 onRotateProperty().set(value); 7694 } 7695 7696 public final EventHandler<? super RotateEvent> getOnRotate() { 7697 return (eventHandlerProperties == null) 7698 ? null : eventHandlerProperties.getOnRotate(); 7699 } 7700 7701 /** 7702 * Defines a function to be called when user performs a rotation action. 7703 * @return the event handler that is called when user performs a rotation 7704 * action 7705 * @since JavaFX 2.2 7706 */ 7707 public final ObjectProperty<EventHandler<? super RotateEvent>> 7708 onRotateProperty() { 7709 return getEventHandlerProperties().onRotateProperty(); 7710 } 7711 7712 public final void setOnRotationFinished( 7713 EventHandler<? super RotateEvent> value) { 7714 onRotationFinishedProperty().set(value); 7715 } 7716 7717 public final EventHandler<? super RotateEvent> getOnRotationFinished() { 7718 return (eventHandlerProperties == null) 7719 ? null : eventHandlerProperties.getOnRotationFinished(); 7720 } 7721 7722 /** 7723 * Defines a function to be called when a rotation gesture ends. 7724 * @return the event handler that is called when a rotation gesture ends 7725 * @since JavaFX 2.2 7726 */ 7727 public final ObjectProperty<EventHandler<? super RotateEvent>> 7728 onRotationFinishedProperty() { 7729 return getEventHandlerProperties().onRotationFinishedProperty(); 7730 } 7731 7732 public final void setOnZoomStarted( 7733 EventHandler<? super ZoomEvent> value) { 7734 onZoomStartedProperty().set(value); 7735 } 7736 7737 public final EventHandler<? super ZoomEvent> getOnZoomStarted() { 7738 return (eventHandlerProperties == null) 7739 ? null : eventHandlerProperties.getOnZoomStarted(); 7740 } 7741 7742 /** 7743 * Defines a function to be called when a zooming gesture is detected. 7744 * @return the event handler that is called when a zooming gesture is 7745 * detected 7746 * @since JavaFX 2.2 7747 */ 7748 public final ObjectProperty<EventHandler<? super ZoomEvent>> 7749 onZoomStartedProperty() { 7750 return getEventHandlerProperties().onZoomStartedProperty(); 7751 } 7752 7753 public final void setOnZoom( 7754 EventHandler<? super ZoomEvent> value) { 7755 onZoomProperty().set(value); 7756 } 7757 7758 public final EventHandler<? super ZoomEvent> getOnZoom() { 7759 return (eventHandlerProperties == null) 7760 ? null : eventHandlerProperties.getOnZoom(); 7761 } 7762 7763 /** 7764 * Defines a function to be called when user performs a zooming action. 7765 * @return the event handler that is called when user performs a zooming 7766 * action 7767 * @since JavaFX 2.2 7768 */ 7769 public final ObjectProperty<EventHandler<? super ZoomEvent>> 7770 onZoomProperty() { 7771 return getEventHandlerProperties().onZoomProperty(); 7772 } 7773 7774 public final void setOnZoomFinished( 7775 EventHandler<? super ZoomEvent> value) { 7776 onZoomFinishedProperty().set(value); 7777 } 7778 7779 public final EventHandler<? super ZoomEvent> getOnZoomFinished() { 7780 return (eventHandlerProperties == null) 7781 ? null : eventHandlerProperties.getOnZoomFinished(); 7782 } 7783 7784 /** 7785 * Defines a function to be called when a zooming gesture ends. 7786 * @return the event handler that is called when a zooming gesture ends 7787 * @since JavaFX 2.2 7788 */ 7789 public final ObjectProperty<EventHandler<? super ZoomEvent>> 7790 onZoomFinishedProperty() { 7791 return getEventHandlerProperties().onZoomFinishedProperty(); 7792 } 7793 7794 public final void setOnSwipeUp( 7795 EventHandler<? super SwipeEvent> value) { 7796 onSwipeUpProperty().set(value); 7797 } 7798 7799 public final EventHandler<? super SwipeEvent> getOnSwipeUp() { 7800 return (eventHandlerProperties == null) 7801 ? null : eventHandlerProperties.getOnSwipeUp(); 7802 } 7803 7804 /** 7805 * Defines a function to be called when an upward swipe gesture 7806 * centered over this node happens. 7807 * @return the event handler that is called when an upward swipe gesture 7808 * centered over this node happens 7809 * @since JavaFX 2.2 7810 */ 7811 public final ObjectProperty<EventHandler<? super SwipeEvent>> 7812 onSwipeUpProperty() { 7813 return getEventHandlerProperties().onSwipeUpProperty(); 7814 } 7815 7816 public final void setOnSwipeDown( 7817 EventHandler<? super SwipeEvent> value) { 7818 onSwipeDownProperty().set(value); 7819 } 7820 7821 public final EventHandler<? super SwipeEvent> getOnSwipeDown() { 7822 return (eventHandlerProperties == null) 7823 ? null : eventHandlerProperties.getOnSwipeDown(); 7824 } 7825 7826 /** 7827 * Defines a function to be called when a downward swipe gesture 7828 * centered over this node happens. 7829 * @return the event handler that is called when a downward swipe gesture 7830 * centered over this node happens 7831 * @since JavaFX 2.2 7832 */ 7833 public final ObjectProperty<EventHandler<? super SwipeEvent>> 7834 onSwipeDownProperty() { 7835 return getEventHandlerProperties().onSwipeDownProperty(); 7836 } 7837 7838 public final void setOnSwipeLeft( 7839 EventHandler<? super SwipeEvent> value) { 7840 onSwipeLeftProperty().set(value); 7841 } 7842 7843 public final EventHandler<? super SwipeEvent> getOnSwipeLeft() { 7844 return (eventHandlerProperties == null) 7845 ? null : eventHandlerProperties.getOnSwipeLeft(); 7846 } 7847 7848 /** 7849 * Defines a function to be called when a leftward swipe gesture 7850 * centered over this node happens. 7851 * @return the event handler that is called when a leftward swipe gesture 7852 * centered over this node happens 7853 * @since JavaFX 2.2 7854 */ 7855 public final ObjectProperty<EventHandler<? super SwipeEvent>> 7856 onSwipeLeftProperty() { 7857 return getEventHandlerProperties().onSwipeLeftProperty(); 7858 } 7859 7860 public final void setOnSwipeRight( 7861 EventHandler<? super SwipeEvent> value) { 7862 onSwipeRightProperty().set(value); 7863 } 7864 7865 public final EventHandler<? super SwipeEvent> getOnSwipeRight() { 7866 return (eventHandlerProperties == null) 7867 ? null : eventHandlerProperties.getOnSwipeRight(); 7868 } 7869 7870 /** 7871 * Defines a function to be called when an rightward swipe gesture 7872 * centered over this node happens. 7873 * @return the event handler that is called when an rightward swipe gesture 7874 * centered over this node happens 7875 * @since JavaFX 2.2 7876 */ 7877 public final ObjectProperty<EventHandler<? super SwipeEvent>> 7878 onSwipeRightProperty() { 7879 return getEventHandlerProperties().onSwipeRightProperty(); 7880 } 7881 7882 7883 /* ************************************************************************* 7884 * * 7885 * Touch Handling * 7886 * * 7887 **************************************************************************/ 7888 7889 public final void setOnTouchPressed( 7890 EventHandler<? super TouchEvent> value) { 7891 onTouchPressedProperty().set(value); 7892 } 7893 7894 public final EventHandler<? super TouchEvent> getOnTouchPressed() { 7895 return (eventHandlerProperties == null) 7896 ? null : eventHandlerProperties.getOnTouchPressed(); 7897 } 7898 7899 /** 7900 * Defines a function to be called when a new touch point is pressed. 7901 * @return the event handler that is called when a new touch point is pressed 7902 * @since JavaFX 2.2 7903 */ 7904 public final ObjectProperty<EventHandler<? super TouchEvent>> 7905 onTouchPressedProperty() { 7906 return getEventHandlerProperties().onTouchPressedProperty(); 7907 } 7908 7909 public final void setOnTouchMoved( 7910 EventHandler<? super TouchEvent> value) { 7911 onTouchMovedProperty().set(value); 7912 } 7913 7914 public final EventHandler<? super TouchEvent> getOnTouchMoved() { 7915 return (eventHandlerProperties == null) 7916 ? null : eventHandlerProperties.getOnTouchMoved(); 7917 } 7918 7919 /** 7920 * Defines a function to be called when a touch point is moved. 7921 * @return the event handler that is called when a touch point is moved 7922 * @since JavaFX 2.2 7923 */ 7924 public final ObjectProperty<EventHandler<? super TouchEvent>> 7925 onTouchMovedProperty() { 7926 return getEventHandlerProperties().onTouchMovedProperty(); 7927 } 7928 7929 public final void setOnTouchReleased( 7930 EventHandler<? super TouchEvent> value) { 7931 onTouchReleasedProperty().set(value); 7932 } 7933 7934 public final EventHandler<? super TouchEvent> getOnTouchReleased() { 7935 return (eventHandlerProperties == null) 7936 ? null : eventHandlerProperties.getOnTouchReleased(); 7937 } 7938 7939 /** 7940 * Defines a function to be called when a touch point is released. 7941 * @return the event handler that is called when a touch point is released 7942 * @since JavaFX 2.2 7943 */ 7944 public final ObjectProperty<EventHandler<? super TouchEvent>> 7945 onTouchReleasedProperty() { 7946 return getEventHandlerProperties().onTouchReleasedProperty(); 7947 } 7948 7949 public final void setOnTouchStationary( 7950 EventHandler<? super TouchEvent> value) { 7951 onTouchStationaryProperty().set(value); 7952 } 7953 7954 public final EventHandler<? super TouchEvent> getOnTouchStationary() { 7955 return (eventHandlerProperties == null) 7956 ? null : eventHandlerProperties.getOnTouchStationary(); 7957 } 7958 7959 /** 7960 * Defines a function to be called when a touch point stays pressed and 7961 * still. 7962 * @return the event handler that is called when a touch point stays pressed 7963 * and still 7964 * @since JavaFX 2.2 7965 */ 7966 public final ObjectProperty<EventHandler<? super TouchEvent>> 7967 onTouchStationaryProperty() { 7968 return getEventHandlerProperties().onTouchStationaryProperty(); 7969 } 7970 7971 /* ************************************************************************* 7972 * * 7973 * Keyboard Handling * 7974 * * 7975 **************************************************************************/ 7976 7977 public final void setOnKeyPressed( 7978 EventHandler<? super KeyEvent> value) { 7979 onKeyPressedProperty().set(value); 7980 } 7981 7982 public final EventHandler<? super KeyEvent> getOnKeyPressed() { 7983 return (eventHandlerProperties == null) 7984 ? null : eventHandlerProperties.getOnKeyPressed(); 7985 } 7986 7987 /** 7988 * Defines a function to be called when this {@code Node} or its child 7989 * {@code Node} has input focus and a key has been pressed. The function 7990 * is called only if the event hasn't been already consumed during its 7991 * capturing or bubbling phase. 7992 * @return the event handler that is called when this {@code Node} or its 7993 * child {@code Node} has input focus and a key has been pressed 7994 */ 7995 public final ObjectProperty<EventHandler<? super KeyEvent>> 7996 onKeyPressedProperty() { 7997 return getEventHandlerProperties().onKeyPressedProperty(); 7998 } 7999 8000 public final void setOnKeyReleased( 8001 EventHandler<? super KeyEvent> value) { 8002 onKeyReleasedProperty().set(value); 8003 } 8004 8005 public final EventHandler<? super KeyEvent> getOnKeyReleased() { 8006 return (eventHandlerProperties == null) 8007 ? null : eventHandlerProperties.getOnKeyReleased(); 8008 } 8009 8010 /** 8011 * Defines a function to be called when this {@code Node} or its child 8012 * {@code Node} has input focus and a key has been released. The function 8013 * is called only if the event hasn't been already consumed during its 8014 * capturing or bubbling phase. 8015 * @return the event handler that is called when this {@code Node} or its 8016 * child {@code Node} has input focus and a key has been released 8017 */ 8018 public final ObjectProperty<EventHandler<? super KeyEvent>> 8019 onKeyReleasedProperty() { 8020 return getEventHandlerProperties().onKeyReleasedProperty(); 8021 } 8022 8023 public final void setOnKeyTyped( 8024 EventHandler<? super KeyEvent> value) { 8025 onKeyTypedProperty().set(value); 8026 } 8027 8028 public final EventHandler<? super KeyEvent> getOnKeyTyped() { 8029 return (eventHandlerProperties == null) 8030 ? null : eventHandlerProperties.getOnKeyTyped(); 8031 } 8032 8033 /** 8034 * Defines a function to be called when this {@code Node} or its child 8035 * {@code Node} has input focus and a key has been typed. The function 8036 * is called only if the event hasn't been already consumed during its 8037 * capturing or bubbling phase. 8038 * @return the event handler that is called when this {@code Node} or its 8039 * child {@code Node} has input focus and a key has been typed 8040 */ 8041 public final ObjectProperty<EventHandler<? super KeyEvent>> 8042 onKeyTypedProperty() { 8043 return getEventHandlerProperties().onKeyTypedProperty(); 8044 } 8045 8046 /* ************************************************************************* 8047 * * 8048 * Input Method Handling * 8049 * * 8050 **************************************************************************/ 8051 8052 public final void setOnInputMethodTextChanged( 8053 EventHandler<? super InputMethodEvent> value) { 8054 onInputMethodTextChangedProperty().set(value); 8055 } 8056 8057 public final EventHandler<? super InputMethodEvent> 8058 getOnInputMethodTextChanged() { 8059 return (eventHandlerProperties == null) 8060 ? null : eventHandlerProperties.getOnInputMethodTextChanged(); 8061 } 8062 8063 /** 8064 * Defines a function to be called when this {@code Node} 8065 * has input focus and the input method text has changed. If this 8066 * function is not defined in this {@code Node}, then it 8067 * receives the result string of the input method composition as a 8068 * series of {@code onKeyTyped} function calls. 8069 * <p> 8070 * When the {@code Node} loses the input focus, the JavaFX runtime 8071 * automatically commits the existing composed text if any. 8072 * </p> 8073 * @return the event handler that is called when this {@code Node} has input 8074 * focus and the input method text has changed 8075 */ 8076 public final ObjectProperty<EventHandler<? super InputMethodEvent>> 8077 onInputMethodTextChangedProperty() { 8078 return getEventHandlerProperties().onInputMethodTextChangedProperty(); 8079 } 8080 8081 public final void setInputMethodRequests(InputMethodRequests value) { 8082 inputMethodRequestsProperty().set(value); 8083 } 8084 8085 public final InputMethodRequests getInputMethodRequests() { 8086 return (miscProperties == null) 8087 ? DEFAULT_INPUT_METHOD_REQUESTS 8088 : miscProperties.getInputMethodRequests(); 8089 } 8090 8091 /** 8092 * Property holding InputMethodRequests. 8093 * 8094 * @return InputMethodRequestsProperty 8095 */ 8096 public final ObjectProperty<InputMethodRequests> inputMethodRequestsProperty() { 8097 return getMiscProperties().inputMethodRequestsProperty(); 8098 } 8099 8100 /*************************************************************************** 8101 * * 8102 * Focus Traversal * 8103 * * 8104 **************************************************************************/ 8105 8106 /** 8107 * Special boolean property which allows for atomic focus change. 8108 * Focus change means defocusing the old focus owner and focusing a new 8109 * one. With a usual property, defocusing the old node fires the value 8110 * changed event and user code can react with something that breaks 8111 * focusability of the new node, or even remove the new node from the scene. 8112 * This leads to various error states. This property allows for setting 8113 * the state without firing the event. The focus change first sets both 8114 * properties and then fires both events. This makes the focus change look 8115 * like an atomic operation - when the old node is notified to loose focus, 8116 * the new node is already focused. 8117 */ 8118 final class FocusedProperty extends ReadOnlyBooleanPropertyBase { 8119 private boolean value; 8120 private boolean valid = true; 8121 private boolean needsChangeEvent = false; 8122 8123 public void store(final boolean value) { 8124 if (value != this.value) { 8125 this.value = value; 8126 markInvalid(); 8127 } 8128 } 8129 8130 public void notifyListeners() { 8131 if (needsChangeEvent) { 8132 fireValueChangedEvent(); 8133 needsChangeEvent = false; 8134 } 8135 } 8136 8137 private void markInvalid() { 8138 if (valid) { 8139 valid = false; 8140 8141 pseudoClassStateChanged(FOCUSED_PSEUDOCLASS_STATE, get()); 8142 PlatformLogger logger = Logging.getFocusLogger(); 8143 if (logger.isLoggable(Level.FINE)) { 8144 logger.fine(this + " focused=" + get()); 8145 } 8146 8147 needsChangeEvent = true; 8148 8149 notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUSED); 8150 } 8151 } 8152 8153 @Override 8154 public boolean get() { 8155 valid = true; 8156 return value; 8157 } 8158 8159 @Override 8160 public Object getBean() { 8161 return Node.this; 8162 } 8163 8164 @Override 8165 public String getName() { 8166 return "focused"; 8167 } 8168 } 8169 8170 /** 8171 * Indicates whether this {@code Node} currently has the input focus. 8172 * To have the input focus, a node must be the {@code Scene}'s focus 8173 * owner, and the scene must be in a {@code Stage} that is visible 8174 * and active. See {@link #requestFocus()} for more information. 8175 * 8176 * @see #requestFocus() 8177 * @defaultValue false 8178 */ 8179 private FocusedProperty focused; 8180 8181 protected final void setFocused(boolean value) { 8182 FocusedProperty fp = focusedPropertyImpl(); 8183 if (fp.value != value) { 8184 fp.store(value); 8185 fp.notifyListeners(); 8186 } 8187 } 8188 8189 public final boolean isFocused() { 8190 return focused == null ? false : focused.get(); 8191 } 8192 8193 public final ReadOnlyBooleanProperty focusedProperty() { 8194 return focusedPropertyImpl(); 8195 } 8196 8197 private FocusedProperty focusedPropertyImpl() { 8198 if (focused == null) { 8199 focused = new FocusedProperty(); 8200 } 8201 return focused; 8202 } 8203 8204 /** 8205 * Specifies whether this {@code Node} should be a part of focus traversal 8206 * cycle. When this property is {@code true} focus can be moved to this 8207 * {@code Node} and from this {@code Node} using regular focus traversal 8208 * keys. On a desktop such keys are usually {@code TAB} for moving focus 8209 * forward and {@code SHIFT+TAB} for moving focus backward. 8210 * 8211 * When a {@code Scene} is created, the system gives focus to a 8212 * {@code Node} whose {@code focusTraversable} variable is true 8213 * and that is eligible to receive the focus, 8214 * unless the focus had been set explicitly via a call 8215 * to {@link #requestFocus()}. 8216 * 8217 * @see #requestFocus() 8218 * @defaultValue false 8219 */ 8220 private BooleanProperty focusTraversable; 8221 8222 public final void setFocusTraversable(boolean value) { 8223 focusTraversableProperty().set(value); 8224 } 8225 public final boolean isFocusTraversable() { 8226 return focusTraversable == null ? false : focusTraversable.get(); 8227 } 8228 8229 public final BooleanProperty focusTraversableProperty() { 8230 if (focusTraversable == null) { 8231 focusTraversable = new StyleableBooleanProperty(false) { 8232 8233 @Override 8234 public void invalidated() { 8235 Scene _scene = getScene(); 8236 if (_scene != null) { 8237 if (get()) { 8238 _scene.initializeInternalEventDispatcher(); 8239 } 8240 focusSetDirty(_scene); 8241 } 8242 } 8243 8244 @Override 8245 public CssMetaData getCssMetaData() { 8246 return StyleableProperties.FOCUS_TRAVERSABLE; 8247 } 8248 8249 @Override 8250 public Object getBean() { 8251 return Node.this; 8252 } 8253 8254 @Override 8255 public String getName() { 8256 return "focusTraversable"; 8257 } 8258 }; 8259 } 8260 return focusTraversable; 8261 } 8262 8263 /** 8264 * Called when something has changed on this node that *may* have made the 8265 * scene's focus dirty. This covers the cases where this node is the focus 8266 * owner and it may have lost eligibility, or it's traversable and it may 8267 * have gained eligibility. Note that we do not want to use disabled 8268 * or treeVisible here, as this function is called from their 8269 * "on invalidate" triggers, and using them will cause them to be 8270 * revalidated. The pulse will revalidate everything and make the final 8271 * determination. 8272 */ 8273 private void focusSetDirty(Scene s) { 8274 if (s != null && 8275 (this == s.getFocusOwner() || isFocusTraversable())) { 8276 s.setFocusDirty(true); 8277 } 8278 } 8279 8280 /** 8281 * Requests that this {@code Node} get the input focus, and that this 8282 * {@code Node}'s top-level ancestor become the focused window. To be 8283 * eligible to receive the focus, the node must be part of a scene, it and 8284 * all of its ancestors must be visible, and it must not be disabled. 8285 * If this node is eligible, this function will cause it to become this 8286 * {@code Scene}'s "focus owner". Each scene has at most one focus owner 8287 * node. The focus owner will not actually have the input focus, however, 8288 * unless the scene belongs to a {@code Stage} that is both visible 8289 * and active. 8290 */ 8291 public void requestFocus() { 8292 if (getScene() != null) { 8293 getScene().requestFocus(this); 8294 } 8295 } 8296 8297 /** 8298 * Traverses from this node in the direction indicated. Note that this 8299 * node need not actually have the focus, nor need it be focusTraversable. 8300 * However, the node must be part of a scene, otherwise this request 8301 * is ignored. 8302 */ 8303 final boolean traverse(Direction dir) { 8304 if (getScene() == null) { 8305 return false; 8306 } 8307 return getScene().traverse(this, dir); 8308 } 8309 8310 //////////////////////////// 8311 // Private Implementation 8312 //////////////////////////// 8313 8314 /** 8315 * Returns a string representation for the object. 8316 * @return a string representation for the object. 8317 */ 8318 @Override 8319 public String toString() { 8320 String klassName = getClass().getName(); 8321 String simpleName = klassName.substring(klassName.lastIndexOf('.')+1); 8322 StringBuilder sbuf = new StringBuilder(simpleName); 8323 boolean hasId = id != null && !"".equals(getId()); 8324 boolean hasStyleClass = !getStyleClass().isEmpty(); 8325 8326 if (!hasId) { 8327 sbuf.append('@'); 8328 sbuf.append(Integer.toHexString(hashCode())); 8329 } else { 8330 sbuf.append("[id="); 8331 sbuf.append(getId()); 8332 if (!hasStyleClass) sbuf.append("]"); 8333 } 8334 if (hasStyleClass) { 8335 if (!hasId) sbuf.append('['); 8336 else sbuf.append(", "); 8337 sbuf.append("styleClass="); 8338 sbuf.append(getStyleClass()); 8339 sbuf.append("]"); 8340 } 8341 return sbuf.toString(); 8342 } 8343 8344 private void preprocessMouseEvent(MouseEvent e) { 8345 final EventType<?> eventType = e.getEventType(); 8346 if (eventType == MouseEvent.MOUSE_PRESSED) { 8347 for (Node n = this; n != null; n = n.getParent()) { 8348 n.setPressed(e.isPrimaryButtonDown()); 8349 } 8350 return; 8351 } 8352 if (eventType == MouseEvent.MOUSE_RELEASED) { 8353 for (Node n = this; n != null; n = n.getParent()) { 8354 n.setPressed(e.isPrimaryButtonDown()); 8355 } 8356 return; 8357 } 8358 8359 if (e.getTarget() == this) { 8360 // the mouse event types are translated only when the node uses 8361 // its internal event dispatcher, so both entered / exited variants 8362 // are possible here 8363 8364 if ((eventType == MouseEvent.MOUSE_ENTERED) 8365 || (eventType == MouseEvent.MOUSE_ENTERED_TARGET)) { 8366 setHover(true); 8367 return; 8368 } 8369 8370 if ((eventType == MouseEvent.MOUSE_EXITED) 8371 || (eventType == MouseEvent.MOUSE_EXITED_TARGET)) { 8372 setHover(false); 8373 return; 8374 } 8375 } 8376 } 8377 8378 void markDirtyLayoutBranch() { 8379 Parent p = getParent(); 8380 while (p != null && p.layoutFlag == LayoutFlags.CLEAN) { 8381 p.setLayoutFlag(LayoutFlags.DIRTY_BRANCH); 8382 if (p.isSceneRoot()) { 8383 Toolkit.getToolkit().requestNextPulse(); 8384 if (getSubScene() != null) { 8385 getSubScene().setDirtyLayout(p); 8386 } 8387 } 8388 p = p.getParent(); 8389 } 8390 8391 } 8392 8393 private boolean isWindowShowing() { 8394 Scene s = getScene(); 8395 if (s == null) return false; 8396 Window w = s.getWindow(); 8397 return w != null && w.isShowing(); 8398 } 8399 8400 private void updateTreeShowing() { 8401 setTreeShowing(isTreeVisible() && isWindowShowing()); 8402 } 8403 8404 private boolean treeShowing; 8405 private TreeShowingPropertyReadOnly treeShowingRO; 8406 8407 final void setTreeShowing(boolean value) { 8408 if (treeShowing != value) { 8409 treeShowing = value; 8410 ((TreeShowingPropertyReadOnly) treeShowingProperty()).invalidate(); 8411 } 8412 } 8413 8414 final boolean isTreeShowing() { 8415 return treeShowingProperty().get(); 8416 } 8417 8418 final BooleanExpression treeShowingProperty() { 8419 if (treeShowingRO == null) { 8420 treeShowingRO = new TreeShowingPropertyReadOnly(); 8421 } 8422 return treeShowingRO; 8423 } 8424 8425 class TreeShowingPropertyReadOnly extends BooleanExpression { 8426 8427 private ExpressionHelper<Boolean> helper; 8428 private boolean valid; 8429 8430 @Override 8431 public void addListener(InvalidationListener listener) { 8432 helper = ExpressionHelper.addListener(helper, this, listener); 8433 } 8434 8435 @Override 8436 public void removeListener(InvalidationListener listener) { 8437 helper = ExpressionHelper.removeListener(helper, listener); 8438 } 8439 8440 @Override 8441 public void addListener(ChangeListener<? super Boolean> listener) { 8442 helper = ExpressionHelper.addListener(helper, this, listener); 8443 } 8444 8445 @Override 8446 public void removeListener(ChangeListener<? super Boolean> listener) { 8447 helper = ExpressionHelper.removeListener(helper, listener); 8448 } 8449 8450 protected void invalidate() { 8451 if (valid) { 8452 valid = false; 8453 ExpressionHelper.fireValueChangedEvent(helper); 8454 } 8455 } 8456 8457 @Override 8458 public boolean get() { 8459 valid = true; 8460 return Node.this.treeShowing; 8461 } 8462 8463 } 8464 8465 private void updateTreeVisible(boolean parentChanged) { 8466 boolean isTreeVisible = isVisible(); 8467 final Node parentNode = getParent() != null ? getParent() : 8468 clipParent != null ? clipParent : 8469 getSubScene() != null ? getSubScene() : null; 8470 if (isTreeVisible) { 8471 isTreeVisible = parentNode == null || parentNode.isTreeVisible(); 8472 } 8473 // When the parent has changed to visible and we have unsynchornized visibility, 8474 // we have to synchronize, because the rendering will now pass throught the newly-visible parent 8475 // Otherwise an invisible Node might get rendered 8476 if (parentChanged && parentNode != null && parentNode.isTreeVisible() 8477 && isDirty(DirtyBits.NODE_VISIBLE)) { 8478 addToSceneDirtyList(); 8479 } 8480 setTreeVisible(isTreeVisible); 8481 8482 updateTreeShowing(); 8483 } 8484 8485 private boolean treeVisible; 8486 private TreeVisiblePropertyReadOnly treeVisibleRO; 8487 8488 final void setTreeVisible(boolean value) { 8489 if (treeVisible != value) { 8490 treeVisible = value; 8491 updateCanReceiveFocus(); 8492 focusSetDirty(getScene()); 8493 if (getClip() != null) { 8494 getClip().updateTreeVisible(true); 8495 } 8496 if (treeVisible && !isDirtyEmpty()) { 8497 addToSceneDirtyList(); 8498 } 8499 ((TreeVisiblePropertyReadOnly) treeVisibleProperty()).invalidate(); 8500 if (Node.this instanceof SubScene) { 8501 Node subSceneRoot = ((SubScene)Node.this).getRoot(); 8502 if (subSceneRoot != null) { 8503 // SubScene.getRoot() is only null if it's constructor 8504 // has not finished. 8505 subSceneRoot.setTreeVisible(value && subSceneRoot.isVisible()); 8506 } 8507 } 8508 } 8509 } 8510 8511 final boolean isTreeVisible() { 8512 return treeVisibleProperty().get(); 8513 } 8514 8515 final BooleanExpression treeVisibleProperty() { 8516 if (treeVisibleRO == null) { 8517 treeVisibleRO = new TreeVisiblePropertyReadOnly(); 8518 } 8519 return treeVisibleRO; 8520 } 8521 8522 class TreeVisiblePropertyReadOnly extends BooleanExpression { 8523 8524 private ExpressionHelper<Boolean> helper; 8525 private boolean valid; 8526 8527 @Override 8528 public void addListener(InvalidationListener listener) { 8529 helper = ExpressionHelper.addListener(helper, this, listener); 8530 } 8531 8532 @Override 8533 public void removeListener(InvalidationListener listener) { 8534 helper = ExpressionHelper.removeListener(helper, listener); 8535 } 8536 8537 @Override 8538 public void addListener(ChangeListener<? super Boolean> listener) { 8539 helper = ExpressionHelper.addListener(helper, this, listener); 8540 } 8541 8542 @Override 8543 public void removeListener(ChangeListener<? super Boolean> listener) { 8544 helper = ExpressionHelper.removeListener(helper, listener); 8545 } 8546 8547 protected void invalidate() { 8548 if (valid) { 8549 valid = false; 8550 ExpressionHelper.fireValueChangedEvent(helper); 8551 } 8552 } 8553 8554 @Override 8555 public boolean get() { 8556 valid = true; 8557 return Node.this.treeVisible; 8558 } 8559 8560 } 8561 8562 private boolean canReceiveFocus = false; 8563 8564 private void setCanReceiveFocus(boolean value) { 8565 canReceiveFocus = value; 8566 } 8567 8568 final boolean isCanReceiveFocus() { 8569 return canReceiveFocus; 8570 } 8571 8572 private void updateCanReceiveFocus() { 8573 setCanReceiveFocus(getScene() != null 8574 && !isDisabled() 8575 && isTreeVisible()); 8576 } 8577 8578 // for indenting messages based on scene-graph depth 8579 String indent() { 8580 String indent = ""; 8581 Parent p = this.getParent(); 8582 while (p != null) { 8583 indent += " "; 8584 p = p.getParent(); 8585 } 8586 return indent; 8587 } 8588 8589 /* 8590 * Should we underline the mnemonic character? 8591 */ 8592 private BooleanProperty showMnemonics; 8593 8594 final void setShowMnemonics(boolean value) { 8595 showMnemonicsProperty().set(value); 8596 } 8597 8598 final boolean isShowMnemonics() { 8599 return showMnemonics == null ? false : showMnemonics.get(); 8600 } 8601 8602 final BooleanProperty showMnemonicsProperty() { 8603 if (showMnemonics == null) { 8604 showMnemonics = new BooleanPropertyBase(false) { 8605 8606 @Override 8607 protected void invalidated() { 8608 pseudoClassStateChanged(SHOW_MNEMONICS_PSEUDOCLASS_STATE, get()); 8609 } 8610 8611 @Override 8612 public Object getBean() { 8613 return Node.this; 8614 } 8615 8616 @Override 8617 public String getName() { 8618 return "showMnemonics"; 8619 } 8620 }; 8621 } 8622 return showMnemonics; 8623 } 8624 8625 8626 /** 8627 * References a node that is a labelFor this node. 8628 * Accessible via a NodeAccessor. See Label.labelFor for details. 8629 */ 8630 private Node labeledBy = null; 8631 8632 8633 /*************************************************************************** 8634 * * 8635 * Event Dispatch * 8636 * * 8637 **************************************************************************/ 8638 8639 // PENDING_DOC_REVIEW 8640 /** 8641 * Specifies the event dispatcher for this node. The default event 8642 * dispatcher sends the received events to the registered event handlers and 8643 * filters. When replacing the value with a new {@code EventDispatcher}, 8644 * the new dispatcher should forward events to the replaced dispatcher 8645 * to maintain the node's default event handling behavior. 8646 */ 8647 private ObjectProperty<EventDispatcher> eventDispatcher; 8648 8649 public final void setEventDispatcher(EventDispatcher value) { 8650 eventDispatcherProperty().set(value); 8651 } 8652 8653 public final EventDispatcher getEventDispatcher() { 8654 return eventDispatcherProperty().get(); 8655 } 8656 8657 public final ObjectProperty<EventDispatcher> eventDispatcherProperty() { 8658 initializeInternalEventDispatcher(); 8659 return eventDispatcher; 8660 } 8661 8662 private NodeEventDispatcher internalEventDispatcher; 8663 8664 // PENDING_DOC_REVIEW 8665 /** 8666 * Registers an event handler to this node. The handler is called when the 8667 * node receives an {@code Event} of the specified type during the bubbling 8668 * phase of event delivery. 8669 * 8670 * @param <T> the specific event class of the handler 8671 * @param eventType the type of the events to receive by the handler 8672 * @param eventHandler the handler to register 8673 * @throws NullPointerException if the event type or handler is null 8674 */ 8675 public final <T extends Event> void addEventHandler( 8676 final EventType<T> eventType, 8677 final EventHandler<? super T> eventHandler) { 8678 getInternalEventDispatcher().getEventHandlerManager() 8679 .addEventHandler(eventType, eventHandler); 8680 } 8681 8682 // PENDING_DOC_REVIEW 8683 /** 8684 * Unregisters a previously registered event handler from this node. One 8685 * handler might have been registered for different event types, so the 8686 * caller needs to specify the particular event type from which to 8687 * unregister the handler. 8688 * 8689 * @param <T> the specific event class of the handler 8690 * @param eventType the event type from which to unregister 8691 * @param eventHandler the handler to unregister 8692 * @throws NullPointerException if the event type or handler is null 8693 */ 8694 public final <T extends Event> void removeEventHandler( 8695 final EventType<T> eventType, 8696 final EventHandler<? super T> eventHandler) { 8697 getInternalEventDispatcher() 8698 .getEventHandlerManager() 8699 .removeEventHandler(eventType, eventHandler); 8700 } 8701 8702 // PENDING_DOC_REVIEW 8703 /** 8704 * Registers an event filter to this node. The filter is called when the 8705 * node receives an {@code Event} of the specified type during the capturing 8706 * phase of event delivery. 8707 * 8708 * @param <T> the specific event class of the filter 8709 * @param eventType the type of the events to receive by the filter 8710 * @param eventFilter the filter to register 8711 * @throws NullPointerException if the event type or filter is null 8712 */ 8713 public final <T extends Event> void addEventFilter( 8714 final EventType<T> eventType, 8715 final EventHandler<? super T> eventFilter) { 8716 getInternalEventDispatcher().getEventHandlerManager() 8717 .addEventFilter(eventType, eventFilter); 8718 } 8719 8720 // PENDING_DOC_REVIEW 8721 /** 8722 * Unregisters a previously registered event filter from this node. One 8723 * filter might have been registered for different event types, so the 8724 * caller needs to specify the particular event type from which to 8725 * unregister the filter. 8726 * 8727 * @param <T> the specific event class of the filter 8728 * @param eventType the event type from which to unregister 8729 * @param eventFilter the filter to unregister 8730 * @throws NullPointerException if the event type or filter is null 8731 */ 8732 public final <T extends Event> void removeEventFilter( 8733 final EventType<T> eventType, 8734 final EventHandler<? super T> eventFilter) { 8735 getInternalEventDispatcher().getEventHandlerManager() 8736 .removeEventFilter(eventType, eventFilter); 8737 } 8738 8739 /** 8740 * Sets the handler to use for this event type. There can only be one such handler 8741 * specified at a time. This handler is guaranteed to be called as the last, after 8742 * handlers added using {@link #addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}. 8743 * This is used for registering the user-defined onFoo event handlers. 8744 * 8745 * @param <T> the specific event class of the handler 8746 * @param eventType the event type to associate with the given eventHandler 8747 * @param eventHandler the handler to register, or null to unregister 8748 * @throws NullPointerException if the event type is null 8749 */ 8750 protected final <T extends Event> void setEventHandler( 8751 final EventType<T> eventType, 8752 final EventHandler<? super T> eventHandler) { 8753 getInternalEventDispatcher().getEventHandlerManager() 8754 .setEventHandler(eventType, eventHandler); 8755 } 8756 8757 private NodeEventDispatcher getInternalEventDispatcher() { 8758 initializeInternalEventDispatcher(); 8759 return internalEventDispatcher; 8760 } 8761 8762 private void initializeInternalEventDispatcher() { 8763 if (internalEventDispatcher == null) { 8764 internalEventDispatcher = createInternalEventDispatcher(); 8765 eventDispatcher = new SimpleObjectProperty<EventDispatcher>( 8766 Node.this, 8767 "eventDispatcher", 8768 internalEventDispatcher); 8769 } 8770 } 8771 8772 private NodeEventDispatcher createInternalEventDispatcher() { 8773 return new NodeEventDispatcher(this); 8774 } 8775 8776 /** 8777 * Event dispatcher for invoking preprocessing of mouse events 8778 */ 8779 private EventDispatcher preprocessMouseEventDispatcher; 8780 8781 // PENDING_DOC_REVIEW 8782 /** 8783 * Construct an event dispatch chain for this node. The event dispatch chain 8784 * contains all event dispatchers from the stage to this node. 8785 * 8786 * @param tail the initial chain to build from 8787 * @return the resulting event dispatch chain for this node 8788 */ 8789 @Override 8790 public EventDispatchChain buildEventDispatchChain( 8791 EventDispatchChain tail) { 8792 8793 if (preprocessMouseEventDispatcher == null) { 8794 preprocessMouseEventDispatcher = (event, tail1) -> { 8795 event = tail1.dispatchEvent(event); 8796 if (event instanceof MouseEvent) { 8797 preprocessMouseEvent((MouseEvent) event); 8798 } 8799 8800 return event; 8801 }; 8802 } 8803 8804 tail = tail.prepend(preprocessMouseEventDispatcher); 8805 8806 // prepend all event dispatchers from this node to the root 8807 Node curNode = this; 8808 do { 8809 if (curNode.eventDispatcher != null) { 8810 final EventDispatcher eventDispatcherValue = 8811 curNode.eventDispatcher.get(); 8812 if (eventDispatcherValue != null) { 8813 tail = tail.prepend(eventDispatcherValue); 8814 } 8815 } 8816 final Node curParent = curNode.getParent(); 8817 curNode = curParent != null ? curParent : curNode.getSubScene(); 8818 } while (curNode != null); 8819 8820 if (getScene() != null) { 8821 // prepend scene's dispatch chain 8822 tail = getScene().buildEventDispatchChain(tail); 8823 } 8824 8825 return tail; 8826 } 8827 8828 // PENDING_DOC_REVIEW 8829 /** 8830 * Fires the specified event. By default the event will travel through the 8831 * hierarchy from the stage to this node. Any event filter encountered will 8832 * be notified and can consume the event. If not consumed by the filters, 8833 * the event handlers on this node are notified. If these don't consume the 8834 * event either, the event will travel back the same path it arrived to 8835 * this node. All event handlers encountered are called and can consume the 8836 * event. 8837 * <p> 8838 * This method must be called on the FX user thread. 8839 * 8840 * @param event the event to fire 8841 */ 8842 public final void fireEvent(Event event) { 8843 8844 /* Log input events. We do a coarse filter for at least the FINE 8845 * level and then granularize from there. 8846 */ 8847 if (event instanceof InputEvent) { 8848 PlatformLogger logger = Logging.getInputLogger(); 8849 if (logger.isLoggable(Level.FINE)) { 8850 EventType eventType = event.getEventType(); 8851 if (eventType == MouseEvent.MOUSE_ENTERED || 8852 eventType == MouseEvent.MOUSE_EXITED) { 8853 logger.finer(event.toString()); 8854 } else if (eventType == MouseEvent.MOUSE_MOVED || 8855 eventType == MouseEvent.MOUSE_DRAGGED) { 8856 logger.finest(event.toString()); 8857 } else { 8858 logger.fine(event.toString()); 8859 } 8860 } 8861 } 8862 8863 Event.fireEvent(this, event); 8864 } 8865 8866 /*************************************************************************** 8867 * * 8868 * Stylesheet Handling * 8869 * * 8870 **************************************************************************/ 8871 8872 8873 /** 8874 * {@inheritDoc} 8875 * @return {@code getClass().getName()} without the package name 8876 * @since JavaFX 8.0 8877 */ 8878 @Override 8879 public String getTypeSelector() { 8880 8881 final Class<?> clazz = getClass(); 8882 final Package pkg = clazz.getPackage(); 8883 8884 // package could be null. not likely, but could be. 8885 int plen = 0; 8886 if (pkg != null) { 8887 plen = pkg.getName().length(); 8888 } 8889 8890 final int clen = clazz.getName().length(); 8891 final int pos = (0 < plen && plen < clen) ? plen + 1 : 0; 8892 8893 return clazz.getName().substring(pos); 8894 } 8895 8896 /** 8897 * {@inheritDoc} 8898 * @return {@code getParent()} 8899 * @since JavaFX 8.0 8900 */ 8901 @Override 8902 public Styleable getStyleableParent() { 8903 return getParent(); 8904 } 8905 8906 8907 /** 8908 * Returns the initial focus traversable state of this node, for use 8909 * by the JavaFX CSS engine to correctly set its initial value. This method 8910 * can be overridden by subclasses in instances where focus traversable should 8911 * initially be true (as the default implementation of this method is to return 8912 * false). 8913 * 8914 * @return the initial focus traversable state for this {@code Node}. 8915 * @since 9 8916 */ 8917 protected Boolean getInitialFocusTraversable() { 8918 return Boolean.FALSE; 8919 } 8920 8921 /** 8922 * Returns the initial cursor state of this node, for use 8923 * by the JavaFX CSS engine to correctly set its initial value. This method 8924 * can be overridden by subclasses in instances where the cursor should 8925 * initially be non-null (as the default implementation of this method is to return 8926 * null). 8927 * 8928 * @return the initial cursor state for this {@code Node}. 8929 * @since 9 8930 */ 8931 protected Cursor getInitialCursor() { 8932 return null; 8933 } 8934 8935 /** 8936 * Super-lazy instantiation pattern from Bill Pugh. 8937 */ 8938 private static class StyleableProperties { 8939 8940 private static final CssMetaData<Node,Cursor> CURSOR = 8941 new CssMetaData<Node,Cursor>("-fx-cursor", CursorConverter.getInstance()) { 8942 8943 @Override 8944 public boolean isSettable(Node node) { 8945 return node.miscProperties == null || node.miscProperties.canSetCursor(); 8946 } 8947 8948 @Override 8949 public StyleableProperty<Cursor> getStyleableProperty(Node node) { 8950 return (StyleableProperty<Cursor>)node.cursorProperty(); 8951 } 8952 8953 @Override 8954 public Cursor getInitialValue(Node node) { 8955 // Most controls default focusTraversable to true. 8956 // Give a way to have them return the correct default value. 8957 return node.getInitialCursor(); 8958 } 8959 8960 }; 8961 private static final CssMetaData<Node,Effect> EFFECT = 8962 new CssMetaData<Node,Effect>("-fx-effect", EffectConverter.getInstance()) { 8963 8964 @Override 8965 public boolean isSettable(Node node) { 8966 return node.miscProperties == null || node.miscProperties.canSetEffect(); 8967 } 8968 8969 @Override 8970 public StyleableProperty<Effect> getStyleableProperty(Node node) { 8971 return (StyleableProperty<Effect>)node.effectProperty(); 8972 } 8973 }; 8974 private static final CssMetaData<Node,Boolean> FOCUS_TRAVERSABLE = 8975 new CssMetaData<Node,Boolean>("-fx-focus-traversable", 8976 BooleanConverter.getInstance(), Boolean.FALSE) { 8977 8978 @Override 8979 public boolean isSettable(Node node) { 8980 return node.focusTraversable == null || !node.focusTraversable.isBound(); 8981 } 8982 8983 @Override 8984 public StyleableProperty<Boolean> getStyleableProperty(Node node) { 8985 return (StyleableProperty<Boolean>)node.focusTraversableProperty(); 8986 } 8987 8988 @Override 8989 public Boolean getInitialValue(Node node) { 8990 // Most controls default focusTraversable to true. 8991 // Give a way to have them return the correct default value. 8992 return node.getInitialFocusTraversable(); 8993 } 8994 8995 }; 8996 private static final CssMetaData<Node,Number> OPACITY = 8997 new CssMetaData<Node,Number>("-fx-opacity", 8998 SizeConverter.getInstance(), 1.0) { 8999 9000 @Override 9001 public boolean isSettable(Node node) { 9002 return node.opacity == null || !node.opacity.isBound(); 9003 } 9004 9005 @Override 9006 public StyleableProperty<Number> getStyleableProperty(Node node) { 9007 return (StyleableProperty<Number>)node.opacityProperty(); 9008 } 9009 }; 9010 private static final CssMetaData<Node,BlendMode> BLEND_MODE = 9011 new CssMetaData<Node,BlendMode>("-fx-blend-mode", new EnumConverter<BlendMode>(BlendMode.class)) { 9012 9013 @Override 9014 public boolean isSettable(Node node) { 9015 return node.blendMode == null || !node.blendMode.isBound(); 9016 } 9017 9018 @Override 9019 public StyleableProperty<BlendMode> getStyleableProperty(Node node) { 9020 return (StyleableProperty<BlendMode>)node.blendModeProperty(); 9021 } 9022 }; 9023 private static final CssMetaData<Node,Number> ROTATE = 9024 new CssMetaData<Node,Number>("-fx-rotate", 9025 SizeConverter.getInstance(), 0.0) { 9026 9027 @Override 9028 public boolean isSettable(Node node) { 9029 return node.nodeTransformation == null 9030 || node.nodeTransformation.rotate == null 9031 || node.nodeTransformation.canSetRotate(); 9032 } 9033 9034 @Override 9035 public StyleableProperty<Number> getStyleableProperty(Node node) { 9036 return (StyleableProperty<Number>)node.rotateProperty(); 9037 } 9038 }; 9039 private static final CssMetaData<Node,Number> SCALE_X = 9040 new CssMetaData<Node,Number>("-fx-scale-x", 9041 SizeConverter.getInstance(), 1.0) { 9042 9043 @Override 9044 public boolean isSettable(Node node) { 9045 return node.nodeTransformation == null 9046 || node.nodeTransformation.scaleX == null 9047 || node.nodeTransformation.canSetScaleX(); 9048 } 9049 9050 @Override 9051 public StyleableProperty<Number> getStyleableProperty(Node node) { 9052 return (StyleableProperty<Number>)node.scaleXProperty(); 9053 } 9054 }; 9055 private static final CssMetaData<Node,Number> SCALE_Y = 9056 new CssMetaData<Node,Number>("-fx-scale-y", 9057 SizeConverter.getInstance(), 1.0) { 9058 9059 @Override 9060 public boolean isSettable(Node node) { 9061 return node.nodeTransformation == null 9062 || node.nodeTransformation.scaleY == null 9063 || node.nodeTransformation.canSetScaleY(); 9064 } 9065 9066 @Override 9067 public StyleableProperty<Number> getStyleableProperty(Node node) { 9068 return (StyleableProperty<Number>)node.scaleYProperty(); 9069 } 9070 }; 9071 private static final CssMetaData<Node,Number> SCALE_Z = 9072 new CssMetaData<Node,Number>("-fx-scale-z", 9073 SizeConverter.getInstance(), 1.0) { 9074 9075 @Override 9076 public boolean isSettable(Node node) { 9077 return node.nodeTransformation == null 9078 || node.nodeTransformation.scaleZ == null 9079 || node.nodeTransformation.canSetScaleZ(); 9080 } 9081 9082 @Override 9083 public StyleableProperty<Number> getStyleableProperty(Node node) { 9084 return (StyleableProperty<Number>)node.scaleZProperty(); 9085 } 9086 }; 9087 private static final CssMetaData<Node,Number> TRANSLATE_X = 9088 new CssMetaData<Node,Number>("-fx-translate-x", 9089 SizeConverter.getInstance(), 0.0) { 9090 9091 @Override 9092 public boolean isSettable(Node node) { 9093 return node.nodeTransformation == null 9094 || node.nodeTransformation.translateX == null 9095 || node.nodeTransformation.canSetTranslateX(); 9096 } 9097 9098 @Override 9099 public StyleableProperty<Number> getStyleableProperty(Node node) { 9100 return (StyleableProperty<Number>)node.translateXProperty(); 9101 } 9102 }; 9103 private static final CssMetaData<Node,Number> TRANSLATE_Y = 9104 new CssMetaData<Node,Number>("-fx-translate-y", 9105 SizeConverter.getInstance(), 0.0) { 9106 9107 @Override 9108 public boolean isSettable(Node node) { 9109 return node.nodeTransformation == null 9110 || node.nodeTransformation.translateY == null 9111 || node.nodeTransformation.canSetTranslateY(); 9112 } 9113 9114 @Override 9115 public StyleableProperty<Number> getStyleableProperty(Node node) { 9116 return (StyleableProperty<Number>)node.translateYProperty(); 9117 } 9118 }; 9119 private static final CssMetaData<Node,Number> TRANSLATE_Z = 9120 new CssMetaData<Node,Number>("-fx-translate-z", 9121 SizeConverter.getInstance(), 0.0) { 9122 9123 @Override 9124 public boolean isSettable(Node node) { 9125 return node.nodeTransformation == null 9126 || node.nodeTransformation.translateZ == null 9127 || node.nodeTransformation.canSetTranslateZ(); 9128 } 9129 9130 @Override 9131 public StyleableProperty<Number> getStyleableProperty(Node node) { 9132 return (StyleableProperty<Number>)node.translateZProperty(); 9133 } 9134 }; 9135 private static final CssMetaData<Node, Number> VIEW_ORDER 9136 = new CssMetaData<Node, Number>("-fx-view-order", 9137 SizeConverter.getInstance(), 0.0) { 9138 9139 @Override 9140 public boolean isSettable(Node node) { 9141 return node.miscProperties == null 9142 || node.miscProperties.viewOrder == null 9143 || !node.miscProperties.viewOrder.isBound(); 9144 } 9145 9146 @Override 9147 public StyleableProperty<Number> getStyleableProperty(Node node) { 9148 return (StyleableProperty<Number>) node.viewOrderProperty(); 9149 } 9150 }; 9151 private static final CssMetaData<Node,Boolean> VISIBILITY = 9152 new CssMetaData<Node,Boolean>("visibility", 9153 new StyleConverter<String,Boolean>() { 9154 9155 @Override 9156 // [ visible | hidden | collapse | inherit ] 9157 public Boolean convert(ParsedValue<String, Boolean> value, Font font) { 9158 final String sval = value != null ? value.getValue() : null; 9159 return "visible".equalsIgnoreCase(sval); 9160 } 9161 9162 }, 9163 Boolean.TRUE) { 9164 9165 @Override 9166 public boolean isSettable(Node node) { 9167 return node.visible == null || !node.visible.isBound(); 9168 } 9169 9170 @Override 9171 public StyleableProperty<Boolean> getStyleableProperty(Node node) { 9172 return (StyleableProperty<Boolean>)node.visibleProperty(); 9173 } 9174 }; 9175 9176 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 9177 9178 static { 9179 9180 final List<CssMetaData<? extends Styleable, ?>> styleables = 9181 new ArrayList<CssMetaData<? extends Styleable, ?>>(); 9182 styleables.add(CURSOR); 9183 styleables.add(EFFECT); 9184 styleables.add(FOCUS_TRAVERSABLE); 9185 styleables.add(OPACITY); 9186 styleables.add(BLEND_MODE); 9187 styleables.add(ROTATE); 9188 styleables.add(SCALE_X); 9189 styleables.add(SCALE_Y); 9190 styleables.add(SCALE_Z); 9191 styleables.add(VIEW_ORDER); 9192 styleables.add(TRANSLATE_X); 9193 styleables.add(TRANSLATE_Y); 9194 styleables.add(TRANSLATE_Z); 9195 styleables.add(VISIBILITY); 9196 STYLEABLES = Collections.unmodifiableList(styleables); 9197 9198 } 9199 } 9200 9201 /** 9202 * @return The CssMetaData associated with this class, which may include the 9203 * CssMetaData of its superclasses. 9204 * @since JavaFX 8.0 9205 */ 9206 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 9207 // 9208 // Super-lazy instantiation pattern from Bill Pugh. StyleableProperties 9209 // is referenced no earlier (and therefore loaded no earlier by the 9210 // class loader) than the moment that getClassCssMetaData() is called. 9211 // This avoids loading the CssMetaData instances until the point at 9212 // which CSS needs the data. 9213 // 9214 return StyleableProperties.STYLEABLES; 9215 } 9216 9217 /** 9218 * This method should delegate to {@link Node#getClassCssMetaData()} so that 9219 * a Node's CssMetaData can be accessed without the need for reflection. 9220 * 9221 * @return The CssMetaData associated with this node, which may include the 9222 * CssMetaData of its superclasses. 9223 * @since JavaFX 8.0 9224 */ 9225 9226 @Override 9227 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 9228 return getClassCssMetaData(); 9229 } 9230 9231 /* 9232 * @return The Styles that match this CSS property for the given Node. The 9233 * list is sorted by descending specificity. 9234 */ 9235 // SB-dependency: RT-21096 has been filed to track this 9236 static List<Style> getMatchingStyles(CssMetaData cssMetaData, Styleable styleable) { 9237 return CssStyleHelper.getMatchingStyles(styleable, cssMetaData); 9238 } 9239 9240 final ObservableMap<StyleableProperty<?>, List<Style>> getStyleMap() { 9241 ObservableMap<StyleableProperty<?>, List<Style>> map = 9242 (ObservableMap<StyleableProperty<?>, List<Style>>)getProperties().get("STYLEMAP"); 9243 Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(map, this); 9244 if (ret != null) { 9245 if (ret instanceof ObservableMap) return (ObservableMap)ret; 9246 return FXCollections.observableMap(ret); 9247 } 9248 return FXCollections.<StyleableProperty<?>, List<Style>>emptyObservableMap(); 9249 } 9250 9251 /* 9252 * RT-17293 9253 */ 9254 // SB-dependency: RT-21096 has been filed to track this 9255 final void setStyleMap(ObservableMap<StyleableProperty<?>, List<Style>> styleMap) { 9256 if (styleMap != null) getProperties().put("STYLEMAP", styleMap); 9257 else getProperties().remove("STYLEMAP"); 9258 } 9259 9260 /* 9261 * Find CSS styles that were used to style this Node in its current pseudo-class state. The map will contain the styles from this node and, 9262 * if the node is a Parent, its children. The node corresponding to an entry in the Map can be obtained by casting a StyleableProperty key to a 9263 * javafx.beans.property.Property and calling getBean(). The List contains only those styles used to style the property and will contain 9264 * styles used to resolve lookup values. 9265 * 9266 * @param styleMap A Map to be populated with the styles. If null, a new Map will be allocated. 9267 * @return The Map populated with matching styles. 9268 */ 9269 // SB-dependency: RT-21096 has been filed to track this 9270 Map<StyleableProperty<?>,List<Style>> findStyles(Map<StyleableProperty<?>,List<Style>> styleMap) { 9271 9272 Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(styleMap, this); 9273 return (ret != null) ? ret : Collections.<StyleableProperty<?>, List<Style>>emptyMap(); 9274 } 9275 9276 /** 9277 * Flags used to indicate in which way this node is dirty (or whether it 9278 * is clean) and what must happen during the next CSS cycle on the 9279 * scenegraph. 9280 */ 9281 CssFlags cssFlag = CssFlags.CLEAN; 9282 9283 /** 9284 * Needed for testing. 9285 */ 9286 final CssFlags getCSSFlags() { return cssFlag; } 9287 9288 /** 9289 * Called when a CSS pseudo-class change would cause styles to be reapplied. 9290 */ 9291 private void requestCssStateTransition() { 9292 // If there is no scene, then we cannot make it dirty, so we'll leave 9293 // the flag alone 9294 if (getScene() == null) return; 9295 // Don't bother doing anything if the cssFlag is not CLEAN. 9296 // If the flag indicates a DIRTY_BRANCH, the flag needs to be changed 9297 // to UPDATE to ensure that NodeHelper.processCSS is called on the node. 9298 if (cssFlag == CssFlags.CLEAN || cssFlag == CssFlags.DIRTY_BRANCH) { 9299 cssFlag = CssFlags.UPDATE; 9300 notifyParentsOfInvalidatedCSS(); 9301 } 9302 } 9303 9304 /** 9305 * Used to specify that a pseudo-class of this Node has changed. If the 9306 * pseudo-class is used in a CSS selector that matches this Node, CSS will 9307 * be reapplied. Typically, this method is called from the {@code invalidated} 9308 * method of a property that is used as a pseudo-class. For example: 9309 * <pre><code> 9310 * 9311 * private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state"); 9312 * 9313 * BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) { 9314 * 9315 * {@literal @}Override public void invalidated() { 9316 * pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get()); 9317 * } 9318 * 9319 * {@literal @}Override public Object getBean() { 9320 * return MyControl.this; 9321 * } 9322 * 9323 * {@literal @}Override public String getName() { 9324 * return "myPseudoClassState"; 9325 * } 9326 * }; 9327 * </code></pre> 9328 * @param pseudoClass the pseudo-class that has changed state 9329 * @param active whether or not the state is active 9330 * @since JavaFX 8.0 9331 */ 9332 public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) { 9333 9334 final boolean modified = active 9335 ? pseudoClassStates.add(pseudoClass) 9336 : pseudoClassStates.remove(pseudoClass); 9337 9338 if (modified && styleHelper != null) { 9339 final boolean isTransition = styleHelper.pseudoClassStateChanged(pseudoClass); 9340 if (isTransition) { 9341 requestCssStateTransition(); 9342 } 9343 } 9344 } 9345 9346 // package so that StyleHelper can get at it 9347 final ObservableSet<PseudoClass> pseudoClassStates = new PseudoClassState(); 9348 /** 9349 * @return The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet 9350 * @since JavaFX 8.0 9351 */ 9352 public final ObservableSet<PseudoClass> getPseudoClassStates() { 9353 9354 return FXCollections.unmodifiableObservableSet(pseudoClassStates); 9355 9356 } 9357 9358 // Walks up the tree telling each parent that the pseudo class state of 9359 // this node has changed. 9360 final void notifyParentsOfInvalidatedCSS() { 9361 SubScene subScene = getSubScene(); 9362 Parent root = (subScene != null) ? 9363 subScene.getRoot() : getScene().getRoot(); 9364 9365 if (!root.isDirty(DirtyBits.NODE_CSS)) { 9366 // Ensure that Scene.root is marked as dirty. If the scene isn't 9367 // dirty, nothing will get repainted. This bit is cleared from 9368 // Scene in doCSSPass(). 9369 NodeHelper.markDirty(root, DirtyBits.NODE_CSS); 9370 if (subScene != null) { 9371 // If the node is part of a subscene, then we must ensure that 9372 // the we not only mark subScene.root dirty, but continue and 9373 // call subScene.notifyParentsOfInvalidatedCSS() until 9374 // Scene.root gets marked dirty, via the recurisve call: 9375 subScene.cssFlag = CssFlags.UPDATE; 9376 subScene.notifyParentsOfInvalidatedCSS(); 9377 } 9378 } 9379 Parent _parent = getParent(); 9380 while (_parent != null) { 9381 if (_parent.cssFlag == CssFlags.CLEAN) { 9382 _parent.cssFlag = CssFlags.DIRTY_BRANCH; 9383 _parent = _parent.getParent(); 9384 } else { 9385 _parent = null; 9386 } 9387 } 9388 } 9389 9390 final void reapplyCSS() { 9391 9392 if (getScene() == null) return; 9393 9394 if (cssFlag == CssFlags.REAPPLY) return; 9395 9396 // RT-36838 - don't reapply CSS in the middle of an update 9397 if (cssFlag == CssFlags.UPDATE) { 9398 cssFlag = CssFlags.REAPPLY; 9399 notifyParentsOfInvalidatedCSS(); 9400 return; 9401 } 9402 9403 reapplyCss(); 9404 9405 // 9406 // One idiom employed by developers is to, during the layout pass, 9407 // add or remove nodes from the scene. For example, a ScrollPane 9408 // might add scroll bars to itself if it determines during layout 9409 // that it needs them, or a ListView might add cells to itself if 9410 // it determines that it needs to. In such situations we must 9411 // apply the CSS immediately and not add it to the scene's queue 9412 // for deferred action. 9413 // 9414 if (getParent() != null && getParent().isPerformingLayout()) { 9415 NodeHelper.processCSS(this); 9416 } else { 9417 notifyParentsOfInvalidatedCSS(); 9418 } 9419 9420 } 9421 9422 // 9423 // This method "reapplies" CSS to this node and all of its children. Reapplying CSS 9424 // means that new style maps are calculated for the node. The process of reapplying 9425 // CSS may reset the CSS properties of a node to their initial state, but the _new_ 9426 // styles are not applied as part of this process. 9427 // 9428 // There is no check of the CSS state of a child since reapply takes precedence 9429 // over other CSS states. 9430 // 9431 private void reapplyCss() { 9432 9433 // Hang on to current styleHelper so we can know whether 9434 // createStyleHelper returned the same styleHelper 9435 final CssStyleHelper oldStyleHelper = styleHelper; 9436 9437 // CSS state is "REAPPLY" 9438 cssFlag = CssFlags.REAPPLY; 9439 9440 styleHelper = CssStyleHelper.createStyleHelper(this); 9441 9442 // REAPPLY to my children, too. 9443 if (this instanceof Parent) { 9444 9445 // minor optimization to avoid calling createStyleHelper on children 9446 // when we know there will not be any change in the style maps. 9447 final boolean visitChildren = 9448 // If we don't have a styleHelper, then we should visit the children of this parent 9449 // since there might be styles that depend on being a child of this parent. 9450 // In other words, we have .a > .b { blah: blort; }, but no styles for ".a" itself. 9451 styleHelper == null || 9452 // if the styleHelper changed, then we definitely need to visit the children 9453 // since the new styles may have an effect on the children's styles calculated values. 9454 (oldStyleHelper != styleHelper) || 9455 // If our parent is null, then we're the root of a scene or sub-scene, most likely, 9456 // and we'll visit children because elsewhere the code depends on root.reapplyCSS() 9457 // to force css to be reapplied (whether it needs to be or not). 9458 (getParent() == null) || 9459 // If our parent's cssFlag is other than clean, then the parent may have just had 9460 // CSS reapplied. If the parent just had CSS reapplied, then some of its styles 9461 // may affect my children's styles. 9462 (getParent().cssFlag != CssFlags.CLEAN); 9463 9464 if (visitChildren) { 9465 9466 List<Node> children = ((Parent) this).getChildren(); 9467 for (int n = 0, nMax = children.size(); n < nMax; n++) { 9468 Node child = children.get(n); 9469 child.reapplyCss(); 9470 } 9471 } 9472 9473 } else if (this instanceof SubScene) { 9474 9475 // SubScene root is a Parent, but reapplyCss is a private method in Node 9476 final Node subSceneRoot = ((SubScene)this).getRoot(); 9477 if (subSceneRoot != null) { 9478 subSceneRoot.reapplyCss(); 9479 } 9480 9481 } else if (styleHelper == null) { 9482 // 9483 // If this is not a Parent and there is no styleHelper, then the CSS state is "CLEAN" 9484 // since there are no styles to apply or children to update. 9485 // 9486 cssFlag = CssFlags.CLEAN; 9487 return; 9488 } 9489 9490 cssFlag = CssFlags.UPDATE; 9491 9492 } 9493 9494 void processCSS() { 9495 switch (cssFlag) { 9496 case CLEAN: 9497 break; 9498 case DIRTY_BRANCH: 9499 { 9500 Parent me = (Parent)this; 9501 // clear the flag first in case the flag is set to something 9502 // other than clean by downstream processing. 9503 me.cssFlag = CssFlags.CLEAN; 9504 List<Node> children = me.getChildren(); 9505 for (int i=0, max=children.size(); i<max; i++) { 9506 children.get(i).processCSS(); 9507 } 9508 break; 9509 } 9510 case REAPPLY: 9511 case UPDATE: 9512 default: 9513 NodeHelper.processCSS(this); 9514 } 9515 } 9516 9517 /** 9518 * If required, apply styles to this Node and its children, if any. This method does not normally need to 9519 * be invoked directly but may be used in conjunction with {@link Parent#layout()} to size a Node before the 9520 * next pulse, or if the {@link #getScene() Scene} is not in a {@link javafx.stage.Stage}. 9521 * <p>Provided that the Node's {@link #getScene() Scene} is not null, CSS is applied to this Node regardless 9522 * of whether this Node's CSS state is clean. CSS styles are applied from the top-most parent 9523 * of this Node whose CSS state is other than clean, which may affect the styling of other nodes. 9524 * This method is a no-op if the Node is not in a Scene. The Scene does not have to be in a Stage.</p> 9525 * <p>This method does not invoke the {@link Parent#layout()} method. Typically, the caller will use the 9526 * following sequence of operations.</p> 9527 * <pre>{@code 9528 * parentNode.applyCss(); 9529 * parentNode.layout(); 9530 * }</pre> 9531 * <p>As a more complete example, the following code uses {@code applyCss()} and {@code layout()} to find 9532 * the width and height of the Button before the Stage has been shown. If either the call to {@code applyCss()} 9533 * or the call to {@code layout()} is commented out, the calls to {@code getWidth()} and {@code getHeight()} 9534 * will return zero (until some time after the Stage is shown). </p> 9535 * <pre><code> 9536 * {@literal @}Override 9537 * public void start(Stage stage) throws Exception { 9538 * 9539 * Group root = new Group(); 9540 * Scene scene = new Scene(root); 9541 * 9542 * Button button = new Button("Hello World"); 9543 * root.getChildren().add(button); 9544 * 9545 * root.applyCss(); 9546 * root.layout(); 9547 * 9548 * double width = button.getWidth(); 9549 * double height = button.getHeight(); 9550 * 9551 * System.out.println(width + ", " + height); 9552 * 9553 * stage.setScene(scene); 9554 * stage.show(); 9555 * } 9556 * </code></pre> 9557 * @since JavaFX 8.0 9558 */ 9559 public final void applyCss() { 9560 9561 if (getScene() == null) { 9562 return; 9563 } 9564 9565 // update, unless reapply 9566 if (cssFlag != CssFlags.REAPPLY) cssFlag = CssFlags.UPDATE; 9567 9568 // 9569 // RT-28394 - need to see if any ancestor has a flag UPDATE 9570 // If so, process css from the top-most CssFlags.UPDATE node 9571 // since my ancestor's styles may affect mine. 9572 // 9573 // If the scene-graph root isn't NODE_CSS dirty, then all my 9574 // ancestor flags should be CLEAN and I can skip this lookup. 9575 // 9576 Node topMost = this; 9577 9578 final boolean dirtyRoot = getScene().getRoot().isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS); 9579 if (dirtyRoot) { 9580 9581 Node _parent = getParent(); 9582 while (_parent != null) { 9583 if (_parent.cssFlag == CssFlags.UPDATE || _parent.cssFlag == CssFlags.REAPPLY) { 9584 topMost = _parent; 9585 } 9586 _parent = _parent.getParent(); 9587 } 9588 9589 // Note: this code used to mark the parent nodes with DIRTY_BRANCH, 9590 // but that isn't necessary since UPDATE will apply css to all of 9591 // a Parent's children. 9592 9593 // If we're at the root of the scene-graph, make sure the NODE_CSS 9594 // dirty bit is cleared (see Scene#doCSSPass()) 9595 if (topMost == getScene().getRoot()) { 9596 getScene().getRoot().clearDirty(DirtyBits.NODE_CSS); 9597 } 9598 } 9599 9600 topMost.processCSS(); 9601 9602 } 9603 9604 /* 9605 * If invoked, will update styles from here on down. This method should not be called directly. If 9606 * overridden, the overriding method must at some point call {@code super.processCSSImpl} to ensure that 9607 * this Node's CSS state is properly updated. 9608 * 9609 * Note that the difference between this method and {@link #applyCss()} is that this method 9610 * updates styles for this node on down; whereas, {@code applyCss()} looks for the top-most ancestor that needs 9611 * CSS update and apply styles from that node on down. 9612 * 9613 * Note: This method MUST only be called via its accessor method. 9614 */ 9615 private void doProcessCSS() { 9616 9617 // Nothing to do... 9618 if (cssFlag == CssFlags.CLEAN) return; 9619 9620 // if REAPPLY was deferred, process it now... 9621 if (cssFlag == CssFlags.REAPPLY) { 9622 reapplyCss(); 9623 } 9624 9625 // Clear the flag first in case the flag is set to something 9626 // other than clean by downstream processing. 9627 cssFlag = CssFlags.CLEAN; 9628 9629 // Transition to the new state and apply styles 9630 if (styleHelper != null && getScene() != null) { 9631 styleHelper.transitionToState(this); 9632 } 9633 } 9634 9635 9636 /** 9637 * A StyleHelper for this node. 9638 * A StyleHelper contains all the css styles for this node 9639 * and knows how to apply them when our state changes. 9640 */ 9641 CssStyleHelper styleHelper; 9642 9643 private static final PseudoClass HOVER_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("hover"); 9644 private static final PseudoClass PRESSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("pressed"); 9645 private static final PseudoClass DISABLED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("disabled"); 9646 private static final PseudoClass FOCUSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("focused"); 9647 private static final PseudoClass SHOW_MNEMONICS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("show-mnemonics"); 9648 9649 private static abstract class LazyTransformProperty 9650 extends ReadOnlyObjectProperty<Transform> { 9651 9652 protected static final int VALID = 0; 9653 protected static final int INVALID = 1; 9654 protected static final int VALIDITY_UNKNOWN = 2; 9655 protected int valid = INVALID; 9656 9657 private ExpressionHelper<Transform> helper; 9658 9659 private Transform transform; 9660 private boolean canReuse = false; 9661 9662 @Override 9663 public void addListener(InvalidationListener listener) { 9664 helper = ExpressionHelper.addListener(helper, this, listener); 9665 } 9666 9667 @Override 9668 public void removeListener(InvalidationListener listener) { 9669 helper = ExpressionHelper.removeListener(helper, listener); 9670 } 9671 9672 @Override 9673 public void addListener(ChangeListener<? super Transform> listener) { 9674 helper = ExpressionHelper.addListener(helper, this, listener); 9675 } 9676 9677 @Override 9678 public void removeListener(ChangeListener<? super Transform> listener) { 9679 helper = ExpressionHelper.removeListener(helper, listener); 9680 } 9681 9682 protected Transform getInternalValue() { 9683 if (valid == INVALID || 9684 (valid == VALIDITY_UNKNOWN && computeValidity() == INVALID)) { 9685 transform = computeTransform(canReuse ? transform : null); 9686 canReuse = true; 9687 valid = validityKnown() ? VALID : VALIDITY_UNKNOWN; 9688 } 9689 9690 return transform; 9691 } 9692 9693 @Override 9694 public Transform get() { 9695 transform = getInternalValue(); 9696 canReuse = false; 9697 return transform; 9698 } 9699 9700 public void validityUnknown() { 9701 if (valid == VALID) { 9702 valid = VALIDITY_UNKNOWN; 9703 } 9704 } 9705 9706 public void invalidate() { 9707 if (valid != INVALID) { 9708 valid = INVALID; 9709 ExpressionHelper.fireValueChangedEvent(helper); 9710 } 9711 } 9712 9713 protected abstract boolean validityKnown(); 9714 protected abstract int computeValidity(); 9715 protected abstract Transform computeTransform(Transform reuse); 9716 } 9717 9718 private static abstract class LazyBoundsProperty 9719 extends ReadOnlyObjectProperty<Bounds> { 9720 private ExpressionHelper<Bounds> helper; 9721 private boolean valid; 9722 9723 private Bounds bounds; 9724 9725 @Override 9726 public void addListener(InvalidationListener listener) { 9727 helper = ExpressionHelper.addListener(helper, this, listener); 9728 } 9729 9730 @Override 9731 public void removeListener(InvalidationListener listener) { 9732 helper = ExpressionHelper.removeListener(helper, listener); 9733 } 9734 9735 @Override 9736 public void addListener(ChangeListener<? super Bounds> listener) { 9737 helper = ExpressionHelper.addListener(helper, this, listener); 9738 } 9739 9740 @Override 9741 public void removeListener(ChangeListener<? super Bounds> listener) { 9742 helper = ExpressionHelper.removeListener(helper, listener); 9743 } 9744 9745 @Override 9746 public Bounds get() { 9747 if (!valid) { 9748 bounds = computeBounds(); 9749 valid = true; 9750 } 9751 9752 return bounds; 9753 } 9754 9755 public void invalidate() { 9756 if (valid) { 9757 valid = false; 9758 ExpressionHelper.fireValueChangedEvent(helper); 9759 } 9760 } 9761 9762 protected abstract Bounds computeBounds(); 9763 } 9764 9765 private static final BoundsAccessor boundsAccessor = (bounds, tx, node) -> node.getGeomBounds(bounds, tx); 9766 9767 /** 9768 * The accessible role for this {@code Node}. 9769 * <p> 9770 * The screen reader uses the role of a node to determine the 9771 * attributes and actions that are supported. 9772 * 9773 * @defaultValue {@link AccessibleRole#NODE} 9774 * @see AccessibleRole 9775 * 9776 * @since JavaFX 8u40 9777 */ 9778 private ObjectProperty<AccessibleRole> accessibleRole; 9779 9780 public final void setAccessibleRole(AccessibleRole value) { 9781 if (value == null) value = AccessibleRole.NODE; 9782 accessibleRoleProperty().set(value); 9783 } 9784 9785 public final AccessibleRole getAccessibleRole() { 9786 if (accessibleRole == null) return AccessibleRole.NODE; 9787 return accessibleRoleProperty().get(); 9788 } 9789 9790 public final ObjectProperty<AccessibleRole> accessibleRoleProperty() { 9791 if (accessibleRole == null) { 9792 accessibleRole = new SimpleObjectProperty<AccessibleRole>(this, "accessibleRole", AccessibleRole.NODE); 9793 } 9794 return accessibleRole; 9795 } 9796 9797 public final void setAccessibleRoleDescription(String value) { 9798 accessibleRoleDescriptionProperty().set(value); 9799 } 9800 9801 public final String getAccessibleRoleDescription() { 9802 if (accessibilityProperties == null) return null; 9803 if (accessibilityProperties.accessibleRoleDescription == null) return null; 9804 return accessibleRoleDescriptionProperty().get(); 9805 } 9806 9807 /** 9808 * The role description of this {@code Node}. 9809 * <p> 9810 * Noramlly, when a role is provided for a node, the screen reader 9811 * speaks the role as well as the contents of the node. When this 9812 * value is set, it is possbile to override the default. This is 9813 * useful because the set of roles is predefined. For example, 9814 * it is possible to set the role of a node to be a button, but 9815 * have the role description be arbitrary text. 9816 * 9817 * @return the role description of this {@code Node}. 9818 * @defaultValue null 9819 * 9820 * @since JavaFX 8u40 9821 */ 9822 public final ObjectProperty<String> accessibleRoleDescriptionProperty() { 9823 return getAccessibilityProperties().getAccessibleRoleDescription(); 9824 } 9825 9826 public final void setAccessibleText(String value) { 9827 accessibleTextProperty().set(value); 9828 } 9829 9830 public final String getAccessibleText() { 9831 if (accessibilityProperties == null) return null; 9832 if (accessibilityProperties.accessibleText == null) return null; 9833 return accessibleTextProperty().get(); 9834 } 9835 9836 /** 9837 * The accessible text for this {@code Node}. 9838 * <p> 9839 * This property is used to set the text that the screen 9840 * reader will speak. If a node normally speaks text, 9841 * that text is overriden. For example, a button 9842 * usually speaks using the text in the control but will 9843 * no longer do this when this value is set. 9844 * 9845 * @return accessible text for this {@code Node}. 9846 * @defaultValue null 9847 * 9848 * @since JavaFX 8u40 9849 */ 9850 public final ObjectProperty<String> accessibleTextProperty() { 9851 return getAccessibilityProperties().getAccessibleText(); 9852 } 9853 9854 public final void setAccessibleHelp(String value) { 9855 accessibleHelpProperty().set(value); 9856 } 9857 9858 public final String getAccessibleHelp() { 9859 if (accessibilityProperties == null) return null; 9860 if (accessibilityProperties.accessibleHelp == null) return null; 9861 return accessibleHelpProperty().get(); 9862 } 9863 9864 /** 9865 * The accessible help text for this {@code Node}. 9866 * <p> 9867 * The help text provides a more detailed description of the 9868 * accessible text for a node. By default, if the node has 9869 * a tool tip, this text is used. 9870 * 9871 * @return the accessible help text for this {@code Node}. 9872 * @defaultValue null 9873 * 9874 * @since JavaFX 8u40 9875 */ 9876 public final ObjectProperty<String> accessibleHelpProperty() { 9877 return getAccessibilityProperties().getAccessibleHelp(); 9878 } 9879 9880 AccessibilityProperties accessibilityProperties; 9881 private AccessibilityProperties getAccessibilityProperties() { 9882 if (accessibilityProperties == null) { 9883 accessibilityProperties = new AccessibilityProperties(); 9884 } 9885 return accessibilityProperties; 9886 } 9887 9888 private class AccessibilityProperties { 9889 ObjectProperty<String> accessibleRoleDescription; 9890 ObjectProperty<String> getAccessibleRoleDescription() { 9891 if (accessibleRoleDescription == null) { 9892 accessibleRoleDescription = new SimpleObjectProperty<String>(Node.this, "accessibleRoleDescription", null); 9893 } 9894 return accessibleRoleDescription; 9895 } 9896 ObjectProperty<String> accessibleText; 9897 ObjectProperty<String> getAccessibleText() { 9898 if (accessibleText == null) { 9899 accessibleText = new SimpleObjectProperty<String>(Node.this, "accessibleText", null); 9900 } 9901 return accessibleText; 9902 } 9903 ObjectProperty<String> accessibleHelp; 9904 ObjectProperty<String> getAccessibleHelp() { 9905 if (accessibleHelp == null) { 9906 accessibleHelp = new SimpleObjectProperty<String>(Node.this, "accessibleHelp", null); 9907 } 9908 return accessibleHelp; 9909 } 9910 } 9911 9912 /** 9913 * This method is called by the assistive technology to request 9914 * the value for an attribute. 9915 * <p> 9916 * This method is commonly overridden by subclasses to implement 9917 * attributes that are required for a specific role.<br> 9918 * If a particular attribute is not handled, the superclass implementation 9919 * must be called. 9920 * </p> 9921 * 9922 * @param attribute the requested attribute 9923 * @param parameters optional list of parameters 9924 * @return the value for the requested attribute 9925 * 9926 * @see AccessibleAttribute 9927 * 9928 * @since JavaFX 8u40 9929 */ 9930 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 9931 switch (attribute) { 9932 case ROLE: return getAccessibleRole(); 9933 case ROLE_DESCRIPTION: return getAccessibleRoleDescription(); 9934 case TEXT: return getAccessibleText(); 9935 case HELP: return getAccessibleHelp(); 9936 case PARENT: return getParent(); 9937 case SCENE: return getScene(); 9938 case BOUNDS: return localToScreen(getBoundsInLocal()); 9939 case DISABLED: return isDisabled(); 9940 case FOCUSED: return isFocused(); 9941 case VISIBLE: return isVisible(); 9942 case LABELED_BY: return labeledBy; 9943 default: return null; 9944 } 9945 } 9946 9947 /** 9948 * This method is called by the assistive technology to request the action 9949 * indicated by the argument should be executed. 9950 * <p> 9951 * This method is commonly overridden by subclasses to implement 9952 * action that are required for a specific role.<br> 9953 * If a particular action is not handled, the superclass implementation 9954 * must be called. 9955 * </p> 9956 * 9957 * @param action the action to execute 9958 * @param parameters optional list of parameters 9959 * 9960 * @see AccessibleAction 9961 * 9962 * @since JavaFX 8u40 9963 */ 9964 public void executeAccessibleAction(AccessibleAction action, Object... parameters) { 9965 switch (action) { 9966 case REQUEST_FOCUS: 9967 if (isFocusTraversable()) { 9968 requestFocus(); 9969 } 9970 break; 9971 case SHOW_MENU: { 9972 Bounds b = getBoundsInLocal(); 9973 Point2D pt = localToScreen(b.getMaxX(), b.getMaxY()); 9974 ContextMenuEvent event = 9975 new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, 9976 b.getMaxX(), b.getMaxY(), pt.getX(), pt.getY(), 9977 false, new PickResult(this, b.getMaxX(), b.getMaxY())); 9978 Event.fireEvent(this, event); 9979 break; 9980 } 9981 default: 9982 } 9983 } 9984 9985 /** 9986 * This method is called by the application to notify the assistive 9987 * technology that the value for an attribute has changed. 9988 * 9989 * @param attributes the attribute whose value has changed 9990 * 9991 * @see AccessibleAttribute 9992 * 9993 * @since JavaFX 8u40 9994 */ 9995 public final void notifyAccessibleAttributeChanged(AccessibleAttribute attributes) { 9996 if (accessible == null) { 9997 Scene scene = getScene(); 9998 if (scene != null) { 9999 accessible = scene.removeAccessible(this); 10000 } 10001 } 10002 if (accessible != null) { 10003 accessible.sendNotification(attributes); 10004 } 10005 } 10006 10007 Accessible accessible; 10008 Accessible getAccessible() { 10009 if (accessible == null) { 10010 Scene scene = getScene(); 10011 /* It is possible the node was reparented and getAccessible() 10012 * is called before the pulse. Try to recycle the accessible 10013 * before creating a new one. 10014 * Note: this code relies that an accessible can never be on 10015 * more than one Scene#accMap. Thus, the only way 10016 * scene#removeAccessible() returns non-null is if the node 10017 * old scene and new scene are the same object. 10018 */ 10019 if (scene != null) { 10020 accessible = scene.removeAccessible(this); 10021 } 10022 } 10023 if (accessible == null) { 10024 accessible = Application.GetApplication().createAccessible(); 10025 accessible.setEventHandler(new Accessible.EventHandler() { 10026 @SuppressWarnings("deprecation") 10027 @Override public AccessControlContext getAccessControlContext() { 10028 Scene scene = getScene(); 10029 if (scene == null) { 10030 /* This can happen during the release process of an accessible object. */ 10031 throw new RuntimeException("Accessbility requested for node not on a scene"); 10032 } 10033 if (scene.getPeer() != null) { 10034 return scene.getPeer().getAccessControlContext(); 10035 } else { 10036 /* In some rare cases the accessible for a Node is needed 10037 * before its scene is made visible. For example, the screen reader 10038 * might ask a Menu for its ContextMenu before the ContextMenu 10039 * is made visible. That is a problem because the Window for the 10040 * ContextMenu is only created immediately before the first time 10041 * it is shown. 10042 */ 10043 return scene.acc; 10044 } 10045 } 10046 @Override public Object getAttribute(AccessibleAttribute attribute, Object... parameters) { 10047 return queryAccessibleAttribute(attribute, parameters); 10048 } 10049 @Override public void executeAction(AccessibleAction action, Object... parameters) { 10050 executeAccessibleAction(action, parameters); 10051 } 10052 @Override public String toString() { 10053 String klassName = Node.this.getClass().getName(); 10054 return klassName.substring(klassName.lastIndexOf('.')+1); 10055 } 10056 }); 10057 } 10058 return accessible; 10059 } 10060 10061 void releaseAccessible() { 10062 Accessible acc = this.accessible; 10063 if (acc != null) { 10064 accessible = null; 10065 acc.dispose(); 10066 } 10067 } 10068 10069 } 10070