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