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