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