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