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