Print this page

    rev 1582 : RT-22121: Have StyleManager for Parent, also

Split | Close
Expand all
Collapse all

              --- old/javafx-ui-common/src/javafx/scene/Node.java
              +++ new/javafx-ui-common/src/javafx/scene/Node.java

       1    1  /*
       2    2   * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       3    3   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4    4   *
       5    5   * This code is free software; you can redistribute it and/or modify it
       6    6   * under the terms of the GNU General Public License version 2 only, as
       7    7   * published by the Free Software Foundation.  Oracle designates this
       8    8   * particular file as subject to the "Classpath" exception as provided
       9    9   * by Oracle in the LICENSE file that accompanied this code.
      10   10   *
      11   11   * This code is distributed in the hope that it will be useful, but WITHOUT
      12   12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13   13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14   14   * version 2 for more details (a copy is included in the LICENSE file that
      15   15   * accompanied this code).
      16   16   *
      17   17   * You should have received a copy of the GNU General Public License version
      18   18   * 2 along with this work; if not, write to the Free Software Foundation,
      19   19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20   20   *
      21   21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22   22   * or visit www.oracle.com if you need additional information or have any
      23   23   * questions.
      24   24   */
      25   25
      26   26  package javafx.scene;
      27   27
      28   28
      29   29  import javafx.beans.InvalidationListener;
      30   30  import javafx.beans.Observable;
      31   31  import javafx.beans.binding.BooleanExpression;
      32   32  import javafx.beans.value.ChangeListener;
      33   33  import javafx.collections.FXCollections;
      34   34  import javafx.collections.ListChangeListener.Change;
      35   35  import javafx.collections.ObservableList;
      36   36  import javafx.collections.ObservableMap;
      37   37  import javafx.event.Event;
      38   38  import javafx.event.EventDispatchChain;
      39   39  import javafx.event.EventDispatcher;
      40   40  import javafx.event.EventHandler;
      41   41  import javafx.event.EventTarget;
      42   42  import javafx.event.EventType;
      43   43  import javafx.geometry.BoundingBox;
      44   44  import javafx.geometry.Bounds;
      45   45  import javafx.geometry.Orientation;
      46   46  import javafx.geometry.Point2D;
      47   47  import javafx.geometry.Point3D;
      48   48  import javafx.scene.effect.Blend;
      49   49  import javafx.scene.effect.BlendMode;
      50   50  import javafx.scene.effect.Effect;
      51   51  import javafx.scene.input.ContextMenuEvent;
      52   52  import javafx.scene.input.DragEvent;
      53   53  import javafx.scene.input.Dragboard;
      54   54  import javafx.scene.input.InputEvent;
      55   55  import javafx.scene.input.InputMethodEvent;
      56   56  import javafx.scene.input.InputMethodRequests;
      57   57  import javafx.scene.input.KeyEvent;
      58   58  import javafx.scene.input.MouseEvent;
      59   59  import javafx.scene.input.MouseDragEvent;
      60   60  import javafx.scene.input.TransferMode;
      61   61  import javafx.scene.input.RotateEvent;
      62   62  import javafx.scene.input.ZoomEvent;
      63   63  import javafx.scene.input.ScrollEvent;
      64   64  import javafx.scene.transform.Rotate;
      65   65  import javafx.scene.transform.Transform;
      66   66
      67   67  import com.sun.javafx.Logging;
      68   68  import com.sun.javafx.TempState;
      69   69  import com.sun.javafx.Utils;
      70   70  import com.sun.javafx.beans.IDProperty;
      71   71  import com.sun.javafx.beans.event.AbstractNotifyListener;
      72   72  import com.sun.javafx.binding.ExpressionHelper;
      73   73  import com.sun.javafx.collections.TrackableObservableList;
      74   74  import com.sun.javafx.collections.UnmodifiableListSet;
      75   75  import com.sun.javafx.css.*;
      76   76  import com.sun.javafx.css.converters.*;
      77   77  import com.sun.javafx.effect.EffectDirtyBits;
      78   78  import com.sun.javafx.geom.BaseBounds;
      79   79  import com.sun.javafx.geom.PickRay;
      80   80  import com.sun.javafx.geom.RectBounds;
      81   81  import com.sun.javafx.geom.transform.Affine3D;
      82   82  import com.sun.javafx.geom.transform.BaseTransform;
      83   83  import com.sun.javafx.geom.transform.NoninvertibleTransformException;
      84   84  import com.sun.javafx.jmx.MXNodeAlgorithm;
      85   85  import com.sun.javafx.jmx.MXNodeAlgorithmContext;
      86   86  import com.sun.javafx.logging.PlatformLogger;
      87   87  import com.sun.javafx.perf.PerformanceTracker;
      88   88  import com.sun.javafx.scene.BoundsAccessor;
      89   89  import com.sun.javafx.scene.CSSFlags;
      90   90  import com.sun.javafx.scene.DirtyBits;
      91   91  import com.sun.javafx.scene.EventHandlerProperties;
      92   92  import com.sun.javafx.scene.NodeEventDispatcher;
      93   93  import com.sun.javafx.scene.transform.TransformUtils;
      94   94  import com.sun.javafx.scene.traversal.Direction;
      95   95  import com.sun.javafx.sg.PGNode;
      96   96  import com.sun.javafx.tk.Toolkit;
      97   97  import java.util.*;
      98   98  import javafx.beans.property.*;
      99   99  import javafx.beans.value.WritableValue;
     100  100  import javafx.geometry.Rectangle2D;
     101  101  import javafx.scene.image.WritableImage;
     102  102  import javafx.scene.input.SwipeEvent;
     103  103  import javafx.scene.input.TouchEvent;
     104  104  import javafx.scene.text.Font;
     105  105  import javafx.scene.text.Text;
     106  106  import javafx.util.Callback;
     107  107
     108  108  /**
     109  109   * Base class for scene graph nodes. A scene graph is a set of tree data structures
     110  110   * where every item has zero or one parent, and each item is either
     111  111   * a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
     112  112   * <p>
     113  113   * Each item in the scene graph is called a {@code Node}. Branch nodes are
     114  114   * of type {@link Parent}, whose concrete subclasses are {@link Group},
     115  115   * {@link javafx.scene.layout.Region}, and {@link javafx.scene.control.Control},
     116  116   * or subclasses thereof.
     117  117   * <p>
     118  118   * Leaf nodes are classes such as
     119  119   * {@link javafx.scene.shape.Rectangle}, {@link javafx.scene.text.Text},
     120  120   * {@link javafx.scene.image.ImageView}, {@link javafx.scene.media.MediaView},
     121  121   * or other such leaf classes which cannot have children. Only a single node within
     122  122   * each scene graph tree will have no parent, which is referred to as the "root" node.
     123  123   * <p>
     124  124   * There may be several trees in the scene graph. Some trees may be part of
     125  125   * a {@link Scene}, in which case they are eligible to be displayed.
     126  126   * Other trees might not be part of any {@link Scene}.
     127  127   * <p>
     128  128   * A node may occur at most once anywhere in the scene graph. Specifically,
     129  129   * a node must appear no more than once in all of the following:
     130  130   * as the root node of a {@link Scene},
     131  131   * the children ObservableList of a {@link Parent},
     132  132   * or as the clip of a {@link Node}.
     133  133   * <p>
     134  134   * The scene graph must not have cycles. A cycle would exist if a node is
     135  135   * an ancestor of itself in the tree, considering the {@link Group} content
     136  136   * ObservableList, {@link Parent} children ObservableList, and {@link Node} clip relationships
     137  137   * mentioned above.
     138  138   * <p>
     139  139   * If a program adds a child node to a Parent (including Group, Region, etc)
     140  140   * and that node is already a child of a different Parent or the root of a Scene,
     141  141   * the node is automatically (and silently) removed from its former parent.
     142  142   * If a program attempts to modify the scene graph in any other way that violates
     143  143   * the above rules, an exception is thrown, the modification attempt is ignored
     144  144   * and the scene graph is restored to its previous state.
     145  145   * <p>
     146  146   * It is possible to rearrange the structure of the scene graph, for
     147  147   * example, to move a subtree from one location in the scene graph to
     148  148   * another. In order to do this, one would normally remove the subtree from
     149  149   * its old location before inserting it at the new location. However, the
     150  150   * subtree will be automatically removed as described above if the application
     151  151   * doesn't explicitly remove it.
     152  152   * <p>
     153  153   * Node objects may be constructed and modified on any thread as long they are
     154  154   * not yet attached to a {@link Scene}. An application must attach nodes to a
     155  155   * Scene, and modify nodes that are already attached to a Scene, on the JavaFX
     156  156   * Application Thread.
     157  157   *
     158  158   * <h4>String ID</h4>
     159  159   * <p>
     160  160   * Each node in the scene graph can be given a unique {@link #idProperty id}. This id is
     161  161   * much like the "id" attribute of an HTML tag in that it is up to the designer
     162  162   * and developer to ensure that the {@code id} is unique within the scene graph.
     163  163   * A convenience function called {@link #lookup(String)} can be used to find
     164  164   * a node with a unique id within the scene graph, or within a subtree of the
     165  165   * scene graph. The id can also be used identify nodes for applying styles; see
     166  166   * the CSS section below.
     167  167   *
     168  168   * <h4>Coordinate System</h4>
     169  169   * <p>
     170  170   * The {@code Node} class defines a traditional computer graphics "local"
     171  171   * coordinate system in which the {@code x} axis increases to the right and the
     172  172   * {@code y} axis increases downwards.  The concrete node classes for shapes
     173  173   * provide variables for defining the geometry and location of the shape
     174  174   * within this local coordinate space.  For example,
     175  175   * {@link javafx.scene.shape.Rectangle} provides {@code x}, {@code y},
     176  176   * {@code width}, {@code height} variables while
     177  177   * {@link javafx.scene.shape.Circle} provides {@code centerX}, {@code centerY},
     178  178   * and {@code radius}.
     179  179   * <p>
     180  180   * At the device pixel level, integer coordinates map onto the corners and
     181  181   * cracks between the pixels and the centers of the pixels appear at the
     182  182   * midpoints between integer pixel locations.  Because all coordinate values
     183  183   * are specified with floating point numbers, coordinates can precisely
     184  184   * point to these corners (when the floating point values have exact integer
     185  185   * values) or to any location on the pixel.  For example, a coordinate of
     186  186   * {@code (0.5, 0.5)} would point to the center of the upper left pixel on the
     187  187   * {@code Stage}.  Similarly, a rectangle at {@code (0, 0)} with dimensions
     188  188   * of {@code 10} by {@code 10} would span from the upper left corner of the
     189  189   * upper left pixel on the {@code Stage} to the lower right corner of the
     190  190   * 10th pixel on the 10th scanline.  The pixel center of the last pixel
     191  191   * inside that rectangle would be at the coordinates {@code (9.5, 9.5)}.
     192  192   * <p>
     193  193   * In practice, most nodes have transformations applied to their coordinate
     194  194   * system as mentioned below.  As a result, the information above describing
     195  195   * the alignment of device coordinates to the pixel grid is relative to
     196  196   * the transformed coordinates, not the local coordinates of the nodes.
     197  197   * The {@link javafx.scene.shape.Shape Shape} class describes some additional
     198  198   * important context-specific information about coordinate mapping and how
     199  199   * it can affect rendering.
     200  200   *
     201  201   * <h4>Transformations</h4>
     202  202   * <p>
     203  203   * Any {@code Node} can have transformations applied to it. These include
     204  204   * translation, rotation, scaling, or shearing.
     205  205   * <p>
     206  206   * A <b>translation</b> transformation is one which shifts the origin of the
     207  207   * node's coordinate space along either the x or y axis. For example, if you
     208  208   * create a {@link javafx.scene.shape.Rectangle} which is drawn at the origin
     209  209   * (x=0, y=0) and has a width of 100 and a height of 50, and then apply a
     210  210   * {@link javafx.scene.transform.Translate} with a shift of 10 along the x axis
     211  211   * (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain
     212  212   * 100 points wide and 50 tall. Note that the origin was shifted, not the
     213  213   * {@code x} variable of the rectangle.
     214  214   * <p>
     215  215   * A common node transform is a translation by an integer distance, most often
     216  216   * used to lay out nodes on the stage.  Such integer translations maintain the
     217  217   * device pixel mapping so that local coordinates that are integers still
     218  218   * map to the cracks between pixels.
     219  219   * <p>
     220  220   * A <b>rotation</b> transformation is one which rotates the coordinate space of
     221  221   * the node about a specified "pivot" point, causing the node to appear rotated.
     222  222   * For example, if you create a {@link javafx.scene.shape.Rectangle} which is
     223  223   * drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and
     224  224   * you apply a {@link javafx.scene.transform.Rotate} with a 90 degree rotation
     225  225   * (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then
     226  226   * the rectangle will be drawn as if its x and y were zero but its height was
     227  227   * 100 and its width -30. That is, it is as if a pin is being stuck at the top
     228  228   * left corner and the rectangle is rotating 90 degrees clockwise around that
     229  229   * pin. If the pivot point is instead placed in the center of the rectangle
     230  230   * (at point x=50, y=15) then the rectangle will instead appear to rotate about
     231  231   * its center.
     232  232   * <p>
     233  233   * Note that as with all transformations, the x, y, width, and height variables
     234  234   * of the rectangle (which remain relative to the local coordinate space) have
     235  235   * not changed, but rather the transformation alters the entire coordinate space
     236  236   * of the rectangle.
     237  237   * <p>
     238  238   * A <b>scaling</b> transformation causes a node to either appear larger or
     239  239   * smaller depending on the scaling factor. Scaling alters the coordinate space
     240  240   * of the node such that each unit of distance along the axis in local
     241  241   * coordinates is multipled by the scale factor. As with rotation
     242  242   * transformations, scaling transformations are applied about a "pivot" point.
     243  243   * You can think of this as the point in the Node around which you "zoom".  For
     244  244   * example, if you create a {@link javafx.scene.shape.Rectangle} with a
     245  245   * {@code strokeWidth} of 5, and a width and height of 50, and you apply a
     246  246   * {@link javafx.scene.transform.Scale} with scale factors (x=2.0, y=2.0) and
     247  247   * a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle
     248  248   * (including the stroke) will double in size, growing to the right and
     249  249   * downwards from the origin.
     250  250   * <p>
     251  251   * A <b>shearing</b> transformation, sometimes called a skew, effectively
     252  252   * rotates one axis so that the x and y axes are no longer perpendicular.
     253  253   * <p>
     254  254   * Multiple transformations may be applied to a node by specifying an ordered
     255  255   * chain of transforms.  The order in which the transforms are applied is
     256  256   * defined by the ObservableList specified in the {@link #getTransforms transforms} variable.
     257  257   *
     258  258   * <h4>Bounding Rectangles</h4>
     259  259   * <p>
     260  260   * Since every {@code Node} has transformations, every Node's geometric
     261  261   * bounding rectangle can be described differently depending on whether
     262  262   * transformations are accounted for or not.
     263  263   * <p>
     264  264   * Each {@code Node} has a read-only {@link #boundsInLocalProperty boundsInLocal}
     265  265   * variable which specifies the bounding rectangle of the {@code Node} in
     266  266   * untransformed local coordinates. {@code boundsInLocal} includes the
     267  267   * Node's shape geometry, including any space required for a
     268  268   * non-zero stroke that may fall outside the local position/size variables,
     269  269   * and its {@link #clipProperty clip} and {@link #effectProperty effect} variables.
     270  270   * <p>
     271  271   * Each {@code Node} also has a read-only {@link #boundsInParentProperty boundsInParent} variable which
     272  272   * specifies the bounding rectangle of the {@code Node} after all transformations
     273  273   * have been applied, including those set in {@link #getTransforms transforms},
     274  274   * {@link #scaleXProperty scaleX}/{@link #scaleYProperty scaleY}, {@link #rotateProperty rotate},
     275  275   * {@link #translateXProperty translateX}/{@link #translateYProperty translateY}, and {@link #layoutXProperty layoutX}/{@link #layoutYProperty layoutY}.
     276  276   * It is called "boundsInParent" because the rectangle will be relative to the
     277  277   * parent's coordinate system.  This is the 'visual' bounds of the node.
     278  278   * <p>
     279  279   * Finally, the {@link #layoutBoundsProperty layoutBounds} variable defines the rectangular bounds of
     280  280   * the {@code Node} that should be used as the basis for layout calculations and
     281  281   * may differ from the visual bounds of the node.  For shapes, Text, and ImageView,
     282  282   * layoutBounds by default includes only the shape geometry, including space required
     283  283   * for a non-zero {@code strokeWidth}, but does <i>not</i> include the effect,
     284  284   * clip, or any transforms. For resizable classes (Regions and Controls)
     285  285   * layoutBounds will always map to {@code 0,0 width x height}.
     286  286   *
     287  287   * <p> The image shows a node with transformation (rotation by 20 degrees)
     288  288   * and its bounds. The red rectangle represents {@code boundsInParent} in the
     289  289   * coordinate space of the Node's parent. The green rectangle represents {@code boundsInLocal}
     290  290   * in coordinate space of the Node. </p>
     291  291   * <p> <img src="doc-files/bounds-complex.png"/> </p>
     292  292   *
     293  293   * <p> The images show a filled and stroked rectangle and their bounds. The
     294  294   * first rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]}
     295  295   * has the following bounds bounds: {@code [x:10.0 y:10.0 width:100.0 height:100.0]}.
     296  296   *
     297  297   * The second rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]}
     298  298   * has the following bounds: {@code [x:5.0 y:5.0 width:110.0 height:110.0]}.
     299  299   *
     300  300   * Since neither of the rectangles has any transformation applied,
     301  301   * {@code boundsInParent} and {@code boundsInLocal} are the same. </p>
     302  302   * <p> <img src="doc-files/bounds.png"/> </p>
     303  303   *
     304  304   *
     305  305   * <h4>CSS</h4>
     306  306   * <p>
     307  307   * The {@code Node} class contains {@code id}, {@code styleClass}, and
     308  308   * {@code style} variables that are used in styling this node from
     309  309   * CSS. The {@code id} and {@code styleClass} variables are used in
     310  310   * CSS style sheets to identify nodes to which styles should be
     311  311   * applied. The {@code style} variable contains style properties and
     312  312   * values that are applied directly to this node.
     313  313   * <p>
     314  314   * For further information about CSS and how to apply CSS styles
     315  315   * to nodes, see the <a href="doc-files/cssref.html">CSS Reference
     316  316   * Guide</a>.
     317  317   */
     318  318  @IDProperty("id")
     319  319  public abstract class Node implements EventTarget {
     320  320
     321  321       static {
     322  322            PerformanceTracker.logEvent("Node class loaded");
     323  323       }
     324  324
     325  325      /**************************************************************************
     326  326       *                                                                        *
     327  327       * Methods and state for managing the dirty bits of a Node. The dirty     *
     328  328       * bits are flags used to keep track of what things are dirty on the      *
     329  329       * node and therefore need processing on the next pulse. Since the pulse  *
     330  330       * happens asynchronously to the change that made the node dirty (for     *
     331  331       * performance reasons), we need to keep track of what things have        *
     332  332       * changed.                                                               *
     333  333       *                                                                        *
     334  334       *************************************************************************/
     335  335
     336  336      /**
     337  337       * Set of dirty bits that are set when state is invalidated and cleared by
     338  338       * the updateState method, which is called from the synchronizer.
     339  339       */
     340  340      private int dirtyBits;
     341  341
     342  342      /**
     343  343       * Mark the specified bit as dirty, and add this node to the scene's dirty list.
     344  344       *
     345  345       * @treatAsPrivate implementation detail
     346  346       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     347  347       */
     348  348      @Deprecated
     349  349      protected void impl_markDirty(DirtyBits dirtyBit) {
     350  350          if (impl_isDirtyEmpty()) {
     351  351              Scene s = getScene();
     352  352              if (s != null)
     353  353                  s.addToDirtyList(this);
     354  354          }
     355  355
     356  356          dirtyBits |= dirtyBit.getMask();
     357  357      }
     358  358
     359  359      /**
     360  360       * Test whether the specified dirty bit is set
     361  361       *
     362  362       * @treatAsPrivate implementation detail
     363  363       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     364  364       */
     365  365      @Deprecated
     366  366      protected final boolean impl_isDirty(DirtyBits dirtyBit) {
     367  367          return (dirtyBits & dirtyBit.getMask()) != 0;
     368  368      }
     369  369
     370  370      /**
     371  371       * Clear the specified dirty bit
     372  372       *
     373  373       * @treatAsPrivate implementation detail
     374  374       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     375  375       */
     376  376      @Deprecated
     377  377      protected final void impl_clearDirty(DirtyBits dirtyBit) {
     378  378          dirtyBits &= ~dirtyBit.getMask();
     379  379      }
     380  380
     381  381      /**
     382  382       * Set all dirty bits
     383  383       */
     384  384      private void setDirty() {
     385  385          dirtyBits = ~0;
     386  386      }
     387  387
     388  388      /**
     389  389       * Clear all dirty bits
     390  390       */
     391  391      private void clearDirty() {
     392  392          dirtyBits = 0;
     393  393      }
     394  394
     395  395      /**
     396  396       * Test whether the set of dirty bits is empty
     397  397       *
     398  398       * @treatAsPrivate implementation detail
     399  399       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     400  400       */
     401  401      @Deprecated
     402  402      protected final boolean impl_isDirtyEmpty() {
     403  403          return dirtyBits == 0;
     404  404      }
     405  405
     406  406      /**************************************************************************
     407  407       *                                                                        *
     408  408       * Methods for synchronizing state from this Node to its PG peer. This    *
     409  409       * should only *ever* be called during synchronization initialized as a   *
     410  410       * result of a pulse. Any attempt to synchronize at any other time may    *
     411  411       * cause rendering artifacts.                                             *
     412  412       *                                                                        *
     413  413       *************************************************************************/
     414  414
     415  415      /**
     416  416       * Called by the synchronizer to update the state and
     417  417       * clear dirtybits of this node in the PG graph
     418  418       *
     419  419       * @treatAsPrivate implementation detail
     420  420       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     421  421       */
     422  422      @Deprecated
     423  423      public final void impl_syncPGNode() {
     424  424          if (!impl_isDirtyEmpty()) {
     425  425              impl_updatePG();
     426  426              clearDirty();
     427  427          }
     428  428      }
     429  429
     430  430      /**
     431  431       * This function is called by the synchronizer to update the state of the
     432  432       * PG Node from the FX Node. Subclasses of Node should override this method
     433  433       * and must call super.impl_updatePG()
     434  434       *
     435  435       * @treatAsPrivate implementation detail
     436  436       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     437  437       */
     438  438      @Deprecated
     439  439      public void impl_updatePG() {
     440  440          final PGNode peer = impl_getPGNode();
     441  441          TempState tempState = null;
     442  442          if (impl_isDirty(DirtyBits.NODE_TRANSFORM)) {
     443  443              tempState = TempState.getInstance();
     444  444              updateLocalToParentTransform();
     445  445              peer.setTransformMatrix(localToParentTx);
     446  446              tempState.bounds = getTransformedBounds(
     447  447                                         tempState.bounds,
     448  448                                         BaseTransform.IDENTITY_TRANSFORM);
     449  449              peer.setTransformedBounds(tempState.bounds);
     450  450          }
     451  451
     452  452          if (impl_isDirty(DirtyBits.NODE_BOUNDS)) {
     453  453              if (tempState == null) {
     454  454                  tempState = TempState.getInstance();
     455  455              }
     456  456               tempState.bounds = getGeomBounds(tempState.bounds,
     457  457                                               BaseTransform.IDENTITY_TRANSFORM);
     458  458              peer.setContentBounds(tempState.bounds);
     459  459              if (!impl_isDirty(DirtyBits.NODE_TRANSFORM)) {
     460  460                  tempState.bounds = getTransformedBounds(
     461  461                                             tempState.bounds,
     462  462                                             BaseTransform.IDENTITY_TRANSFORM);
     463  463                  peer.setTransformedBounds(tempState.bounds);
     464  464              }
     465  465          }
     466  466
     467  467          if (impl_isDirty(DirtyBits.NODE_OPACITY)) {
     468  468              peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
     469  469          }
     470  470
     471  471          if (impl_isDirty(DirtyBits.NODE_CACHE)) {
     472  472              peer.setCachedAsBitmap(isCache(), toPGCacheHint(getCacheHint()));
     473  473          }
     474  474
     475  475          if (impl_isDirty(DirtyBits.NODE_CLIP)) {
     476  476              peer.setClipNode(getClip() != null ? getClip().impl_getPGNode() : null);
     477  477          }
     478  478
     479  479          if (impl_isDirty(DirtyBits.EFFECT_EFFECT)) {
     480  480              if (getEffect() != null) {
     481  481                  getEffect().impl_sync();
     482  482                  peer.effectChanged();
     483  483              }
     484  484          }
     485  485
     486  486          if (impl_isDirty(DirtyBits.NODE_EFFECT)) {
     487  487              peer.setEffect(getEffect() != null ? getEffect().impl_getImpl() : null);
     488  488          }
     489  489
     490  490          if (impl_isDirty(DirtyBits.NODE_VISIBLE)) {
     491  491              peer.setVisible(isVisible());
     492  492          }
     493  493
     494  494          if (impl_isDirty(DirtyBits.NODE_DEPTH_TEST)) {
     495  495              peer.setDepthTest(isDerivedDepthTest());
     496  496          }
     497  497
     498  498          if (impl_isDirty(DirtyBits.NODE_BLENDMODE)) {
     499  499              BlendMode mode = getBlendMode();
     500  500              peer.setNodeBlendMode((mode == null)
     501  501                                    ? null
     502  502                                    : Blend.impl_getToolkitMode(mode));
     503  503          }
     504  504      }
     505  505
     506  506      /*************************************************************************
     507  507      *                                                                        *
     508  508      *                                                                        *
     509  509      *                                                                        *
     510  510      *************************************************************************/
     511  511
     512  512      private static final Object USER_DATA_KEY = new Object();
     513  513      // A map containing a set of properties for this node
     514  514      private ObservableMap<Object, Object> properties;
     515  515
     516  516      /**
     517  517        * Returns an observable map of properties on this node for use primarily
     518  518        * by application developers.
     519  519        *
     520  520        * @return an observable map of properties on this node for use primarily
     521  521        * by application developers
     522  522        */
     523  523       public final ObservableMap<Object, Object> getProperties() {
     524  524          if (properties == null) {
     525  525              properties = FXCollections.observableMap(new HashMap<Object, Object>());
     526  526          }
     527  527          return properties;
     528  528      }
     529  529
     530  530      /**
     531  531       * Tests if Node has properties.
     532  532       * @return true if node has properties.
     533  533       */
     534  534       public boolean hasProperties() {
     535  535          return properties != null;
     536  536      }
     537  537
     538  538      /**
     539  539       * Convenience method for setting a single Object property that can be
     540  540       * retrieved at a later date. This is functionally equivalent to calling
     541  541       * the getProperties().put(Object key, Object value) method. This can later
     542  542       * be retrieved by calling {@link Node#getUserData()}.
     543  543       *
     544  544       * @param value The value to be stored - this can later be retrieved by calling
     545  545       *          {@link Node#getUserData()}.
     546  546       */
     547  547      public void setUserData(Object value) {
     548  548          getProperties().put(USER_DATA_KEY, value);
     549  549      }
     550  550
     551  551      /**
     552  552       * Returns a previously set Object property, or null if no such property
     553  553       * has been set using the {@link Node#setUserData(java.lang.Object)} method.
     554  554       *
     555  555       * @return The Object that was previously set, or null if no property
     556  556       *          has been set or if null was set.
     557  557       */
     558  558      public Object getUserData() {
     559  559          return getProperties().get(USER_DATA_KEY);
     560  560      }
     561  561
     562  562      /**************************************************************************
     563  563       *                                                                        *
     564  564       *
     565  565       *                                                                        *
     566  566       *************************************************************************/
     567  567
     568  568      /**
     569  569       * The parent of this {@code Node}. If this {@code Node} has not been added
     570  570       * to a scene graph, then parent will be null.
     571  571       *
     572  572       * @defaultValue null
     573  573       */
     574  574      private ReadOnlyObjectWrapper<Parent> parent;
     575  575
     576  576      final void setParent(Parent value) {
     577  577          parentPropertyImpl().set(value);
     578  578      }
     579  579
     580  580      public final Parent getParent() {
     581  581          return parent == null ? null : parent.get();
     582  582      }
     583  583
     584  584      public final ReadOnlyObjectProperty<Parent> parentProperty() {
     585  585          return parentPropertyImpl().getReadOnlyProperty();
     586  586      }
     587  587
     588  588      private ReadOnlyObjectWrapper<Parent> parentPropertyImpl() {
     589  589          if (parent == null) {
     590  590              parent = new ReadOnlyObjectWrapper<Parent>() {
     591  591                  private Parent oldParent;
     592  592
     593  593                  @Override
     594  594                  protected void invalidated() {
     595  595                      if (oldParent != null) {
     596  596                          oldParent.disabledProperty().removeListener(parentDisabledChangedListener);
     597  597                          oldParent.treeVisibleProperty().removeListener(parentTreeVisibleChangedListener);
     598  598                          if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
     599  599                              ((Node) oldParent).localToSceneTransformProperty().removeListener(
     600  600                                      nodeTransformation.getLocalToSceneInvalidationListener());
     601  601                          }
     602  602                      }
     603  603                      updateDisabled();
     604  604                      computeDerivedDepthTest();
     605  605                      final Parent newParent = get();
     606  606                      if (newParent != null) {
     607  607                          newParent.disabledProperty().addListener(parentDisabledChangedListener);
     608  608                          newParent.treeVisibleProperty().addListener(parentTreeVisibleChangedListener);
     609  609                          if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
     610  610                              ((Node) newParent).localToSceneTransformProperty().addListener(
     611  611                                      nodeTransformation.getLocalToSceneInvalidationListener());
     612  612                          }
     613  613                          //
     614  614                          // if parent changed, then CSS needs to be reapplied so
     615  615                          // that this node will get the right styles. This used
     616  616                          // to be done from Parent.children's onChanged method.
     617  617                          // See the comments there, also.
     618  618                          //
     619  619                          impl_reapplyCSS();
     620  620                      }
     621  621                      updateTreeVisible();
     622  622                      oldParent = newParent;
     623  623                      invalidateLocalToSceneTransform();
     624  624                  }
     625  625
     626  626                  @Override
     627  627                  public Object getBean() {
     628  628                      return Node.this;
     629  629                  }
     630  630
     631  631                  @Override
     632  632                  public String getName() {
     633  633                      return "parent";
     634  634                  }
     635  635              };
     636  636          }
     637  637          return parent;
     638  638                      }
     639  639
     640  640      private final InvalidationListener parentDisabledChangedListener = new InvalidationListener() {
     641  641          @Override public void invalidated(Observable valueModel) {
     642  642              updateDisabled();
     643  643          }
     644  644      };
     645  645
     646  646      private final InvalidationListener parentTreeVisibleChangedListener = new InvalidationListener() {
     647  647          @Override public void invalidated(Observable valueModel) {
     648  648              updateTreeVisible();
     649  649          }
     650  650      };
     651  651
     652  652      /**
     653  653       * The {@link Scene} that this {@code Node} is part of. If the Node is not
     654  654       * part of a scene, then this variable will be null.
     655  655       *
     656  656       * @defaultValue null
     657  657       */
     658  658      private ReadOnlyObjectWrapper<Scene> scene;
     659  659
     660  660      final void setScene(Scene value) {
     661  661          scenePropertyImpl().set(value);
     662  662      }
     663  663
     664  664      public final Scene getScene() {
     665  665          return scene == null ? null : scene.get();
     666  666      }
     667  667
     668  668      /**
     669  669       * Exists for Parent
     670  670       */
     671  671      void sceneChanged(Scene old) { }
     672  672
     673  673      public final ReadOnlyObjectProperty<Scene> sceneProperty() {
     674  674          return scenePropertyImpl().getReadOnlyProperty();
     675  675      }
     676  676
     677  677      private ReadOnlyObjectWrapper<Scene> scenePropertyImpl() {
     678  678          if (scene == null) {
     679  679              scene = new ReadOnlyObjectWrapper<Scene>() {
     680  680                  private Scene oldScene;
     681  681
     682  682                  @Override
     683  683                  protected void invalidated() {
     684  684                      Scene _scene = get();
     685  685                      if (getClip() != null) {
     686  686                          getClip().setScene(_scene);
     687  687                      }
     688  688                      updateCanReceiveFocus();
     689  689                      if (isFocusTraversable()) {
     690  690                          if (oldScene != null) {
     691  691                              oldScene.unregisterTraversable(Node.this);
     692  692                          }
     693  693                          if (_scene != null) {
     694  694                              _scene.registerTraversable(Node.this);
     695  695                          }
     696  696                      }
     697  697                      focusSetDirty(oldScene);
     698  698                      focusSetDirty(_scene);
     699  699                      sceneChanged(oldScene);
     700  700                      if (oldScene != _scene) {
     701  701                          //Note: no need to remove from scene's dirty list
     702  702                          //Scene's is checking if the node's scene is correct
     703  703                          impl_reapplyCSS();
     704  704                          if (_scene != null && !impl_isDirtyEmpty()) {
     705  705                              _scene.addToDirtyList(Node.this);
     706  706                          }
     707  707                      }
     708  708                      oldScene = _scene;
     709  709                  }
     710  710
     711  711                  @Override
     712  712                  public Object getBean() {
     713  713                      return Node.this;
     714  714                  }
     715  715
     716  716                  @Override
     717  717                  public String getName() {
     718  718                      return "scene";
     719  719                  }
     720  720              };
     721  721          }
     722  722          return scene;
     723  723      }
     724  724
     725  725      /**
     726  726       * The id of this {@code Node}. This simple string identifier is useful for
     727  727       * finding a specific Node within the scene graph. While the id of a Node
     728  728       * should be unique within the scene graph, this uniqueness is not enforced.
     729  729       * This is analogous to the "id" attribute on an HTML element
     730  730       * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
     731  731       * <p>
     732  732       *     For example, if a Node is given the id of "myId", then the lookup method can
     733  733       *     be used to find this node as follows: <code>scene.lookup("#myId");</code>.
     734  734       * </p>
     735  735       *
     736  736       * @defaultValue null
     737  737       */
     738  738      private StringProperty id;
     739  739
     740  740      public final void setId(String value) {
     741  741          idProperty().set(value);
     742  742      }
     743  743
     744  744      //TODO: this is copied from the property in order to add the @return statement.
     745  745      //      We should have a better, general solution without the need to copy it.
     746  746      /**
     747  747       * The id of this {@code Node}. This simple string identifier is useful for
     748  748       * finding a specific Node within the scene graph. While the id of a Node
     749  749       * should be unique within the scene graph, this uniqueness is not enforced.
     750  750       * This is analogous to the "id" attribute on an HTML element
     751  751       * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
     752  752       *
     753  753       * @return the id assigned to this {@code Node} using the {@code setId}
     754  754       *         method or {@code null}, if no id has been assigned.
     755  755       * @defaultValue null
     756  756       */
     757  757      public final String getId() {
     758  758          return id == null ? null : id.get();
     759  759      }
     760  760
     761  761      public final StringProperty idProperty() {
     762  762          if (id == null) {
     763  763              id = new StringPropertyBase() {
     764  764
     765  765                  @Override
     766  766                  protected void invalidated() {
     767  767                       impl_reapplyCSS();
     768  768                  }
     769  769
     770  770                  @Override
     771  771                  public Object getBean() {
     772  772                      return Node.this;
     773  773                  }
     774  774
     775  775                  @Override
     776  776                  public String getName() {
     777  777                      return "id";
     778  778                  }
     779  779              };
     780  780          }
     781  781          return id;
     782  782      }
     783  783
     784  784      /**
     785  785       * A list of String identifiers which can be used to logically group
     786  786       * Nodes, specifically for an external style engine. This variable is
     787  787       * analogous to the "class" attribute on an HTML element and, as such,
     788  788       * each element of the list is a style class to which this Node belongs.
     789  789       *
     790  790       * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
     791  791       * @defaultValue null
     792  792       */
     793  793      private ObservableList<String> styleClass = new TrackableObservableList<String>() {
     794  794          @Override
     795  795          protected void onChanged(Change<String> c) {
     796  796              impl_reapplyCSS();
     797  797          }
     798  798
     799  799          @Override
     800  800          public String toString() {
     801  801              if (size() == 0) {
     802  802                  return "";
     803  803              } else if (size() == 1) {
     804  804                  return get(0);
     805  805              } else {
     806  806                  StringBuilder buf = new StringBuilder();
     807  807                  for (int i = 0; i < size(); i++) {
     808  808                      buf.append(get(i));
     809  809                      if (i + 1 < size()) {
     810  810                          buf.append(' ');
     811  811                      }
     812  812                  }
     813  813                  return buf.toString();
     814  814              }
     815  815          }
     816  816      };
     817  817
     818  818      public final ObservableList<String> getStyleClass() {
     819  819          return styleClass;
     820  820      }
     821  821
     822  822      /**
     823  823       * A string representation of the CSS style associated with this
     824  824       * specific {@code Node}. This is analogous to the "style" attribute of an
     825  825       * HTML element. Note that, like the HTML style attribute, this
     826  826       * variable contains style properties and values and not the
     827  827       * selector portion of a style rule.
     828  828       * @defaultValue empty string
     829  829       */
     830  830      private StringProperty style;
     831  831
     832  832      /**
     833  833       * A string representation of the CSS style associated with this
     834  834       * specific {@code Node}. This is analogous to the "style" attribute of an
     835  835       * HTML element. Note that, like the HTML style attribute, this
     836  836       * variable contains style properties and values and not the
     837  837       * selector portion of a style rule.
     838  838       * @param value The inline CSS style to use for this {@code Node}.
     839  839       *         {@code null} is implicitly converted to an empty String.
     840  840       * @defaultValue empty string
     841  841       */
     842  842      public final void setStyle(String value) {
     843  843          styleProperty().set(value);
     844  844      }
     845  845
     846  846      // TODO: javadoc copied from property for the sole purpose of providing a return tag
     847  847      /**
     848  848       * A string representation of the CSS style associated with this
     849  849       * specific {@code Node}. This is analogous to the "style" attribute of an
     850  850       * HTML element. Note that, like the HTML style attribute, this
     851  851       * variable contains style properties and values and not the
     852  852       * selector portion of a style rule.
     853  853       * @defaultValue empty string
     854  854       * @return The inline CSS style associated with this {@code Node}.
     855  855       *         If this {@code Node} does not have an inline style,
     856  856       *         an empty String is returned.
     857  857       */
     858  858      public final String getStyle() {
     859  859          return style == null ? "" : style.get();
     860  860      }
     861  861
     862  862      public final StringProperty styleProperty() {
     863  863          if (style == null) {
     864  864              style = new StringPropertyBase("") {
     865  865
     866  866                  @Override public void set(String value) {
     867  867                      // getStyle returns an empty string if the style property
     868  868                      // is null. To be consistent, getStyle should also return
     869  869                      // an empty string when the style property's value is null.
     870  870                      super.set((value != null) ? value : "");
     871  871                  }
     872  872
     873  873                  @Override
     874  874                  protected void invalidated() {
     875  875                      impl_reapplyCSS();
     876  876                  }
     877  877
     878  878                  @Override
     879  879                  public Object getBean() {
     880  880                      return Node.this;
     881  881                  }
     882  882
     883  883                  @Override
     884  884                  public String getName() {
     885  885                      return "style";
     886  886                  }
     887  887              };
     888  888          }
     889  889          return style;
     890  890      }
     891  891
     892  892      /**
     893  893       * Specifies whether this {@code Node} and any subnodes should be rendered
     894  894       * as part of the scene graph. A node may be visible and yet not be shown
     895  895       * in the rendered scene if, for instance, it is off the screen or obscured
     896  896       * by another Node. Invisible nodes never receive mouse events or
     897  897       * keyboard focus and never maintain keyboard focus when they become
     898  898       * invisible.
     899  899       *
     900  900       * @defaultValue true
     901  901       */
     902  902      private BooleanProperty visible;
     903  903
     904  904      public final void setVisible(boolean value) {
     905  905          visibleProperty().set(value);
     906  906      }
     907  907
     908  908      public final boolean isVisible() {
     909  909          return visible == null ? true : visible.get();
     910  910      }
     911  911
     912  912      public final BooleanProperty visibleProperty() {
     913  913          if (visible == null) {
     914  914              visible = new StyleableBooleanProperty(true) {
     915  915
     916  916                  @Override
     917  917                  protected void invalidated() {
     918  918                      impl_markDirty(DirtyBits.NODE_VISIBLE);
     919  919                      impl_geomChanged();
     920  920                      updateTreeVisible();
     921  921                      if (getParent() != null) {
     922  922                          // notify the parent of the potential change in visibility
     923  923                          // of this node, since visibility affects bounds of the
     924  924                          // parent node
     925  925                          getParent().childVisibilityChanged(Node.this);
     926  926                      }
     927  927                  }
     928  928
     929  929                  @Override
     930  930                  public StyleableProperty getStyleableProperty() {
     931  931                      return StyleableProperties.VISIBILITY;
     932  932                  }
     933  933
     934  934                  @Override
     935  935                  public Object getBean() {
     936  936                      return Node.this;
     937  937                  }
     938  938
     939  939                  @Override
     940  940                  public String getName() {
     941  941                      return "visible";
     942  942                  }
     943  943              };
     944  944          }
     945  945          return visible;
     946  946      }
     947  947
     948  948      public final void setCursor(Cursor value) {
     949  949          cursorProperty().set(value);
     950  950      }
     951  951
     952  952      public final Cursor getCursor() {
     953  953          return (miscProperties == null) ? DEFAULT_CURSOR
     954  954                                          : miscProperties.getCursor();
     955  955      }
     956  956
     957  957      /**
     958  958       * Defines the mouse cursor for this {@code Node} and subnodes. If null,
     959  959       * then the cursor of the first parent node with a non-null cursor will be
     960  960       * used. If no Node in the scene graph defines a cursor, then the cursor
     961  961       * of the {@code Scene} will be used.
     962  962       *
     963  963       * @defaultValue null
     964  964       */
     965  965      public final ObjectProperty<Cursor> cursorProperty() {
     966  966          return getMiscProperties().cursorProperty();
     967  967      }
     968  968
     969  969      /**
     970  970       * Specifies how opaque (that is, solid) the {@code Node} appears. A Node
     971  971       * with 0% opacity is fully translucent. That is, while it is still
     972  972       * {@link #visibleProperty visible} and rendered, you generally won't be able to see it. The
     973  973       * exception to this rule is when the {@code Node} is combined with a
     974  974       * blending mode and blend effect in which case a translucent Node may still
     975  975       * have an impact in rendering. An opacity of 50% will render the node as
     976  976       * being 50% transparent.
     977  977       * <p>
     978  978       * A {@link #visibleProperty visible} node with any opacity setting still receives mouse
     979  979       * events and can receive keyboard focus. For example, if you want to have
     980  980       * a large invisible rectangle overlay all {@code Node}s in the scene graph
     981  981       * in order to intercept mouse events but not be visible to the user, you could
     982  982       * create a large {@code Rectangle} that had an opacity of 0%.
     983  983       * <p>
     984  984       * Opacity is specified as a value between 0 and 1. Values less than 0 are
     985  985       * treated as 0, values greater than 1 are treated as 1.
     986  986       * <p>
     987  987       * On some platforms ImageView might not support opacity variable.
     988  988       *
     989  989       * <p>
     990  990       * There is a known limitation of mixing opacity < 1.0 with a 3D Transform.
     991  991       * Opacity/Blending is essentially a 2D image operation. The result of
     992  992       * an opacity < 1.0 set on a {@link Group} node with 3D transformed children
     993  993       * will cause its children to be rendered in order without Z-buffering
     994  994       * applied between those children.
     995  995       *
     996  996       * @defaultValue 1.0
     997  997       */
     998  998      private DoubleProperty opacity;
     999  999
    1000 1000      public final void setOpacity(double value) {
    1001 1001          opacityProperty().set(value);
    1002 1002      }
    1003 1003      public final double getOpacity() {
    1004 1004          return opacity == null ? 1 : opacity.get();
    1005 1005      }
    1006 1006
    1007 1007      public final DoubleProperty opacityProperty() {
    1008 1008          if (opacity == null) {
    1009 1009              opacity = new StyleableDoubleProperty(1) {
    1010 1010
    1011 1011                  @Override
    1012 1012                  public void invalidated() {
    1013 1013                      impl_markDirty(DirtyBits.NODE_OPACITY);
    1014 1014                  }
    1015 1015
    1016 1016                  @Override
    1017 1017                  public StyleableProperty getStyleableProperty() {
    1018 1018                      return StyleableProperties.OPACITY;
    1019 1019                  }
    1020 1020
    1021 1021                  @Override
    1022 1022                  public Object getBean() {
    1023 1023                      return Node.this;
    1024 1024                  }
    1025 1025
    1026 1026                  @Override
    1027 1027                  public String getName() {
    1028 1028                      return "opacity";
    1029 1029                  }
    1030 1030              };
    1031 1031          }
    1032 1032          return opacity;
    1033 1033      }
    1034 1034
    1035 1035      /**
    1036 1036       * The {@link javafx.scene.effect.BlendMode} used to blend this individual node
    1037 1037       * into the scene behind it. If this node happens to be a Group then all of the
    1038 1038       * children will be composited individually into a temporary buffer using their
    1039 1039       * own blend modes and then that temporary buffer will be composited into the
    1040 1040       * scene using the specified blend mode.
    1041 1041       *
    1042 1042       * A value of {@code null} is treated as pass-though this means no effect on a
    1043 1043       * parent such as a Group and the equivalent of SRC_OVER for a single Node.
    1044 1044       *
    1045 1045       * @defaultValue null
    1046 1046       */
    1047 1047      private javafx.beans.property.ObjectProperty<BlendMode> blendMode;
    1048 1048
    1049 1049      public final void setBlendMode(BlendMode value) {
    1050 1050          blendModeProperty().set(value);
    1051 1051      }
    1052 1052      public final BlendMode getBlendMode() {
    1053 1053          return blendMode == null ? null : blendMode.get();
    1054 1054      }
    1055 1055
    1056 1056      public final ObjectProperty<BlendMode> blendModeProperty() {
    1057 1057          if (blendMode == null) {
    1058 1058              blendMode = new StyleableObjectProperty<BlendMode>(null) {
    1059 1059                  @Override public void invalidated() {
    1060 1060                      impl_markDirty(DirtyBits.NODE_BLENDMODE);
    1061 1061                  }
    1062 1062
    1063 1063                  @Override
    1064 1064                  public StyleableProperty getStyleableProperty() {
    1065 1065                      return StyleableProperties.BLEND_MODE;
    1066 1066                  }
    1067 1067
    1068 1068                  @Override
    1069 1069                  public Object getBean() {
    1070 1070                      return Node.this;
    1071 1071                  }
    1072 1072
    1073 1073                  @Override
    1074 1074                  public String getName() {
    1075 1075                      return "blendMode";
    1076 1076                  }
    1077 1077              };
    1078 1078          }
    1079 1079          return blendMode;
    1080 1080      }
    1081 1081
    1082 1082      public final void setClip(Node value) {
    1083 1083          clipProperty().set(value);
    1084 1084      }
    1085 1085
    1086 1086      public final Node getClip() {
    1087 1087          return (miscProperties == null) ? DEFAULT_CLIP
    1088 1088                                          : miscProperties.getClip();
    1089 1089      }
    1090 1090
    1091 1091      /**
    1092 1092       * Specifies a {@code Node} to use to define the the clipping shape for this
    1093 1093       * Node. This clipping Node is not a child of this {@code Node} in the scene
    1094 1094       * graph sense. Rather, it is used to define the clip for this {@code Node}.
    1095 1095       * <p>
    1096 1096       * For example, you can use an {@link javafx.scene.image.ImageView} Node as
    1097 1097       * a mask to represent the Clip. Or you could use one of the geometric shape
    1098 1098       * Nodes such as {@link javafx.scene.shape.Rectangle} or
    1099 1099       * {@link javafx.scene.shape.Circle}. Or you could use a
    1100 1100       * {@link javafx.scene.text.Text} node to represent the Clip.
    1101 1101       * <p>
    1102 1102       * See the class documentation for {@link Node} for scene graph structure
    1103 1103       * restrictions on setting the clip. If these restrictions are violated by
    1104 1104       * a change to the clip variable, the change is ignored and the
    1105 1105       * previous value of the clip variable is restored.
    1106 1106       * <p>
    1107 1107       * Note that this is a conditional feature. See
    1108 1108       * {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP}
    1109 1109       * for more information.
    1110 1110       * <p>
    1111 1111       * There is a known limitation of mixing Clip with a 3D Transform.
    1112 1112       * Clipping is essentially a 2D image operation. The result of
    1113 1113       * a Clip set on a {@link Group} node with 3D transformed children
    1114 1114       * will cause its children to be rendered in order without Z-buffering
    1115 1115       * applied between those children.
    1116 1116       *
    1117 1117       * @defaultValue null
    1118 1118       */
    1119 1119      public final ObjectProperty<Node> clipProperty() {
    1120 1120          return getMiscProperties().clipProperty();
    1121 1121      }
    1122 1122
    1123 1123      private com.sun.javafx.sg.PGNode.CacheHint toPGCacheHint(CacheHint ch) {
    1124 1124          if (ch == CacheHint.DEFAULT) {
    1125 1125              return PGNode.CacheHint.DEFAULT;
    1126 1126          } else if (ch == CacheHint.SCALE) {
    1127 1127              return PGNode.CacheHint.SCALE;
    1128 1128          } else if (ch == CacheHint.ROTATE) {
    1129 1129              return PGNode.CacheHint.ROTATE;
    1130 1130          } else if (ch == CacheHint.SCALE_AND_ROTATE) {
    1131 1131              return PGNode.CacheHint.SCALE_AND_ROTATE;
    1132 1132          } else if (ch == CacheHint.SPEED) {
    1133 1133              return PGNode.CacheHint.SCALE_AND_ROTATE;
    1134 1134          } else if (ch == CacheHint.QUALITY) {
    1135 1135              return PGNode.CacheHint.DEFAULT;
    1136 1136          } else { // impossible
    1137 1137              return PGNode.CacheHint.DEFAULT;
    1138 1138          }
    1139 1139      }
    1140 1140
    1141 1141      public final void setCache(boolean value) {
    1142 1142          cacheProperty().set(value);
    1143 1143      }
    1144 1144
    1145 1145      public final boolean isCache() {
    1146 1146          return (miscProperties == null) ? DEFAULT_CACHE
    1147 1147                                          : miscProperties.isCache();
    1148 1148      }
    1149 1149
    1150 1150      /**
    1151 1151       * A performance hint to the system to indicate that this {@code Node}
    1152 1152       * should be cached as a bitmap. Rendering a bitmap representation of a node
    1153 1153       * will be faster than rendering primitives in many cases, especially in the
    1154 1154       * case of primitives with effects applied (such as a blur). However, it
    1155 1155       * also increases memory usage. This hint indicates whether that trade-off
    1156 1156       * (increased memory usage for increased performance) is worthwhile. Also
    1157 1157       * note that on some platforms such as GPU accelerated platforms there is
    1158 1158       * little benefit to caching Nodes as bitmaps when blurs and other effects
    1159 1159       * are used since they are very fast to render on the GPU.
    1160 1160       *
    1161 1161       * The {@link #cacheHintProperty} variable provides additional options for enabling
    1162 1162       * more aggressive bitmap caching.
    1163 1163       *
    1164 1164       * <p>
    1165 1165       * Caching may be disabled for any node that has a 3D transform on itself,
    1166 1166       * any of its ancestors, or any of its descendants.
    1167 1167       *
    1168 1168       * @see #cacheHintProperty
    1169 1169       * @defaultValue false
    1170 1170       */
    1171 1171      public final BooleanProperty cacheProperty() {
    1172 1172          return getMiscProperties().cacheProperty();
    1173 1173      }
    1174 1174
    1175 1175      public final void setCacheHint(CacheHint value) {
    1176 1176          cacheHintProperty().set(value);
    1177 1177      }
    1178 1178
    1179 1179      public final CacheHint getCacheHint() {
    1180 1180          return (miscProperties == null) ? DEFAULT_CACHE_HINT
    1181 1181                                          : miscProperties.getCacheHint();
    1182 1182      }
    1183 1183
    1184 1184      /**
    1185 1185       * Additional hint for controlling bitmap caching.
    1186 1186       * <p>
    1187 1187       * Under certain circumstances, such as animating nodes that are very
    1188 1188       * expensive to render, it is desirable to be able to perform
    1189 1189       * transformations on the node without having to regenerate the cached
    1190 1190       * bitmap.  An option in such cases is to perform the transforms on the
    1191 1191       * cached bitmap itself.
    1192 1192       * <p>
    1193 1193       * This technique can provide a dramatic improvement to animation
    1194 1194       * performance, though may also result in a reduction in visual quality.
    1195 1195       * The {@code cacheHint} variable provides a hint to the system about how
    1196 1196       * and when that trade-off (visual quality for animation performance) is
    1197 1197       * acceptable.
    1198 1198       * <p>
    1199 1199       * It is possible to enable the cacheHint only at times when your node is
    1200 1200       * animating.  In this way, expensive nodes can appear on screen with full
    1201 1201       * visual quality, yet still animate smoothly.
    1202 1202       * <p>
    1203 1203       * Example:
    1204 1204       * <pre><code>
    1205 1205          expensiveNode.setCache(true);
    1206 1206          expensiveNode.setCacheHint(CacheHint.QUALITY);
    1207 1207          ...
    1208 1208          // Do an animation
    1209 1209          expensiveNode.setCacheHint(CacheHint.SPEED);
    1210 1210          new Timeline(
    1211 1211              new KeyFrame(Duration.seconds(2),
    1212 1212                  new KeyValue(expensiveNode.scaleXProperty(), 2.0),
    1213 1213                  new KeyValue(expensiveNode.scaleYProperty(), 2.0),
    1214 1214                  new KeyValue(expensiveNode.rotateProperty(), 360),
    1215 1215                  new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
    1216 1216              )
    1217 1217          ).play();
    1218 1218       </code></pre>
    1219 1219       *
    1220 1220       * Note that {@code cacheHint} is only a hint to the system.  Depending on
    1221 1221       * the details of the node or the transform, this hint may be ignored.
    1222 1222       *
    1223 1223       * <p>
    1224 1224       * If {@code Node.cache} is false, cacheHint is ignored.
    1225 1225       * Caching may be disabled for any node that has a 3D transform on itself,
    1226 1226       * any of its ancestors, or any of its descendants.
    1227 1227       *
    1228 1228       * @see #cacheProperty
    1229 1229       * @since JavaFX 1.3
    1230 1230       * @defaultValue CacheHint.DEFAULT
    1231 1231       */
    1232 1232      public final ObjectProperty<CacheHint> cacheHintProperty() {
    1233 1233          return getMiscProperties().cacheHintProperty();
    1234 1234      }
    1235 1235
    1236 1236      public final void setEffect(Effect value) {
    1237 1237          effectProperty().set(value);
    1238 1238      }
    1239 1239
    1240 1240      public final Effect getEffect() {
    1241 1241          return (miscProperties == null) ? DEFAULT_EFFECT
    1242 1242                                          : miscProperties.getEffect();
    1243 1243      }
    1244 1244
    1245 1245      /**
    1246 1246       * Specifies an effect to apply to this {@code Node}.
    1247 1247       * <p>
    1248 1248       * Note that this is a conditional feature. See
    1249 1249       * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT}
    1250 1250       * for more information.
    1251 1251       *
    1252 1252       * <p>
    1253 1253       * There is a known limitation of mixing Effect with a 3D Transform. Effect is
    1254 1254       * essentially a 2D image operation. The result of an Effect set on
    1255 1255       * a {@link Group} node with 3D transformed children will cause its children
    1256 1256       * to be rendered in order without Z-buffering applied between those
    1257 1257       * children.
    1258 1258       *
    1259 1259       * @defaultValue null
    1260 1260       */
    1261 1261      public final ObjectProperty<Effect> effectProperty() {
    1262 1262          return getMiscProperties().effectProperty();
    1263 1263      }
    1264 1264
    1265 1265      public final void setDepthTest(DepthTest value) {
    1266 1266          depthTestProperty().set(value);
    1267 1267      }
    1268 1268
    1269 1269      public final DepthTest getDepthTest() {
    1270 1270          return (miscProperties == null) ? DEFAULT_DEPTH_TEST
    1271 1271                                          : miscProperties.getDepthTest();
    1272 1272      }
    1273 1273
    1274 1274      /**
    1275 1275       * Indicates whether depth testing is used when rendering this node.
    1276 1276       * If the depthTest flag is {@code DepthTest.DISABLE}, then depth testing
    1277 1277       * is disabled for this node.
    1278 1278       * If the depthTest flag is {@code DepthTest.ENABLE}, then depth testing
    1279 1279       * is enabled for this node.
    1280 1280       * If the depthTest flag is {@code DepthTest.INHERIT}, then depth testing
    1281 1281       * is enabled for this node if it is enabled for the parent node or the
    1282 1282       * parent node is null.
    1283 1283       * <p>
    1284 1284       * The depthTest flag is only used when the depthBuffer flag for
    1285 1285       * the {@link Scene} is true (meaning that the
    1286 1286       * {@link Scene} has an associated depth buffer)
    1287 1287       * <p>
    1288 1288       * Note that this is a conditional feature. See
    1289 1289       * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
    1290 1290       * for more information.
    1291 1291       *<p>
    1292 1292       * See the constructor in Scene with depthBuffer as one of its input
    1293 1293       * arguments.
    1294 1294       *
    1295 1295       * @see javafx.scene.Scene
    1296 1296       * @defaultValue INHERIT
    1297 1297       */
    1298 1298      public final ObjectProperty<DepthTest> depthTestProperty() {
    1299 1299          return getMiscProperties().depthTestProperty();
    1300 1300      }
    1301 1301
    1302 1302      /**
    1303 1303       * Recompute the derived depth test flag. This flag is true
    1304 1304       * if the depthTest flag for this node is true and the
    1305 1305       * depth test flag for each ancestor node is true. It is false
    1306 1306       * otherwise. Equivalently, the derived depth flag is true
    1307 1307       * if the depthTest flag for this node is true and the derivedDepthTest
    1308 1308       * flag for its parent is true.
    1309 1309       */
    1310 1310      void computeDerivedDepthTest() {
    1311 1311          boolean newDDT;
    1312 1312          if (getDepthTest() == DepthTest.INHERIT) {
    1313 1313              if (getParent() != null) {
    1314 1314                  newDDT = getParent().isDerivedDepthTest();
    1315 1315              } else {
    1316 1316                  newDDT = true;
    1317 1317              }
    1318 1318          } else if (getDepthTest() == DepthTest.ENABLE) {
    1319 1319              newDDT = true;
    1320 1320          } else {
    1321 1321              newDDT = false;
    1322 1322          }
    1323 1323
    1324 1324          if (isDerivedDepthTest() != newDDT) {
    1325 1325              impl_markDirty(DirtyBits.NODE_DEPTH_TEST);
    1326 1326              setDerivedDepthTest(newDDT);
    1327 1327          }
    1328 1328      }
    1329 1329
    1330 1330      // This is the derived depthTest value to pass to PG level
    1331 1331      private boolean derivedDepthTest = true;
    1332 1332
    1333 1333      void setDerivedDepthTest(boolean value) {
    1334 1334          derivedDepthTest = value;
    1335 1335      }
    1336 1336
    1337 1337      boolean isDerivedDepthTest() {
    1338 1338          return derivedDepthTest;
    1339 1339      }
    1340 1340
    1341 1341      public final void setDisable(boolean value) {
    1342 1342          disableProperty().set(value);
    1343 1343      }
    1344 1344
    1345 1345      public final boolean isDisable() {
    1346 1346          return (miscProperties == null) ? DEFAULT_DISABLE
    1347 1347                                          : miscProperties.isDisable();
    1348 1348      }
    1349 1349
    1350 1350      /**
    1351 1351       * Sets the individual disabled state of this {@code Node}. Setting
    1352 1352       * {@code disable} to true will cause this {@code Node} and any subnodes to
    1353 1353       * become disabled. This variable should be used only to set the disabled
    1354 1354       * state of a {@code Node}.  For querying the disabled state of a
    1355 1355       * {@code Node}, the {@link #disabledProperty disabled} variable should instead be used,
    1356 1356       * since it is possible that a {@code Node} was disabled as a result of an
    1357 1357       * ancestor being disabled even if the individual {@code disable} state on
    1358 1358       * this {@code Node} is {@code false}.
    1359 1359       *
    1360 1360       * @defaultValue false
    1361 1361       */
    1362 1362      public final BooleanProperty disableProperty() {
    1363 1363          return getMiscProperties().disableProperty();
    1364 1364      }
    1365 1365
    1366 1366      /**************************************************************************
    1367 1367       *                                                                        *
    1368 1368       *
    1369 1369       *                                                                        *
    1370 1370       *************************************************************************/
    1371 1371      /**
    1372 1372       * Defines how the picking computation is done for this node when
    1373 1373       * triggered by a {@code MouseEvent} or a {@code contains} function call.
    1374 1374       *
    1375 1375       * If {@code pickOnBounds} is true, then picking is computed by
    1376 1376       * intersecting with the bounds of this node, else picking is computed
    1377 1377       * by intersecting with the geometric shape of this node.
    1378 1378       *
    1379 1379       * @defaultValue false
    1380 1380       * @since JavaFX 1.3
    1381 1381       */
    1382 1382      private BooleanProperty pickOnBounds;
    1383 1383
    1384 1384      public final void setPickOnBounds(boolean value) {
    1385 1385          pickOnBoundsProperty().set(value);
    1386 1386      }
    1387 1387
    1388 1388      public final boolean isPickOnBounds() {
    1389 1389          return pickOnBounds == null ? false : pickOnBounds.get();
    1390 1390      }
    1391 1391
    1392 1392      public final BooleanProperty pickOnBoundsProperty() {
    1393 1393          if (pickOnBounds == null) {
    1394 1394              pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");
    1395 1395          }
    1396 1396          return pickOnBounds;
    1397 1397      }
    1398 1398
    1399 1399      /**
    1400 1400       * Indicates whether or not this {@code Node} is disabled.  A {@code Node}
    1401 1401       * will become disabled if {@link #disableProperty disable} is set to {@code true} on either
    1402 1402       * itself or one of its ancestors in the scene graph.
    1403 1403       * <p>
    1404 1404       * A disabled {@code Node} should render itself differently to indicate its
    1405 1405       * disabled state to the user.
    1406 1406       * Such disabled rendering is dependent on the implementation of the
    1407 1407       * {@code Node}. The shape classes contained in {@code javafx.scene.shape}
    1408 1408       * do not implement such rendering by default, therefore applications using
    1409 1409       * shapes for handling input must implement appropriate disabled rendering
    1410 1410       * themselves. The user-interface controls defined in
    1411 1411       * {@code javafx.scene.control} will implement disabled-sensitive rendering,
    1412 1412       * however.
    1413 1413       * <p>
    1414 1414       * A disabled {@code Node} does not receive mouse or key events.
    1415 1415       *
    1416 1416       * @defaultValue false
    1417 1417       */
    1418 1418      private ReadOnlyBooleanWrapper disabled;
    1419 1419
    1420 1420      protected final void setDisabled(boolean value) {
    1421 1421          disabledPropertyImpl().set(value);
    1422 1422      }
    1423 1423
    1424 1424      public final boolean isDisabled() {
    1425 1425          return disabled == null ? false : disabled.get();
    1426 1426      }
    1427 1427
    1428 1428      public final ReadOnlyBooleanProperty disabledProperty() {
    1429 1429          return disabledPropertyImpl().getReadOnlyProperty();
    1430 1430      }
    1431 1431
    1432 1432      private ReadOnlyBooleanWrapper disabledPropertyImpl() {
    1433 1433          if (disabled == null) {
    1434 1434              disabled = new ReadOnlyBooleanWrapper() {
    1435 1435
    1436 1436                  @Override
    1437 1437                  protected void invalidated() {
    1438 1438                      impl_pseudoClassStateChanged("disabled");
    1439 1439                      updateCanReceiveFocus();
    1440 1440                      focusSetDirty(getScene());
    1441 1441                  }
    1442 1442
    1443 1443                  @Override
    1444 1444                  public Object getBean() {
    1445 1445                      return Node.this;
    1446 1446                  }
    1447 1447
    1448 1448                  @Override
    1449 1449                  public String getName() {
    1450 1450                      return "disabled";
    1451 1451                  }
    1452 1452              };
    1453 1453          }
    1454 1454          return disabled;
    1455 1455      }
    1456 1456
    1457 1457      private void updateDisabled() {
    1458 1458          setDisabled(isDisable() || (getParent() != null && getParent().isDisabled()));
    1459 1459      }
    1460 1460
    1461 1461      /**
    1462 1462       * Finds this {@code Node}, or the first sub-node, based on the given CSS selector.
    1463 1463       * If this node is a {@code Parent}, then this function will traverse down
    1464 1464       * into the branch until it finds a match. If more than one sub-node matches the
    1465 1465       * specified selector, this function returns the first of them.
    1466 1466       * <p>
    1467 1467       *     For example, if a Node is given the id of "myId", then the lookup method can
    1468 1468       *     be used to find this node as follows: <code>scene.lookup("#myId");</code>.
    1469 1469       * </p>
    1470 1470       *
    1471 1471       * @param selector The css selector of the node to find
    1472 1472       * @return The first node, starting from this {@code Node}, which matches
    1473 1473       *         the CSS {@code selector}, null if none is found.
    1474 1474       */
    1475 1475      public Node lookup(String selector) {
    1476 1476          if (selector == null) return null;
    1477 1477          Selector s = Selector.createSelector(selector);
    1478 1478          return s != null && s.applies(this) ? this : null;
    1479 1479      }
    1480 1480
    1481 1481      /**
    1482 1482       * Finds all {@code Node}s, including this one and any children, which match
    1483 1483       * the given CSS selector. If no matches are found, an empty unmodifiable set is
    1484 1484       * returned. The set is explicitly unordered.
    1485 1485       *
    1486 1486       * @param selector The css selector of the nodes to find
    1487 1487       * @return All nodes, starting from and including this {@code Node}, which match
    1488 1488       *         the CSS {@code selector}. The returned set is always unordered and
    1489 1489       *         unmodifiable, and never null.
    1490 1490       */
    1491 1491      public Set<Node> lookupAll(String selector) {
    1492 1492          final Selector s = Selector.createSelector(selector);
    1493 1493          final Set<Node> empty = Collections.emptySet();
    1494 1494          if (s == null) return empty;
    1495 1495          List<Node> results = lookupAll(s, null);
    1496 1496          return results == null ? empty : new UnmodifiableListSet<Node>(results);
    1497 1497      }
    1498 1498
    1499 1499      /**
    1500 1500       * Used by Node and Parent for traversing the tree and adding all nodes which
    1501 1501       * match the given selector.
    1502 1502       *
    1503 1503       * @param selector The Selector. This will never be null.
    1504 1504       * @param results The results. This will never be null.
    1505 1505       */
    1506 1506      List<Node> lookupAll(Selector selector, List<Node> results) {
    1507 1507          if (selector.applies(this)) {
    1508 1508              // Lazily create the set to reduce some trash.
    1509 1509              if (results == null) {
    1510 1510                  results = new LinkedList<Node>();
    1511 1511              }
    1512 1512              results.add(this);
    1513 1513          }
    1514 1514          return results;
    1515 1515      }
    1516 1516
    1517 1517      /**
    1518 1518       * Moves this {@code Node} to the back of its sibling nodes in terms of
    1519 1519       * z-order.  This is accomplished by moving this {@code Node} to the
    1520 1520       * first position in its parent's {@code content} ObservableList.
    1521 1521       * This function has no effect if this {@code Node} is not part of a group.
    1522 1522       */
    1523 1523      public void toBack() {
    1524 1524          if (getParent() != null) {
    1525 1525              getParent().impl_toBack(this);
    1526 1526          }
    1527 1527      }
    1528 1528
    1529 1529      /**
    1530 1530       * Moves this {@code Node} to the front of its sibling nodes in terms of
    1531 1531       * z-order.  This is accomplished by moving this {@code Node} to the
    1532 1532       * last position in its parent's {@code content} ObservableList.
    1533 1533       * This function has no effect if this {@code Node} is not part of a group.
    1534 1534       */
    1535 1535      public void toFront() {
    1536 1536          if (getParent() != null) {
    1537 1537              getParent().impl_toFront(this);
    1538 1538          }
    1539 1539      }
    1540 1540
    1541 1541      // TODO: need to verify whether this is OK to do starting from a node in
    1542 1542      // the scene graph other than the root.
    1543 1543      private void doCSSPass() {

↓ open down ↓ | 1543 lines elided | ↑ open up ↑

    1544 1544          if (this.cssFlag != CSSFlags.CLEAN) {
    1545 1545              // The dirty bit isn't checked but we must ensure it is cleared.
    1546 1546              // The cssFlag is set to clean in either Node.processCSS or
    1547 1547              // Node.impl_processCSS(boolean)
    1548 1548
    1549 1549              // Don't clear the dirty bit in case it will cause problems
    1550 1550              // with a full CSS pass on the scene.
    1551 1551              // TODO: is this the right thing to do?
    1552 1552              // this.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
    1553 1553
    1554      -            this.processCSS();
         1554 +            this.processCSS(getScene().styleManager);
    1555 1555          }
    1556 1556      }
    1557 1557
    1558 1558      /**
    1559 1559       * Recursive function for synchronizing a node and all descendents
    1560 1560       */
    1561 1561      private static void syncAll(Node node) {
    1562 1562          node.impl_syncPGNode();
    1563 1563          if (node instanceof Parent) {
    1564 1564              Parent p = (Parent) node;

    1565 1565              final int childrenCount = p.getChildren().size();
    1566 1566
    1567 1567              for (int i = 0; i < childrenCount; i++) {
    1568 1568                  Node n = p.getChildren().get(i);
    1569 1569                  if (n != null) {
    1570 1570                      syncAll(n);
    1571 1571                  }
    1572 1572              }
    1573 1573          }
    1574 1574          if (node.getClip() != null) {
    1575 1575              syncAll(node.getClip());
    1576 1576          }
    1577 1577      }
    1578 1578
    1579 1579      private void doLayoutPass() {
    1580 1580          if (this instanceof Parent) {
    1581 1581              // TODO: As an optimization we only need to layout those dirty
    1582 1582              // roots that are descendents of this node
    1583 1583              Parent p = (Parent)this;
    1584 1584              for (int i = 0; i < 3; i++) {
    1585 1585                  p.layout();
    1586 1586              }
    1587 1587          }
    1588 1588      }
    1589 1589
    1590 1590      private void doCSSLayoutSyncForSnapshot() {
    1591 1591          doCSSPass();
    1592 1592          doLayoutPass();
    1593 1593          Scene.impl_setAllowPGAccess(true);
    1594 1594          syncAll(this);
    1595 1595          Scene.impl_setAllowPGAccess(false);
    1596 1596      }
    1597 1597
    1598 1598      private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) {
    1599 1599          if (getScene() != null) {
    1600 1600              getScene().doCSSLayoutSyncForSnapshot(this);
    1601 1601          } else {
    1602 1602              doCSSLayoutSyncForSnapshot();
    1603 1603          }
    1604 1604
    1605 1605          BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
    1606 1606          if (params.getTransform() != null) {
    1607 1607              Affine3D tempTx = new Affine3D();
    1608 1608              params.getTransform().impl_apply(tempTx);
    1609 1609              transform = tempTx;
    1610 1610          }
    1611 1611          double x;
    1612 1612          double y;
    1613 1613          double w;
    1614 1614          double h;
    1615 1615          Rectangle2D viewport = params.getViewport();
    1616 1616          if (viewport != null) {
    1617 1617              // Use the specified viewport
    1618 1618              x = viewport.getMinX();
    1619 1619              y = viewport.getMinY();
    1620 1620              w = viewport.getWidth();
    1621 1621              h = viewport.getHeight();
    1622 1622          } else {
    1623 1623              // Get the bounds in parent of this node, transformed by the
    1624 1624              // specified transform.
    1625 1625              BaseBounds tempBounds = TempState.getInstance().bounds;
    1626 1626              tempBounds = getTransformedBounds(tempBounds, transform);
    1627 1627              x = tempBounds.getMinX();
    1628 1628              y = tempBounds.getMinY();
    1629 1629              w = tempBounds.getWidth();
    1630 1630              h = tempBounds.getHeight();
    1631 1631          }
    1632 1632          WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h,
    1633 1633                  this, transform, params.isDepthBuffer(),
    1634 1634                  params.getFill(), params.getCamera(), img);
    1635 1635
    1636 1636          return result;
    1637 1637      }
    1638 1638
    1639 1639      /**
    1640 1640       * Takes a snapshot of this node and returns the rendered image when
    1641 1641       * it is ready.
    1642 1642       * CSS and layout processing will be done for the node, and any of its
    1643 1643       * children, prior to rendering it.
    1644 1644       * The entire destination image is cleared to the fill {@code Paint}
    1645 1645       * specified by the SnapshotParameters. This node is then rendered to
    1646 1646       * the image.
    1647 1647       * If the viewport specified by the SnapshotParameters is null, the
    1648 1648       * upper-left pixel of the {@code boundsInParent} of this
    1649 1649       * node, after first applying the transform specified by the
    1650 1650       * SnapshotParameters,
    1651 1651       * is mapped to the upper-left pixel (0,0) in the image.
    1652 1652       * If a non-null viewport is specified,
    1653 1653       * the upper-left pixel of the viewport is mapped to upper-left pixel
    1654 1654       * (0,0) in the image.
    1655 1655       * In both cases, this mapping to (0,0) of the image is done with an integer
    1656 1656       * translation. The portion of the node that is outside of the rendered
    1657 1657       * image will be clipped by the image.
    1658 1658       *
    1659 1659       * <p>
    1660 1660       * When taking a snapshot of a scene that is being animated, either
    1661 1661       * explicitly by the application or implicitly (such as chart animation),
    1662 1662       * the snapshot will be rendered based on the state of the scene graph at
    1663 1663       * the moment the snapshot is taken and will not reflect any subsequent
    1664 1664       * animation changes.
    1665 1665       * </p>
    1666 1666       *
    1667 1667       * <p>
    1668 1668       * NOTE: In order for CSS and layout to function correctly, the node
    1669 1669       * must be part of a Scene (the Scene may be attached to a Stage, but need
    1670 1670       * not be).
    1671 1671       * </p>
    1672 1672       *
    1673 1673       * @param params the snapshot parameters containing attributes that
    1674 1674       * will control the rendering. If the SnapshotParameters object is null,
    1675 1675       * then the Scene's attributes will be used if this node is part of a scene,
    1676 1676       * or default attributes will be used if this node is not part of a scene.
    1677 1677       *
    1678 1678       * @param image the writable image that will be used to hold the rendered node.
    1679 1679       * It may be null in which case a new WritableImage will be constructed.
    1680 1680       * The new image is constructed using integer width and
    1681 1681       * height values that are derived either from the transformed bounds of this
    1682 1682       * Node or from the size of the viewport as specified in the
    1683 1683       * SnapShotParameters. These integer values are chosen such that the image
    1684 1684       * will wholly contain the bounds of this Node or the specified viewport.
    1685 1685       * If the image is non-null, the node will be rendered into the
    1686 1686       * existing image.
    1687 1687       * In this case, the width and height of the image determine the area
    1688 1688       * that is rendered instead of the width and height of the bounds or
    1689 1689       * viewport.
    1690 1690       *
    1691 1691       * @throws IllegalStateException if this method is called on a thread
    1692 1692       *     other than the JavaFX Application Thread.
    1693 1693       *
    1694 1694       * @return the rendered image
    1695 1695       * @since 2.2
    1696 1696       */
    1697 1697      public WritableImage snapshot(SnapshotParameters params, WritableImage image) {
    1698 1698          Toolkit.getToolkit().checkFxUserThread();
    1699 1699
    1700 1700          if (params == null) {
    1701 1701              params = new SnapshotParameters();
    1702 1702              Scene s = getScene();
    1703 1703              if (s != null) {
    1704 1704                  params.setCamera(s.getCamera());
    1705 1705                  params.setDepthBuffer(s.isDepthBuffer());
    1706 1706                  params.setFill(s.getFill());
    1707 1707              }
    1708 1708          }
    1709 1709
    1710 1710          return doSnapshot(params, image);
    1711 1711      }
    1712 1712
    1713 1713      /**
    1714 1714       * Takes a snapshot of this node at the next frame and calls the
    1715 1715       * specified callback method when the image is ready.
    1716 1716       * CSS and layout processing will be done for the node, and any of its
    1717 1717       * children, prior to rendering it.
    1718 1718       * The entire destination image is cleared to the fill {@code Paint}
    1719 1719       * specified by the SnapshotParameters. This node is then rendered to
    1720 1720       * the image.
    1721 1721       * If the viewport specified by the SnapshotParameters is null, the
    1722 1722       * upper-left pixel of the {@code boundsInParent} of this
    1723 1723       * node, after first applying the transform specified by the
    1724 1724       * SnapshotParameters,
    1725 1725       * is mapped to the upper-left pixel (0,0) in the image.
    1726 1726       * If a non-null viewport is specified,
    1727 1727       * the upper-left pixel of the viewport is mapped to upper-left pixel
    1728 1728       * (0,0) in the image.
    1729 1729       * In both cases, this mapping to (0,0) of the image is done with an integer
    1730 1730       * translation. The portion of the node that is outside of the rendered
    1731 1731       * image will be clipped by the image.
    1732 1732       *
    1733 1733       * <p>
    1734 1734       * This is an asynchronous call, which means that other
    1735 1735       * events or animation might be processed before the node is rendered.
    1736 1736       * If any such events modify the node, or any of its children, that
    1737 1737       * modification will be reflected in the rendered image (just like it
    1738 1738       * will also be reflected in the frame rendered to the Stage, if this node
    1739 1739       * is part of a live scene graph).
    1740 1740       * </p>
    1741 1741       *
    1742 1742       * <p>
    1743 1743       * When taking a snapshot of a node that is being animated, either
    1744 1744       * explicitly by the application or implicitly (such as chart animation),
    1745 1745       * the snapshot will be rendered based on the state of the scene graph at
    1746 1746       * the moment the snapshot is taken and will not reflect any subsequent
    1747 1747       * animation changes.
    1748 1748       * </p>
    1749 1749       *
    1750 1750       * <p>
    1751 1751       * NOTE: In order for CSS and layout to function correctly, the node
    1752 1752       * must be part of a Scene (the Scene may be attached to a Stage, but need
    1753 1753       * not be).
    1754 1754       * </p>
    1755 1755       *
    1756 1756       * @param callback a class whose call method will be called when the image
    1757 1757       * is ready. The SnapshotResult that is passed into the call method of
    1758 1758       * the callback will contain the rendered image, the source node
    1759 1759       * that was rendered, and a copy of the SnapshotParameters.
    1760 1760       * The callback parameter must not be null.
    1761 1761       *
    1762 1762       * @param params the snapshot parameters containing attributes that
    1763 1763       * will control the rendering. If the SnapshotParameters object is null,
    1764 1764       * then the Scene's attributes will be used if this node is part of a scene,
    1765 1765       * or default attributes will be used if this node is not part of a scene.
    1766 1766       *
    1767 1767       * @param image the writable image that will be used to hold the rendered node.
    1768 1768       * It may be null in which case a new WritableImage will be constructed.
    1769 1769       * The new image is constructed using integer width and
    1770 1770       * height values that are derived either from the transformed bounds of this
    1771 1771       * Node or from the size of the viewport as specified in the
    1772 1772       * SnapShotParameters. These integer values are chosen such that the image
    1773 1773       * will wholly contain the bounds of this Node or the specified viewport.
    1774 1774       * If the image is non-null, the node will be rendered into the
    1775 1775       * existing image.
    1776 1776       * In this case, the width and height of the image determine the area
    1777 1777       * that is rendered instead of the width and height of the bounds or
    1778 1778       * viewport.
    1779 1779       *
    1780 1780       * @throws IllegalStateException if this method is called on a thread
    1781 1781       *     other than the JavaFX Application Thread.
    1782 1782       *
    1783 1783       * @throws NullPointerException if the callback parameter is null.
    1784 1784       * @since 2.2
    1785 1785       */
    1786 1786      public void snapshot(Callback<SnapshotResult, Void> callback,
    1787 1787              SnapshotParameters params, WritableImage image) {
    1788 1788
    1789 1789          Toolkit.getToolkit().checkFxUserThread();
    1790 1790          if (callback == null) {
    1791 1791              throw new NullPointerException("The callback must not be null");
    1792 1792          }
    1793 1793
    1794 1794          if (params == null) {
    1795 1795              params = new SnapshotParameters();
    1796 1796              Scene s = getScene();
    1797 1797              if (s != null) {
    1798 1798                  params.setCamera(s.getCamera());
    1799 1799                  params.setDepthBuffer(s.isDepthBuffer());
    1800 1800                  params.setFill(s.getFill());
    1801 1801              }
    1802 1802          } else {
    1803 1803              params = params.copy();
    1804 1804          }
    1805 1805
    1806 1806          final SnapshotParameters theParams = params;
    1807 1807          final Callback<SnapshotResult, Void> theCallback = callback;
    1808 1808          final WritableImage theImage = image;
    1809 1809
    1810 1810          // Create a deferred runnable that will be run from a pulse listener
    1811 1811          // that is called after all of the scenes have been synced but before
    1812 1812          // any of them have been rendered.
    1813 1813          final Runnable snapshotRunnable = new Runnable() {
    1814 1814              @Override public void run() {
    1815 1815                  WritableImage img = doSnapshot(theParams, theImage);
    1816 1816                  SnapshotResult result = new SnapshotResult(img, Node.this, theParams);
    1817 1817  //                System.err.println("Calling snapshot callback");
    1818 1818                  try {
    1819 1819                      Void v = theCallback.call(result);
    1820 1820                  } catch (Throwable th) {
    1821 1821                      System.err.println("Exception in snapshot callback");
    1822 1822                      th.printStackTrace(System.err);
    1823 1823                  }
    1824 1824              }
    1825 1825          };
    1826 1826
    1827 1827  //        System.err.println("Schedule a snapshot in the future");
    1828 1828          Scene.addSnapshotRunnable(snapshotRunnable);
    1829 1829      }
    1830 1830
    1831 1831      /* ************************************************************************
    1832 1832       *                                                                        *
    1833 1833       *
    1834 1834       *                                                                        *
    1835 1835       *************************************************************************/
    1836 1836
    1837 1837      public final void setOnDragEntered(
    1838 1838              EventHandler<? super DragEvent> value) {
    1839 1839          onDragEnteredProperty().set(value);
    1840 1840      }
    1841 1841
    1842 1842      public final EventHandler<? super DragEvent> getOnDragEntered() {
    1843 1843          return (eventHandlerProperties == null)
    1844 1844                  ? null : eventHandlerProperties.getOnDragEntered();
    1845 1845      }
    1846 1846
    1847 1847      /**
    1848 1848       * Defines a function to be called when drag gesture
    1849 1849       * enters this {@code Node}.
    1850 1850       */
    1851 1851      public final ObjectProperty<EventHandler<? super DragEvent>>
    1852 1852              onDragEnteredProperty() {
    1853 1853          return getEventHandlerProperties().onDragEnteredProperty();
    1854 1854      }
    1855 1855
    1856 1856      public final void setOnDragExited(
    1857 1857              EventHandler<? super DragEvent> value) {
    1858 1858          onDragExitedProperty().set(value);
    1859 1859      }
    1860 1860
    1861 1861      public final EventHandler<? super DragEvent> getOnDragExited() {
    1862 1862          return (eventHandlerProperties == null)
    1863 1863                  ? null : eventHandlerProperties.getOnDragExited();
    1864 1864      }
    1865 1865
    1866 1866      /**
    1867 1867       * Defines a function to be called when drag gesture
    1868 1868       * exits this {@code Node}.
    1869 1869       */
    1870 1870      public final ObjectProperty<EventHandler<? super DragEvent>>
    1871 1871              onDragExitedProperty() {
    1872 1872          return getEventHandlerProperties().onDragExitedProperty();
    1873 1873      }
    1874 1874
    1875 1875      public final void setOnDragOver(
    1876 1876              EventHandler<? super DragEvent> value) {
    1877 1877          onDragOverProperty().set(value);
    1878 1878      }
    1879 1879
    1880 1880      public final EventHandler<? super DragEvent> getOnDragOver() {
    1881 1881          return (eventHandlerProperties == null)
    1882 1882                  ? null : eventHandlerProperties.getOnDragOver();
    1883 1883      }
    1884 1884
    1885 1885      /**
    1886 1886       * Defines a function to be called when drag gesture progresses within
    1887 1887       * this {@code Node}.
    1888 1888       */
    1889 1889      public final ObjectProperty<EventHandler<? super DragEvent>>
    1890 1890              onDragOverProperty() {
    1891 1891          return getEventHandlerProperties().onDragOverProperty();
    1892 1892      }
    1893 1893
    1894 1894      // Do we want DRAG_TRANSFER_MODE_CHANGED event?
    1895 1895  //    public final void setOnDragTransferModeChanged(
    1896 1896  //            EventHandler<? super DragEvent> value) {
    1897 1897  //        onDragTransferModeChangedProperty().set(value);
    1898 1898  //    }
    1899 1899  //
    1900 1900  //    public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() {
    1901 1901  //        return (eventHandlerProperties == null)
    1902 1902  //                ? null : eventHandlerProperties.getOnDragTransferModeChanged();
    1903 1903  //    }
    1904 1904  //
    1905 1905  //    /**
    1906 1906  //     * Defines a function to be called this {@code Node} if it is a potential
    1907 1907  //     * drag-and-drop target when the user takes action to change the intended
    1908 1908  //     * {@code TransferMode}.
    1909 1909  //     * The user can change the intended {@link TransferMode} by holding down
    1910 1910  //     * or releasing key modifiers.
    1911 1911  //     */
    1912 1912  //    public final ObjectProperty<EventHandler<? super DragEvent>>
    1913 1913  //            onDragTransferModeChangedProperty() {
    1914 1914  //        return getEventHandlerProperties().onDragTransferModeChangedProperty();
    1915 1915  //    }
    1916 1916
    1917 1917      public final void setOnDragDropped(
    1918 1918              EventHandler<? super DragEvent> value) {
    1919 1919          onDragDroppedProperty().set(value);
    1920 1920      }
    1921 1921
    1922 1922      public final EventHandler<? super DragEvent> getOnDragDropped() {
    1923 1923          return (eventHandlerProperties == null)
    1924 1924                  ? null : eventHandlerProperties.getOnDragDropped();
    1925 1925      }
    1926 1926
    1927 1927      /**
    1928 1928       * Defines a function to be called when the mouse button is released
    1929 1929       * on this {@code Node} during drag and drop gesture. Transfer of data from
    1930 1930       * the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should
    1931 1931       * happen in this function.
    1932 1932       */
    1933 1933      public final ObjectProperty<EventHandler<? super DragEvent>>
    1934 1934              onDragDroppedProperty() {
    1935 1935          return getEventHandlerProperties().onDragDroppedProperty();
    1936 1936      }
    1937 1937
    1938 1938      public final void setOnDragDone(
    1939 1939              EventHandler<? super DragEvent> value) {
    1940 1940          onDragDoneProperty().set(value);
    1941 1941      }
    1942 1942
    1943 1943      public final EventHandler<? super DragEvent> getOnDragDone() {
    1944 1944          return (eventHandlerProperties == null)
    1945 1945                  ? null : eventHandlerProperties.getOnDragDone();
    1946 1946      }
    1947 1947
    1948 1948      /**
    1949 1949       * Defines a function to be called when this {@code Node} is a
    1950 1950       * drag and drop gesture source after its data has
    1951 1951       * been dropped on a drop target. The {@code transferMode} of the
    1952 1952       * event shows what just happened at the drop target.
    1953 1953       * If {@code transferMode} has the value {@code MOVE}, then the source can
    1954 1954       * clear out its data. Clearing the source's data gives the appropriate
    1955 1955       * appearance to a user that the data has been moved by the drag and drop
    1956 1956       * gesture. A {@code transferMode} that has the value {@code NONE}
    1957 1957       * indicates that no data was transferred during the drag and drop gesture.
    1958 1958       */
    1959 1959      public final ObjectProperty<EventHandler<? super DragEvent>>
    1960 1960              onDragDoneProperty() {
    1961 1961          return getEventHandlerProperties().onDragDoneProperty();
    1962 1962      }
    1963 1963
    1964 1964      /**
    1965 1965       * Confirms a potential drag and drop gesture that is recognized over this
    1966 1966       * {@code Node}.
    1967 1967       * Can be called only from a DRAG_DETECTED event handler. The returned
    1968 1968       * {@link Dragboard} is used to transfer data during
    1969 1969       * the drag and drop gesture. Placing this {@code Node}'s data on the
    1970 1970       * {@link Dragboard} also identifies this {@code Node} as the source of
    1971 1971       * the drag and drop gesture.
    1972 1972       * More detail about drag and drop gestures is described in the overivew
    1973 1973       * of {@link DragEvent}.
    1974 1974       *
    1975 1975       * @see DragEvent
    1976 1976       * @param transferModes The supported {@code TransferMode}(s) of this {@code Node}
    1977 1977       * @return A {@code Dragboard} to place this {@code Node}'s data on
    1978 1978       * @throws IllegalStateException if drag and drop cannot be started at this
    1979 1979       * moment (it's called outside of {@code DRAG_DETECTED} event handling or
    1980 1980       * this node is not in scene).
    1981 1981       */
    1982 1982      public Dragboard startDragAndDrop(TransferMode... transferModes) {
    1983 1983          if (getScene() != null) {
    1984 1984              return getScene().startDragAndDrop(this, transferModes);
    1985 1985          }
    1986 1986
    1987 1987          throw new IllegalStateException("Cannot start drag and drop on node "
    1988 1988                  + "that is not in scene");
    1989 1989      }
    1990 1990
    1991 1991      /**
    1992 1992       * Starts a full press-drag-release gesture with this node as gesture
    1993 1993       * source. This method can be called only from a {@code DRAG_DETECTED} mouse
    1994 1994       * event handler. More detail about dragging gestures can be found
    1995 1995       * in the overview of {@link MouseEvent} and {@link MouseDragEvent}.
    1996 1996       *
    1997 1997       * @see MouseEvent
    1998 1998       * @see MouseDragEvent
    1999 1999       * @throws IllegalStateException if the full press-drag-release gesture
    2000 2000       * cannot be started at this moment (it's called outside of
    2001 2001       * {@code DRAG_DETECTED} event handling or this node is not in scene).
    2002 2002       */
    2003 2003      public void startFullDrag() {
    2004 2004          if (getScene() != null) {
    2005 2005              getScene().startFullDrag(this);
    2006 2006              return;
    2007 2007          }
    2008 2008
    2009 2009          throw new IllegalStateException("Cannot start full drag on node "
    2010 2010                  + "that is not in scene");
    2011 2011      }
    2012 2012
    2013 2013      ////////////////////////////
    2014 2014      //  Private Implementation
    2015 2015      ////////////////////////////
    2016 2016
    2017 2017      /**
    2018 2018       * If this Node is being used as the clip of another Node, that other node
    2019 2019       * is referred to as the clipParent. If the boundsInParent of this Node
    2020 2020       * changes, it must update the clipParent's bounds as well.
    2021 2021       */
    2022 2022      private Node clipParent;
    2023 2023      // Use a getter function instead of giving clipParent package access,
    2024 2024      // so that clipParent doesn't get turned into a Location.
    2025 2025      final Node getClipParent() {
    2026 2026          return clipParent;
    2027 2027      }
    2028 2028
    2029 2029      /**
    2030 2030       * Determines whether this node is connected anywhere in the scene graph.
    2031 2031       */
    2032 2032      boolean isConnected() {
    2033 2033          // don't need to check scene, because if scene is non-null
    2034 2034          // parent must also be non-null
    2035 2035          return getParent() != null || clipParent != null;
    2036 2036      }
    2037 2037
    2038 2038      /**
    2039 2039       * Tests whether creating a parent-child relationship between these
    2040 2040       * nodes would cause a cycle. The parent relationship includes not only
    2041 2041       * the "real" parent (child of Group) but also the clipParent.
    2042 2042       */
    2043 2043      boolean wouldCreateCycle(Node parent, Node child) {
    2044 2044          if (child != null && child.getClip() == null && (!(child instanceof Parent))) {
    2045 2045              return false;
    2046 2046      }
    2047 2047
    2048 2048          Node n = parent;
    2049 2049          while (n != child) {
    2050 2050              if (n.getParent() != null) {
    2051 2051                  n = n.getParent();
    2052 2052              } else if (n.clipParent != null) {
    2053 2053                  n = n.clipParent;
    2054 2054              } else {
    2055 2055                  return false;
    2056 2056      }
    2057 2057          }
    2058 2058          return true;
    2059 2059      }
    2060 2060
    2061 2061      /**
    2062 2062       * The peer node created by the graphics Toolkit/Pipeline implementation
    2063 2063       */
    2064 2064      private PGNode peer;
    2065 2065
    2066 2066      /**
    2067 2067       * @treatAsPrivate implementation detail
    2068 2068       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    2069 2069       */
    2070 2070      @Deprecated
    2071 2071      @SuppressWarnings("CallToPrintStackTrace")
    2072 2072      public PGNode impl_getPGNode() {
    2073 2073          if (Utils.assertionEnabled()) {
    2074 2074              // Assertion checking code
    2075 2075              if (!Scene.isPGAccessAllowed()) {
    2076 2076                  java.lang.System.err.println();
    2077 2077                  java.lang.System.err.println("*** unexpected PG access");
    2078 2078                  java.lang.Thread.dumpStack();
    2079 2079              }
    2080 2080          }
    2081 2081
    2082 2082          if (peer == null) {
    2083 2083              //if (PerformanceTracker.isLoggingEnabled()) {
    2084 2084              //    PerformanceTracker.logEvent("Creating PGNode for [{this}, id=\"{id}\"]");
    2085 2085              //}
    2086 2086              peer = impl_createPGNode();
    2087 2087              //if (PerformanceTracker.isLoggingEnabled()) {
    2088 2088              //    PerformanceTracker.logEvent("PGNode created");
    2089 2089              //}
    2090 2090          }
    2091 2091          return peer;
    2092 2092      }
    2093 2093
    2094 2094      /**
    2095 2095       * @treatAsPrivate implementation detail
    2096 2096       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    2097 2097       */
    2098 2098      @Deprecated
    2099 2099      protected abstract PGNode impl_createPGNode();
    2100 2100
    2101 2101      /***************************************************************************
    2102 2102       *                                                                         *
    2103 2103       *                              Initialization                             *
    2104 2104       *                                                                         *
    2105 2105       *  To Note limit the number of bounds computations and improve startup    *
    2106 2106       *  performance.                                                           *
    2107 2107       *                                                                         *
    2108 2108       **************************************************************************/
    2109 2109
    2110 2110      /**
    2111 2111       * Creates a new instance of Node.
    2112 2112       */
    2113 2113      protected Node() {
    2114 2114          //if (PerformanceTracker.isLoggingEnabled()) {
    2115 2115          //    PerformanceTracker.logEvent("Node.init for [{this}, id=\"{id}\"]");
    2116 2116          //}
    2117 2117
    2118 2118          setDirty();
    2119 2119          updateTreeVisible();
    2120 2120          //if (PerformanceTracker.isLoggingEnabled()) {
    2121 2121          //    PerformanceTracker.logEvent("Node.postinit " +
    2122 2122          //                                "for [{this}, id=\"{id}\"] finished");
    2123 2123          //}
    2124 2124      }
    2125 2125
    2126 2126      /***************************************************************************
    2127 2127       *                                                                         *
    2128 2128       * Layout related APIs.                                                    *
    2129 2129       *                                                                         *
    2130 2130       **************************************************************************/
    2131 2131      /**
    2132 2132       * Defines whether or not this node's layout will be managed by it's parent.
    2133 2133       * If the node is managed, it's parent will factor the node's geometry
    2134 2134       * into its own preferred size and {@link #layoutBoundsProperty layoutBounds}
    2135 2135       * calculations and will lay it
    2136 2136       * out during the scene's layout pass.  If a managed node's layoutBounds
    2137 2137       * changes, it will automatically trigger relayout up the scene-graph
    2138 2138       * to the nearest layout root (which is typically the scene's root node).
    2139 2139       * <p>
    2140 2140       * If the node is unmanaged, its parent will ignore the child in both preferred
    2141 2141       * size computations and layout.   Changes in layoutBounds will not trigger
    2142 2142       * relayout above it.   If an unmanaged node is of type {@link javafx.scene.Parent Parent},
    2143 2143       * it will act as a "layout root", meaning that calls to {@link Parent#requestLayout()}
    2144 2144       * beneath it will cause only the branch rooted by the node to be relayed out,
    2145 2145       * thereby isolating layout changes to that root and below.  It's the application's
    2146 2146       * responsibility to set the size and position of an unmanaged node.
    2147 2147       * <p>
    2148 2148       * By default all nodes are managed.
    2149 2149       * </p>
    2150 2150       *
    2151 2151       * @see #isResizable()
    2152 2152       * @see #layoutBoundsProperty()
    2153 2153       * @see Parent#requestLayout()
    2154 2154       *
    2155 2155       */
    2156 2156      private BooleanProperty managed;
    2157 2157
    2158 2158      public final void setManaged(boolean value) {
    2159 2159          managedProperty().set(value);
    2160 2160      }
    2161 2161
    2162 2162      public final boolean isManaged() {
    2163 2163          return managed == null ? true : managed.get();
    2164 2164      }
    2165 2165
    2166 2166      public final BooleanProperty managedProperty() {
    2167 2167          if (managed == null) {
    2168 2168              managed = new BooleanPropertyBase(true) {
    2169 2169
    2170 2170                  @Override
    2171 2171                  protected void invalidated() {
    2172 2172                      final Parent parent = getParent();
    2173 2173                      if (parent != null) {
    2174 2174                          parent.managedChildChanged();
    2175 2175                      }
    2176 2176                      notifyManagedChanged();
    2177 2177                  }
    2178 2178
    2179 2179                  @Override
    2180 2180                  public Object getBean() {
    2181 2181                      return Node.this;
    2182 2182                  }
    2183 2183
    2184 2184                  @Override
    2185 2185                  public String getName() {
    2186 2186                      return "managed";
    2187 2187                  }
    2188 2188
    2189 2189              };
    2190 2190          }
    2191 2191          return managed;
    2192 2192      }
    2193 2193
    2194 2194      /**
    2195 2195       * Called whenever the "managed" flag has changed. This is only
    2196 2196       * used by Parent as an optimization to keep track of whether a
    2197 2197       * Parent node is a layout root or not.
    2198 2198       */
    2199 2199      void notifyManagedChanged() { }
    2200 2200
    2201 2201      /**
    2202 2202       * Defines the x coordinate of the translation that is added to this {@code Node}'s
    2203 2203       * transform for the purpose of layout.  The value should be computed as the
    2204 2204       * offset required to adjust the position of the node from its current
    2205 2205       * {@link #layoutBoundsProperty() layoutBounds minX} position (which might not be 0) to the desired location.
    2206 2206       *
    2207 2207       * <p>For example, if {@code textnode} should be positioned at {@code finalX}
    2208 2208       * <code><pre>
    2209 2209       *     textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
    2210 2210       * </pre></code>
    2211 2211       * <p>
    2212 2212       * Failure to subtract {@code layoutBounds minX} may result in misplacement
    2213 2213       * of the node.  The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
    2214 2214       * correct computation and should generally be used over setting layoutX directly.
    2215 2215       * <p>
    2216 2216       * The node's final translation will be computed as {@code layoutX} + {@link #translateXProperty translateX},
    2217 2217       * where {@code layoutX} establishes the node's stable position
    2218 2218       * and {@code translateX} optionally makes dynamic adjustments to that
    2219 2219       * position.
    2220 2220       * <p>
    2221 2221       * If the node is managed and has a {@link javafx.scene.layout.Region}
    2222 2222       * as its parent, then the layout region will set {@code layoutX} according to its
    2223 2223       * own layout policy.   If the node is unmanaged or parented by a {@link Group},
    2224 2224       * then the application may set {@code layoutX} directly to position it.
    2225 2225       * <p>
    2226 2226       * @see #relocate(double, double)
    2227 2227       * @see #layoutBoundsProperty()
    2228 2228       *
    2229 2229       */
    2230 2230      private DoubleProperty layoutX;
    2231 2231
    2232 2232      public final void setLayoutX(double value) {
    2233 2233          layoutXProperty().set(value);
    2234 2234      }
    2235 2235
    2236 2236      public final double getLayoutX() {
    2237 2237          return layoutX == null ? 0.0 : layoutX.get();
    2238 2238      }
    2239 2239
    2240 2240      public final DoubleProperty layoutXProperty() {
    2241 2241          if (layoutX == null) {
    2242 2242              layoutX = new DoublePropertyBase(0.0) {
    2243 2243
    2244 2244                  @Override
    2245 2245                  protected void invalidated() {
    2246 2246                      impl_transformsChanged();
    2247 2247                  }
    2248 2248
    2249 2249                  @Override
    2250 2250                  public Object getBean() {
    2251 2251                      return Node.this;
    2252 2252                  }
    2253 2253
    2254 2254                  @Override
    2255 2255                  public String getName() {
    2256 2256                      return "layoutX";
    2257 2257                  }
    2258 2258              };
    2259 2259          }
    2260 2260          return layoutX;
    2261 2261      }
    2262 2262
    2263 2263      /**
    2264 2264       * Defines the y coordinate of the translation that is added to this {@code Node}'s
    2265 2265       * transform for the purpose of layout.  The value should be computed as the
    2266 2266       * offset required to adjust the position of the node from its current
    2267 2267       * {@link #layoutBoundsProperty() layoutBounds minY} position (which might not be 0) to the desired location.
    2268 2268       *
    2269 2269       * <p>For example, if {@code textnode} should be positioned at {@code finalY}
    2270 2270       * <code><pre>
    2271 2271       *     textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
    2272 2272       * </pre></code>
    2273 2273       * <p>
    2274 2274       * Failure to subtract {@code layoutBounds minY} may result in misplacement
    2275 2275       * of the node.  The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
    2276 2276       * correct computation and should generally be used over setting layoutY directly.
    2277 2277       * <p>
    2278 2278       * The node's final translation will be computed as {@code layoutY} + {@link #translateYProperty translateY},
    2279 2279       * where {@code layoutY} establishes the node's stable position
    2280 2280       * and {@code translateY} optionally makes dynamic adjustments to that
    2281 2281       * position.
    2282 2282       * <p>
    2283 2283       * If the node is managed and has a {@link javafx.scene.layout.Region}
    2284 2284       * as its parent, then the region will set {@code layoutY} according to its
    2285 2285       * own layout policy.   If the node is unmanaged or parented by a {@link Group},
    2286 2286       * then the application may set {@code layoutY} directly to position it.
    2287 2287       *
    2288 2288       * @see #relocate(double, double)
    2289 2289       * @see #layoutBoundsProperty()
    2290 2290       */
    2291 2291      private DoubleProperty layoutY;
    2292 2292
    2293 2293      public final void setLayoutY(double value) {
    2294 2294          layoutYProperty().set(value);
    2295 2295      }
    2296 2296
    2297 2297      public final double getLayoutY() {
    2298 2298          return layoutY == null ? 0.0 : layoutY.get();
    2299 2299      }
    2300 2300
    2301 2301      public final DoubleProperty layoutYProperty() {
    2302 2302          if (layoutY == null) {
    2303 2303              layoutY = new DoublePropertyBase(0.0) {
    2304 2304
    2305 2305                  @Override
    2306 2306                  protected void invalidated() {
    2307 2307                      impl_transformsChanged();
    2308 2308                  }
    2309 2309
    2310 2310                  @Override
    2311 2311                  public Object getBean() {
    2312 2312                      return Node.this;
    2313 2313                  }
    2314 2314
    2315 2315                  @Override
    2316 2316                  public String getName() {
    2317 2317                      return "layoutY";
    2318 2318                  }
    2319 2319
    2320 2320              };
    2321 2321          }
    2322 2322          return layoutY;
    2323 2323      }
    2324 2324
    2325 2325      /**
    2326 2326       * Sets the node's layoutX and layoutY translation properties in order to
    2327 2327       * relocate this node to the x,y location in the parent.
    2328 2328       * <p>
    2329 2329       * This method does not alter translateX or translateY, which if also set
    2330 2330       * will be added to layoutX and layoutY, adjusting the final location by
    2331 2331       * corresponding amounts.
    2332 2332       *
    2333 2333       * @param x the target x coordinate location
    2334 2334       * @param y the target y coordinate location
    2335 2335       */
    2336 2336      public void relocate(double x, double y) {
    2337 2337          setLayoutX(x - getLayoutBounds().getMinX());
    2338 2338          setLayoutY(y - getLayoutBounds().getMinY());
    2339 2339
    2340 2340          PlatformLogger logger = Logging.getLayoutLogger();
    2341 2341          if (logger.isLoggable(PlatformLogger.FINER)) {
    2342 2342              logger.finer(this.toString()+" moved to ("+x+","+y+")");
    2343 2343          }
    2344 2344      }
    2345 2345
    2346 2346      /**
    2347 2347       * Indicates whether this node is a type which can be resized by its parent.
    2348 2348       * If this method returns true, then the parent will resize the node (ideally
    2349 2349       * within its size range) by calling node.resize(width,height) during the
    2350 2350       * layout pass.  All Regions, Controls, and WebView are resizable classes
    2351 2351       * which depend on their parents resizing them during layout once all sizing
    2352 2352       * and CSS styling information has been applied.
    2353 2353       * <p>
    2354 2354       * If this method returns false, then the parent cannot resize it during
    2355 2355       * layout (resize() is a no-op) and it should return its layoutBounds for
    2356 2356       * minimum, preferred, and maximum sizes.  Group, Text, and all Shapes are not
    2357 2357       * resizable and hence depend on the application to establish their sizing
    2358 2358       * by setting appropriate properties (e.g.  width/height for Rectangle,
    2359 2359       * text on Text, and so on).  Non-resizable nodes may still be relocated
    2360 2360       * during layout.
    2361 2361       *
    2362 2362       * @see #getContentBias()
    2363 2363       * @see #minWidth(double)
    2364 2364       * @see #minHeight(double)
    2365 2365       * @see #prefWidth(double)
    2366 2366       * @see #prefHeight(double)
    2367 2367       * @see #maxWidth(double)
    2368 2368       * @see #maxHeight(double)
    2369 2369       * @see #resize(double, double)
    2370 2370       * @see #getLayoutBounds()
    2371 2371       *
    2372 2372       * @return whether or not this node type can be resized by its parent during layout
    2373 2373       */
    2374 2374      public boolean isResizable() {
    2375 2375          return false;
    2376 2376      }
    2377 2377
    2378 2378      /**
    2379 2379       * Returns the orientation of a node's resizing bias for layout purposes.
    2380 2380       * If the node type has no bias, returns null.  If the node is resizable and
    2381 2381       * it's height depends on its width, returns HORIZONTAL, else if its width
    2382 2382       * depends on its height, returns VERTICAL.
    2383 2383       * <p>
    2384 2384       * Resizable subclasses should override this method to return an
    2385 2385       * appropriate value.
    2386 2386       *
    2387 2387       * @see #isResizable()
    2388 2388       * @see #minWidth(double)
    2389 2389       * @see #minHeight(double)
    2390 2390       * @see #prefWidth(double)
    2391 2391       * @see #prefHeight(double)
    2392 2392       * @see #maxWidth(double)
    2393 2393       * @see #maxHeight(double)
    2394 2394       *
    2395 2395       * @return orientation of width/height dependency or null if there is none
    2396 2396       */
    2397 2397      public Orientation getContentBias() {
    2398 2398          return null;
    2399 2399      }
    2400 2400
    2401 2401      /**
    2402 2402       * Returns the node's minimum width for use in layout calculations.
    2403 2403       * If the node is resizable, its parent should not resize its width any
    2404 2404       * smaller than this value.  If the node is not resizable, returns its
    2405 2405       * layoutBounds width.
    2406 2406       * <p>
    2407 2407       * Layout code which calls this method should first check the content-bias
    2408 2408       * of the node.  If the node has a vertical content-bias, then callers
    2409 2409       * should pass in a height value that the minimum width should be based on.
    2410 2410       * If the node has either a horizontal or null content-bias, then the caller
    2411 2411       * should pass in -1.
    2412 2412       * <p>
    2413 2413       * Node subclasses with a vertical content-bias should honor the height
    2414 2414       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2415 2415       * the height parameter (which will likely be -1).
    2416 2416       * <p>
    2417 2417       * @see #isResizable()
    2418 2418       * @see #getContentBias()
    2419 2419       *
    2420 2420       * @param height the height that should be used if minimum width depends on it
    2421 2421       * @return the minimum width that the node should be resized to during layout
    2422 2422       *
    2423 2423       */
    2424 2424      public double minWidth(double height) {
    2425 2425          return prefWidth(height);
    2426 2426      }
    2427 2427
    2428 2428      /**
    2429 2429       * Returns the node's minimum height for use in layout calculations.
    2430 2430       * If the node is resizable, its parent should not resize its height any
    2431 2431       * smaller than this value.  If the node is not resizable, returns its
    2432 2432       * layoutBounds height.
    2433 2433       * <p>
    2434 2434       * Layout code which calls this method should first check the content-bias
    2435 2435       * of the node.  If the node has a horizontal content-bias, then callers
    2436 2436       * should pass in a width value that the minimum height should be based on.
    2437 2437       * If the node has either a vertical or null content-bias, then the caller
    2438 2438       * should pass in -1.
    2439 2439       * <p>
    2440 2440       * Node subclasses with a horizontal content-bias should honor the width
    2441 2441       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2442 2442       * the width parameter (which will likely be -1).
    2443 2443       * <p>
    2444 2444       * @see #isResizable()
    2445 2445       * @see #getContentBias()
    2446 2446       *
    2447 2447       * @param width the width that should be used if minimum height depends on it
    2448 2448       * @return the minimum height that the node should be resized to during layout
    2449 2449       *
    2450 2450       */
    2451 2451      public double minHeight(double width) {
    2452 2452          return prefHeight(width);
    2453 2453      }
    2454 2454
    2455 2455
    2456 2456      /**
    2457 2457       * Returns the node's preferred width for use in layout calculations.
    2458 2458       * If the node is resizable, its parent should treat this value as the
    2459 2459       * node's ideal width within its range.  If the node is not resizable,
    2460 2460       * just returns its layoutBounds width, which should be treated as the rigid
    2461 2461       * width of the node.
    2462 2462       * <p>
    2463 2463       * Layout code which calls this method should first check the content-bias
    2464 2464       * of the node.  If the node has a vertical content-bias, then callers
    2465 2465       * should pass in a height value that the preferred width should be based on.
    2466 2466       * If the node has either a horizontal or null content-bias, then the caller
    2467 2467       * should pass in -1.
    2468 2468       * <p>
    2469 2469       * Node subclasses with a vertical content-bias should honor the height
    2470 2470       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2471 2471       * the height parameter (which will likely be -1).
    2472 2472       * <p>
    2473 2473       * @see #isResizable()
    2474 2474       * @see #getContentBias()
    2475 2475       * @see #autosize()
    2476 2476       *
    2477 2477       * @param height the height that should be used if preferred width depends on it
    2478 2478       * @return the preferred width that the node should be resized to during layout
    2479 2479       */
    2480 2480      public double prefWidth(double height) {
    2481 2481          return getLayoutBounds().getWidth();
    2482 2482      }
    2483 2483
    2484 2484      /**
    2485 2485       * Returns the node's preferred height for use in layout calculations.
    2486 2486       * If the node is resizable, its parent should treat this value as the
    2487 2487       * node's ideal height within its range.  If the node is not resizable,
    2488 2488       * just returns its layoutBounds height, which should be treated as the rigid
    2489 2489       * height of the node.
    2490 2490       * <p>
    2491 2491       * Layout code which calls this method should first check the content-bias
    2492 2492       * of the node.  If the node has a horizontal content-bias, then callers
    2493 2493       * should pass in a width value that the preferred height should be based on.
    2494 2494       * If the node has either a vertical or null content-bias, then the caller
    2495 2495       * should pass in -1.
    2496 2496       * <p>
    2497 2497       * Node subclasses with a horizontal content-bias should honor the height
    2498 2498       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2499 2499       * the height parameter (which will likely be -1).
    2500 2500       * <p>
    2501 2501       * @see #getContentBias()
    2502 2502       * @see #autosize()
    2503 2503       *
    2504 2504       * @param width the width that should be used if preferred height depends on it
    2505 2505       * @return the preferred height that the node should be resized to during layout
    2506 2506       */
    2507 2507      public double prefHeight(double width) {
    2508 2508          return getLayoutBounds().getHeight();
    2509 2509      }
    2510 2510
    2511 2511      /**
    2512 2512       * Returns the node's maximum width for use in layout calculations.
    2513 2513       * If the node is resizable, its parent should not resize its width any
    2514 2514       * larger than this value.  A value of Double.MAX_VALUE indicates the
    2515 2515       * parent may expand the node's width beyond its preferred without limits.
    2516 2516       * <p>
    2517 2517       * If the node is not resizable, returns its layoutBounds width.
    2518 2518       * <p>
    2519 2519       * Layout code which calls this method should first check the content-bias
    2520 2520       * of the node.  If the node has a vertical content-bias, then callers
    2521 2521       * should pass in a height value that the maximum width should be based on.
    2522 2522       * If the node has either a horizontal or null content-bias, then the caller
    2523 2523       * should pass in -1.
    2524 2524       * <p>
    2525 2525       * Node subclasses with a vertical content-bias should honor the height
    2526 2526       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2527 2527       * the height parameter (which will likely be -1).
    2528 2528       * <p>
    2529 2529       * @see #isResizable()
    2530 2530       * @see #getContentBias()
    2531 2531       *
    2532 2532       * @param height the height that should be used if maximum width depends on it
    2533 2533       * @return the maximum width that the node should be resized to during layout
    2534 2534       *
    2535 2535       */
    2536 2536      public double maxWidth(double height) {
    2537 2537          return prefWidth(height);
    2538 2538      }
    2539 2539
    2540 2540      /**
    2541 2541       * Returns the node's maximum height for use in layout calculations.
    2542 2542       * If the node is resizable, its parent should not resize its height any
    2543 2543       * larger than this value.  A value of Double.MAX_VALUE indicates the
    2544 2544       * parent may expand the node's height beyond its preferred without limits.
    2545 2545       * <p>
    2546 2546       * If the node is not resizable, returns its layoutBounds height.
    2547 2547       * <p>
    2548 2548       * Layout code which calls this method should first check the content-bias
    2549 2549       * of the node.  If the node has a horizontal content-bias, then callers
    2550 2550       * should pass in a width value that the maximum height should be based on.
    2551 2551       * If the node has either a vertical or null content-bias, then the caller
    2552 2552       * should pass in -1.
    2553 2553       * <p>
    2554 2554       * Node subclasses with a horizontal content-bias should honor the width
    2555 2555       * parameter whether -1 or a positive value.   All other subclasses may ignore
    2556 2556       * the width parameter (which will likely be -1).
    2557 2557       * <p>
    2558 2558       * @see #isResizable()
    2559 2559       * @see #getContentBias()
    2560 2560       *
    2561 2561       * @param width the width that should be used if maximum height depends on it
    2562 2562       * @return the maximum height that the node should be resized to during layout
    2563 2563       *
    2564 2564       */
    2565 2565      public double maxHeight(double width) {
    2566 2566          return prefHeight(width);
    2567 2567      }
    2568 2568
    2569 2569      /**
    2570 2570       * If the node is resizable, will set its layout bounds to the specified
    2571 2571       * width and height.   If the node is not resizable, this method is a no-op.
    2572 2572       * <p>
    2573 2573       * This method should generally only be called by parent nodes from their
    2574 2574       * layoutChildren() methods.   All Parent classes will automatically resize
    2575 2575       * resizable children, so resizing done directly by the application will be
    2576 2576       * overridden by the node's parent, unless the child is unmanaged.
    2577 2577       * <p>
    2578 2578       * Parents are responsible for ensuring the width and height values fall
    2579 2579       * within the resizable node's preferred range.  The autosize() method may
    2580 2580       * be used if the parent just needs to resize the node to its preferred size.
    2581 2581       *
    2582 2582       * <p>
    2583 2583       * @see #isResizable()
    2584 2584       * @see #getContentBias()
    2585 2585       * @see #autosize()
    2586 2586       * @see #minWidth(double)
    2587 2587       * @see #minHeight(double)
    2588 2588       * @see #prefWidth(double)
    2589 2589       * @see #prefHeight(double)
    2590 2590       * @see #maxWidth(double)
    2591 2591       * @see #maxHeight(double)
    2592 2592       * @see #getLayoutBounds()
    2593 2593       *
    2594 2594       * @param width the target layout bounds width
    2595 2595       * @param height the target layout bounds height
    2596 2596       */
    2597 2597      public void resize(double width, double height) {
    2598 2598      }
    2599 2599
    2600 2600      /**
    2601 2601       * If the node is resizable, will set its layout bounds to its current preferred
    2602 2602       * width and height. If the node is not resizable, this method is a no-op.
    2603 2603       * <p>
    2604 2604       * This method automatically queries the node's content-bias and if it's
    2605 2605       * horizontal, will pass in the node's preferred width to get the preferred
    2606 2606       * height; if vertical, will pass in the node's preferred height to get the width,
    2607 2607       * and if null, will compute the preferred width/height independently.
    2608 2608       * <p>
    2609 2609       *
    2610 2610       * @see #isResizable()
    2611 2611       * @see #getContentBias()
    2612 2612       *
    2613 2613       */
    2614 2614      public final void autosize() {
    2615 2615          if (isResizable()) {
    2616 2616              Orientation contentBias = getContentBias();
    2617 2617              double w, h;
    2618 2618              if (contentBias == null) {
    2619 2619                  w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
    2620 2620                  h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
    2621 2621
    2622 2622              } else if (contentBias == Orientation.HORIZONTAL) {
    2623 2623                  w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
    2624 2624                  h = prefHeight(w);
    2625 2625
    2626 2626              } else { // bias == VERTICAL
    2627 2627                  h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
    2628 2628                  w = prefWidth(h);
    2629 2629              }
    2630 2630              resize(w,h);
    2631 2631          }
    2632 2632      }
    2633 2633
    2634 2634      double boundedSize(double value, double min, double max) {
    2635 2635          // if max < value, return max
    2636 2636          // if min > value, return min
    2637 2637          // if min > max, return min
    2638 2638          return Math.min(Math.max(value, min), Math.max(min,max));
    2639 2639      }
    2640 2640
    2641 2641      /**
    2642 2642       * If the node is resizable, will set its layout bounds to the specified
    2643 2643       * width and height.   If the node is not resizable, the resize step is skipped.
    2644 2644       * <p>
    2645 2645       * Once the node has been resized (if resizable) then sets the node's layoutX
    2646 2646       * and layoutY translation properties in order to relocate it to x,y in the
    2647 2647       * parent's coordinate space.
    2648 2648       * <p>
    2649 2649       * This method should generally only be called by parent nodes from their
    2650 2650       * layoutChildren() methods.   All Parent classes will automatically resize
    2651 2651       * resizable children, so resizing done directly by the application will be
    2652 2652       * overridden by the node's parent, unless the child is unmanaged.
    2653 2653       * <p>
    2654 2654       * Parents are responsible for ensuring the width and height values fall
    2655 2655       * within the resizable node's preferred range.  The autosize() and relocate()
    2656 2656       * methods may be used if the parent just needs to resize the node to its
    2657 2657       * preferred size and reposition it.
    2658 2658       * <p>
    2659 2659       * @see #isResizable()
    2660 2660       * @see #getContentBias()
    2661 2661       * @see #autosize()
    2662 2662       * @see #minWidth(double)
    2663 2663       * @see #minHeight(double)
    2664 2664       * @see #prefWidth(double)
    2665 2665       * @see #prefHeight(double)
    2666 2666       * @see #maxWidth(double)
    2667 2667       * @see #maxHeight(double)
    2668 2668       *
    2669 2669       * @param x the target x coordinate location
    2670 2670       * @param y the target y coordinate location
    2671 2671       * @param width the target layout bounds width
    2672 2672       * @param height the target layout bounds height
    2673 2673       *
    2674 2674       */
    2675 2675      public void resizeRelocate(double x, double y, double width, double height) {
    2676 2676          resize(width, height);
    2677 2677          relocate(x,y);
    2678 2678      }
    2679 2679
    2680 2680      /**
    2681 2681       * The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location
    2682 2682       * that should be used when this node is being vertically aligned by baseline with
    2683 2683       * other nodes.  By default this returns the layoutBounds height of the node.  Subclasses
    2684 2684       * which contain text should override this method to return their actual text baseline offset.
    2685 2685       *
    2686 2686       * @return offset of text baseline from layoutBounds.minY
    2687 2687       */
    2688 2688      public double getBaselineOffset() {
    2689 2689          return getLayoutBounds().getHeight();
    2690 2690      }
    2691 2691
    2692 2692      /* *************************************************************************
    2693 2693       *                                                                         *
    2694 2694       * Bounds related APIs                                                     *
    2695 2695       *                                                                         *
    2696 2696       **************************************************************************/
    2697 2697
    2698 2698      public final Bounds getBoundsInParent() {
    2699 2699          return boundsInParentProperty().get();
    2700 2700      }
    2701 2701
    2702 2702      /**
    2703 2703       * The rectangular bounds of this {@code Node} which include its transforms.
    2704 2704       * {@code boundsInParent} is calculated by
    2705 2705       * taking the local bounds (defined by {@link #boundsInLocalProperty boundsInLocal}) and applying
    2706 2706       * the transform created by setting the following additional variables
    2707 2707       * <ol>
    2708 2708       * <li>{@link #getTransforms transforms} ObservableList</li>
    2709 2709       * <li>{@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}</li>
    2710 2710       * <li>{@link #rotateProperty rotate}</li>
    2711 2711       * <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li>
    2712 2712       * <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY}</li>
    2713 2713       * </ol>
    2714 2714       * <p>
    2715 2715       * The resulting bounds will be conceptually in the coordinate space of the
    2716 2716       * {@code Node}'s parent, however the node need not have a parent to calculate
    2717 2717       * these bounds.
    2718 2718       * <p>
    2719 2719       * Note that this method does not take the node's visibility into account;
    2720 2720       * the computation is based on the geometry of this {@code Node} only.
    2721 2721       * <p>
    2722 2722       * This property will always have a non-null value.
    2723 2723       * <p>
    2724 2724       * Note that boundsInParent is automatically recomputed whenever the
    2725 2725       * geometry of a node changes, or when any of the following the change:
    2726 2726       * transforms ObservableList, translateX, translateY, layoutX, layoutY,
    2727 2727       * scaleX, scaleY, or the rotate variable. For this reason, it is an error
    2728 2728       * to bind any of these values in a node to an expression that depends upon
    2729 2729       * this variable. For example, the x or y variables of a shape, or
    2730 2730       * translateX, translateY should never be bound to boundsInParent
    2731 2731       * for the purpose of positioning the node.
    2732 2732       */
    2733 2733      public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
    2734 2734          return getMiscProperties().boundsInParentProperty();
    2735 2735      }
    2736 2736
    2737 2737      private void invalidateBoundsInParent() {
    2738 2738          if (miscProperties != null) {
    2739 2739              miscProperties.invalidateBoundsInParent();
    2740 2740          }
    2741 2741      }
    2742 2742
    2743 2743      public final Bounds getBoundsInLocal() {
    2744 2744          return boundsInLocalProperty().get();
    2745 2745      }
    2746 2746
    2747 2747      /**
    2748 2748       * The rectangular bounds of this {@code Node} in the node's
    2749 2749       * untransformed local coordinate space.  For nodes that extend
    2750 2750       * {@link javafx.scene.shape.Shape}, the local bounds will also include
    2751 2751       * space required for a non-zero stroke that may fall outside the shape's
    2752 2752       * geometry that is defined by position and size attributes.
    2753 2753       * The local bounds will also include any clipping set with {@link #clipProperty clip}
    2754 2754       * as well as effects set with {@link #effectProperty effect}.
    2755 2755       *
    2756 2756       * <p>
    2757 2757       * Note that this method does not take the node's visibility into account;
    2758 2758       * the computation is based on the geometry of this {@code Node} only.
    2759 2759       * <p>
    2760 2760       * This property will always have a non-null value.
    2761 2761       * <p>
    2762 2762       * Note that boundsInLocal is automatically recomputed whenever the
    2763 2763       * geometry of a node changes. For this reason, it is an error to bind any
    2764 2764       * of these values in a node to an expression that depends upon this variable.
    2765 2765       * For example, the x or y variables of a shape should never be bound
    2766 2766       * to boundsInLocal for the purpose of positioning the node.
    2767 2767       */
    2768 2768      public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
    2769 2769          return getMiscProperties().boundsInLocalProperty();
    2770 2770      }
    2771 2771
    2772 2772      private void invalidateBoundsInLocal() {
    2773 2773          if (miscProperties != null) {
    2774 2774              miscProperties.invalidateBoundsInLocal();
    2775 2775          }
    2776 2776      }
    2777 2777
    2778 2778      /**
    2779 2779       * The rectangular bounds that should be used for layout calculations for
    2780 2780       * this node. {@code layoutBounds} may differ from the visual bounds
    2781 2781       * of the node and is computed differently depending on the node type.
    2782 2782       * <p>
    2783 2783       * If the node type is resizable ({@link javafx.scene.layout.Region Region},
    2784 2784       * {@link javafx.scene.control.Control Control}, or {@link javafx.scene.web.WebView WebView})
    2785 2785       * then the layoutBounds will always be {@code 0,0 width x height}.
    2786 2786       * If the node type is not resizable ({@link javafx.scene.shape.Shape Shape},
    2787 2787       * {@link javafx.scene.text.Text Text}, or {@link Group}), then the layoutBounds
    2788 2788       * are computed based on the node's geometric properties and does not include the
    2789 2789       * node's clip, effect, or transforms.  See individual class documentation
    2790 2790       * for details.
    2791 2791       * <p>
    2792 2792       * Note that the {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}, {@link #translateXProperty translateX}, and
    2793 2793       * {@link #translateYProperty translateY} variables are not included in the layoutBounds.
    2794 2794       * This is important because layout code must first determine the current
    2795 2795       * size and location of the node (using layoutBounds) and then set
    2796 2796       * {@code layoutX} and {@code layoutY} to adjust the translation of the
    2797 2797       * node so that it will have the desired layout position.
    2798 2798       * <p>
    2799 2799       * Because the computation of layoutBounds is often tied to a node's
    2800 2800       * geometric variables, it is an error to bind any such variables to an
    2801 2801       * expression that depends upon {@code layoutBounds}. For example, the
    2802 2802       * x or y variables of a shape should never be bound to layoutBounds
    2803 2803       * for the purpose of positioning the node.
    2804 2804       * <p>
    2805 2805       * The layoutBounds will never be null.
    2806 2806       *
    2807 2807       */
    2808 2808      private LazyBoundsProperty layoutBounds = new LazyBoundsProperty() {
    2809 2809          @Override
    2810 2810          protected Bounds computeBounds() {
    2811 2811              return impl_computeLayoutBounds();
    2812 2812          }
    2813 2813
    2814 2814          @Override
    2815 2815          public Object getBean() {
    2816 2816              return Node.this;
    2817 2817          }
    2818 2818
    2819 2819          @Override
    2820 2820          public String getName() {
    2821 2821              return "layoutBounds";
    2822 2822          }
    2823 2823      };
    2824 2824
    2825 2825      public final Bounds getLayoutBounds() {
    2826 2826          return layoutBoundsProperty().get();
    2827 2827      }
    2828 2828
    2829 2829      public final ReadOnlyObjectProperty<Bounds> layoutBoundsProperty() {
    2830 2830          return layoutBounds;
    2831 2831      }
    2832 2832
    2833 2833      /*
    2834 2834       *                  Bounds And Transforms Computation
    2835 2835       *
    2836 2836       *  This section of the code is responsible for computing and caching
    2837 2837       *  various bounds and transforms. For optimal performance and minimal
    2838 2838       *  recomputation of bounds (which can be quite expensive), we cache
    2839 2839       *  values on two different levels. We expose two public immutable
    2840 2840       *  Bounds boundsInParent objects and boundsInLocal. Because they are
    2841 2841       *  immutable and because they may change quite frequently (especially
    2842 2842       *  in the case of a Parent who's children are animated), it is
    2843 2843       *  important that the system does not rely on these variables, because
    2844 2844       *  doing so would produce a large amount of garbage. Rather, these
    2845 2845       *  variables are provided solely for the convenience of application
    2846 2846       *  developers and, being lazily bound, should generally be created at
    2847 2847       *  most once per frame.
    2848 2848       *
    2849 2849       *  The second level of caching are within local Bounds2D variables.
    2850 2850       *  These variables, txBounds and geomBounds, are mutable and as such
    2851 2851       *  can be cached and updated as frequently as necessary without creating
    2852 2852       *  excessive garbage. However, since the computation of bounds is still
    2853 2853       *  expensive, it is desirable to cache both the geometric bounds and
    2854 2854       *  the "complete" transformed bounds (essentially, boundsInParent).
    2855 2855       *  Cached txBounds is particularly useful when computing the geometric
    2856 2856       *  bounds of a Parent since it would not require complete or partial
    2857 2857       *  recomputation of each child.
    2858 2858       *
    2859 2859       *  Finally, we cache the complete transform for this node which converts
    2860 2860       *  its coord system from local to parent coords. This is useful both for
    2861 2861       *  minimizing bounds recomputations in the case of the geometry having
    2862 2862       *  changed but the transform not having changed, and also because the tx
    2863 2863       *  is required for several different computations (for example, it must
    2864 2864       *  be computed once during state synchronization with the PG peer, and
    2865 2865       *  must also be computed when the pivot point changes, and also when
    2866 2866       *  deriving the txBounds of the Node).
    2867 2867       *
    2868 2868       *  As with any caching system, a subtle and non-trivial amount of code
    2869 2869       *  is devoted to invalidating the bounds / transforms at appropriate
    2870 2870       *  times and in appropriate places to make sure bounds / transforms
    2871 2871       *  are recomputed at all necessary times.
    2872 2872       *
    2873 2873       *  There are three computeXXX functions. One is for computing the
    2874 2874       *  boundsInParent, the second for computing boundsInLocal, and the
    2875 2875       *  third for computing the default layout bounds (which, by default,
    2876 2876       *  is based on the geometric bounds). These functions are all prefixed
    2877 2877       *  with "compute" because they create and return new immutable
    2878 2878       *  Bounds objects.
    2879 2879       *
    2880 2880       *  There are three getXXXBounds functions. One is for returning the
    2881 2881       *  complete transformed bounds. The second is for returning the
    2882 2882       *  local bounds. The last is for returning the geometric bounds. These
    2883 2883       *  functions are all prefixed with "get" because they may well return
    2884 2884       *  a cached value, or may actually compute the bounds if necessary. These
    2885 2885       *  functions all have the same signature. They take a Bounds2D and
    2886 2886       *  BaseTransform, and return a Bounds2D (the same as they took). These
    2887 2887       *  functions essentially populate the supplied bounds2D with the
    2888 2888       *  appropriate bounds information, leveraging cached bounds if possible.
    2889 2889       *
    2890 2890       *  There is a single impl_computeGeomBounds function which is abstract.
    2891 2891       *  This must be implemented in each subclass, and is responsible for
    2892 2892       *  computing the actual geometric bounds for the Node. For example, Parent
    2893 2893       *  is written such that this function is the union of the transformed
    2894 2894       *  bounds of each child. Rectangle is written such that this takes into
    2895 2895       *  account the size and stroke. Text is written such that it is computed
    2896 2896       *  based on the actual glyphs.
    2897 2897       *
    2898 2898       *  There are two updateXXX functions, updateGeomBounds and updateTxBounds.
    2899 2899       *  These functions are for ensuring that geomBounds and txBounds are
    2900 2900       *  valid. They only execute in the case of the cached value being invalid,
    2901 2901       *  so the function call is very cheap in cases where the cached bounds
    2902 2902       *  values are still valid.
    2903 2903       */
    2904 2904
    2905 2905      /**
    2906 2906       * An affine transform that holds the computed local-to-parent transform.
    2907 2907       * This is the concatenation of all transforms in this node, including all
    2908 2908       * of the convenience transforms.
    2909 2909       */
    2910 2910      private final Affine3D localToParentTx = new Affine3D();
    2911 2911
    2912 2912      /**
    2913 2913       * This flag is used to indicate that localToParentTx is dirty and needs
    2914 2914       * to be recomputed.
    2915 2915       */
    2916 2916      private boolean transformDirty = true;
    2917 2917
    2918 2918      /**
    2919 2919       * The cached transformed bounds. This is never null, but is frequently set
    2920 2920       * to be invalid whenever the bounds for the node have changed. These are
    2921 2921       * "complete" bounds, that is, with transforms and effect and clip applied.
    2922 2922       * Note that this is equivalent to boundsInParent
    2923 2923       */
    2924 2924      private BaseBounds txBounds = new RectBounds();
    2925 2925
    2926 2926      /**
    2927 2927       * The cached bounds. This is never null, but is frequently set to be
    2928 2928       * invalid whenever the bounds for the node have changed. These are the
    2929 2929       * "content" bounds, that is, without transforms or effects applied.
    2930 2930       */
    2931 2931      private BaseBounds geomBounds = new RectBounds();
    2932 2932
    2933 2933      /**
    2934 2934       * This special flag is used only by Parent to flag whether or not
    2935 2935       * the *parent* has processed the fact that bounds have changed for this
    2936 2936       * child Node. We need some way of flagging this on a per-node basis to
    2937 2937       * enable the significant performance optimizations and fast paths that
    2938 2938       * are in the Parent code.
    2939 2939       * <p>
    2940 2940       * To reduce confusion, although this variable is defined on Node, it
    2941 2941       * really belongs to the Parent of the node and should *only* be modified
    2942 2942       * by the parent.
    2943 2943       */
    2944 2944      boolean boundsChanged = true;
    2945 2945
    2946 2946      /**
    2947 2947       * Returns geometric bounds, but may be over-ridden by a subclass.
    2948 2948       * @treatAsPrivate implementation detail
    2949 2949       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    2950 2950       */
    2951 2951      @Deprecated
    2952 2952      protected Bounds impl_computeLayoutBounds() {
    2953 2953          BaseBounds tempBounds = TempState.getInstance().bounds;
    2954 2954          tempBounds = getGeomBounds(tempBounds,
    2955 2955                                     BaseTransform.IDENTITY_TRANSFORM);
    2956 2956          return new BoundingBox(tempBounds.getMinX(),
    2957 2957                                 tempBounds.getMinY(),
    2958 2958                                 tempBounds.getMinZ(),
    2959 2959                                 tempBounds.getWidth(),
    2960 2960                                 tempBounds.getHeight(),
    2961 2961                                 tempBounds.getDepth());
    2962 2962      }
    2963 2963
    2964 2964      /**
    2965 2965       * Subclasses may customize the layoutBounds by means of overriding the
    2966 2966       * impl_computeLayoutBounds method. If the layout bounds need to be
    2967 2967       * recomputed, the subclass must notify the Node implementation of this
    2968 2968       * fact so that appropriate notifications and internal state can be
    2969 2969       * kept in sync. Subclasses must call impl_layoutBoundsChanged to
    2970 2970       * let Node know that the layout bounds are invalid and need to be
    2971 2971       * recomputed.
    2972 2972       *
    2973 2973       * @treatAsPrivate implementation detail
    2974 2974       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    2975 2975       */
    2976 2976      @Deprecated
    2977 2977      protected final void impl_layoutBoundsChanged() {
    2978 2978          if (!layoutBounds.valid) {
    2979 2979              return;
    2980 2980          }
    2981 2981          layoutBounds.invalidate();
    2982 2982          if (nodeTransformation != null && nodeTransformation.hasScaleOrRotate()) {
    2983 2983              // if either the scale or rotate convenience variables are used,
    2984 2984              // then we need a valid pivot point. Since the layoutBounds
    2985 2985              // affects the pivot we need to invalidate the transform
    2986 2986              impl_transformsChanged();
    2987 2987          }
    2988 2988          // notify the parent
    2989 2989          // Group instanceof check a little hoaky, but it allows us to disable
    2990 2990          // unnecessary layout for the case of a non-resizable within a group
    2991 2991          Parent p = getParent();
    2992 2992          if (isManaged() && (p != null) && !(p instanceof Group && !isResizable())) {
    2993 2993              p.requestLayout();
    2994 2994          }
    2995 2995      }
    2996 2996
    2997 2997      /**
    2998 2998       * Loads the given bounds object with the transformed bounds relative to,
    2999 2999       * and based on, the given transform. That is, this is the local bounds
    3000 3000       * with the local-to-parent transform applied.
    3001 3001       *
    3002 3002       * We *never* pass null in as a bounds. This method will
    3003 3003       * NOT take a null bounds object. The returned value may be
    3004 3004       * the same bounds object passed in, or it may be a new object.
    3005 3005       * The reason for this object promotion is in the case of needing
    3006 3006       * to promote from a RectBounds to a BoxBounds (3D).
    3007 3007       */
    3008 3008      BaseBounds getTransformedBounds(BaseBounds bounds, BaseTransform tx) {
    3009 3009          updateLocalToParentTransform();
    3010 3010          if (tx.isTranslateOrIdentity()) {
    3011 3011              updateTxBounds();
    3012 3012              bounds = bounds.deriveWithNewBounds(txBounds);
    3013 3013              if (!tx.isIdentity()) {
    3014 3014                  final double translateX = tx.getMxt();
    3015 3015                  final double translateY = tx.getMyt();
    3016 3016                  final double translateZ = tx.getMzt();
    3017 3017                  bounds = bounds.deriveWithNewBounds(
    3018 3018                                      (float) (bounds.getMinX() + translateX),
    3019 3019                                      (float) (bounds.getMinY() + translateY),
    3020 3020                                      (float) (bounds.getMinZ() + translateZ),
    3021 3021                                      (float) (bounds.getMaxX() + translateX),
    3022 3022                                      (float) (bounds.getMaxY() + translateY),
    3023 3023                                      (float) (bounds.getMaxZ() + translateZ));
    3024 3024              }
    3025 3025              return bounds;
    3026 3026          } else if (localToParentTx.isIdentity()) {
    3027 3027              return getLocalBounds(bounds, tx);
    3028 3028          } else {
    3029 3029              double mxx = tx.getMxx();
    3030 3030              double mxy = tx.getMxy();
    3031 3031              double mxz = tx.getMxz();
    3032 3032              double mxt = tx.getMxt();
    3033 3033              double myx = tx.getMyx();
    3034 3034              double myy = tx.getMyy();
    3035 3035              double myz = tx.getMyz();
    3036 3036              double myt = tx.getMyt();
    3037 3037              double mzx = tx.getMzx();
    3038 3038              double mzy = tx.getMzy();
    3039 3039              double mzz = tx.getMzz();
    3040 3040              double mzt = tx.getMzt();
    3041 3041              BaseTransform boundsTx = tx.deriveWithConcatenation(localToParentTx);
    3042 3042              bounds = getLocalBounds(bounds, boundsTx);
    3043 3043              if (boundsTx == tx) {
    3044 3044                  tx.restoreTransform(mxx, mxy, mxz, mxt,
    3045 3045                                      myx, myy, myz, myt,
    3046 3046                                      mzx, mzy, mzz, mzt);
    3047 3047              }
    3048 3048              return bounds;
    3049 3049          }
    3050 3050      }
    3051 3051
    3052 3052      /**
    3053 3053       * Loads the given bounds object with the local bounds relative to,
    3054 3054       * and based on, the given transform. That is, these are the geometric
    3055 3055       * bounds + clip and effect.
    3056 3056       *
    3057 3057       * We *never* pass null in as a bounds. This method will
    3058 3058       * NOT take a null bounds object. The returned value may be
    3059 3059       * the same bounds object passed in, or it may be a new object.
    3060 3060       * The reason for this object promotion is in the case of needing
    3061 3061       * to promote from a RectBounds to a BoxBounds (3D).
    3062 3062       */
    3063 3063      BaseBounds getLocalBounds(BaseBounds bounds, BaseTransform tx) {
    3064 3064          if (getEffect() != null || getClip() != null) {
    3065 3065              // We either get the bounds of the effect (if it isn't null)
    3066 3066              // or we get the geom bounds (if effect is null). We will then
    3067 3067              // intersect this with the clip.
    3068 3068              if (getEffect() != null) {
    3069 3069                  BaseBounds b = getEffect().impl_getBounds(bounds, tx, this, boundsAccessor);
    3070 3070                  bounds = bounds.deriveWithNewBounds(b);
    3071 3071              } else {
    3072 3072                  bounds = getGeomBounds(bounds, tx);
    3073 3073              }
    3074 3074              // intersect with the clip. Take care with "bounds" as it may
    3075 3075              // actually be TEMP_BOUNDS, so we save off state
    3076 3076              if (getClip() != null) {
    3077 3077                  //TODO: For 3D case the intersecting transformed bounds and
    3078 3078                  //      transformed clip will not produce the correct bounds.
    3079 3079                  double x1 = bounds.getMinX();
    3080 3080                  double y1 = bounds.getMinY();
    3081 3081                  double x2 = bounds.getMaxX();
    3082 3082                  double y2 = bounds.getMaxY();
    3083 3083                  double z1 = bounds.getMinZ();
    3084 3084                  double z2 = bounds.getMaxZ();
    3085 3085                  bounds = getClip().getTransformedBounds(bounds, tx);
    3086 3086                  bounds.intersectWith((float)x1, (float)y1, (float)z1,
    3087 3087                          (float)x2, (float)y2, (float)z2);
    3088 3088              }
    3089 3089              // return the bounds
    3090 3090              return bounds;
    3091 3091          } else {
    3092 3092              // fast path, simply return geom bounds
    3093 3093              return getGeomBounds(bounds, tx);
    3094 3094          }
    3095 3095      }
    3096 3096
    3097 3097      /**
    3098 3098       * Loads the given bounds object with the geometric bounds relative to,
    3099 3099       * and based on, the given transform.
    3100 3100       *
    3101 3101       * We *never* pass null in as a bounds. This method will
    3102 3102       * NOT take a null bounds object. The returned value may be
    3103 3103       * the same bounds object passed in, or it may be a new object.
    3104 3104       * The reason for this object promotion is in the case of needing
    3105 3105       * to promote from a RectBounds to a BoxBounds (3D).
    3106 3106       */
    3107 3107      BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx) {
    3108 3108          if (tx.isTranslateOrIdentity()) {
    3109 3109              // we can take a fast path since we know tx is either a simple
    3110 3110              // translation or is identity
    3111 3111              updateGeomBounds();
    3112 3112              bounds = bounds.deriveWithNewBounds(geomBounds);
    3113 3113              if (!tx.isIdentity()) {
    3114 3114                  double translateX = tx.getMxt();
    3115 3115                  double translateY = tx.getMyt();
    3116 3116                  double translateZ = tx.getMzt();
    3117 3117                  bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
    3118 3118                          (float) (bounds.getMinY() + translateY),
    3119 3119                          (float) (bounds.getMinZ() + translateZ),
    3120 3120                          (float) (bounds.getMaxX() + translateX),
    3121 3121                          (float) (bounds.getMaxY() + translateY),
    3122 3122                          (float) (bounds.getMaxZ() + translateZ));
    3123 3123              }
    3124 3124              return bounds;
    3125 3125          } else if (tx.is2D()) {
    3126 3126              // this is a scale / rotate / skew transform
    3127 3127              return impl_computeGeomBounds(bounds, tx);
    3128 3128          } else {
    3129 3129              // Note: Transforming the local geomBounds into a 3D space will yield a bounds
    3130 3130              // that isn't as tight as transforming its geometry and compute it bounds.
    3131 3131              updateGeomBounds();
    3132 3132              return tx.transform(geomBounds, bounds);
    3133 3133          }
    3134 3134      }
    3135 3135
    3136 3136      /**
    3137 3137       * Computes the geometric bounds for this Node. This method is abstract
    3138 3138       * and must be implemented by each Node subclass.
    3139 3139       * @treatAsPrivate implementation detail
    3140 3140       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3141 3141       */
    3142 3142      @Deprecated
    3143 3143      public abstract BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx);
    3144 3144
    3145 3145      /**
    3146 3146       * If necessary, recomputes the cached local bounds. If the bounds are not
    3147 3147       * invalid, then this method is a no-op.
    3148 3148       */
    3149 3149      void updateGeomBounds() {
    3150 3150          if (geomBoundsInvalid) {
    3151 3151              geomBounds = impl_computeGeomBounds(geomBounds, BaseTransform.IDENTITY_TRANSFORM);
    3152 3152              geomBoundsInvalid = false;
    3153 3153          }
    3154 3154      }
    3155 3155
    3156 3156      /**
    3157 3157       * If necessary, recomputes the cached transformed bounds.
    3158 3158       * If the cached transformed bounds are not invalid, then
    3159 3159       * this method is a no-op.
    3160 3160       */
    3161 3161      void updateTxBounds() {
    3162 3162          if (txBoundsInvalid) {
    3163 3163              updateLocalToParentTransform();
    3164 3164              txBounds = getLocalBounds(txBounds, localToParentTx);
    3165 3165              txBoundsInvalid = false;
    3166 3166          }
    3167 3167      }
    3168 3168
    3169 3169      /**
    3170 3170       * @treatAsPrivate implementation detail
    3171 3171       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3172 3172       */
    3173 3173      @Deprecated
    3174 3174      protected abstract boolean impl_computeContains(double localX, double localY);
    3175 3175
    3176 3176      private double min4(double v1, double v2, double v3, double v4) {
    3177 3177          return Math.min(Math.min(v1, v2), Math.min(v3, v4));
    3178 3178      }
    3179 3179
    3180 3180      private double max4(double v1, double v2, double v3, double v4) {
    3181 3181          return Math.max(Math.max(v1, v2), Math.max(v3, v4));
    3182 3182      }
    3183 3183
    3184 3184      private double min8(double v1, double v2, double v3, double v4,
    3185 3185              double v5, double v6, double v7, double v8) {
    3186 3186          return Math.min(min4(v1, v2, v3, v4), min4(v5, v6, v7, v8));
    3187 3187      }
    3188 3188
    3189 3189      private double max8(double v1, double v2, double v3, double v4,
    3190 3190              double v5, double v6, double v7, double v8) {
    3191 3191          return Math.max(max4(v1, v2, v3, v4), max4(v5, v6, v7, v8));
    3192 3192      }
    3193 3193
    3194 3194      Bounds createBoundingBox(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
    3195 3195              Point3D p5, Point3D p6, Point3D p7, Point3D p8) {
    3196 3196          double minX = min8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
    3197 3197                  p5.getX(), p6.getX(), p7.getX(), p8.getX());
    3198 3198          double maxX = max8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
    3199 3199                  p5.getX(), p6.getX(), p7.getX(), p8.getX());
    3200 3200          double minY = min8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
    3201 3201                  p5.getY(), p6.getY(), p7.getY(), p8.getY());
    3202 3202          double maxY = max8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
    3203 3203                  p5.getY(), p6.getY(), p7.getY(), p8.getY());
    3204 3204          double minZ = min8(p1.getZ(), p2.getZ(), p3.getZ(), p4.getZ(),
    3205 3205                  p5.getZ(), p6.getZ(), p7.getZ(), p8.getZ());
    3206 3206          double maxZ = max8(p1.getZ(), p2.getZ(), p3.getZ(), p4.getZ(),
    3207 3207                  p5.getZ(), p6.getZ(), p7.getZ(), p8.getZ());
    3208 3208
    3209 3209          return new BoundingBox(minX, minY, minZ, maxX - minX, maxY - minY, maxZ - minZ);
    3210 3210      }
    3211 3211
    3212 3212      Bounds createBoundingBox(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
    3213 3213          double minX = min4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
    3214 3214          double maxX = max4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
    3215 3215          double minY = min4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
    3216 3216          double maxY = max4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
    3217 3217
    3218 3218          return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
    3219 3219      }
    3220 3220
    3221 3221      /*
    3222 3222       *                   Bounds Invalidation And Notification
    3223 3223       *
    3224 3224       *  The goal of this section is to efficiently propagate bounds
    3225 3225       *  invalidation through the scenegraph while also being semantically
    3226 3226       *  correct.
    3227 3227       *
    3228 3228       *  The code path for invalidation of layout bounds is somewhat confusing
    3229 3229       *  primarily due to performance enhancements and the desire to reduce the
    3230 3230       *  number of requestLayout() calls that are performed when layout bounds
    3231 3231       *  change. Before diving into layout bounds, I will first describe how
    3232 3232       *  normal bounds invalidation occurs.
    3233 3233       *
    3234 3234       *  When a node's geometry changes (for example, if the width of a
    3235 3235       *  Rectangle is changed) then the Node must call impl_geomChanged().
    3236 3236       *  Invoking this function will eventually clear all cached bounds and
    3237 3237       *  notify to each parent up the tree that their bounds may have changed.
    3238 3238       *
    3239 3239       *  After invalidating geomBounds (and after kicking off layout bounds
    3240 3240       *  notification), impl_geomChanged calls localBoundsChanged(). It should
    3241 3241       *  be noted that impl_geomChanged should only be called when the geometry
    3242 3242       *  of the node has changed such that it may result in the geom bounds
    3243 3243       *  actually changing.
    3244 3244       *
    3245 3245       *  localBoundsChanged() simply invalidates boundsInLocal and then calls
    3246 3246       *  transformedBoundsChanged().
    3247 3247       *
    3248 3248       *  transformedBoundsChanged() is responsible for invalidating
    3249 3249       *  boundsInParent and txBounds. If the Node is not visible, then there is
    3250 3250       *  no need to notify the parent of the bounds change because the parent's
    3251 3251       *  bounds do not include invisible nodes. If the node is visible, then
    3252 3252       *  it must tell the parent that this child node's bounds have changed.
    3253 3253       *  It is up to the parent to eventually invoke its own impl_geomChanged
    3254 3254       *  function. If instead of a parent this node has a clipParent, then the
    3255 3255       *  clipParent's localBoundsChanged() is called instead.
    3256 3256       *
    3257 3257       *  There are a few other ways in which we enter the invalidate steps
    3258 3258       *  beyond just the geometry changes. If the visibility of a Node changes,
    3259 3259       *  its own bounds are not affected but its parent's bounds are. So a
    3260 3260       *  special call to parent.childVisibilityChanged is made so the parent
    3261 3261       *  can react accordingly.
    3262 3262       *
    3263 3263       *  If a transform is changed (layoutX, layoutY, rotate, transforms, etc)
    3264 3264       *  then the transform must be invalidated. When a transform is invalidated,
    3265 3265       *  it must also invalidate the txBounds by invoking
    3266 3266       *  transformedBoundsChanged, which will in turn notify the parent as
    3267 3267       *  before.
    3268 3268       *
    3269 3269       *  If an effect is changed or replaced then the local bounds must be
    3270 3270       *  invalidated, as well as the transformedBounds and the parent notified
    3271 3271       *  of the change in bounds.
    3272 3272       *
    3273 3273       *  layoutBound is somewhat unique in that it can be redefined in
    3274 3274       *  subclasses. By default, the layoutBounds is the geomBounds, and so
    3275 3275       *  whenever the impl_geomBounds() function is called the layoutBounds
    3276 3276       *  must be invalidated. However in subclasses, especially Resizables,
    3277 3277       *  the layout bounds may not be defined to be the same as the geometric
    3278 3278       *  bounds. This is both useful and provides a very nice performance
    3279 3279       *  optimization for regions and controls. In this case, subclasses
    3280 3280       *  need some way to interpose themselves such that a call to
    3281 3281       *  impl_geomChanged() *does not* invalidate the layout bounds.
    3282 3282       *
    3283 3283       *  This interposition happens by providing the
    3284 3284       *  impl_notifyLayoutBoundsChanged function. The default implementation
    3285 3285       *  simply invalidates boundsInLocal. Subclasses (such as Region and
    3286 3286       *  Control) can override this function so that it does not invalidate
    3287 3287       *  the layout bounds.
    3288 3288       *
    3289 3289       *  An on invalidate trigger on layoutBounds handles kicking off the rest
    3290 3290       *  of the invalidate process for layoutBounds. Because the layout bounds
    3291 3291       *  define the pivot point, if scaleX, scaleY, or rotate contain
    3292 3292       *  non-identity values then whenever the layoutBounds change the
    3293 3293       *  transformed bounds also change. Finally, if this node's parent is
    3294 3294       *  a Region and if the Node is being managed by the Region, then
    3295 3295       *  we must call requestLayout on the Region whenever the layout bounds
    3296 3296       *  have changed.
    3297 3297       */
    3298 3298
    3299 3299      /**
    3300 3300       * Invoked by subclasses whenever their geometric bounds have changed.
    3301 3301       * Because the default layout bounds is based on the node geometry, this
    3302 3302       * function will invoke impl_notifyLayoutBoundsChanged. The default
    3303 3303       * implementation of impl_notifyLayoutBoundsChanged() will simply invalidate
    3304 3304       * layoutBounds. Resizable subclasses will want to override this function
    3305 3305       * in most cases to be a no-op.
    3306 3306       * <p>
    3307 3307       * This function will also invalidate the cached geom bounds, and then
    3308 3308       * invoke localBoundsChanged() which will eventually end up invoking a
    3309 3309       * chain of functions up the tree to ensure that each parent of this
    3310 3310       * Node is notified that its bounds may have also changed.
    3311 3311       * <p>
    3312 3312       * This function should be treated as though it were final. It is not
    3313 3313       * intended to be overridden by subclasses.
    3314 3314       *
    3315 3315       * @treatAsPrivate implementation detail
    3316 3316       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3317 3317       */
    3318 3318      @Deprecated
    3319 3319      protected void impl_geomChanged() {
    3320 3320          if (geomBoundsInvalid) {
    3321 3321              //We need to call this even if geomBounds are already invalid.
    3322 3322              //Text is relying on this behaviour.
    3323 3323              impl_notifyLayoutBoundsChanged();
    3324 3324              return;
    3325 3325          }
    3326 3326          geomBounds.makeEmpty();
    3327 3327          geomBoundsInvalid = true;
    3328 3328          impl_markDirty(DirtyBits.NODE_BOUNDS);
    3329 3329          impl_notifyLayoutBoundsChanged();
    3330 3330          localBoundsChanged();
    3331 3331      }
    3332 3332
    3333 3333      private boolean geomBoundsInvalid = true;
    3334 3334      private boolean txBoundsInvalid = true;
    3335 3335
    3336 3336      /**
    3337 3337       * Responds to changes in the local bounds by invalidating boundsInLocal
    3338 3338       * and notifying this node that its transformed bounds have changed.
    3339 3339       */
    3340 3340      void localBoundsChanged() {
    3341 3341          invalidateBoundsInLocal();
    3342 3342          transformedBoundsChanged();
    3343 3343      }
    3344 3344
    3345 3345      /**
    3346 3346       * Responds to changes in the transformed bounds by invalidating txBounds
    3347 3347       * and boundsInParent. If this Node is not visible, then we have no need
    3348 3348       * to walk further up the tree but can instead simply invalidate state.
    3349 3349       * Otherwise, this function will notify parents (either the parent or the
    3350 3350       * clipParent) that this child Node's bounds have changed.
    3351 3351       */
    3352 3352      void transformedBoundsChanged() {
    3353 3353          if (txBoundsInvalid) {
    3354 3354              return;
    3355 3355          }
    3356 3356          txBounds.makeEmpty();
    3357 3357          txBoundsInvalid = true;
    3358 3358          invalidateBoundsInParent();
    3359 3359          if (isVisible()) {
    3360 3360              notifyParentOfBoundsChange();
    3361 3361          }
    3362 3362          impl_markDirty(DirtyBits.NODE_BOUNDS);
    3363 3363      }
    3364 3364
    3365 3365      /**
    3366 3366       * Invoked by impl_geomChanged(). Since layoutBounds is by default based
    3367 3367       * on the geometric bounds, the default implementation of this function will
    3368 3368       * invalidate the layoutBounds. Resizable Node subclasses generally base
    3369 3369       * layoutBounds on the width/height instead of the geometric bounds, and so
    3370 3370       * will generally want to override this function to be a no-op.
    3371 3371       *
    3372 3372       * @treatAsPrivate implementation detail
    3373 3373       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3374 3374       */
    3375 3375      @Deprecated
    3376 3376      protected void impl_notifyLayoutBoundsChanged() {
    3377 3377          impl_layoutBoundsChanged();
    3378 3378      }
    3379 3379
    3380 3380      /**
    3381 3381       * Notifies both the real parent and the clip parent (if they exist) that
    3382 3382       * the bounds of the child has changed. Note that since FX doesn't throw
    3383 3383       * NPE's, things actually are faster if we don't check twice for Null
    3384 3384       * (we check once, the compiler checks again)
    3385 3385       */
    3386 3386      void notifyParentOfBoundsChange() {
    3387 3387          // let the parent know which node has changed and the parent will
    3388 3388          // deal with marking itself invalid correctly
    3389 3389          Parent p = getParent();
    3390 3390          if (p != null) {
    3391 3391              p.childBoundsChanged(this);
    3392 3392          }
    3393 3393          // since the clip is used to compute the local bounds (and not the
    3394 3394          // geom bounds), we just need to notify that local bounds on the
    3395 3395          // clip parent have changed
    3396 3396          if (clipParent != null) {
    3397 3397              clipParent.localBoundsChanged();
    3398 3398          }
    3399 3399      }
    3400 3400
    3401 3401      /***************************************************************************
    3402 3402       *                                                                         *
    3403 3403       * Geometry and coordinate system related APIs. For example, methods       *
    3404 3404       * related to containment, intersection, coordinate space conversion, etc. *
    3405 3405       *                                                                         *
    3406 3406       **************************************************************************/
    3407 3407
    3408 3408      /**
    3409 3409       * Returns {@code true} if the given point (specified in the local
    3410 3410       * coordinate space of this {@code Node}) is contained within the shape of
    3411 3411       * this {@code Node}. Note that this method does not take visibility into
    3412 3412       * account; the test is based on the geometry of this {@code Node} only.
    3413 3413       */
    3414 3414      public boolean contains(double localX, double localY) {
    3415 3415          if (containsBounds(localX, localY)) {
    3416 3416              return (isPickOnBounds() || impl_computeContains(localX, localY));
    3417 3417          }
    3418 3418          return false;
    3419 3419      }
    3420 3420
    3421 3421      /**
    3422 3422       * This method only does the contains check based on the bounds, clip and
    3423 3423       * effect of this node, excluding its shape (or geometry).
    3424 3424       *
    3425 3425       * Returns true if the given point (specified in the local
    3426 3426       * coordinate space of this {@code Node}) is contained within the bounds,
    3427 3427       * clip and effect of this node.
    3428 3428       * @treatAsPrivate implementation detail
    3429 3429       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3430 3430       */
    3431 3431      @Deprecated
    3432 3432      protected boolean containsBounds(double localX, double localY) {
    3433 3433            final TempState tempState = TempState.getInstance();
    3434 3434          BaseBounds tempBounds = tempState.bounds;
    3435 3435
    3436 3436          // first, we do a quick test to see if the point is contained in
    3437 3437          // our local bounds. If so, then we will go the next step and check
    3438 3438          // the clip, effect, and geometry for containment.
    3439 3439          tempBounds = getLocalBounds(tempBounds,
    3440 3440                                      BaseTransform.IDENTITY_TRANSFORM);
    3441 3441          if (tempBounds.contains((float)localX, (float)localY)) {
    3442 3442              // if the clip is defined, then check it for containment, being
    3443 3443              // sure to convert from this node's local coordinate system
    3444 3444              // to the local coordinate system of the clip node
    3445 3445              if (getClip() != null) {
    3446 3446                  tempState.point.x = (float)localX;
    3447 3447                  tempState.point.y = (float)localY;
    3448 3448                  try {
    3449 3449                      getClip().parentToLocal(tempState.point);
    3450 3450                  } catch (NoninvertibleTransformException e) {
    3451 3451                      return false;
    3452 3452                  }
    3453 3453                  if (!getClip().contains(tempState.point.x, tempState.point.y)) {
    3454 3454                      return false;
    3455 3455                  }
    3456 3456              }
    3457 3457              // if there is an effect, then we need to check the effect
    3458 3458              // for containment as well.
    3459 3459              if (getEffect() != null) {
    3460 3460                  tempBounds = getEffect().impl_getBounds(
    3461 3461                                      tempBounds,
    3462 3462                                      BaseTransform.IDENTITY_TRANSFORM,
    3463 3463                                      this, boundsAccessor);
    3464 3464
    3465 3465                  if (!tempBounds.contains((float)localX, (float)localY)) {
    3466 3466                      return false;
    3467 3467                  }
    3468 3468              }
    3469 3469              return true;
    3470 3470          }
    3471 3471          return false;
    3472 3472      }
    3473 3473
    3474 3474      /**
    3475 3475       * Returns {@code true} if the given point (specified in the local
    3476 3476       * coordinate space of this {@code Node}) is contained within the shape of
    3477 3477       * this {@code Node}. Note that this method does not take visibility into
    3478 3478       * account; the test is based on the geometry of this {@code Node} only.
    3479 3479       */
    3480 3480      public boolean contains(Point2D localPoint) {
    3481 3481          return contains(localPoint.getX(), localPoint.getY());
    3482 3482      }
    3483 3483
    3484 3484      /**
    3485 3485       * Returns {@code true} if the given rectangle (specified in the local
    3486 3486       * coordinate space of this {@code Node}) intersects the shape of this
    3487 3487       * {@code Node}. Note that this method does not take visibility into
    3488 3488       * account; the test is based on the geometry of this {@code Node} only.
    3489 3489       * The default behavior of this function is simply to check if the
    3490 3490       * given coordinates intersect with the local bounds.
    3491 3491       */
    3492 3492      public boolean intersects(double localX, double localY, double localWidth, double localHeight) {
    3493 3493          BaseBounds tempBounds = TempState.getInstance().bounds;
    3494 3494          tempBounds = getLocalBounds(tempBounds,
    3495 3495                                      BaseTransform.IDENTITY_TRANSFORM);
    3496 3496          return tempBounds.intersects((float)localX,
    3497 3497                                       (float)localY,
    3498 3498                                       (float)localWidth,
    3499 3499                                       (float)localHeight);
    3500 3500      }
    3501 3501
    3502 3502      /**
    3503 3503       * Returns {@code true} if the given bounds (specified in the local
    3504 3504       * coordinate space of this {@code Node}) intersects the shape of this
    3505 3505       * {@code Node}. Note that this method does not take visibility into
    3506 3506       * account; the test is based on the geometry of this {@code Node} only.
    3507 3507       * The default behavior of this function is simply to check if the
    3508 3508       * given coordinates intersect with the local bounds.
    3509 3509       */
    3510 3510      public boolean intersects(Bounds localBounds) {
    3511 3511          return intersects(localBounds.getMinX(), localBounds.getMinY(), localBounds.getWidth(), localBounds.getHeight());
    3512 3512      }
    3513 3513
    3514 3514      /**
    3515 3515       * Transforms a point from the coordinate space of the {@link Scene}
    3516 3516       * into the local coordinate space of this {@code Node}.
    3517 3517       */
    3518 3518      public Point2D sceneToLocal(double sceneX, double sceneY) {
    3519 3519          final com.sun.javafx.geom.Point2D tempPt =
    3520 3520                  TempState.getInstance().point;
    3521 3521          tempPt.setLocation((float)sceneX, (float)sceneY);
    3522 3522          try {
    3523 3523              sceneToLocal(tempPt);
    3524 3524          } catch (NoninvertibleTransformException e) {
    3525 3525              return null;
    3526 3526          }
    3527 3527          return new Point2D(tempPt.x, tempPt.y);
    3528 3528      }
    3529 3529
    3530 3530      /**
    3531 3531       * Transforms a point from the coordinate space of the {@link javafx.scene.Scene}
    3532 3532       * into the local coordinate space of this {@code Node}.
    3533 3533       */
    3534 3534      public Point2D sceneToLocal(Point2D scenePoint) {
    3535 3535          return sceneToLocal(scenePoint.getX(), scenePoint.getY());
    3536 3536      }
    3537 3537
    3538 3538      // Why is this method private?
    3539 3539      private Point3D sceneToLocal(double x, double y, double z) {
    3540 3540          final com.sun.javafx.geom.Vec3d tempV3D =
    3541 3541                  TempState.getInstance().vec3d;
    3542 3542          tempV3D.set(x, y, z);
    3543 3543          try {
    3544 3544              sceneToLocal(tempV3D);
    3545 3545          } catch (NoninvertibleTransformException e) {
    3546 3546              return null;
    3547 3547          }
    3548 3548          return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
    3549 3549      }
    3550 3550
    3551 3551      /**
    3552 3552       * Transforms a rectangle from the coordinate space of the
    3553 3553       * {@link javafx.scene.Scene} into the local coordinate space of this
    3554 3554       * {@code Node}.
    3555 3555       */
    3556 3556      public Bounds sceneToLocal(Bounds sceneBounds) {
    3557 3557          // Do a quick update of localToParentTransform so that we can determine
    3558 3558          // if this tx is 2D transform
    3559 3559          updateLocalToParentTransform();
    3560 3560          if (localToParentTx.is2D() && (sceneBounds.getMinZ() == 0) && (sceneBounds.getMaxZ() == 0)) {
    3561 3561              Point2D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY());
    3562 3562              Point2D p2 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY());
    3563 3563              Point2D p3 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY());
    3564 3564              Point2D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY());
    3565 3565
    3566 3566              return createBoundingBox(p1, p2, p3, p4);
    3567 3567          }
    3568 3568          Point3D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
    3569 3569          Point3D p2 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
    3570 3570          Point3D p3 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
    3571 3571          Point3D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
    3572 3572          Point3D p5 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
    3573 3573          Point3D p6 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
    3574 3574          Point3D p7 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
    3575 3575          Point3D p8 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
    3576 3576          return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
    3577 3577      }
    3578 3578
    3579 3579      /**
    3580 3580       * Transforms a point from the local coordinate space of this {@code Node}
    3581 3581       * into the coordinate space of its {@link javafx.scene.Scene}.
    3582 3582       */
    3583 3583      public Point2D localToScene(double localX, double localY) {
    3584 3584          final com.sun.javafx.geom.Point2D tempPt =
    3585 3585                  TempState.getInstance().point;
    3586 3586          tempPt.setLocation((float)localX, (float)localY);
    3587 3587          localToScene(tempPt);
    3588 3588          return new Point2D(tempPt.x, tempPt.y);
    3589 3589      }
    3590 3590
    3591 3591      /**
    3592 3592       * Transforms a point from the local coordinate space of this {@code Node}
    3593 3593       * into the coordinate space of its {@link javafx.scene.Scene}.
    3594 3594       */
    3595 3595      public Point2D localToScene(Point2D localPoint) {
    3596 3596          return localToScene(localPoint.getX(), localPoint.getY());
    3597 3597      }
    3598 3598
    3599 3599      private Point3D localToScene(double x, double y, double z) {
    3600 3600          final com.sun.javafx.geom.Vec3d tempV3D =
    3601 3601                  TempState.getInstance().vec3d;
    3602 3602          tempV3D.set(x, y, z);
    3603 3603          localToScene(tempV3D);
    3604 3604          return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
    3605 3605      }
    3606 3606
    3607 3607      /**
    3608 3608       * Transforms a bounds from the local coordinate space of this
    3609 3609       * {@code Node} into the coordinate space of its {@link javafx.scene.Scene}.
    3610 3610       */
    3611 3611      public Bounds localToScene(Bounds localBounds) {
    3612 3612          // Do a quick update of localToParentTransform so that we can determine
    3613 3613          // if this tx is 2D transform
    3614 3614          updateLocalToParentTransform();
    3615 3615          if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
    3616 3616              Point2D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY());
    3617 3617              Point2D p2 = localToScene(localBounds.getMaxX(), localBounds.getMinY());
    3618 3618              Point2D p3 = localToScene(localBounds.getMaxX(), localBounds.getMaxY());
    3619 3619              Point2D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY());
    3620 3620
    3621 3621              return createBoundingBox(p1, p2, p3, p4);
    3622 3622          }
    3623 3623          Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
    3624 3624          Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
    3625 3625          Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
    3626 3626          Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
    3627 3627          Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
    3628 3628          Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
    3629 3629          Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
    3630 3630          Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
    3631 3631          return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
    3632 3632
    3633 3633      }
    3634 3634
    3635 3635      /**
    3636 3636       * Transforms a point from the coordinate space of the parent into the
    3637 3637       * local coordinate space of this {@code Node}.
    3638 3638       */
    3639 3639      public Point2D parentToLocal(double parentX, double parentY) {
    3640 3640          final com.sun.javafx.geom.Point2D tempPt =
    3641 3641                  TempState.getInstance().point;
    3642 3642          tempPt.setLocation((float)parentX, (float)parentY);
    3643 3643          try {
    3644 3644              parentToLocal(tempPt);
    3645 3645          } catch (NoninvertibleTransformException e) {
    3646 3646              return null;
    3647 3647          }
    3648 3648          return new Point2D(tempPt.x, tempPt.y);
    3649 3649      }
    3650 3650
    3651 3651      /**
    3652 3652       * Transforms a point from the coordinate space of the parent into the
    3653 3653       * local coordinate space of this {@code Node}.
    3654 3654       */
    3655 3655      public Point2D parentToLocal(Point2D parentPoint) {
    3656 3656          return parentToLocal(parentPoint.getX(), parentPoint.getY());
    3657 3657      }
    3658 3658
    3659 3659      private Point3D parentToLocal(double x, double y, double z) {
    3660 3660          final com.sun.javafx.geom.Vec3d tempV3D =
    3661 3661                  TempState.getInstance().vec3d;
    3662 3662          tempV3D.set(x, y, z);
    3663 3663          try {
    3664 3664              parentToLocal(tempV3D);
    3665 3665          } catch (NoninvertibleTransformException e) {
    3666 3666              return null;
    3667 3667          }
    3668 3668          return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
    3669 3669      }
    3670 3670
    3671 3671      /**
    3672 3672       * Transforms a rectangle from the coordinate space of the parent into the
    3673 3673       * local coordinate space of this {@code Node}.
    3674 3674       */
    3675 3675      public Bounds parentToLocal(Bounds parentBounds) {
    3676 3676          // Do a quick update of localToParentTransform so that we can determine
    3677 3677          // if this tx is 2D transform
    3678 3678          updateLocalToParentTransform();
    3679 3679          if (localToParentTx.is2D() && (parentBounds.getMinZ() == 0) && (parentBounds.getMaxZ() == 0)) {
    3680 3680              Point2D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY());
    3681 3681              Point2D p2 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY());
    3682 3682              Point2D p3 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY());
    3683 3683              Point2D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY());
    3684 3684
    3685 3685              return createBoundingBox(p1, p2, p3, p4);
    3686 3686          }
    3687 3687          Point3D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMinZ());
    3688 3688          Point3D p2 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMaxZ());
    3689 3689          Point3D p3 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMinZ());
    3690 3690          Point3D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
    3691 3691          Point3D p5 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMinZ());
    3692 3692          Point3D p6 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
    3693 3693          Point3D p7 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMinZ());
    3694 3694          Point3D p8 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMaxZ());
    3695 3695          return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
    3696 3696      }
    3697 3697
    3698 3698      /**
    3699 3699       * Transforms a point from the local coordinate space of this {@code Node}
    3700 3700       * into the coordinate space of its parent.
    3701 3701       */
    3702 3702      public Point2D localToParent(double localX, double localY) {
    3703 3703          final com.sun.javafx.geom.Point2D tempPt =
    3704 3704                  TempState.getInstance().point;
    3705 3705          tempPt.setLocation((float)localX, (float)localY);
    3706 3706          localToParent(tempPt);
    3707 3707          return new Point2D(tempPt.x, tempPt.y);
    3708 3708      }
    3709 3709
    3710 3710      /**
    3711 3711       * Transforms a point from the local coordinate space of this {@code Node}
    3712 3712       * into the coordinate space of its parent.
    3713 3713       */
    3714 3714      public Point2D localToParent(Point2D localPoint) {
    3715 3715          return localToParent(localPoint.getX(), localPoint.getY());
    3716 3716      }
    3717 3717
    3718 3718      private Point3D localToParent(double x, double y, double z) {
    3719 3719          final com.sun.javafx.geom.Vec3d tempV3D =
    3720 3720                  TempState.getInstance().vec3d;
    3721 3721          tempV3D.set(x, y, z);
    3722 3722          localToParent(tempV3D);
    3723 3723          return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
    3724 3724      }
    3725 3725
    3726 3726      /**
    3727 3727       * Transforms a bounds from the local coordinate space of this
    3728 3728       * {@code Node} into the coordinate space of its parent.
    3729 3729       */
    3730 3730      public Bounds localToParent(Bounds localBounds) {
    3731 3731          // Do a quick update of localToParentTransform so that we can determine
    3732 3732          // if this tx is 2D transform
    3733 3733          updateLocalToParentTransform();
    3734 3734          if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
    3735 3735              Point2D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY());
    3736 3736              Point2D p2 = localToParent(localBounds.getMaxX(), localBounds.getMinY());
    3737 3737              Point2D p3 = localToParent(localBounds.getMaxX(), localBounds.getMaxY());
    3738 3738              Point2D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY());
    3739 3739
    3740 3740              return createBoundingBox(p1, p2, p3, p4);
    3741 3741          }
    3742 3742          Point3D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
    3743 3743          Point3D p2 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
    3744 3744          Point3D p3 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
    3745 3745          Point3D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
    3746 3746          Point3D p5 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
    3747 3747          Point3D p6 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
    3748 3748          Point3D p7 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
    3749 3749          Point3D p8 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
    3750 3750          return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
    3751 3751      }
    3752 3752
    3753 3753      /**
    3754 3754       * Copy the localToParent transform into specified transform.
    3755 3755       */
    3756 3756      BaseTransform getLocalToParentTransform(BaseTransform tx) {
    3757 3757          updateLocalToParentTransform();
    3758 3758          tx.setTransform(localToParentTx);
    3759 3759          return tx;
    3760 3760      }
    3761 3761
    3762 3762      /**
    3763 3763       * Currently used only by AnimationPath.createAnimationPathHelper().
    3764 3764       * @treatAsPrivate implementation detail
    3765 3765       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3766 3766       */
    3767 3767      @Deprecated
    3768 3768      public final BaseTransform impl_getLeafTransform() {
    3769 3769          return getLocalToParentTransform(TempState.getInstance().leafTx);
    3770 3770      }
    3771 3771
    3772 3772      /**
    3773 3773       * Invoked whenever the transforms[] ObservableList changes, or by the transforms
    3774 3774       * in that ObservableList whenever they are changed.
    3775 3775       * @treatAsPrivate implementation detail
    3776 3776       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3777 3777       */
    3778 3778      @Deprecated
    3779 3779      public final void impl_transformsChanged() {
    3780 3780          if (!transformDirty) {
    3781 3781              impl_markDirty(DirtyBits.NODE_TRANSFORM);
    3782 3782              transformDirty = true;
    3783 3783              transformedBoundsChanged();
    3784 3784          }
    3785 3785          invalidateLocalToParentTransform();
    3786 3786          invalidateLocalToSceneTransform();
    3787 3787      }
    3788 3788
    3789 3789      /**
    3790 3790       * @treatAsPrivate implementation detail
    3791 3791       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3792 3792       */
    3793 3793      @Deprecated
    3794 3794      public final double impl_getPivotX() {
    3795 3795          final Bounds bounds = getLayoutBounds();
    3796 3796          return bounds.getMinX() + bounds.getWidth()/2;
    3797 3797      }
    3798 3798
    3799 3799      /**
    3800 3800       * @treatAsPrivate implementation detail
    3801 3801       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3802 3802       */
    3803 3803      @Deprecated
    3804 3804      public final double impl_getPivotY() {
    3805 3805          final Bounds bounds = getLayoutBounds();
    3806 3806          return bounds.getMinY() + bounds.getHeight()/2;
    3807 3807      }
    3808 3808
    3809 3809      /**
    3810 3810       * @treatAsPrivate implementation detail
    3811 3811       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3812 3812       */
    3813 3813      @Deprecated
    3814 3814      public final double impl_getPivotZ() {
    3815 3815          final Bounds bounds = getLayoutBounds();
    3816 3816          return bounds.getMinZ() + bounds.getDepth()/2;
    3817 3817      }
    3818 3818
    3819 3819      /**
    3820 3820       * This helper function will update the transform matrix on the peer based
    3821 3821       * on the "complete" transform for this node.
    3822 3822       */
    3823 3823      void updateLocalToParentTransform() {
    3824 3824          if (transformDirty) {
    3825 3825              localToParentTx.setToIdentity();
    3826 3826              if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) {
    3827 3827                  // recompute pivotX, pivotY and pivotZ
    3828 3828                  double pivotX = impl_getPivotX();
    3829 3829                  double pivotY = impl_getPivotY();
    3830 3830                  double pivotZ = impl_getPivotZ();
    3831 3831                  localToParentTx.translate(getTranslateX() + getLayoutX() + pivotX, getTranslateY() + getLayoutY() + pivotY, getTranslateZ() + pivotZ);
    3832 3832                  localToParentTx.rotate(Math.toRadians(getRotate()), getRotationAxis().getX(), getRotationAxis().getY(), getRotationAxis().getZ());
    3833 3833                  localToParentTx.scale(getScaleX(), getScaleY(), getScaleZ());
    3834 3834                  localToParentTx.translate(-pivotX, -pivotY, -pivotZ);
    3835 3835              } else {
    3836 3836                  localToParentTx.translate(getTranslateX() + getLayoutX(), getTranslateY() + getLayoutY(), getTranslateZ());
    3837 3837              }
    3838 3838
    3839 3839              if (impl_hasTransforms()) {
    3840 3840                  for (Transform t : getTransforms()) {
    3841 3841                      t.impl_apply(localToParentTx);
    3842 3842                  }
    3843 3843              }
    3844 3844
    3845 3845              transformDirty = false;
    3846 3846          }
    3847 3847      }
    3848 3848
    3849 3849      /**
    3850 3850       * Transforms in place the specified point from parent coords to local
    3851 3851       * coords. Made package private for the sake of testing.
    3852 3852       */
    3853 3853      void parentToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
    3854 3854          updateLocalToParentTransform();
    3855 3855          localToParentTx.inverseTransform(pt, pt);
    3856 3856      }
    3857 3857
    3858 3858      void parentToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
    3859 3859          updateLocalToParentTransform();
    3860 3860          localToParentTx.inverseTransform(pt, pt);
    3861 3861      }
    3862 3862
    3863 3863      void sceneToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
    3864 3864          if (getParent() != null) {
    3865 3865              getParent().sceneToLocal(pt);
    3866 3866          }
    3867 3867          parentToLocal(pt);
    3868 3868      }
    3869 3869
    3870 3870      void sceneToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
    3871 3871          if (getParent() != null) {
    3872 3872              getParent().sceneToLocal(pt);
    3873 3873          }
    3874 3874          parentToLocal(pt);
    3875 3875      }
    3876 3876
    3877 3877      void localToScene(com.sun.javafx.geom.Point2D pt) {
    3878 3878          localToParent(pt);
    3879 3879          if (getParent() != null) {
    3880 3880              getParent().localToScene(pt);
    3881 3881          }
    3882 3882      }
    3883 3883
    3884 3884      void localToScene(com.sun.javafx.geom.Vec3d pt) {
    3885 3885          localToParent(pt);
    3886 3886          if (getParent() != null) {
    3887 3887              getParent().localToScene(pt);
    3888 3888          }
    3889 3889      }
    3890 3890
    3891 3891      /***************************************************************************
    3892 3892       *                                                                         *
    3893 3893       * Mouse event related APIs                                                *
    3894 3894       *                                                                         *
    3895 3895       **************************************************************************/
    3896 3896
    3897 3897      /**
    3898 3898       * Transforms in place the specified point from local coords to parent
    3899 3899       * coords. Made package private for the sake of testing.
    3900 3900       */
    3901 3901      void localToParent(com.sun.javafx.geom.Point2D pt) {
    3902 3902          updateLocalToParentTransform();
    3903 3903          localToParentTx.transform(pt, pt);
    3904 3904      }
    3905 3905
    3906 3906      void localToParent(com.sun.javafx.geom.Vec3d pt) {
    3907 3907          updateLocalToParentTransform();
    3908 3908          localToParentTx.transform(pt, pt);
    3909 3909      }
    3910 3910
    3911 3911      /**
    3912 3912       * Finds a top-most child node that contains the given coordinates.
    3913 3913       *
    3914 3914       * Returns the picked node, null if no such node was found.
    3915 3915       * @treatAsPrivate implementation detail
    3916 3916       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3917 3917       */
    3918 3918      @Deprecated
    3919 3919      public final Node impl_pickNode(double parentX, double parentY) {
    3920 3920
    3921 3921          // In some conditions we can omit picking this node or subgraph
    3922 3922          if (!isVisible() || isDisable() || isMouseTransparent()) {
    3923 3923              return null;
    3924 3924          }
    3925 3925
    3926 3926          final com.sun.javafx.geom.Point2D tempPt =
    3927 3927                  TempState.getInstance().point;
    3928 3928
    3929 3929          // convert to local coordinates
    3930 3930          tempPt.setLocation((float)parentX, (float)parentY);
    3931 3931          try {
    3932 3932              parentToLocal(tempPt);
    3933 3933          } catch (NoninvertibleTransformException e) {
    3934 3934              return null;
    3935 3935          }
    3936 3936
    3937 3937          // Delegate to a function which can be overridden by subclasses which
    3938 3938          // actually does the pick. The implementation is markedly different
    3939 3939          // for leaf nodes vs. parent nodes vs. region nodes.
    3940 3940          return impl_pickNodeLocal(tempPt.x, tempPt.y);
    3941 3941      }
    3942 3942
    3943 3943      /**
    3944 3944       * Finds a top-most child node that contains the given local coordinates.
    3945 3945       *
    3946 3946       * Returns the picked node, null if no such node was found.
    3947 3947       * @treatAsPrivate implementation detail
    3948 3948       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3949 3949       */
    3950 3950      @Deprecated
    3951 3951      protected Node impl_pickNodeLocal(double localX, double localY) {
    3952 3952          if (contains(localX, localY)) {
    3953 3953              return this;
    3954 3954          }
    3955 3955          return null;
    3956 3956      }
    3957 3957
    3958 3958      /**
    3959 3959       * Finds a top-most child node that contains the given local coordinates.
    3960 3960       *
    3961 3961       * Returns the picked node, null if no such node was found.
    3962 3962       * @treatAsPrivate implementation detail
    3963 3963       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3964 3964       */
    3965 3965      @Deprecated
    3966 3966      protected Node impl_pickNodeLocal(PickRay localPickRay) {
    3967 3967          if (impl_intersects(localPickRay)) {
    3968 3968              return this;
    3969 3969          }
    3970 3970          return null;
    3971 3971      }
    3972 3972
    3973 3973      /**
    3974 3974       * Finds a top-most child node that intersects the given ray.
    3975 3975       *
    3976 3976       * Returns the picked node, null if no such node was found.
    3977 3977       * @treatAsPrivate implementation detail
    3978 3978       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    3979 3979       */
    3980 3980      @Deprecated
    3981 3981      public final Node impl_pickNode(PickRay pickRay) {
    3982 3982
    3983 3983          // In some conditions we can omit picking this node or subgraph
    3984 3984          if (!isVisible() || isDisable() || isMouseTransparent()) {
    3985 3985              return null;
    3986 3986          }
    3987 3987
    3988 3988          final BaseTransform tempPickTx = TempState.getInstance().pickTx;
    3989 3989
    3990 3990          getLocalToParentTransform(tempPickTx);
    3991 3991          try {
    3992 3992              tempPickTx.invert();
    3993 3993          } catch (NoninvertibleTransformException e) {
    3994 3994              throw new RuntimeException(e);
    3995 3995          }
    3996 3996          PickRay localPickRay = pickRay.copy();
    3997 3997          localPickRay.transform(tempPickTx);
    3998 3998
    3999 3999          // Delegate to a function which can be overridden by subclasses which
    4000 4000          // actually does the pick. The implementation is markedly different
    4001 4001          // for leaf nodes vs. parent nodes vs. region nodes.
    4002 4002          return impl_pickNodeLocal(localPickRay);
    4003 4003      }
    4004 4004
    4005 4005      /**
    4006 4006       * Returns {@code true} if the given ray (start, dir), specified in the
    4007 4007       * local coordinate space of this {@code Node}, intersects the
    4008 4008       * shape of this {@code Node}. Note that this method does not take visibility
    4009 4009       * into account; the test is based on the geometry of this {@code Node} only.
    4010 4010       * <p>
    4011 4011       * Note that this is a conditional feature. See
    4012 4012       * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
    4013 4013       * for more information.
    4014 4014       *
    4015 4015       * @treatAsPrivate implementation detail
    4016 4016       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    4017 4017       */
    4018 4018      @Deprecated
    4019 4019      protected final boolean impl_intersects(PickRay pickRay) {
    4020 4020          double origZ = pickRay.getOriginNoClone().z;
    4021 4021          double dirZ = pickRay.getDirectionNoClone().z;
    4022 4022          // Handle the case where pickRay is almost parallel to the Z-plane
    4023 4023          if (almostZero(dirZ)) {
    4024 4024              return false;
    4025 4025          }
    4026 4026          double t = -origZ / dirZ;
    4027 4027          double x = pickRay.getOriginNoClone().x + (pickRay.getDirectionNoClone().x * t);
    4028 4028          double y = pickRay.getOriginNoClone().y + (pickRay.getDirectionNoClone().y * t);
    4029 4029          return contains((float) x, (float) y); // was contentContains, the difference is that effect / clip are included
    4030 4030      }
    4031 4031
    4032 4032      // Good to find a home for commonly use util. code such as EPS.
    4033 4033      // and almostZero. This code currently defined in multiple places,
    4034 4034      // such as Affine3D and GeneralTransform3D.
    4035 4035      private static final double EPSILON_ABSOLUTE = 1.0e-5;
    4036 4036
    4037 4037      static boolean almostZero(double a) {
    4038 4038          return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
    4039 4039      }
    4040 4040      /***************************************************************************
    4041 4041       *                                                                         *
    4042 4042       *                             Transformations                             *
    4043 4043       *                                                                         *
    4044 4044       **************************************************************************/
    4045 4045      /**
    4046 4046       * Defines the ObservableList of {@link javafx.scene.transform.Transform} objects
    4047 4047       * to be applied to this {@code Node}. This ObservableList of transforms is applied
    4048 4048       * before {@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #scaleXProperty scaleX}, and
    4049 4049       * {@link #scaleYProperty scaleY}, {@link #rotateProperty rotate} transforms.
    4050 4050       *
    4051 4051       * @defaultValue empty
    4052 4052       */
    4053 4053      public final ObservableList<Transform> getTransforms() {
    4054 4054          return transformsProperty();
    4055 4055      }
    4056 4056
    4057 4057      private ObservableList<Transform> transformsProperty() {
    4058 4058          return getNodeTransformation().getTransforms();
    4059 4059      }
    4060 4060
    4061 4061      public final void setTranslateX(double value) {
    4062 4062          translateXProperty().set(value);
    4063 4063      }
    4064 4064
    4065 4065      public final double getTranslateX() {
    4066 4066          return (nodeTransformation == null)
    4067 4067                  ? DEFAULT_TRANSLATE_X
    4068 4068                  : nodeTransformation.getTranslateX();
    4069 4069      }
    4070 4070
    4071 4071      /**
    4072 4072       * Defines the x coordinate of the translation that is added to this {@code Node}'s
    4073 4073       * transform.
    4074 4074       * <p>
    4075 4075       * The node's final translation will be computed as {@link #layoutXProperty layoutX} + {@code translateX},
    4076 4076       * where {@code layoutX} establishes the node's stable position and {@code translateX}
    4077 4077       * optionally makes dynamic adjustments to that position.
    4078 4078       *<p>
    4079 4079       * This variable can be used to alter the location of a node without disturbing
    4080 4080       * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
    4081 4081       *
    4082 4082       * @defaultValue 0
    4083 4083       */
    4084 4084      public final DoubleProperty translateXProperty() {
    4085 4085          return getNodeTransformation().translateXProperty();
    4086 4086      }
    4087 4087
    4088 4088      public final void setTranslateY(double value) {
    4089 4089          translateYProperty().set(value);
    4090 4090      }
    4091 4091
    4092 4092      public final double getTranslateY() {
    4093 4093          return (nodeTransformation == null)
    4094 4094                  ? DEFAULT_TRANSLATE_Y
    4095 4095                  : nodeTransformation.getTranslateY();
    4096 4096      }
    4097 4097
    4098 4098      /**
    4099 4099       * Defines the y coordinate of the translation that is added to this {@code Node}'s
    4100 4100       * transform.
    4101 4101       * <p>
    4102 4102       * The node's final translation will be computed as {@link #layoutYProperty layoutY} + {@code translateY},
    4103 4103       * where {@code layoutY} establishes the node's stable position and {@code translateY}
    4104 4104       * optionally makes dynamic adjustments to that position.
    4105 4105       *<p>
    4106 4106       * This variable can be used to alter the location of a node without disturbing
    4107 4107       * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
    4108 4108       *
    4109 4109       * @defaultValue 0
    4110 4110       */
    4111 4111      public final DoubleProperty translateYProperty() {
    4112 4112          return getNodeTransformation().translateYProperty();
    4113 4113      }
    4114 4114
    4115 4115      public final void setTranslateZ(double value) {
    4116 4116          translateZProperty().set(value);
    4117 4117      }
    4118 4118
    4119 4119      public final double getTranslateZ() {
    4120 4120          return (nodeTransformation == null)
    4121 4121                  ? DEFAULT_TRANSLATE_Z
    4122 4122                  : nodeTransformation.getTranslateZ();
    4123 4123      }
    4124 4124
    4125 4125      /**
    4126 4126       * Defines the Z coordinate of the translation that is added to the
    4127 4127       * transformed coordinates of this {@code Node}.  This value will be added
    4128 4128       * to any translation defined by the {@code transforms} ObservableList and
    4129 4129       * {@code layoutZ}.
    4130 4130       *<p>
    4131 4131       * This variable can be used to alter the location of a Node without
    4132 4132       * disturbing its layout bounds, which makes it useful for animating a
    4133 4133       * node's location.
    4134 4134       * <p>
    4135 4135       * Note that this is a conditional feature. See
    4136 4136       * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
    4137 4137       * for more information.
    4138 4138       *
    4139 4139       * @defaultValue 0
    4140 4140       * @since JavaFX 1.3
    4141 4141       */
    4142 4142      public final DoubleProperty translateZProperty() {
    4143 4143          return getNodeTransformation().translateZProperty();
    4144 4144      }
    4145 4145
    4146 4146      public final void setScaleX(double value) {
    4147 4147          scaleXProperty().set(value);
    4148 4148      }
    4149 4149
    4150 4150      public final double getScaleX() {
    4151 4151          return (nodeTransformation == null) ? DEFAULT_SCALE_X
    4152 4152                                              : nodeTransformation.getScaleX();
    4153 4153      }
    4154 4154
    4155 4155      /**
    4156 4156       * Defines the factor by which coordinates are scaled about the center of the
    4157 4157       * object along the X axis of this {@code Node}. This is used to stretch or
    4158 4158       * animate the node either manually or by using an animation.
    4159 4159       * <p>
    4160 4160       * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
    4161 4161       * default, which makes it ideal for scaling the entire node after
    4162 4162       * all effects and transforms have been taken into account.
    4163 4163       * <p>
    4164 4164       * The pivot point about which the scale occurs is the center of the
    4165 4165       * untransformed {@link #layoutBoundsProperty layoutBounds}.
    4166 4166       *
    4167 4167       * @defaultValue 1.0
    4168 4168       */
    4169 4169      public final DoubleProperty scaleXProperty() {
    4170 4170          return getNodeTransformation().scaleXProperty();
    4171 4171      }
    4172 4172
    4173 4173      public final void setScaleY(double value) {
    4174 4174          scaleYProperty().set(value);
    4175 4175      }
    4176 4176
    4177 4177      public final double getScaleY() {
    4178 4178          return (nodeTransformation == null) ? DEFAULT_SCALE_Y
    4179 4179                                              : nodeTransformation.getScaleY();
    4180 4180      }
    4181 4181
    4182 4182      /**
    4183 4183       * Defines the factor by which coordinates are scaled about the center of the
    4184 4184       * object along the Y axis of this {@code Node}. This is used to stretch or
    4185 4185       * animate the node either manually or by using an animation.
    4186 4186       * <p>
    4187 4187       * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
    4188 4188       * default, which makes it ideal for scaling the entire node after
    4189 4189       * all effects and transforms have been taken into account.
    4190 4190       * <p>
    4191 4191       * The pivot point about which the scale occurs is the center of the
    4192 4192       * untransformed {@link #layoutBoundsProperty layoutBounds}.
    4193 4193       *
    4194 4194       * @defaultValue 1.0
    4195 4195       */
    4196 4196      public final DoubleProperty scaleYProperty() {
    4197 4197          return getNodeTransformation().scaleYProperty();
    4198 4198      }
    4199 4199
    4200 4200      public final void setScaleZ(double value) {
    4201 4201          scaleZProperty().set(value);
    4202 4202      }
    4203 4203
    4204 4204      public final double getScaleZ() {
    4205 4205          return (nodeTransformation == null) ? DEFAULT_SCALE_Z
    4206 4206                                              : nodeTransformation.getScaleZ();
    4207 4207      }
    4208 4208
    4209 4209      /**
    4210 4210       * Defines the factor by which coordinates are scaled about the center of the
    4211 4211       * object along the Z axis of this {@code Node}. This is used to stretch or
    4212 4212       * animate the node either manually or by using an animation.
    4213 4213       * <p>
    4214 4214       * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
    4215 4215       * default, which makes it ideal for scaling the entire node after
    4216 4216       * all effects and transforms have been taken into account.
    4217 4217       * <p>
    4218 4218       * The pivot point about which the scale occurs is the center of the
    4219 4219       * rectangular bounds formed by taking {@link #boundsInLocalProperty boundsInLocal} and applying
    4220 4220       * all the transforms in the {@link #getTransforms transforms} ObservableList.
    4221 4221       * <p>
    4222 4222       * Note that this is a conditional feature. See
    4223 4223       * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
    4224 4224       * for more information.
    4225 4225       *
    4226 4226       * @defaultValue 1.0
    4227 4227       * @since JavaFX 1.3
    4228 4228       */
    4229 4229      public final DoubleProperty scaleZProperty() {
    4230 4230          return getNodeTransformation().scaleZProperty();
    4231 4231      }
    4232 4232
    4233 4233      public final void setRotate(double value) {
    4234 4234          rotateProperty().set(value);
    4235 4235      }
    4236 4236
    4237 4237      public final double getRotate() {
    4238 4238          return (nodeTransformation == null) ? DEFAULT_ROTATE
    4239 4239                                              : nodeTransformation.getRotate();
    4240 4240      }
    4241 4241
    4242 4242      /**
    4243 4243       * Defines the angle of rotation about the {@code Node}'s center, measured in
    4244 4244       * degrees. This is used to rotate the {@code Node}.
    4245 4245       * <p>
    4246 4246       * This rotation factor is not included in {@link #layoutBoundsProperty layoutBounds} by
    4247 4247       * default, which makes it ideal for rotating the entire node after
    4248 4248       * all effects and transforms have been taken into account.
    4249 4249       * <p>
    4250 4250       * The pivot point about which the rotation occurs is the center of the
    4251 4251       * untransformed {@link #layoutBoundsProperty layoutBounds}.
    4252 4252       * <p>
    4253 4253       * Note that because the pivot point is computed as the center of this
    4254 4254       * {@code Node}'s layout bounds, any change to the layout bounds will cause
    4255 4255       * the pivot point to change, which can move the object. For a leaf node,
    4256 4256       * any change to the geometry will cause the layout bounds to change.
    4257 4257       * For a group node, any change to any of its children, including a
    4258 4258       * change in a child's geometry, clip, effect, position, orientation, or
    4259 4259       * scale, will cause the group's layout bounds to change. If this movement
    4260 4260       * of the pivot point is not
    4261 4261       * desired, applications should instead use the Node's {@link #getTransforms transforms}
    4262 4262       * ObservableList, and add a {@link javafx.scene.transform.Rotate} transform,
    4263 4263       * which has a user-specifiable pivot point.
    4264 4264       *
    4265 4265       * @defaultValue 0.0
    4266 4266       */
    4267 4267      public final DoubleProperty rotateProperty() {
    4268 4268          return getNodeTransformation().rotateProperty();
    4269 4269      }
    4270 4270
    4271 4271      public final void setRotationAxis(Point3D value) {
    4272 4272          rotationAxisProperty().set(value);
    4273 4273      }
    4274 4274
    4275 4275      public final Point3D getRotationAxis() {
    4276 4276          return (nodeTransformation == null)
    4277 4277                  ? DEFAULT_ROTATION_AXIS
    4278 4278                  : nodeTransformation.getRotationAxis();
    4279 4279      }
    4280 4280
    4281 4281      /**
    4282 4282       * Defines the axis of rotation of this {@code Node}.
    4283 4283       * <p>
    4284 4284       * Note that this is a conditional feature. See
    4285 4285       * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
    4286 4286       * for more information.
    4287 4287       *
    4288 4288       * @defaultValue Rotate.Z_AXIS
    4289 4289       * @since JavaFX 1.3
    4290 4290       */
    4291 4291      public final ObjectProperty<Point3D> rotationAxisProperty() {
    4292 4292          return getNodeTransformation().rotationAxisProperty();
    4293 4293      }
    4294 4294
    4295 4295      /**
    4296 4296       * An affine transform that holds the computed local-to-parent transform.
    4297 4297       * This is the concatenation of all transforms in this node, including all
    4298 4298       * of the convenience transforms.
    4299 4299       * @since 2.2
    4300 4300       */
    4301 4301      public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
    4302 4302          return getNodeTransformation().localToParentTransformProperty();
    4303 4303      }
    4304 4304
    4305 4305      private void invalidateLocalToParentTransform() {
    4306 4306          if (nodeTransformation != null) {
    4307 4307              nodeTransformation.invalidateLocalToParentTransform();
    4308 4308          }
    4309 4309      }
    4310 4310
    4311 4311      public final Transform getLocalToParentTransform() {
    4312 4312          return localToParentTransformProperty().get();
    4313 4313      }
    4314 4314
    4315 4315      /**
    4316 4316       * An affine transform that holds the computed local-to-scene transform.
    4317 4317       * This is the concatenation of all transforms in this node's parents and
    4318 4318       * in this node, including all of the convenience transforms.
    4319 4319       *
    4320 4320       * <p>
    4321 4321       * Note that when you register a listener or a binding to this property,
    4322 4322       * it needs to listen for invalidation on all its parents to the root node.
    4323 4323       * This means that registering a listener on this
    4324 4324       * property on many nodes may negatively affect performance of
    4325 4325       * transformation changes in their common parents.
    4326 4326       * </p>
    4327 4327       * @since 2.2
    4328 4328       */
    4329 4329      public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
    4330 4330          return getNodeTransformation().localToSceneTransformProperty();
    4331 4331      }
    4332 4332
    4333 4333      private void invalidateLocalToSceneTransform() {
    4334 4334          if (nodeTransformation != null) {
    4335 4335              nodeTransformation.invalidateLocalToSceneTransform();
    4336 4336          }
    4337 4337      }
    4338 4338
    4339 4339      public final Transform getLocalToSceneTransform() {
    4340 4340          return localToSceneTransformProperty().get();
    4341 4341      }
    4342 4342
    4343 4343      private NodeTransformation nodeTransformation;
    4344 4344
    4345 4345      private NodeTransformation getNodeTransformation() {
    4346 4346          if (nodeTransformation == null) {
    4347 4347              nodeTransformation = new NodeTransformation();
    4348 4348          }
    4349 4349
    4350 4350          return nodeTransformation;
    4351 4351      }
    4352 4352
    4353 4353      /**
    4354 4354       * @treatAsPrivate implementation detail
    4355 4355       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    4356 4356       */
    4357 4357      @Deprecated
    4358 4358      public boolean impl_hasTransforms() {
    4359 4359          return (nodeTransformation != null)
    4360 4360                  && nodeTransformation.hasTransforms();
    4361 4361      }
    4362 4362
    4363 4363      // for tests only
    4364 4364      Transform getCurrentLocalToSceneTransformState() {
    4365 4365          if (nodeTransformation == null ||
    4366 4366                  nodeTransformation.localToSceneTransform == null) {
    4367 4367              return null;
    4368 4368          }
    4369 4369
    4370 4370          return nodeTransformation.localToSceneTransform.transform;
    4371 4371      }
    4372 4372
    4373 4373      private static final double DEFAULT_TRANSLATE_X = 0;
    4374 4374      private static final double DEFAULT_TRANSLATE_Y = 0;
    4375 4375      private static final double DEFAULT_TRANSLATE_Z = 0;
    4376 4376      private static final double DEFAULT_SCALE_X = 1;
    4377 4377      private static final double DEFAULT_SCALE_Y = 1;
    4378 4378      private static final double DEFAULT_SCALE_Z = 1;
    4379 4379      private static final double DEFAULT_ROTATE = 0;
    4380 4380      private static final Point3D DEFAULT_ROTATION_AXIS = Rotate.Z_AXIS;
    4381 4381
    4382 4382      private final class NodeTransformation {
    4383 4383          private DoubleProperty translateX;
    4384 4384          private DoubleProperty translateY;
    4385 4385          private DoubleProperty translateZ;
    4386 4386          private DoubleProperty scaleX;
    4387 4387          private DoubleProperty scaleY;
    4388 4388          private DoubleProperty scaleZ;
    4389 4389          private DoubleProperty rotate;
    4390 4390          private ObjectProperty<Point3D> rotationAxis;
    4391 4391          private ObservableList<Transform> transforms;
    4392 4392          private LazyTransformProperty localToParentTransform;
    4393 4393          private LazyTransformProperty localToSceneTransform;
    4394 4394          private int listenerReasons = 0;
    4395 4395          private InvalidationListener localToSceneInvLstnr;
    4396 4396
    4397 4397          private InvalidationListener getLocalToSceneInvalidationListener() {
    4398 4398              if (localToSceneInvLstnr == null) {
    4399 4399                  localToSceneInvLstnr = new InvalidationListener() {
    4400 4400                      @Override
    4401 4401                      public void invalidated(Observable observable) {
    4402 4402                          invalidateLocalToSceneTransform();
    4403 4403                      }
    4404 4404                  };
    4405 4405              }
    4406 4406              return localToSceneInvLstnr;
    4407 4407          }
    4408 4408
    4409 4409          public void incListenerReasons() {
    4410 4410              if (listenerReasons == 0) {
    4411 4411                  Node n = Node.this.getParent();
    4412 4412                  if (n != null) {
    4413 4413                      n.localToSceneTransformProperty().addListener(
    4414 4414                              getLocalToSceneInvalidationListener());
    4415 4415                  }
    4416 4416              }
    4417 4417              listenerReasons++;
    4418 4418          }
    4419 4419
    4420 4420          public void decListenerReasons() {
    4421 4421              listenerReasons--;
    4422 4422              if (listenerReasons == 0) {
    4423 4423                  Node n = Node.this.getParent();
    4424 4424                  if (n != null) {
    4425 4425                      n.localToSceneTransformProperty().removeListener(
    4426 4426                              getLocalToSceneInvalidationListener());
    4427 4427                  }
    4428 4428              }
    4429 4429          }
    4430 4430
    4431 4431          public final Transform getLocalToParentTransform() {
    4432 4432              return localToParentTransformProperty().get();
    4433 4433          }
    4434 4434
    4435 4435          public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
    4436 4436              if (localToParentTransform == null) {
    4437 4437                  localToParentTransform = new LazyTransformProperty() {
    4438 4438                      @Override
    4439 4439                      protected Transform computeTransform() {
    4440 4440                          updateLocalToParentTransform();
    4441 4441                          return TransformUtils.immutableTransform(
    4442 4442                                  localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(),
    4443 4443                                  localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(),
    4444 4444                                  localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt());
    4445 4445                      }
    4446 4446
    4447 4447                      @Override
    4448 4448                      public Object getBean() {
    4449 4449                          return Node.this;
    4450 4450                      }
    4451 4451
    4452 4452                      @Override
    4453 4453                      public String getName() {
    4454 4454                          return "localToParentTransform";
    4455 4455                      }
    4456 4456                  };
    4457 4457              }
    4458 4458
    4459 4459              return localToParentTransform;
    4460 4460          }
    4461 4461
    4462 4462          public void invalidateLocalToParentTransform() {
    4463 4463              if (localToParentTransform != null) {
    4464 4464                  localToParentTransform.invalidate();
    4465 4465              }
    4466 4466          }
    4467 4467
    4468 4468          public final Transform getLocalToSceneTransform() {
    4469 4469              return localToSceneTransformProperty().get();
    4470 4470          }
    4471 4471
    4472 4472          public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
    4473 4473              if (localToSceneTransform == null) {
    4474 4474                  localToSceneTransform = new LazyTransformProperty() {
    4475 4475                      // need this to track number of listeners
    4476 4476                      private List localToSceneListeners;
    4477 4477
    4478 4478                      @Override
    4479 4479                      protected Transform computeTransform() {
    4480 4480                          updateLocalToParentTransform();
    4481 4481
    4482 4482                          Transform result = null;
    4483 4483                          Node parentNode = Node.this.getParent();
    4484 4484                          if (parentNode != null) {
    4485 4485                              result = parentNode.getLocalToSceneTransform();
    4486 4486                              result = result.impl_getConcatenation(getLocalToParentTransform());
    4487 4487                          } else {
    4488 4488                              result = getLocalToParentTransform();
    4489 4489                          }
    4490 4490                          return result;
    4491 4491                      }
    4492 4492
    4493 4493                      @Override
    4494 4494                      public Object getBean() {
    4495 4495                          return Node.this;
    4496 4496                      }
    4497 4497
    4498 4498                      @Override
    4499 4499                      public String getName() {
    4500 4500                          return "localToSceneTransform";
    4501 4501                      }
    4502 4502
    4503 4503                      @Override
    4504 4504                      public Transform get() {
    4505 4505                          Transform t = super.get();
    4506 4506                          if (listenerReasons == 0) {
    4507 4507                              // we don't get invalidation notifications
    4508 4508                              // so we must expect it to be always invalid
    4509 4509                              invalidate();
    4510 4510                          }
    4511 4511                          return t;
    4512 4512                      }
    4513 4513
    4514 4514                      @Override
    4515 4515                      public void addListener(InvalidationListener listener) {
    4516 4516                          incListenerReasons();
    4517 4517                          if (localToSceneListeners == null) {
    4518 4518                              localToSceneListeners = new LinkedList<Object>();
    4519 4519                          }
    4520 4520                          localToSceneListeners.add(listener);
    4521 4521                          super.addListener(listener);
    4522 4522                      }
    4523 4523
    4524 4524                      @Override
    4525 4525                      public void addListener(ChangeListener<? super Transform> listener) {
    4526 4526                          incListenerReasons();
    4527 4527                          if (localToSceneListeners == null) {
    4528 4528                              localToSceneListeners = new LinkedList<Object>();
    4529 4529                          }
    4530 4530                          localToSceneListeners.add(listener);
    4531 4531                          super.addListener(listener);
    4532 4532                      }
    4533 4533
    4534 4534                      @Override
    4535 4535                      public void removeListener(InvalidationListener listener) {
    4536 4536                          if (localToSceneListeners != null &&
    4537 4537                                  localToSceneListeners.remove(listener)) {
    4538 4538                              decListenerReasons();
    4539 4539                          }
    4540 4540                          super.removeListener(listener);
    4541 4541                      }
    4542 4542
    4543 4543                      @Override
    4544 4544                      public void removeListener(ChangeListener<? super Transform> listener) {
    4545 4545                          if (localToSceneListeners != null &&
    4546 4546                                  localToSceneListeners.remove(listener)) {
    4547 4547                              decListenerReasons();
    4548 4548                          }
    4549 4549                          super.removeListener(listener);
    4550 4550                      }
    4551 4551                  };
    4552 4552              }
    4553 4553
    4554 4554              return localToSceneTransform;
    4555 4555          }
    4556 4556
    4557 4557          public void invalidateLocalToSceneTransform() {
    4558 4558              if (localToSceneTransform != null) {
    4559 4559                  localToSceneTransform.invalidate();
    4560 4560              }
    4561 4561          }
    4562 4562
    4563 4563          public double getTranslateX() {
    4564 4564              return (translateX == null) ? DEFAULT_TRANSLATE_X
    4565 4565                                          : translateX.get();
    4566 4566          }
    4567 4567
    4568 4568          public final DoubleProperty translateXProperty() {
    4569 4569              if (translateX == null) {
    4570 4570                  translateX = new StyleableDoubleProperty(DEFAULT_TRANSLATE_X) {
    4571 4571                      @Override
    4572 4572                      public void invalidated() {
    4573 4573                          impl_transformsChanged();
    4574 4574                      }
    4575 4575
    4576 4576                      @Override
    4577 4577                      public StyleableProperty getStyleableProperty() {
    4578 4578                          return StyleableProperties.TRANSLATE_X;
    4579 4579                      }
    4580 4580
    4581 4581                      @Override
    4582 4582                      public Object getBean() {
    4583 4583                          return Node.this;
    4584 4584                      }
    4585 4585
    4586 4586                      @Override
    4587 4587                      public String getName() {
    4588 4588                          return "translateX";
    4589 4589                      }
    4590 4590                  };
    4591 4591              }
    4592 4592              return translateX;
    4593 4593          }
    4594 4594
    4595 4595          public double getTranslateY() {
    4596 4596              return (translateY == null) ? DEFAULT_TRANSLATE_Y : translateY.get();
    4597 4597          }
    4598 4598
    4599 4599          public final DoubleProperty translateYProperty() {
    4600 4600              if (translateY == null) {
    4601 4601                  translateY = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Y) {
    4602 4602                      @Override
    4603 4603                      public void invalidated() {
    4604 4604                          impl_transformsChanged();
    4605 4605                      }
    4606 4606
    4607 4607                      @Override
    4608 4608                      public StyleableProperty getStyleableProperty() {
    4609 4609                          return StyleableProperties.TRANSLATE_Y;
    4610 4610                      }
    4611 4611
    4612 4612                      @Override
    4613 4613                      public Object getBean() {
    4614 4614                          return Node.this;
    4615 4615                      }
    4616 4616
    4617 4617                      @Override
    4618 4618                      public String getName() {
    4619 4619                          return "translateY";
    4620 4620                      }
    4621 4621                  };
    4622 4622              }
    4623 4623              return translateY;
    4624 4624          }
    4625 4625
    4626 4626          public double getTranslateZ() {
    4627 4627              return (translateZ == null) ? DEFAULT_TRANSLATE_Z : translateZ.get();
    4628 4628          }
    4629 4629
    4630 4630          public final DoubleProperty translateZProperty() {
    4631 4631              if (translateZ == null) {
    4632 4632                  translateZ = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Z) {
    4633 4633                      @Override
    4634 4634                      public void invalidated() {
    4635 4635                          impl_transformsChanged();
    4636 4636                      }
    4637 4637
    4638 4638                      @Override
    4639 4639                      public StyleableProperty getStyleableProperty() {
    4640 4640                          return StyleableProperties.TRANSLATE_Z;
    4641 4641                      }
    4642 4642
    4643 4643                      @Override
    4644 4644                      public Object getBean() {
    4645 4645                          return Node.this;
    4646 4646                      }
    4647 4647
    4648 4648                      @Override
    4649 4649                      public String getName() {
    4650 4650                          return "translateZ";
    4651 4651                      }
    4652 4652                  };
    4653 4653              }
    4654 4654              return translateZ;
    4655 4655          }
    4656 4656
    4657 4657          public double getScaleX() {
    4658 4658              return (scaleX == null) ? DEFAULT_SCALE_X : scaleX.get();
    4659 4659          }
    4660 4660
    4661 4661          public final DoubleProperty scaleXProperty() {
    4662 4662              if (scaleX == null) {
    4663 4663                  scaleX = new StyleableDoubleProperty(DEFAULT_SCALE_X) {
    4664 4664                      @Override
    4665 4665                      public void invalidated() {
    4666 4666                          impl_transformsChanged();
    4667 4667                      }
    4668 4668
    4669 4669                      @Override
    4670 4670                      public StyleableProperty getStyleableProperty() {
    4671 4671                          return StyleableProperties.SCALE_X;
    4672 4672                      }
    4673 4673
    4674 4674                      @Override
    4675 4675                      public Object getBean() {
    4676 4676                          return Node.this;
    4677 4677                      }
    4678 4678
    4679 4679                      @Override
    4680 4680                      public String getName() {
    4681 4681                          return "scaleX";
    4682 4682                      }
    4683 4683                  };
    4684 4684              }
    4685 4685              return scaleX;
    4686 4686          }
    4687 4687
    4688 4688          public double getScaleY() {
    4689 4689              return (scaleY == null) ? DEFAULT_SCALE_Y : scaleY.get();
    4690 4690          }
    4691 4691
    4692 4692          public final DoubleProperty scaleYProperty() {
    4693 4693              if (scaleY == null) {
    4694 4694                  scaleY = new StyleableDoubleProperty(DEFAULT_SCALE_Y) {
    4695 4695                      @Override
    4696 4696                      public void invalidated() {
    4697 4697                          impl_transformsChanged();
    4698 4698                      }
    4699 4699
    4700 4700                      @Override
    4701 4701                      public StyleableProperty getStyleableProperty() {
    4702 4702                          return StyleableProperties.SCALE_Y;
    4703 4703                      }
    4704 4704
    4705 4705                      @Override
    4706 4706                      public Object getBean() {
    4707 4707                          return Node.this;
    4708 4708                      }
    4709 4709
    4710 4710                      @Override
    4711 4711                      public String getName() {
    4712 4712                          return "scaleY";
    4713 4713                      }
    4714 4714                  };
    4715 4715              }
    4716 4716              return scaleY;
    4717 4717          }
    4718 4718
    4719 4719          public double getScaleZ() {
    4720 4720              return (scaleZ == null) ? DEFAULT_SCALE_Z : scaleZ.get();
    4721 4721          }
    4722 4722
    4723 4723          public final DoubleProperty scaleZProperty() {
    4724 4724              if (scaleZ == null) {
    4725 4725                  scaleZ = new StyleableDoubleProperty(DEFAULT_SCALE_Z) {
    4726 4726                      @Override
    4727 4727                      public void invalidated() {
    4728 4728                          impl_transformsChanged();
    4729 4729                      }
    4730 4730
    4731 4731                      @Override
    4732 4732                      public StyleableProperty getStyleableProperty() {
    4733 4733                          return StyleableProperties.SCALE_Z;
    4734 4734                      }
    4735 4735
    4736 4736                      @Override
    4737 4737                      public Object getBean() {
    4738 4738                          return Node.this;
    4739 4739                      }
    4740 4740
    4741 4741                      @Override
    4742 4742                      public String getName() {
    4743 4743                          return "scaleZ";
    4744 4744                      }
    4745 4745                  };
    4746 4746              }
    4747 4747              return scaleZ;
    4748 4748          }
    4749 4749
    4750 4750          public double getRotate() {
    4751 4751              return (rotate == null) ? DEFAULT_ROTATE : rotate.get();
    4752 4752          }
    4753 4753
    4754 4754          public final DoubleProperty rotateProperty() {
    4755 4755              if (rotate == null) {
    4756 4756                  rotate = new StyleableDoubleProperty(DEFAULT_ROTATE) {
    4757 4757                      @Override
    4758 4758                      public void invalidated() {
    4759 4759                          impl_transformsChanged();
    4760 4760                      }
    4761 4761
    4762 4762                      @Override
    4763 4763                      public StyleableProperty getStyleableProperty() {
    4764 4764                          return StyleableProperties.ROTATE;
    4765 4765                      }
    4766 4766
    4767 4767                      @Override
    4768 4768                      public Object getBean() {
    4769 4769                          return Node.this;
    4770 4770                      }
    4771 4771
    4772 4772                      @Override
    4773 4773                      public String getName() {
    4774 4774                          return "rotate";
    4775 4775                      }
    4776 4776                  };
    4777 4777              }
    4778 4778              return rotate;
    4779 4779          }
    4780 4780
    4781 4781          public Point3D getRotationAxis() {
    4782 4782              return (rotationAxis == null) ? DEFAULT_ROTATION_AXIS
    4783 4783                                            : rotationAxis.get();
    4784 4784          }
    4785 4785
    4786 4786          public final ObjectProperty<Point3D> rotationAxisProperty() {
    4787 4787              if (rotationAxis == null) {
    4788 4788                  rotationAxis = new ObjectPropertyBase<Point3D>(
    4789 4789                                             DEFAULT_ROTATION_AXIS) {
    4790 4790                      @Override
    4791 4791                      protected void invalidated() {
    4792 4792                          impl_transformsChanged();
    4793 4793                      }
    4794 4794
    4795 4795                      @Override
    4796 4796                      public Object getBean() {
    4797 4797                          return Node.this;
    4798 4798                      }
    4799 4799
    4800 4800                      @Override
    4801 4801                      public String getName() {
    4802 4802                          return "rotationAxis";
    4803 4803                      }
    4804 4804                  };
    4805 4805              }
    4806 4806              return rotationAxis;
    4807 4807          }
    4808 4808
    4809 4809          public ObservableList<Transform> getTransforms() {
    4810 4810              if (transforms == null) {
    4811 4811                  transforms = new TrackableObservableList<Transform>() {
    4812 4812                      @Override
    4813 4813                      protected void onChanged(Change<Transform> c) {
    4814 4814                          while (c.next()) {
    4815 4815                              for (Transform t : c.getRemoved()) {
    4816 4816                                  t.impl_remove(Node.this);
    4817 4817                              }
    4818 4818                              for (Transform t : c.getAddedSubList()) {
    4819 4819                                  t.impl_add(Node.this);
    4820 4820                              }
    4821 4821                          }
    4822 4822
    4823 4823                          impl_transformsChanged();
    4824 4824                      }
    4825 4825                  };
    4826 4826              }
    4827 4827
    4828 4828              return transforms;
    4829 4829          }
    4830 4830
    4831 4831          public boolean canSetTranslateX() {
    4832 4832              return (translateX == null) || !translateX.isBound();
    4833 4833          }
    4834 4834
    4835 4835          public boolean canSetTranslateY() {
    4836 4836              return (translateY == null) || !translateY.isBound();
    4837 4837          }
    4838 4838
    4839 4839          public boolean canSetTranslateZ() {
    4840 4840              return (translateZ == null) || !translateZ.isBound();
    4841 4841          }
    4842 4842
    4843 4843          public boolean canSetScaleX() {
    4844 4844              return (scaleX == null) || !scaleX.isBound();
    4845 4845          }
    4846 4846
    4847 4847          public boolean canSetScaleY() {
    4848 4848              return (scaleY == null) || !scaleY.isBound();
    4849 4849          }
    4850 4850
    4851 4851          public boolean canSetScaleZ() {
    4852 4852              return (scaleZ == null) || !scaleZ.isBound();
    4853 4853          }
    4854 4854
    4855 4855          public boolean canSetRotate() {
    4856 4856              return (rotate == null) || !rotate.isBound();
    4857 4857          }
    4858 4858
    4859 4859          public boolean hasTransforms() {
    4860 4860              return (transforms != null && !transforms.isEmpty());
    4861 4861          }
    4862 4862
    4863 4863          public boolean hasScaleOrRotate() {
    4864 4864              if (scaleX != null && scaleX.get() != DEFAULT_SCALE_X) {
    4865 4865                  return true;
    4866 4866              }
    4867 4867              if (scaleY != null && scaleY.get() != DEFAULT_SCALE_Y) {
    4868 4868                  return true;
    4869 4869              }
    4870 4870              if (scaleZ != null && scaleZ.get() != DEFAULT_SCALE_Z) {
    4871 4871                  return true;
    4872 4872              }
    4873 4873              if (rotate != null && rotate.get() != DEFAULT_ROTATE) {
    4874 4874                  return true;
    4875 4875              }
    4876 4876              return false;
    4877 4877          }
    4878 4878
    4879 4879      }
    4880 4880
    4881 4881      ////////////////////////////
    4882 4882      //  Private Implementation
    4883 4883      ////////////////////////////
    4884 4884
    4885 4885      /***************************************************************************
    4886 4886       *                                                                         *
    4887 4887       *                        Event Handler Properties                         *
    4888 4888       *                                                                         *
    4889 4889       **************************************************************************/
    4890 4890
    4891 4891      private EventHandlerProperties eventHandlerProperties;
    4892 4892
    4893 4893      private EventHandlerProperties getEventHandlerProperties() {
    4894 4894          if (eventHandlerProperties == null) {
    4895 4895              eventHandlerProperties =
    4896 4896                      new EventHandlerProperties(
    4897 4897                          getInternalEventDispatcher().getEventHandlerManager(),
    4898 4898                          this);
    4899 4899          }
    4900 4900
    4901 4901          return eventHandlerProperties;
    4902 4902      }
    4903 4903
    4904 4904      /***************************************************************************
    4905 4905       *                                                                         *
    4906 4906       *                       Misc Seldom Used Properties                       *
    4907 4907       *                                                                         *
    4908 4908       **************************************************************************/
    4909 4909
    4910 4910      private MiscProperties miscProperties;
    4911 4911
    4912 4912      private MiscProperties getMiscProperties() {
    4913 4913          if (miscProperties == null) {
    4914 4914              miscProperties = new MiscProperties();
    4915 4915          }
    4916 4916
    4917 4917          return miscProperties;
    4918 4918      }
    4919 4919
    4920 4920      private static final boolean DEFAULT_CACHE = false;
    4921 4921      private static final CacheHint DEFAULT_CACHE_HINT = CacheHint.DEFAULT;
    4922 4922      private static final Node DEFAULT_CLIP = null;
    4923 4923      private static final Cursor DEFAULT_CURSOR = null;
    4924 4924      private static final DepthTest DEFAULT_DEPTH_TEST = DepthTest.INHERIT;
    4925 4925      private static final boolean DEFAULT_DISABLE = false;
    4926 4926      private static final Effect DEFAULT_EFFECT = null;
    4927 4927      private static final InputMethodRequests DEFAULT_INPUT_METHOD_REQUESTS =
    4928 4928              null;
    4929 4929      private static final boolean DEFAULT_MOUSE_TRANSPARENT = false;
    4930 4930
    4931 4931      private final class MiscProperties {
    4932 4932          private LazyBoundsProperty boundsInParent;
    4933 4933          private LazyBoundsProperty boundsInLocal;
    4934 4934          private BooleanProperty cache;
    4935 4935          private ObjectProperty<CacheHint> cacheHint;
    4936 4936          private ObjectProperty<Node> clip;
    4937 4937          private ObjectProperty<Cursor> cursor;
    4938 4938          private ObjectProperty<DepthTest> depthTest;
    4939 4939          private BooleanProperty disable;
    4940 4940          private ObjectProperty<Effect> effect;
    4941 4941          private ObjectProperty<InputMethodRequests> inputMethodRequests;
    4942 4942          private BooleanProperty mouseTransparent;
    4943 4943
    4944 4944          public final Bounds getBoundsInParent() {
    4945 4945              return boundsInParentProperty().get();
    4946 4946          }
    4947 4947
    4948 4948          public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
    4949 4949              if (boundsInParent == null) {
    4950 4950                  boundsInParent = new LazyBoundsProperty() {
    4951 4951                      /**
    4952 4952                       * Computes the bounds including the clip, effects, and all
    4953 4953                       * transforms. This function is essentially how to compute
    4954 4954                       * the boundsInParent. Optimizations are made to compute as
    4955 4955                       * little as possible and create as little trash as
    4956 4956                       * possible.
    4957 4957                       */
    4958 4958                      @Override
    4959 4959                      protected Bounds computeBounds() {
    4960 4960                          BaseBounds tempBounds = TempState.getInstance().bounds;
    4961 4961                          tempBounds = getTransformedBounds(
    4962 4962                                               tempBounds,
    4963 4963                                               BaseTransform.IDENTITY_TRANSFORM);
    4964 4964                          return new BoundingBox(tempBounds.getMinX(),
    4965 4965                                                 tempBounds.getMinY(),
    4966 4966                                                 tempBounds.getMinZ(),
    4967 4967                                                 tempBounds.getWidth(),
    4968 4968                                                 tempBounds.getHeight(),
    4969 4969                                                 tempBounds.getDepth());
    4970 4970                      }
    4971 4971
    4972 4972                      @Override
    4973 4973                      public Object getBean() {
    4974 4974                          return Node.this;
    4975 4975                      }
    4976 4976
    4977 4977                      @Override
    4978 4978                      public String getName() {
    4979 4979                          return "boundsInParent";
    4980 4980                      }
    4981 4981                  };
    4982 4982              }
    4983 4983
    4984 4984              return boundsInParent;
    4985 4985          }
    4986 4986
    4987 4987          public void invalidateBoundsInParent() {
    4988 4988              if (boundsInParent != null) {
    4989 4989                  boundsInParent.invalidate();
    4990 4990              }
    4991 4991          }
    4992 4992
    4993 4993          public final Bounds getBoundsInLocal() {
    4994 4994              return boundsInLocalProperty().get();
    4995 4995          }
    4996 4996
    4997 4997          public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
    4998 4998              if (boundsInLocal == null) {
    4999 4999                  boundsInLocal = new LazyBoundsProperty() {
    5000 5000                      @Override
    5001 5001                      protected Bounds computeBounds() {
    5002 5002                          BaseBounds tempBounds = TempState.getInstance().bounds;
    5003 5003                          tempBounds = getLocalBounds(
    5004 5004                                               tempBounds,
    5005 5005                                               BaseTransform.IDENTITY_TRANSFORM);
    5006 5006                          return new BoundingBox(tempBounds.getMinX(),
    5007 5007                                                 tempBounds.getMinY(),
    5008 5008                                                 tempBounds.getMinZ(),
    5009 5009                                                 tempBounds.getWidth(),
    5010 5010                                                 tempBounds.getHeight(),
    5011 5011                                                 tempBounds.getDepth());
    5012 5012                      }
    5013 5013
    5014 5014                      @Override
    5015 5015                      public Object getBean() {
    5016 5016                          return Node.this;
    5017 5017                      }
    5018 5018
    5019 5019                      @Override
    5020 5020                      public String getName() {
    5021 5021                          return "boundsInLocal";
    5022 5022                      }
    5023 5023                  };
    5024 5024              }
    5025 5025
    5026 5026              return boundsInLocal;
    5027 5027          }
    5028 5028
    5029 5029          public void invalidateBoundsInLocal() {
    5030 5030              if (boundsInLocal != null) {
    5031 5031                  boundsInLocal.invalidate();
    5032 5032              }
    5033 5033          }
    5034 5034
    5035 5035          public final boolean isCache() {
    5036 5036              return (cache == null) ? DEFAULT_CACHE
    5037 5037                                     : cache.get();
    5038 5038          }
    5039 5039
    5040 5040          public final BooleanProperty cacheProperty() {
    5041 5041              if (cache == null) {
    5042 5042                  cache = new BooleanPropertyBase(DEFAULT_CACHE) {
    5043 5043                      @Override
    5044 5044                      protected void invalidated() {
    5045 5045                          impl_markDirty(DirtyBits.NODE_CACHE);
    5046 5046                      }
    5047 5047
    5048 5048                      @Override
    5049 5049                      public Object getBean() {
    5050 5050                          return Node.this;
    5051 5051                      }
    5052 5052
    5053 5053                      @Override
    5054 5054                      public String getName() {
    5055 5055                          return "cache";
    5056 5056                      }
    5057 5057                  };
    5058 5058              }
    5059 5059              return cache;
    5060 5060          }
    5061 5061
    5062 5062          public final CacheHint getCacheHint() {
    5063 5063              return (cacheHint == null) ? DEFAULT_CACHE_HINT
    5064 5064                                         : cacheHint.get();
    5065 5065          }
    5066 5066
    5067 5067          public final ObjectProperty<CacheHint> cacheHintProperty() {
    5068 5068              if (cacheHint == null) {
    5069 5069                  cacheHint = new ObjectPropertyBase<CacheHint>(DEFAULT_CACHE_HINT) {
    5070 5070                      @Override
    5071 5071                      protected void invalidated() {
    5072 5072                          impl_markDirty(DirtyBits.NODE_CACHE);
    5073 5073                      }
    5074 5074
    5075 5075                      @Override
    5076 5076                      public Object getBean() {
    5077 5077                          return Node.this;
    5078 5078                      }
    5079 5079
    5080 5080                      @Override
    5081 5081                      public String getName() {
    5082 5082                          return "cacheHint";
    5083 5083                      }
    5084 5084                  };
    5085 5085              }
    5086 5086              return cacheHint;
    5087 5087          }
    5088 5088
    5089 5089          public final Node getClip() {
    5090 5090              return (clip == null) ? DEFAULT_CLIP : clip.get();
    5091 5091          }
    5092 5092
    5093 5093          public final ObjectProperty<Node> clipProperty() {
    5094 5094              if (clip == null) {
    5095 5095                  clip = new ObjectPropertyBase<Node>(DEFAULT_CLIP) {
    5096 5096
    5097 5097                      //temp variables used when clip was invalid to rollback to
    5098 5098                      // last value
    5099 5099                      private Node oldClip;
    5100 5100
    5101 5101                      @Override
    5102 5102                      protected void invalidated() {
    5103 5103                          final Node newClip = get();
    5104 5104                          if ((newClip != null)
    5105 5105                                  && ((newClip.isConnected()
    5106 5106                                             && newClip.clipParent != Node.this)
    5107 5107                                         || wouldCreateCycle(Node.this,
    5108 5108                                                             newClip))) {
    5109 5109                              // Assigning this node to clip is illegal.
    5110 5110                              // Roll back to the previous state and throw an
    5111 5111                              // exception.
    5112 5112                              final String cause =
    5113 5113                                      newClip.isConnected()
    5114 5114                                          && (newClip.clipParent != Node.this)
    5115 5115                                              ? "node already connected"
    5116 5116                                              : "cycle detected";
    5117 5117
    5118 5118                              if (isBound()) {
    5119 5119                                  unbind();
    5120 5120                                  set(oldClip);
    5121 5121                                  throw new IllegalArgumentException(
    5122 5122                                          "Node's clip set to incorrect value "
    5123 5123                                              + " through binding"
    5124 5124                                              + " (" + cause + ", node  = "
    5125 5125                                                     + Node.this + ", clip = "
    5126 5126                                                     + clip + ")."
    5127 5127                                              + " Binding has been removed.");
    5128 5128                              } else {
    5129 5129                                  set(oldClip);
    5130 5130                                  throw new IllegalArgumentException(
    5131 5131                                          "Node's clip set to incorrect value"
    5132 5132                                              + " (" + cause + ", node  = "
    5133 5133                                                     + Node.this + ", clip = "
    5134 5134                                                     + clip + ").");
    5135 5135                              }
    5136 5136                          } else {
    5137 5137                              if (oldClip != null) {
    5138 5138                                  oldClip.clipParent = null;
    5139 5139                                  oldClip.setScene(null);
    5140 5140                              }
    5141 5141
    5142 5142                              if (newClip != null) {
    5143 5143                                  newClip.clipParent = Node.this;
    5144 5144                                  newClip.setScene(getScene());
    5145 5145                              }
    5146 5146
    5147 5147                              impl_markDirty(DirtyBits.NODE_CLIP);
    5148 5148
    5149 5149                              // the local bounds have (probably) changed
    5150 5150                              localBoundsChanged();
    5151 5151
    5152 5152                              oldClip = newClip;
    5153 5153                          }
    5154 5154                      }
    5155 5155
    5156 5156                      @Override
    5157 5157                      public Object getBean() {
    5158 5158                          return Node.this;
    5159 5159                      }
    5160 5160
    5161 5161                      @Override
    5162 5162                      public String getName() {
    5163 5163                          return "clip";
    5164 5164                      }
    5165 5165                  };
    5166 5166              }
    5167 5167              return clip;
    5168 5168          }
    5169 5169
    5170 5170          public final Cursor getCursor() {
    5171 5171              return (cursor == null) ? DEFAULT_CURSOR : cursor.get();
    5172 5172          }
    5173 5173
    5174 5174          public final ObjectProperty<Cursor> cursorProperty() {
    5175 5175              if (cursor == null) {
    5176 5176                  cursor = new StyleableObjectProperty<Cursor>(DEFAULT_CURSOR) {
    5177 5177
    5178 5178                      @Override
    5179 5179                      public StyleableProperty getStyleableProperty() {
    5180 5180                          return StyleableProperties.CURSOR;
    5181 5181                      }
    5182 5182
    5183 5183                      @Override
    5184 5184                      public Object getBean() {
    5185 5185                          return Node.this;
    5186 5186                      }
    5187 5187
    5188 5188                      @Override
    5189 5189                      public String getName() {
    5190 5190                          return "cursor";
    5191 5191                      }
    5192 5192
    5193 5193                  };
    5194 5194              }
    5195 5195              return cursor;
    5196 5196          }
    5197 5197
    5198 5198          public final DepthTest getDepthTest() {
    5199 5199              return (depthTest == null) ? DEFAULT_DEPTH_TEST
    5200 5200                                         : depthTest.get();
    5201 5201          }
    5202 5202
    5203 5203          public final ObjectProperty<DepthTest> depthTestProperty() {
    5204 5204              if (depthTest == null) {
    5205 5205                  depthTest = new ObjectPropertyBase<DepthTest>(DEFAULT_DEPTH_TEST) {
    5206 5206                      @Override protected void invalidated() {
    5207 5207                          computeDerivedDepthTest();
    5208 5208                      }
    5209 5209
    5210 5210                      @Override
    5211 5211                      public Object getBean() {
    5212 5212                          return Node.this;
    5213 5213                      }
    5214 5214
    5215 5215                      @Override
    5216 5216                      public String getName() {
    5217 5217                          return "depthTest";
    5218 5218                      }
    5219 5219                  };
    5220 5220              }
    5221 5221              return depthTest;
    5222 5222          }
    5223 5223
    5224 5224          public final boolean isDisable() {
    5225 5225              return (disable == null) ? DEFAULT_DISABLE : disable.get();
    5226 5226          }
    5227 5227
    5228 5228          public final BooleanProperty disableProperty() {
    5229 5229              if (disable == null) {
    5230 5230                  disable = new BooleanPropertyBase(DEFAULT_DISABLE) {
    5231 5231                      @Override
    5232 5232                      protected void invalidated() {
    5233 5233                          updateDisabled();
    5234 5234                      }
    5235 5235
    5236 5236                      @Override
    5237 5237                      public Object getBean() {
    5238 5238                          return Node.this;
    5239 5239                      }
    5240 5240
    5241 5241                      @Override
    5242 5242                      public String getName() {
    5243 5243                          return "disable";
    5244 5244                      }
    5245 5245                  };
    5246 5246              }
    5247 5247              return disable;
    5248 5248          }
    5249 5249
    5250 5250          public final Effect getEffect() {
    5251 5251              return (effect == null) ? DEFAULT_EFFECT : effect.get();
    5252 5252          }
    5253 5253
    5254 5254          public final ObjectProperty<Effect> effectProperty() {
    5255 5255              if (effect == null) {
    5256 5256                  effect = new StyleableObjectProperty<Effect>(DEFAULT_EFFECT) {
    5257 5257                      private Effect oldEffect = null;
    5258 5258                      private int oldBits;
    5259 5259
    5260 5260                      private final AbstractNotifyListener effectChangeListener =
    5261 5261                              new AbstractNotifyListener() {
    5262 5262
    5263 5263                          @Override
    5264 5264                          public void invalidated(Observable valueModel) {
    5265 5265                              int newBits = ((IntegerProperty) valueModel).get();
    5266 5266                              int changedBits = newBits ^ oldBits;
    5267 5267                              oldBits = newBits;
    5268 5268                              if (EffectDirtyBits.isSet(
    5269 5269                                      changedBits,
    5270 5270                                      EffectDirtyBits.EFFECT_DIRTY)
    5271 5271                                  && EffectDirtyBits.isSet(
    5272 5272                                         newBits,
    5273 5273                                         EffectDirtyBits.EFFECT_DIRTY)) {
    5274 5274                                  impl_markDirty(DirtyBits.EFFECT_EFFECT);
    5275 5275                              }
    5276 5276                              if (EffectDirtyBits.isSet(
    5277 5277                                      changedBits,
    5278 5278                                      EffectDirtyBits.BOUNDS_CHANGED)) {
    5279 5279                                  localBoundsChanged();
    5280 5280                              }
    5281 5281                          }
    5282 5282                      };
    5283 5283
    5284 5284                      @Override
    5285 5285                      protected void invalidated() {
    5286 5286                          Effect _effect = get();
    5287 5287                          if (oldEffect != null) {
    5288 5288                              oldEffect.impl_effectDirtyProperty().removeListener(
    5289 5289                                      effectChangeListener.getWeakListener());
    5290 5290                          }
    5291 5291                          oldEffect = _effect;
    5292 5292                          if (_effect != null) {
    5293 5293                              _effect.impl_effectDirtyProperty()
    5294 5294                                     .addListener(
    5295 5295                                         effectChangeListener.getWeakListener());
    5296 5296                              if (_effect.impl_isEffectDirty()) {
    5297 5297                                  impl_markDirty(DirtyBits.EFFECT_EFFECT);
    5298 5298                              }
    5299 5299                              oldBits = _effect.impl_effectDirtyProperty().get();
    5300 5300                          }
    5301 5301
    5302 5302                          impl_markDirty(DirtyBits.NODE_EFFECT);
    5303 5303                          // bounds may have changed regardeless whether
    5304 5304                          // the dirty flag on efffect is set
    5305 5305                          localBoundsChanged();
    5306 5306                      }
    5307 5307
    5308 5308                      @Override
    5309 5309                      public StyleableProperty getStyleableProperty() {
    5310 5310                          return StyleableProperties.EFFECT;
    5311 5311                      }
    5312 5312
    5313 5313                      @Override
    5314 5314                      public Object getBean() {
    5315 5315                          return Node.this;
    5316 5316                      }
    5317 5317
    5318 5318                      @Override
    5319 5319                      public String getName() {
    5320 5320                          return "effect";
    5321 5321                      }
    5322 5322                  };
    5323 5323              }
    5324 5324              return effect;
    5325 5325          }
    5326 5326
    5327 5327          public final InputMethodRequests getInputMethodRequests() {
    5328 5328              return (inputMethodRequests == null) ? DEFAULT_INPUT_METHOD_REQUESTS
    5329 5329                                                   : inputMethodRequests.get();
    5330 5330          }
    5331 5331
    5332 5332          public ObjectProperty<InputMethodRequests>
    5333 5333                  inputMethodRequestsProperty() {
    5334 5334              if (inputMethodRequests == null) {
    5335 5335                  inputMethodRequests =
    5336 5336                          new SimpleObjectProperty<InputMethodRequests>(
    5337 5337                                  Node.this,
    5338 5338                                  "inputMethodRequests",
    5339 5339                                  DEFAULT_INPUT_METHOD_REQUESTS);
    5340 5340              }
    5341 5341              return inputMethodRequests;
    5342 5342          }
    5343 5343
    5344 5344          public final boolean isMouseTransparent() {
    5345 5345              return (mouseTransparent == null) ? DEFAULT_MOUSE_TRANSPARENT
    5346 5346                                                : mouseTransparent.get();
    5347 5347          }
    5348 5348
    5349 5349          public final BooleanProperty mouseTransparentProperty() {
    5350 5350              if (mouseTransparent == null) {
    5351 5351                  mouseTransparent =
    5352 5352                          new SimpleBooleanProperty(
    5353 5353                                  Node.this,
    5354 5354                                  "mouseTransparent",
    5355 5355                                  DEFAULT_MOUSE_TRANSPARENT);
    5356 5356              }
    5357 5357              return mouseTransparent;
    5358 5358          }
    5359 5359
    5360 5360          public boolean canSetCursor() {
    5361 5361              return (cursor == null) || !cursor.isBound();
    5362 5362          }
    5363 5363
    5364 5364          public boolean canSetEffect() {
    5365 5365              return (effect == null) || !effect.isBound();
    5366 5366          }
    5367 5367      }
    5368 5368
    5369 5369      /* *************************************************************************
    5370 5370       *                                                                         *
    5371 5371       *                             Mouse Handling                              *
    5372 5372       *                                                                         *
    5373 5373       **************************************************************************/
    5374 5374
    5375 5375      public final void setMouseTransparent(boolean value) {
    5376 5376          mouseTransparentProperty().set(value);
    5377 5377      }
    5378 5378
    5379 5379      public final boolean isMouseTransparent() {
    5380 5380          return (miscProperties == null) ? DEFAULT_MOUSE_TRANSPARENT
    5381 5381                                          : miscProperties.isMouseTransparent();
    5382 5382      }
    5383 5383
    5384 5384      /**
    5385 5385       * If {@code true}, this node (together with all its children) is completely
    5386 5386       * transparent to mouse events. When choosing target for mouse event, nodes
    5387 5387       * with {@code mouseTransparent} set to {@code true} and their subtrees
    5388 5388       * won't be taken into account.
    5389 5389       */
    5390 5390      public final BooleanProperty mouseTransparentProperty() {
    5391 5391          return getMiscProperties().mouseTransparentProperty();
    5392 5392      }
    5393 5393
    5394 5394      /**
    5395 5395       * Whether or not this {@code Node} is being hovered over. Typically this is
    5396 5396       * due to the mouse being over the node, though it could be due to a pen
    5397 5397       * hovering on a graphics tablet or other form of input.
    5398 5398       *
    5399 5399       * <p>Note that current implementation of hover relies on mouse enter and
    5400 5400       * exit events to determine whether this Node is in the hover state; this
    5401 5401       * means that this feature is currently supported only on systems that
    5402 5402       * have a mouse. Future implementations may provide alternative means of
    5403 5403       * supporting hover.
    5404 5404       *
    5405 5405       * @defaultValue false
    5406 5406       */
    5407 5407      private ReadOnlyBooleanWrapper hover;
    5408 5408
    5409 5409      protected final void setHover(boolean value) {
    5410 5410          hoverPropertyImpl().set(value);
    5411 5411      }
    5412 5412
    5413 5413      public final boolean isHover() {
    5414 5414          return hover == null ? false : hover.get();
    5415 5415      }
    5416 5416
    5417 5417      public final ReadOnlyBooleanProperty hoverProperty() {
    5418 5418          return hoverPropertyImpl().getReadOnlyProperty();
    5419 5419      }
    5420 5420
    5421 5421      private ReadOnlyBooleanWrapper hoverPropertyImpl() {
    5422 5422          if (hover == null) {
    5423 5423              hover = new ReadOnlyBooleanWrapper() {
    5424 5424
    5425 5425                  @Override
    5426 5426                  protected void invalidated() {
    5427 5427                      PlatformLogger logger = Logging.getInputLogger();
    5428 5428                      if (logger.isLoggable(PlatformLogger.FINER)) {
    5429 5429                          logger.finer(this + " hover=" + get());
    5430 5430                      }
    5431 5431                      impl_pseudoClassStateChanged("hover");
    5432 5432                  }
    5433 5433
    5434 5434                  @Override
    5435 5435                  public Object getBean() {
    5436 5436                      return Node.this;
    5437 5437                  }
    5438 5438
    5439 5439                  @Override
    5440 5440                  public String getName() {
    5441 5441                      return "hover";
    5442 5442                  }
    5443 5443              };
    5444 5444          }
    5445 5445          return hover;
    5446 5446      }
    5447 5447
    5448 5448      /**
    5449 5449       * Whether or not the {@code Node} is pressed. Typically this is true when
    5450 5450       * the primary mouse button is down, though subclasses may define other
    5451 5451       * mouse button state or key state to cause the node to be "pressed".
    5452 5452       *
    5453 5453       * @defaultValue false
    5454 5454       */
    5455 5455      private ReadOnlyBooleanWrapper pressed;
    5456 5456
    5457 5457      protected final void setPressed(boolean value) {
    5458 5458          pressedPropertyImpl().set(value);
    5459 5459      }
    5460 5460
    5461 5461      public final boolean isPressed() {
    5462 5462          return pressed == null ? false : pressed.get();
    5463 5463      }
    5464 5464
    5465 5465      public final ReadOnlyBooleanProperty pressedProperty() {
    5466 5466          return pressedPropertyImpl().getReadOnlyProperty();
    5467 5467      }
    5468 5468
    5469 5469      private ReadOnlyBooleanWrapper pressedPropertyImpl() {
    5470 5470          if (pressed == null) {
    5471 5471              pressed = new ReadOnlyBooleanWrapper() {
    5472 5472
    5473 5473                  @Override
    5474 5474                  protected void invalidated() {
    5475 5475                      PlatformLogger logger = Logging.getInputLogger();
    5476 5476                      if (logger.isLoggable(PlatformLogger.FINER)) {
    5477 5477                          logger.finer(this + " pressed=" + get());
    5478 5478                      }
    5479 5479                      impl_pseudoClassStateChanged("pressed");
    5480 5480                  }
    5481 5481
    5482 5482                  @Override
    5483 5483                  public Object getBean() {
    5484 5484                      return Node.this;
    5485 5485                  }
    5486 5486
    5487 5487                  @Override
    5488 5488                  public String getName() {
    5489 5489                      return "pressed";
    5490 5490                  }
    5491 5491              };
    5492 5492          }
    5493 5493          return pressed;
    5494 5494      }
    5495 5495
    5496 5496      public final void setOnContextMenuRequested(
    5497 5497              EventHandler<? super ContextMenuEvent> value) {
    5498 5498          onContextMenuRequestedProperty().set(value);
    5499 5499      }
    5500 5500
    5501 5501      public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() {
    5502 5502          return (eventHandlerProperties == null)
    5503 5503                  ? null : eventHandlerProperties.onContextMenuRequested();
    5504 5504      }
    5505 5505
    5506 5506      /**
    5507 5507       * Defines a function to be called when a context menu
    5508 5508       * has been requested on this {@code Node}.
    5509 5509       */
    5510 5510      public final ObjectProperty<EventHandler<? super ContextMenuEvent>>
    5511 5511              onContextMenuRequestedProperty() {
    5512 5512          return getEventHandlerProperties().onContextMenuRequestedProperty();
    5513 5513      }
    5514 5514
    5515 5515      public final void setOnMouseClicked(
    5516 5516              EventHandler<? super MouseEvent> value) {
    5517 5517          onMouseClickedProperty().set(value);
    5518 5518      }
    5519 5519
    5520 5520      public final EventHandler<? super MouseEvent> getOnMouseClicked() {
    5521 5521          return (eventHandlerProperties == null)
    5522 5522                  ? null : eventHandlerProperties.getOnMouseClicked();
    5523 5523      }
    5524 5524
    5525 5525      /**
    5526 5526       * Defines a function to be called when a mouse button has been clicked
    5527 5527       * (pressed and released) on this {@code Node}.
    5528 5528       */
    5529 5529      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5530 5530              onMouseClickedProperty() {
    5531 5531          return getEventHandlerProperties().onMouseClickedProperty();
    5532 5532      }
    5533 5533
    5534 5534      public final void setOnMouseDragged(
    5535 5535              EventHandler<? super MouseEvent> value) {
    5536 5536          onMouseDraggedProperty().set(value);
    5537 5537      }
    5538 5538
    5539 5539      public final EventHandler<? super MouseEvent> getOnMouseDragged() {
    5540 5540          return (eventHandlerProperties == null)
    5541 5541                  ? null : eventHandlerProperties.getOnMouseDragged();
    5542 5542      }
    5543 5543
    5544 5544      /**
    5545 5545       * Defines a function to be called when a mouse button is pressed
    5546 5546       * on this {@code Node} and then dragged.
    5547 5547       */
    5548 5548      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5549 5549              onMouseDraggedProperty() {
    5550 5550          return getEventHandlerProperties().onMouseDraggedProperty();
    5551 5551      }
    5552 5552
    5553 5553      public final void setOnMouseEntered(
    5554 5554              EventHandler<? super MouseEvent> value) {
    5555 5555          onMouseEnteredProperty().set(value);
    5556 5556      }
    5557 5557
    5558 5558      public final EventHandler<? super MouseEvent> getOnMouseEntered() {
    5559 5559          return (eventHandlerProperties == null)
    5560 5560                  ? null : eventHandlerProperties.getOnMouseEntered();
    5561 5561      }
    5562 5562
    5563 5563      /**
    5564 5564       * Defines a function to be called when the mouse enters this {@code Node}.
    5565 5565       */
    5566 5566      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5567 5567              onMouseEnteredProperty() {
    5568 5568          return getEventHandlerProperties().onMouseEnteredProperty();
    5569 5569      }
    5570 5570
    5571 5571      public final void setOnMouseExited(
    5572 5572              EventHandler<? super MouseEvent> value) {
    5573 5573          onMouseExitedProperty().set(value);
    5574 5574      }
    5575 5575
    5576 5576      public final EventHandler<? super MouseEvent> getOnMouseExited() {
    5577 5577          return (eventHandlerProperties == null)
    5578 5578                  ? null : eventHandlerProperties.getOnMouseExited();
    5579 5579      }
    5580 5580
    5581 5581      /**
    5582 5582       * Defines a function to be called when the mouse exits this {@code Node}.
    5583 5583       */
    5584 5584      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5585 5585              onMouseExitedProperty() {
    5586 5586          return getEventHandlerProperties().onMouseExitedProperty();
    5587 5587      }
    5588 5588
    5589 5589      public final void setOnMouseMoved(
    5590 5590              EventHandler<? super MouseEvent> value) {
    5591 5591          onMouseMovedProperty().set(value);
    5592 5592      }
    5593 5593
    5594 5594      public final EventHandler<? super MouseEvent> getOnMouseMoved() {
    5595 5595          return (eventHandlerProperties == null)
    5596 5596                  ? null : eventHandlerProperties.getOnMouseMoved();
    5597 5597      }
    5598 5598
    5599 5599      /**
    5600 5600       * Defines a function to be called when mouse cursor moves within
    5601 5601       * this {@code Node} but no buttons have been pushed.
    5602 5602       */
    5603 5603      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5604 5604              onMouseMovedProperty() {
    5605 5605          return getEventHandlerProperties().onMouseMovedProperty();
    5606 5606      }
    5607 5607
    5608 5608      public final void setOnMousePressed(
    5609 5609              EventHandler<? super MouseEvent> value) {
    5610 5610          onMousePressedProperty().set(value);
    5611 5611      }
    5612 5612
    5613 5613      public final EventHandler<? super MouseEvent> getOnMousePressed() {
    5614 5614          return (eventHandlerProperties == null)
    5615 5615                  ? null : eventHandlerProperties.getOnMousePressed();
    5616 5616      }
    5617 5617
    5618 5618      /**
    5619 5619       * Defines a function to be called when a mouse button
    5620 5620       * has been pressed on this {@code Node}.
    5621 5621       */
    5622 5622      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5623 5623              onMousePressedProperty() {
    5624 5624          return getEventHandlerProperties().onMousePressedProperty();
    5625 5625      }
    5626 5626
    5627 5627      public final void setOnMouseReleased(
    5628 5628              EventHandler<? super MouseEvent> value) {
    5629 5629          onMouseReleasedProperty().set(value);
    5630 5630      }
    5631 5631
    5632 5632      public final EventHandler<? super MouseEvent> getOnMouseReleased() {
    5633 5633          return (eventHandlerProperties == null)
    5634 5634                  ? null : eventHandlerProperties.getOnMouseReleased();
    5635 5635      }
    5636 5636
    5637 5637      /**
    5638 5638       * Defines a function to be called when a mouse button
    5639 5639       * has been released on this {@code Node}.
    5640 5640       */
    5641 5641      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5642 5642              onMouseReleasedProperty() {
    5643 5643          return getEventHandlerProperties().onMouseReleasedProperty();
    5644 5644      }
    5645 5645
    5646 5646      public final void setOnDragDetected(
    5647 5647              EventHandler<? super MouseEvent> value) {
    5648 5648          onDragDetectedProperty().set(value);
    5649 5649      }
    5650 5650
    5651 5651      public final EventHandler<? super MouseEvent> getOnDragDetected() {
    5652 5652          return (eventHandlerProperties == null)
    5653 5653                  ? null : eventHandlerProperties.getOnDragDetected();
    5654 5654      }
    5655 5655
    5656 5656      /**
    5657 5657       * Defines a function to be called when drag gesture has been
    5658 5658       * detected. This is the right place to start drag and drop operation.
    5659 5659       */
    5660 5660      public final ObjectProperty<EventHandler<? super MouseEvent>>
    5661 5661              onDragDetectedProperty() {
    5662 5662          return getEventHandlerProperties().onDragDetectedProperty();
    5663 5663      }
    5664 5664
    5665 5665      public final void setOnMouseDragOver(
    5666 5666              EventHandler<? super MouseDragEvent> value) {
    5667 5667          onMouseDragOverProperty().set(value);
    5668 5668      }
    5669 5669
    5670 5670      public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
    5671 5671          return (eventHandlerProperties == null)
    5672 5672                  ? null : eventHandlerProperties.getOnMouseDragOver();
    5673 5673      }
    5674 5674
    5675 5675      /**
    5676 5676       * Defines a function to be called when a full press-drag-release gesture
    5677 5677       * progresses within this {@code Node}.
    5678 5678       */
    5679 5679      public final ObjectProperty<EventHandler<? super MouseDragEvent>>
    5680 5680              onMouseDragOverProperty() {
    5681 5681          return getEventHandlerProperties().onMouseDragOverProperty();
    5682 5682      }
    5683 5683
    5684 5684      public final void setOnMouseDragReleased(
    5685 5685              EventHandler<? super MouseDragEvent> value) {
    5686 5686          onMouseDragReleasedProperty().set(value);
    5687 5687      }
    5688 5688
    5689 5689      public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
    5690 5690          return (eventHandlerProperties == null)
    5691 5691                  ? null : eventHandlerProperties.getOnMouseDragReleased();
    5692 5692      }
    5693 5693
    5694 5694      /**
    5695 5695       * Defines a function to be called when a full press-drag-release gesture
    5696 5696       * ends (by releasing mouse button) within this {@code Node}.
    5697 5697       */
    5698 5698      public final ObjectProperty<EventHandler<? super MouseDragEvent>>
    5699 5699              onMouseDragReleasedProperty() {
    5700 5700          return getEventHandlerProperties().onMouseDragReleasedProperty();
    5701 5701      }
    5702 5702
    5703 5703      public final void setOnMouseDragEntered(
    5704 5704              EventHandler<? super MouseDragEvent> value) {
    5705 5705          onMouseDragEnteredProperty().set(value);
    5706 5706      }
    5707 5707
    5708 5708      public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
    5709 5709          return (eventHandlerProperties == null)
    5710 5710                  ? null : eventHandlerProperties.getOnMouseDragEntered();
    5711 5711      }
    5712 5712
    5713 5713      /**
    5714 5714       * Defines a function to be called when a full press-drag-release gesture
    5715 5715       * enters this {@code Node}.
    5716 5716       */
    5717 5717      public final ObjectProperty<EventHandler<? super MouseDragEvent>>
    5718 5718              onMouseDragEnteredProperty() {
    5719 5719          return getEventHandlerProperties().onMouseDragEnteredProperty();
    5720 5720      }
    5721 5721
    5722 5722      public final void setOnMouseDragExited(
    5723 5723              EventHandler<? super MouseDragEvent> value) {
    5724 5724          onMouseDragExitedProperty().set(value);
    5725 5725      }
    5726 5726
    5727 5727      public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
    5728 5728          return (eventHandlerProperties == null)
    5729 5729                  ? null : eventHandlerProperties.getOnMouseDragExited();
    5730 5730      }
    5731 5731
    5732 5732      /**
    5733 5733       * Defines a function to be called when a full press-drag-release gesture
    5734 5734       * leaves this {@code Node}.
    5735 5735       */
    5736 5736      public final ObjectProperty<EventHandler<? super MouseDragEvent>>
    5737 5737              onMouseDragExitedProperty() {
    5738 5738          return getEventHandlerProperties().onMouseDragExitedProperty();
    5739 5739      }
    5740 5740
    5741 5741
    5742 5742      /* *************************************************************************
    5743 5743       *                                                                         *
    5744 5744       *                           Gestures Handling                             *
    5745 5745       *                                                                         *
    5746 5746       **************************************************************************/
    5747 5747
    5748 5748      public final void setOnScrollStarted(
    5749 5749              EventHandler<? super ScrollEvent> value) {
    5750 5750          onScrollStartedProperty().set(value);
    5751 5751      }
    5752 5752
    5753 5753      public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
    5754 5754          return (eventHandlerProperties == null)
    5755 5755                  ? null : eventHandlerProperties.getOnScrollStarted();
    5756 5756      }
    5757 5757
    5758 5758      /**
    5759 5759       * Defines a function to be called when a scrolling gesture is detected.
    5760 5760       * @since 2.2
    5761 5761       */
    5762 5762      public final ObjectProperty<EventHandler<? super ScrollEvent>>
    5763 5763              onScrollStartedProperty() {
    5764 5764          return getEventHandlerProperties().onScrollStartedProperty();
    5765 5765      }
    5766 5766
    5767 5767      public final void setOnScroll(
    5768 5768              EventHandler<? super ScrollEvent> value) {
    5769 5769          onScrollProperty().set(value);
    5770 5770      }
    5771 5771
    5772 5772      public final EventHandler<? super ScrollEvent> getOnScroll() {
    5773 5773          return (eventHandlerProperties == null)
    5774 5774                  ? null : eventHandlerProperties.getOnScroll();
    5775 5775      }
    5776 5776
    5777 5777      /**
    5778 5778       * Defines a function to be called when user performs a scrolling action.
    5779 5779       */
    5780 5780      public final ObjectProperty<EventHandler<? super ScrollEvent>>
    5781 5781              onScrollProperty() {
    5782 5782          return getEventHandlerProperties().onScrollProperty();
    5783 5783      }
    5784 5784
    5785 5785      public final void setOnScrollFinished(
    5786 5786              EventHandler<? super ScrollEvent> value) {
    5787 5787          onScrollFinishedProperty().set(value);
    5788 5788      }
    5789 5789
    5790 5790      public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
    5791 5791          return (eventHandlerProperties == null)
    5792 5792                  ? null : eventHandlerProperties.getOnScrollFinished();
    5793 5793      }
    5794 5794
    5795 5795      /**
    5796 5796       * Defines a function to be called when a scrolling gesture ends.
    5797 5797       * @since 2.2
    5798 5798       */
    5799 5799      public final ObjectProperty<EventHandler<? super ScrollEvent>>
    5800 5800              onScrollFinishedProperty() {
    5801 5801          return getEventHandlerProperties().onScrollFinishedProperty();
    5802 5802      }
    5803 5803
    5804 5804      public final void setOnRotationStarted(
    5805 5805              EventHandler<? super RotateEvent> value) {
    5806 5806          onRotationStartedProperty().set(value);
    5807 5807      }
    5808 5808
    5809 5809      public final EventHandler<? super RotateEvent> getOnRotationStarted() {
    5810 5810          return (eventHandlerProperties == null)
    5811 5811                  ? null : eventHandlerProperties.getOnRotationStarted();
    5812 5812      }
    5813 5813
    5814 5814      /**
    5815 5815       * Defines a function to be called when a rotation gesture is detected.
    5816 5816       * @since 2.2
    5817 5817       */
    5818 5818      public final ObjectProperty<EventHandler<? super RotateEvent>>
    5819 5819              onRotationStartedProperty() {
    5820 5820          return getEventHandlerProperties().onRotationStartedProperty();
    5821 5821      }
    5822 5822
    5823 5823      public final void setOnRotate(
    5824 5824              EventHandler<? super RotateEvent> value) {
    5825 5825          onRotateProperty().set(value);
    5826 5826      }
    5827 5827
    5828 5828      public final EventHandler<? super RotateEvent> getOnRotate() {
    5829 5829          return (eventHandlerProperties == null)
    5830 5830                  ? null : eventHandlerProperties.getOnRotate();
    5831 5831      }
    5832 5832
    5833 5833      /**
    5834 5834       * Defines a function to be called when user performs a rotation action.
    5835 5835       * @since 2.2
    5836 5836       */
    5837 5837      public final ObjectProperty<EventHandler<? super RotateEvent>>
    5838 5838              onRotateProperty() {
    5839 5839          return getEventHandlerProperties().onRotateProperty();
    5840 5840      }
    5841 5841
    5842 5842      public final void setOnRotationFinished(
    5843 5843              EventHandler<? super RotateEvent> value) {
    5844 5844          onRotationFinishedProperty().set(value);
    5845 5845      }
    5846 5846
    5847 5847      public final EventHandler<? super RotateEvent> getOnRotationFinished() {
    5848 5848          return (eventHandlerProperties == null)
    5849 5849                  ? null : eventHandlerProperties.getOnRotationFinished();
    5850 5850      }
    5851 5851
    5852 5852      /**
    5853 5853       * Defines a function to be called when a rotation gesture ends.
    5854 5854       * @since 2.2
    5855 5855       */
    5856 5856      public final ObjectProperty<EventHandler<? super RotateEvent>>
    5857 5857              onRotationFinishedProperty() {
    5858 5858          return getEventHandlerProperties().onRotationFinishedProperty();
    5859 5859      }
    5860 5860
    5861 5861      public final void setOnZoomStarted(
    5862 5862              EventHandler<? super ZoomEvent> value) {
    5863 5863          onZoomStartedProperty().set(value);
    5864 5864      }
    5865 5865
    5866 5866      public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
    5867 5867          return (eventHandlerProperties == null)
    5868 5868                  ? null : eventHandlerProperties.getOnZoomStarted();
    5869 5869      }
    5870 5870
    5871 5871      /**
    5872 5872       * Defines a function to be called when a zooming gesture is detected.
    5873 5873       * @since 2.2
    5874 5874       */
    5875 5875      public final ObjectProperty<EventHandler<? super ZoomEvent>>
    5876 5876              onZoomStartedProperty() {
    5877 5877          return getEventHandlerProperties().onZoomStartedProperty();
    5878 5878      }
    5879 5879
    5880 5880      public final void setOnZoom(
    5881 5881              EventHandler<? super ZoomEvent> value) {
    5882 5882          onZoomProperty().set(value);
    5883 5883      }
    5884 5884
    5885 5885      public final EventHandler<? super ZoomEvent> getOnZoom() {
    5886 5886          return (eventHandlerProperties == null)
    5887 5887                  ? null : eventHandlerProperties.getOnZoom();
    5888 5888      }
    5889 5889
    5890 5890      /**
    5891 5891       * Defines a function to be called when user performs a zooming action.
    5892 5892       * @since 2.2
    5893 5893       */
    5894 5894      public final ObjectProperty<EventHandler<? super ZoomEvent>>
    5895 5895              onZoomProperty() {
    5896 5896          return getEventHandlerProperties().onZoomProperty();
    5897 5897      }
    5898 5898
    5899 5899      public final void setOnZoomFinished(
    5900 5900              EventHandler<? super ZoomEvent> value) {
    5901 5901          onZoomFinishedProperty().set(value);
    5902 5902      }
    5903 5903
    5904 5904      public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
    5905 5905          return (eventHandlerProperties == null)
    5906 5906                  ? null : eventHandlerProperties.getOnZoomFinished();
    5907 5907      }
    5908 5908
    5909 5909      /**
    5910 5910       * Defines a function to be called when a zooming gesture ends.
    5911 5911       * @since 2.2
    5912 5912       */
    5913 5913      public final ObjectProperty<EventHandler<? super ZoomEvent>>
    5914 5914              onZoomFinishedProperty() {
    5915 5915          return getEventHandlerProperties().onZoomFinishedProperty();
    5916 5916      }
    5917 5917
    5918 5918      public final void setOnSwipeUp(
    5919 5919              EventHandler<? super SwipeEvent> value) {
    5920 5920          onSwipeUpProperty().set(value);
    5921 5921      }
    5922 5922
    5923 5923      public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
    5924 5924          return (eventHandlerProperties == null)
    5925 5925                  ? null : eventHandlerProperties.getOnSwipeUp();
    5926 5926      }
    5927 5927
    5928 5928      /**
    5929 5929       * Defines a function to be called when an upward swipe gesture
    5930 5930       * centered over this node happens.
    5931 5931       * @since 2.2
    5932 5932       */
    5933 5933      public final ObjectProperty<EventHandler<? super SwipeEvent>>
    5934 5934              onSwipeUpProperty() {
    5935 5935          return getEventHandlerProperties().onSwipeUpProperty();
    5936 5936      }
    5937 5937
    5938 5938      public final void setOnSwipeDown(
    5939 5939              EventHandler<? super SwipeEvent> value) {
    5940 5940          onSwipeDownProperty().set(value);
    5941 5941      }
    5942 5942
    5943 5943      public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
    5944 5944          return (eventHandlerProperties == null)
    5945 5945                  ? null : eventHandlerProperties.getOnSwipeDown();
    5946 5946      }
    5947 5947
    5948 5948      /**
    5949 5949       * Defines a function to be called when a downward swipe gesture
    5950 5950       * centered over this node happens.
    5951 5951       * @since 2.2
    5952 5952       */
    5953 5953      public final ObjectProperty<EventHandler<? super SwipeEvent>>
    5954 5954              onSwipeDownProperty() {
    5955 5955          return getEventHandlerProperties().onSwipeDownProperty();
    5956 5956      }
    5957 5957
    5958 5958      public final void setOnSwipeLeft(
    5959 5959              EventHandler<? super SwipeEvent> value) {
    5960 5960          onSwipeLeftProperty().set(value);
    5961 5961      }
    5962 5962
    5963 5963      public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
    5964 5964          return (eventHandlerProperties == null)
    5965 5965                  ? null : eventHandlerProperties.getOnSwipeLeft();
    5966 5966      }
    5967 5967
    5968 5968      /**
    5969 5969       * Defines a function to be called when a leftward swipe gesture
    5970 5970       * centered over this node happens.
    5971 5971       * @since 2.2
    5972 5972       */
    5973 5973      public final ObjectProperty<EventHandler<? super SwipeEvent>>
    5974 5974              onSwipeLeftProperty() {
    5975 5975          return getEventHandlerProperties().onSwipeLeftProperty();
    5976 5976      }
    5977 5977
    5978 5978      public final void setOnSwipeRight(
    5979 5979              EventHandler<? super SwipeEvent> value) {
    5980 5980          onSwipeRightProperty().set(value);
    5981 5981      }
    5982 5982
    5983 5983      public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
    5984 5984          return (eventHandlerProperties == null)
    5985 5985                  ? null : eventHandlerProperties.getOnSwipeRight();
    5986 5986      }
    5987 5987
    5988 5988      /**
    5989 5989       * Defines a function to be called when an rightward swipe gesture
    5990 5990       * centered over this node happens.
    5991 5991       * @since 2.2
    5992 5992       */
    5993 5993      public final ObjectProperty<EventHandler<? super SwipeEvent>>
    5994 5994              onSwipeRightProperty() {
    5995 5995          return getEventHandlerProperties().onSwipeRightProperty();
    5996 5996      }
    5997 5997
    5998 5998
    5999 5999      /* *************************************************************************
    6000 6000       *                                                                         *
    6001 6001       *                             Touch Handling                              *
    6002 6002       *                                                                         *
    6003 6003       **************************************************************************/
    6004 6004
    6005 6005      public final void setOnTouchPressed(
    6006 6006              EventHandler<? super TouchEvent> value) {
    6007 6007          onTouchPressedProperty().set(value);
    6008 6008      }
    6009 6009
    6010 6010      public final EventHandler<? super TouchEvent> getOnTouchPressed() {
    6011 6011          return (eventHandlerProperties == null)
    6012 6012                  ? null : eventHandlerProperties.getOnTouchPressed();
    6013 6013      }
    6014 6014
    6015 6015      /**
    6016 6016       * Defines a function to be called when a new touch point is pressed.
    6017 6017       * @since 2.2
    6018 6018       */
    6019 6019      public final ObjectProperty<EventHandler<? super TouchEvent>>
    6020 6020              onTouchPressedProperty() {
    6021 6021          return getEventHandlerProperties().onTouchPressedProperty();
    6022 6022      }
    6023 6023
    6024 6024      public final void setOnTouchMoved(
    6025 6025              EventHandler<? super TouchEvent> value) {
    6026 6026          onTouchMovedProperty().set(value);
    6027 6027      }
    6028 6028
    6029 6029      public final EventHandler<? super TouchEvent> getOnTouchMoved() {
    6030 6030          return (eventHandlerProperties == null)
    6031 6031                  ? null : eventHandlerProperties.getOnTouchMoved();
    6032 6032      }
    6033 6033
    6034 6034      /**
    6035 6035       * Defines a function to be called when a touch point is moved.
    6036 6036       * @since 2.2
    6037 6037       */
    6038 6038      public final ObjectProperty<EventHandler<? super TouchEvent>>
    6039 6039              onTouchMovedProperty() {
    6040 6040          return getEventHandlerProperties().onTouchMovedProperty();
    6041 6041      }
    6042 6042
    6043 6043      public final void setOnTouchReleased(
    6044 6044              EventHandler<? super TouchEvent> value) {
    6045 6045          onTouchReleasedProperty().set(value);
    6046 6046      }
    6047 6047
    6048 6048      public final EventHandler<? super TouchEvent> getOnTouchReleased() {
    6049 6049          return (eventHandlerProperties == null)
    6050 6050                  ? null : eventHandlerProperties.getOnTouchReleased();
    6051 6051      }
    6052 6052
    6053 6053      /**
    6054 6054       * Defines a function to be called when a touch point is released.
    6055 6055       * @since 2.2
    6056 6056       */
    6057 6057      public final ObjectProperty<EventHandler<? super TouchEvent>>
    6058 6058              onTouchReleasedProperty() {
    6059 6059          return getEventHandlerProperties().onTouchReleasedProperty();
    6060 6060      }
    6061 6061
    6062 6062      public final void setOnTouchStationary(
    6063 6063              EventHandler<? super TouchEvent> value) {
    6064 6064          onTouchStationaryProperty().set(value);
    6065 6065      }
    6066 6066
    6067 6067      public final EventHandler<? super TouchEvent> getOnTouchStationary() {
    6068 6068          return (eventHandlerProperties == null)
    6069 6069                  ? null : eventHandlerProperties.getOnTouchStationary();
    6070 6070      }
    6071 6071
    6072 6072      /**
    6073 6073       * Defines a function to be called when a touch point stays pressed and
    6074 6074       * still.
    6075 6075       * @since 2.2
    6076 6076       */
    6077 6077      public final ObjectProperty<EventHandler<? super TouchEvent>>
    6078 6078              onTouchStationaryProperty() {
    6079 6079          return getEventHandlerProperties().onTouchStationaryProperty();
    6080 6080      }
    6081 6081
    6082 6082      /* *************************************************************************
    6083 6083       *                                                                         *
    6084 6084       *                           Keyboard Handling                             *
    6085 6085       *                                                                         *
    6086 6086       **************************************************************************/
    6087 6087
    6088 6088      public final void setOnKeyPressed(
    6089 6089              EventHandler<? super KeyEvent> value) {
    6090 6090          onKeyPressedProperty().set(value);
    6091 6091      }
    6092 6092
    6093 6093      public final EventHandler<? super KeyEvent> getOnKeyPressed() {
    6094 6094          return (eventHandlerProperties == null)
    6095 6095                  ? null : eventHandlerProperties.getOnKeyPressed();
    6096 6096      }
    6097 6097
    6098 6098      /**
    6099 6099       * Defines a function to be called when this {@code Node} or its child
    6100 6100       * {@code Node} has input focus and a key has been pressed. The function
    6101 6101       * is called only if the event hasn't been already consumed during its
    6102 6102       * capturing or bubbling phase.
    6103 6103       */
    6104 6104      public final ObjectProperty<EventHandler<? super KeyEvent>>
    6105 6105              onKeyPressedProperty() {
    6106 6106          return getEventHandlerProperties().onKeyPressedProperty();
    6107 6107      }
    6108 6108
    6109 6109      public final void setOnKeyReleased(
    6110 6110              EventHandler<? super KeyEvent> value) {
    6111 6111          onKeyReleasedProperty().set(value);
    6112 6112      }
    6113 6113
    6114 6114      public final EventHandler<? super KeyEvent> getOnKeyReleased() {
    6115 6115          return (eventHandlerProperties == null)
    6116 6116                  ? null : eventHandlerProperties.getOnKeyReleased();
    6117 6117      }
    6118 6118
    6119 6119      /**
    6120 6120       * Defines a function to be called when this {@code Node} or its child
    6121 6121       * {@code Node} has input focus and a key has been released. The function
    6122 6122       * is called only if the event hasn't been already consumed during its
    6123 6123       * capturing or bubbling phase.
    6124 6124       */
    6125 6125      public final ObjectProperty<EventHandler<? super KeyEvent>>
    6126 6126              onKeyReleasedProperty() {
    6127 6127          return getEventHandlerProperties().onKeyReleasedProperty();
    6128 6128      }
    6129 6129
    6130 6130      public final void setOnKeyTyped(
    6131 6131              EventHandler<? super KeyEvent> value) {
    6132 6132          onKeyTypedProperty().set(value);
    6133 6133      }
    6134 6134
    6135 6135      public final EventHandler<? super KeyEvent> getOnKeyTyped() {
    6136 6136          return (eventHandlerProperties == null)
    6137 6137                  ? null : eventHandlerProperties.getOnKeyTyped();
    6138 6138      }
    6139 6139
    6140 6140      /**
    6141 6141       * Defines a function to be called when this {@code Node} or its child
    6142 6142       * {@code Node} has input focus and a key has been typed. The function
    6143 6143       * is called only if the event hasn't been already consumed during its
    6144 6144       * capturing or bubbling phase.
    6145 6145       */
    6146 6146      public final ObjectProperty<EventHandler<? super KeyEvent>>
    6147 6147              onKeyTypedProperty() {
    6148 6148          return getEventHandlerProperties().onKeyTypedProperty();
    6149 6149      }
    6150 6150
    6151 6151      /* *************************************************************************
    6152 6152       *                                                                         *
    6153 6153       *                           Input Method Handling                         *
    6154 6154       *                                                                         *
    6155 6155       **************************************************************************/
    6156 6156
    6157 6157      public final void setOnInputMethodTextChanged(
    6158 6158              EventHandler<? super InputMethodEvent> value) {
    6159 6159          onInputMethodTextChangedProperty().set(value);
    6160 6160      }
    6161 6161
    6162 6162      public final EventHandler<? super InputMethodEvent>
    6163 6163              getOnInputMethodTextChanged() {
    6164 6164          return (eventHandlerProperties == null)
    6165 6165                  ? null : eventHandlerProperties.getOnInputMethodTextChanged();
    6166 6166      }
    6167 6167
    6168 6168      /**
    6169 6169       * Defines a function to be called when this {@code Node}
    6170 6170       * has input focus and the input method text has changed.  If this
    6171 6171       * function is not defined in this {@code Node}, then it
    6172 6172       * receives the result string of the input method composition as a
    6173 6173       * series of {@code onKeyTyped} function calls.
    6174 6174       * </p>
    6175 6175       * When the {@code Node} loses the input focus, the JavaFX runtime
    6176 6176       * automatically commits the existing composed text if any.
    6177 6177       */
    6178 6178      public final ObjectProperty<EventHandler<? super InputMethodEvent>>
    6179 6179              onInputMethodTextChangedProperty() {
    6180 6180          return getEventHandlerProperties().onInputMethodTextChangedProperty();
    6181 6181      }
    6182 6182
    6183 6183      public final void setInputMethodRequests(InputMethodRequests value) {
    6184 6184          inputMethodRequestsProperty().set(value);
    6185 6185      }
    6186 6186
    6187 6187      public final InputMethodRequests getInputMethodRequests() {
    6188 6188          return (miscProperties == null)
    6189 6189                         ? DEFAULT_INPUT_METHOD_REQUESTS
    6190 6190                         : miscProperties.getInputMethodRequests();
    6191 6191      }
    6192 6192
    6193 6193      /**
    6194 6194       * Property holding InputMethodRequests.
    6195 6195       *
    6196 6196       * @return InputMethodRequestsProperty
    6197 6197       */
    6198 6198      public final ObjectProperty<InputMethodRequests> inputMethodRequestsProperty() {
    6199 6199          return getMiscProperties().inputMethodRequestsProperty();
    6200 6200      }
    6201 6201
    6202 6202      /***************************************************************************
    6203 6203       *                                                                         *
    6204 6204       *                             Focus Traversal                             *
    6205 6205       *                                                                         *
    6206 6206       **************************************************************************/
    6207 6207
    6208 6208      /**
    6209 6209       * Special boolean property which allows for atomic focus change.
    6210 6210       * Focus change means defocusing the old focus owner and focusing a new
    6211 6211       * one. With a usual property, defocusing the old node fires the value
    6212 6212       * changed event and user code can react with something that breaks
    6213 6213       * focusability of the new node, or even remove the new node from the scene.
    6214 6214       * This leads to various error states. This property allows for setting
    6215 6215       * the state without firing the event. The focus change first sets both
    6216 6216       * properties and then fires both events. This makes the focus change look
    6217 6217       * like an atomic operation - when the old node is notified to loose focus,
    6218 6218       * the new node is already focused.
    6219 6219       */
    6220 6220      final class FocusedProperty extends ReadOnlyBooleanPropertyBase {
    6221 6221          private boolean value;
    6222 6222          private boolean valid = true;
    6223 6223          private boolean needsChangeEvent = false;
    6224 6224
    6225 6225          public void store(final boolean value) {
    6226 6226              if (value != this.value) {
    6227 6227                  this.value = value;
    6228 6228                  markInvalid();
    6229 6229              }
    6230 6230          }
    6231 6231
    6232 6232          public void notifyListeners() {
    6233 6233              if (needsChangeEvent) {
    6234 6234                  fireValueChangedEvent();
    6235 6235                  needsChangeEvent = false;
    6236 6236              }
    6237 6237          }
    6238 6238
    6239 6239          private void markInvalid() {
    6240 6240              if (valid) {
    6241 6241                  valid = false;
    6242 6242
    6243 6243                  impl_pseudoClassStateChanged("focused");
    6244 6244                  PlatformLogger logger = Logging.getFocusLogger();
    6245 6245                  if (logger.isLoggable(PlatformLogger.FINE)) {
    6246 6246                      logger.fine(this + " focused=" + get());
    6247 6247                  }
    6248 6248
    6249 6249                  needsChangeEvent = true;
    6250 6250              }
    6251 6251          }
    6252 6252
    6253 6253          @Override
    6254 6254          public boolean get() {
    6255 6255              valid = true;
    6256 6256              return value;
    6257 6257          }
    6258 6258
    6259 6259          @Override
    6260 6260          public Object getBean() {
    6261 6261              return Node.this;
    6262 6262          }
    6263 6263
    6264 6264          @Override
    6265 6265          public String getName() {
    6266 6266              return "focused";
    6267 6267          }
    6268 6268      }
    6269 6269
    6270 6270      /**
    6271 6271       * Indicates whether this {@code Node} currently has the input focus.
    6272 6272       * To have the input focus, a node must be the {@code Scene}'s focus
    6273 6273       * owner, and the scene must be in a {@code Stage} that is visible
    6274 6274       * and active. See {@link #requestFocus()} for more information.
    6275 6275       *
    6276 6276       * @see #requestFocus()
    6277 6277       * @defaultValue false
    6278 6278       */
    6279 6279      private FocusedProperty focused;
    6280 6280
    6281 6281      protected final void setFocused(boolean value) {
    6282 6282          FocusedProperty fp = focusedPropertyImpl();
    6283 6283          if (fp.value != value) {
    6284 6284              fp.store(value);
    6285 6285              fp.notifyListeners();
    6286 6286          }
    6287 6287      }
    6288 6288
    6289 6289      public final boolean isFocused() {
    6290 6290          return focused == null ? false : focused.get();
    6291 6291      }
    6292 6292
    6293 6293      public final ReadOnlyBooleanProperty focusedProperty() {
    6294 6294          return focusedPropertyImpl();
    6295 6295      }
    6296 6296
    6297 6297      private FocusedProperty focusedPropertyImpl() {
    6298 6298          if (focused == null) {
    6299 6299              focused = new FocusedProperty();
    6300 6300          }
    6301 6301          return focused;
    6302 6302      }
    6303 6303
    6304 6304      /**
    6305 6305       * Specifies whether this {@code Node} should be a part of focus traversal
    6306 6306       * cycle. When this property is {@code true} focus can be moved to this
    6307 6307       * {@code Node} and from this {@code Node} using regular focus traversal
    6308 6308       * keys. On a desktop such keys are usually {@code TAB} for moving focus
    6309 6309       * forward and {@code SHIFT+TAB} for moving focus backward.
    6310 6310       *
    6311 6311       * When a {@code Scene} is created, the system gives focus to a
    6312 6312       * {@code Node} whose {@code focusTraversable} variable is true
    6313 6313       * and that is eligible to receive the focus,
    6314 6314       * unless the focus had been set explicitly via a call
    6315 6315       * to {@link #requestFocus()}.
    6316 6316       *
    6317 6317       * @see #requestFocus()
    6318 6318       * @defaultValue false
    6319 6319       */
    6320 6320      private BooleanProperty focusTraversable;
    6321 6321
    6322 6322      public final void setFocusTraversable(boolean value) {
    6323 6323          focusTraversableProperty().set(value);
    6324 6324      }
    6325 6325      public final boolean isFocusTraversable() {
    6326 6326          return focusTraversable == null ? false : focusTraversable.get();
    6327 6327      }
    6328 6328
    6329 6329      public final BooleanProperty focusTraversableProperty() {
    6330 6330          if (focusTraversable == null) {
    6331 6331              focusTraversable = new StyleableBooleanProperty(false) {
    6332 6332
    6333 6333                  @Override
    6334 6334                  public void invalidated() {
    6335 6335                      Scene _scene = getScene();
    6336 6336                      if (_scene != null) {
    6337 6337                          if (get()) {
    6338 6338                              _scene.registerTraversable(Node.this);
    6339 6339                          } else {
    6340 6340                              _scene.unregisterTraversable(Node.this);
    6341 6341                          }
    6342 6342                          focusSetDirty(_scene);
    6343 6343                      }
    6344 6344                  }
    6345 6345
    6346 6346                  @Override
    6347 6347                  public StyleableProperty getStyleableProperty() {
    6348 6348                      return StyleableProperties.FOCUS_TRAVERSABLE;
    6349 6349                  }
    6350 6350
    6351 6351                  @Override
    6352 6352                  public Object getBean() {
    6353 6353                      return Node.this;
    6354 6354                  }
    6355 6355
    6356 6356                  @Override
    6357 6357                  public String getName() {
    6358 6358                      return "focusTraversable";
    6359 6359                  }
    6360 6360              };
    6361 6361          }
    6362 6362          return focusTraversable;
    6363 6363      }
    6364 6364
    6365 6365      /**
    6366 6366       * Called when something has changed on this node that *may* have made the
    6367 6367       * scene's focus dirty. This covers the cases where this node is the focus
    6368 6368       * owner and it may have lost eligibility, or it's traversable and it may
    6369 6369       * have gained eligibility. Note that we do not want to use disabled
    6370 6370       * or treeVisible here, as this function is called from their
    6371 6371       * "on invalidate" triggers, and using them will cause them to be
    6372 6372       * revalidated. The pulse will revalidate everything and make the final
    6373 6373       * determination.
    6374 6374       */
    6375 6375      private void focusSetDirty(Scene s) {
    6376 6376          if (s != null &&
    6377 6377              (this == s.getFocusOwner() || isFocusTraversable())) {
    6378 6378                  s.setFocusDirty(true);
    6379 6379          }
    6380 6380      }
    6381 6381
    6382 6382      /**
    6383 6383       * Requests that this {@code Node} get the input focus, and that this
    6384 6384       * {@code Node}'s top-level ancestor become the focused window. To be
    6385 6385       * eligible to receive the focus, the node must be part of a scene, it and
    6386 6386       * all of its ancestors must be visible, and it must not be disabled.
    6387 6387       * If this node is eligible, this function will cause it to become this
    6388 6388       * {@code Scene}'s "focus owner". Each scene has at most one focus owner
    6389 6389       * node. The focus owner will not actually have the input focus, however,
    6390 6390       * unless the scene belongs to a {@code Stage} that is both visible
    6391 6391       * and active.
    6392 6392       */
    6393 6393      public void requestFocus() {
    6394 6394          if (getScene() != null) {
    6395 6395              getScene().requestFocus(this);
    6396 6396          }
    6397 6397      }
    6398 6398
    6399 6399      /**
    6400 6400       * Traverses from this node in the direction indicated. Note that this
    6401 6401       * node need not actually have the focus, nor need it be focusTraversable.
    6402 6402       * However, the node must be part of a scene, otherwise this request
    6403 6403       * is ignored.
    6404 6404       *
    6405 6405       * @treatAsPrivate implementation detail
    6406 6406       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6407 6407       */
    6408 6408      @Deprecated
    6409 6409      public final void impl_traverse(Direction dir) {
    6410 6410          if (getScene() == null) {
    6411 6411              return;
    6412 6412          }
    6413 6413          getScene().traverse(this, dir);
    6414 6414      }
    6415 6415
    6416 6416      ////////////////////////////
    6417 6417      //  Private Implementation
    6418 6418      ////////////////////////////
    6419 6419
    6420 6420       /**
    6421 6421        * Returns a string representation for the object.
    6422 6422        * @return a string representation for the object.
    6423 6423        */
    6424 6424      @Override
    6425 6425      public String toString() {
    6426 6426          String klassName = getClass().getName();
    6427 6427          String simpleName = klassName.substring(klassName.lastIndexOf('.')+1);
    6428 6428          StringBuilder sbuf = new StringBuilder(simpleName);
    6429 6429          boolean hasId = id != null && !"".equals(getId());
    6430 6430          boolean hasStyleClass = !getStyleClass().isEmpty();
    6431 6431
    6432 6432          if (!hasId) {
    6433 6433              sbuf.append('@');
    6434 6434              sbuf.append(Integer.toHexString(hashCode()));
    6435 6435          } else {
    6436 6436              sbuf.append("[id=");
    6437 6437              sbuf.append(getId());
    6438 6438              if (!hasStyleClass) sbuf.append("]");
    6439 6439          }
    6440 6440          if (hasStyleClass) {
    6441 6441              if (!hasId) sbuf.append('[');
    6442 6442              else sbuf.append(", ");
    6443 6443              sbuf.append("styleClass=");
    6444 6444              sbuf.append(getStyleClass());
    6445 6445              sbuf.append("]");
    6446 6446          }
    6447 6447          return sbuf.toString();
    6448 6448      }
    6449 6449
    6450 6450      private void preprocessMouseEvent(MouseEvent e) {
    6451 6451          final EventType<?> eventType = e.getEventType();
    6452 6452          if (eventType == MouseEvent.MOUSE_PRESSED) {
    6453 6453              for (Node n = this; n != null; n = n.getParent()) {
    6454 6454                  n.setPressed(e.isPrimaryButtonDown());
    6455 6455              }
    6456 6456              return;
    6457 6457          }
    6458 6458          if (eventType == MouseEvent.MOUSE_RELEASED) {
    6459 6459              for (Node n = this; n != null; n = n.getParent()) {
    6460 6460                  n.setPressed(e.isPrimaryButtonDown());
    6461 6461              }
    6462 6462              return;
    6463 6463          }
    6464 6464
    6465 6465          if (e.getTarget() == this) {
    6466 6466              // the mouse event types are translated only when the node uses
    6467 6467              // its internal event dispatcher, so both entered / exited variants
    6468 6468              // are possible here
    6469 6469
    6470 6470              if ((eventType == MouseEvent.MOUSE_ENTERED)
    6471 6471                      || (eventType == MouseEvent.MOUSE_ENTERED_TARGET)) {
    6472 6472                  setHover(true);
    6473 6473                  return;
    6474 6474              }
    6475 6475
    6476 6476              if ((eventType == MouseEvent.MOUSE_EXITED)
    6477 6477                      || (eventType == MouseEvent.MOUSE_EXITED_TARGET)) {
    6478 6478                  setHover(false);
    6479 6479                  return;
    6480 6480              }
    6481 6481          }
    6482 6482      }
    6483 6483
    6484 6484      private void updateTreeVisible() {
    6485 6485          setTreeVisible(isVisible() && ((getParent() == null) || getParent().impl_isTreeVisible()));
    6486 6486      }
    6487 6487
    6488 6488      private boolean treeVisible;
    6489 6489      private TreeVisiblePropertyReadOnly treeVisibleRO;
    6490 6490
    6491 6491      private void setTreeVisible(boolean value) {
    6492 6492          if (treeVisible != value) {
    6493 6493              treeVisible = value;
    6494 6494              updateCanReceiveFocus();
    6495 6495              focusSetDirty(getScene());
    6496 6496              ((TreeVisiblePropertyReadOnly)treeVisibleProperty()).invalidate();
    6497 6497          }
    6498 6498      }
    6499 6499
    6500 6500      /**
    6501 6501       * @treatAsPrivate implementation detail
    6502 6502       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6503 6503       */
    6504 6504      @Deprecated
    6505 6505      public final boolean impl_isTreeVisible() {
    6506 6506          return treeVisibleProperty().get();
    6507 6507      }
    6508 6508
    6509 6509      BooleanExpression treeVisibleProperty() {
    6510 6510          if (treeVisibleRO == null) {
    6511 6511              treeVisibleRO = new TreeVisiblePropertyReadOnly();
    6512 6512          }
    6513 6513          return treeVisibleRO;
    6514 6514      }
    6515 6515
    6516 6516      class TreeVisiblePropertyReadOnly extends BooleanExpression {
    6517 6517
    6518 6518          private ExpressionHelper<Boolean> helper;
    6519 6519          private boolean valid;
    6520 6520
    6521 6521          @Override
    6522 6522          public void addListener(InvalidationListener listener) {
    6523 6523              helper = ExpressionHelper.addListener(helper, this, listener);
    6524 6524          }
    6525 6525
    6526 6526          @Override
    6527 6527          public void removeListener(InvalidationListener listener) {
    6528 6528              helper = ExpressionHelper.removeListener(helper, listener);
    6529 6529          }
    6530 6530
    6531 6531          @Override
    6532 6532          public void addListener(ChangeListener<? super Boolean> listener) {
    6533 6533              helper = ExpressionHelper.addListener(helper, this, listener);
    6534 6534          }
    6535 6535
    6536 6536          @Override
    6537 6537          public void removeListener(ChangeListener<? super Boolean> listener) {
    6538 6538              helper = ExpressionHelper.removeListener(helper, listener);
    6539 6539          }
    6540 6540
    6541 6541          protected void invalidate() {
    6542 6542              if (valid) {
    6543 6543                  valid = false;
    6544 6544                  ExpressionHelper.fireValueChangedEvent(helper);
    6545 6545              }
    6546 6546          }
    6547 6547
    6548 6548          @Override
    6549 6549          public boolean get() {
    6550 6550              valid = true;
    6551 6551              return Node.this.treeVisible;
    6552 6552          }
    6553 6553
    6554 6554      }
    6555 6555
    6556 6556      private boolean canReceiveFocus = false;
    6557 6557
    6558 6558      private void setCanReceiveFocus(boolean value) {
    6559 6559          canReceiveFocus = value;
    6560 6560      }
    6561 6561
    6562 6562      final boolean isCanReceiveFocus() {
    6563 6563          return canReceiveFocus;
    6564 6564      }
    6565 6565
    6566 6566      private void updateCanReceiveFocus() {
    6567 6567          setCanReceiveFocus(getScene() != null
    6568 6568            && !isDisabled()
    6569 6569            && impl_isTreeVisible());
    6570 6570      }
    6571 6571
    6572 6572      // for indenting messages based on scene-graph depth
    6573 6573      String indent() {
    6574 6574          String indent = "";
    6575 6575          Parent p = this.getParent();
    6576 6576          while (p != null) {
    6577 6577              indent += "  ";
    6578 6578              p = p.getParent();
    6579 6579          }
    6580 6580          return indent;
    6581 6581      }
    6582 6582
    6583 6583
    6584 6584
    6585 6585
    6586 6586      /**
    6587 6587       * Should we underline the mnemonic character?
    6588 6588       *
    6589 6589       * @treatAsPrivate implementation detail
    6590 6590       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6591 6591       */
    6592 6592      @Deprecated
    6593 6593      private BooleanProperty impl_showMnemonics;
    6594 6594
    6595 6595      /**
    6596 6596       * @treatAsPrivate implementation detail
    6597 6597       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6598 6598       */
    6599 6599      @Deprecated
    6600 6600      public final void impl_setShowMnemonics(boolean value) {
    6601 6601          impl_showMnemonicsProperty().set(value);
    6602 6602      }
    6603 6603
    6604 6604      /**
    6605 6605       * @treatAsPrivate implementation detail
    6606 6606       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6607 6607       */
    6608 6608      @Deprecated
    6609 6609      public final boolean impl_isShowMnemonics() {
    6610 6610          return impl_showMnemonics == null ? false : impl_showMnemonics.get();
    6611 6611      }
    6612 6612
    6613 6613      /**
    6614 6614       * @treatAsPrivate implementation detail
    6615 6615       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    6616 6616       */
    6617 6617      @Deprecated
    6618 6618      public final BooleanProperty impl_showMnemonicsProperty() {
    6619 6619          if (impl_showMnemonics == null) {
    6620 6620              impl_showMnemonics = new BooleanPropertyBase(false) {
    6621 6621
    6622 6622                  @Override
    6623 6623                  protected void invalidated() {
    6624 6624                      impl_pseudoClassStateChanged("show-mnemonics");
    6625 6625                  }
    6626 6626                  @Override
    6627 6627                  public Object getBean() {
    6628 6628                      return Node.this;
    6629 6629                  }
    6630 6630
    6631 6631                  @Override
    6632 6632                  public String getName() {
    6633 6633                      return "showMnemonics";
    6634 6634                  }
    6635 6635              };
    6636 6636          }
    6637 6637          return impl_showMnemonics;
    6638 6638      }
    6639 6639
    6640 6640
    6641 6641
    6642 6642
    6643 6643
    6644 6644      /***************************************************************************
    6645 6645       *                                                                         *
    6646 6646       *                         Event Dispatch                                  *
    6647 6647       *                                                                         *
    6648 6648       **************************************************************************/
    6649 6649
    6650 6650      // PENDING_DOC_REVIEW
    6651 6651      /**
    6652 6652       * Specifies the event dispatcher for this node. The default event
    6653 6653       * dispatcher sends the received events to the registered event handlers and
    6654 6654       * filters. When replacing the value with a new {@code EventDispatcher},
    6655 6655       * the new dispatcher should forward events to the replaced dispatcher
    6656 6656       * to maintain the node's default event handling behavior.
    6657 6657       */
    6658 6658      private ObjectProperty<EventDispatcher> eventDispatcher;
    6659 6659
    6660 6660      public final void setEventDispatcher(EventDispatcher value) {
    6661 6661          eventDispatcherProperty().set(value);
    6662 6662      }
    6663 6663
    6664 6664      public final EventDispatcher getEventDispatcher() {
    6665 6665          return eventDispatcherProperty().get();
    6666 6666      }
    6667 6667
    6668 6668      public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
    6669 6669          initializeInternalEventDispatcher();
    6670 6670          return eventDispatcher;
    6671 6671      }
    6672 6672
    6673 6673      private NodeEventDispatcher internalEventDispatcher;
    6674 6674
    6675 6675      // PENDING_DOC_REVIEW
    6676 6676      /**
    6677 6677       * Registers an event handler to this node. The handler is called when the
    6678 6678       * node receives an {@code Event} of the specified type during the bubbling
    6679 6679       * phase of event delivery.
    6680 6680       *
    6681 6681       * @param <T> the specific event class of the handler
    6682 6682       * @param eventType the type of the events to receive by the handler
    6683 6683       * @param eventHandler the handler to register
    6684 6684       * @throws NullPointerException if the event type or handler is null
    6685 6685       */
    6686 6686      public final <T extends Event> void addEventHandler(
    6687 6687              final EventType<T> eventType,
    6688 6688              final EventHandler<? super T> eventHandler) {
    6689 6689          getInternalEventDispatcher().getEventHandlerManager()
    6690 6690                                      .addEventHandler(eventType, eventHandler);
    6691 6691      }
    6692 6692
    6693 6693      // PENDING_DOC_REVIEW
    6694 6694      /**
    6695 6695       * Unregisters a previously registered event handler from this node. One
    6696 6696       * handler might have been registered for different event types, so the
    6697 6697       * caller needs to specify the particular event type from which to
    6698 6698       * unregister the handler.
    6699 6699       *
    6700 6700       * @param <T> the specific event class of the handler
    6701 6701       * @param eventType the event type from which to unregister
    6702 6702       * @param eventHandler the handler to unregister
    6703 6703       * @throws NullPointerException if the event type or handler is null
    6704 6704       */
    6705 6705      public final <T extends Event> void removeEventHandler(
    6706 6706              final EventType<T> eventType,
    6707 6707              final EventHandler<? super T> eventHandler) {
    6708 6708          getInternalEventDispatcher()
    6709 6709                  .getEventHandlerManager()
    6710 6710                  .removeEventHandler(eventType, eventHandler);
    6711 6711      }
    6712 6712
    6713 6713      // PENDING_DOC_REVIEW
    6714 6714      /**
    6715 6715       * Registers an event filter to this node. The filter is called when the
    6716 6716       * node receives an {@code Event} of the specified type during the capturing
    6717 6717       * phase of event delivery.
    6718 6718       *
    6719 6719       * @param <T> the specific event class of the filter
    6720 6720       * @param eventType the type of the events to receive by the filter
    6721 6721       * @param eventFilter the filter to register
    6722 6722       * @throws NullPointerException if the event type or filter is null
    6723 6723       */
    6724 6724      public final <T extends Event> void addEventFilter(
    6725 6725              final EventType<T> eventType,
    6726 6726              final EventHandler<? super T> eventFilter) {
    6727 6727          getInternalEventDispatcher().getEventHandlerManager()
    6728 6728                                      .addEventFilter(eventType, eventFilter);
    6729 6729      }
    6730 6730
    6731 6731      // PENDING_DOC_REVIEW
    6732 6732      /**
    6733 6733       * Unregisters a previously registered event filter from this node. One
    6734 6734       * filter might have been registered for different event types, so the
    6735 6735       * caller needs to specify the particular event type from which to
    6736 6736       * unregister the filter.
    6737 6737       *
    6738 6738       * @param <T> the specific event class of the filter
    6739 6739       * @param eventType the event type from which to unregister
    6740 6740       * @param eventFilter the filter to unregister
    6741 6741       * @throws NullPointerException if the event type or filter is null
    6742 6742       */
    6743 6743      public final <T extends Event> void removeEventFilter(
    6744 6744              final EventType<T> eventType,
    6745 6745              final EventHandler<? super T> eventFilter) {
    6746 6746          getInternalEventDispatcher().getEventHandlerManager()
    6747 6747                                      .removeEventFilter(eventType, eventFilter);
    6748 6748      }
    6749 6749
    6750 6750      /**
    6751 6751       * Sets the handler to use for this event type. There can only be one such handler
    6752 6752       * specified at a time. This handler is guaranteed to be called first. This is
    6753 6753       * used for registering the user-defined onFoo event handlers.
    6754 6754       *
    6755 6755       * @param <T> the specific event class of the handler
    6756 6756       * @param eventType the event type to associate with the given eventHandler
    6757 6757       * @param eventHandler the handler to register, or null to unregister
    6758 6758       * @throws NullPointerException if the event type is null
    6759 6759       */
    6760 6760      protected final <T extends Event> void setEventHandler(
    6761 6761              final EventType<T> eventType,
    6762 6762              final EventHandler<? super T> eventHandler) {
    6763 6763          getInternalEventDispatcher().getEventHandlerManager()
    6764 6764                                      .setEventHandler(eventType, eventHandler);
    6765 6765      }
    6766 6766
    6767 6767      private NodeEventDispatcher getInternalEventDispatcher() {
    6768 6768          initializeInternalEventDispatcher();
    6769 6769          return internalEventDispatcher;
    6770 6770      }
    6771 6771
    6772 6772      private void initializeInternalEventDispatcher() {
    6773 6773          if (internalEventDispatcher == null) {
    6774 6774              internalEventDispatcher = createInternalEventDispatcher();
    6775 6775              eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
    6776 6776                                            Node.this,
    6777 6777                                            "eventDispatcher",
    6778 6778                                            internalEventDispatcher);
    6779 6779          }
    6780 6780      }
    6781 6781
    6782 6782      private NodeEventDispatcher createInternalEventDispatcher() {
    6783 6783          return new NodeEventDispatcher(this);
    6784 6784      }
    6785 6785
    6786 6786      /**
    6787 6787       * Event dispatcher for invoking preprocessing of mouse events
    6788 6788       */
    6789 6789      private EventDispatcher preprocessMouseEventDispatcher;
    6790 6790
    6791 6791      // PENDING_DOC_REVIEW
    6792 6792      /**
    6793 6793       * Construct an event dispatch chain for this node. The event dispatch chain
    6794 6794       * contains all event dispatchers from the stage to this node.
    6795 6795       *
    6796 6796       * @param tail the initial chain to build from
    6797 6797       * @return the resulting event dispatch chain for this node
    6798 6798       */
    6799 6799      @Override
    6800 6800      public EventDispatchChain buildEventDispatchChain(
    6801 6801              EventDispatchChain tail) {
    6802 6802
    6803 6803          if (preprocessMouseEventDispatcher == null) {
    6804 6804              preprocessMouseEventDispatcher = new EventDispatcher() {
    6805 6805                  @Override
    6806 6806                  public Event dispatchEvent(Event event,
    6807 6807                                             EventDispatchChain tail) {
    6808 6808                      event = tail.dispatchEvent(event);
    6809 6809                      if (event instanceof MouseEvent) {
    6810 6810                          preprocessMouseEvent((MouseEvent) event);
    6811 6811                      }
    6812 6812
    6813 6813                      return event;
    6814 6814                  }
    6815 6815              };
    6816 6816          }
    6817 6817
    6818 6818          tail = tail.prepend(preprocessMouseEventDispatcher);
    6819 6819
    6820 6820          // prepend all event dispatchers from this node to the root
    6821 6821          Node curNode = this;
    6822 6822          do {
    6823 6823              if (curNode.eventDispatcher != null) {
    6824 6824                  tail = tail.prepend(curNode.eventDispatcher.get());
    6825 6825              }
    6826 6826              curNode = curNode.getParent();
    6827 6827          } while (curNode != null);
    6828 6828
    6829 6829          if (getScene() != null) {
    6830 6830              // prepend scene's dispatch chain
    6831 6831              tail = getScene().buildEventDispatchChain(tail);
    6832 6832          }
    6833 6833
    6834 6834          return tail;
    6835 6835      }
    6836 6836
    6837 6837      // PENDING_DOC_REVIEW
    6838 6838      /**
    6839 6839       * Fires the specified event. By default the event will travel through the
    6840 6840       * hierarchy from the stage to this node. Any event filter encountered will
    6841 6841       * be notified and can consume the event. If not consumed by the filters,
    6842 6842       * the event handlers on this node are notified. If these don't consume the
    6843 6843       * event eighter, the event will travel back the same path it arrived to
    6844 6844       * this node. All event handlers encountered are called and can consume the
    6845 6845       * event.
    6846 6846       * <p>
    6847 6847       * This method must be called on the FX user thread.
    6848 6848       *
    6849 6849       * @param event the event to fire
    6850 6850       */
    6851 6851      public final void fireEvent(Event event) {
    6852 6852
    6853 6853          /* Log input events.  We do a coarse filter for at least the FINE
    6854 6854           * level and then granularize from there.
    6855 6855           */
    6856 6856          if (event instanceof InputEvent) {
    6857 6857              PlatformLogger logger = Logging.getInputLogger();
    6858 6858              if (logger.isLoggable(PlatformLogger.FINE)) {
    6859 6859                  EventType eventType = event.getEventType();
    6860 6860                  if (eventType == MouseEvent.MOUSE_ENTERED ||
    6861 6861                      eventType == MouseEvent.MOUSE_EXITED) {
    6862 6862                      logger.finer(event.toString());
    6863 6863                  } else if (eventType == MouseEvent.MOUSE_MOVED ||
    6864 6864                             eventType == MouseEvent.MOUSE_DRAGGED) {
    6865 6865                      logger.finest(event.toString());
    6866 6866                  } else {
    6867 6867                      logger.fine(event.toString());
    6868 6868                  }
    6869 6869              }
    6870 6870          }
    6871 6871
    6872 6872          Event.fireEvent(this, event);
    6873 6873      }
    6874 6874
    6875 6875      /***************************************************************************
    6876 6876       *                                                                         *
    6877 6877       *                         Stylesheet Handling                             *
    6878 6878       *                                                                         *
    6879 6879       **************************************************************************/
    6880 6880
    6881 6881      private Styleable styleable;
    6882 6882      /**
    6883 6883       * RT-19263
    6884 6884       * @treatAsPrivate implementation detail
    6885 6885       * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
    6886 6886       */
    6887 6887      @Deprecated // SB-dependency: RT-21094 has been filed to track this
    6888 6888      public final Styleable impl_getStyleable() {
    6889 6889
    6890 6890          if (styleable == null) {
    6891 6891              styleable = new Styleable() {
    6892 6892
    6893 6893                  Styleable styleableParent = null;
    6894 6894
    6895 6895                  @Override
    6896 6896                  public String getId() {
    6897 6897                      return Node.this.getId();
    6898 6898                  }
    6899 6899
    6900 6900                  @Override
    6901 6901                  public List<String> getStyleClass() {
    6902 6902                      return Node.this.getStyleClass();
    6903 6903                  }
    6904 6904
    6905 6905                  @Override
    6906 6906                  public String getStyle() {
    6907 6907                      return Node.this.getStyle();
    6908 6908                  }
    6909 6909
    6910 6910                  @Override
    6911 6911                  public Styleable getStyleableParent() {
    6912 6912                      Parent parent = null;
    6913 6913                      if (styleableParent == null && (parent = Node.this.getParent()) != null) {
    6914 6914                          styleableParent = parent.impl_getStyleable();
    6915 6915                      };
    6916 6916                      return styleableParent;
    6917 6917                  }
    6918 6918
    6919 6919                  @Override
    6920 6920                  public List<StyleableProperty> getStyleableProperties() {
    6921 6921                      return Node.this.impl_getStyleableProperties();
    6922 6922                  }
    6923 6923
    6924 6924                  @Override
    6925 6925                  public Node getNode() {
    6926 6926                      return Node.this;
    6927 6927                  }
    6928 6928
    6929 6929             };
    6930 6930          }
    6931 6931          return styleable;
    6932 6932      }
    6933 6933
    6934 6934       /**
    6935 6935        * Not everything uses the default value of false for focusTraversable.
    6936 6936        * This method provides a way to have them return the correct initial value.
    6937 6937        * @treatAsPrivate implementation detail
    6938 6938        */
    6939 6939      @Deprecated
    6940 6940      protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
    6941 6941          return Boolean.FALSE;
    6942 6942      }
    6943 6943
    6944 6944       /**
    6945 6945        * Not everything uses the default value of null for cursor.
    6946 6946        * This method provides a way to have them return the correct initial value.
    6947 6947        * @treatAsPrivate implementation detail
    6948 6948        */
    6949 6949      @Deprecated
    6950 6950      protected /*do not make final*/ Cursor impl_cssGetCursorInitialValue() {
    6951 6951          return null;
    6952 6952      }
    6953 6953
    6954 6954       /**
    6955 6955        * Super-lazy instantiation pattern from Bill Pugh.
    6956 6956        * @treatAsPrivate implementation detail
    6957 6957        */
    6958 6958       private static class StyleableProperties {
    6959 6959
    6960 6960          private static final StyleableProperty<Node,Cursor> CURSOR =
    6961 6961              new StyleableProperty<Node,Cursor>("-fx-cursor", CursorConverter.getInstance()) {
    6962 6962
    6963 6963                  @Override
    6964 6964                  public boolean isSettable(Node node) {
    6965 6965                      return node.miscProperties == null || node.miscProperties.canSetCursor();
    6966 6966                  }
    6967 6967
    6968 6968                  @Override
    6969 6969                  public WritableValue<Cursor> getWritableValue(Node node) {
    6970 6970                      return node.cursorProperty();
    6971 6971                  }
    6972 6972
    6973 6973                  @Override
    6974 6974                  public Cursor getInitialValue(Node node) {
    6975 6975                      // Most controls default focusTraversable to true.
    6976 6976                      // Give a way to have them return the correct default value.
    6977 6977                      return node.impl_cssGetCursorInitialValue();
    6978 6978                  }
    6979 6979
    6980 6980              };
    6981 6981          private static final StyleableProperty<Node,Effect> EFFECT =
    6982 6982              new StyleableProperty<Node,Effect>("-fx-effect", EffectConverter.getInstance()) {
    6983 6983
    6984 6984                  @Override
    6985 6985                  public boolean isSettable(Node node) {
    6986 6986                      return node.miscProperties == null || node.miscProperties.canSetEffect();
    6987 6987                  }
    6988 6988
    6989 6989                  @Override
    6990 6990                  public WritableValue<Effect> getWritableValue(Node node) {
    6991 6991                      return node.effectProperty();
    6992 6992                  }
    6993 6993              };
    6994 6994          private static final StyleableProperty<Node,Boolean> FOCUS_TRAVERSABLE =
    6995 6995              new StyleableProperty<Node,Boolean>("-fx-focus-traversable",
    6996 6996                  BooleanConverter.getInstance(), Boolean.FALSE) {
    6997 6997
    6998 6998                  @Override
    6999 6999                  public boolean isSettable(Node node) {
    7000 7000                      return node.focusTraversable == null || !node.focusTraversable.isBound();
    7001 7001                  }
    7002 7002
    7003 7003                  @Override
    7004 7004                  public BooleanProperty getWritableValue(Node node) {
    7005 7005                      return node.focusTraversableProperty();
    7006 7006                  }
    7007 7007
    7008 7008                  @Override
    7009 7009                  public Boolean getInitialValue(Node node) {
    7010 7010                      // Most controls default focusTraversable to true.
    7011 7011                      // Give a way to have them return the correct default value.
    7012 7012                      return node.impl_cssGetFocusTraversableInitialValue();
    7013 7013                  }
    7014 7014
    7015 7015              };
    7016 7016          private static final StyleableProperty<Node,Number> OPACITY =
    7017 7017              new StyleableProperty<Node,Number>("-fx-opacity",
    7018 7018                  SizeConverter.getInstance(), 1.0) {
    7019 7019
    7020 7020                  @Override
    7021 7021                  public boolean isSettable(Node node) {
    7022 7022                      return node.opacity == null || !node.opacity.isBound();
    7023 7023                  }
    7024 7024
    7025 7025                  @Override
    7026 7026                  public WritableValue<Number> getWritableValue(Node node) {
    7027 7027                      return node.opacityProperty();
    7028 7028                  }
    7029 7029              };
    7030 7030          private static final StyleableProperty<Node,BlendMode> BLEND_MODE =
    7031 7031              new StyleableProperty<Node,BlendMode>("-fx-blend-mode", new EnumConverter<BlendMode>(BlendMode.class)) {
    7032 7032
    7033 7033                  @Override
    7034 7034                  public boolean isSettable(Node node) {
    7035 7035                      return node.blendMode == null || !node.blendMode.isBound();
    7036 7036                  }
    7037 7037
    7038 7038                  @Override
    7039 7039                  public WritableValue<BlendMode> getWritableValue(Node node) {
    7040 7040                      return node.blendModeProperty();
    7041 7041                  }
    7042 7042              };
    7043 7043          private static final StyleableProperty<Node,Number> ROTATE =
    7044 7044              new StyleableProperty<Node,Number>("-fx-rotate",
    7045 7045                  SizeConverter.getInstance(), 0.0) {
    7046 7046
    7047 7047                  @Override
    7048 7048                  public boolean isSettable(Node node) {
    7049 7049                      return node.nodeTransformation == null
    7050 7050                          || node.nodeTransformation.rotate == null
    7051 7051                          || node.nodeTransformation.canSetRotate();
    7052 7052                  }
    7053 7053
    7054 7054                  @Override
    7055 7055                  public WritableValue<Number> getWritableValue(Node node) {
    7056 7056                      return node.rotateProperty();
    7057 7057                  }
    7058 7058              };
    7059 7059          private static final StyleableProperty<Node,Number> SCALE_X =
    7060 7060              new StyleableProperty<Node,Number>("-fx-scale-x",
    7061 7061                  SizeConverter.getInstance(), 1.0) {
    7062 7062
    7063 7063                  @Override
    7064 7064                  public boolean isSettable(Node node) {
    7065 7065                      return node.nodeTransformation == null
    7066 7066                          || node.nodeTransformation.scaleX == null
    7067 7067                          || node.nodeTransformation.canSetScaleX();
    7068 7068                  }
    7069 7069
    7070 7070                  @Override
    7071 7071                  public WritableValue<Number> getWritableValue(Node node) {
    7072 7072                      return node.scaleXProperty();
    7073 7073                  }
    7074 7074              };
    7075 7075          private static final StyleableProperty<Node,Number> SCALE_Y =
    7076 7076              new StyleableProperty<Node,Number>("-fx-scale-y",
    7077 7077                  SizeConverter.getInstance(), 1.0) {
    7078 7078
    7079 7079                  @Override
    7080 7080                  public boolean isSettable(Node node) {
    7081 7081                      return node.nodeTransformation == null
    7082 7082                          || node.nodeTransformation.scaleY == null
    7083 7083                          || node.nodeTransformation.canSetScaleY();
    7084 7084                  }
    7085 7085
    7086 7086                  @Override
    7087 7087                  public WritableValue<Number> getWritableValue(Node node) {
    7088 7088                      return node.scaleYProperty();
    7089 7089                  }
    7090 7090              };
    7091 7091          private static final StyleableProperty<Node,Number> SCALE_Z =
    7092 7092              new StyleableProperty<Node,Number>("-fx-scale-z",
    7093 7093                  SizeConverter.getInstance(), 1.0) {
    7094 7094
    7095 7095                  @Override
    7096 7096                  public boolean isSettable(Node node) {
    7097 7097                      return node.nodeTransformation == null
    7098 7098                          || node.nodeTransformation.scaleZ == null
    7099 7099                          || node.nodeTransformation.canSetScaleZ();
    7100 7100                  }
    7101 7101
    7102 7102                  @Override
    7103 7103                  public WritableValue<Number> getWritableValue(Node node) {
    7104 7104                      return node.scaleZProperty();
    7105 7105                  }
    7106 7106              };
    7107 7107          private static final StyleableProperty<Node,Number> TRANSLATE_X =
    7108 7108              new StyleableProperty<Node,Number>("-fx-translate-x",
    7109 7109                  SizeConverter.getInstance(), 0.0) {
    7110 7110
    7111 7111                  @Override
    7112 7112                  public boolean isSettable(Node node) {
    7113 7113                      return node.nodeTransformation == null
    7114 7114                          || node.nodeTransformation.translateX == null
    7115 7115                          || node.nodeTransformation.canSetTranslateX();
    7116 7116                  }
    7117 7117
    7118 7118                  @Override
    7119 7119                  public WritableValue<Number> getWritableValue(Node node) {
    7120 7120                      return node.translateXProperty();
    7121 7121                  }
    7122 7122              };
    7123 7123          private static final StyleableProperty<Node,Number> TRANSLATE_Y =
    7124 7124              new StyleableProperty<Node,Number>("-fx-translate-y",
    7125 7125                  SizeConverter.getInstance(), 0.0) {
    7126 7126
    7127 7127                  @Override
    7128 7128                  public boolean isSettable(Node node) {
    7129 7129                      return node.nodeTransformation == null
    7130 7130                          || node.nodeTransformation.translateY == null
    7131 7131                          || node.nodeTransformation.canSetTranslateY();
    7132 7132                  }
    7133 7133
    7134 7134                  @Override
    7135 7135                  public WritableValue<Number> getWritableValue(Node node) {
    7136 7136                      return node.translateYProperty();
    7137 7137                  }
    7138 7138              };
    7139 7139          private static final StyleableProperty<Node,Number> TRANSLATE_Z =
    7140 7140              new StyleableProperty<Node,Number>("-fx-translate-z",
    7141 7141                  SizeConverter.getInstance(), 0.0) {
    7142 7142
    7143 7143                  @Override
    7144 7144                  public boolean isSettable(Node node) {
    7145 7145                      return node.nodeTransformation == null
    7146 7146                          || node.nodeTransformation.translateZ == null
    7147 7147                          || node.nodeTransformation.canSetTranslateZ();
    7148 7148                  }
    7149 7149
    7150 7150                  @Override
    7151 7151                  public WritableValue<Number> getWritableValue(Node node) {
    7152 7152                      return node.translateZProperty();
    7153 7153                  }
    7154 7154              };
    7155 7155          private static final StyleableProperty<Node,Boolean> VISIBILITY =
    7156 7156              new StyleableProperty<Node,Boolean>("visibility",
    7157 7157                  new StyleConverter<String,Boolean>() {
    7158 7158
    7159 7159                      @Override
    7160 7160                      // [ visible | hidden | collapse | inherit ]
    7161 7161                      public Boolean convert(ParsedValue<String, Boolean> value, Font font) {
    7162 7162                          final String sval = value != null ? value.getValue() : null;
    7163 7163                          return "visible".equalsIgnoreCase(sval);
    7164 7164                      }
    7165 7165
    7166 7166                  },
    7167 7167                  Boolean.TRUE) {
    7168 7168
    7169 7169                  @Override
    7170 7170                  public boolean isSettable(Node node) {
    7171 7171                      return node.visible == null || !node.visible.isBound();
    7172 7172                  }
    7173 7173
    7174 7174                  @Override
    7175 7175                  public WritableValue<Boolean> getWritableValue(Node node) {
    7176 7176                      return node.visibleProperty();
    7177 7177                  }
    7178 7178              };
    7179 7179
    7180 7180           private static final List<StyleableProperty> STYLEABLES;
    7181 7181
    7182 7182           static {
    7183 7183
    7184 7184               final List<StyleableProperty> styleables = new ArrayList<StyleableProperty>();
    7185 7185               Collections.addAll(styleables,
    7186 7186                   CURSOR,
    7187 7187                   EFFECT,
    7188 7188                   FOCUS_TRAVERSABLE,
    7189 7189                   OPACITY,
    7190 7190                   BLEND_MODE,
    7191 7191                   ROTATE,
    7192 7192                   SCALE_X,
    7193 7193                   SCALE_Y,
    7194 7194                   SCALE_Z,
    7195 7195                   TRANSLATE_X,
    7196 7196                   TRANSLATE_Y,
    7197 7197                   TRANSLATE_Z,
    7198 7198                   VISIBILITY);
    7199 7199               STYLEABLES = Collections.unmodifiableList(styleables);
    7200 7200
    7201 7201           }
    7202 7202      }
    7203 7203
    7204 7204       /**
    7205 7205        * Super-lazy instantiation pattern from Bill Pugh. StyleableProperties is referenced
    7206 7206        * no earlier (and therefore loaded no earlier by the class loader) than
    7207 7207        * the moment that  impl_CSS_STYLEABLES() is called.
    7208 7208        *
    7209 7209        * @treatAsPrivate implementation detail
    7210 7210        * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7211 7211        */
    7212 7212       @Deprecated
    7213 7213       public static List<StyleableProperty> impl_CSS_STYLEABLES() {
    7214 7214           return Node.StyleableProperties.STYLEABLES;
    7215 7215       }
    7216 7216
    7217 7217      /**
    7218 7218       * RT-19263
    7219 7219       * @treatAsPrivate implementation detail
    7220 7220       * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
    7221 7221       */
    7222 7222      @Deprecated
    7223 7223      public List<StyleableProperty> impl_getStyleableProperties() {
    7224 7224          return impl_CSS_STYLEABLES();
    7225 7225      }
    7226 7226
    7227 7227
    7228 7228       /**
    7229 7229        * RT-17293
    7230 7230        * @treatAsPrivate implementation detail
    7231 7231        * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
    7232 7232        */
    7233 7233       @Deprecated // SB-dependency: RT-21096 has been filed to track this
    7234 7234       public final ObservableMap<WritableValue, List<Style>> impl_getStyleMap() {
    7235 7235           return impl_getStyleable().getStyleMap();
    7236 7236       }
    7237 7237
    7238 7238       /**
    7239 7239        * RT-17293
    7240 7240        * @treatAsPrivate implementation detail
    7241 7241        * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
    7242 7242        */
    7243 7243       @Deprecated // SB-dependency: RT-21096 has been filed to track this
    7244 7244       public final void impl_setStyleMap(ObservableMap<WritableValue, List<Style>> styleMap) {
    7245 7245           impl_getStyleable().setStyleMap(styleMap);
    7246 7246       }
    7247 7247
    7248 7248      /**
    7249 7249       * Flags used to indicate in which way this node is dirty (or whether it
    7250 7250       * is clean) and what must happen during the next CSS cycle on the
    7251 7251       * scenegraph.
    7252 7252       */
    7253 7253      CSSFlags cssFlag = CSSFlags.CLEAN;
    7254 7254
    7255 7255      /**
    7256 7256       * Needed for testing.
    7257 7257       */
    7258 7258      final CSSFlags getCSSFlags() { return cssFlag; }
    7259 7259
    7260 7260      /**
    7261 7261       * Used to specify that the list of which pseudoclasses apply to this
    7262 7262       * Node has changed. The given parameter is the name of the pseudoclass
    7263 7263       * that has changed.
    7264 7264       *
    7265 7265       * @treatAsPrivate implementation detail
    7266 7266       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7267 7267       */
    7268 7268      @Deprecated
    7269 7269      protected void impl_pseudoClassStateChanged(String pseudoClass) {
    7270 7270          // If there is no scene, then we cannot make it dirty, so we'll leave
    7271 7271          // the flag alone
    7272 7272          if (getScene() == null) return;
    7273 7273          // Don't bother doing anything if the cssFlag is not CLEAN.
    7274 7274          // If the flag indicates a DIRTY_BRANCH, the flag might need to be
    7275 7275          // changed to UPDATE if the pseudoclass is used. This is necessary
    7276 7276          // to ensure that impl_processCSS is called on the node.
    7277 7277          if (cssFlag == CSSFlags.CLEAN || cssFlag == CSSFlags.DIRTY_BRANCH) {
    7278 7278              // We only want to mark the node as needing an UPDATE if the
    7279 7279              // pseudoclass is actually used by some CSS rule in the stylesheet.
    7280 7280              // If not, then we can safely ignore it
    7281 7281              if (isPseudoclassUsed(pseudoClass)) {
    7282 7282                  cssFlag = CSSFlags.UPDATE;
    7283 7283                  notifyParentsOfInvalidatedCSS();
    7284 7284              }
    7285 7285          }
    7286 7286      }
    7287 7287
    7288 7288      // Walks up the tree telling each parent that the pseudo class state of
    7289 7289      // this node has changed.
    7290 7290      /** @treatAsPrivate */
    7291 7291      private void notifyParentsOfInvalidatedCSS() {
    7292 7292          if (!getScene().getRoot().impl_isDirty(DirtyBits.NODE_CSS)) {
    7293 7293              // Ensure that Scene.root is marked as dirty. If the scene isn't
    7294 7294              // dirty, nothing will get repainted. This bit is cleared from
    7295 7295              // Scene in doCSSPass().
    7296 7296              getScene().getRoot().impl_markDirty(DirtyBits.NODE_CSS);
    7297 7297          }
    7298 7298          Parent parent = getParent();
    7299 7299          while (parent != null) {
    7300 7300              if (parent.cssFlag == CSSFlags.CLEAN) {

↓ open down ↓ | 5736 lines elided | ↑ open up ↑

    7301 7301                  parent.cssFlag = CSSFlags.DIRTY_BRANCH;
    7302 7302                  parent = parent.getParent();
    7303 7303              } else {
    7304 7304                  parent = null;
    7305 7305              }
    7306 7306          }
    7307 7307      }
    7308 7308
    7309 7309      // this function primarily exists as a hook to aid in testing
    7310 7310      boolean isPseudoclassUsed(String pseudoclass) {
    7311      -        final StyleHelper styleHelper = impl_getStyleHelper();
    7312 7311          return (styleHelper != null) ? styleHelper.isPseudoclassUsed(pseudoclass) : false;
    7313 7312      }
    7314 7313
    7315 7314      /**
    7316 7315       * @treatAsPrivate implementation detail
    7317 7316       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7318 7317       */
    7319 7318      @Deprecated
    7320 7319      public void impl_cssResetInitialValues() {
    7321 7320

    7322 7321          //
    7323 7322          // RT-9784: reset all properties that have been set by CSS back
    7324 7323          // to their default value. Called when a stylesheet is removed from
    7325 7324          // a parent or from the scene. This has to be done before calling
    7326 7325          // impl_reapplyCSS.
    7327 7326          //
    7328 7327          final List<StyleableProperty> styleables = impl_getStyleableProperties();
    7329 7328          final int nStyleables = styleables != null ? styleables.size() : 0;
    7330 7329          for (int n=0; n<nStyleables; n++) {
    7331 7330              final StyleableProperty styleable = styleables.get(n);
    7332 7331              if (styleable.isSettable(this) == false) continue;
    7333 7332              final WritableValue writable = styleable.getWritableValue(this);
    7334 7333              if (writable != null) {
    7335 7334                  final Stylesheet.Origin origin = StyleableProperty.getOrigin(writable);
    7336 7335                  if (origin != null && origin != Stylesheet.Origin.USER) {
    7337 7336                      // If a property is never set by the user or by CSS, then
    7338 7337                      // the Origin of the property is null. So, passing null
    7339 7338                      // here makes the property look (to CSS) like it was
    7340 7339                      // initialized but never used.
    7341 7340                      styleable.set(this, styleable.getInitialValue(this), null);
    7342 7341                  }
    7343 7342              }
    7344 7343          }
    7345 7344      }
    7346 7345
    7347 7346      /**
    7348 7347       * @treatAsPrivate implementation detail
    7349 7348       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7350 7349       */
    7351 7350      @Deprecated
    7352 7351      public final void impl_reapplyCSS() {
    7353 7352          // If there is no scene, then we cannot make it dirty, so we'll leave
    7354 7353          // the flag alone
    7355 7354          if (getScene() == null) return;
    7356 7355          // If the css flag is already "REAPPLY", then do nothing
    7357 7356          if (cssFlag == CSSFlags.REAPPLY) return;
    7358 7357          // Update the flag
    7359 7358          cssFlag = CSSFlags.REAPPLY;
    7360 7359
    7361 7360          // One idiom employed by developers is to, during the layout pass,
    7362 7361          // add or remove nodes from the scene. For example, a ScrollPane
    7363 7362          // might add scroll bars to itself if it determines during layout
    7364 7363          // that it needs them, or a ListView might add cells to itself if

↓ open down ↓ | 43 lines elided | ↑ open up ↑

    7365 7364          // it determines that it needs to. In such situations we must
    7366 7365          // apply the CSS immediately and not add it to the scene's queue
    7367 7366          // for deferred action.
    7368 7367          if (getParent() != null && getParent().performingLayout) {
    7369 7368              impl_processCSS(true);
    7370 7369          } else if (getScene() != null) {
    7371 7370              notifyParentsOfInvalidatedCSS();
    7372 7371          }
    7373 7372      }
    7374 7373
    7375      -    void processCSS() {
         7374 +    void processCSS(StyleManager styleManager) {
    7376 7375          switch (cssFlag) {
    7377 7376              case CLEAN:
    7378 7377                  break;
    7379 7378              case DIRTY_BRANCH:
    7380 7379                  Parent me = (Parent)this;
    7381 7380                  // clear the flag first in case the flag is set to something
    7382 7381                  // other than clean by downstream processing.
    7383 7382                  me.cssFlag = CSSFlags.CLEAN;
    7384 7383                  List<Node> children = me.getChildren();
    7385 7384                  for (int i=0, max=children.size(); i<max; i++) {
    7386      -                    children.get(i).processCSS();
         7385 +                    children.get(i).processCSS(styleManager);
    7387 7386                  }
    7388 7387                  break;
    7389 7388              case REAPPLY:
    7390 7389              case UPDATE:
    7391 7390              default:
    7392      -                impl_processCSS(cssFlag == CSSFlags.REAPPLY);
         7391 +                impl_processCSS(styleManager, cssFlag == CSSFlags.REAPPLY);
    7393 7392          }
    7394 7393      }
    7395 7394
    7396 7395      /**
    7397 7396       * If invoked, will update / reapply styles from here on down. If reapply
    7398 7397       * is false, then we will only update from here on down, otherwise we will
    7399 7398       * do a full reapply.
    7400 7399       *
    7401 7400       * @treatAsPrivate implementation detail
    7402 7401       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7403 7402       */
    7404 7403      @Deprecated // SB-dependency: RT-21206 has been filed to track this
    7405      -    public void impl_processCSS(boolean reapply) {
         7404 +    public void impl_processCSS(boolean reapply) {
         7405 +        final StyleManager styleManager = StyleManager.getStyleManager(this.getScene());
         7406 +        impl_processCSS(styleManager, reapply);
         7407 +    }
         7408 +
         7409 +    /**
         7410 +     * If invoked, will update / reapply styles from here on down. If reapply
         7411 +     * is false, then we will only update from here on down, otherwise we will
         7412 +     * do a full reapply.
         7413 +     *
         7414 +     * @treatAsPrivate implementation detail
         7415 +     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
         7416 +     */
         7417 +    @Deprecated // SB-dependency: RT-21206 has been filed to track this
         7418 +    public void impl_processCSS(StyleManager styleManager, boolean reapply) {
    7406 7419
    7407      -        StyleHelper styleHelper = null;
    7408 7420          // Create a new StyleHelper either if I am told I need to reapply
    7409 7421          // or if my own flag indicates I need to reapply
    7410 7422          if (reapply || (cssFlag == CSSFlags.REAPPLY)) {
    7411 7423
    7412      -            styleHelper = impl_createStyleHelper();
         7424 +            styleHelper = styleManager.getStyleHelper(this);
    7413 7425
    7414      -        } else {
    7415      -            styleHelper = impl_getStyleHelper();
    7416      -        }
         7426 +        }
    7417 7427
    7418 7428          // Clear the flag first in case the flag is set to something
    7419 7429          // other than clean by downstream processing.
    7420 7430          cssFlag = CSSFlags.CLEAN;
    7421 7431
    7422 7432          // Transition to the new state
    7423 7433          if (styleHelper != null) {
    7424 7434              styleHelper.transitionToState(this);
    7425 7435          }
    7426 7436      }

    7427 7437
    7428 7438      /**

↓ open down ↓ | 2 lines elided | ↑ open up ↑

    7429 7439       * A StyleHelper for this node.
    7430 7440       * A StyleHelper contains all the css styles for this node
    7431 7441       * and knows how to apply them when our state changes.
    7432 7442       */
    7433 7443      private StyleHelper styleHelper;
    7434 7444
    7435 7445      /**
    7436 7446       * @treatAsPrivate implementation detail
    7437 7447       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7438 7448       */
    7439      -    @Deprecated
    7440      -    protected StyleHelper impl_createStyleHelper() {
    7441      -
    7442      -        styleHelper = getScene().styleManager.getStyleHelper(this);
    7443      -        return styleHelper;
    7444      -    }
         7449 +//    @Deprecated
         7450 +//    protected StyleHelper impl_createStyleHelper() {
         7451 +//
         7452 +//        styleHelper = getScene().styleManager.getStyleHelper(this);
         7453 +//        return styleHelper;
         7454 +//    }
    7445 7455
    7446 7456      /**
    7447 7457       * Get this nodes StyleHelper
    7448 7458       * @treatAsPrivate implementation detail
    7449 7459       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7450 7460       */
    7451 7461      @Deprecated
    7452 7462      public StyleHelper impl_getStyleHelper() {
    7453 7463          return styleHelper;
    7454 7464      }

    7455 7465
    7456 7466      private static final long HOVER_PSEUDOCLASS_STATE = StyleManager.getPseudoclassMask("hover");
    7457 7467      private static final long PRESSED_PSEUDOCLASS_STATE = StyleManager.getPseudoclassMask("pressed");
    7458 7468      private static final long DISABLED_PSEUDOCLASS_STATE = StyleManager.getPseudoclassMask("disabled");
    7459 7469      private static final long FOCUSED_PSEUDOCLASS_STATE = StyleManager.getPseudoclassMask("focused");
    7460 7470      private static final long SHOW_MNEMONICS_PSEUDOCLASS_STATE = StyleManager.getPseudoclassMask("show-mnemonics");
    7461 7471
    7462 7472      /**
    7463 7473       * @treatAsPrivate implementation detail
    7464 7474       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7465 7475       */
    7466 7476      @Deprecated
    7467 7477      public long impl_getPseudoClassState() {
    7468 7478          long mask = 0;
    7469 7479          if(isHover()) mask |= HOVER_PSEUDOCLASS_STATE;
    7470 7480          if(isPressed()) mask |= PRESSED_PSEUDOCLASS_STATE;
    7471 7481          if(isDisabled()) mask |= DISABLED_PSEUDOCLASS_STATE;
    7472 7482          if(isFocused()) mask |= FOCUSED_PSEUDOCLASS_STATE;
    7473 7483          if(impl_isShowMnemonics()) mask |= SHOW_MNEMONICS_PSEUDOCLASS_STATE;
    7474 7484          return mask;
    7475 7485      }
    7476 7486
    7477 7487      private static abstract class LazyTransformProperty
    7478 7488              extends ReadOnlyObjectProperty<Transform> {
    7479 7489          private ExpressionHelper<Transform> helper;
    7480 7490          private boolean valid;
    7481 7491
    7482 7492          private Transform transform;
    7483 7493
    7484 7494          @Override
    7485 7495          public void addListener(InvalidationListener listener) {
    7486 7496              helper = ExpressionHelper.addListener(helper, this, listener);
    7487 7497          }
    7488 7498
    7489 7499          @Override
    7490 7500          public void removeListener(InvalidationListener listener) {
    7491 7501              helper = ExpressionHelper.removeListener(helper, listener);
    7492 7502          }
    7493 7503
    7494 7504          @Override
    7495 7505          public void addListener(ChangeListener<? super Transform> listener) {
    7496 7506              helper = ExpressionHelper.addListener(helper, this, listener);
    7497 7507          }
    7498 7508
    7499 7509          @Override
    7500 7510          public void removeListener(ChangeListener<? super Transform> listener) {
    7501 7511              helper = ExpressionHelper.removeListener(helper, listener);
    7502 7512          }
    7503 7513
    7504 7514          @Override
    7505 7515          public Transform get() {
    7506 7516              if (!valid) {
    7507 7517                  transform = computeTransform();
    7508 7518                  valid = true;
    7509 7519              }
    7510 7520
    7511 7521              return transform;
    7512 7522          }
    7513 7523
    7514 7524          public void invalidate() {
    7515 7525              if (valid) {
    7516 7526                  valid = false;
    7517 7527                  ExpressionHelper.fireValueChangedEvent(helper);
    7518 7528              }
    7519 7529          }
    7520 7530
    7521 7531          protected abstract Transform computeTransform();
    7522 7532      }
    7523 7533
    7524 7534      private static abstract class LazyBoundsProperty
    7525 7535              extends ReadOnlyObjectProperty<Bounds> {
    7526 7536          private ExpressionHelper<Bounds> helper;
    7527 7537          private boolean valid;
    7528 7538
    7529 7539          private Bounds bounds;
    7530 7540
    7531 7541          @Override
    7532 7542          public void addListener(InvalidationListener listener) {
    7533 7543              helper = ExpressionHelper.addListener(helper, this, listener);
    7534 7544          }
    7535 7545
    7536 7546          @Override
    7537 7547          public void removeListener(InvalidationListener listener) {
    7538 7548              helper = ExpressionHelper.removeListener(helper, listener);
    7539 7549          }
    7540 7550
    7541 7551          @Override
    7542 7552          public void addListener(ChangeListener<? super Bounds> listener) {
    7543 7553              helper = ExpressionHelper.addListener(helper, this, listener);
    7544 7554          }
    7545 7555
    7546 7556          @Override
    7547 7557          public void removeListener(ChangeListener<? super Bounds> listener) {
    7548 7558              helper = ExpressionHelper.removeListener(helper, listener);
    7549 7559          }
    7550 7560
    7551 7561          @Override
    7552 7562          public Bounds get() {
    7553 7563              if (!valid) {
    7554 7564                  bounds = computeBounds();
    7555 7565                  valid = true;
    7556 7566              }
    7557 7567
    7558 7568              return bounds;
    7559 7569          }
    7560 7570
    7561 7571          public void invalidate() {
    7562 7572              if (valid) {
    7563 7573                  valid = false;
    7564 7574                  ExpressionHelper.fireValueChangedEvent(helper);
    7565 7575              }
    7566 7576          }
    7567 7577
    7568 7578          protected abstract Bounds computeBounds();
    7569 7579      }
    7570 7580
    7571 7581      private static final BoundsAccessor boundsAccessor = new BoundsAccessor() {
    7572 7582          @Override
    7573 7583          public BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx, Node node) {
    7574 7584              return node.getGeomBounds(bounds, tx);
    7575 7585          }
    7576 7586      };
    7577 7587
    7578 7588      /**
    7579 7589       * This method is used by Scene-graph JMX bean to obtain the Scene-graph structure.
    7580 7590       *
    7581 7591       * @param alg current algorithm to process this node
    7582 7592       * @param ctx current context
    7583 7593       * @return the algorithm specific result for this node
    7584 7594       * @treatAsPrivate implementation detail
    7585 7595       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    7586 7596       */
    7587 7597      @Deprecated
    7588 7598      public abstract Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx);
    7589 7599  }
    7590 7600

↓ open down ↓ | 136 lines elided | ↑ open up ↑

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX