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