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