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