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