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