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