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