/*
* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene;
import com.sun.javafx.geometry.BoundsUtils;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanPropertyBase;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectPropertyBase;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.css.CssMetaData;
import javafx.css.ParsedValue;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.geometry.Rectangle2D;
import javafx.scene.effect.BlendMode;
import javafx.scene.effect.Effect;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.InputEvent;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodRequests;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.input.RotateEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.SwipeEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.input.ZoomEvent;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Window;
import javafx.util.Callback;
import java.security.AccessControlContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sun.glass.ui.Accessible;
import com.sun.glass.ui.Application;
import com.sun.javafx.util.Logging;
import com.sun.javafx.util.TempState;
import com.sun.javafx.util.Utils;
import com.sun.javafx.beans.IDProperty;
import com.sun.javafx.beans.event.AbstractNotifyListener;
import com.sun.javafx.binding.ExpressionHelper;
import com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.collections.UnmodifiableListSet;
import com.sun.javafx.css.PseudoClassState;
import javafx.css.Selector;
import javafx.css.Style;
import javafx.css.converter.BooleanConverter;
import javafx.css.converter.CursorConverter;
import javafx.css.converter.EffectConverter;
import javafx.css.converter.EnumConverter;
import javafx.css.converter.SizeConverter;
import com.sun.javafx.effect.EffectDirtyBits;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.BoxBounds;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.GeneralTransform3D;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.scene.BoundsAccessor;
import com.sun.javafx.scene.CameraHelper;
import com.sun.javafx.scene.CssFlags;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.EventHandlerProperties;
import com.sun.javafx.scene.LayoutFlags;
import com.sun.javafx.scene.NodeEventDispatcher;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.scene.SceneUtils;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.scene.transform.TransformHelper;
import com.sun.javafx.scene.transform.TransformUtils;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.prism.impl.PrismSettings;
import com.sun.scenario.effect.EffectHelper;
import javafx.scene.shape.Shape3D;
import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;
Base class for scene graph nodes. A scene graph is a set of tree data structures
where every item has zero or one parent, and each item is either
a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
Each item in the scene graph is called a Node
. Branch nodes are of type Parent
, whose concrete subclasses are Group
, Region
, and Control
, or subclasses thereof.
Leaf nodes are classes such as Rectangle
, Text
, ImageView
, MediaView
, or other such leaf classes which cannot have children. Only a single node within each scene graph tree will have no parent, which is referred to as the "root" node.
There may be several trees in the scene graph. Some trees may be part of a Scene
, in which case they are eligible to be displayed. Other trees might not be part of any Scene
.
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in all of the following: as the root node of a Scene
, the children ObservableList of a Parent
, or as the clip of a Node
.
The scene graph must not have cycles. A cycle would exist if a node is an ancestor of itself in the tree, considering the Group
content ObservableList, Parent
children ObservableList, and Node
clip relationships mentioned above.
If a program adds a child node to a Parent (including Group, Region, etc)
and that node is already a child of a different Parent or the root of a Scene,
the node is automatically (and silently) removed from its former parent.
If a program attempts to modify the scene graph in any other way that violates
the above rules, an exception is thrown, the modification attempt is ignored
and the scene graph is restored to its previous state.
It is possible to rearrange the structure of the scene graph, for
example, to move a subtree from one location in the scene graph to
another. In order to do this, one would normally remove the subtree from
its old location before inserting it at the new location. However, the
subtree will be automatically removed as described above if the application
doesn't explicitly remove it.
Node objects may be constructed and modified on any thread as long they are not yet attached to a Scene
in a Window
that is showing
. An application must attach nodes to such a Scene or modify them on the JavaFX Application Thread.
The JavaFX Application Thread is created as part of the startup process for the JavaFX runtime. See the Application
class and the Platform.startup(Runnable)
method for more information.
An application should not extend the Node class directly. Doing so may lead to
an UnsupportedOperationException being thrown.
String ID
Each node in the scene graph can be given a unique id
. This id is much like the "id" attribute of an HTML tag in that it is up to the designer and developer to ensure that the id
is unique within the scene graph. A convenience function called lookup(String)
can be used to find a node with a unique id within the scene graph, or within a subtree of the scene graph. The id can also be used identify nodes for applying styles; see the CSS section below.
Coordinate System
The Node
class defines a traditional computer graphics "local" coordinate system in which the x
axis increases to the right and the y
axis increases downwards. The concrete node classes for shapes provide variables for defining the geometry and location of the shape within this local coordinate space. For example, Rectangle
provides x
, y
, width
, height
variables while Circle
provides centerX
, centerY
, and radius
.
At the device pixel level, integer coordinates map onto the corners and cracks between the pixels and the centers of the pixels appear at the midpoints between integer pixel locations. Because all coordinate values are specified with floating point numbers, coordinates can precisely point to these corners (when the floating point values have exact integer values) or to any location on the pixel. For example, a coordinate of (0.5, 0.5)
would point to the center of the upper left pixel on the Stage
. Similarly, a rectangle at (0, 0)
with dimensions of 10
by 10
would span from the upper left corner of the upper left pixel on the Stage
to the lower right corner of the 10th pixel on the 10th scanline. The pixel center of the last pixel inside that rectangle would be at the coordinates (9.5, 9.5)
.
In practice, most nodes have transformations applied to their coordinate system as mentioned below. As a result, the information above describing the alignment of device coordinates to the pixel grid is relative to the transformed coordinates, not the local coordinates of the nodes. The Shape
class describes some additional important context-specific information about coordinate mapping and how it can affect rendering.
Transformations
Any Node
can have transformations applied to it. These include translation, rotation, scaling, or shearing.
A translation transformation is one which shifts the origin of the node's coordinate space along either the x or y axis. For example, if you create a Rectangle
which is drawn at the origin (x=0, y=0) and has a width of 100 and a height of 50, and then apply a Translate
with a shift of 10 along the x axis (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain 100 points wide and 50 tall. Note that the origin was shifted, not the x
variable of the rectangle.
A common node transform is a translation by an integer distance, most often
used to lay out nodes on the stage. Such integer translations maintain the
device pixel mapping so that local coordinates that are integers still
map to the cracks between pixels.
A rotation transformation is one which rotates the coordinate space of the node about a specified "pivot" point, causing the node to appear rotated. For example, if you create a Rectangle
which is drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and you apply a Rotate
with a 90 degree rotation (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then the rectangle will be drawn as if its x and y were zero but its height was 100 and its width -30. That is, it is as if a pin is being stuck at the top left corner and the rectangle is rotating 90 degrees clockwise around that pin. If the pivot point is instead placed in the center of the rectangle (at point x=50, y=15) then the rectangle will instead appear to rotate about its center.
Note that as with all transformations, the x, y, width, and height variables
of the rectangle (which remain relative to the local coordinate space) have
not changed, but rather the transformation alters the entire coordinate space
of the rectangle.
A scaling transformation causes a node to either appear larger or smaller depending on the scaling factor. Scaling alters the coordinate space of the node such that each unit of distance along the axis in local coordinates is multiplied by the scale factor. As with rotation transformations, scaling transformations are applied about a "pivot" point. You can think of this as the point in the Node around which you "zoom". For example, if you create a Rectangle
with a strokeWidth
of 5, and a width and height of 50, and you apply a Scale
with scale factors (x=2.0, y=2.0) and a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle (including the stroke) will double in size, growing to the right and downwards from the origin.
A shearing transformation, sometimes called a skew, effectively
rotates one axis so that the x and y axes are no longer perpendicular.
Multiple transformations may be applied to a node by specifying an ordered chain of transforms. The order in which the transforms are applied is defined by the ObservableList specified in the transforms
variable.
Bounding Rectangles
Since every Node
has transformations, every Node's geometric bounding rectangle can be described differently depending on whether transformations are accounted for or not.
Each Node
has a read-only boundsInLocal
variable which specifies the bounding rectangle of the Node
in untransformed local coordinates. boundsInLocal
includes the Node's shape geometry, including any space required for a non-zero stroke that may fall outside the local position/size variables, and its clip
and effect
variables.
Each Node
also has a read-only boundsInParent
variable which specifies the bounding rectangle of the Node
after all transformations have been applied, including those set in transforms
, scaleX
/scaleY
, rotate
, translateX
/translateY
, and layoutX
/layoutY
. It is called "boundsInParent" because the rectangle will be relative to the parent's coordinate system. This is the 'visual' bounds of the node.
Finally, the layoutBounds
variable defines the rectangular bounds of the Node
that should be used as the basis for layout calculations and may differ from the visual bounds of the node. For shapes, Text, and ImageView, layoutBounds by default includes only the shape geometry, including space required for a non-zero strokeWidth
, but does not include the effect, clip, or any transforms. For resizable classes (Regions and Controls) layoutBounds will always map to 0,0 width x height
.
The image shows a node without any transformation and its boundsInLocal
:
If we rotate the image by 20 degrees we get following result:
The red rectangle represents boundsInParent
in the coordinate space of the Node's parent. The boundsInLocal
stays the same as in the first image, the green rectangle in this image represents boundsInLocal
in the coordinate space of the Node. The images show a filled and stroked rectangle and their bounds. The first rectangle [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]
has the following bounds bounds: [x:10.0 y:10.0 width:100.0 height:100.0]
. The second rectangle [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]
has the following bounds: [x:7.5 y:7.5 width:105 height:105]
(the stroke is centered by default, so only half of it is outside of the original bounds; it is also possible to create inside or outside stroke). Since neither of the rectangles has any transformation applied, boundsInParent
and boundsInLocal
are the same.
CSS
The Node
class contains id
, styleClass
, and style
variables that are used in styling this node from CSS. The id
and styleClass
variables are used in CSS style sheets to identify nodes to which styles should be applied. The style
variable contains style properties and values that are applied directly to this node.
For further information about CSS and how to apply CSS styles
to nodes, see the CSS Reference
Guide.
Since: JavaFX 2.0
/**
* Base class for scene graph nodes. A scene graph is a set of tree data structures
* where every item has zero or one parent, and each item is either
* a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
* <p>
* Each item in the scene graph is called a {@code Node}. Branch nodes are
* of type {@link Parent}, whose concrete subclasses are {@link Group},
* {@link javafx.scene.layout.Region}, and {@link javafx.scene.control.Control},
* or subclasses thereof.
* <p>
* Leaf nodes are classes such as
* {@link javafx.scene.shape.Rectangle}, {@link javafx.scene.text.Text},
* {@link javafx.scene.image.ImageView}, {@link javafx.scene.media.MediaView},
* or other such leaf classes which cannot have children. Only a single node within
* each scene graph tree will have no parent, which is referred to as the "root" node.
* <p>
* There may be several trees in the scene graph. Some trees may be part of
* a {@link Scene}, in which case they are eligible to be displayed.
* Other trees might not be part of any {@link Scene}.
* <p>
* A node may occur at most once anywhere in the scene graph. Specifically,
* a node must appear no more than once in all of the following:
* as the root node of a {@link Scene},
* the children ObservableList of a {@link Parent},
* or as the clip of a {@link Node}.
* <p>
* The scene graph must not have cycles. A cycle would exist if a node is
* an ancestor of itself in the tree, considering the {@link Group} content
* ObservableList, {@link Parent} children ObservableList, and {@link Node} clip relationships
* mentioned above.
* <p>
* If a program adds a child node to a Parent (including Group, Region, etc)
* and that node is already a child of a different Parent or the root of a Scene,
* the node is automatically (and silently) removed from its former parent.
* If a program attempts to modify the scene graph in any other way that violates
* the above rules, an exception is thrown, the modification attempt is ignored
* and the scene graph is restored to its previous state.
* <p>
* It is possible to rearrange the structure of the scene graph, for
* example, to move a subtree from one location in the scene graph to
* another. In order to do this, one would normally remove the subtree from
* its old location before inserting it at the new location. However, the
* subtree will be automatically removed as described above if the application
* doesn't explicitly remove it.
* <p>
* Node objects may be constructed and modified on any thread as long they are
* not yet attached to a {@link Scene} in a {@link Window} that is
* {@link Window#isShowing showing}.
* An application must attach nodes to such a Scene or modify them on the JavaFX
* Application Thread.
*
* <p>
* The JavaFX Application Thread is created as part of the startup process for
* the JavaFX runtime. See the {@link javafx.application.Application} class and
* the {@link Platform#startup(Runnable)} method for more information.
* </p>
*
* <p>
* An application should not extend the Node class directly. Doing so may lead to
* an UnsupportedOperationException being thrown.
* </p>
*
* <h3>String ID</h3>
* <p>
* Each node in the scene graph can be given a unique {@link #idProperty id}. This id is
* much like the "id" attribute of an HTML tag in that it is up to the designer
* and developer to ensure that the {@code id} is unique within the scene graph.
* A convenience function called {@link #lookup(String)} can be used to find
* a node with a unique id within the scene graph, or within a subtree of the
* scene graph. The id can also be used identify nodes for applying styles; see
* the CSS section below.
*
* <h3>Coordinate System</h3>
* <p>
* The {@code Node} class defines a traditional computer graphics "local"
* coordinate system in which the {@code x} axis increases to the right and the
* {@code y} axis increases downwards. The concrete node classes for shapes
* provide variables for defining the geometry and location of the shape
* within this local coordinate space. For example,
* {@link javafx.scene.shape.Rectangle} provides {@code x}, {@code y},
* {@code width}, {@code height} variables while
* {@link javafx.scene.shape.Circle} provides {@code centerX}, {@code centerY},
* and {@code radius}.
* <p>
* At the device pixel level, integer coordinates map onto the corners and
* cracks between the pixels and the centers of the pixels appear at the
* midpoints between integer pixel locations. Because all coordinate values
* are specified with floating point numbers, coordinates can precisely
* point to these corners (when the floating point values have exact integer
* values) or to any location on the pixel. For example, a coordinate of
* {@code (0.5, 0.5)} would point to the center of the upper left pixel on the
* {@code Stage}. Similarly, a rectangle at {@code (0, 0)} with dimensions
* of {@code 10} by {@code 10} would span from the upper left corner of the
* upper left pixel on the {@code Stage} to the lower right corner of the
* 10th pixel on the 10th scanline. The pixel center of the last pixel
* inside that rectangle would be at the coordinates {@code (9.5, 9.5)}.
* <p>
* In practice, most nodes have transformations applied to their coordinate
* system as mentioned below. As a result, the information above describing
* the alignment of device coordinates to the pixel grid is relative to
* the transformed coordinates, not the local coordinates of the nodes.
* The {@link javafx.scene.shape.Shape Shape} class describes some additional
* important context-specific information about coordinate mapping and how
* it can affect rendering.
*
* <h3>Transformations</h3>
* <p>
* Any {@code Node} can have transformations applied to it. These include
* translation, rotation, scaling, or shearing.
* <p>
* A <b>translation</b> transformation is one which shifts the origin of the
* node's coordinate space along either the x or y axis. For example, if you
* create a {@link javafx.scene.shape.Rectangle} which is drawn at the origin
* (x=0, y=0) and has a width of 100 and a height of 50, and then apply a
* {@link javafx.scene.transform.Translate} with a shift of 10 along the x axis
* (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain
* 100 points wide and 50 tall. Note that the origin was shifted, not the
* {@code x} variable of the rectangle.
* <p>
* A common node transform is a translation by an integer distance, most often
* used to lay out nodes on the stage. Such integer translations maintain the
* device pixel mapping so that local coordinates that are integers still
* map to the cracks between pixels.
* <p>
* A <b>rotation</b> transformation is one which rotates the coordinate space of
* the node about a specified "pivot" point, causing the node to appear rotated.
* For example, if you create a {@link javafx.scene.shape.Rectangle} which is
* drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and
* you apply a {@link javafx.scene.transform.Rotate} with a 90 degree rotation
* (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then
* the rectangle will be drawn as if its x and y were zero but its height was
* 100 and its width -30. That is, it is as if a pin is being stuck at the top
* left corner and the rectangle is rotating 90 degrees clockwise around that
* pin. If the pivot point is instead placed in the center of the rectangle
* (at point x=50, y=15) then the rectangle will instead appear to rotate about
* its center.
* <p>
* Note that as with all transformations, the x, y, width, and height variables
* of the rectangle (which remain relative to the local coordinate space) have
* not changed, but rather the transformation alters the entire coordinate space
* of the rectangle.
* <p>
* A <b>scaling</b> transformation causes a node to either appear larger or
* smaller depending on the scaling factor. Scaling alters the coordinate space
* of the node such that each unit of distance along the axis in local
* coordinates is multiplied by the scale factor. As with rotation
* transformations, scaling transformations are applied about a "pivot" point.
* You can think of this as the point in the Node around which you "zoom". For
* example, if you create a {@link javafx.scene.shape.Rectangle} with a
* {@code strokeWidth} of 5, and a width and height of 50, and you apply a
* {@link javafx.scene.transform.Scale} with scale factors (x=2.0, y=2.0) and
* a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle
* (including the stroke) will double in size, growing to the right and
* downwards from the origin.
* <p>
* A <b>shearing</b> transformation, sometimes called a skew, effectively
* rotates one axis so that the x and y axes are no longer perpendicular.
* <p>
* Multiple transformations may be applied to a node by specifying an ordered
* chain of transforms. The order in which the transforms are applied is
* defined by the ObservableList specified in the {@link #getTransforms transforms} variable.
*
* <h3>Bounding Rectangles</h3>
* <p>
* Since every {@code Node} has transformations, every Node's geometric
* bounding rectangle can be described differently depending on whether
* transformations are accounted for or not.
* <p>
* Each {@code Node} has a read-only {@link #boundsInLocalProperty boundsInLocal}
* variable which specifies the bounding rectangle of the {@code Node} in
* untransformed local coordinates. {@code boundsInLocal} includes the
* Node's shape geometry, including any space required for a
* non-zero stroke that may fall outside the local position/size variables,
* and its {@link #clipProperty clip} and {@link #effectProperty effect} variables.
* <p>
* Each {@code Node} also has a read-only {@link #boundsInParentProperty boundsInParent} variable which
* specifies the bounding rectangle of the {@code Node} after all transformations
* have been applied, including those set in {@link #getTransforms transforms},
* {@link #scaleXProperty scaleX}/{@link #scaleYProperty scaleY}, {@link #rotateProperty rotate},
* {@link #translateXProperty translateX}/{@link #translateYProperty translateY}, and {@link #layoutXProperty layoutX}/{@link #layoutYProperty layoutY}.
* It is called "boundsInParent" because the rectangle will be relative to the
* parent's coordinate system. This is the 'visual' bounds of the node.
* <p>
* Finally, the {@link #layoutBoundsProperty layoutBounds} variable defines the rectangular bounds of
* the {@code Node} that should be used as the basis for layout calculations and
* may differ from the visual bounds of the node. For shapes, Text, and ImageView,
* layoutBounds by default includes only the shape geometry, including space required
* for a non-zero {@code strokeWidth}, but does <i>not</i> include the effect,
* clip, or any transforms. For resizable classes (Regions and Controls)
* layoutBounds will always map to {@code 0,0 width x height}.
*
* <p> The image shows a node without any transformation and its {@code boundsInLocal}:
* <p> <img src="doc-files/boundsLocal.png" alt="A sine wave shape enclosed by
* an axis-aligned rectangular bounds"> </p>
* If we rotate the image by 20 degrees we get following result:
* <p> <img src="doc-files/boundsParent.png" alt="An axis-aligned rectangular
* bounds that encloses the shape rotated by 20 degrees"> </p>
* The red rectangle represents {@code boundsInParent} in the
* coordinate space of the Node's parent. The {@code boundsInLocal} stays the same
* as in the first image, the green rectangle in this image represents {@code boundsInLocal}
* in the coordinate space of the Node.
*
* <p> The images show a filled and stroked rectangle and their bounds. The
* first rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]}
* has the following bounds bounds: {@code [x:10.0 y:10.0 width:100.0 height:100.0]}.
*
* The second rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]}
* has the following bounds: {@code [x:7.5 y:7.5 width:105 height:105]}
* (the stroke is centered by default, so only half of it is outside
* of the original bounds; it is also possible to create inside or outside
* stroke).
*
* Since neither of the rectangles has any transformation applied,
* {@code boundsInParent} and {@code boundsInLocal} are the same. </p>
* <p> <img src="doc-files/bounds.png" alt="The rectangles are enclosed by their
* respective bounds"> </p>
*
*
* <h3>CSS</h3>
* <p>
* The {@code Node} class contains {@code id}, {@code styleClass}, and
* {@code style} variables that are used in styling this node from
* CSS. The {@code id} and {@code styleClass} variables are used in
* CSS style sheets to identify nodes to which styles should be
* applied. The {@code style} variable contains style properties and
* values that are applied directly to this node.
* <p>
* For further information about CSS and how to apply CSS styles
* to nodes, see the <a href="doc-files/cssref.html">CSS Reference
* Guide</a>.
* @since JavaFX 2.0
*/
@IDProperty("id")
public abstract class Node implements EventTarget, Styleable {
/*
* Store the singleton instance of the NodeHelper subclass corresponding
* to the subclass of this instance of Node
*/
private NodeHelper nodeHelper = null;
static {
PerformanceTracker.logEvent("Node class loaded");
// This is used by classes in different packages to get access to
// private and package private methods.
NodeHelper.setNodeAccessor(new NodeHelper.NodeAccessor() {
@Override
public NodeHelper getHelper(Node node) {
return node.nodeHelper;
}
@Override
public void setHelper(Node node, NodeHelper nodeHelper) {
node.nodeHelper = nodeHelper;
}
@Override
public void doMarkDirty(Node node, DirtyBits dirtyBit) {
node.doMarkDirty(dirtyBit);
}
@Override
public void doUpdatePeer(Node node) {
node.doUpdatePeer();
}
@Override
public BaseTransform getLeafTransform(Node node) {
return node.getLeafTransform();
}
@Override
public Bounds doComputeLayoutBounds(Node node) {
return node.doComputeLayoutBounds();
}
@Override
public void doTransformsChanged(Node node) {
node.doTransformsChanged();
}
@Override
public void doPickNodeLocal(Node node, PickRay localPickRay,
PickResultChooser result) {
node.doPickNodeLocal(localPickRay, result);
}
@Override
public boolean doComputeIntersects(Node node, PickRay pickRay,
PickResultChooser pickResult) {
return node.doComputeIntersects(pickRay, pickResult);
}
@Override
public void doGeomChanged(Node node) {
node.doGeomChanged();
}
@Override
public void doNotifyLayoutBoundsChanged(Node node) {
node.doNotifyLayoutBoundsChanged();
}
@Override
public void doProcessCSS(Node node) {
node.doProcessCSS();
}
@Override
public boolean isDirty(Node node, DirtyBits dirtyBit) {
return node.isDirty(dirtyBit);
}
@Override
public boolean isDirtyEmpty(Node node) {
return node.isDirtyEmpty();
}
@Override
public void syncPeer(Node node) {
node.syncPeer();
}
@Override
public void layoutBoundsChanged(Node node) {
node.layoutBoundsChanged();
}
@Override
public <P extends NGNode> P getPeer(Node node) {
return node.getPeer();
}
@Override
public void setShowMnemonics(Node node, boolean value) {
node.setShowMnemonics(value);
}
@Override
public boolean isShowMnemonics(Node node) {
return node.isShowMnemonics();
}
@Override
public BooleanProperty showMnemonicsProperty(Node node) {
return node.showMnemonicsProperty();
}
@Override
public boolean traverse(Node node, Direction direction) {
return node.traverse(direction);
}
@Override
public double getPivotX(Node node) {
return node.getPivotX();
}
@Override
public double getPivotY(Node node) {
return node.getPivotY();
}
@Override
public double getPivotZ(Node node) {
return node.getPivotZ();
}
@Override
public void pickNode(Node node,PickRay pickRay,
PickResultChooser result) {
node.pickNode(pickRay, result);
}
@Override
public boolean intersects(Node node, PickRay pickRay,
PickResultChooser pickResult) {
return node.intersects(pickRay, pickResult);
}
@Override
public double intersectsBounds(Node node, PickRay pickRay) {
return node.intersectsBounds(pickRay);
}
@Override
public void layoutNodeForPrinting(Node node) {
node.doCSSLayoutSyncForSnapshot();
}
@Override
public boolean isDerivedDepthTest(Node node) {
return node.isDerivedDepthTest();
}
@Override
public SubScene getSubScene(Node node) {
return node.getSubScene();
}
@Override
public void setLabeledBy(Node node, Node labeledBy) {
node.labeledBy = labeledBy;
}
@Override
public Accessible getAccessible(Node node) {
return node.getAccessible();
}
@Override
public void reapplyCSS(Node node) {
node.reapplyCSS();
}
@Override
public boolean isTreeVisible(Node node) {
return node.isTreeVisible();
}
@Override
public BooleanExpression treeVisibleProperty(Node node) {
return node.treeVisibleProperty();
}
@Override
public boolean isTreeShowing(Node node) {
return node.isTreeShowing();
}
@Override
public BooleanExpression treeShowingProperty(Node node) {
return node.treeShowingProperty();
}
@Override
public List<Style> getMatchingStyles(CssMetaData cssMetaData,
Styleable styleable) {
return Node.getMatchingStyles(cssMetaData, styleable);
}
@Override
public Map<StyleableProperty<?>, List<Style>> findStyles(Node node,
Map<StyleableProperty<?>, List<Style>> styleMap) {
return node.findStyles(styleMap);
}
});
}
*
Methods and state for managing the dirty bits of a Node. The dirty *
bits are flags used to keep track of what things are dirty on the *
node and therefore need processing on the next pulse. Since the pulse *
happens asynchronously to the change that made the node dirty (for *
performance reasons), we need to keep track of what things have *
changed. *
*
/**************************************************************************
* *
* Methods and state for managing the dirty bits of a Node. The dirty *
* bits are flags used to keep track of what things are dirty on the *
* node and therefore need processing on the next pulse. Since the pulse *
* happens asynchronously to the change that made the node dirty (for *
* performance reasons), we need to keep track of what things have *
* changed. *
* *
*************************************************************************/
/*
* Set of dirty bits that are set when state is invalidated and cleared by
* the updateState method, which is called from the synchronizer.
*/
private int dirtyBits;
/*
* Mark the specified bit as dirty, and add this node to the scene's dirty list.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doMarkDirty(DirtyBits dirtyBit) {
if (isDirtyEmpty()) {
addToSceneDirtyList();
}
dirtyBits |= dirtyBit.getMask();
}
private void addToSceneDirtyList() {
Scene s = getScene();
if (s != null) {
s.addToDirtyList(this);
if (getSubScene() != null) {
getSubScene().setDirty(this);
}
}
}
/*
* Test whether the specified dirty bit is set
*/
final boolean isDirty(DirtyBits dirtyBit) {
return (dirtyBits & dirtyBit.getMask()) != 0;
}
/*
* Clear the specified dirty bit
*/
final void clearDirty(DirtyBits dirtyBit) {
dirtyBits &= ~dirtyBit.getMask();
}
/*
* Set all dirty bits
*/
private void setDirty() {
dirtyBits = ~0;
}
/*
* Clear all dirty bits
*/
private void clearDirty() {
dirtyBits = 0;
}
/*
* Test whether the set of dirty bits is empty
*/
final boolean isDirtyEmpty() {
return dirtyBits == 0;
}
*
Methods for synchronizing state from this Node to its PG peer. This *
should only *ever* be called during synchronization initialized as a *
result of a pulse. Any attempt to synchronize at any other time may *
cause rendering artifacts. *
*
/**************************************************************************
* *
* Methods for synchronizing state from this Node to its PG peer. This *
* should only *ever* be called during synchronization initialized as a *
* result of a pulse. Any attempt to synchronize at any other time may *
* cause rendering artifacts. *
* *
*************************************************************************/
/*
* Called by the synchronizer to update the state and
* clear dirtybits of this node in the PG graph
*/
final void syncPeer() {
// Do not synchronize invisible nodes unless their visibility has changed
// or they have requested a forced synchronization
if (!isDirtyEmpty() && (treeVisible
|| isDirty(DirtyBits.NODE_VISIBLE)
|| isDirty(DirtyBits.NODE_FORCE_SYNC)))
{
NodeHelper.updatePeer(this);
clearDirty();
}
}
A temporary rect used for computing bounds by the various bounds
variables. This bounds starts life as a RectBounds, but may be promoted
to a BoxBounds if there is a 3D transform mixed into its computation.
These two fields were held in a thread local, but were then pulled
out of it so that we could compute bounds before holding the
synchronization lock. These objects have to be per-instance so
that we can pass the right data down to the PG side later during
synchronization (rather than statics as they were before).
/**
* A temporary rect used for computing bounds by the various bounds
* variables. This bounds starts life as a RectBounds, but may be promoted
* to a BoxBounds if there is a 3D transform mixed into its computation.
* These two fields were held in a thread local, but were then pulled
* out of it so that we could compute bounds before holding the
* synchronization lock. These objects have to be per-instance so
* that we can pass the right data down to the PG side later during
* synchronization (rather than statics as they were before).
*/
private BaseBounds _geomBounds = new RectBounds(0, 0, -1, -1);
private BaseBounds _txBounds = new RectBounds(0, 0, -1, -1);
private boolean pendingUpdateBounds = false;
// Happens before we hold the sync lock
void updateBounds() {
// Note: the clip must be handled before the visibility is checked. This is because the visiblity might be
// changing in the clip and it is going to be synchronized, so it needs to recompute the bounds.
Node n = getClip();
if (n != null) {
n.updateBounds();
}
// See syncPeer()
if (!treeVisible && !isDirty(DirtyBits.NODE_VISIBLE)) {
// Need to save the dirty bits since they will be cleared even for the
// case of short circuiting dirty bit processing.
if (isDirty(DirtyBits.NODE_TRANSFORM)
|| isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)
|| isDirty(DirtyBits.NODE_BOUNDS)) {
pendingUpdateBounds = true;
}
return;
}
// Set transform and bounds dirty bits when this node becomes visible
if (pendingUpdateBounds) {
NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM);
NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS);
NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS);
pendingUpdateBounds = false;
}
if (isDirty(DirtyBits.NODE_TRANSFORM) || isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
if (isDirty(DirtyBits.NODE_TRANSFORM)) {
updateLocalToParentTransform();
}
_txBounds = getTransformedBounds(_txBounds,
BaseTransform.IDENTITY_TRANSFORM);
}
if (isDirty(DirtyBits.NODE_BOUNDS)) {
_geomBounds = getGeomBounds(_geomBounds,
BaseTransform.IDENTITY_TRANSFORM);
}
}
/*
* This function is called during synchronization to update the state of the
* NG Node from the FX Node. Subclasses of Node should override this method
* and must call NodeHelper.updatePeer(this)
*
* Note: This method MUST only be called via its accessor method.
*/
private void doUpdatePeer() {
final NGNode peer = getPeer();
// For debug / diagnostic purposes, we will copy across a name for this node down to
// the NG layer, where we can use the name to figure out what the NGNode represents.
// An alternative would be to have a back-reference from the NGNode back to the Node it
// is a peer to, however it was felt that this would make it too easy to communicate back
// to the Node and possibly violate thread invariants. But of course, we only need to do this
// if we're going to print the render graph (otherwise all the work we'd do to keep the name
// properly updated would be a waste).
if (PrismSettings.printRenderGraph && isDirty(DirtyBits.DEBUG)) {
final String id = getId();
String className = getClass().getSimpleName();
if (className.isEmpty()) {
className = getClass().getName();
}
peer.setName(id == null ? className : id + "(" + className + ")");
}
if (isDirty(DirtyBits.NODE_TRANSFORM)) {
peer.setTransformMatrix(localToParentTx);
}
if (isDirty(DirtyBits.NODE_VIEW_ORDER)) {
peer.setViewOrder(getViewOrder());
}
if (isDirty(DirtyBits.NODE_BOUNDS)) {
peer.setContentBounds(_geomBounds);
}
if (isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
peer.setTransformedBounds(_txBounds, !isDirty(DirtyBits.NODE_BOUNDS));
}
if (isDirty(DirtyBits.NODE_OPACITY)) {
peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
}
if (isDirty(DirtyBits.NODE_CACHE)) {
peer.setCachedAsBitmap(isCache(), getCacheHint());
}
if (isDirty(DirtyBits.NODE_CLIP)) {
peer.setClipNode(getClip() != null ? getClip().getPeer() : null);
}
if (isDirty(DirtyBits.EFFECT_EFFECT)) {
if (getEffect() != null) {
EffectHelper.sync(getEffect());
peer.effectChanged();
}
}
if (isDirty(DirtyBits.NODE_EFFECT)) {
peer.setEffect(getEffect() != null ? EffectHelper.getPeer(getEffect()) : null);
}
if (isDirty(DirtyBits.NODE_VISIBLE)) {
peer.setVisible(isVisible());
}
if (isDirty(DirtyBits.NODE_DEPTH_TEST)) {
peer.setDepthTest(isDerivedDepthTest());
}
if (isDirty(DirtyBits.NODE_BLENDMODE)) {
BlendMode mode = getBlendMode();
peer.setNodeBlendMode((mode == null)
? null
: EffectHelper.getToolkitBlendMode(mode));
}
}
*
*
*
/*************************************************************************
* *
* *
* *
*************************************************************************/
private static final Object USER_DATA_KEY = new Object();
// A map containing a set of properties for this node
private ObservableMap<Object, Object> properties;
Returns an observable map of properties on this node for use primarily
by application developers.
Returns: an observable map of properties on this node for use primarily
by application developers
/**
* Returns an observable map of properties on this node for use primarily
* by application developers.
*
* @return an observable map of properties on this node for use primarily
* by application developers
*/
public final ObservableMap<Object, Object> getProperties() {
if (properties == null) {
properties = FXCollections.observableMap(new HashMap<Object, Object>());
}
return properties;
}
Tests if Node has properties.
Returns: true if node has properties.
/**
* Tests if Node has properties.
* @return true if node has properties.
*/
public boolean hasProperties() {
return properties != null && !properties.isEmpty();
}
Convenience method for setting a single Object property that can be retrieved at a later date. This is functionally equivalent to calling the getProperties().put(Object key, Object value) method. This can later be retrieved by calling getUserData()
. Params: - value – The value to be stored - this can later be retrieved by calling
getUserData()
.
/**
* Convenience method for setting a single Object property that can be
* retrieved at a later date. This is functionally equivalent to calling
* the getProperties().put(Object key, Object value) method. This can later
* be retrieved by calling {@link Node#getUserData()}.
*
* @param value The value to be stored - this can later be retrieved by calling
* {@link Node#getUserData()}.
*/
public void setUserData(Object value) {
getProperties().put(USER_DATA_KEY, value);
}
Returns a previously set Object property, or null if no such property has been set using the setUserData(Object)
method. Returns: The Object that was previously set, or null if no property
has been set or if null was set.
/**
* Returns a previously set Object property, or null if no such property
* has been set using the {@link Node#setUserData(java.lang.Object)} method.
*
* @return The Object that was previously set, or null if no property
* has been set or if null was set.
*/
public Object getUserData() {
return getProperties().get(USER_DATA_KEY);
}
/**************************************************************************
* *
*
* *
*************************************************************************/
The parent of this Node
. If this Node
has not been added to a scene graph, then parent will be null. @defaultValue null
/**
* The parent of this {@code Node}. If this {@code Node} has not been added
* to a scene graph, then parent will be null.
*
* @defaultValue null
*/
private ReadOnlyObjectWrapper<Parent> parent;
final void setParent(Parent value) {
parentPropertyImpl().set(value);
}
public final Parent getParent() {
return parent == null ? null : parent.get();
}
public final ReadOnlyObjectProperty<Parent> parentProperty() {
return parentPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyObjectWrapper<Parent> parentPropertyImpl() {
if (parent == null) {
parent = new ReadOnlyObjectWrapper<Parent>() {
private Parent oldParent;
@Override
protected void invalidated() {
if (oldParent != null) {
oldParent.disabledProperty().removeListener(parentDisabledChangedListener);
oldParent.treeVisibleProperty().removeListener(parentTreeVisibleChangedListener);
if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
((Node) oldParent).localToSceneTransformProperty().removeListener(
nodeTransformation.getLocalToSceneInvalidationListener());
}
}
updateDisabled();
computeDerivedDepthTest();
final Parent newParent = get();
if (newParent != null) {
newParent.disabledProperty().addListener(parentDisabledChangedListener);
newParent.treeVisibleProperty().addListener(parentTreeVisibleChangedListener);
if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
((Node) newParent).localToSceneTransformProperty().addListener(
nodeTransformation.getLocalToSceneInvalidationListener());
}
//
// if parent changed, then CSS needs to be reapplied so
// that this node will get the right styles. This used
// to be done from Parent.children's onChanged method.
// See the comments there, also.
//
reapplyCSS();
} else {
// RT-31168: reset CssFlag to clean so css will be reapplied if the node is added back later.
// If flag is REAPPLY, then reapplyCSS() will just return and the call to
// notifyParentsOfInvalidatedCSS() will be skipped thus leaving the node un-styled.
cssFlag = CssFlags.CLEAN;
}
updateTreeVisible(true);
oldParent = newParent;
invalidateLocalToSceneTransform();
parentResolvedOrientationInvalidated();
notifyAccessibleAttributeChanged(AccessibleAttribute.PARENT);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "parent";
}
};
}
return parent;
}
private final InvalidationListener parentDisabledChangedListener = valueModel -> updateDisabled();
private final InvalidationListener parentTreeVisibleChangedListener = valueModel -> updateTreeVisible(true);
private final ChangeListener<Boolean> windowShowingChangedListener
= (win, oldVal, newVal) -> updateTreeShowing();
private final ChangeListener<Window> sceneWindowChangedListener = (scene, oldWindow, newWindow) -> {
// Replace the windowShowingListener and call updateTreeShowing()
if (oldWindow != null) {
oldWindow.showingProperty().removeListener(windowShowingChangedListener);
}
if (newWindow != null) {
newWindow.showingProperty().addListener(windowShowingChangedListener);
}
updateTreeShowing();
};
private SubScene subScene = null;
The Scene
that this Node
is part of. If the Node is not part of a scene, then this variable will be null. @defaultValue null
/**
* The {@link Scene} that this {@code Node} is part of. If the Node is not
* part of a scene, then this variable will be null.
*
* @defaultValue null
*/
private ReadOnlyObjectWrapperManualFire<Scene> scene = new ReadOnlyObjectWrapperManualFire<Scene>();
private class ReadOnlyObjectWrapperManualFire<T> extends ReadOnlyObjectWrapper<T> {
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "scene";
}
@Override
protected void fireValueChangedEvent() {
/*
* Note: This method has been intentionally made into a no-op. In
* order to override the default set behavior. By default calling
* set(...) on a different scene will trigger:
* - invalidated();
* - fireValueChangedEvent();
* Both of the above are no-ops, but are handled manually via
* - Node.this.setScenes(...)
* - Node.this.invalidatedScenes(...)
* - forceValueChangedEvent()
*/
}
public void fireSuperValueChangedEvent() {
super.fireValueChangedEvent();
}
}
private void invalidatedScenes(Scene oldScene, SubScene oldSubScene) {
Scene newScene = sceneProperty().get();
boolean sceneChanged = oldScene != newScene;
SubScene newSubScene = subScene;
if (getClip() != null) {
getClip().setScenes(newScene, newSubScene);
}
if (sceneChanged) {
updateCanReceiveFocus();
if (isFocusTraversable()) {
if (newScene != null) {
newScene.initializeInternalEventDispatcher();
}
}
focusSetDirty(oldScene);
focusSetDirty(newScene);
}
scenesChanged(newScene, newSubScene, oldScene, oldSubScene);
// isTreeShowing needs to take into account of Window's showing
if (oldScene != null) {
oldScene.windowProperty().removeListener(sceneWindowChangedListener);
Window window = oldScene.windowProperty().get();
if (window != null) {
window.showingProperty().removeListener(windowShowingChangedListener);
}
}
if (newScene != null) {
newScene.windowProperty().addListener(sceneWindowChangedListener);
Window window = newScene.windowProperty().get();
if (window != null) {
window.showingProperty().addListener(windowShowingChangedListener);
}
}
updateTreeShowing();
if (sceneChanged) reapplyCSS();
if (sceneChanged && !isDirtyEmpty()) {
//Note: no need to remove from scene's dirty list
//Scene's is checking if the node's scene is correct
/* TODO: looks like an existing bug when a node is moved from one
* location to another, setScenes will be called twice by
* Parent.VetoableListDecorator onProposedChange and onChanged
* respectively. Removing the node and setting setScense(null,null)
* then adding it back to potentially the same scene. Causing the
* same node to being added twice to the same scene.
*/
addToSceneDirtyList();
}
if (newScene == null && peer != null) {
peer.release();
}
if (oldScene != null) {
oldScene.clearNodeMnemonics(this);
}
if (getParent() == null) {
// if we are the root we need to handle scene change
parentResolvedOrientationInvalidated();
}
if (sceneChanged) { scene.fireSuperValueChangedEvent(); }
/* Dispose the accessible peer, if any. If AT ever needs this node again
* a new accessible peer is created. */
if (accessible != null) {
/* Generally accessibility does not retain any state, therefore deleting objects
* generally does not cause problems (AT just asks everything back).
* The exception to this rule is when the object sends a notifications to the AT,
* in which case it is expected to be around to answer request for the new values.
* It is possible that a object is reparented (within the scene) in the middle of
* this process. For example, when a tree item is expanded, the notification is
* sent to the AT by the cell. But when the TreeView relayouts the cell can be
* reparented before AT can query the relevant information about the expand event.
* If the accessible was disposed, AT can't properly report the event.
*
* The fix is to defer the disposal of the accessible to the next pulse.
* If at that time the node is placed back to the scene, then the accessible is hooked
* to Node and AT requests are processed. Otherwise the accessible is disposed.
*/
if (oldScene != null && oldScene != newScene && newScene == null) {
// Strictly speaking we need some type of accessible.thaw() at this point.
oldScene.addAccessible(Node.this, accessible);
} else {
accessible.dispose();
}
/* Always set to null to ensure this accessible is never on more than one
* Scene#accMap at the same time (At lest not with the same accessible).
*/
accessible = null;
}
}
final void setScenes(Scene newScene, SubScene newSubScene) {
Scene oldScene = sceneProperty().get();
if (newScene != oldScene || newSubScene != subScene) {
scene.set(newScene);
SubScene oldSubScene = subScene;
subScene = newSubScene;
invalidatedScenes(oldScene, oldSubScene);
if (this instanceof SubScene) { // TODO: find better solution
SubScene thisSubScene = (SubScene)this;
thisSubScene.getRoot().setScenes(newScene, thisSubScene);
}
}
}
final SubScene getSubScene() {
return subScene;
}
public final Scene getScene() {
return scene.get();
}
public final ReadOnlyObjectProperty<Scene> sceneProperty() {
return scene.getReadOnlyProperty();
}
Exists for Parent and LightBase
/**
* Exists for Parent and LightBase
*/
void scenesChanged(final Scene newScene, final SubScene newSubScene,
final Scene oldScene, final SubScene oldSubScene) { }
The id of this Node
. This simple string identifier is useful for finding a specific Node within the scene graph. While the id of a Node should be unique within the scene graph, this uniqueness is not enforced. This is analogous to the "id" attribute on an HTML element (CSS ID Specification).
For example, if a Node is given the id of "myId", then the lookup method can
be used to find this node as follows: scene.lookup("#myId");
.
See Also: @defaultValue null
/**
* The id of this {@code Node}. This simple string identifier is useful for
* finding a specific Node within the scene graph. While the id of a Node
* should be unique within the scene graph, this uniqueness is not enforced.
* This is analogous to the "id" attribute on an HTML element
* (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
* <p>
* For example, if a Node is given the id of "myId", then the lookup method can
* be used to find this node as follows: <code>scene.lookup("#myId");</code>.
* </p>
*
* @defaultValue null
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
*/
private StringProperty id;
public final void setId(String value) {
idProperty().set(value);
}
//TODO: this is copied from the property in order to add the @return statement.
// We should have a better, general solution without the need to copy it.
The id of this Node
. This simple string identifier is useful for finding a specific Node within the scene graph. While the id of a Node should be unique within the scene graph, this uniqueness is not enforced. This is analogous to the "id" attribute on an HTML element (CSS ID Specification).
See Also: Returns: the id assigned to this Node
using the setId
method or null
, if no id has been assigned. @defaultValue null
/**
* The id of this {@code Node}. This simple string identifier is useful for
* finding a specific Node within the scene graph. While the id of a Node
* should be unique within the scene graph, this uniqueness is not enforced.
* This is analogous to the "id" attribute on an HTML element
* (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
*
* @return the id assigned to this {@code Node} using the {@code setId}
* method or {@code null}, if no id has been assigned.
* @defaultValue null
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
*/
public final String getId() {
return id == null ? null : id.get();
}
public final StringProperty idProperty() {
if (id == null) {
id = new StringPropertyBase() {
@Override
protected void invalidated() {
reapplyCSS();
if (PrismSettings.printRenderGraph) {
NodeHelper.markDirty(Node.this, DirtyBits.DEBUG);
}
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "id";
}
};
}
return id;
}
A list of String identifiers which can be used to logically group
Nodes, specifically for an external style engine. This variable is
analogous to the "class" attribute on an HTML element and, as such,
each element of the list is a style class to which this Node belongs.
See Also: @defaultValue null
/**
* A list of String identifiers which can be used to logically group
* Nodes, specifically for an external style engine. This variable is
* analogous to the "class" attribute on an HTML element and, as such,
* each element of the list is a style class to which this Node belongs.
*
* @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
* @defaultValue null
*/
private ObservableList<String> styleClass = new TrackableObservableList<String>() {
@Override
protected void onChanged(Change<String> c) {
reapplyCSS();
}
@Override
public String toString() {
if (size() == 0) {
return "";
} else if (size() == 1) {
return get(0);
} else {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < size(); i++) {
buf.append(get(i));
if (i + 1 < size()) {
buf.append(' ');
}
}
return buf.toString();
}
}
};
@Override
public final ObservableList<String> getStyleClass() {
return styleClass;
}
A string representation of the CSS style associated with this specific Node
. This is analogous to the "style" attribute of an HTML element. Note that, like the HTML style attribute, this variable contains style properties and values and not the selector portion of a style rule. See Also: @defaultValue empty string
/**
* A string representation of the CSS style associated with this
* specific {@code Node}. This is analogous to the "style" attribute of an
* HTML element. Note that, like the HTML style attribute, this
* variable contains style properties and values and not the
* selector portion of a style rule.
* @defaultValue empty string
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
*/
private StringProperty style;
A string representation of the CSS style associated with this specific Node
. This is analogous to the "style" attribute of an HTML element. Note that, like the HTML style attribute, this variable contains style properties and values and not the selector portion of a style rule. Params: - value – The inline CSS style to use for this
Node
. null
is implicitly converted to an empty String.
See Also: @defaultValue empty string
/**
* A string representation of the CSS style associated with this
* specific {@code Node}. This is analogous to the "style" attribute of an
* HTML element. Note that, like the HTML style attribute, this
* variable contains style properties and values and not the
* selector portion of a style rule.
* @param value The inline CSS style to use for this {@code Node}.
* {@code null} is implicitly converted to an empty String.
* @defaultValue empty string
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
*/
public final void setStyle(String value) {
styleProperty().set(value);
}
// TODO: javadoc copied from property for the sole purpose of providing a return tag
A string representation of the CSS style associated with this specific Node
. This is analogous to the "style" attribute of an HTML element. Note that, like the HTML style attribute, this variable contains style properties and values and not the selector portion of a style rule. See Also: @defaultValue empty string Returns: The inline CSS style associated with this Node
. If this Node
does not have an inline style, an empty String is returned.
/**
* A string representation of the CSS style associated with this
* specific {@code Node}. This is analogous to the "style" attribute of an
* HTML element. Note that, like the HTML style attribute, this
* variable contains style properties and values and not the
* selector portion of a style rule.
* @defaultValue empty string
* @return The inline CSS style associated with this {@code Node}.
* If this {@code Node} does not have an inline style,
* an empty String is returned.
* @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
*/
public final String getStyle() {
return style == null ? "" : style.get();
}
public final StringProperty styleProperty() {
if (style == null) {
style = new StringPropertyBase("") {
@Override public void set(String value) {
// getStyle returns an empty string if the style property
// is null. To be consistent, getStyle should also return
// an empty string when the style property's value is null.
super.set((value != null) ? value : "");
}
@Override
protected void invalidated() {
// If the style has changed, then styles of this node
// and child nodes might be affected.
reapplyCSS();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "style";
}
};
}
return style;
}
Specifies whether this Node
and any subnodes should be rendered as part of the scene graph. A node may be visible and yet not be shown in the rendered scene if, for instance, it is off the screen or obscured by another Node. Invisible nodes never receive mouse events or keyboard focus and never maintain keyboard focus when they become invisible. @defaultValue true
/**
* Specifies whether this {@code Node} and any subnodes should be rendered
* as part of the scene graph. A node may be visible and yet not be shown
* in the rendered scene if, for instance, it is off the screen or obscured
* by another Node. Invisible nodes never receive mouse events or
* keyboard focus and never maintain keyboard focus when they become
* invisible.
*
* @defaultValue true
*/
private BooleanProperty visible;
public final void setVisible(boolean value) {
visibleProperty().set(value);
}
public final boolean isVisible() {
return visible == null ? true : visible.get();
}
public final BooleanProperty visibleProperty() {
if (visible == null) {
visible = new StyleableBooleanProperty(true) {
boolean oldValue = true;
@Override
protected void invalidated() {
if (oldValue != get()) {
NodeHelper.markDirty(Node.this, DirtyBits.NODE_VISIBLE);
NodeHelper.geomChanged(Node.this);
updateTreeVisible(false);
if (getParent() != null) {
// notify the parent of the potential change in visibility
// of this node, since visibility affects bounds of the
// parent node
getParent().childVisibilityChanged(Node.this);
}
oldValue = get();
}
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.VISIBILITY;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "visible";
}
};
}
return visible;
}
public final void setCursor(Cursor value) {
cursorProperty().set(value);
}
public final Cursor getCursor() {
return (miscProperties == null) ? DEFAULT_CURSOR
: miscProperties.getCursor();
}
Defines the mouse cursor for this Node
and subnodes. If null, then the cursor of the first parent node with a non-null cursor will be used. If no Node in the scene graph defines a cursor, then the cursor of the Scene
will be used. Returns: the mouse cursor for this Node
and subnodes @defaultValue null
/**
* Defines the mouse cursor for this {@code Node} and subnodes. If null,
* then the cursor of the first parent node with a non-null cursor will be
* used. If no Node in the scene graph defines a cursor, then the cursor
* of the {@code Scene} will be used.
*
* @return the mouse cursor for this {@code Node} and subnodes
* @defaultValue null
*/
public final ObjectProperty<Cursor> cursorProperty() {
return getMiscProperties().cursorProperty();
}
Specifies how opaque (that is, solid) the Node
appears. A Node with 0% opacity is fully translucent. That is, while it is still visible
and rendered, you generally won't be able to see it. The exception to this rule is when the Node
is combined with a blending mode and blend effect in which case a translucent Node may still have an impact in rendering. An opacity of 50% will render the node as being 50% transparent. A visible
node with any opacity setting still receives mouse events and can receive keyboard focus. For example, if you want to have a large invisible rectangle overlay all Node
s in the scene graph in order to intercept mouse events but not be visible to the user, you could create a large Rectangle
that had an opacity of 0%.
Opacity is specified as a value between 0 and 1. Values less than 0 are
treated as 0, values greater than 1 are treated as 1.
On some platforms ImageView might not support opacity variable.
There is a known limitation of mixing opacity < 1.0 with a 3D Transform. Opacity/Blending is essentially a 2D image operation. The result of an opacity < 1.0 set on a Group
node with 3D transformed children will cause its children to be rendered in order without Z-buffering applied between those children.
@defaultValue 1.0
/**
* Specifies how opaque (that is, solid) the {@code Node} appears. A Node
* with 0% opacity is fully translucent. That is, while it is still
* {@link #visibleProperty visible} and rendered, you generally won't be able to see it. The
* exception to this rule is when the {@code Node} is combined with a
* blending mode and blend effect in which case a translucent Node may still
* have an impact in rendering. An opacity of 50% will render the node as
* being 50% transparent.
* <p>
* A {@link #visibleProperty visible} node with any opacity setting still receives mouse
* events and can receive keyboard focus. For example, if you want to have
* a large invisible rectangle overlay all {@code Node}s in the scene graph
* in order to intercept mouse events but not be visible to the user, you could
* create a large {@code Rectangle} that had an opacity of 0%.
* <p>
* Opacity is specified as a value between 0 and 1. Values less than 0 are
* treated as 0, values greater than 1 are treated as 1.
* <p>
* On some platforms ImageView might not support opacity variable.
*
* <p>
* There is a known limitation of mixing opacity < 1.0 with a 3D Transform.
* Opacity/Blending is essentially a 2D image operation. The result of
* an opacity < 1.0 set on a {@link Group} node with 3D transformed children
* will cause its children to be rendered in order without Z-buffering
* applied between those children.
*
* @defaultValue 1.0
*/
private DoubleProperty opacity;
public final void setOpacity(double value) {
opacityProperty().set(value);
}
public final double getOpacity() {
return opacity == null ? 1 : opacity.get();
}
public final DoubleProperty opacityProperty() {
if (opacity == null) {
opacity = new StyleableDoubleProperty(1) {
@Override
public void invalidated() {
NodeHelper.markDirty(Node.this, DirtyBits.NODE_OPACITY);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.OPACITY;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "opacity";
}
};
}
return opacity;
}
The BlendMode
used to blend this individual node into the scene behind it. If this node happens to be a Group then all of the children will be composited individually into a temporary buffer using their own blend modes and then that temporary buffer will be composited into the scene using the specified blend mode. A value of null
is treated as pass-though this means no effect on a parent such as a Group and the equivalent of SRC_OVER for a single Node. @defaultValue null
/**
* The {@link javafx.scene.effect.BlendMode} used to blend this individual node
* into the scene behind it. If this node happens to be a Group then all of the
* children will be composited individually into a temporary buffer using their
* own blend modes and then that temporary buffer will be composited into the
* scene using the specified blend mode.
*
* A value of {@code null} is treated as pass-though this means no effect on a
* parent such as a Group and the equivalent of SRC_OVER for a single Node.
*
* @defaultValue null
*/
private javafx.beans.property.ObjectProperty<BlendMode> blendMode;
public final void setBlendMode(BlendMode value) {
blendModeProperty().set(value);
}
public final BlendMode getBlendMode() {
return blendMode == null ? null : blendMode.get();
}
public final ObjectProperty<BlendMode> blendModeProperty() {
if (blendMode == null) {
blendMode = new StyleableObjectProperty<BlendMode>(null) {
@Override public void invalidated() {
NodeHelper.markDirty(Node.this, DirtyBits.NODE_BLENDMODE);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.BLEND_MODE;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "blendMode";
}
};
}
return blendMode;
}
public final void setClip(Node value) {
clipProperty().set(value);
}
public final Node getClip() {
return (miscProperties == null) ? DEFAULT_CLIP
: miscProperties.getClip();
}
Specifies a Node
to use to define the the clipping shape for this Node. This clipping Node is not a child of this Node
in the scene graph sense. Rather, it is used to define the clip for this Node
. For example, you can use an ImageView
Node as a mask to represent the Clip. Or you could use one of the geometric shape Nodes such as Rectangle
or Circle
. Or you could use a Text
node to represent the Clip.
See the class documentation for Node
for scene graph structure restrictions on setting the clip. If these restrictions are violated by a change to the clip variable, the change is ignored and the previous value of the clip variable is restored.
Note that this is a conditional feature. See ConditionalFeature.SHAPE_CLIP
for more information.
There is a known limitation of mixing Clip with a 3D Transform. Clipping is essentially a 2D image operation. The result of a Clip set on a Group
node with 3D transformed children will cause its children to be rendered in order without Z-buffering applied between those children.
Returns: the the clipping shape for this Node
@defaultValue null
/**
* Specifies a {@code Node} to use to define the the clipping shape for this
* Node. This clipping Node is not a child of this {@code Node} in the scene
* graph sense. Rather, it is used to define the clip for this {@code Node}.
* <p>
* For example, you can use an {@link javafx.scene.image.ImageView} Node as
* a mask to represent the Clip. Or you could use one of the geometric shape
* Nodes such as {@link javafx.scene.shape.Rectangle} or
* {@link javafx.scene.shape.Circle}. Or you could use a
* {@link javafx.scene.text.Text} node to represent the Clip.
* <p>
* See the class documentation for {@link Node} for scene graph structure
* restrictions on setting the clip. If these restrictions are violated by
* a change to the clip variable, the change is ignored and the
* previous value of the clip variable is restored.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP}
* for more information.
* <p>
* There is a known limitation of mixing Clip with a 3D Transform.
* Clipping is essentially a 2D image operation. The result of
* a Clip set on a {@link Group} node with 3D transformed children
* will cause its children to be rendered in order without Z-buffering
* applied between those children.
*
* @return the the clipping shape for this {@code Node}
* @defaultValue null
*/
public final ObjectProperty<Node> clipProperty() {
return getMiscProperties().clipProperty();
}
public final void setCache(boolean value) {
cacheProperty().set(value);
}
public final boolean isCache() {
return (miscProperties == null) ? DEFAULT_CACHE
: miscProperties.isCache();
}
A performance hint to the system to indicate that this Node
should be cached as a bitmap. Rendering a bitmap representation of a node will be faster than rendering primitives in many cases, especially in the case of primitives with effects applied (such as a blur). However, it also increases memory usage. This hint indicates whether that trade-off (increased memory usage for increased performance) is worthwhile. Also note that on some platforms such as GPU accelerated platforms there is little benefit to caching Nodes as bitmaps when blurs and other effects are used since they are very fast to render on the GPU. The cacheHintProperty
variable provides additional options for enabling more aggressive bitmap caching.
Caching may be disabled for any node that has a 3D transform on itself,
any of its ancestors, or any of its descendants.
See Also: Returns: the hint to cache for this Node
@defaultValue false
/**
* A performance hint to the system to indicate that this {@code Node}
* should be cached as a bitmap. Rendering a bitmap representation of a node
* will be faster than rendering primitives in many cases, especially in the
* case of primitives with effects applied (such as a blur). However, it
* also increases memory usage. This hint indicates whether that trade-off
* (increased memory usage for increased performance) is worthwhile. Also
* note that on some platforms such as GPU accelerated platforms there is
* little benefit to caching Nodes as bitmaps when blurs and other effects
* are used since they are very fast to render on the GPU.
*
* The {@link #cacheHintProperty} variable provides additional options for enabling
* more aggressive bitmap caching.
*
* <p>
* Caching may be disabled for any node that has a 3D transform on itself,
* any of its ancestors, or any of its descendants.
*
* @return the hint to cache for this {@code Node}
* @see #cacheHintProperty
* @defaultValue false
*/
public final BooleanProperty cacheProperty() {
return getMiscProperties().cacheProperty();
}
public final void setCacheHint(CacheHint value) {
cacheHintProperty().set(value);
}
public final CacheHint getCacheHint() {
return (miscProperties == null) ? DEFAULT_CACHE_HINT
: miscProperties.getCacheHint();
}
Additional hint for controlling bitmap caching.
Under certain circumstances, such as animating nodes that are very
expensive to render, it is desirable to be able to perform
transformations on the node without having to regenerate the cached
bitmap. An option in such cases is to perform the transforms on the
cached bitmap itself.
This technique can provide a dramatic improvement to animation performance, though may also result in a reduction in visual quality. The cacheHint
variable provides a hint to the system about how and when that trade-off (visual quality for animation performance) is acceptable.
It is possible to enable the cacheHint only at times when your node is
animating. In this way, expensive nodes can appear on screen with full
visual quality, yet still animate smoothly.
Example:
expensiveNode.setCache(true);
expensiveNode.setCacheHint(CacheHint.QUALITY);
...
// Do an animation
expensiveNode.setCacheHint(CacheHint.SPEED);
new Timeline(
new KeyFrame(Duration.seconds(2),
new KeyValue(expensiveNode.scaleXProperty(), 2.0),
new KeyValue(expensiveNode.scaleYProperty(), 2.0),
new KeyValue(expensiveNode.rotateProperty(), 360),
new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
)
).play();
Note that cacheHint
is only a hint to the system. Depending on the details of the node or the transform, this hint may be ignored. If Node.cache
is false, cacheHint is ignored. Caching may be disabled for any node that has a 3D transform on itself, any of its ancestors, or any of its descendants.
See Also: Returns: the CacheHint
for this Node
@defaultValue CacheHint.DEFAULT
/**
* Additional hint for controlling bitmap caching.
* <p>
* Under certain circumstances, such as animating nodes that are very
* expensive to render, it is desirable to be able to perform
* transformations on the node without having to regenerate the cached
* bitmap. An option in such cases is to perform the transforms on the
* cached bitmap itself.
* <p>
* This technique can provide a dramatic improvement to animation
* performance, though may also result in a reduction in visual quality.
* The {@code cacheHint} variable provides a hint to the system about how
* and when that trade-off (visual quality for animation performance) is
* acceptable.
* <p>
* It is possible to enable the cacheHint only at times when your node is
* animating. In this way, expensive nodes can appear on screen with full
* visual quality, yet still animate smoothly.
* <p>
* Example:
* <pre>{@code
expensiveNode.setCache(true);
expensiveNode.setCacheHint(CacheHint.QUALITY);
...
// Do an animation
expensiveNode.setCacheHint(CacheHint.SPEED);
new Timeline(
new KeyFrame(Duration.seconds(2),
new KeyValue(expensiveNode.scaleXProperty(), 2.0),
new KeyValue(expensiveNode.scaleYProperty(), 2.0),
new KeyValue(expensiveNode.rotateProperty(), 360),
new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
)
).play();
}</pre>
*
* Note that {@code cacheHint} is only a hint to the system. Depending on
* the details of the node or the transform, this hint may be ignored.
*
* <p>
* If {@code Node.cache} is false, cacheHint is ignored.
* Caching may be disabled for any node that has a 3D transform on itself,
* any of its ancestors, or any of its descendants.
*
* @return the {@code CacheHint} for this {@code Node}
* @see #cacheProperty
* @defaultValue CacheHint.DEFAULT
*/
public final ObjectProperty<CacheHint> cacheHintProperty() {
return getMiscProperties().cacheHintProperty();
}
public final void setEffect(Effect value) {
effectProperty().set(value);
}
public final Effect getEffect() {
return (miscProperties == null) ? DEFAULT_EFFECT
: miscProperties.getEffect();
}
Specifies an effect to apply to this Node
. Note that this is a conditional feature. See ConditionalFeature.EFFECT
for more information.
There is a known limitation of mixing Effect with a 3D Transform. Effect is essentially a 2D image operation. The result of an Effect set on a Group
node with 3D transformed children will cause its children to be rendered in order without Z-buffering applied between those children.
Returns: the effect for this Node
@defaultValue null
/**
* Specifies an effect to apply to this {@code Node}.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT}
* for more information.
*
* <p>
* There is a known limitation of mixing Effect with a 3D Transform. Effect is
* essentially a 2D image operation. The result of an Effect set on
* a {@link Group} node with 3D transformed children will cause its children
* to be rendered in order without Z-buffering applied between those
* children.
*
* @return the effect for this {@code Node}
* @defaultValue null
*/
public final ObjectProperty<Effect> effectProperty() {
return getMiscProperties().effectProperty();
}
public final void setDepthTest(DepthTest value) {
depthTestProperty().set(value);
}
public final DepthTest getDepthTest() {
return (miscProperties == null) ? DEFAULT_DEPTH_TEST
: miscProperties.getDepthTest();
}
Indicates whether depth testing is used when rendering this node. If the depthTest flag is DepthTest.DISABLE
, then depth testing is disabled for this node. If the depthTest flag is DepthTest.ENABLE
, then depth testing is enabled for this node. If the depthTest flag is DepthTest.INHERIT
, then depth testing is enabled for this node if it is enabled for the parent node or the parent node is null. The depthTest flag is only used when the depthBuffer flag for the Scene
is true (meaning that the Scene
has an associated depth buffer)
Depth test comparison is only done among nodes with depthTest enabled.
A node with depthTest disabled does not read, test, or write the depth buffer,
that is to say its Z value will not be considered for depth testing
with other nodes.
Note that this is a conditional feature. See ConditionalFeature.SCENE3D
for more information.
See the constructor in Scene with depthBuffer as one of its input
arguments.
See Also: Returns: the depth test setting for this Node
@defaultValue INHERIT
/**
* Indicates whether depth testing is used when rendering this node.
* If the depthTest flag is {@code DepthTest.DISABLE}, then depth testing
* is disabled for this node.
* If the depthTest flag is {@code DepthTest.ENABLE}, then depth testing
* is enabled for this node.
* If the depthTest flag is {@code DepthTest.INHERIT}, then depth testing
* is enabled for this node if it is enabled for the parent node or the
* parent node is null.
* <p>
* The depthTest flag is only used when the depthBuffer flag for
* the {@link Scene} is true (meaning that the
* {@link Scene} has an associated depth buffer)
* <p>
* Depth test comparison is only done among nodes with depthTest enabled.
* A node with depthTest disabled does not read, test, or write the depth buffer,
* that is to say its Z value will not be considered for depth testing
* with other nodes.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
* <p>
* See the constructor in Scene with depthBuffer as one of its input
* arguments.
*
* @return the depth test setting for this {@code Node}
* @see javafx.scene.Scene
* @defaultValue INHERIT
*/
public final ObjectProperty<DepthTest> depthTestProperty() {
return getMiscProperties().depthTestProperty();
}
Recompute the derived depth test flag. This flag is true
if the depthTest flag for this node is true and the
depth test flag for each ancestor node is true. It is false
otherwise. Equivalently, the derived depth flag is true
if the depthTest flag for this node is true and the derivedDepthTest
flag for its parent is true.
/**
* Recompute the derived depth test flag. This flag is true
* if the depthTest flag for this node is true and the
* depth test flag for each ancestor node is true. It is false
* otherwise. Equivalently, the derived depth flag is true
* if the depthTest flag for this node is true and the derivedDepthTest
* flag for its parent is true.
*/
void computeDerivedDepthTest() {
boolean newDDT;
if (getDepthTest() == DepthTest.INHERIT) {
if (getParent() != null) {
newDDT = getParent().isDerivedDepthTest();
} else {
newDDT = true;
}
} else if (getDepthTest() == DepthTest.ENABLE) {
newDDT = true;
} else {
newDDT = false;
}
if (isDerivedDepthTest() != newDDT) {
NodeHelper.markDirty(this, DirtyBits.NODE_DEPTH_TEST);
setDerivedDepthTest(newDDT);
}
}
// This is the derived depthTest value to pass to PG level
private boolean derivedDepthTest = true;
void setDerivedDepthTest(boolean value) {
derivedDepthTest = value;
}
boolean isDerivedDepthTest() {
return derivedDepthTest;
}
public final void setDisable(boolean value) {
disableProperty().set(value);
}
public final boolean isDisable() {
return (miscProperties == null) ? DEFAULT_DISABLE
: miscProperties.isDisable();
}
Defines the individual disabled state of this Node
. Setting disable
to true will cause this Node
and any subnodes to become disabled. This property should be used only to set the disabled state of a Node
. For querying the disabled state of a Node
, the disabled
property should instead be used, since it is possible that a Node
was disabled as a result of an ancestor being disabled even if the individual disable
state on this Node
is false
. Returns: the disabled state for this Node
@defaultValue false
/**
* Defines the individual disabled state of this {@code Node}. Setting
* {@code disable} to true will cause this {@code Node} and any subnodes to
* become disabled. This property should be used only to set the disabled
* state of a {@code Node}. For querying the disabled state of a
* {@code Node}, the {@link #disabledProperty disabled} property should instead be used,
* since it is possible that a {@code Node} was disabled as a result of an
* ancestor being disabled even if the individual {@code disable} state on
* this {@code Node} is {@code false}.
*
* @return the disabled state for this {@code Node}
* @defaultValue false
*/
public final BooleanProperty disableProperty() {
return getMiscProperties().disableProperty();
}
// /**
// * TODO document - null by default, could be non-null in subclasses (e.g. Control)
// */
// public final ObjectProperty<InputMap<?>> inputMapProperty() {
// if (inputMap == null) {
// inputMap = new SimpleObjectProperty<InputMap<?>>(this, "inputMap") {
// private InputMap<?> currentMap = get();
// @Override protected void invalidated() {
// if (currentMap != null) {
// currentMap.dispose();
// }
// currentMap = get();
// }
// };
// }
// return inputMap;
// }
// public final void setInputMap(InputMap<?> value) { inputMapProperty().set(value); }
// public final InputMap<?> getInputMap() { return inputMapProperty().getValue(); }
// private ObjectProperty<InputMap<?>> inputMap;
/**************************************************************************
* *
*
* *
*************************************************************************/
Defines how the picking computation is done for this node when triggered by a MouseEvent
or a contains
function call. If pickOnBounds
is true
, then picking is computed by intersecting with the bounds of this node, else picking is computed by intersecting with the geometric shape of this node. The default value of this property is false
unless overridden by a subclass. The default value is true
for Region
. @defaultValue false; true for Region
/**
* Defines how the picking computation is done for this node when
* triggered by a {@code MouseEvent} or a {@code contains} function call.
*
* If {@code pickOnBounds} is {@code true}, then picking is computed by
* intersecting with the bounds of this node, else picking is computed
* by intersecting with the geometric shape of this node.
*
* The default value of this property is {@code false} unless
* overridden by a subclass. The default value is {@code true}
* for {@link javafx.scene.layout.Region}.
*
* @defaultValue false; true for {@code Region}
*/
private BooleanProperty pickOnBounds;
public final void setPickOnBounds(boolean value) {
pickOnBoundsProperty().set(value);
}
public final boolean isPickOnBounds() {
return pickOnBounds == null ? false : pickOnBounds.get();
}
public final BooleanProperty pickOnBoundsProperty() {
if (pickOnBounds == null) {
pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");
}
return pickOnBounds;
}
Indicates whether or not this Node
is disabled. A Node
will become disabled if disable
is set to true
on either itself or one of its ancestors in the scene graph. A disabled Node
should render itself differently to indicate its disabled state to the user. Such disabled rendering is dependent on the implementation of the Node
. The shape classes contained in javafx.scene.shape
do not implement such rendering by default, therefore applications using shapes for handling input must implement appropriate disabled rendering themselves. The user-interface controls defined in javafx.scene.control
will implement disabled-sensitive rendering, however.
A disabled Node
does not receive mouse or key events.
@defaultValue false
/**
* Indicates whether or not this {@code Node} is disabled. A {@code Node}
* will become disabled if {@link #disableProperty disable} is set to {@code true} on either
* itself or one of its ancestors in the scene graph.
* <p>
* A disabled {@code Node} should render itself differently to indicate its
* disabled state to the user.
* Such disabled rendering is dependent on the implementation of the
* {@code Node}. The shape classes contained in {@code javafx.scene.shape}
* do not implement such rendering by default, therefore applications using
* shapes for handling input must implement appropriate disabled rendering
* themselves. The user-interface controls defined in
* {@code javafx.scene.control} will implement disabled-sensitive rendering,
* however.
* <p>
* A disabled {@code Node} does not receive mouse or key events.
*
* @defaultValue false
*/
private ReadOnlyBooleanWrapper disabled;
protected final void setDisabled(boolean value) {
disabledPropertyImpl().set(value);
}
public final boolean isDisabled() {
return disabled == null ? false : disabled.get();
}
public final ReadOnlyBooleanProperty disabledProperty() {
return disabledPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper disabledPropertyImpl() {
if (disabled == null) {
disabled = new ReadOnlyBooleanWrapper() {
@Override
protected void invalidated() {
pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, get());
updateCanReceiveFocus();
focusSetDirty(getScene());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "disabled";
}
};
}
return disabled;
}
private void updateDisabled() {
boolean isDisabled = isDisable();
if (!isDisabled) {
isDisabled = getParent() != null ? getParent().isDisabled() :
getSubScene() != null && getSubScene().isDisabled();
}
setDisabled(isDisabled);
if (this instanceof SubScene) {
((SubScene)this).getRoot().setDisabled(isDisabled);
}
}
Finds this Node
, or the first sub-node, based on the given CSS selector. If this node is a Parent
, then this function will traverse down into the branch until it finds a match. If more than one sub-node matches the specified selector, this function returns the first of them.
For example, if a Node is given the id of "myId", then the lookup method can
be used to find this node as follows: scene.lookup("#myId");
.
Params: - selector – The css selector of the node to find
Returns: The first node, starting from this Node
, which matches the CSS selector
, null if none is found.
/**
* Finds this {@code Node}, or the first sub-node, based on the given CSS selector.
* If this node is a {@code Parent}, then this function will traverse down
* into the branch until it finds a match. If more than one sub-node matches the
* specified selector, this function returns the first of them.
* <p>
* For example, if a Node is given the id of "myId", then the lookup method can
* be used to find this node as follows: <code>scene.lookup("#myId");</code>.
* </p>
*
* @param selector The css selector of the node to find
* @return The first node, starting from this {@code Node}, which matches
* the CSS {@code selector}, null if none is found.
*/
public Node lookup(String selector) {
if (selector == null) return null;
Selector s = Selector.createSelector(selector);
return s != null && s.applies(this) ? this : null;
}
Finds all Node
s, including this one and any children, which match the given CSS selector. If no matches are found, an empty unmodifiable set is returned. The set is explicitly unordered. Params: - selector – The css selector of the nodes to find
Returns: All nodes, starting from and including this Node
, which match the CSS selector
. The returned set is always unordered and unmodifiable, and never null.
/**
* Finds all {@code Node}s, including this one and any children, which match
* the given CSS selector. If no matches are found, an empty unmodifiable set is
* returned. The set is explicitly unordered.
*
* @param selector The css selector of the nodes to find
* @return All nodes, starting from and including this {@code Node}, which match
* the CSS {@code selector}. The returned set is always unordered and
* unmodifiable, and never null.
*/
public Set<Node> lookupAll(String selector) {
final Selector s = Selector.createSelector(selector);
final Set<Node> empty = Collections.emptySet();
if (s == null) return empty;
List<Node> results = lookupAll(s, null);
return results == null ? empty : new UnmodifiableListSet<Node>(results);
}
Used by Node and Parent for traversing the tree and adding all nodes which
match the given selector.
Params: - selector – The Selector. This will never be null.
- results – The results. This will never be null.
/**
* Used by Node and Parent for traversing the tree and adding all nodes which
* match the given selector.
*
* @param selector The Selector. This will never be null.
* @param results The results. This will never be null.
*/
List<Node> lookupAll(Selector selector, List<Node> results) {
if (selector.applies(this)) {
// Lazily create the set to reduce some trash.
if (results == null) {
results = new LinkedList<Node>();
}
results.add(this);
}
return results;
}
Moves this Node
to the back of its sibling nodes in terms of z-order. This is accomplished by moving this Node
to the first position in its parent's content
ObservableList. This function has no effect if this Node
is not part of a group. /**
* Moves this {@code Node} to the back of its sibling nodes in terms of
* z-order. This is accomplished by moving this {@code Node} to the
* first position in its parent's {@code content} ObservableList.
* This function has no effect if this {@code Node} is not part of a group.
*/
public void toBack() {
if (getParent() != null) {
getParent().toBack(this);
}
}
Moves this Node
to the front of its sibling nodes in terms of z-order. This is accomplished by moving this Node
to the last position in its parent's content
ObservableList. This function has no effect if this Node
is not part of a group. /**
* Moves this {@code Node} to the front of its sibling nodes in terms of
* z-order. This is accomplished by moving this {@code Node} to the
* last position in its parent's {@code content} ObservableList.
* This function has no effect if this {@code Node} is not part of a group.
*/
public void toFront() {
if (getParent() != null) {
getParent().toFront(this);
}
}
// TODO: need to verify whether this is OK to do starting from a node in
// the scene graph other than the root.
private void doCSSPass() {
if (this.cssFlag != CssFlags.CLEAN) {
// The dirty bit isn't checked but we must ensure it is cleared.
// The cssFlag is set to clean in either Node.processCSS or
// NodeHelper.processCSS
// Don't clear the dirty bit in case it will cause problems
// with a full CSS pass on the scene.
// TODO: is this the right thing to do?
// this.clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
this.processCSS();
}
}
Recursive function for synchronizing a node and all descendents
/**
* Recursive function for synchronizing a node and all descendents
*/
private static void syncAll(Node node) {
node.syncPeer();
if (node instanceof Parent) {
Parent p = (Parent) node;
final int childrenCount = p.getChildren().size();
for (int i = 0; i < childrenCount; i++) {
Node n = p.getChildren().get(i);
if (n != null) {
syncAll(n);
}
}
}
if (node.getClip() != null) {
syncAll(node.getClip());
}
}
private void doLayoutPass() {
if (this instanceof Parent) {
// TODO: As an optimization we only need to layout those dirty
// roots that are descendants of this node
Parent p = (Parent)this;
for (int i = 0; i < 3; i++) {
p.layout();
}
}
}
private void doCSSLayoutSyncForSnapshot() {
doCSSPass();
doLayoutPass();
updateBounds();
Scene.setAllowPGAccess(true);
syncAll(this);
Scene.setAllowPGAccess(false);
}
private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) {
if (getScene() != null) {
getScene().doCSSLayoutSyncForSnapshot(this);
} else {
doCSSLayoutSyncForSnapshot();
}
BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
if (params.getTransform() != null) {
Affine3D tempTx = new Affine3D();
TransformHelper.apply(params.getTransform(), tempTx);
transform = tempTx;
}
double x;
double y;
double w;
double h;
Rectangle2D viewport = params.getViewport();
if (viewport != null) {
// Use the specified viewport
x = viewport.getMinX();
y = viewport.getMinY();
w = viewport.getWidth();
h = viewport.getHeight();
} else {
// Get the bounds in parent of this node, transformed by the
// specified transform.
BaseBounds tempBounds = TempState.getInstance().bounds;
tempBounds = getTransformedBounds(tempBounds, transform);
x = tempBounds.getMinX();
y = tempBounds.getMinY();
w = tempBounds.getWidth();
h = tempBounds.getHeight();
}
WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h,
this, transform, params.isDepthBufferInternal(),
params.getFill(), params.getEffectiveCamera(), img);
return result;
}
Takes a snapshot of this node and returns the rendered image when it is ready. CSS and layout processing will be done for the node, and any of its children, prior to rendering it. The entire destination image is cleared to the fill Paint
specified by the SnapshotParameters. This node is then rendered to the image. If the viewport specified by the SnapshotParameters is null, the upper-left pixel of the boundsInParent
of this node, after first applying the transform specified by the SnapshotParameters, is mapped to the upper-left pixel (0,0) in the image. If a non-null viewport is specified, the upper-left pixel of the viewport is mapped to upper-left pixel (0,0) in the image. In both cases, this mapping to (0,0) of the image is done with an integer translation. The portion of the node that is outside of the rendered image will be clipped by the image.
When taking a snapshot of a scene that is being animated, either
explicitly by the application or implicitly (such as chart animation),
the snapshot will be rendered based on the state of the scene graph at
the moment the snapshot is taken and will not reflect any subsequent
animation changes.
NOTE: In order for CSS and layout to function correctly, the node
must be part of a Scene (the Scene may be attached to a Stage, but need
not be).
Params: - params – the snapshot parameters containing attributes that
will control the rendering. If the SnapshotParameters object is null,
then the Scene's attributes will be used if this node is part of a scene,
or default attributes will be used if this node is not part of a scene.
- image – the writable image that will be used to hold the rendered node.
It may be null in which case a new WritableImage will be constructed.
The new image is constructed using integer width and
height values that are derived either from the transformed bounds of this
Node or from the size of the viewport as specified in the
SnapShotParameters. These integer values are chosen such that the image
will wholly contain the bounds of this Node or the specified viewport.
If the image is non-null, the node will be rendered into the
existing image.
In this case, the width and height of the image determine the area
that is rendered instead of the width and height of the bounds or
viewport.
Throws: - IllegalStateException – if this method is called on a thread
other than the JavaFX Application Thread.
Returns: the rendered image Since: JavaFX 2.2
/**
* Takes a snapshot of this node and returns the rendered image when
* it is ready.
* CSS and layout processing will be done for the node, and any of its
* children, prior to rendering it.
* The entire destination image is cleared to the fill {@code Paint}
* specified by the SnapshotParameters. This node is then rendered to
* the image.
* If the viewport specified by the SnapshotParameters is null, the
* upper-left pixel of the {@code boundsInParent} of this
* node, after first applying the transform specified by the
* SnapshotParameters,
* is mapped to the upper-left pixel (0,0) in the image.
* If a non-null viewport is specified,
* the upper-left pixel of the viewport is mapped to upper-left pixel
* (0,0) in the image.
* In both cases, this mapping to (0,0) of the image is done with an integer
* translation. The portion of the node that is outside of the rendered
* image will be clipped by the image.
*
* <p>
* When taking a snapshot of a scene that is being animated, either
* explicitly by the application or implicitly (such as chart animation),
* the snapshot will be rendered based on the state of the scene graph at
* the moment the snapshot is taken and will not reflect any subsequent
* animation changes.
* </p>
*
* <p>
* NOTE: In order for CSS and layout to function correctly, the node
* must be part of a Scene (the Scene may be attached to a Stage, but need
* not be).
* </p>
*
* @param params the snapshot parameters containing attributes that
* will control the rendering. If the SnapshotParameters object is null,
* then the Scene's attributes will be used if this node is part of a scene,
* or default attributes will be used if this node is not part of a scene.
*
* @param image the writable image that will be used to hold the rendered node.
* It may be null in which case a new WritableImage will be constructed.
* The new image is constructed using integer width and
* height values that are derived either from the transformed bounds of this
* Node or from the size of the viewport as specified in the
* SnapShotParameters. These integer values are chosen such that the image
* will wholly contain the bounds of this Node or the specified viewport.
* If the image is non-null, the node will be rendered into the
* existing image.
* In this case, the width and height of the image determine the area
* that is rendered instead of the width and height of the bounds or
* viewport.
*
* @throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
*
* @return the rendered image
* @since JavaFX 2.2
*/
public WritableImage snapshot(SnapshotParameters params, WritableImage image) {
Toolkit.getToolkit().checkFxUserThread();
if (params == null) {
params = new SnapshotParameters();
Scene s = getScene();
if (s != null) {
params.setCamera(s.getEffectiveCamera());
params.setDepthBuffer(s.isDepthBufferInternal());
params.setFill(s.getFill());
}
}
return doSnapshot(params, image);
}
Takes a snapshot of this node at the next frame and calls the specified callback method when the image is ready. CSS and layout processing will be done for the node, and any of its children, prior to rendering it. The entire destination image is cleared to the fill Paint
specified by the SnapshotParameters. This node is then rendered to the image. If the viewport specified by the SnapshotParameters is null, the upper-left pixel of the boundsInParent
of this node, after first applying the transform specified by the SnapshotParameters, is mapped to the upper-left pixel (0,0) in the image. If a non-null viewport is specified, the upper-left pixel of the viewport is mapped to upper-left pixel (0,0) in the image. In both cases, this mapping to (0,0) of the image is done with an integer translation. The portion of the node that is outside of the rendered image will be clipped by the image.
This is an asynchronous call, which means that other
events or animation might be processed before the node is rendered.
If any such events modify the node, or any of its children, that
modification will be reflected in the rendered image (just like it
will also be reflected in the frame rendered to the Stage, if this node
is part of a live scene graph).
When taking a snapshot of a node that is being animated, either
explicitly by the application or implicitly (such as chart animation),
the snapshot will be rendered based on the state of the scene graph at
the moment the snapshot is taken and will not reflect any subsequent
animation changes.
NOTE: In order for CSS and layout to function correctly, the node
must be part of a Scene (the Scene may be attached to a Stage, but need
not be).
Params: - callback – a class whose call method will be called when the image
is ready. The SnapshotResult that is passed into the call method of
the callback will contain the rendered image, the source node
that was rendered, and a copy of the SnapshotParameters.
The callback parameter must not be null.
- params – the snapshot parameters containing attributes that
will control the rendering. If the SnapshotParameters object is null,
then the Scene's attributes will be used if this node is part of a scene,
or default attributes will be used if this node is not part of a scene.
- image – the writable image that will be used to hold the rendered node.
It may be null in which case a new WritableImage will be constructed.
The new image is constructed using integer width and
height values that are derived either from the transformed bounds of this
Node or from the size of the viewport as specified in the
SnapShotParameters. These integer values are chosen such that the image
will wholly contain the bounds of this Node or the specified viewport.
If the image is non-null, the node will be rendered into the
existing image.
In this case, the width and height of the image determine the area
that is rendered instead of the width and height of the bounds or
viewport.
Throws: - IllegalStateException – if this method is called on a thread
other than the JavaFX Application Thread.
- NullPointerException – if the callback parameter is null.
Since: JavaFX 2.2
/**
* Takes a snapshot of this node at the next frame and calls the
* specified callback method when the image is ready.
* CSS and layout processing will be done for the node, and any of its
* children, prior to rendering it.
* The entire destination image is cleared to the fill {@code Paint}
* specified by the SnapshotParameters. This node is then rendered to
* the image.
* If the viewport specified by the SnapshotParameters is null, the
* upper-left pixel of the {@code boundsInParent} of this
* node, after first applying the transform specified by the
* SnapshotParameters,
* is mapped to the upper-left pixel (0,0) in the image.
* If a non-null viewport is specified,
* the upper-left pixel of the viewport is mapped to upper-left pixel
* (0,0) in the image.
* In both cases, this mapping to (0,0) of the image is done with an integer
* translation. The portion of the node that is outside of the rendered
* image will be clipped by the image.
*
* <p>
* This is an asynchronous call, which means that other
* events or animation might be processed before the node is rendered.
* If any such events modify the node, or any of its children, that
* modification will be reflected in the rendered image (just like it
* will also be reflected in the frame rendered to the Stage, if this node
* is part of a live scene graph).
* </p>
*
* <p>
* When taking a snapshot of a node that is being animated, either
* explicitly by the application or implicitly (such as chart animation),
* the snapshot will be rendered based on the state of the scene graph at
* the moment the snapshot is taken and will not reflect any subsequent
* animation changes.
* </p>
*
* <p>
* NOTE: In order for CSS and layout to function correctly, the node
* must be part of a Scene (the Scene may be attached to a Stage, but need
* not be).
* </p>
*
* @param callback a class whose call method will be called when the image
* is ready. The SnapshotResult that is passed into the call method of
* the callback will contain the rendered image, the source node
* that was rendered, and a copy of the SnapshotParameters.
* The callback parameter must not be null.
*
* @param params the snapshot parameters containing attributes that
* will control the rendering. If the SnapshotParameters object is null,
* then the Scene's attributes will be used if this node is part of a scene,
* or default attributes will be used if this node is not part of a scene.
*
* @param image the writable image that will be used to hold the rendered node.
* It may be null in which case a new WritableImage will be constructed.
* The new image is constructed using integer width and
* height values that are derived either from the transformed bounds of this
* Node or from the size of the viewport as specified in the
* SnapShotParameters. These integer values are chosen such that the image
* will wholly contain the bounds of this Node or the specified viewport.
* If the image is non-null, the node will be rendered into the
* existing image.
* In this case, the width and height of the image determine the area
* that is rendered instead of the width and height of the bounds or
* viewport.
*
* @throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
*
* @throws NullPointerException if the callback parameter is null.
* @since JavaFX 2.2
*/
public void snapshot(Callback<SnapshotResult, Void> callback,
SnapshotParameters params, WritableImage image) {
Toolkit.getToolkit().checkFxUserThread();
if (callback == null) {
throw new NullPointerException("The callback must not be null");
}
if (params == null) {
params = new SnapshotParameters();
Scene s = getScene();
if (s != null) {
params.setCamera(s.getEffectiveCamera());
params.setDepthBuffer(s.isDepthBufferInternal());
params.setFill(s.getFill());
}
} else {
params = params.copy();
}
final SnapshotParameters theParams = params;
final Callback<SnapshotResult, Void> theCallback = callback;
final WritableImage theImage = image;
// Create a deferred runnable that will be run from a pulse listener
// that is called after all of the scenes have been synced but before
// any of them have been rendered.
final Runnable snapshotRunnable = () -> {
WritableImage img = doSnapshot(theParams, theImage);
SnapshotResult result = new SnapshotResult(img, Node.this, theParams);
// System.err.println("Calling snapshot callback");
try {
Void v = theCallback.call(result);
} catch (Throwable th) {
System.err.println("Exception in snapshot callback");
th.printStackTrace(System.err);
}
};
// System.err.println("Schedule a snapshot in the future");
Scene.addSnapshotRunnable(snapshotRunnable);
}
/* ************************************************************************
* *
*
* *
*************************************************************************/
public final void setOnDragEntered(
EventHandler<? super DragEvent> value) {
onDragEnteredProperty().set(value);
}
public final EventHandler<? super DragEvent> getOnDragEntered() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragEntered();
}
Defines a function to be called when drag gesture enters this Node
. Returns: the event handler that is called when drag gesture enters this Node
/**
* Defines a function to be called when drag gesture
* enters this {@code Node}.
* @return the event handler that is called when drag gesture enters this
* {@code Node}
*/
public final ObjectProperty<EventHandler<? super DragEvent>>
onDragEnteredProperty() {
return getEventHandlerProperties().onDragEnteredProperty();
}
public final void setOnDragExited(
EventHandler<? super DragEvent> value) {
onDragExitedProperty().set(value);
}
public final EventHandler<? super DragEvent> getOnDragExited() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragExited();
}
Defines a function to be called when drag gesture exits this Node
. Returns: the event handler that is called when drag gesture exits this Node
/**
* Defines a function to be called when drag gesture
* exits this {@code Node}.
* @return the event handler that is called when drag gesture exits this
* {@code Node}
*/
public final ObjectProperty<EventHandler<? super DragEvent>>
onDragExitedProperty() {
return getEventHandlerProperties().onDragExitedProperty();
}
public final void setOnDragOver(
EventHandler<? super DragEvent> value) {
onDragOverProperty().set(value);
}
public final EventHandler<? super DragEvent> getOnDragOver() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragOver();
}
Defines a function to be called when drag gesture progresses within this Node
. Returns: the event handler that is called when drag gesture progresses within this Node
/**
* Defines a function to be called when drag gesture progresses within
* this {@code Node}.
* @return the event handler that is called when drag gesture progresses
* within this {@code Node}
*/
public final ObjectProperty<EventHandler<? super DragEvent>>
onDragOverProperty() {
return getEventHandlerProperties().onDragOverProperty();
}
// Do we want DRAG_TRANSFER_MODE_CHANGED event?
// public final void setOnDragTransferModeChanged(
// EventHandler<? super DragEvent> value) {
// onDragTransferModeChangedProperty().set(value);
// }
//
// public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() {
// return (eventHandlerProperties == null)
// ? null : eventHandlerProperties.getOnDragTransferModeChanged();
// }
//
// /**
// * Defines a function to be called this {@code Node} if it is a potential
// * drag-and-drop target when the user takes action to change the intended
// * {@code TransferMode}.
// * The user can change the intended {@link TransferMode} by holding down
// * or releasing key modifiers.
// */
// public final ObjectProperty<EventHandler<? super DragEvent>>
// onDragTransferModeChangedProperty() {
// return getEventHandlerProperties().onDragTransferModeChangedProperty();
// }
public final void setOnDragDropped(
EventHandler<? super DragEvent> value) {
onDragDroppedProperty().set(value);
}
public final EventHandler<? super DragEvent> getOnDragDropped() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragDropped();
}
Defines a function to be called when the mouse button is released on this Node
during drag and drop gesture. Transfer of data from the DragEvent
's dragboard
should happen in this function. Returns: the event handler that is called when the mouse button is released on this Node
/**
* Defines a function to be called when the mouse button is released
* on this {@code Node} during drag and drop gesture. Transfer of data from
* the {@link DragEvent}'s {@link DragEvent#getDragboard() dragboard} should
* happen in this function.
* @return the event handler that is called when the mouse button is
* released on this {@code Node}
*/
public final ObjectProperty<EventHandler<? super DragEvent>>
onDragDroppedProperty() {
return getEventHandlerProperties().onDragDroppedProperty();
}
public final void setOnDragDone(
EventHandler<? super DragEvent> value) {
onDragDoneProperty().set(value);
}
public final EventHandler<? super DragEvent> getOnDragDone() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragDone();
}
Defines a function to be called when this Node
is a drag and drop gesture source after its data has been dropped on a drop target. The transferMode
of the event shows what just happened at the drop target. If transferMode
has the value MOVE
, then the source can clear out its data. Clearing the source's data gives the appropriate appearance to a user that the data has been moved by the drag and drop gesture. A transferMode
that has the value NONE
indicates that no data was transferred during the drag and drop gesture. Returns: the event handler that is called when this Node
is a drag and drop gesture source after its data has been dropped on a drop target
/**
* Defines a function to be called when this {@code Node} is a
* drag and drop gesture source after its data has
* been dropped on a drop target. The {@code transferMode} of the
* event shows what just happened at the drop target.
* If {@code transferMode} has the value {@code MOVE}, then the source can
* clear out its data. Clearing the source's data gives the appropriate
* appearance to a user that the data has been moved by the drag and drop
* gesture. A {@code transferMode} that has the value {@code NONE}
* indicates that no data was transferred during the drag and drop gesture.
* @return the event handler that is called when this {@code Node} is a drag
* and drop gesture source after its data has been dropped on a drop target
*/
public final ObjectProperty<EventHandler<? super DragEvent>>
onDragDoneProperty() {
return getEventHandlerProperties().onDragDoneProperty();
}
Confirms a potential drag and drop gesture that is recognized over this Node
. Can be called only from a DRAG_DETECTED event handler. The returned Dragboard
is used to transfer data during the drag and drop gesture. Placing this Node
's data on the Dragboard
also identifies this Node
as the source of the drag and drop gesture. More detail about drag and drop gestures is described in the overivew of DragEvent
. Params: - transferModes – The supported
TransferMode
(s) of this Node
Throws: - IllegalStateException – if drag and drop cannot be started at this moment (it's called outside of
DRAG_DETECTED
event handling or this node is not in scene).
See Also: Returns: A Dragboard
to place this Node
's data on
/**
* Confirms a potential drag and drop gesture that is recognized over this
* {@code Node}.
* Can be called only from a DRAG_DETECTED event handler. The returned
* {@link Dragboard} is used to transfer data during
* the drag and drop gesture. Placing this {@code Node}'s data on the
* {@link Dragboard} also identifies this {@code Node} as the source of
* the drag and drop gesture.
* More detail about drag and drop gestures is described in the overivew
* of {@link DragEvent}.
*
* @see DragEvent
* @param transferModes The supported {@code TransferMode}(s) of this {@code Node}
* @return A {@code Dragboard} to place this {@code Node}'s data on
* @throws IllegalStateException if drag and drop cannot be started at this
* moment (it's called outside of {@code DRAG_DETECTED} event handling or
* this node is not in scene).
*/
public Dragboard startDragAndDrop(TransferMode... transferModes) {
if (getScene() != null) {
return getScene().startDragAndDrop(this, transferModes);
}
throw new IllegalStateException("Cannot start drag and drop on node "
+ "that is not in scene");
}
Starts a full press-drag-release gesture with this node as gesture source. This method can be called only from a DRAG_DETECTED
mouse event handler. More detail about dragging gestures can be found in the overview of MouseEvent
and MouseDragEvent
. Throws: - IllegalStateException – if the full press-drag-release gesture cannot be started at this moment (it's called outside of
DRAG_DETECTED
event handling or this node is not in scene).
See Also: Since: JavaFX 2.1
/**
* Starts a full press-drag-release gesture with this node as gesture
* source. This method can be called only from a {@code DRAG_DETECTED} mouse
* event handler. More detail about dragging gestures can be found
* in the overview of {@link MouseEvent} and {@link MouseDragEvent}.
*
* @see MouseEvent
* @see MouseDragEvent
* @throws IllegalStateException if the full press-drag-release gesture
* cannot be started at this moment (it's called outside of
* {@code DRAG_DETECTED} event handling or this node is not in scene).
* @since JavaFX 2.1
*/
public void startFullDrag() {
if (getScene() != null) {
getScene().startFullDrag(this);
return;
}
throw new IllegalStateException("Cannot start full drag on node "
+ "that is not in scene");
}
////////////////////////////
// Private Implementation
////////////////////////////
If this Node is being used as the clip of another Node, that other node
is referred to as the clipParent. If the boundsInParent of this Node
changes, it must update the clipParent's bounds as well.
/**
* If this Node is being used as the clip of another Node, that other node
* is referred to as the clipParent. If the boundsInParent of this Node
* changes, it must update the clipParent's bounds as well.
*/
private Node clipParent;
// Use a getter function instead of giving clipParent package access,
// so that clipParent doesn't get turned into a Location.
final Node getClipParent() {
return clipParent;
}
Determines whether this node is connected anywhere in the scene graph.
/**
* Determines whether this node is connected anywhere in the scene graph.
*/
boolean isConnected() {
// don't need to check scene, because if scene is non-null
// parent must also be non-null
return getParent() != null || clipParent != null;
}
Tests whether creating a parent-child relationship between these
nodes would cause a cycle. The parent relationship includes not only
the "real" parent (child of Group) but also the clipParent.
/**
* Tests whether creating a parent-child relationship between these
* nodes would cause a cycle. The parent relationship includes not only
* the "real" parent (child of Group) but also the clipParent.
*/
boolean wouldCreateCycle(Node parent, Node child) {
if (child != null && child.getClip() == null && (!(child instanceof Parent))) {
return false;
}
Node n = parent;
while (n != child) {
if (n.getParent() != null) {
n = n.getParent();
} else if (n.getSubScene() != null) {
n = n.getSubScene();
} else if (n.clipParent != null) {
n = n.clipParent;
} else {
return false;
}
}
return true;
}
The peer node created by the graphics Toolkit/Pipeline implementation
/**
* The peer node created by the graphics Toolkit/Pipeline implementation
*/
private NGNode peer;
@SuppressWarnings("CallToPrintStackTrace")
<P extends NGNode> P getPeer() {
if (Utils.assertionEnabled()) {
// Assertion checking code
if (getScene() != null && !Scene.isPGAccessAllowed()) {
java.lang.System.err.println();
java.lang.System.err.println("*** unexpected PG access");
java.lang.Thread.dumpStack();
}
}
if (peer == null) {
//if (PerformanceTracker.isLoggingEnabled()) {
// PerformanceTracker.logEvent("Creating NGNode for [{this}, id=\"{id}\"]");
//}
peer = NodeHelper.createPeer(this);
//if (PerformanceTracker.isLoggingEnabled()) {
// PerformanceTracker.logEvent("NGNode created");
//}
}
return (P) peer;
}
/***************************************************************************
* *
* Initialization *
* *
* To Note limit the number of bounds computations and improve startup *
* performance. *
* *
**************************************************************************/
Creates a new instance of Node.
/**
* Creates a new instance of Node.
*/
protected Node() {
//if (PerformanceTracker.isLoggingEnabled()) {
// PerformanceTracker.logEvent("Node.init for [{this}, id=\"{id}\"]");
//}
setDirty();
updateTreeVisible(false);
//if (PerformanceTracker.isLoggingEnabled()) {
// PerformanceTracker.logEvent("Node.postinit " +
// "for [{this}, id=\"{id}\"] finished");
//}
}
/***************************************************************************
* *
* Layout related APIs. *
* *
**************************************************************************/
Defines whether or not this node's layout will be managed by it's parent. If the node is managed, it's parent will factor the node's geometry into its own preferred size and layoutBounds
calculations and will lay it out during the scene's layout pass. If a managed node's layoutBounds changes, it will automatically trigger relayout up the scene-graph to the nearest layout root (which is typically the scene's root node). If the node is unmanaged, its parent will ignore the child in both preferred size computations and layout. Changes in layoutBounds will not trigger relayout above it. If an unmanaged node is of type Parent
, it will act as a "layout root", meaning that calls to Parent.requestLayout()
beneath it will cause only the branch rooted by the node to be relayed out, thereby isolating layout changes to that root and below. It's the application's responsibility to set the size and position of an unmanaged node.
By default all nodes are managed.
See Also:
/**
* Defines whether or not this node's layout will be managed by it's parent.
* If the node is managed, it's parent will factor the node's geometry
* into its own preferred size and {@link #layoutBoundsProperty layoutBounds}
* calculations and will lay it
* out during the scene's layout pass. If a managed node's layoutBounds
* changes, it will automatically trigger relayout up the scene-graph
* to the nearest layout root (which is typically the scene's root node).
* <p>
* If the node is unmanaged, its parent will ignore the child in both preferred
* size computations and layout. Changes in layoutBounds will not trigger
* relayout above it. If an unmanaged node is of type {@link javafx.scene.Parent Parent},
* it will act as a "layout root", meaning that calls to {@link Parent#requestLayout()}
* beneath it will cause only the branch rooted by the node to be relayed out,
* thereby isolating layout changes to that root and below. It's the application's
* responsibility to set the size and position of an unmanaged node.
* <p>
* By default all nodes are managed.
* </p>
*
* @see #isResizable()
* @see #layoutBoundsProperty()
* @see Parent#requestLayout()
*
*/
private BooleanProperty managed;
public final void setManaged(boolean value) {
managedProperty().set(value);
}
public final boolean isManaged() {
return managed == null ? true : managed.get();
}
public final BooleanProperty managedProperty() {
if (managed == null) {
managed = new BooleanPropertyBase(true) {
@Override
protected void invalidated() {
final Parent parent = getParent();
if (parent != null) {
parent.managedChildChanged();
}
notifyManagedChanged();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "managed";
}
};
}
return managed;
}
Called whenever the "managed" flag has changed. This is only
used by Parent as an optimization to keep track of whether a
Parent node is a layout root or not.
/**
* Called whenever the "managed" flag has changed. This is only
* used by Parent as an optimization to keep track of whether a
* Parent node is a layout root or not.
*/
void notifyManagedChanged() { }
Defines the x coordinate of the translation that is added to this Node
's transform for the purpose of layout. The value should be computed as the offset required to adjust the position of the node from its current layoutBounds minX
position (which might not be 0) to the desired location. For example, if textnode
should be positioned at finalX
textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
Failure to subtract layoutBounds minX
may result in misplacement of the node. The relocate(x, y)
method will automatically do the correct computation and should generally be used over setting layoutX directly.
The node's final translation will be computed as layoutX
+ translateX
, where layoutX
establishes the node's stable position and translateX
optionally makes dynamic adjustments to that position.
If the node is managed and has a Region
as its parent, then the layout region will set layoutX
according to its own layout policy. If the node is unmanaged or parented by a Group
, then the application may set layoutX
directly to position it.
See Also:
/**
* Defines the x coordinate of the translation that is added to this {@code Node}'s
* transform for the purpose of layout. The value should be computed as the
* offset required to adjust the position of the node from its current
* {@link #layoutBoundsProperty() layoutBounds minX} position (which might not be 0) to the desired location.
*
* <p>For example, if {@code textnode} should be positioned at {@code finalX}
* <pre>{@code
* textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
* }</pre>
* <p>
* Failure to subtract {@code layoutBounds minX} may result in misplacement
* of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
* correct computation and should generally be used over setting layoutX directly.
* <p>
* The node's final translation will be computed as {@code layoutX} + {@link #translateXProperty translateX},
* where {@code layoutX} establishes the node's stable position
* and {@code translateX} optionally makes dynamic adjustments to that
* position.
* <p>
* If the node is managed and has a {@link javafx.scene.layout.Region}
* as its parent, then the layout region will set {@code layoutX} according to its
* own layout policy. If the node is unmanaged or parented by a {@link Group},
* then the application may set {@code layoutX} directly to position it.
*
* @see #relocate(double, double)
* @see #layoutBoundsProperty()
*
*/
private DoubleProperty layoutX;
public final void setLayoutX(double value) {
layoutXProperty().set(value);
}
public final double getLayoutX() {
return layoutX == null ? 0.0 : layoutX.get();
}
public final DoubleProperty layoutXProperty() {
if (layoutX == null) {
layoutX = new DoublePropertyBase(0.0) {
@Override
protected void invalidated() {
NodeHelper.transformsChanged(Node.this);
final Parent p = getParent();
// Propagate layout if this change isn't triggered by its parent
if (p != null && !p.isCurrentLayoutChild(Node.this)) {
if (isManaged()) {
// Force its parent to fix the layout since it is a managed child.
p.requestLayout(true);
} else {
// Parent size changed, parent's parent might need to re-layout
p.clearSizeCache();
p.requestParentLayout();
}
}
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "layoutX";
}
};
}
return layoutX;
}
Defines the y coordinate of the translation that is added to this Node
's transform for the purpose of layout. The value should be computed as the offset required to adjust the position of the node from its current layoutBounds minY
position (which might not be 0) to the desired location. For example, if textnode
should be positioned at finalY
textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
Failure to subtract layoutBounds minY
may result in misplacement of the node. The relocate(x, y)
method will automatically do the correct computation and should generally be used over setting layoutY directly.
The node's final translation will be computed as layoutY
+ translateY
, where layoutY
establishes the node's stable position and translateY
optionally makes dynamic adjustments to that position.
If the node is managed and has a Region
as its parent, then the region will set layoutY
according to its own layout policy. If the node is unmanaged or parented by a Group
, then the application may set layoutY
directly to position it.
See Also:
/**
* Defines the y coordinate of the translation that is added to this {@code Node}'s
* transform for the purpose of layout. The value should be computed as the
* offset required to adjust the position of the node from its current
* {@link #layoutBoundsProperty() layoutBounds minY} position (which might not be 0) to the desired location.
*
* <p>For example, if {@code textnode} should be positioned at {@code finalY}
* <pre>{@code
* textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
* }</pre>
* <p>
* Failure to subtract {@code layoutBounds minY} may result in misplacement
* of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
* correct computation and should generally be used over setting layoutY directly.
* <p>
* The node's final translation will be computed as {@code layoutY} + {@link #translateYProperty translateY},
* where {@code layoutY} establishes the node's stable position
* and {@code translateY} optionally makes dynamic adjustments to that
* position.
* <p>
* If the node is managed and has a {@link javafx.scene.layout.Region}
* as its parent, then the region will set {@code layoutY} according to its
* own layout policy. If the node is unmanaged or parented by a {@link Group},
* then the application may set {@code layoutY} directly to position it.
*
* @see #relocate(double, double)
* @see #layoutBoundsProperty()
*/
private DoubleProperty layoutY;
public final void setLayoutY(double value) {
layoutYProperty().set(value);
}
public final double getLayoutY() {
return layoutY == null ? 0.0 : layoutY.get();
}
public final DoubleProperty layoutYProperty() {
if (layoutY == null) {
layoutY = new DoublePropertyBase(0.0) {
@Override
protected void invalidated() {
NodeHelper.transformsChanged(Node.this);
final Parent p = getParent();
// Propagate layout if this change isn't triggered by its parent
if (p != null && !p.isCurrentLayoutChild(Node.this)) {
if (isManaged()) {
// Force its parent to fix the layout since it is a managed child.
p.requestLayout(true);
} else {
// Parent size changed, parent's parent might need to re-layout
p.clearSizeCache();
p.requestParentLayout();
}
}
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "layoutY";
}
};
}
return layoutY;
}
Sets the node's layoutX and layoutY translation properties in order to
relocate this node to the x,y location in the parent.
This method does not alter translateX or translateY, which if also set
will be added to layoutX and layoutY, adjusting the final location by
corresponding amounts.
Params: - x – the target x coordinate location
- y – the target y coordinate location
/**
* Sets the node's layoutX and layoutY translation properties in order to
* relocate this node to the x,y location in the parent.
* <p>
* This method does not alter translateX or translateY, which if also set
* will be added to layoutX and layoutY, adjusting the final location by
* corresponding amounts.
*
* @param x the target x coordinate location
* @param y the target y coordinate location
*/
public void relocate(double x, double y) {
setLayoutX(x - getLayoutBounds().getMinX());
setLayoutY(y - getLayoutBounds().getMinY());
PlatformLogger logger = Logging.getLayoutLogger();
if (logger.isLoggable(Level.FINER)) {
logger.finer(this.toString()+" moved to ("+x+","+y+")");
}
}
Indicates whether this node is a type which can be resized by its parent.
If this method returns true, then the parent will resize the node (ideally
within its size range) by calling node.resize(width,height) during the
layout pass. All Regions, Controls, and WebView are resizable classes
which depend on their parents resizing them during layout once all sizing
and CSS styling information has been applied.
If this method returns false, then the parent cannot resize it during
layout (resize() is a no-op) and it should return its layoutBounds for
minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not
resizable and hence depend on the application to establish their sizing
by setting appropriate properties (e.g. width/height for Rectangle,
text on Text, and so on). Non-resizable nodes may still be relocated
during layout.
See Also: Returns: whether or not this node type can be resized by its parent during layout
/**
* Indicates whether this node is a type which can be resized by its parent.
* If this method returns true, then the parent will resize the node (ideally
* within its size range) by calling node.resize(width,height) during the
* layout pass. All Regions, Controls, and WebView are resizable classes
* which depend on their parents resizing them during layout once all sizing
* and CSS styling information has been applied.
* <p>
* If this method returns false, then the parent cannot resize it during
* layout (resize() is a no-op) and it should return its layoutBounds for
* minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not
* resizable and hence depend on the application to establish their sizing
* by setting appropriate properties (e.g. width/height for Rectangle,
* text on Text, and so on). Non-resizable nodes may still be relocated
* during layout.
*
* @see #getContentBias()
* @see #minWidth(double)
* @see #minHeight(double)
* @see #prefWidth(double)
* @see #prefHeight(double)
* @see #maxWidth(double)
* @see #maxHeight(double)
* @see #resize(double, double)
* @see #getLayoutBounds()
*
* @return whether or not this node type can be resized by its parent during layout
*/
public boolean isResizable() {
return false;
}
Returns the orientation of a node's resizing bias for layout purposes.
If the node type has no bias, returns null. If the node is resizable and
it's height depends on its width, returns HORIZONTAL, else if its width
depends on its height, returns VERTICAL.
Resizable subclasses should override this method to return an
appropriate value.
See Also: Returns: orientation of width/height dependency or null if there is none
/**
* Returns the orientation of a node's resizing bias for layout purposes.
* If the node type has no bias, returns null. If the node is resizable and
* it's height depends on its width, returns HORIZONTAL, else if its width
* depends on its height, returns VERTICAL.
* <p>
* Resizable subclasses should override this method to return an
* appropriate value.
*
* @see #isResizable()
* @see #minWidth(double)
* @see #minHeight(double)
* @see #prefWidth(double)
* @see #prefHeight(double)
* @see #maxWidth(double)
* @see #maxHeight(double)
*
* @return orientation of width/height dependency or null if there is none
*/
public Orientation getContentBias() {
return null;
}
Returns the node's minimum width for use in layout calculations.
If the node is resizable, its parent should not resize its width any
smaller than this value. If the node is not resizable, returns its
layoutBounds width.
Layout code which calls this method should first check the content-bias
of the node. If the node has a vertical content-bias, then callers
should pass in a height value that the minimum width should be based on.
If the node has either a horizontal or null content-bias, then the caller
should pass in -1.
Node subclasses with a vertical content-bias should honor the height
parameter whether -1 or a positive value. All other subclasses may ignore
the height parameter (which will likely be -1).
If Node's maxWidth(double)
is lower than this number, minWidth
takes precedence. This means the Node should never be resized below minWidth
.
Params: - height – the height that should be used if minimum width depends on it
See Also: Returns: the minimum width that the node should be resized to during layout.
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's minimum width for use in layout calculations.
* If the node is resizable, its parent should not resize its width any
* smaller than this value. If the node is not resizable, returns its
* layoutBounds width.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a vertical content-bias, then callers
* should pass in a height value that the minimum width should be based on.
* If the node has either a horizontal or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a vertical content-bias should honor the height
* parameter whether -1 or a positive value. All other subclasses may ignore
* the height parameter (which will likely be -1).
* <p>
* If Node's {@link #maxWidth(double)} is lower than this number,
* {@code minWidth} takes precedence. This means the Node should never be resized below {@code minWidth}.
*
* @see #isResizable()
* @see #getContentBias()
*
* @param height the height that should be used if minimum width depends on it
* @return the minimum width that the node should be resized to during layout.
* The result will never be NaN, nor will it ever be negative.
*/
public double minWidth(double height) {
return prefWidth(height);
}
Returns the node's minimum height for use in layout calculations.
If the node is resizable, its parent should not resize its height any
smaller than this value. If the node is not resizable, returns its
layoutBounds height.
Layout code which calls this method should first check the content-bias
of the node. If the node has a horizontal content-bias, then callers
should pass in a width value that the minimum height should be based on.
If the node has either a vertical or null content-bias, then the caller
should pass in -1.
Node subclasses with a horizontal content-bias should honor the width
parameter whether -1 or a positive value. All other subclasses may ignore
the width parameter (which will likely be -1).
If Node's maxHeight(double)
is lower than this number, minHeight
takes precedence. This means the Node should never be resized below minHeight
.
Params: - width – the width that should be used if minimum height depends on it
See Also: Returns: the minimum height that the node should be resized to during layout
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's minimum height for use in layout calculations.
* If the node is resizable, its parent should not resize its height any
* smaller than this value. If the node is not resizable, returns its
* layoutBounds height.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a horizontal content-bias, then callers
* should pass in a width value that the minimum height should be based on.
* If the node has either a vertical or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a horizontal content-bias should honor the width
* parameter whether -1 or a positive value. All other subclasses may ignore
* the width parameter (which will likely be -1).
* <p>
* If Node's {@link #maxHeight(double)} is lower than this number,
* {@code minHeight} takes precedence. This means the Node should never be resized below {@code minHeight}.
*
* @see #isResizable()
* @see #getContentBias()
*
* @param width the width that should be used if minimum height depends on it
* @return the minimum height that the node should be resized to during layout
* The result will never be NaN, nor will it ever be negative.
*/
public double minHeight(double width) {
return prefHeight(width);
}
Returns the node's preferred width for use in layout calculations.
If the node is resizable, its parent should treat this value as the
node's ideal width within its range. If the node is not resizable,
just returns its layoutBounds width, which should be treated as the rigid
width of the node.
Layout code which calls this method should first check the content-bias
of the node. If the node has a vertical content-bias, then callers
should pass in a height value that the preferred width should be based on.
If the node has either a horizontal or null content-bias, then the caller
should pass in -1.
Node subclasses with a vertical content-bias should honor the height
parameter whether -1 or a positive value. All other subclasses may ignore
the height parameter (which will likely be -1).
Params: - height – the height that should be used if preferred width depends on it
See Also: - isResizable()
- getContentBias()
- autosize()
Returns: the preferred width that the node should be resized to during layout
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's preferred width for use in layout calculations.
* If the node is resizable, its parent should treat this value as the
* node's ideal width within its range. If the node is not resizable,
* just returns its layoutBounds width, which should be treated as the rigid
* width of the node.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a vertical content-bias, then callers
* should pass in a height value that the preferred width should be based on.
* If the node has either a horizontal or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a vertical content-bias should honor the height
* parameter whether -1 or a positive value. All other subclasses may ignore
* the height parameter (which will likely be -1).
*
* @see #isResizable()
* @see #getContentBias()
* @see #autosize()
*
* @param height the height that should be used if preferred width depends on it
* @return the preferred width that the node should be resized to during layout
* The result will never be NaN, nor will it ever be negative.
*/
public double prefWidth(double height) {
final double result = getLayoutBounds().getWidth();
return Double.isNaN(result) || result < 0 ? 0 : result;
}
Returns the node's preferred height for use in layout calculations.
If the node is resizable, its parent should treat this value as the
node's ideal height within its range. If the node is not resizable,
just returns its layoutBounds height, which should be treated as the rigid
height of the node.
Layout code which calls this method should first check the content-bias
of the node. If the node has a horizontal content-bias, then callers
should pass in a width value that the preferred height should be based on.
If the node has either a vertical or null content-bias, then the caller
should pass in -1.
Node subclasses with a horizontal content-bias should honor the height
parameter whether -1 or a positive value. All other subclasses may ignore
the height parameter (which will likely be -1).
Params: - width – the width that should be used if preferred height depends on it
See Also: - getContentBias()
- autosize()
Returns: the preferred height that the node should be resized to during layout
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's preferred height for use in layout calculations.
* If the node is resizable, its parent should treat this value as the
* node's ideal height within its range. If the node is not resizable,
* just returns its layoutBounds height, which should be treated as the rigid
* height of the node.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a horizontal content-bias, then callers
* should pass in a width value that the preferred height should be based on.
* If the node has either a vertical or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a horizontal content-bias should honor the height
* parameter whether -1 or a positive value. All other subclasses may ignore
* the height parameter (which will likely be -1).
*
* @see #getContentBias()
* @see #autosize()
*
* @param width the width that should be used if preferred height depends on it
* @return the preferred height that the node should be resized to during layout
* The result will never be NaN, nor will it ever be negative.
*/
public double prefHeight(double width) {
final double result = getLayoutBounds().getHeight();
return Double.isNaN(result) || result < 0 ? 0 : result;
}
Returns the node's maximum width for use in layout calculations.
If the node is resizable, its parent should not resize its width any
larger than this value. A value of Double.MAX_VALUE indicates the
parent may expand the node's width beyond its preferred without limits.
If the node is not resizable, returns its layoutBounds width.
Layout code which calls this method should first check the content-bias
of the node. If the node has a vertical content-bias, then callers
should pass in a height value that the maximum width should be based on.
If the node has either a horizontal or null content-bias, then the caller
should pass in -1.
Node subclasses with a vertical content-bias should honor the height
parameter whether -1 or a positive value. All other subclasses may ignore
the height parameter (which will likely be -1).
If Node's minWidth(double)
is greater, it should take precedence over the maxWidth
. This means the Node should never be resized below minWidth
.
Params: - height – the height that should be used if maximum width depends on it
See Also: Returns: the maximum width that the node should be resized to during layout
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's maximum width for use in layout calculations.
* If the node is resizable, its parent should not resize its width any
* larger than this value. A value of Double.MAX_VALUE indicates the
* parent may expand the node's width beyond its preferred without limits.
* <p>
* If the node is not resizable, returns its layoutBounds width.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a vertical content-bias, then callers
* should pass in a height value that the maximum width should be based on.
* If the node has either a horizontal or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a vertical content-bias should honor the height
* parameter whether -1 or a positive value. All other subclasses may ignore
* the height parameter (which will likely be -1).
* <p>
* If Node's {@link #minWidth(double)} is greater, it should take precedence
* over the {@code maxWidth}. This means the Node should never be resized below {@code minWidth}.
*
* @see #isResizable()
* @see #getContentBias()
*
* @param height the height that should be used if maximum width depends on it
* @return the maximum width that the node should be resized to during layout
* The result will never be NaN, nor will it ever be negative.
*/
public double maxWidth(double height) {
return prefWidth(height);
}
Returns the node's maximum height for use in layout calculations.
If the node is resizable, its parent should not resize its height any
larger than this value. A value of Double.MAX_VALUE indicates the
parent may expand the node's height beyond its preferred without limits.
If the node is not resizable, returns its layoutBounds height.
Layout code which calls this method should first check the content-bias
of the node. If the node has a horizontal content-bias, then callers
should pass in a width value that the maximum height should be based on.
If the node has either a vertical or null content-bias, then the caller
should pass in -1.
Node subclasses with a horizontal content-bias should honor the width
parameter whether -1 or a positive value. All other subclasses may ignore
the width parameter (which will likely be -1).
If Node's minHeight(double)
is greater, it should take precedence over the maxHeight
. This means the Node should never be resized below minHeight
.
Params: - width – the width that should be used if maximum height depends on it
See Also: Returns: the maximum height that the node should be resized to during layout
The result will never be NaN, nor will it ever be negative.
/**
* Returns the node's maximum height for use in layout calculations.
* If the node is resizable, its parent should not resize its height any
* larger than this value. A value of Double.MAX_VALUE indicates the
* parent may expand the node's height beyond its preferred without limits.
* <p>
* If the node is not resizable, returns its layoutBounds height.
* <p>
* Layout code which calls this method should first check the content-bias
* of the node. If the node has a horizontal content-bias, then callers
* should pass in a width value that the maximum height should be based on.
* If the node has either a vertical or null content-bias, then the caller
* should pass in -1.
* <p>
* Node subclasses with a horizontal content-bias should honor the width
* parameter whether -1 or a positive value. All other subclasses may ignore
* the width parameter (which will likely be -1).
* <p>
* If Node's {@link #minHeight(double)} is greater, it should take precedence
* over the {@code maxHeight}. This means the Node should never be resized below {@code minHeight}.
*
* @see #isResizable()
* @see #getContentBias()
*
* @param width the width that should be used if maximum height depends on it
* @return the maximum height that the node should be resized to during layout
* The result will never be NaN, nor will it ever be negative.
*/
public double maxHeight(double width) {
return prefHeight(width);
}
If the node is resizable, will set its layout bounds to the specified
width and height. If the node is not resizable, this method is a no-op.
This method should generally only be called by parent nodes from their
layoutChildren() methods. All Parent classes will automatically resize
resizable children, so resizing done directly by the application will be
overridden by the node's parent, unless the child is unmanaged.
Parents are responsible for ensuring the width and height values fall
within the resizable node's preferred range. The autosize() method may
be used if the parent just needs to resize the node to its preferred size.
Params: - width – the target layout bounds width
- height – the target layout bounds height
See Also:
/**
* If the node is resizable, will set its layout bounds to the specified
* width and height. If the node is not resizable, this method is a no-op.
* <p>
* This method should generally only be called by parent nodes from their
* layoutChildren() methods. All Parent classes will automatically resize
* resizable children, so resizing done directly by the application will be
* overridden by the node's parent, unless the child is unmanaged.
* <p>
* Parents are responsible for ensuring the width and height values fall
* within the resizable node's preferred range. The autosize() method may
* be used if the parent just needs to resize the node to its preferred size.
*
* @see #isResizable()
* @see #getContentBias()
* @see #autosize()
* @see #minWidth(double)
* @see #minHeight(double)
* @see #prefWidth(double)
* @see #prefHeight(double)
* @see #maxWidth(double)
* @see #maxHeight(double)
* @see #getLayoutBounds()
*
* @param width the target layout bounds width
* @param height the target layout bounds height
*/
public void resize(double width, double height) {
}
If the node is resizable, will set its layout bounds to its current preferred
width and height. If the node is not resizable, this method is a no-op.
This method automatically queries the node's content-bias and if it's
horizontal, will pass in the node's preferred width to get the preferred
height; if vertical, will pass in the node's preferred height to get the width,
and if null, will compute the preferred width/height independently.
See Also: - isResizable()
- getContentBias()
/**
* If the node is resizable, will set its layout bounds to its current preferred
* width and height. If the node is not resizable, this method is a no-op.
* <p>
* This method automatically queries the node's content-bias and if it's
* horizontal, will pass in the node's preferred width to get the preferred
* height; if vertical, will pass in the node's preferred height to get the width,
* and if null, will compute the preferred width/height independently.
* </p>
*
* @see #isResizable()
* @see #getContentBias()
*
*/
public final void autosize() {
if (isResizable()) {
Orientation contentBias = getContentBias();
double w, h;
if (contentBias == null) {
w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
} else if (contentBias == Orientation.HORIZONTAL) {
w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
h = boundedSize(prefHeight(w), minHeight(w), maxHeight(w));
} else { // bias == VERTICAL
h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
w = boundedSize(prefWidth(h), minWidth(h), maxWidth(h));
}
resize(w,h);
}
}
double boundedSize(double value, double min, double max) {
// if max < value, return max
// if min > value, return min
// if min > max, return min
return Math.min(Math.max(value, min), Math.max(min,max));
}
If the node is resizable, will set its layout bounds to the specified
width and height. If the node is not resizable, the resize step is skipped.
Once the node has been resized (if resizable) then sets the node's layoutX
and layoutY translation properties in order to relocate it to x,y in the
parent's coordinate space.
This method should generally only be called by parent nodes from their
layoutChildren() methods. All Parent classes will automatically resize
resizable children, so resizing done directly by the application will be
overridden by the node's parent, unless the child is unmanaged.
Parents are responsible for ensuring the width and height values fall
within the resizable node's preferred range. The autosize() and relocate()
methods may be used if the parent just needs to resize the node to its
preferred size and reposition it.
Params: - x – the target x coordinate location
- y – the target y coordinate location
- width – the target layout bounds width
- height – the target layout bounds height
See Also:
/**
* If the node is resizable, will set its layout bounds to the specified
* width and height. If the node is not resizable, the resize step is skipped.
* <p>
* Once the node has been resized (if resizable) then sets the node's layoutX
* and layoutY translation properties in order to relocate it to x,y in the
* parent's coordinate space.
* <p>
* This method should generally only be called by parent nodes from their
* layoutChildren() methods. All Parent classes will automatically resize
* resizable children, so resizing done directly by the application will be
* overridden by the node's parent, unless the child is unmanaged.
* <p>
* Parents are responsible for ensuring the width and height values fall
* within the resizable node's preferred range. The autosize() and relocate()
* methods may be used if the parent just needs to resize the node to its
* preferred size and reposition it.
*
* @see #isResizable()
* @see #getContentBias()
* @see #autosize()
* @see #minWidth(double)
* @see #minHeight(double)
* @see #prefWidth(double)
* @see #prefHeight(double)
* @see #maxWidth(double)
* @see #maxHeight(double)
*
* @param x the target x coordinate location
* @param y the target y coordinate location
* @param width the target layout bounds width
* @param height the target layout bounds height
*
*/
public void resizeRelocate(double x, double y, double width, double height) {
resize(width, height);
relocate(x,y);
}
This is a special value that might be returned by getBaselineOffset()
. This means that the Parent (layout Pane) of this Node should use the height of this Node as a baseline. /**
* This is a special value that might be returned by {@link #getBaselineOffset()}.
* This means that the Parent (layout Pane) of this Node should use the height of this Node as a baseline.
*/
public static final double BASELINE_OFFSET_SAME_AS_HEIGHT = Double.NEGATIVE_INFINITY;
The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location that should be used when this node is being vertically aligned by baseline with other nodes. By default this returns BASELINE_OFFSET_SAME_AS_HEIGHT
for resizable Nodes and layoutBounds height for non-resizable. Subclasses which contain text should override this method to return their actual text baseline offset. Returns: offset of text baseline from layoutBounds.minY for non-resizable Nodes or BASELINE_OFFSET_SAME_AS_HEIGHT
otherwise
/**
* The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location
* that should be used when this node is being vertically aligned by baseline with
* other nodes. By default this returns {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} for resizable Nodes
* and layoutBounds height for non-resizable. Subclasses
* which contain text should override this method to return their actual text baseline offset.
*
* @return offset of text baseline from layoutBounds.minY for non-resizable Nodes or {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} otherwise
*/
public double getBaselineOffset() {
if (isResizable()) {
return BASELINE_OFFSET_SAME_AS_HEIGHT;
} else {
return getLayoutBounds().getHeight();
}
}
Returns the area of this Node
projected onto the physical screen in pixel units. Returns: the area of this Node
projected onto the physical screen Since: JavaFX 8.0
/**
* Returns the area of this {@code Node} projected onto the
* physical screen in pixel units.
* @return the area of this {@code Node} projected onto the physical screen
* @since JavaFX 8.0
*/
public double computeAreaInScreen() {
return doComputeAreaInScreen();
}
/*
* Help application or utility to implement LOD support by returning the
* projected area of a Node in pixel unit. The projected area is not clipped.
*
* For perspective camera, this method first exams node's bounds against
* camera's clipping plane to cut off those out of viewing frustrum. After
* computing areaInScreen, it applies a tight viewing frustrum check using
* canonical view volume.
*
* The result of areaInScreen comes from the product of
* (projViewTx x localToSceneTransform x localBounds).
*
* Returns 0 for those fall outside viewing frustrum.
*/
private double doComputeAreaInScreen() {
Scene tmpScene = getScene();
if (tmpScene != null) {
Bounds bounds = getBoundsInLocal();
Camera camera = tmpScene.getEffectiveCamera();
boolean isPerspective = camera instanceof PerspectiveCamera ? true : false;
Transform localToSceneTx = getLocalToSceneTransform();
Affine3D tempTx = TempState.getInstance().tempTx;
BaseBounds localBounds = new BoxBounds((float) bounds.getMinX(),
(float) bounds.getMinY(),
(float) bounds.getMinZ(),
(float) bounds.getMaxX(),
(float) bounds.getMaxY(),
(float) bounds.getMaxZ());
// NOTE: Viewing frustrum check on camera's clipping plane is now only
// for perspective camera.
// TODO: Need to hook up parallel camera's nearClip and farClip.
if (isPerspective) {
Transform cameraL2STx = camera.getLocalToSceneTransform();
// If camera transform only contains translate, compare in scene
// coordinate. Otherwise, compare in camera coordinate.
if (cameraL2STx.getMxx() == 1.0
&& cameraL2STx.getMxy() == 0.0
&& cameraL2STx.getMxz() == 0.0
&& cameraL2STx.getMyx() == 0.0
&& cameraL2STx.getMyy() == 1.0
&& cameraL2STx.getMyz() == 0.0
&& cameraL2STx.getMzx() == 0.0
&& cameraL2STx.getMzy() == 0.0
&& cameraL2STx.getMzz() == 1.0) {
double minZ, maxZ;
// If node transform only contains translate, only convert
// minZ and maxZ to scene coordinate. Otherwise, convert
// node bounds to scene coordinate.
if (localToSceneTx.getMxx() == 1.0
&& localToSceneTx.getMxy() == 0.0
&& localToSceneTx.getMxz() == 0.0
&& localToSceneTx.getMyx() == 0.0
&& localToSceneTx.getMyy() == 1.0
&& localToSceneTx.getMyz() == 0.0
&& localToSceneTx.getMzx() == 0.0
&& localToSceneTx.getMzy() == 0.0
&& localToSceneTx.getMzz() == 1.0) {
Vec3d tempV3D = TempState.getInstance().vec3d;
tempV3D.set(0, 0, bounds.getMinZ());
localToScene(tempV3D);
minZ = tempV3D.z;
tempV3D.set(0, 0, bounds.getMaxZ());
localToScene(tempV3D);
maxZ = tempV3D.z;
} else {
Bounds nodeInSceneBounds = localToScene(bounds);
minZ = nodeInSceneBounds.getMinZ();
maxZ = nodeInSceneBounds.getMaxZ();
}
if (minZ > camera.getFarClipInScene()
|| maxZ < camera.getNearClipInScene()) {
return 0;
}
} else {
BaseBounds nodeInCameraBounds = new BoxBounds();
// We need to set tempTx to identity since it is a recycled transform.
// This is because TransformHelper.apply() is a matrix concatenation operation.
tempTx.setToIdentity();
TransformHelper.apply(localToSceneTx, tempTx);
// Convert node from local coordinate to camera coordinate
tempTx.preConcatenate(camera.getSceneToLocalTransform());
tempTx.transform(localBounds, nodeInCameraBounds);
// Compare in camera coordinate
if (nodeInCameraBounds.getMinZ() > camera.getFarClip()
|| nodeInCameraBounds.getMaxZ() < camera.getNearClip()) {
return 0;
}
}
}
GeneralTransform3D projViewTx = TempState.getInstance().projViewTx;
projViewTx.set(camera.getProjViewTransform());
// We need to set tempTx to identity since it is a recycled transform.
// This is because TransformHelper.apply() is a matrix concatenation operation.
tempTx.setToIdentity();
TransformHelper.apply(localToSceneTx, tempTx);
// The product of projViewTx * localToSceneTransform
GeneralTransform3D tx = projViewTx.mul(tempTx);
// Transform localBounds to projected bounds
localBounds = tx.transform(localBounds, localBounds);
double area = localBounds.getWidth() * localBounds.getHeight();
// Use canonical view volume to check whether object is outside the
// viewing frustrum
if (isPerspective) {
localBounds.intersectWith(-1, -1, 0, 1, 1, 1);
area = (localBounds.getWidth() < 0 || localBounds.getHeight() < 0) ? 0 : area;
}
return area * (camera.getViewWidth() / 2 * camera.getViewHeight() / 2);
}
return 0;
}
/* *************************************************************************
* *
* Bounds related APIs *
* *
**************************************************************************/
public final Bounds getBoundsInParent() {
return boundsInParentProperty().get();
}
The rectangular bounds of this Node
which include its transforms. boundsInParent
is calculated by taking the local bounds (defined by boundsInLocal
) and applying the transform created by setting the following additional variables
transforms
ObservableList
scaleX
, scaleY
, scaleZ
rotate
layoutX
, layoutY
translateX
, translateY
, translateZ
The resulting bounds will be conceptually in the coordinate space of the Node
's parent, however the node need not have a parent to calculate these bounds.
Note that this method does not take the node's visibility into account; the computation is based on the geometry of this Node
only.
This property will always have a non-null value.
Note that boundsInParent
is automatically recomputed whenever the geometry of a node changes, or when any of the following the change: transforms ObservableList
, any of the translate, layout or scale variables, or the rotate variable. For this reason, it is an error to bind any of these values in a node to an expression that depends upon this variable. For example, the x or y variables of a shape, or translateX
, translateY
should never be bound to boundsInParent
for the purpose of positioning the node.
Returns: the boundsInParent for this Node
/**
* The rectangular bounds of this {@code Node} which include its transforms.
* {@code boundsInParent} is calculated by
* taking the local bounds (defined by {@link #boundsInLocalProperty boundsInLocal}) and applying
* the transform created by setting the following additional variables
* <ol>
* <li>{@link #getTransforms transforms} ObservableList</li>
* <li>{@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}, {@link #scaleZProperty scaleZ}</li>
* <li>{@link #rotateProperty rotate}</li>
* <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li>
* <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY},
* {@link #translateZProperty translateZ}</li>
* </ol>
* <p>
* The resulting bounds will be conceptually in the coordinate space of the
* {@code Node}'s parent, however the node need not have a parent to calculate
* these bounds.
* <p>
* Note that this method does not take the node's visibility into account;
* the computation is based on the geometry of this {@code Node} only.
* <p>
* This property will always have a non-null value.
* <p>
* Note that {@code boundsInParent} is automatically recomputed whenever the
* geometry of a node changes, or when any of the following the change:
* transforms {@code ObservableList}, any of the translate, layout or scale
* variables, or the rotate variable. For this reason, it is an error
* to bind any of these values in a node to an expression that depends upon
* this variable. For example, the x or y variables of a shape, or
* {@code translateX}, {@code translateY} should never be bound to
* {@code boundsInParent} for the purpose of positioning the node.
* @return the boundsInParent for this {@code Node}
*/
public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
return getMiscProperties().boundsInParentProperty();
}
private void invalidateBoundsInParent() {
if (miscProperties != null) {
miscProperties.invalidateBoundsInParent();
}
}
public final Bounds getBoundsInLocal() {
return boundsInLocalProperty().get();
}
The rectangular bounds of this Node
in the node's untransformed local coordinate space. For nodes that extend Shape
, the local bounds will also include space required for a non-zero stroke that may fall outside the shape's geometry that is defined by position and size attributes. The local bounds will also include any clipping set with clip
as well as effects set with effect
. Note that this method does not take the node's visibility into account; the computation is based on the geometry of this Node
only.
This property will always have a non-null value.
Note that boundsInLocal is automatically recomputed whenever the
geometry of a node changes. For this reason, it is an error to bind any
of these values in a node to an expression that depends upon this variable.
For example, the x or y variables of a shape should never be bound
to boundsInLocal for the purpose of positioning the node.
Returns: the boundsInLocal for this Node
/**
* The rectangular bounds of this {@code Node} in the node's
* untransformed local coordinate space. For nodes that extend
* {@link javafx.scene.shape.Shape}, the local bounds will also include
* space required for a non-zero stroke that may fall outside the shape's
* geometry that is defined by position and size attributes.
* The local bounds will also include any clipping set with {@link #clipProperty clip}
* as well as effects set with {@link #effectProperty effect}.
*
* <p>
* Note that this method does not take the node's visibility into account;
* the computation is based on the geometry of this {@code Node} only.
* <p>
* This property will always have a non-null value.
* <p>
* Note that boundsInLocal is automatically recomputed whenever the
* geometry of a node changes. For this reason, it is an error to bind any
* of these values in a node to an expression that depends upon this variable.
* For example, the x or y variables of a shape should never be bound
* to boundsInLocal for the purpose of positioning the node.
* @return the boundsInLocal for this {@code Node}
*/
public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
return getMiscProperties().boundsInLocalProperty();
}
private void invalidateBoundsInLocal() {
if (miscProperties != null) {
miscProperties.invalidateBoundsInLocal();
}
}
The rectangular bounds that should be used for layout calculations for this node. layoutBounds
may differ from the visual bounds of the node and is computed differently depending on the node type. If the node type is resizable (Region
, Control
, or WebView
) then the layoutBounds will always be 0,0 width x height
. If the node type is not resizable (Shape
, Text
, or Group
), then the layoutBounds
are computed based on the node's geometric properties and does not include the node's clip, effect, or transforms. See individual class documentation for details.
Note that the layoutX
, layoutY
, translateX
, and translateY
variables are not included in the layoutBounds. This is important because layout code must first determine the current size and location of the node (using layoutBounds
) and then set layoutX
and layoutY
to adjust the translation of the node so that it will have the desired layout position.
Because the computation of layoutBounds is often tied to a node's geometric variables, it is an error to bind any such variables to an expression that depends upon layoutBounds
. For example, the x or y variables of a shape should never be bound to layoutBounds
for the purpose of positioning the node.
Note that for 3D shapes, the layout bounds is actually a rectangular box
with X, Y, and Z values, although only X and Y are used in layout calculations.
The layoutBounds
will never be null.
/**
* The rectangular bounds that should be used for layout calculations for
* this node. {@code layoutBounds} may differ from the visual bounds
* of the node and is computed differently depending on the node type.
* <p>
* If the node type is resizable ({@link javafx.scene.layout.Region Region},
* {@link javafx.scene.control.Control Control}, or {@link javafx.scene.web.WebView WebView})
* then the layoutBounds will always be {@code 0,0 width x height}.
* If the node type is not resizable ({@link javafx.scene.shape.Shape Shape},
* {@link javafx.scene.text.Text Text}, or {@link Group}), then the {@code layoutBounds}
* are computed based on the node's geometric properties and does not include the
* node's clip, effect, or transforms. See individual class documentation
* for details.
* <p>
* Note that the {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY},
* {@link #translateXProperty translateX}, and {@link #translateYProperty translateY}
* variables are not included in the layoutBounds.
* This is important because layout code must first determine the current
* size and location of the node (using {@code layoutBounds}) and then set
* {@code layoutX} and {@code layoutY} to adjust the translation of the
* node so that it will have the desired layout position.
* <p>
* Because the computation of layoutBounds is often tied to a node's
* geometric variables, it is an error to bind any such variables to an
* expression that depends upon {@code layoutBounds}. For example, the
* x or y variables of a shape should never be bound to {@code layoutBounds}
* for the purpose of positioning the node.
* <p>
* Note that for 3D shapes, the layout bounds is actually a rectangular box
* with X, Y, and Z values, although only X and Y are used in layout calculations.
* <p>
* The {@code layoutBounds} will never be null.
*
*/
private LazyBoundsProperty layoutBounds = new LazyBoundsProperty() {
@Override
protected Bounds computeBounds() {
return NodeHelper.computeLayoutBounds(Node.this);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "layoutBounds";
}
};
public final Bounds getLayoutBounds() {
return layoutBoundsProperty().get();
}
public final ReadOnlyObjectProperty<Bounds> layoutBoundsProperty() {
return layoutBounds;
}
/*
* Bounds And Transforms Computation
*
* This section of the code is responsible for computing and caching
* various bounds and transforms. For optimal performance and minimal
* recomputation of bounds (which can be quite expensive), we cache
* values on two different levels. We expose two public immutable
* Bounds boundsInParent objects and boundsInLocal. Because they are
* immutable and because they may change quite frequently (especially
* in the case of a Parent whose children are animated), it is
* important that the system does not rely on these variables, because
* doing so would produce a large amount of garbage. Rather, these
* variables are provided solely for the convenience of application
* developers and, being lazily bound, should generally be created at
* most once per frame.
*
* The second level of caching are within local Bounds2D variables.
* These variables, txBounds and geomBounds, are mutable and as such
* can be cached and updated as frequently as necessary without creating
* excessive garbage. However, since the computation of bounds is still
* expensive, it is desirable to cache both the geometric bounds and
* the "complete" transformed bounds (essentially, boundsInParent).
* Cached txBounds is particularly useful when computing the geometric
* bounds of a Parent since it would not require complete or partial
* recomputation of each child.
*
* Finally, we cache the complete transform for this node which converts
* its coord system from local to parent coords. This is useful both for
* minimizing bounds recomputations in the case of the geometry having
* changed but the transform not having changed, and also because the tx
* is required for several different computations (for example, it must
* be computed once during state synchronization with the PG peer, and
* must also be computed when the pivot point changes, and also when
* deriving the txBounds of the Node).
*
* As with any caching system, a subtle and non-trivial amount of code
* is devoted to invalidating the bounds / transforms at appropriate
* times and in appropriate places to make sure bounds / transforms
* are recomputed at all necessary times.
*
* There are three computeXXX functions. One is for computing the
* boundsInParent, the second for computing boundsInLocal, and the
* third for computing the default layout bounds (which, by default,
* is based on the geometric bounds). These functions are all prefixed
* with "compute" because they create and return new immutable
* Bounds objects.
*
* There are three getXXXBounds functions. One is for returning the
* complete transformed bounds. The second is for returning the
* local bounds. The last is for returning the geometric bounds. These
* functions are all prefixed with "get" because they may well return
* a cached value, or may actually compute the bounds if necessary. These
* functions all have the same signature. They take a Bounds2D and
* BaseTransform, and return a Bounds2D (the same as they took). These
* functions essentially populate the supplied bounds2D with the
* appropriate bounds information, leveraging cached bounds if possible.
*
* There is a single NodeHelper.computeGeomBoundsImpl function which is abstract.
* This must be implemented in each subclass, and is responsible for
* computing the actual geometric bounds for the Node. For example, Parent
* is written such that this function is the union of the transformed
* bounds of each child. Rectangle is written such that this takes into
* account the size and stroke. Text is written such that it is computed
* based on the actual glyphs.
*
* There are two updateXXX functions, updateGeomBounds and updateTxBounds.
* These functions are for ensuring that geomBounds and txBounds are
* valid. They only execute in the case of the cached value being invalid,
* so the function call is very cheap in cases where the cached bounds
* values are still valid.
*/
An affine transform that holds the computed local-to-parent transform.
This is the concatenation of all transforms in this node, including all
of the convenience transforms.
/**
* An affine transform that holds the computed local-to-parent transform.
* This is the concatenation of all transforms in this node, including all
* of the convenience transforms.
*/
private BaseTransform localToParentTx = BaseTransform.IDENTITY_TRANSFORM;
This flag is used to indicate that localToParentTx is dirty and needs
to be recomputed.
/**
* This flag is used to indicate that localToParentTx is dirty and needs
* to be recomputed.
*/
private boolean transformDirty = true;
The cached transformed bounds. This is never null, but is frequently set
to be invalid whenever the bounds for the node have changed. These are
"complete" bounds, that is, with transforms and effect and clip applied.
Note that this is equivalent to boundsInParent
/**
* The cached transformed bounds. This is never null, but is frequently set
* to be invalid whenever the bounds for the node have changed. These are
* "complete" bounds, that is, with transforms and effect and clip applied.
* Note that this is equivalent to boundsInParent
*/
private BaseBounds txBounds = new RectBounds();
The cached bounds. This is never null, but is frequently set to be
invalid whenever the bounds for the node have changed. These are the
"content" bounds, that is, without transforms or effects applied.
/**
* The cached bounds. This is never null, but is frequently set to be
* invalid whenever the bounds for the node have changed. These are the
* "content" bounds, that is, without transforms or effects applied.
*/
private BaseBounds geomBounds = new RectBounds();
The cached local bounds (without transforms, with clip and effects).
If there is neither clip nor effect
local bounds are equal to geom bounds, so in this case we don't keep
the extra instance and set null to this variable.
/**
* The cached local bounds (without transforms, with clip and effects).
* If there is neither clip nor effect
* local bounds are equal to geom bounds, so in this case we don't keep
* the extra instance and set null to this variable.
*/
private BaseBounds localBounds = null;
This special flag is used only by Parent to flag whether or not
the *parent* has processed the fact that bounds have changed for this
child Node. We need some way of flagging this on a per-node basis to
enable the significant performance optimizations and fast paths that
are in the Parent code.
To reduce confusion, although this variable is defined on Node, it
really belongs to the Parent of the node and should *only* be modified
by the parent.
/**
* This special flag is used only by Parent to flag whether or not
* the *parent* has processed the fact that bounds have changed for this
* child Node. We need some way of flagging this on a per-node basis to
* enable the significant performance optimizations and fast paths that
* are in the Parent code.
* <p>
* To reduce confusion, although this variable is defined on Node, it
* really belongs to the Parent of the node and should *only* be modified
* by the parent.
*/
boolean boundsChanged;
/*
* Returns geometric bounds, but may be over-ridden by a subclass.
*/
private Bounds doComputeLayoutBounds() {
BaseBounds tempBounds = TempState.getInstance().bounds;
tempBounds = getGeomBounds(tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
return new BoundingBox(tempBounds.getMinX(),
tempBounds.getMinY(),
tempBounds.getMinZ(),
tempBounds.getWidth(),
tempBounds.getHeight(),
tempBounds.getDepth());
}
/*
* Subclasses may customize the layoutBounds by means of overriding the
* NodeHelper.computeLayoutBoundsImpl method. If the layout bounds need to be
* recomputed, the subclass must notify the Node implementation of this
* fact so that appropriate notifications and internal state can be
* kept in sync. Subclasses must call NodeHelper.layoutBoundsChanged to
* let Node know that the layout bounds are invalid and need to be
* recomputed.
*/
final void layoutBoundsChanged() {
if (!layoutBounds.valid) {
return;
}
layoutBounds.invalidate();
if ((nodeTransformation != null && nodeTransformation.hasScaleOrRotate()) || hasMirroring()) {
// if either the scale or rotate convenience variables are used,
// then we need a valid pivot point. Since the layoutBounds
// affects the pivot we need to invalidate the transform
NodeHelper.transformsChanged(this);
}
}
Loads the given bounds object with the transformed bounds relative to,
and based on, the given transform. That is, this is the local bounds
with the local-to-parent transform applied.
We *never* pass null in as a bounds. This method will
NOT take a null bounds object. The returned value may be
the same bounds object passed in, or it may be a new object.
The reason for this object promotion is in the case of needing
to promote from a RectBounds to a BoxBounds (3D).
/**
* Loads the given bounds object with the transformed bounds relative to,
* and based on, the given transform. That is, this is the local bounds
* with the local-to-parent transform applied.
*
* We *never* pass null in as a bounds. This method will
* NOT take a null bounds object. The returned value may be
* the same bounds object passed in, or it may be a new object.
* The reason for this object promotion is in the case of needing
* to promote from a RectBounds to a BoxBounds (3D).
*/
BaseBounds getTransformedBounds(BaseBounds bounds, BaseTransform tx) {
updateLocalToParentTransform();
if (tx.isTranslateOrIdentity()) {
updateTxBounds();
bounds = bounds.deriveWithNewBounds(txBounds);
if (!tx.isIdentity()) {
final double translateX = tx.getMxt();
final double translateY = tx.getMyt();
final double translateZ = tx.getMzt();
bounds = bounds.deriveWithNewBounds(
(float) (bounds.getMinX() + translateX),
(float) (bounds.getMinY() + translateY),
(float) (bounds.getMinZ() + translateZ),
(float) (bounds.getMaxX() + translateX),
(float) (bounds.getMaxY() + translateY),
(float) (bounds.getMaxZ() + translateZ));
}
return bounds;
} else if (localToParentTx.isIdentity()) {
return getLocalBounds(bounds, tx);
} else {
double mxx = tx.getMxx();
double mxy = tx.getMxy();
double mxz = tx.getMxz();
double mxt = tx.getMxt();
double myx = tx.getMyx();
double myy = tx.getMyy();
double myz = tx.getMyz();
double myt = tx.getMyt();
double mzx = tx.getMzx();
double mzy = tx.getMzy();
double mzz = tx.getMzz();
double mzt = tx.getMzt();
BaseTransform boundsTx = tx.deriveWithConcatenation(localToParentTx);
bounds = getLocalBounds(bounds, boundsTx);
if (boundsTx == tx) {
tx.restoreTransform(mxx, mxy, mxz, mxt,
myx, myy, myz, myt,
mzx, mzy, mzz, mzt);
}
return bounds;
}
}
Loads the given bounds object with the local bounds relative to,
and based on, the given transform. That is, these are the geometric
bounds + clip and effect.
We *never* pass null in as a bounds. This method will
NOT take a null bounds object. The returned value may be
the same bounds object passed in, or it may be a new object.
The reason for this object promotion is in the case of needing
to promote from a RectBounds to a BoxBounds (3D).
/**
* Loads the given bounds object with the local bounds relative to,
* and based on, the given transform. That is, these are the geometric
* bounds + clip and effect.
*
* We *never* pass null in as a bounds. This method will
* NOT take a null bounds object. The returned value may be
* the same bounds object passed in, or it may be a new object.
* The reason for this object promotion is in the case of needing
* to promote from a RectBounds to a BoxBounds (3D).
*/
BaseBounds getLocalBounds(BaseBounds bounds, BaseTransform tx) {
if (getEffect() == null && getClip() == null) {
return getGeomBounds(bounds, tx);
}
if (tx.isTranslateOrIdentity()) {
// we can take a fast path since we know tx is either a simple
// translation or is identity
updateLocalBounds();
bounds = bounds.deriveWithNewBounds(localBounds);
if (!tx.isIdentity()) {
double translateX = tx.getMxt();
double translateY = tx.getMyt();
double translateZ = tx.getMzt();
bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
(float) (bounds.getMinY() + translateY),
(float) (bounds.getMinZ() + translateZ),
(float) (bounds.getMaxX() + translateX),
(float) (bounds.getMaxY() + translateY),
(float) (bounds.getMaxZ() + translateZ));
}
return bounds;
} else if (tx.is2D()
&& (tx.getType()
& ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION
| BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) {
// this is a non-uniform scale / non-quadrant rotate / skew transform
return computeLocalBounds(bounds, tx);
} else {
// 3D transformations and
// selected 2D transformations (uniform transform, flip, quadrant rotation).
// These 2D transformation will yield tight bounds when applied on the pre-computed
// geomBounds
// Note: Transforming the local bounds into a 3D space will yield a bounds
// that isn't as tight as transforming its geometry and compute it bounds.
updateLocalBounds();
return tx.transform(localBounds, bounds);
}
}
Loads the given bounds object with the geometric bounds relative to,
and based on, the given transform.
We *never* pass null in as a bounds. This method will
NOT take a null bounds object. The returned value may be
the same bounds object passed in, or it may be a new object.
The reason for this object promotion is in the case of needing
to promote from a RectBounds to a BoxBounds (3D).
/**
* Loads the given bounds object with the geometric bounds relative to,
* and based on, the given transform.
*
* We *never* pass null in as a bounds. This method will
* NOT take a null bounds object. The returned value may be
* the same bounds object passed in, or it may be a new object.
* The reason for this object promotion is in the case of needing
* to promote from a RectBounds to a BoxBounds (3D).
*/
BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx) {
if (tx.isTranslateOrIdentity()) {
// we can take a fast path since we know tx is either a simple
// translation or is identity
updateGeomBounds();
bounds = bounds.deriveWithNewBounds(geomBounds);
if (!tx.isIdentity()) {
double translateX = tx.getMxt();
double translateY = tx.getMyt();
double translateZ = tx.getMzt();
bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
(float) (bounds.getMinY() + translateY),
(float) (bounds.getMinZ() + translateZ),
(float) (bounds.getMaxX() + translateX),
(float) (bounds.getMaxY() + translateY),
(float) (bounds.getMaxZ() + translateZ));
}
return bounds;
} else if (tx.is2D()
&& (tx.getType()
& ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION
| BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) {
// this is a non-uniform scale / non-quadrant rotate / skew transform
return NodeHelper.computeGeomBounds(this, bounds, tx);
} else {
// 3D transformations and
// selected 2D transformations (unifrom transform, flip, quadrant rotation).
// These 2D transformation will yield tight bounds when applied on the pre-computed
// geomBounds
// Note: Transforming the local geomBounds into a 3D space will yield a bounds
// that isn't as tight as transforming its geometry and compute it bounds.
updateGeomBounds();
return tx.transform(geomBounds, bounds);
}
}
If necessary, recomputes the cached geom bounds. If the bounds are not
invalid, then this method is a no-op.
/**
* If necessary, recomputes the cached geom bounds. If the bounds are not
* invalid, then this method is a no-op.
*/
void updateGeomBounds() {
if (geomBoundsInvalid) {
geomBounds = NodeHelper.computeGeomBounds(this, geomBounds, BaseTransform.IDENTITY_TRANSFORM);
geomBoundsInvalid = false;
}
}
Computes the local bounds of this Node.
/**
* Computes the local bounds of this Node.
*/
private BaseBounds computeLocalBounds(BaseBounds bounds, BaseTransform tx) {
// We either get the bounds of the effect (if it isn't null)
// or we get the geom bounds (if effect is null). We will then
// intersect this with the clip.
if (getEffect() != null) {
BaseBounds b = EffectHelper.getBounds(getEffect(), bounds, tx, this, boundsAccessor);
bounds = bounds.deriveWithNewBounds(b);
} else {
bounds = getGeomBounds(bounds, tx);
}
// intersect with the clip. Take care with "bounds" as it may
// actually be TEMP_BOUNDS, so we save off state
if (getClip() != null
// FIXME: All 3D picking is currently ignored by rendering.
// Until this is fixed or defined differently (RT-28510),
// we follow this behavior.
&& !(this instanceof Shape3D) && !(getClip() instanceof Shape3D)) {
double x1 = bounds.getMinX();
double y1 = bounds.getMinY();
double x2 = bounds.getMaxX();
double y2 = bounds.getMaxY();
double z1 = bounds.getMinZ();
double z2 = bounds.getMaxZ();
bounds = getClip().getTransformedBounds(bounds, tx);
bounds.intersectWith((float)x1, (float)y1, (float)z1,
(float)x2, (float)y2, (float)z2);
}
return bounds;
}
If necessary, recomputes the cached local bounds. If the bounds are not
invalid, then this method is a no-op.
/**
* If necessary, recomputes the cached local bounds. If the bounds are not
* invalid, then this method is a no-op.
*/
private void updateLocalBounds() {
if (localBoundsInvalid) {
if (getClip() != null || getEffect() != null) {
localBounds = computeLocalBounds(
localBounds == null ? new RectBounds() : localBounds,
BaseTransform.IDENTITY_TRANSFORM);
} else {
localBounds = null;
}
localBoundsInvalid = false;
}
}
If necessary, recomputes the cached transformed bounds.
If the cached transformed bounds are not invalid, then
this method is a no-op.
/**
* If necessary, recomputes the cached transformed bounds.
* If the cached transformed bounds are not invalid, then
* this method is a no-op.
*/
void updateTxBounds() {
if (txBoundsInvalid) {
updateLocalToParentTransform();
txBounds = getLocalBounds(txBounds, localToParentTx);
txBoundsInvalid = false;
}
}
/*
* Bounds Invalidation And Notification
*
* The goal of this section is to efficiently propagate bounds
* invalidation through the scenegraph while also being semantically
* correct.
*
* The code path for invalidation of layout bounds is somewhat confusing
* primarily due to performance enhancements and the desire to reduce the
* number of requestLayout() calls that are performed when layout bounds
* change. Before diving into layout bounds, I will first describe how
* normal bounds invalidation occurs.
*
* When a node's geometry changes (for example, if the width of a
* Rectangle is changed) then the Node must call NodeHelper.geomChanged().
* Invoking this function will eventually clear all cached bounds and
* notify to each parent up the tree that their bounds may have changed.
*
* After invalidating geomBounds (and after kicking off layout bounds
* notification), NodeHelper.geomChanged calls localBoundsChanged(). It should
* be noted that NodeHelper.geomChanged should only be called when the geometry
* of the node has changed such that it may result in the geom bounds
* actually changing.
*
* localBoundsChanged() simply invalidates boundsInLocal and then calls
* transformedBoundsChanged().
*
* transformedBoundsChanged() is responsible for invalidating
* boundsInParent and txBounds. If the Node is not visible, then there is
* no need to notify the parent of the bounds change because the parent's
* bounds do not include invisible nodes. If the node is visible, then
* it must tell the parent that this child node's bounds have changed.
* It is up to the parent to eventually invoke its own NodeHelper.geomChanged
* function. If instead of a parent this node has a clipParent, then the
* clipParent's localBoundsChanged() is called instead.
*
* There are a few other ways in which we enter the invalidate steps
* beyond just the geometry changes. If the visibility of a Node changes,
* its own bounds are not affected but its parent's bounds are. So a
* special call to parent.childVisibilityChanged is made so the parent
* can react accordingly.
*
* If a transform is changed (layoutX, layoutY, rotate, transforms, etc)
* then the transform must be invalidated. When a transform is invalidated,
* it must also invalidate the txBounds by invoking
* transformedBoundsChanged, which will in turn notify the parent as
* before.
*
* If an effect is changed or replaced then the local bounds must be
* invalidated, as well as the transformedBounds and the parent notified
* of the change in bounds.
*
* layoutBound is somewhat unique in that it can be redefined in
* subclasses. By default, the layoutBounds is the geomBounds, and so
* whenever the geomBounds() function is called the layoutBounds
* must be invalidated. However in subclasses, especially Resizables,
* the layout bounds may not be defined to be the same as the geometric
* bounds. This is both useful and provides a very nice performance
* optimization for regions and controls. In this case, subclasses
* need some way to interpose themselves such that a call to
* NodeHelper.geomChanged() *does not* invalidate the layout bounds.
*
* This interposition happens by providing the
* NodeHelper.notifyLayoutBoundsChanged function. The default implementation
* simply invalidates boundsInLocal. Subclasses (such as Region and
* Control) can override this function so that it does not invalidate
* the layout bounds.
*
* An on invalidate trigger on layoutBounds handles kicking off the rest
* of the invalidate process for layoutBounds. Because the layout bounds
* define the pivot point, if scaleX, scaleY, or rotate contain
* non-identity values then whenever the layoutBounds change the
* transformed bounds also change. Finally, if this node's parent is
* a Region and if the Node is being managed by the Region, then
* we must call requestLayout on the Region whenever the layout bounds
* have changed.
*/
/*
* Invoked by subclasses whenever their geometric bounds have changed.
* Because the default layout bounds is based on the node geometry, this
* function will invoke NodeHelper.notifyLayoutBoundsChanged. The default
* implementation of NodeHelper.notifyLayoutBoundsChanged() will simply invalidate
* layoutBounds. Resizable subclasses will want to override this function
* in most cases to be a no-op.
*
* This function will also invalidate the cached geom bounds, and then
* invoke localBoundsChanged() which will eventually end up invoking a
* chain of functions up the tree to ensure that each parent of this
* Node is notified that its bounds may have also changed.
*
* This function should be treated as though it were final. It is not
* intended to be overridden by subclasses.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doGeomChanged() {
if (geomBoundsInvalid) {
// GeomBoundsInvalid is false when node geometry changed and
// the untransformed node bounds haven't been recalculated yet.
// Most of the time, the recalculation of layout and transformed
// node bounds don't require validation of untransformed bounds
// and so we can not skip the following notifications.
NodeHelper.notifyLayoutBoundsChanged(this);
transformedBoundsChanged();
return;
}
geomBounds.makeEmpty();
geomBoundsInvalid = true;
NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS);
NodeHelper.notifyLayoutBoundsChanged(this);
localBoundsChanged();
}
private boolean geomBoundsInvalid = true;
private boolean localBoundsInvalid = true;
private boolean txBoundsInvalid = true;
Responds to changes in the local bounds by invalidating boundsInLocal
and notifying this node that its transformed bounds have changed.
/**
* Responds to changes in the local bounds by invalidating boundsInLocal
* and notifying this node that its transformed bounds have changed.
*/
void localBoundsChanged() {
localBoundsInvalid = true;
invalidateBoundsInLocal();
transformedBoundsChanged();
}
Responds to changes in the transformed bounds by invalidating txBounds
and boundsInParent. If this Node is not visible, then we have no need
to walk further up the tree but can instead simply invalidate state.
Otherwise, this function will notify parents (either the parent or the
clipParent) that this child Node's bounds have changed.
/**
* Responds to changes in the transformed bounds by invalidating txBounds
* and boundsInParent. If this Node is not visible, then we have no need
* to walk further up the tree but can instead simply invalidate state.
* Otherwise, this function will notify parents (either the parent or the
* clipParent) that this child Node's bounds have changed.
*/
void transformedBoundsChanged() {
if (!txBoundsInvalid) {
txBounds.makeEmpty();
txBoundsInvalid = true;
invalidateBoundsInParent();
NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS);
}
if (isVisible()) {
notifyParentOfBoundsChange();
}
}
/*
* Invoked by geomChanged(). Since layoutBounds is by default based
* on the geometric bounds, the default implementation of this function will
* invalidate the layoutBounds. Resizable Node subclasses generally base
* layoutBounds on the width/height instead of the geometric bounds, and so
* will generally want to override this function to be a no-op.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doNotifyLayoutBoundsChanged() {
layoutBoundsChanged();
// notify the parent
// Group instanceof check a little hoaky, but it allows us to disable
// unnecessary layout for the case of a non-resizable within a group
Parent p = getParent();
// Need to propagate layout if parent isn't part of performing layout
if (isManaged() && (p != null) && !(p instanceof Group && !isResizable())
&& !p.isPerformingLayout()) {
// Force its parent to fix the layout since it is a managed child.
p.requestLayout(true);
}
}
Notifies both the real parent and the clip parent (if they exist) that
the bounds of the child has changed. Note that since FX doesn't throw
NPE's, things actually are faster if we don't check twice for Null
(we check once, the compiler checks again)
/**
* Notifies both the real parent and the clip parent (if they exist) that
* the bounds of the child has changed. Note that since FX doesn't throw
* NPE's, things actually are faster if we don't check twice for Null
* (we check once, the compiler checks again)
*/
void notifyParentOfBoundsChange() {
// let the parent know which node has changed and the parent will
// deal with marking itself invalid correctly
Parent p = getParent();
if (p != null) {
p.childBoundsChanged(this);
}
// since the clip is used to compute the local bounds (and not the
// geom bounds), we just need to notify that local bounds on the
// clip parent have changed
if (clipParent != null) {
clipParent.localBoundsChanged();
}
}
/***************************************************************************
* *
* Geometry and coordinate system related APIs. For example, methods *
* related to containment, intersection, coordinate space conversion, etc. *
* *
**************************************************************************/
Returns true
if the given point (specified in the local coordinate space of this Node
) is contained within the shape of this Node
. Note that this method does not take visibility into account; the test is based on the geometry of this Node
only. Params: - localX – the x coordinate of the point in Node's space
- localY – the y coordinate of the point in Node's space
Returns: the result of contains for this Node
/**
* Returns {@code true} if the given point (specified in the local
* coordinate space of this {@code Node}) is contained within the shape of
* this {@code Node}. Note that this method does not take visibility into
* account; the test is based on the geometry of this {@code Node} only.
* @param localX the x coordinate of the point in Node's space
* @param localY the y coordinate of the point in Node's space
* @return the result of contains for this {@code Node}
*/
public boolean contains(double localX, double localY) {
if (containsBounds(localX, localY)) {
return (isPickOnBounds() || NodeHelper.computeContains(this, localX, localY));
}
return false;
}
/*
* This method only does the contains check based on the bounds, clip and
* effect of this node, excluding its shape (or geometry).
*
* Returns true if the given point (specified in the local
* coordinate space of this {@code Node}) is contained within the bounds,
* clip and effect of this node.
*/
private boolean containsBounds(double localX, double localY) {
final TempState tempState = TempState.getInstance();
BaseBounds tempBounds = tempState.bounds;
// first, we do a quick test to see if the point is contained in
// our local bounds. If so, then we will go the next step and check
// the clip, effect, and geometry for containment.
tempBounds = getLocalBounds(tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
if (tempBounds.contains((float)localX, (float)localY)) {
// if the clip is defined, then check it for containment, being
// sure to convert from this node's local coordinate system
// to the local coordinate system of the clip node
if (getClip() != null) {
tempState.point.x = (float)localX;
tempState.point.y = (float)localY;
try {
getClip().parentToLocal(tempState.point);
} catch (NoninvertibleTransformException e) {
return false;
}
if (!getClip().contains(tempState.point.x, tempState.point.y)) {
return false;
}
}
return true;
}
return false;
}
Returns true
if the given point (specified in the local coordinate space of this Node
) is contained within the shape of this Node
. Note that this method does not take visibility into account; the test is based on the geometry of this Node
only. Params: - localPoint – the 2D point in Node's space
Returns: the result of contains for this Node
/**
* Returns {@code true} if the given point (specified in the local
* coordinate space of this {@code Node}) is contained within the shape of
* this {@code Node}. Note that this method does not take visibility into
* account; the test is based on the geometry of this {@code Node} only.
* @param localPoint the 2D point in Node's space
* @return the result of contains for this {@code Node}
*/
public boolean contains(Point2D localPoint) {
return contains(localPoint.getX(), localPoint.getY());
}
Returns true
if the given rectangle (specified in the local coordinate space of this Node
) intersects the shape of this Node
. Note that this method does not take visibility into account; the test is based on the geometry of this Node
only. The default behavior of this function is simply to check if the given coordinates intersect with the local bounds. Params: - localX – the x coordinate of a rectangle in Node's space
- localY – the y coordinate of a rectangle in Node's space
- localWidth – the width of a rectangle in Node's space
- localHeight – the height of a rectangle in Node's space
Returns: the result of intersects for this Node
/**
* Returns {@code true} if the given rectangle (specified in the local
* coordinate space of this {@code Node}) intersects the shape of this
* {@code Node}. Note that this method does not take visibility into
* account; the test is based on the geometry of this {@code Node} only.
* The default behavior of this function is simply to check if the
* given coordinates intersect with the local bounds.
* @param localX the x coordinate of a rectangle in Node's space
* @param localY the y coordinate of a rectangle in Node's space
* @param localWidth the width of a rectangle in Node's space
* @param localHeight the height of a rectangle in Node's space
* @return the result of intersects for this {@code Node}
*/
public boolean intersects(double localX, double localY, double localWidth, double localHeight) {
BaseBounds tempBounds = TempState.getInstance().bounds;
tempBounds = getLocalBounds(tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
return tempBounds.intersects((float)localX,
(float)localY,
(float)localWidth,
(float)localHeight);
}
Returns true
if the given bounds (specified in the local coordinate space of this Node
) intersects the shape of this Node
. Note that this method does not take visibility into account; the test is based on the geometry of this Node
only. The default behavior of this function is simply to check if the given coordinates intersect with the local bounds. Params: - localBounds – the bounds
Returns: the result of intersects for this Node
/**
* Returns {@code true} if the given bounds (specified in the local
* coordinate space of this {@code Node}) intersects the shape of this
* {@code Node}. Note that this method does not take visibility into
* account; the test is based on the geometry of this {@code Node} only.
* The default behavior of this function is simply to check if the
* given coordinates intersect with the local bounds.
* @param localBounds the bounds
* @return the result of intersects for this {@code Node}
*/
public boolean intersects(Bounds localBounds) {
return intersects(localBounds.getMinX(), localBounds.getMinY(), localBounds.getWidth(), localBounds.getHeight());
}
Transforms a point from the coordinate space of the Screen
into the local coordinate space of this Node
. Params: - screenX – x coordinate of a point on a Screen
- screenY – y coordinate of a point on a Screen
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible. Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the {@link javafx.stage.Screen}
* into the local coordinate space of this {@code Node}.
* @param screenX x coordinate of a point on a Screen
* @param screenY y coordinate of a point on a Screen
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
* @since JavaFX 8.0
*/
public Point2D screenToLocal(double screenX, double screenY) {
Scene scene = getScene();
if (scene == null) return null;
Window window = scene.getWindow();
if (window == null) return null;
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)(screenX - scene.getX() - window.getX()),
(float)(screenY - scene.getY() - window.getY()));
final SubScene subScene = getSubScene();
if (subScene != null) {
final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene,
new Point2D(tempPt.x, tempPt.y));
if (ssCoord == null) {
return null;
}
tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY());
}
final Point3D ppIntersect =
scene.getEffectiveCamera().pickProjectPlane(tempPt.x, tempPt.y);
tempPt.setLocation((float) ppIntersect.getX(), (float) ppIntersect.getY());
try {
sceneToLocal(tempPt);
} catch (NoninvertibleTransformException e) {
return null;
}
return new Point2D(tempPt.x, tempPt.y);
}
Transforms a point from the coordinate space of the Screen
into the local coordinate space of this Node
. Params: - screenPoint – a point on a Screen
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible. Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the {@link javafx.stage.Screen}
* into the local coordinate space of this {@code Node}.
* @param screenPoint a point on a Screen
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
* @since JavaFX 8.0
*/
public Point2D screenToLocal(Point2D screenPoint) {
return screenToLocal(screenPoint.getX(), screenPoint.getY());
}
Transforms a rectangle from the coordinate space of the Screen
into the local coordinate space of this Node
. Returns reasonable result only in 2D space. Params: - screenBounds – bounds on a Screen
Returns: bounds in the local Node'space or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible. Since: JavaFX 8.0
/**
* Transforms a rectangle from the coordinate space of the
* {@link javafx.stage.Screen} into the local coordinate space of this
* {@code Node}. Returns reasonable result only in 2D space.
* @param screenBounds bounds on a Screen
* @return bounds in the local Node'space or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
* @since JavaFX 8.0
*/
public Bounds screenToLocal(Bounds screenBounds) {
final Point2D p1 = screenToLocal(screenBounds.getMinX(), screenBounds.getMinY());
final Point2D p2 = screenToLocal(screenBounds.getMinX(), screenBounds.getMaxY());
final Point2D p3 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMinY());
final Point2D p4 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMaxY());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. If the Node does not have any SubScene
or rootScene
is set to true, the arguments are in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling sceneToLocal(double, double)
. Params: - x – the x coordinate
- y – the y coordinate
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
Returns: local coordinates of the point Since: JavaFX 8u40
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #sceneToLocal(double, double)}.
*
* @param x the x coordinate
* @param y the y coordinate
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return local coordinates of the point
* @since JavaFX 8u40
*/
public Point2D sceneToLocal(double x, double y, boolean rootScene) {
if (!rootScene) {
return sceneToLocal(x, y);
}
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)(x), (float)y);
final SubScene subScene = getSubScene();
if (subScene != null) {
final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene,
new Point2D(tempPt.x, tempPt.y));
if (ssCoord == null) {
return null;
}
tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY());
}
try {
sceneToLocal(tempPt);
return new Point2D(tempPt.x, tempPt.y);
} catch (NoninvertibleTransformException e) {
return null;
}
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. If the Node does not have any SubScene
or rootScene
is set to true, the arguments are in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling sceneToLocal(Point2D)
. Params: - point – the point
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
Returns: local coordinates of the point Since: JavaFX 8u40
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #sceneToLocal(javafx.geometry.Point2D)}.
*
* @param point the point
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return local coordinates of the point
* @since JavaFX 8u40
*/
public Point2D sceneToLocal(Point2D point, boolean rootScene) {
return sceneToLocal(point.getX(), point.getY(), rootScene);
}
Transforms a bounds from the coordinate space of the scene into the local coordinate space of this Node
. If the Node does not have any SubScene
or rootScene
is set to true, the arguments are in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling sceneToLocal(Bounds)
. Since 3D bounds cannot be converted with rootScene
set to true
, trying to convert 3D bounds will yield null
.
Params: - bounds – the bounds
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
Returns: local coordinates of the bounds Since: JavaFX 8u40
/**
* Transforms a bounds from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #sceneToLocal(javafx.geometry.Bounds)}.
* <p>
* Since 3D bounds cannot be converted with {@code rootScene} set to {@code true}, trying to convert 3D bounds will yield {@code null}.
* </p>
* @param bounds the bounds
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return local coordinates of the bounds
* @since JavaFX 8u40
*/
public Bounds sceneToLocal(Bounds bounds, boolean rootScene) {
if (!rootScene) {
return sceneToLocal(bounds);
}
if (bounds.getMinZ() != 0 || bounds.getMaxZ() != 0) {
return null;
}
final Point2D p1 = sceneToLocal(bounds.getMinX(), bounds.getMinY(), true);
final Point2D p2 = sceneToLocal(bounds.getMinX(), bounds.getMaxY(), true);
final Point2D p3 = sceneToLocal(bounds.getMaxX(), bounds.getMinY(), true);
final Point2D p4 = sceneToLocal(bounds.getMaxX(), bounds.getMaxY(), true);
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. Note that if this node is in a SubScene
, the arguments should be in the subscene coordinates, not that of Scene
. Params: - sceneX – x coordinate of a point on a Scene
- sceneY – y coordinate of a point on a Scene
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible.
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
*
* Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
*
* @param sceneX x coordinate of a point on a Scene
* @param sceneY y coordinate of a point on a Scene
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
*/
public Point2D sceneToLocal(double sceneX, double sceneY) {
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)sceneX, (float)sceneY);
try {
sceneToLocal(tempPt);
} catch (NoninvertibleTransformException e) {
return null;
}
return new Point2D(tempPt.x, tempPt.y);
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. Note that if this node is in a SubScene
, the arguments should be in the subscene coordinates, not that of Scene
. Params: - scenePoint – a point on a Scene
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible.
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
*
* Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
*
* @param scenePoint a point on a Scene
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
*/
public Point2D sceneToLocal(Point2D scenePoint) {
return sceneToLocal(scenePoint.getX(), scenePoint.getY());
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. Note that if this node is in a SubScene
, the arguments should be in the subscene coordinates, not that of Scene
. Params: - scenePoint – a point on a Scene
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible. Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
*
* Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
*
* @param scenePoint a point on a Scene
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
* @since JavaFX 8.0
*/
public Point3D sceneToLocal(Point3D scenePoint) {
return sceneToLocal(scenePoint.getX(), scenePoint.getY(), scenePoint.getZ());
}
Transforms a point from the coordinate space of the scene into the local coordinate space of this Node
. Note that if this node is in a SubScene
, the arguments should be in the subscene coordinates, not that of Scene
. Params: - sceneX – x coordinate of a point on a Scene
- sceneY – y coordinate of a point on a Scene
- sceneZ – z coordinate of a point on a Scene
Returns: local Node's coordinates of the point or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible. Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the scene
* into the local coordinate space of this {@code Node}.
*
* Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
*
* @param sceneX x coordinate of a point on a Scene
* @param sceneY y coordinate of a point on a Scene
* @param sceneZ z coordinate of a point on a Scene
* @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
* @since JavaFX 8.0
*/
public Point3D sceneToLocal(double sceneX, double sceneY, double sceneZ) {
try {
return sceneToLocal0(sceneX, sceneY, sceneZ);
} catch (NoninvertibleTransformException ex) {
return null;
}
}
Internal method to transform a point from scene to local coordinates.
/**
* Internal method to transform a point from scene to local coordinates.
*/
private Point3D sceneToLocal0(double x, double y, double z) throws NoninvertibleTransformException {
final com.sun.javafx.geom.Vec3d tempV3D =
TempState.getInstance().vec3d;
tempV3D.set(x, y, z);
sceneToLocal(tempV3D);
return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
}
Transforms a rectangle from the coordinate space of the scene into the local coordinate space of this Node
. Note that if this node is in a SubScene
, the arguments should be in the subscene coordinates, not that of Scene
. Params: - sceneBounds – bounds on a Scene
Returns: bounds in the local Node'space or null if Node is not in a Window
. Null is also returned if the transformation from local to Scene is not invertible.
/**
* Transforms a rectangle from the coordinate space of the
* scene into the local coordinate space of this
* {@code Node}.
*
* Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
*
* @param sceneBounds bounds on a Scene
* @return bounds in the local Node'space or null if Node is not in a {@link Window}.
* Null is also returned if the transformation from local to Scene is not invertible.
*/
public Bounds sceneToLocal(Bounds sceneBounds) {
// Do a quick update of localToParentTransform so that we can determine
// if this tx is 2D transform
updateLocalToParentTransform();
if (localToParentTx.is2D() && (sceneBounds.getMinZ() == 0) && (sceneBounds.getMaxZ() == 0)) {
Point2D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY());
Point2D p2 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY());
Point2D p3 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY());
Point2D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
try {
Point3D p1 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
Point3D p2 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
Point3D p3 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
Point3D p4 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
Point3D p5 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
Point3D p6 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
Point3D p7 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
Point3D p8 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
} catch (NoninvertibleTransformException e) {
return null;
}
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its Screen
. Params: - localX – x coordinate of a point in Node's space
- localY – y coordinate of a point in Node's space
Returns: screen coordinates of the point or null if Node is not in a Window
Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its {@link javafx.stage.Screen}.
* @param localX x coordinate of a point in Node's space
* @param localY y coordinate of a point in Node's space
* @return screen coordinates of the point or null if Node is not in a {@link Window}
* @since JavaFX 8.0
*/
public Point2D localToScreen(double localX, double localY) {
return localToScreen(localX, localY, 0.0);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its Screen
. Params: - localPoint – a point in Node's space
Returns: screen coordinates of the point or null if Node is not in a Window
Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its {@link javafx.stage.Screen}.
* @param localPoint a point in Node's space
* @return screen coordinates of the point or null if Node is not in a {@link Window}
* @since JavaFX 8.0
*/
public Point2D localToScreen(Point2D localPoint) {
return localToScreen(localPoint.getX(), localPoint.getY());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its Screen
. Params: - localX – x coordinate of a point in Node's space
- localY – y coordinate of a point in Node's space
- localZ – z coordinate of a point in Node's space
Returns: screen coordinates of the point or null if Node is not in a Window
Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its {@link javafx.stage.Screen}.
* @param localX x coordinate of a point in Node's space
* @param localY y coordinate of a point in Node's space
* @param localZ z coordinate of a point in Node's space
* @return screen coordinates of the point or null if Node is not in a {@link Window}
* @since JavaFX 8.0
*/
public Point2D localToScreen(double localX, double localY, double localZ) {
Scene scene = getScene();
if (scene == null) return null;
Window window = scene.getWindow();
if (window == null) return null;
Point3D pt = localToScene(localX, localY, localZ);
final SubScene subScene = getSubScene();
if (subScene != null) {
pt = SceneUtils.subSceneToScene(subScene, pt);
}
final Point2D projection = CameraHelper.project(
SceneHelper.getEffectiveCamera(getScene()), pt);
return new Point2D(projection.getX() + scene.getX() + window.getX(),
projection.getY() + scene.getY() + window.getY());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its Screen
. Params: - localPoint – a point in Node's space
Returns: screen coordinates of the point or null if Node is not in a Window
Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its {@link javafx.stage.Screen}.
* @param localPoint a point in Node's space
* @return screen coordinates of the point or null if Node is not in a {@link Window}
* @since JavaFX 8.0
*/
public Point2D localToScreen(Point3D localPoint) {
return localToScreen(localPoint.getX(), localPoint.getY(), localPoint.getZ());
}
Transforms a bounds from the local coordinate space of this Node
into the coordinate space of its Screen
. Params: - localBounds – bounds in Node's space
Returns: the bounds in screen coordinates or null if Node is not in a Window
Since: JavaFX 8.0
/**
* Transforms a bounds from the local coordinate space of this
* {@code Node} into the coordinate space of its {@link javafx.stage.Screen}.
* @param localBounds bounds in Node's space
* @return the bounds in screen coordinates or null if Node is not in a {@link Window}
* @since JavaFX 8.0
*/
public Bounds localToScreen(Bounds localBounds) {
final Point2D p1 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
final Point2D p2 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
final Point2D p3 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
final Point2D p4 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
final Point2D p5 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
final Point2D p6 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
final Point2D p7 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
final Point2D p8 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. Note that if this node is in a SubScene
, the result is in the subscene coordinates, not that of Scene
. Params: - localX – x coordinate of a point in Node's space
- localY – y coordinate of a point in Node's space
Returns: scene coordinates of the point or null if Node is not in a Window
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
* @param localX x coordinate of a point in Node's space
* @param localY y coordinate of a point in Node's space
* @return scene coordinates of the point or null if Node is not in a {@link Window}
*/
public Point2D localToScene(double localX, double localY) {
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)localX, (float)localY);
localToScene(tempPt);
return new Point2D(tempPt.x, tempPt.y);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. Note that if this node is in a SubScene
, the result is in the subscene coordinates, not that of Scene
. Params: - localPoint – a point in Node's space
Returns: scene coordinates of the point or null if Node is not in a Window
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
* @param localPoint a point in Node's space
* @return scene coordinates of the point or null if Node is not in a {@link Window}
*/
public Point2D localToScene(Point2D localPoint) {
return localToScene(localPoint.getX(), localPoint.getY());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. Note that if this node is in a SubScene
, the result is in the subscene coordinates, not that of Scene
. Params: - localPoint – a 3D point in Node's space
See Also: Returns: the transformed 3D point in Scene's space Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
* @param localPoint a 3D point in Node's space
* @return the transformed 3D point in Scene's space
* @see #localToScene(javafx.geometry.Point3D, boolean)
* @since JavaFX 8.0
*/
public Point3D localToScene(Point3D localPoint) {
return localToScene(localPoint.getX(), localPoint.getY(), localPoint.getZ());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. Note that if this node is in a SubScene
, the result is in the subscene coordinates, not that of Scene
. Params: - x – the x coordinate of a point in Node's space
- y – the y coordinate of a point in Node's space
- z – the z coordinate of a point in Node's space
See Also: Returns: the transformed 3D point in Scene's space Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
* @param x the x coordinate of a point in Node's space
* @param y the y coordinate of a point in Node's space
* @param z the z coordinate of a point in Node's space
* @return the transformed 3D point in Scene's space
* @see #localToScene(double, double, double, boolean)
* @since JavaFX 8.0
*/
public Point3D localToScene(double x, double y, double z) {
final com.sun.javafx.geom.Vec3d tempV3D =
TempState.getInstance().vec3d;
tempV3D.set(x, y, z);
localToScene(tempV3D);
return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. If the Node does not have any SubScene
or rootScene
is set to true, the result point is in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Point3D)
. Params: - localPoint – the point in local coordinates
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
See Also: Returns: transformed point Since: JavaFX 8u40
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #localToScene(javafx.geometry.Point3D)}.
*
* @param localPoint the point in local coordinates
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return transformed point
*
* @see #localToScene(javafx.geometry.Point3D)
* @since JavaFX 8u40
*/
public Point3D localToScene(Point3D localPoint, boolean rootScene) {
Point3D pt = localToScene(localPoint);
if (rootScene) {
final SubScene subScene = getSubScene();
if (subScene != null) {
pt = SceneUtils.subSceneToScene(subScene, pt);
}
}
return pt;
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. If the Node does not have any SubScene
or rootScene
is set to true, the result point is in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(double, double, double)
. Params: - x – the x coordinate of the point in local coordinates
- y – the y coordinate of the point in local coordinates
- z – the z coordinate of the point in local coordinates
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
See Also: Returns: transformed point Since: JavaFX 8u40
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #localToScene(double, double, double)}.
*
* @param x the x coordinate of the point in local coordinates
* @param y the y coordinate of the point in local coordinates
* @param z the z coordinate of the point in local coordinates
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return transformed point
*
* @see #localToScene(double, double, double)
* @since JavaFX 8u40
*/
public Point3D localToScene(double x, double y, double z, boolean rootScene) {
return localToScene(new Point3D(x, y, z), rootScene);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. If the Node does not have any SubScene
or rootScene
is set to true, the result point is in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Point2D)
. Params: - localPoint – the point in local coordinates
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
See Also: Returns: transformed point Since: JavaFX 8u40
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #localToScene(javafx.geometry.Point2D)}.
*
* @param localPoint the point in local coordinates
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return transformed point
*
* @see #localToScene(javafx.geometry.Point2D)
* @since JavaFX 8u40
*/
public Point2D localToScene(Point2D localPoint, boolean rootScene) {
if (!rootScene) {
return localToScene(localPoint);
}
Point3D pt = localToScene(localPoint.getX(), localPoint.getY(), 0, rootScene);
return new Point2D(pt.getX(), pt.getY());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its scene. If the Node does not have any SubScene
or rootScene
is set to true, the result point is in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(double, double)
. Params: - x – the x coordinate of the point in local coordinates
- y – the y coordinate of the point in local coordinates
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
See Also: Returns: transformed point Since: JavaFX 8u40
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #localToScene(double, double)}.
*
* @param x the x coordinate of the point in local coordinates
* @param y the y coordinate of the point in local coordinates
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return transformed point
*
* @see #localToScene(double, double)
* @since JavaFX 8u40
*/
public Point2D localToScene(double x, double y, boolean rootScene) {
return localToScene(new Point2D(x, y), rootScene);
}
Transforms a bounds from the local coordinate space of this Node
into the coordinate space of its scene. If the Node does not have any SubScene
or rootScene
is set to true, the result bounds are in Scene
coordinates of the Node returned by getScene()
. Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Bounds)
. Params: - localBounds – the bounds in local coordinates
- rootScene – whether Scene coordinates should be used even if the Node is in a SubScene
See Also: Returns: transformed bounds Since: JavaFX 8u40
/**
* Transforms a bounds from the local coordinate space of this {@code Node}
* into the coordinate space of its scene.
* If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
* result bounds are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
* Otherwise, the subscene coordinates are used, which is equivalent to calling
* {@link #localToScene(javafx.geometry.Bounds)}.
*
* @param localBounds the bounds in local coordinates
* @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
* @return transformed bounds
*
* @see #localToScene(javafx.geometry.Bounds)
* @since JavaFX 8u40
*/
public Bounds localToScene(Bounds localBounds, boolean rootScene) {
if (!rootScene) {
return localToScene(localBounds);
}
Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ(), true);
Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ(), true);
Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ(), true);
Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ(), true);
Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ(), true);
Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ(), true);
Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ(), true);
Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ(), true);
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
}
Transforms a bounds from the local coordinate space of this Node
into the coordinate space of its scene. Note that if this node is in a SubScene
, the result is in the subscene coordinates, not that of Scene
. Params: - localBounds – bounds in Node's space
See Also: Returns: the bounds in the scene coordinates or null if Node is not in a Window
/**
* Transforms a bounds from the local coordinate space of this
* {@code Node} into the coordinate space of its scene.
* Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
* not that of {@link javafx.scene.Scene}.
* @param localBounds bounds in Node's space
* @return the bounds in the scene coordinates or null if Node is not in a {@link Window}
* @see #localToScene(javafx.geometry.Bounds, boolean)
*/
public Bounds localToScene(Bounds localBounds) {
// Do a quick update of localToParentTransform so that we can determine
// if this tx is 2D transform
updateLocalToParentTransform();
if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
Point2D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY());
Point2D p2 = localToScene(localBounds.getMaxX(), localBounds.getMinY());
Point2D p3 = localToScene(localBounds.getMaxX(), localBounds.getMaxY());
Point2D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
}
Transforms a point from the coordinate space of the parent into the local coordinate space of this Node
. Params: - parentX – the x coordinate in Parent's space
- parentY – the y coordinate in Parent's space
Returns: the transformed 2D point in Node's space
/**
* Transforms a point from the coordinate space of the parent into the
* local coordinate space of this {@code Node}.
* @param parentX the x coordinate in Parent's space
* @param parentY the y coordinate in Parent's space
* @return the transformed 2D point in Node's space
*/
public Point2D parentToLocal(double parentX, double parentY) {
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)parentX, (float)parentY);
try {
parentToLocal(tempPt);
} catch (NoninvertibleTransformException e) {
return null;
}
return new Point2D(tempPt.x, tempPt.y);
}
Transforms a point from the coordinate space of the parent into the local coordinate space of this Node
. Params: - parentPoint – the 2D point in Parent's space
Returns: the transformed 2D point in Node's space
/**
* Transforms a point from the coordinate space of the parent into the
* local coordinate space of this {@code Node}.
* @param parentPoint the 2D point in Parent's space
* @return the transformed 2D point in Node's space
*/
public Point2D parentToLocal(Point2D parentPoint) {
return parentToLocal(parentPoint.getX(), parentPoint.getY());
}
Transforms a point from the coordinate space of the parent into the local coordinate space of this Node
. Params: - parentPoint – parentPoint the 3D point in Parent's space
Returns: the transformed 3D point in Node's space Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the parent into the
* local coordinate space of this {@code Node}.
* @param parentPoint parentPoint the 3D point in Parent's space
* @return the transformed 3D point in Node's space
* @since JavaFX 8.0
*/
public Point3D parentToLocal(Point3D parentPoint) {
return parentToLocal(parentPoint.getX(), parentPoint.getY(), parentPoint.getZ());
}
Transforms a point from the coordinate space of the parent into the local coordinate space of this Node
. Params: - parentX – the x coordinate in Parent's space
- parentY – the y coordinate in Parent's space
- parentZ – the z coordinate in Parent's space
Returns: the transformed 3D point in Node's space Since: JavaFX 8.0
/**
* Transforms a point from the coordinate space of the parent into the
* local coordinate space of this {@code Node}.
* @param parentX the x coordinate in Parent's space
* @param parentY the y coordinate in Parent's space
* @param parentZ the z coordinate in Parent's space
* @return the transformed 3D point in Node's space
* @since JavaFX 8.0
*/
public Point3D parentToLocal(double parentX, double parentY, double parentZ) {
final com.sun.javafx.geom.Vec3d tempV3D =
TempState.getInstance().vec3d;
tempV3D.set(parentX, parentY, parentZ);
try {
parentToLocal(tempV3D);
} catch (NoninvertibleTransformException e) {
return null;
}
return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
}
Transforms a rectangle from the coordinate space of the parent into the local coordinate space of this Node
. Params: - parentBounds – the bounds in Parent's space
Returns: the transformed bounds in Node's space
/**
* Transforms a rectangle from the coordinate space of the parent into the
* local coordinate space of this {@code Node}.
* @param parentBounds the bounds in Parent's space
* @return the transformed bounds in Node's space
*/
public Bounds parentToLocal(Bounds parentBounds) {
// Do a quick update of localToParentTransform so that we can determine
// if this tx is 2D transform
updateLocalToParentTransform();
if (localToParentTx.is2D() && (parentBounds.getMinZ() == 0) && (parentBounds.getMaxZ() == 0)) {
Point2D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY());
Point2D p2 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY());
Point2D p3 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY());
Point2D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
Point3D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMinZ());
Point3D p2 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMaxZ());
Point3D p3 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMinZ());
Point3D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
Point3D p5 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMinZ());
Point3D p6 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
Point3D p7 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMinZ());
Point3D p8 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMaxZ());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its parent. Params: - localX – the x coordinate of the point in Node's space
- localY – the y coordinate of the point in Node's space
Returns: the transformed 2D point in Parent's space
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its parent.
* @param localX the x coordinate of the point in Node's space
* @param localY the y coordinate of the point in Node's space
* @return the transformed 2D point in Parent's space
*/
public Point2D localToParent(double localX, double localY) {
final com.sun.javafx.geom.Point2D tempPt =
TempState.getInstance().point;
tempPt.setLocation((float)localX, (float)localY);
localToParent(tempPt);
return new Point2D(tempPt.x, tempPt.y);
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its parent. Params: - localPoint – the 2D point in Node's space
Returns: the transformed 2D point in Parent's space
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its parent.
* @param localPoint the 2D point in Node's space
* @return the transformed 2D point in Parent's space
*/
public Point2D localToParent(Point2D localPoint) {
return localToParent(localPoint.getX(), localPoint.getY());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its parent. Params: - localPoint – the 3D point in Node's space
Returns: the transformed 3D point in Parent's space Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its parent.
* @param localPoint the 3D point in Node's space
* @return the transformed 3D point in Parent's space
* @since JavaFX 8.0
*/
public Point3D localToParent(Point3D localPoint) {
return localToParent(localPoint.getX(), localPoint.getY(), localPoint.getZ());
}
Transforms a point from the local coordinate space of this Node
into the coordinate space of its parent. Params: - x – the x coordinate of the point in Node's space
- y – the y coordinate of the point in Node's space
- z – the z coordinate of the point in Node's space
Returns: the transformed 3D point in Parent's space Since: JavaFX 8.0
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its parent.
* @param x the x coordinate of the point in Node's space
* @param y the y coordinate of the point in Node's space
* @param z the z coordinate of the point in Node's space
* @return the transformed 3D point in Parent's space
* @since JavaFX 8.0
*/
public Point3D localToParent(double x, double y, double z) {
final com.sun.javafx.geom.Vec3d tempV3D =
TempState.getInstance().vec3d;
tempV3D.set(x, y, z);
localToParent(tempV3D);
return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
}
Transforms a bounds from the local coordinate space of this Node
into the coordinate space of its parent. Params: - localBounds – the bounds in Node's space
Returns: the transformed bounds in Parent's space
/**
* Transforms a bounds from the local coordinate space of this
* {@code Node} into the coordinate space of its parent.
* @param localBounds the bounds in Node's space
* @return the transformed bounds in Parent's space
*/
public Bounds localToParent(Bounds localBounds) {
// Do a quick update of localToParentTransform so that we can determine
// if this tx is 2D transform
updateLocalToParentTransform();
if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
Point2D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY());
Point2D p2 = localToParent(localBounds.getMaxX(), localBounds.getMinY());
Point2D p3 = localToParent(localBounds.getMaxX(), localBounds.getMaxY());
Point2D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
}
Point3D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
Point3D p2 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
Point3D p3 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
Point3D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
Point3D p5 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
Point3D p6 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
Point3D p7 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
Point3D p8 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
}
Copy the localToParent transform into specified transform.
/**
* Copy the localToParent transform into specified transform.
*/
BaseTransform getLocalToParentTransform(BaseTransform tx) {
updateLocalToParentTransform();
tx.setTransform(localToParentTx);
return tx;
}
/*
* Currently used only by PathTransition
*/
final BaseTransform getLeafTransform() {
return getLocalToParentTransform(TempState.getInstance().leafTx);
}
/*
* Invoked whenever the transforms[] ObservableList changes, or by the transforms
* in that ObservableList whenever they are changed.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doTransformsChanged() {
if (!transformDirty) {
NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM);
transformDirty = true;
transformedBoundsChanged();
}
invalidateLocalToParentTransform();
invalidateLocalToSceneTransform();
}
final double getPivotX() {
final Bounds bounds = getLayoutBounds();
return bounds.getMinX() + bounds.getWidth()/2;
}
final double getPivotY() {
final Bounds bounds = getLayoutBounds();
return bounds.getMinY() + bounds.getHeight()/2;
}
final double getPivotZ() {
final Bounds bounds = getLayoutBounds();
return bounds.getMinZ() + bounds.getDepth()/2;
}
This helper function will update the transform matrix on the peer based
on the "complete" transform for this node.
/**
* This helper function will update the transform matrix on the peer based
* on the "complete" transform for this node.
*/
void updateLocalToParentTransform() {
if (transformDirty) {
localToParentTx.setToIdentity();
boolean mirror = false;
double mirroringCenter = 0;
if (hasMirroring()) {
final Scene sceneValue = getScene();
if ((sceneValue != null) && (sceneValue.getRoot() == this)) {
// handle scene mirroring in this branch
// (must be the last transformation)
mirroringCenter = sceneValue.getWidth() / 2;
if (mirroringCenter == 0.0) {
mirroringCenter = getPivotX();
}
localToParentTx = localToParentTx.deriveWithTranslation(
mirroringCenter, 0.0);
localToParentTx = localToParentTx.deriveWithScale(
-1.0, 1.0, 1.0);
localToParentTx = localToParentTx.deriveWithTranslation(
-mirroringCenter, 0.0);
} else {
// mirror later
mirror = true;
mirroringCenter = getPivotX();
}
}
if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) {
// recompute pivotX, pivotY and pivotZ
double pivotX = getPivotX();
double pivotY = getPivotY();
double pivotZ = getPivotZ();
localToParentTx = localToParentTx.deriveWithTranslation(
getTranslateX() + getLayoutX() + pivotX,
getTranslateY() + getLayoutY() + pivotY,
getTranslateZ() + pivotZ);
localToParentTx = localToParentTx.deriveWithRotation(
Math.toRadians(getRotate()), getRotationAxis().getX(),
getRotationAxis().getY(), getRotationAxis().getZ());
localToParentTx = localToParentTx.deriveWithScale(
getScaleX(), getScaleY(), getScaleZ());
localToParentTx = localToParentTx.deriveWithTranslation(
-pivotX, -pivotY, -pivotZ);
} else {
localToParentTx = localToParentTx.deriveWithTranslation(
getTranslateX() + getLayoutX(),
getTranslateY() + getLayoutY(),
getTranslateZ());
}
if (hasTransforms()) {
for (Transform t : getTransforms()) {
localToParentTx = TransformHelper.derive(t, localToParentTx);
}
}
// Check to see whether the node requires mirroring
if (mirror) {
localToParentTx = localToParentTx.deriveWithTranslation(
mirroringCenter, 0);
localToParentTx = localToParentTx.deriveWithScale(
-1.0, 1.0, 1.0);
localToParentTx = localToParentTx.deriveWithTranslation(
-mirroringCenter, 0);
}
transformDirty = false;
}
}
Transforms in place the specified point from parent coords to local
coords. Made package private for the sake of testing.
/**
* Transforms in place the specified point from parent coords to local
* coords. Made package private for the sake of testing.
*/
void parentToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
updateLocalToParentTransform();
localToParentTx.inverseTransform(pt, pt);
}
void parentToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
updateLocalToParentTransform();
localToParentTx.inverseTransform(pt, pt);
}
void sceneToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
if (getParent() != null) {
getParent().sceneToLocal(pt);
}
parentToLocal(pt);
}
void sceneToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
if (getParent() != null) {
getParent().sceneToLocal(pt);
}
parentToLocal(pt);
}
void localToScene(com.sun.javafx.geom.Point2D pt) {
localToParent(pt);
if (getParent() != null) {
getParent().localToScene(pt);
}
}
void localToScene(com.sun.javafx.geom.Vec3d pt) {
localToParent(pt);
if (getParent() != null) {
getParent().localToScene(pt);
}
}
/***************************************************************************
* *
* Mouse event related APIs *
* *
**************************************************************************/
Transforms in place the specified point from local coords to parent
coords. Made package private for the sake of testing.
/**
* Transforms in place the specified point from local coords to parent
* coords. Made package private for the sake of testing.
*/
void localToParent(com.sun.javafx.geom.Point2D pt) {
updateLocalToParentTransform();
localToParentTx.transform(pt, pt);
}
void localToParent(com.sun.javafx.geom.Vec3d pt) {
updateLocalToParentTransform();
localToParentTx.transform(pt, pt);
}
/*
* Finds a top-most child node that contains the given local coordinates.
*
* The result argument is used for storing the picking result.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doPickNodeLocal(PickRay localPickRay, PickResultChooser result) {
intersects(localPickRay, result);
}
/*
* Finds a top-most child node that intersects the given ray.
*
* The result argument is used for storing the picking result.
*/
final void pickNode(PickRay pickRay, PickResultChooser result) {
// In some conditions we can omit picking this node or subgraph
if (!isVisible() || isDisable() || isMouseTransparent()) {
return;
}
final Vec3d o = pickRay.getOriginNoClone();
final double ox = o.x;
final double oy = o.y;
final double oz = o.z;
final Vec3d d = pickRay.getDirectionNoClone();
final double dx = d.x;
final double dy = d.y;
final double dz = d.z;
updateLocalToParentTransform();
try {
localToParentTx.inverseTransform(o, o);
localToParentTx.inverseDeltaTransform(d, d);
// Delegate to a function which can be overridden by subclasses which
// actually does the pick. The implementation is markedly different
// for leaf nodes vs. parent nodes vs. region nodes.
NodeHelper.pickNodeLocal(this, pickRay, result);
} catch (NoninvertibleTransformException e) {
// in this case we just don't pick anything
}
pickRay.setOrigin(ox, oy, oz);
pickRay.setDirection(dx, dy, dz);
}
/*
* Returns {@code true} if the given ray (start, dir), specified in the
* local coordinate space of this {@code Node}, intersects the
* shape of this {@code Node}. Note that this method does not take visibility
* into account; the test is based on the geometry of this {@code Node} only.
* <p>
* The pickResult is updated if the found intersection is closer than
* the currently held one.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
*/
final boolean intersects(PickRay pickRay, PickResultChooser pickResult) {
double boundsDistance = intersectsBounds(pickRay);
if (!Double.isNaN(boundsDistance)) {
if (isPickOnBounds()) {
if (pickResult != null) {
pickResult.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
}
return true;
} else {
return NodeHelper.computeIntersects(this, pickRay, pickResult);
}
}
return false;
}
/*
* Computes the intersection of the pickRay with this node.
* The pickResult argument is updated if the found intersection
* is closer than the passed one. On the other hand, the return value
* specifies whether the intersection exists, regardless of its comparison
* with the given pickResult.
*/
private boolean doComputeIntersects(PickRay pickRay, PickResultChooser pickResult) {
double origZ = pickRay.getOriginNoClone().z;
double dirZ = pickRay.getDirectionNoClone().z;
// Handle the case where pickRay is almost parallel to the Z-plane
if (almostZero(dirZ)) {
return false;
}
double t = -origZ / dirZ;
if (t < pickRay.getNearClip() || t > pickRay.getFarClip()) {
return false;
}
double x = pickRay.getOriginNoClone().x + (pickRay.getDirectionNoClone().x * t);
double y = pickRay.getOriginNoClone().y + (pickRay.getDirectionNoClone().y * t);
if (contains((float) x, (float) y)) {
if (pickResult != null) {
pickResult.offer(this, t, PickResultChooser.computePoint(pickRay, t));
}
return true;
}
return false;
}
/*
* Computes the intersection of the pickRay with the bounds of this node.
* The return value is the distance between the camera and the intersection
* point, measured in pickRay direction magnitudes. If there is
* no intersection, it returns NaN.
*
* @param pickRay The pick ray
* @return Distance of the intersection point, a NaN if there
* is no intersection
*/
final double intersectsBounds(PickRay pickRay) {
final Vec3d dir = pickRay.getDirectionNoClone();
double tmin, tmax;
final Vec3d origin = pickRay.getOriginNoClone();
final double originX = origin.x;
final double originY = origin.y;
final double originZ = origin.z;
final TempState tempState = TempState.getInstance();
BaseBounds tempBounds = tempState.bounds;
tempBounds = getLocalBounds(tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
if (dir.x == 0.0 && dir.y == 0.0) {
// fast path for the usual 2D picking
if (dir.z == 0.0) {
return Double.NaN;
}
if (originX < tempBounds.getMinX() ||
originX > tempBounds.getMaxX() ||
originY < tempBounds.getMinY() ||
originY > tempBounds.getMaxY()) {
return Double.NaN;
}
final double invDirZ = 1.0 / dir.z;
final boolean signZ = invDirZ < 0.0;
final double minZ = tempBounds.getMinZ();
final double maxZ = tempBounds.getMaxZ();
tmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
tmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
} else if (tempBounds.getDepth() == 0.0) {
// fast path for 3D picking of 2D bounds
if (almostZero(dir.z)) {
return Double.NaN;
}
final double t = (tempBounds.getMinZ() - originZ) / dir.z;
final double x = originX + (dir.x * t);
final double y = originY + (dir.y * t);
if (x < tempBounds.getMinX() ||
x > tempBounds.getMaxX() ||
y < tempBounds.getMinY() ||
y > tempBounds.getMaxY()) {
return Double.NaN;
}
tmin = tmax = t;
} else {
final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x);
final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y);
final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z);
final boolean signX = invDirX < 0.0;
final boolean signY = invDirY < 0.0;
final boolean signZ = invDirZ < 0.0;
final double minX = tempBounds.getMinX();
final double minY = tempBounds.getMinY();
final double maxX = tempBounds.getMaxX();
final double maxY = tempBounds.getMaxY();
tmin = Double.NEGATIVE_INFINITY;
tmax = Double.POSITIVE_INFINITY;
if (Double.isInfinite(invDirX)) {
if (minX <= originX && maxX >= originX) {
// move on, we are inside for the whole length
} else {
return Double.NaN;
}
} else {
tmin = ((signX ? maxX : minX) - originX) * invDirX;
tmax = ((signX ? minX : maxX) - originX) * invDirX;
}
if (Double.isInfinite(invDirY)) {
if (minY <= originY && maxY >= originY) {
// move on, we are inside for the whole length
} else {
return Double.NaN;
}
} else {
final double tymin = ((signY ? maxY : minY) - originY) * invDirY;
final double tymax = ((signY ? minY : maxY) - originY) * invDirY;
if ((tmin > tymax) || (tymin > tmax)) {
return Double.NaN;
}
if (tymin > tmin) {
tmin = tymin;
}
if (tymax < tmax) {
tmax = tymax;
}
}
final double minZ = tempBounds.getMinZ();
final double maxZ = tempBounds.getMaxZ();
if (Double.isInfinite(invDirZ)) {
if (minZ <= originZ && maxZ >= originZ) {
// move on, we are inside for the whole length
} else {
return Double.NaN;
}
} else {
final double tzmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
final double tzmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
if ((tmin > tzmax) || (tzmin > tmax)) {
return Double.NaN;
}
if (tzmin > tmin) {
tmin = tzmin;
}
if (tzmax < tmax) {
tmax = tzmax;
}
}
}
// For clip we use following semantics: pick the node normally
// if there is an intersection with the clip node. We don't consider
// clip node distance.
Node clip = getClip();
if (clip != null
// FIXME: All 3D picking is currently ignored by rendering.
// Until this is fixed or defined differently (RT-28510),
// we follow this behavior.
&& !(this instanceof Shape3D) && !(clip instanceof Shape3D)) {
final double dirX = dir.x;
final double dirY = dir.y;
final double dirZ = dir.z;
clip.updateLocalToParentTransform();
boolean hitClip = true;
try {
clip.localToParentTx.inverseTransform(origin, origin);
clip.localToParentTx.inverseDeltaTransform(dir, dir);
} catch (NoninvertibleTransformException e) {
hitClip = false;
}
hitClip = hitClip && clip.intersects(pickRay, null);
pickRay.setOrigin(originX, originY, originZ);
pickRay.setDirection(dirX, dirY, dirZ);
if (!hitClip) {
return Double.NaN;
}
}
if (Double.isInfinite(tmin) || Double.isNaN(tmin)) {
// We've got a nonsense pick ray or bounds.
return Double.NaN;
}
final double minDistance = pickRay.getNearClip();
final double maxDistance = pickRay.getFarClip();
if (tmin < minDistance) {
if (tmax >= minDistance) {
// we are inside bounds
return 0.0;
} else {
return Double.NaN;
}
} else if (tmin > maxDistance) {
return Double.NaN;
}
return tmin;
}
// Good to find a home for commonly use util. code such as EPS.
// and almostZero. This code currently defined in multiple places,
// such as Affine3D and GeneralTransform3D.
private static final double EPSILON_ABSOLUTE = 1.0e-5;
static boolean almostZero(double a) {
return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
}
/***************************************************************************
* *
* viewOrder property handling *
* *
**************************************************************************/
Defines the rendering and picking order of this Node
within its parent. This property is used to alter the rendering and picking order of a node within its parent without reordering the parent's children
list. For example, this can be used as a more efficient way to implement transparency sorting. To do this, an application can assign the viewOrder value of each node to the computed distance between that node and the viewer.
The parent will traverse its children
in decreasing viewOrder
order. This means that a child with a lower viewOrder
will be in front of a child with a higher viewOrder
. If two children have the same viewOrder
, the parent will traverse them in the order they appear in the parent's children
list.
However, viewOrder
does not alter the layout and focus traversal order of this Node within its parent. A parent always traverses its children
list in order when doing layout or focus traversal.
Returns: the view order for this Node
@defaultValue 0.0 Since: 9
/**
* Defines the rendering and picking order of this {@code Node} within its
* parent.
* <p>
* This property is used to alter the rendering and picking order of a node
* within its parent without reordering the parent's {@code children} list.
* For example, this can be used as a more efficient way to implement
* transparency sorting. To do this, an application can assign the viewOrder
* value of each node to the computed distance between that node and the
* viewer.
* </p>
* <p>
* The parent will traverse its {@code children} in decreasing
* {@code viewOrder} order. This means that a child with a lower
* {@code viewOrder} will be in front of a child with a higher
* {@code viewOrder}. If two children have the same {@code viewOrder}, the
* parent will traverse them in the order they appear in the parent's
* {@code children} list.
* </p>
* <p>
* However, {@code viewOrder} does not alter the layout and focus traversal
* order of this Node within its parent. A parent always traverses its
* {@code children} list in order when doing layout or focus traversal.
* </p>
*
* @return the view order for this {@code Node}
* @defaultValue 0.0
*
* @since 9
*/
public final DoubleProperty viewOrderProperty() {
return getMiscProperties().viewOrderProperty();
}
public final void setViewOrder(double value) {
viewOrderProperty().set(value);
}
public final double getViewOrder() {
return (miscProperties == null) ? DEFAULT_VIEW_ORDER
: miscProperties.getViewOrder();
}
/***************************************************************************
* *
* Transformations *
* *
**************************************************************************/
Defines the ObservableList of Transform
objects to be applied to this Node
. This ObservableList of transforms is applied before translateX
, translateY
, scaleX
, and scaleY
, rotate
transforms. Returns: the transforms for this Node
@defaultValue empty
/**
* Defines the ObservableList of {@link javafx.scene.transform.Transform} objects
* to be applied to this {@code Node}. This ObservableList of transforms is applied
* before {@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #scaleXProperty scaleX}, and
* {@link #scaleYProperty scaleY}, {@link #rotateProperty rotate} transforms.
*
* @return the transforms for this {@code Node}
* @defaultValue empty
*/
public final ObservableList<Transform> getTransforms() {
return transformsProperty();
}
private ObservableList<Transform> transformsProperty() {
return getNodeTransformation().getTransforms();
}
public final void setTranslateX(double value) {
translateXProperty().set(value);
}
public final double getTranslateX() {
return (nodeTransformation == null)
? DEFAULT_TRANSLATE_X
: nodeTransformation.getTranslateX();
}
Defines the x coordinate of the translation that is added to this Node
's transform. The node's final translation will be computed as layoutX
+ translateX
, where layoutX
establishes the node's stable position and translateX
optionally makes dynamic adjustments to that position.
This variable can be used to alter the location of a node without disturbing its layoutBounds
, which makes it useful for animating a node's location.
Returns: the translateX for this Node
@defaultValue 0
/**
* Defines the x coordinate of the translation that is added to this {@code Node}'s
* transform.
* <p>
* The node's final translation will be computed as {@link #layoutXProperty layoutX} + {@code translateX},
* where {@code layoutX} establishes the node's stable position and {@code translateX}
* optionally makes dynamic adjustments to that position.
*<p>
* This variable can be used to alter the location of a node without disturbing
* its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
*
* @return the translateX for this {@code Node}
* @defaultValue 0
*/
public final DoubleProperty translateXProperty() {
return getNodeTransformation().translateXProperty();
}
public final void setTranslateY(double value) {
translateYProperty().set(value);
}
public final double getTranslateY() {
return (nodeTransformation == null)
? DEFAULT_TRANSLATE_Y
: nodeTransformation.getTranslateY();
}
Defines the y coordinate of the translation that is added to this Node
's transform. The node's final translation will be computed as layoutY
+ translateY
, where layoutY
establishes the node's stable position and translateY
optionally makes dynamic adjustments to that position.
This variable can be used to alter the location of a node without disturbing its layoutBounds
, which makes it useful for animating a node's location.
Returns: the translateY for this Node
@defaultValue 0
/**
* Defines the y coordinate of the translation that is added to this {@code Node}'s
* transform.
* <p>
* The node's final translation will be computed as {@link #layoutYProperty layoutY} + {@code translateY},
* where {@code layoutY} establishes the node's stable position and {@code translateY}
* optionally makes dynamic adjustments to that position.
* <p>
* This variable can be used to alter the location of a node without disturbing
* its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
*
* @return the translateY for this {@code Node}
* @defaultValue 0
*/
public final DoubleProperty translateYProperty() {
return getNodeTransformation().translateYProperty();
}
public final void setTranslateZ(double value) {
translateZProperty().set(value);
}
public final double getTranslateZ() {
return (nodeTransformation == null)
? DEFAULT_TRANSLATE_Z
: nodeTransformation.getTranslateZ();
}
Defines the Z coordinate of the translation that is added to the transformed coordinates of this Node
. This value will be added to any translation defined by the transforms
ObservableList and layoutZ
.
This variable can be used to alter the location of a Node without
disturbing its layout bounds, which makes it useful for animating a
node's location.
Note that this is a conditional feature. See ConditionalFeature.SCENE3D
for more information.
Returns: the translateZ for this Node
@defaultValue 0
/**
* Defines the Z coordinate of the translation that is added to the
* transformed coordinates of this {@code Node}. This value will be added
* to any translation defined by the {@code transforms} ObservableList and
* {@code layoutZ}.
* <p>
* This variable can be used to alter the location of a Node without
* disturbing its layout bounds, which makes it useful for animating a
* node's location.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
*
* @return the translateZ for this {@code Node}
* @defaultValue 0
*/
public final DoubleProperty translateZProperty() {
return getNodeTransformation().translateZProperty();
}
public final void setScaleX(double value) {
scaleXProperty().set(value);
}
public final double getScaleX() {
return (nodeTransformation == null) ? DEFAULT_SCALE_X
: nodeTransformation.getScaleX();
}
Defines the factor by which coordinates are scaled about the center of the object along the X axis of this Node
. This is used to stretch or shrink the node either manually or by using an animation. This scale factor is not included in layoutBounds
by default, which makes it ideal for scaling the entire node after all effects and transforms have been taken into account.
The pivot point about which the scale occurs is the center of the untransformed layoutBounds
.
Returns: the scaleX for this Node
@defaultValue 1.0
/**
* Defines the factor by which coordinates are scaled about the center of the
* object along the X axis of this {@code Node}. This is used to stretch or
* shrink the node either manually or by using an animation.
* <p>
* This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
* default, which makes it ideal for scaling the entire node after
* all effects and transforms have been taken into account.
* <p>
* The pivot point about which the scale occurs is the center of the
* untransformed {@link #layoutBoundsProperty layoutBounds}.
*
* @return the scaleX for this {@code Node}
* @defaultValue 1.0
*/
public final DoubleProperty scaleXProperty() {
return getNodeTransformation().scaleXProperty();
}
public final void setScaleY(double value) {
scaleYProperty().set(value);
}
public final double getScaleY() {
return (nodeTransformation == null) ? DEFAULT_SCALE_Y
: nodeTransformation.getScaleY();
}
Defines the factor by which coordinates are scaled about the center of the object along the Y axis of this Node
. This is used to stretch or shrink the node either manually or by using an animation. This scale factor is not included in layoutBounds
by default, which makes it ideal for scaling the entire node after all effects and transforms have been taken into account.
The pivot point about which the scale occurs is the center of the untransformed layoutBounds
.
Returns: the scaleY for this Node
@defaultValue 1.0
/**
* Defines the factor by which coordinates are scaled about the center of the
* object along the Y axis of this {@code Node}. This is used to stretch or
* shrink the node either manually or by using an animation.
* <p>
* This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
* default, which makes it ideal for scaling the entire node after
* all effects and transforms have been taken into account.
* <p>
* The pivot point about which the scale occurs is the center of the
* untransformed {@link #layoutBoundsProperty layoutBounds}.
*
* @return the scaleY for this {@code Node}
* @defaultValue 1.0
*/
public final DoubleProperty scaleYProperty() {
return getNodeTransformation().scaleYProperty();
}
public final void setScaleZ(double value) {
scaleZProperty().set(value);
}
public final double getScaleZ() {
return (nodeTransformation == null) ? DEFAULT_SCALE_Z
: nodeTransformation.getScaleZ();
}
Defines the factor by which coordinates are scaled about the center of the object along the Z axis of this Node
. This is used to stretch or shrink the node either manually or by using an animation. This scale factor is not included in layoutBounds
by default, which makes it ideal for scaling the entire node after all effects and transforms have been taken into account.
The pivot point about which the scale occurs is the center of the rectangular bounds formed by taking boundsInLocal
and applying all the transforms in the transforms
ObservableList.
Note that this is a conditional feature. See ConditionalFeature.SCENE3D
for more information.
Returns: the scaleZ for this Node
@defaultValue 1.0
/**
* Defines the factor by which coordinates are scaled about the center of the
* object along the Z axis of this {@code Node}. This is used to stretch or
* shrink the node either manually or by using an animation.
* <p>
* This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
* default, which makes it ideal for scaling the entire node after
* all effects and transforms have been taken into account.
* <p>
* The pivot point about which the scale occurs is the center of the
* rectangular bounds formed by taking {@link #boundsInLocalProperty boundsInLocal} and applying
* all the transforms in the {@link #getTransforms transforms} ObservableList.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
*
* @return the scaleZ for this {@code Node}
* @defaultValue 1.0
*/
public final DoubleProperty scaleZProperty() {
return getNodeTransformation().scaleZProperty();
}
public final void setRotate(double value) {
rotateProperty().set(value);
}
public final double getRotate() {
return (nodeTransformation == null) ? DEFAULT_ROTATE
: nodeTransformation.getRotate();
}
Defines the angle of rotation about the Node
's center, measured in degrees. This is used to rotate the Node
. This rotation factor is not included in layoutBounds
by default, which makes it ideal for rotating the entire node after all effects and transforms have been taken into account.
The pivot point about which the rotation occurs is the center of the untransformed layoutBounds
.
Note that because the pivot point is computed as the center of this Node
's layout bounds, any change to the layout bounds will cause the pivot point to change, which can move the object. For a leaf node, any change to the geometry will cause the layout bounds to change. For a group node, any change to any of its children, including a change in a child's geometry, clip, effect, position, orientation, or scale, will cause the group's layout bounds to change. If this movement of the pivot point is not desired, applications should instead use the Node's transforms
ObservableList, and add a Rotate
transform, which has a user-specifiable pivot point.
Returns: the rotate for this Node
@defaultValue 0.0
/**
* Defines the angle of rotation about the {@code Node}'s center, measured in
* degrees. This is used to rotate the {@code Node}.
* <p>
* This rotation factor is not included in {@link #layoutBoundsProperty layoutBounds} by
* default, which makes it ideal for rotating the entire node after
* all effects and transforms have been taken into account.
* <p>
* The pivot point about which the rotation occurs is the center of the
* untransformed {@link #layoutBoundsProperty layoutBounds}.
* <p>
* Note that because the pivot point is computed as the center of this
* {@code Node}'s layout bounds, any change to the layout bounds will cause
* the pivot point to change, which can move the object. For a leaf node,
* any change to the geometry will cause the layout bounds to change.
* For a group node, any change to any of its children, including a
* change in a child's geometry, clip, effect, position, orientation, or
* scale, will cause the group's layout bounds to change. If this movement
* of the pivot point is not
* desired, applications should instead use the Node's {@link #getTransforms transforms}
* ObservableList, and add a {@link javafx.scene.transform.Rotate} transform,
* which has a user-specifiable pivot point.
*
* @return the rotate for this {@code Node}
* @defaultValue 0.0
*/
public final DoubleProperty rotateProperty() {
return getNodeTransformation().rotateProperty();
}
public final void setRotationAxis(Point3D value) {
rotationAxisProperty().set(value);
}
public final Point3D getRotationAxis() {
return (nodeTransformation == null)
? DEFAULT_ROTATION_AXIS
: nodeTransformation.getRotationAxis();
}
Defines the axis of rotation of this Node
. Note that this is a conditional feature. See ConditionalFeature.SCENE3D
for more information.
Returns: the rotationAxis for this Node
@defaultValue Rotate.Z_AXIS
/**
* Defines the axis of rotation of this {@code Node}.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
*
* @return the rotationAxis for this {@code Node}
* @defaultValue Rotate.Z_AXIS
*/
public final ObjectProperty<Point3D> rotationAxisProperty() {
return getNodeTransformation().rotationAxisProperty();
}
An affine transform that holds the computed local-to-parent transform.
This is the concatenation of all transforms in this node, including all
of the convenience transforms.
Returns: the localToParent transform for this Node
Since: JavaFX 2.2
/**
* An affine transform that holds the computed local-to-parent transform.
* This is the concatenation of all transforms in this node, including all
* of the convenience transforms.
* @return the localToParent transform for this {@code Node}
* @since JavaFX 2.2
*/
public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
return getNodeTransformation().localToParentTransformProperty();
}
private void invalidateLocalToParentTransform() {
if (nodeTransformation != null) {
nodeTransformation.invalidateLocalToParentTransform();
}
}
public final Transform getLocalToParentTransform() {
return localToParentTransformProperty().get();
}
An affine transform that holds the computed local-to-scene transform. This is the concatenation of all transforms in this node's parents and in this node, including all of the convenience transforms up to the root. If this node is in a SubScene
, this property represents transforms up to the subscene, not the root scene.
Note that when you register a listener or a binding to this property,
it needs to listen for invalidation on all its parents to the root node.
This means that registering a listener on this
property on many nodes may negatively affect performance of
transformation changes in their common parents.
Returns: the localToScene transform for this Node
Since: JavaFX 2.2
/**
* An affine transform that holds the computed local-to-scene transform.
* This is the concatenation of all transforms in this node's parents and
* in this node, including all of the convenience transforms up to the root.
* If this node is in a {@link javafx.scene.SubScene}, this property represents
* transforms up to the subscene, not the root scene.
*
* <p>
* Note that when you register a listener or a binding to this property,
* it needs to listen for invalidation on all its parents to the root node.
* This means that registering a listener on this
* property on many nodes may negatively affect performance of
* transformation changes in their common parents.
* </p>
*
* @return the localToScene transform for this {@code Node}
* @since JavaFX 2.2
*/
public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
return getNodeTransformation().localToSceneTransformProperty();
}
private void invalidateLocalToSceneTransform() {
if (nodeTransformation != null) {
nodeTransformation.invalidateLocalToSceneTransform();
}
}
public final Transform getLocalToSceneTransform() {
return localToSceneTransformProperty().get();
}
private NodeTransformation nodeTransformation;
private NodeTransformation getNodeTransformation() {
if (nodeTransformation == null) {
nodeTransformation = new NodeTransformation();
}
return nodeTransformation;
}
private boolean hasTransforms() {
return (nodeTransformation != null)
&& nodeTransformation.hasTransforms();
}
// for tests only
Transform getCurrentLocalToSceneTransformState() {
if (nodeTransformation == null ||
nodeTransformation.localToSceneTransform == null) {
return null;
}
return nodeTransformation.localToSceneTransform.transform;
}
private static final double DEFAULT_TRANSLATE_X = 0;
private static final double DEFAULT_TRANSLATE_Y = 0;
private static final double DEFAULT_TRANSLATE_Z = 0;
private static final double DEFAULT_SCALE_X = 1;
private static final double DEFAULT_SCALE_Y = 1;
private static final double DEFAULT_SCALE_Z = 1;
private static final double DEFAULT_ROTATE = 0;
private static final Point3D DEFAULT_ROTATION_AXIS = Rotate.Z_AXIS;
private final class NodeTransformation {
private DoubleProperty translateX;
private DoubleProperty translateY;
private DoubleProperty translateZ;
private DoubleProperty scaleX;
private DoubleProperty scaleY;
private DoubleProperty scaleZ;
private DoubleProperty rotate;
private ObjectProperty<Point3D> rotationAxis;
private ObservableList<Transform> transforms;
private LazyTransformProperty localToParentTransform;
private LazyTransformProperty localToSceneTransform;
private int listenerReasons = 0;
private InvalidationListener localToSceneInvLstnr;
private InvalidationListener getLocalToSceneInvalidationListener() {
if (localToSceneInvLstnr == null) {
localToSceneInvLstnr = observable -> invalidateLocalToSceneTransform();
}
return localToSceneInvLstnr;
}
public void incListenerReasons() {
if (listenerReasons == 0) {
Node n = Node.this.getParent();
if (n != null) {
n.localToSceneTransformProperty().addListener(
getLocalToSceneInvalidationListener());
}
}
listenerReasons++;
}
public void decListenerReasons() {
listenerReasons--;
if (listenerReasons == 0) {
Node n = Node.this.getParent();
if (n != null) {
n.localToSceneTransformProperty().removeListener(
getLocalToSceneInvalidationListener());
}
if (localToSceneTransform != null) {
localToSceneTransform.validityUnknown();
}
}
}
public final Transform getLocalToParentTransform() {
return localToParentTransformProperty().get();
}
public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
if (localToParentTransform == null) {
localToParentTransform = new LazyTransformProperty() {
@Override
protected Transform computeTransform(Transform reuse) {
updateLocalToParentTransform();
return TransformUtils.immutableTransform(reuse,
localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(),
localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(),
localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt());
}
@Override
protected boolean validityKnown() {
return true;
}
@Override
protected int computeValidity() {
return valid;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "localToParentTransform";
}
};
}
return localToParentTransform;
}
public void invalidateLocalToParentTransform() {
if (localToParentTransform != null) {
localToParentTransform.invalidate();
}
}
public final Transform getLocalToSceneTransform() {
return localToSceneTransformProperty().get();
}
class LocalToSceneTransformProperty extends LazyTransformProperty {
// need this to track number of listeners
private List localToSceneListeners;
// stamps to watch for parent changes when the listeners
// are not present
private long stamp, parentStamp;
@Override
protected Transform computeTransform(Transform reuse) {
stamp++;
updateLocalToParentTransform();
Node parentNode = Node.this.getParent();
if (parentNode != null) {
final LocalToSceneTransformProperty parentProperty =
(LocalToSceneTransformProperty) parentNode.localToSceneTransformProperty();
final Transform parentTransform = parentProperty.getInternalValue();
parentStamp = parentProperty.stamp;
return TransformUtils.immutableTransform(reuse,
parentTransform,
((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
} else {
return TransformUtils.immutableTransform(reuse,
((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
}
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "localToSceneTransform";
}
@Override
protected boolean validityKnown() {
return listenerReasons > 0;
}
@Override
protected int computeValidity() {
if (valid != VALIDITY_UNKNOWN) {
return valid;
}
Node n = (Node) getBean();
Node parent = n.getParent();
if (parent != null) {
final LocalToSceneTransformProperty parentProperty =
(LocalToSceneTransformProperty) parent.localToSceneTransformProperty();
if (parentStamp != parentProperty.stamp) {
valid = INVALID;
return INVALID;
}
int parentValid = parentProperty.computeValidity();
if (parentValid == INVALID) {
valid = INVALID;
}
return parentValid;
}
// Validity unknown for root means it is valid
return VALID;
}
@Override
public void addListener(InvalidationListener listener) {
incListenerReasons();
if (localToSceneListeners == null) {
localToSceneListeners = new LinkedList<Object>();
}
localToSceneListeners.add(listener);
super.addListener(listener);
}
@Override
public void addListener(ChangeListener<? super Transform> listener) {
incListenerReasons();
if (localToSceneListeners == null) {
localToSceneListeners = new LinkedList<Object>();
}
localToSceneListeners.add(listener);
super.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
if (localToSceneListeners != null &&
localToSceneListeners.remove(listener)) {
decListenerReasons();
}
super.removeListener(listener);
}
@Override
public void removeListener(ChangeListener<? super Transform> listener) {
if (localToSceneListeners != null &&
localToSceneListeners.remove(listener)) {
decListenerReasons();
}
super.removeListener(listener);
}
}
public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
if (localToSceneTransform == null) {
localToSceneTransform = new LocalToSceneTransformProperty();
}
return localToSceneTransform;
}
public void invalidateLocalToSceneTransform() {
if (localToSceneTransform != null) {
localToSceneTransform.invalidate();
}
}
public double getTranslateX() {
return (translateX == null) ? DEFAULT_TRANSLATE_X
: translateX.get();
}
public final DoubleProperty translateXProperty() {
if (translateX == null) {
translateX = new StyleableDoubleProperty(DEFAULT_TRANSLATE_X) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.TRANSLATE_X;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "translateX";
}
};
}
return translateX;
}
public double getTranslateY() {
return (translateY == null) ? DEFAULT_TRANSLATE_Y : translateY.get();
}
public final DoubleProperty translateYProperty() {
if (translateY == null) {
translateY = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Y) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.TRANSLATE_Y;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "translateY";
}
};
}
return translateY;
}
public double getTranslateZ() {
return (translateZ == null) ? DEFAULT_TRANSLATE_Z : translateZ.get();
}
public final DoubleProperty translateZProperty() {
if (translateZ == null) {
translateZ = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Z) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.TRANSLATE_Z;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "translateZ";
}
};
}
return translateZ;
}
public double getScaleX() {
return (scaleX == null) ? DEFAULT_SCALE_X : scaleX.get();
}
public final DoubleProperty scaleXProperty() {
if (scaleX == null) {
scaleX = new StyleableDoubleProperty(DEFAULT_SCALE_X) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SCALE_X;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "scaleX";
}
};
}
return scaleX;
}
public double getScaleY() {
return (scaleY == null) ? DEFAULT_SCALE_Y : scaleY.get();
}
public final DoubleProperty scaleYProperty() {
if (scaleY == null) {
scaleY = new StyleableDoubleProperty(DEFAULT_SCALE_Y) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SCALE_Y;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "scaleY";
}
};
}
return scaleY;
}
public double getScaleZ() {
return (scaleZ == null) ? DEFAULT_SCALE_Z : scaleZ.get();
}
public final DoubleProperty scaleZProperty() {
if (scaleZ == null) {
scaleZ = new StyleableDoubleProperty(DEFAULT_SCALE_Z) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SCALE_Z;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "scaleZ";
}
};
}
return scaleZ;
}
public double getRotate() {
return (rotate == null) ? DEFAULT_ROTATE : rotate.get();
}
public final DoubleProperty rotateProperty() {
if (rotate == null) {
rotate = new StyleableDoubleProperty(DEFAULT_ROTATE) {
@Override
public void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.ROTATE;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "rotate";
}
};
}
return rotate;
}
public Point3D getRotationAxis() {
return (rotationAxis == null) ? DEFAULT_ROTATION_AXIS
: rotationAxis.get();
}
public final ObjectProperty<Point3D> rotationAxisProperty() {
if (rotationAxis == null) {
rotationAxis = new ObjectPropertyBase<Point3D>(
DEFAULT_ROTATION_AXIS) {
@Override
protected void invalidated() {
NodeHelper.transformsChanged(Node.this);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "rotationAxis";
}
};
}
return rotationAxis;
}
public ObservableList<Transform> getTransforms() {
if (transforms == null) {
transforms = new TrackableObservableList<Transform>() {
@Override
protected void onChanged(Change<Transform> c) {
while (c.next()) {
for (Transform t : c.getRemoved()) {
TransformHelper.remove(t, Node.this);
}
for (Transform t : c.getAddedSubList()) {
TransformHelper.add(t, Node.this);
}
}
NodeHelper.transformsChanged(Node.this);
}
};
}
return transforms;
}
public boolean canSetTranslateX() {
return (translateX == null) || !translateX.isBound();
}
public boolean canSetTranslateY() {
return (translateY == null) || !translateY.isBound();
}
public boolean canSetTranslateZ() {
return (translateZ == null) || !translateZ.isBound();
}
public boolean canSetScaleX() {
return (scaleX == null) || !scaleX.isBound();
}
public boolean canSetScaleY() {
return (scaleY == null) || !scaleY.isBound();
}
public boolean canSetScaleZ() {
return (scaleZ == null) || !scaleZ.isBound();
}
public boolean canSetRotate() {
return (rotate == null) || !rotate.isBound();
}
public boolean hasTransforms() {
return (transforms != null && !transforms.isEmpty());
}
public boolean hasScaleOrRotate() {
if (scaleX != null && scaleX.get() != DEFAULT_SCALE_X) {
return true;
}
if (scaleY != null && scaleY.get() != DEFAULT_SCALE_Y) {
return true;
}
if (scaleZ != null && scaleZ.get() != DEFAULT_SCALE_Z) {
return true;
}
if (rotate != null && rotate.get() != DEFAULT_ROTATE) {
return true;
}
return false;
}
}
////////////////////////////
// Private Implementation
////////////////////////////
*
Event Handler Properties *
*
/***************************************************************************
* *
* Event Handler Properties *
* *
**************************************************************************/
private EventHandlerProperties eventHandlerProperties;
private EventHandlerProperties getEventHandlerProperties() {
if (eventHandlerProperties == null) {
eventHandlerProperties =
new EventHandlerProperties(
getInternalEventDispatcher().getEventHandlerManager(),
this);
}
return eventHandlerProperties;
}
*
Component Orientation Properties *
*
/***************************************************************************
* *
* Component Orientation Properties *
* *
**************************************************************************/
private ObjectProperty<NodeOrientation> nodeOrientation;
private EffectiveOrientationProperty effectiveNodeOrientationProperty;
private static final byte EFFECTIVE_ORIENTATION_LTR = 0;
private static final byte EFFECTIVE_ORIENTATION_RTL = 1;
private static final byte EFFECTIVE_ORIENTATION_MASK = 1;
private static final byte AUTOMATIC_ORIENTATION_LTR = 0;
private static final byte AUTOMATIC_ORIENTATION_RTL = 2;
private static final byte AUTOMATIC_ORIENTATION_MASK = 2;
private byte resolvedNodeOrientation =
EFFECTIVE_ORIENTATION_LTR | AUTOMATIC_ORIENTATION_LTR;
public final void setNodeOrientation(NodeOrientation orientation) {
nodeOrientationProperty().set(orientation);
}
public final NodeOrientation getNodeOrientation() {
return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get();
}
Property holding NodeOrientation.
Node orientation describes the flow of visual data within a node.
In the English speaking world, visual data normally flows from
left-to-right. In an Arabic or Hebrew world, visual data flows
from right-to-left. This is consistent with the reading order
of text in both worlds. The default value is left-to-right.
Returns: NodeOrientation Since: JavaFX 8.0
/**
* Property holding NodeOrientation.
* <p>
* Node orientation describes the flow of visual data within a node.
* In the English speaking world, visual data normally flows from
* left-to-right. In an Arabic or Hebrew world, visual data flows
* from right-to-left. This is consistent with the reading order
* of text in both worlds. The default value is left-to-right.
* </p>
*
* @return NodeOrientation
* @since JavaFX 8.0
*/
public final ObjectProperty<NodeOrientation> nodeOrientationProperty() {
if (nodeOrientation == null) {
nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) {
@Override
protected void invalidated() {
nodeResolvedOrientationInvalidated();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "nodeOrientation";
}
@Override
public CssMetaData getCssMetaData() {
//TODO - not supported
throw new UnsupportedOperationException("Not supported yet.");
}
};
}
return nodeOrientation;
}
public final NodeOrientation getEffectiveNodeOrientation() {
return (getEffectiveOrientation(resolvedNodeOrientation)
== EFFECTIVE_ORIENTATION_LTR)
? NodeOrientation.LEFT_TO_RIGHT
: NodeOrientation.RIGHT_TO_LEFT;
}
The effective orientation of a node resolves the inheritance of
node orientation, returning either left-to-right or right-to-left.
Returns: the node orientation for this Node
Since: JavaFX 8.0
/**
* The effective orientation of a node resolves the inheritance of
* node orientation, returning either left-to-right or right-to-left.
* @return the node orientation for this {@code Node}
* @since JavaFX 8.0
*/
public final ReadOnlyObjectProperty<NodeOrientation>
effectiveNodeOrientationProperty() {
if (effectiveNodeOrientationProperty == null) {
effectiveNodeOrientationProperty =
new EffectiveOrientationProperty();
}
return effectiveNodeOrientationProperty;
}
Determines whether a node should be mirrored when node orientation
is right-to-left.
When a node is mirrored, the origin is automatically moved to the top right corner causing the node to layout children and draw from right to left using a mirroring transformation. Some nodes may wish to draw from right to left without using a transformation. These nodes will will answer false
and implement right-to-left orientation without using the automatic transformation.
Returns: true if this Node
should be mirrored Since: JavaFX 8.0
/**
* Determines whether a node should be mirrored when node orientation
* is right-to-left.
* <p>
* When a node is mirrored, the origin is automatically moved to the
* top right corner causing the node to layout children and draw from
* right to left using a mirroring transformation. Some nodes may wish
* to draw from right to left without using a transformation. These
* nodes will will answer {@code false} and implement right-to-left
* orientation without using the automatic transformation.
* </p>
* @return true if this {@code Node} should be mirrored
* @since JavaFX 8.0
*/
public boolean usesMirroring() {
return true;
}
final void parentResolvedOrientationInvalidated() {
if (getNodeOrientation() == NodeOrientation.INHERIT) {
nodeResolvedOrientationInvalidated();
} else {
// mirroring changed
NodeHelper.transformsChanged(this);
}
}
final void nodeResolvedOrientationInvalidated() {
final byte oldResolvedNodeOrientation =
resolvedNodeOrientation;
resolvedNodeOrientation =
(byte) (calcEffectiveNodeOrientation()
| calcAutomaticNodeOrientation());
if ((effectiveNodeOrientationProperty != null)
&& (getEffectiveOrientation(resolvedNodeOrientation)
!= getEffectiveOrientation(
oldResolvedNodeOrientation))) {
effectiveNodeOrientationProperty.invalidate();
}
// mirroring changed
NodeHelper.transformsChanged(this);
if (resolvedNodeOrientation != oldResolvedNodeOrientation) {
nodeResolvedOrientationChanged();
}
}
void nodeResolvedOrientationChanged() {
// overriden in Parent
}
private Node getMirroringOrientationParent() {
Node parentValue = getParent();
while (parentValue != null) {
if (parentValue.usesMirroring()) {
return parentValue;
}
parentValue = parentValue.getParent();
}
final Node subSceneValue = getSubScene();
if (subSceneValue != null) {
return subSceneValue;
}
return null;
}
private Node getOrientationParent() {
final Node parentValue = getParent();
if (parentValue != null) {
return parentValue;
}
final Node subSceneValue = getSubScene();
if (subSceneValue != null) {
return subSceneValue;
}
return null;
}
private byte calcEffectiveNodeOrientation() {
final NodeOrientation nodeOrientationValue = getNodeOrientation();
if (nodeOrientationValue != NodeOrientation.INHERIT) {
return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
? EFFECTIVE_ORIENTATION_LTR
: EFFECTIVE_ORIENTATION_RTL;
}
final Node parentValue = getOrientationParent();
if (parentValue != null) {
return getEffectiveOrientation(parentValue.resolvedNodeOrientation);
}
final Scene sceneValue = getScene();
if (sceneValue != null) {
return (sceneValue.getEffectiveNodeOrientation()
== NodeOrientation.LEFT_TO_RIGHT)
? EFFECTIVE_ORIENTATION_LTR
: EFFECTIVE_ORIENTATION_RTL;
}
return EFFECTIVE_ORIENTATION_LTR;
}
private byte calcAutomaticNodeOrientation() {
if (!usesMirroring()) {
return AUTOMATIC_ORIENTATION_LTR;
}
final NodeOrientation nodeOrientationValue = getNodeOrientation();
if (nodeOrientationValue != NodeOrientation.INHERIT) {
return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
? AUTOMATIC_ORIENTATION_LTR
: AUTOMATIC_ORIENTATION_RTL;
}
final Node parentValue = getMirroringOrientationParent();
if (parentValue != null) {
// automatic node orientation is inherited
return getAutomaticOrientation(parentValue.resolvedNodeOrientation);
}
final Scene sceneValue = getScene();
if (sceneValue != null) {
return (sceneValue.getEffectiveNodeOrientation()
== NodeOrientation.LEFT_TO_RIGHT)
? AUTOMATIC_ORIENTATION_LTR
: AUTOMATIC_ORIENTATION_RTL;
}
return AUTOMATIC_ORIENTATION_LTR;
}
// Return true if the node needs to be mirrored.
// A node has mirroring if the orientation differs from the parent
// package private for testing
final boolean hasMirroring() {
final Node parentValue = getOrientationParent();
final byte thisOrientation =
getAutomaticOrientation(resolvedNodeOrientation);
final byte parentOrientation =
(parentValue != null)
? getAutomaticOrientation(
parentValue.resolvedNodeOrientation)
: AUTOMATIC_ORIENTATION_LTR;
return thisOrientation != parentOrientation;
}
private static byte getEffectiveOrientation(
final byte resolvedNodeOrientation) {
return (byte) (resolvedNodeOrientation & EFFECTIVE_ORIENTATION_MASK);
}
private static byte getAutomaticOrientation(
final byte resolvedNodeOrientation) {
return (byte) (resolvedNodeOrientation & AUTOMATIC_ORIENTATION_MASK);
}
private final class EffectiveOrientationProperty
extends ReadOnlyObjectPropertyBase<NodeOrientation> {
@Override
public NodeOrientation get() {
return getEffectiveNodeOrientation();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "effectiveNodeOrientation";
}
public void invalidate() {
fireValueChangedEvent();
}
}
*
Misc Seldom Used Properties *
*
/***************************************************************************
* *
* Misc Seldom Used Properties *
* *
**************************************************************************/
private MiscProperties miscProperties;
private MiscProperties getMiscProperties() {
if (miscProperties == null) {
miscProperties = new MiscProperties();
}
return miscProperties;
}
private static final double DEFAULT_VIEW_ORDER = 0;
private static final boolean DEFAULT_CACHE = false;
private static final CacheHint DEFAULT_CACHE_HINT = CacheHint.DEFAULT;
private static final Node DEFAULT_CLIP = null;
private static final Cursor DEFAULT_CURSOR = null;
private static final DepthTest DEFAULT_DEPTH_TEST = DepthTest.INHERIT;
private static final boolean DEFAULT_DISABLE = false;
private static final Effect DEFAULT_EFFECT = null;
private static final InputMethodRequests DEFAULT_INPUT_METHOD_REQUESTS =
null;
private static final boolean DEFAULT_MOUSE_TRANSPARENT = false;
private final class MiscProperties {
private LazyBoundsProperty boundsInParent;
private LazyBoundsProperty boundsInLocal;
private BooleanProperty cache;
private ObjectProperty<CacheHint> cacheHint;
private ObjectProperty<Node> clip;
private ObjectProperty<Cursor> cursor;
private ObjectProperty<DepthTest> depthTest;
private BooleanProperty disable;
private ObjectProperty<Effect> effect;
private ObjectProperty<InputMethodRequests> inputMethodRequests;
private BooleanProperty mouseTransparent;
private DoubleProperty viewOrder;
public double getViewOrder() {
return (viewOrder == null) ? DEFAULT_VIEW_ORDER : viewOrder.get();
}
public final DoubleProperty viewOrderProperty() {
if (viewOrder == null) {
viewOrder = new StyleableDoubleProperty(DEFAULT_VIEW_ORDER) {
@Override
public void invalidated() {
Parent p = getParent();
if (p != null) {
// Parent will be responsible to update sorted children list
p.markViewOrderChildrenDirty();
}
NodeHelper.markDirty(Node.this, DirtyBits.NODE_VIEW_ORDER);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.VIEW_ORDER;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "viewOrder";
}
};
}
return viewOrder;
}
public final Bounds getBoundsInParent() {
return boundsInParentProperty().get();
}
public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
if (boundsInParent == null) {
boundsInParent = new LazyBoundsProperty() {
Computes the bounds including the clip, effects, and all
transforms. This function is essentially how to compute
the boundsInParent. Optimizations are made to compute as
little as possible and create as little trash as
possible.
/**
* Computes the bounds including the clip, effects, and all
* transforms. This function is essentially how to compute
* the boundsInParent. Optimizations are made to compute as
* little as possible and create as little trash as
* possible.
*/
@Override
protected Bounds computeBounds() {
BaseBounds tempBounds = TempState.getInstance().bounds;
tempBounds = getTransformedBounds(
tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
return new BoundingBox(tempBounds.getMinX(),
tempBounds.getMinY(),
tempBounds.getMinZ(),
tempBounds.getWidth(),
tempBounds.getHeight(),
tempBounds.getDepth());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "boundsInParent";
}
};
}
return boundsInParent;
}
public void invalidateBoundsInParent() {
if (boundsInParent != null) {
boundsInParent.invalidate();
}
}
public final Bounds getBoundsInLocal() {
return boundsInLocalProperty().get();
}
public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
if (boundsInLocal == null) {
boundsInLocal = new LazyBoundsProperty() {
@Override
protected Bounds computeBounds() {
BaseBounds tempBounds = TempState.getInstance().bounds;
tempBounds = getLocalBounds(
tempBounds,
BaseTransform.IDENTITY_TRANSFORM);
return new BoundingBox(tempBounds.getMinX(),
tempBounds.getMinY(),
tempBounds.getMinZ(),
tempBounds.getWidth(),
tempBounds.getHeight(),
tempBounds.getDepth());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "boundsInLocal";
}
};
}
return boundsInLocal;
}
public void invalidateBoundsInLocal() {
if (boundsInLocal != null) {
boundsInLocal.invalidate();
}
}
public final boolean isCache() {
return (cache == null) ? DEFAULT_CACHE
: cache.get();
}
public final BooleanProperty cacheProperty() {
if (cache == null) {
cache = new BooleanPropertyBase(DEFAULT_CACHE) {
@Override
protected void invalidated() {
NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "cache";
}
};
}
return cache;
}
public final CacheHint getCacheHint() {
return (cacheHint == null) ? DEFAULT_CACHE_HINT
: cacheHint.get();
}
public final ObjectProperty<CacheHint> cacheHintProperty() {
if (cacheHint == null) {
cacheHint = new ObjectPropertyBase<CacheHint>(DEFAULT_CACHE_HINT) {
@Override
protected void invalidated() {
NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "cacheHint";
}
};
}
return cacheHint;
}
public final Node getClip() {
return (clip == null) ? DEFAULT_CLIP : clip.get();
}
public final ObjectProperty<Node> clipProperty() {
if (clip == null) {
clip = new ObjectPropertyBase<Node>(DEFAULT_CLIP) {
//temp variables used when clip was invalid to rollback to
// last value
private Node oldClip;
@Override
protected void invalidated() {
final Node newClip = get();
if ((newClip != null)
&& ((newClip.isConnected()
&& newClip.clipParent != Node.this)
|| wouldCreateCycle(Node.this,
newClip))) {
// Assigning this node to clip is illegal.
// Roll back to the previous state and throw an
// exception.
final String cause =
newClip.isConnected()
&& (newClip.clipParent != Node.this)
? "node already connected"
: "cycle detected";
if (isBound()) {
unbind();
set(oldClip);
throw new IllegalArgumentException(
"Node's clip set to incorrect value "
+ " through binding"
+ " (" + cause + ", node = "
+ Node.this + ", clip = "
+ clip + ")."
+ " Binding has been removed.");
} else {
set(oldClip);
throw new IllegalArgumentException(
"Node's clip set to incorrect value"
+ " (" + cause + ", node = "
+ Node.this + ", clip = "
+ clip + ").");
}
} else {
if (oldClip != null) {
oldClip.clipParent = null;
oldClip.setScenes(null, null);
oldClip.updateTreeVisible(false);
}
if (newClip != null) {
newClip.clipParent = Node.this;
newClip.setScenes(getScene(), getSubScene());
newClip.updateTreeVisible(true);
}
NodeHelper.markDirty(Node.this, DirtyBits.NODE_CLIP);
// the local bounds have (probably) changed
localBoundsChanged();
oldClip = newClip;
}
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "clip";
}
};
}
return clip;
}
public final Cursor getCursor() {
return (cursor == null) ? DEFAULT_CURSOR : cursor.get();
}
public final ObjectProperty<Cursor> cursorProperty() {
if (cursor == null) {
cursor = new StyleableObjectProperty<Cursor>(DEFAULT_CURSOR) {
@Override
protected void invalidated() {
final Scene sceneValue = getScene();
if (sceneValue != null) {
sceneValue.markCursorDirty();
}
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.CURSOR;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "cursor";
}
};
}
return cursor;
}
public final DepthTest getDepthTest() {
return (depthTest == null) ? DEFAULT_DEPTH_TEST
: depthTest.get();
}
public final ObjectProperty<DepthTest> depthTestProperty() {
if (depthTest == null) {
depthTest = new ObjectPropertyBase<DepthTest>(DEFAULT_DEPTH_TEST) {
@Override protected void invalidated() {
computeDerivedDepthTest();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "depthTest";
}
};
}
return depthTest;
}
public final boolean isDisable() {
return (disable == null) ? DEFAULT_DISABLE : disable.get();
}
public final BooleanProperty disableProperty() {
if (disable == null) {
disable = new BooleanPropertyBase(DEFAULT_DISABLE) {
@Override
protected void invalidated() {
updateDisabled();
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "disable";
}
};
}
return disable;
}
public final Effect getEffect() {
return (effect == null) ? DEFAULT_EFFECT : effect.get();
}
public final ObjectProperty<Effect> effectProperty() {
if (effect == null) {
effect = new StyleableObjectProperty<Effect>(DEFAULT_EFFECT) {
private Effect oldEffect = null;
private int oldBits;
private final AbstractNotifyListener effectChangeListener =
new AbstractNotifyListener() {
@Override
public void invalidated(Observable valueModel) {
int newBits = ((IntegerProperty) valueModel).get();
int changedBits = newBits ^ oldBits;
oldBits = newBits;
if (EffectDirtyBits.isSet(
changedBits,
EffectDirtyBits.EFFECT_DIRTY)
&& EffectDirtyBits.isSet(
newBits,
EffectDirtyBits.EFFECT_DIRTY)) {
NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT);
}
if (EffectDirtyBits.isSet(
changedBits,
EffectDirtyBits.BOUNDS_CHANGED)) {
localBoundsChanged();
}
}
};
@Override
protected void invalidated() {
Effect _effect = get();
if (oldEffect != null) {
EffectHelper.effectDirtyProperty(oldEffect).removeListener(
effectChangeListener.getWeakListener());
}
oldEffect = _effect;
if (_effect != null) {
EffectHelper.effectDirtyProperty(_effect)
.addListener(
effectChangeListener.getWeakListener());
if (EffectHelper.isEffectDirty(_effect)) {
NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT);
}
oldBits = EffectHelper.effectDirtyProperty(_effect).get();
}
NodeHelper.markDirty(Node.this, DirtyBits.NODE_EFFECT);
// bounds may have changed regardless whether
// the dirty flag on effect is set
localBoundsChanged();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.EFFECT;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "effect";
}
};
}
return effect;
}
public final InputMethodRequests getInputMethodRequests() {
return (inputMethodRequests == null) ? DEFAULT_INPUT_METHOD_REQUESTS
: inputMethodRequests.get();
}
public ObjectProperty<InputMethodRequests>
inputMethodRequestsProperty() {
if (inputMethodRequests == null) {
inputMethodRequests =
new SimpleObjectProperty<InputMethodRequests>(
Node.this,
"inputMethodRequests",
DEFAULT_INPUT_METHOD_REQUESTS);
}
return inputMethodRequests;
}
public final boolean isMouseTransparent() {
return (mouseTransparent == null) ? DEFAULT_MOUSE_TRANSPARENT
: mouseTransparent.get();
}
public final BooleanProperty mouseTransparentProperty() {
if (mouseTransparent == null) {
mouseTransparent =
new SimpleBooleanProperty(
Node.this,
"mouseTransparent",
DEFAULT_MOUSE_TRANSPARENT);
}
return mouseTransparent;
}
public boolean canSetCursor() {
return (cursor == null) || !cursor.isBound();
}
public boolean canSetEffect() {
return (effect == null) || !effect.isBound();
}
}
/* *************************************************************************
* *
* Mouse Handling *
* *
**************************************************************************/
public final void setMouseTransparent(boolean value) {
mouseTransparentProperty().set(value);
}
public final boolean isMouseTransparent() {
return (miscProperties == null) ? DEFAULT_MOUSE_TRANSPARENT
: miscProperties.isMouseTransparent();
}
If true
, this node (together with all its children) is completely transparent to mouse events. When choosing target for mouse event, nodes with mouseTransparent
set to true
and their subtrees won't be taken into account. Returns: is this Node
(together with all its children) is completely transparent to mouse events.
/**
* If {@code true}, this node (together with all its children) is completely
* transparent to mouse events. When choosing target for mouse event, nodes
* with {@code mouseTransparent} set to {@code true} and their subtrees
* won't be taken into account.
* @return is this {@code Node} (together with all its children) is completely
* transparent to mouse events.
*/
public final BooleanProperty mouseTransparentProperty() {
return getMiscProperties().mouseTransparentProperty();
}
Whether or not this Node
is being hovered over. Typically this is due to the mouse being over the node, though it could be due to a pen hovering on a graphics tablet or other form of input. Note that current implementation of hover relies on mouse enter and
exit events to determine whether this Node is in the hover state; this
means that this feature is currently supported only on systems that
have a mouse. Future implementations may provide alternative means of
supporting hover.
@defaultValue false
/**
* Whether or not this {@code Node} is being hovered over. Typically this is
* due to the mouse being over the node, though it could be due to a pen
* hovering on a graphics tablet or other form of input.
*
* <p>Note that current implementation of hover relies on mouse enter and
* exit events to determine whether this Node is in the hover state; this
* means that this feature is currently supported only on systems that
* have a mouse. Future implementations may provide alternative means of
* supporting hover.
*
* @defaultValue false
*/
private ReadOnlyBooleanWrapper hover;
protected final void setHover(boolean value) {
hoverPropertyImpl().set(value);
}
public final boolean isHover() {
return hover == null ? false : hover.get();
}
public final ReadOnlyBooleanProperty hoverProperty() {
return hoverPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper hoverPropertyImpl() {
if (hover == null) {
hover = new ReadOnlyBooleanWrapper() {
@Override
protected void invalidated() {
PlatformLogger logger = Logging.getInputLogger();
if (logger.isLoggable(Level.FINER)) {
logger.finer(this + " hover=" + get());
}
pseudoClassStateChanged(HOVER_PSEUDOCLASS_STATE, get());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "hover";
}
};
}
return hover;
}
Whether or not the Node
is pressed. Typically this is true when the primary mouse button is down, though subclasses may define other mouse button state or key state to cause the node to be "pressed". @defaultValue false
/**
* Whether or not the {@code Node} is pressed. Typically this is true when
* the primary mouse button is down, though subclasses may define other
* mouse button state or key state to cause the node to be "pressed".
*
* @defaultValue false
*/
private ReadOnlyBooleanWrapper pressed;
protected final void setPressed(boolean value) {
pressedPropertyImpl().set(value);
}
public final boolean isPressed() {
return pressed == null ? false : pressed.get();
}
public final ReadOnlyBooleanProperty pressedProperty() {
return pressedPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper pressedPropertyImpl() {
if (pressed == null) {
pressed = new ReadOnlyBooleanWrapper() {
@Override
protected void invalidated() {
PlatformLogger logger = Logging.getInputLogger();
if (logger.isLoggable(Level.FINER)) {
logger.finer(this + " pressed=" + get());
}
pseudoClassStateChanged(PRESSED_PSEUDOCLASS_STATE, get());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "pressed";
}
};
}
return pressed;
}
public final void setOnContextMenuRequested(
EventHandler<? super ContextMenuEvent> value) {
onContextMenuRequestedProperty().set(value);
}
public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.onContextMenuRequested();
}
Defines a function to be called when a context menu has been requested on this Node
. Returns: the event handler that is called when a context menu has been requested on this Node
Since: JavaFX 2.1
/**
* Defines a function to be called when a context menu
* has been requested on this {@code Node}.
* @return the event handler that is called when a context menu has been
* requested on this {@code Node}
* @since JavaFX 2.1
*/
public final ObjectProperty<EventHandler<? super ContextMenuEvent>>
onContextMenuRequestedProperty() {
return getEventHandlerProperties().onContextMenuRequestedProperty();
}
public final void setOnMouseClicked(
EventHandler<? super MouseEvent> value) {
onMouseClickedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseClicked() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseClicked();
}
Defines a function to be called when a mouse button has been clicked (pressed and released) on this Node
. Returns: the event handler that is called when a mouse button has been clicked (pressed and released) on this Node
/**
* Defines a function to be called when a mouse button has been clicked
* (pressed and released) on this {@code Node}.
* @return the event handler that is called when a mouse button has been
* clicked (pressed and released) on this {@code Node}
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseClickedProperty() {
return getEventHandlerProperties().onMouseClickedProperty();
}
public final void setOnMouseDragged(
EventHandler<? super MouseEvent> value) {
onMouseDraggedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseDragged() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseDragged();
}
Defines a function to be called when a mouse button is pressed on this Node
and then dragged. Returns: the event handler that is called when a mouse button is pressed on this Node
and then dragged
/**
* Defines a function to be called when a mouse button is pressed
* on this {@code Node} and then dragged.
* @return the event handler that is called when a mouse button is pressed
* on this {@code Node} and then dragged
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseDraggedProperty() {
return getEventHandlerProperties().onMouseDraggedProperty();
}
public final void setOnMouseEntered(
EventHandler<? super MouseEvent> value) {
onMouseEnteredProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseEntered() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseEntered();
}
Defines a function to be called when the mouse enters this Node
. Returns: the event handler that is called when a mouse enters this Node
/**
* Defines a function to be called when the mouse enters this {@code Node}.
* @return the event handler that is called when a mouse enters this
* {@code Node}
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseEnteredProperty() {
return getEventHandlerProperties().onMouseEnteredProperty();
}
public final void setOnMouseExited(
EventHandler<? super MouseEvent> value) {
onMouseExitedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseExited() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseExited();
}
Defines a function to be called when the mouse exits this Node
. Returns: the event handler that is called when a mouse exits this Node
/**
* Defines a function to be called when the mouse exits this {@code Node}.
* @return the event handler that is called when a mouse exits this
* {@code Node}
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseExitedProperty() {
return getEventHandlerProperties().onMouseExitedProperty();
}
public final void setOnMouseMoved(
EventHandler<? super MouseEvent> value) {
onMouseMovedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseMoved() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseMoved();
}
Defines a function to be called when mouse cursor moves within this Node
but no buttons have been pushed. Returns: the event handler that is called when a mouse cursor moves within this Node
but no buttons have been pushed
/**
* Defines a function to be called when mouse cursor moves within
* this {@code Node} but no buttons have been pushed.
* @return the event handler that is called when a mouse cursor moves
* within this {@code Node} but no buttons have been pushed
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseMovedProperty() {
return getEventHandlerProperties().onMouseMovedProperty();
}
public final void setOnMousePressed(
EventHandler<? super MouseEvent> value) {
onMousePressedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMousePressed() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMousePressed();
}
Defines a function to be called when a mouse button has been pressed on this Node
. Returns: the event handler that is called when a mouse button has been pressed on this Node
/**
* Defines a function to be called when a mouse button
* has been pressed on this {@code Node}.
* @return the event handler that is called when a mouse button has been
* pressed on this {@code Node}
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMousePressedProperty() {
return getEventHandlerProperties().onMousePressedProperty();
}
public final void setOnMouseReleased(
EventHandler<? super MouseEvent> value) {
onMouseReleasedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnMouseReleased() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseReleased();
}
Defines a function to be called when a mouse button has been released on this Node
. Returns: the event handler that is called when a mouse button has been released on this Node
/**
* Defines a function to be called when a mouse button
* has been released on this {@code Node}.
* @return the event handler that is called when a mouse button has been
* released on this {@code Node}
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onMouseReleasedProperty() {
return getEventHandlerProperties().onMouseReleasedProperty();
}
public final void setOnDragDetected(
EventHandler<? super MouseEvent> value) {
onDragDetectedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnDragDetected() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragDetected();
}
Defines a function to be called when drag gesture has been
detected. This is the right place to start drag and drop operation.
Returns: the event handler that is called when drag gesture has been
detected
/**
* Defines a function to be called when drag gesture has been
* detected. This is the right place to start drag and drop operation.
* @return the event handler that is called when drag gesture has been
* detected
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onDragDetectedProperty() {
return getEventHandlerProperties().onDragDetectedProperty();
}
public final void setOnMouseDragOver(
EventHandler<? super MouseDragEvent> value) {
onMouseDragOverProperty().set(value);
}
public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseDragOver();
}
Defines a function to be called when a full press-drag-release gesture progresses within this Node
. Returns: the event handler that is called when a full press-drag-release gesture progresses within this Node
Since: JavaFX 2.1
/**
* Defines a function to be called when a full press-drag-release gesture
* progresses within this {@code Node}.
* @return the event handler that is called when a full press-drag-release
* gesture progresses within this {@code Node}
* @since JavaFX 2.1
*/
public final ObjectProperty<EventHandler<? super MouseDragEvent>>
onMouseDragOverProperty() {
return getEventHandlerProperties().onMouseDragOverProperty();
}
public final void setOnMouseDragReleased(
EventHandler<? super MouseDragEvent> value) {
onMouseDragReleasedProperty().set(value);
}
public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseDragReleased();
}
Defines a function to be called when a full press-drag-release gesture ends (by releasing mouse button) within this Node
. Returns: the event handler that is called when a full press-drag-release gesture ends (by releasing mouse button) within this Node
Since: JavaFX 2.1
/**
* Defines a function to be called when a full press-drag-release gesture
* ends (by releasing mouse button) within this {@code Node}.
* @return the event handler that is called when a full press-drag-release
* gesture ends (by releasing mouse button) within this {@code Node}
* @since JavaFX 2.1
*/
public final ObjectProperty<EventHandler<? super MouseDragEvent>>
onMouseDragReleasedProperty() {
return getEventHandlerProperties().onMouseDragReleasedProperty();
}
public final void setOnMouseDragEntered(
EventHandler<? super MouseDragEvent> value) {
onMouseDragEnteredProperty().set(value);
}
public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseDragEntered();
}
Defines a function to be called when a full press-drag-release gesture enters this Node
. Returns: the event handler that is called when a full press-drag-release gesture enters this Node
Since: JavaFX 2.1
/**
* Defines a function to be called when a full press-drag-release gesture
* enters this {@code Node}.
* @return the event handler that is called when a full press-drag-release
* gesture enters this {@code Node}
* @since JavaFX 2.1
*/
public final ObjectProperty<EventHandler<? super MouseDragEvent>>
onMouseDragEnteredProperty() {
return getEventHandlerProperties().onMouseDragEnteredProperty();
}
public final void setOnMouseDragExited(
EventHandler<? super MouseDragEvent> value) {
onMouseDragExitedProperty().set(value);
}
public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnMouseDragExited();
}
Defines a function to be called when a full press-drag-release gesture leaves this Node
. Returns: the event handler that is called when a full press-drag-release gesture leaves this Node
Since: JavaFX 2.1
/**
* Defines a function to be called when a full press-drag-release gesture
* leaves this {@code Node}.
* @return the event handler that is called when a full press-drag-release
* gesture leaves this {@code Node}
* @since JavaFX 2.1
*/
public final ObjectProperty<EventHandler<? super MouseDragEvent>>
onMouseDragExitedProperty() {
return getEventHandlerProperties().onMouseDragExitedProperty();
}
/* *************************************************************************
* *
* Gestures Handling *
* *
**************************************************************************/
public final void setOnScrollStarted(
EventHandler<? super ScrollEvent> value) {
onScrollStartedProperty().set(value);
}
public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnScrollStarted();
}
Defines a function to be called when a scrolling gesture is detected.
Returns: the event handler that is called when a scrolling gesture is
detected Since: JavaFX 2.2
/**
* Defines a function to be called when a scrolling gesture is detected.
* @return the event handler that is called when a scrolling gesture is
* detected
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super ScrollEvent>>
onScrollStartedProperty() {
return getEventHandlerProperties().onScrollStartedProperty();
}
public final void setOnScroll(
EventHandler<? super ScrollEvent> value) {
onScrollProperty().set(value);
}
public final EventHandler<? super ScrollEvent> getOnScroll() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnScroll();
}
Defines a function to be called when user performs a scrolling action.
Returns: the event handler that is called when user performs a scrolling
action
/**
* Defines a function to be called when user performs a scrolling action.
* @return the event handler that is called when user performs a scrolling
* action
*/
public final ObjectProperty<EventHandler<? super ScrollEvent>>
onScrollProperty() {
return getEventHandlerProperties().onScrollProperty();
}
public final void setOnScrollFinished(
EventHandler<? super ScrollEvent> value) {
onScrollFinishedProperty().set(value);
}
public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnScrollFinished();
}
Defines a function to be called when a scrolling gesture ends.
Returns: the event handler that is called when a scrolling gesture ends Since: JavaFX 2.2
/**
* Defines a function to be called when a scrolling gesture ends.
* @return the event handler that is called when a scrolling gesture ends
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super ScrollEvent>>
onScrollFinishedProperty() {
return getEventHandlerProperties().onScrollFinishedProperty();
}
public final void setOnRotationStarted(
EventHandler<? super RotateEvent> value) {
onRotationStartedProperty().set(value);
}
public final EventHandler<? super RotateEvent> getOnRotationStarted() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnRotationStarted();
}
Defines a function to be called when a rotation gesture is detected.
Returns: the event handler that is called when a rotation gesture is
detected Since: JavaFX 2.2
/**
* Defines a function to be called when a rotation gesture is detected.
* @return the event handler that is called when a rotation gesture is
* detected
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super RotateEvent>>
onRotationStartedProperty() {
return getEventHandlerProperties().onRotationStartedProperty();
}
public final void setOnRotate(
EventHandler<? super RotateEvent> value) {
onRotateProperty().set(value);
}
public final EventHandler<? super RotateEvent> getOnRotate() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnRotate();
}
Defines a function to be called when user performs a rotation action.
Returns: the event handler that is called when user performs a rotation
action Since: JavaFX 2.2
/**
* Defines a function to be called when user performs a rotation action.
* @return the event handler that is called when user performs a rotation
* action
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super RotateEvent>>
onRotateProperty() {
return getEventHandlerProperties().onRotateProperty();
}
public final void setOnRotationFinished(
EventHandler<? super RotateEvent> value) {
onRotationFinishedProperty().set(value);
}
public final EventHandler<? super RotateEvent> getOnRotationFinished() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnRotationFinished();
}
Defines a function to be called when a rotation gesture ends.
Returns: the event handler that is called when a rotation gesture ends Since: JavaFX 2.2
/**
* Defines a function to be called when a rotation gesture ends.
* @return the event handler that is called when a rotation gesture ends
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super RotateEvent>>
onRotationFinishedProperty() {
return getEventHandlerProperties().onRotationFinishedProperty();
}
public final void setOnZoomStarted(
EventHandler<? super ZoomEvent> value) {
onZoomStartedProperty().set(value);
}
public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnZoomStarted();
}
Defines a function to be called when a zooming gesture is detected.
Returns: the event handler that is called when a zooming gesture is
detected Since: JavaFX 2.2
/**
* Defines a function to be called when a zooming gesture is detected.
* @return the event handler that is called when a zooming gesture is
* detected
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super ZoomEvent>>
onZoomStartedProperty() {
return getEventHandlerProperties().onZoomStartedProperty();
}
public final void setOnZoom(
EventHandler<? super ZoomEvent> value) {
onZoomProperty().set(value);
}
public final EventHandler<? super ZoomEvent> getOnZoom() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnZoom();
}
Defines a function to be called when user performs a zooming action.
Returns: the event handler that is called when user performs a zooming
action Since: JavaFX 2.2
/**
* Defines a function to be called when user performs a zooming action.
* @return the event handler that is called when user performs a zooming
* action
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super ZoomEvent>>
onZoomProperty() {
return getEventHandlerProperties().onZoomProperty();
}
public final void setOnZoomFinished(
EventHandler<? super ZoomEvent> value) {
onZoomFinishedProperty().set(value);
}
public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnZoomFinished();
}
Defines a function to be called when a zooming gesture ends.
Returns: the event handler that is called when a zooming gesture ends Since: JavaFX 2.2
/**
* Defines a function to be called when a zooming gesture ends.
* @return the event handler that is called when a zooming gesture ends
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super ZoomEvent>>
onZoomFinishedProperty() {
return getEventHandlerProperties().onZoomFinishedProperty();
}
public final void setOnSwipeUp(
EventHandler<? super SwipeEvent> value) {
onSwipeUpProperty().set(value);
}
public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnSwipeUp();
}
Defines a function to be called when an upward swipe gesture
centered over this node happens.
Returns: the event handler that is called when an upward swipe gesture
centered over this node happens Since: JavaFX 2.2
/**
* Defines a function to be called when an upward swipe gesture
* centered over this node happens.
* @return the event handler that is called when an upward swipe gesture
* centered over this node happens
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super SwipeEvent>>
onSwipeUpProperty() {
return getEventHandlerProperties().onSwipeUpProperty();
}
public final void setOnSwipeDown(
EventHandler<? super SwipeEvent> value) {
onSwipeDownProperty().set(value);
}
public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnSwipeDown();
}
Defines a function to be called when a downward swipe gesture
centered over this node happens.
Returns: the event handler that is called when a downward swipe gesture
centered over this node happens Since: JavaFX 2.2
/**
* Defines a function to be called when a downward swipe gesture
* centered over this node happens.
* @return the event handler that is called when a downward swipe gesture
* centered over this node happens
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super SwipeEvent>>
onSwipeDownProperty() {
return getEventHandlerProperties().onSwipeDownProperty();
}
public final void setOnSwipeLeft(
EventHandler<? super SwipeEvent> value) {
onSwipeLeftProperty().set(value);
}
public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnSwipeLeft();
}
Defines a function to be called when a leftward swipe gesture
centered over this node happens.
Returns: the event handler that is called when a leftward swipe gesture
centered over this node happens Since: JavaFX 2.2
/**
* Defines a function to be called when a leftward swipe gesture
* centered over this node happens.
* @return the event handler that is called when a leftward swipe gesture
* centered over this node happens
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super SwipeEvent>>
onSwipeLeftProperty() {
return getEventHandlerProperties().onSwipeLeftProperty();
}
public final void setOnSwipeRight(
EventHandler<? super SwipeEvent> value) {
onSwipeRightProperty().set(value);
}
public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnSwipeRight();
}
Defines a function to be called when an rightward swipe gesture
centered over this node happens.
Returns: the event handler that is called when an rightward swipe gesture
centered over this node happens Since: JavaFX 2.2
/**
* Defines a function to be called when an rightward swipe gesture
* centered over this node happens.
* @return the event handler that is called when an rightward swipe gesture
* centered over this node happens
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super SwipeEvent>>
onSwipeRightProperty() {
return getEventHandlerProperties().onSwipeRightProperty();
}
/* *************************************************************************
* *
* Touch Handling *
* *
**************************************************************************/
public final void setOnTouchPressed(
EventHandler<? super TouchEvent> value) {
onTouchPressedProperty().set(value);
}
public final EventHandler<? super TouchEvent> getOnTouchPressed() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnTouchPressed();
}
Defines a function to be called when a new touch point is pressed.
Returns: the event handler that is called when a new touch point is pressed Since: JavaFX 2.2
/**
* Defines a function to be called when a new touch point is pressed.
* @return the event handler that is called when a new touch point is pressed
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super TouchEvent>>
onTouchPressedProperty() {
return getEventHandlerProperties().onTouchPressedProperty();
}
public final void setOnTouchMoved(
EventHandler<? super TouchEvent> value) {
onTouchMovedProperty().set(value);
}
public final EventHandler<? super TouchEvent> getOnTouchMoved() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnTouchMoved();
}
Defines a function to be called when a touch point is moved.
Returns: the event handler that is called when a touch point is moved Since: JavaFX 2.2
/**
* Defines a function to be called when a touch point is moved.
* @return the event handler that is called when a touch point is moved
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super TouchEvent>>
onTouchMovedProperty() {
return getEventHandlerProperties().onTouchMovedProperty();
}
public final void setOnTouchReleased(
EventHandler<? super TouchEvent> value) {
onTouchReleasedProperty().set(value);
}
public final EventHandler<? super TouchEvent> getOnTouchReleased() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnTouchReleased();
}
Defines a function to be called when a touch point is released.
Returns: the event handler that is called when a touch point is released Since: JavaFX 2.2
/**
* Defines a function to be called when a touch point is released.
* @return the event handler that is called when a touch point is released
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super TouchEvent>>
onTouchReleasedProperty() {
return getEventHandlerProperties().onTouchReleasedProperty();
}
public final void setOnTouchStationary(
EventHandler<? super TouchEvent> value) {
onTouchStationaryProperty().set(value);
}
public final EventHandler<? super TouchEvent> getOnTouchStationary() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnTouchStationary();
}
Defines a function to be called when a touch point stays pressed and
still.
Returns: the event handler that is called when a touch point stays pressed
and still Since: JavaFX 2.2
/**
* Defines a function to be called when a touch point stays pressed and
* still.
* @return the event handler that is called when a touch point stays pressed
* and still
* @since JavaFX 2.2
*/
public final ObjectProperty<EventHandler<? super TouchEvent>>
onTouchStationaryProperty() {
return getEventHandlerProperties().onTouchStationaryProperty();
}
/* *************************************************************************
* *
* Keyboard Handling *
* *
**************************************************************************/
public final void setOnKeyPressed(
EventHandler<? super KeyEvent> value) {
onKeyPressedProperty().set(value);
}
public final EventHandler<? super KeyEvent> getOnKeyPressed() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnKeyPressed();
}
Defines a function to be called when this Node
or its child Node
has input focus and a key has been pressed. The function is called only if the event hasn't been already consumed during its capturing or bubbling phase. Returns: the event handler that is called when this Node
or its child Node
has input focus and a key has been pressed
/**
* Defines a function to be called when this {@code Node} or its child
* {@code Node} has input focus and a key has been pressed. The function
* is called only if the event hasn't been already consumed during its
* capturing or bubbling phase.
* @return the event handler that is called when this {@code Node} or its
* child {@code Node} has input focus and a key has been pressed
*/
public final ObjectProperty<EventHandler<? super KeyEvent>>
onKeyPressedProperty() {
return getEventHandlerProperties().onKeyPressedProperty();
}
public final void setOnKeyReleased(
EventHandler<? super KeyEvent> value) {
onKeyReleasedProperty().set(value);
}
public final EventHandler<? super KeyEvent> getOnKeyReleased() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnKeyReleased();
}
Defines a function to be called when this Node
or its child Node
has input focus and a key has been released. The function is called only if the event hasn't been already consumed during its capturing or bubbling phase. Returns: the event handler that is called when this Node
or its child Node
has input focus and a key has been released
/**
* Defines a function to be called when this {@code Node} or its child
* {@code Node} has input focus and a key has been released. The function
* is called only if the event hasn't been already consumed during its
* capturing or bubbling phase.
* @return the event handler that is called when this {@code Node} or its
* child {@code Node} has input focus and a key has been released
*/
public final ObjectProperty<EventHandler<? super KeyEvent>>
onKeyReleasedProperty() {
return getEventHandlerProperties().onKeyReleasedProperty();
}
public final void setOnKeyTyped(
EventHandler<? super KeyEvent> value) {
onKeyTypedProperty().set(value);
}
public final EventHandler<? super KeyEvent> getOnKeyTyped() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnKeyTyped();
}
Defines a function to be called when this Node
or its child Node
has input focus and a key has been typed. The function is called only if the event hasn't been already consumed during its capturing or bubbling phase. Returns: the event handler that is called when this Node
or its child Node
has input focus and a key has been typed
/**
* Defines a function to be called when this {@code Node} or its child
* {@code Node} has input focus and a key has been typed. The function
* is called only if the event hasn't been already consumed during its
* capturing or bubbling phase.
* @return the event handler that is called when this {@code Node} or its
* child {@code Node} has input focus and a key has been typed
*/
public final ObjectProperty<EventHandler<? super KeyEvent>>
onKeyTypedProperty() {
return getEventHandlerProperties().onKeyTypedProperty();
}
/* *************************************************************************
* *
* Input Method Handling *
* *
**************************************************************************/
public final void setOnInputMethodTextChanged(
EventHandler<? super InputMethodEvent> value) {
onInputMethodTextChangedProperty().set(value);
}
public final EventHandler<? super InputMethodEvent>
getOnInputMethodTextChanged() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnInputMethodTextChanged();
}
Defines a function to be called when this Node
has input focus and the input method text has changed. If this function is not defined in this Node
, then it receives the result string of the input method composition as a series of onKeyTyped
function calls. When the Node
loses the input focus, the JavaFX runtime automatically commits the existing composed text if any.
Returns: the event handler that is called when this Node
has input focus and the input method text has changed
/**
* Defines a function to be called when this {@code Node}
* has input focus and the input method text has changed. If this
* function is not defined in this {@code Node}, then it
* receives the result string of the input method composition as a
* series of {@code onKeyTyped} function calls.
* <p>
* When the {@code Node} loses the input focus, the JavaFX runtime
* automatically commits the existing composed text if any.
* </p>
* @return the event handler that is called when this {@code Node} has input
* focus and the input method text has changed
*/
public final ObjectProperty<EventHandler<? super InputMethodEvent>>
onInputMethodTextChangedProperty() {
return getEventHandlerProperties().onInputMethodTextChangedProperty();
}
public final void setInputMethodRequests(InputMethodRequests value) {
inputMethodRequestsProperty().set(value);
}
public final InputMethodRequests getInputMethodRequests() {
return (miscProperties == null)
? DEFAULT_INPUT_METHOD_REQUESTS
: miscProperties.getInputMethodRequests();
}
Property holding InputMethodRequests.
Returns: InputMethodRequestsProperty
/**
* Property holding InputMethodRequests.
*
* @return InputMethodRequestsProperty
*/
public final ObjectProperty<InputMethodRequests> inputMethodRequestsProperty() {
return getMiscProperties().inputMethodRequestsProperty();
}
/***************************************************************************
* *
* Focus Traversal *
* *
**************************************************************************/
Special boolean property which allows for atomic focus change.
Focus change means defocusing the old focus owner and focusing a new
one. With a usual property, defocusing the old node fires the value
changed event and user code can react with something that breaks
focusability of the new node, or even remove the new node from the scene.
This leads to various error states. This property allows for setting
the state without firing the event. The focus change first sets both
properties and then fires both events. This makes the focus change look
like an atomic operation - when the old node is notified to loose focus,
the new node is already focused.
/**
* Special boolean property which allows for atomic focus change.
* Focus change means defocusing the old focus owner and focusing a new
* one. With a usual property, defocusing the old node fires the value
* changed event and user code can react with something that breaks
* focusability of the new node, or even remove the new node from the scene.
* This leads to various error states. This property allows for setting
* the state without firing the event. The focus change first sets both
* properties and then fires both events. This makes the focus change look
* like an atomic operation - when the old node is notified to loose focus,
* the new node is already focused.
*/
final class FocusedProperty extends ReadOnlyBooleanPropertyBase {
private boolean value;
private boolean valid = true;
private boolean needsChangeEvent = false;
public void store(final boolean value) {
if (value != this.value) {
this.value = value;
markInvalid();
}
}
public void notifyListeners() {
if (needsChangeEvent) {
fireValueChangedEvent();
needsChangeEvent = false;
}
}
private void markInvalid() {
if (valid) {
valid = false;
pseudoClassStateChanged(FOCUSED_PSEUDOCLASS_STATE, get());
PlatformLogger logger = Logging.getFocusLogger();
if (logger.isLoggable(Level.FINE)) {
logger.fine(this + " focused=" + get());
}
needsChangeEvent = true;
notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUSED);
}
}
@Override
public boolean get() {
valid = true;
return value;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "focused";
}
}
Indicates whether this Node
currently has the input focus. To have the input focus, a node must be the Scene
's focus owner, and the scene must be in a Stage
that is visible and active. See requestFocus()
for more information. See Also: @defaultValue false
/**
* Indicates whether this {@code Node} currently has the input focus.
* To have the input focus, a node must be the {@code Scene}'s focus
* owner, and the scene must be in a {@code Stage} that is visible
* and active. See {@link #requestFocus()} for more information.
*
* @see #requestFocus()
* @defaultValue false
*/
private FocusedProperty focused;
protected final void setFocused(boolean value) {
FocusedProperty fp = focusedPropertyImpl();
if (fp.value != value) {
fp.store(value);
fp.notifyListeners();
}
}
public final boolean isFocused() {
return focused == null ? false : focused.get();
}
public final ReadOnlyBooleanProperty focusedProperty() {
return focusedPropertyImpl();
}
private FocusedProperty focusedPropertyImpl() {
if (focused == null) {
focused = new FocusedProperty();
}
return focused;
}
Specifies whether this Node
should be a part of focus traversal cycle. When this property is true
focus can be moved to this Node
and from this Node
using regular focus traversal keys. On a desktop such keys are usually TAB
for moving focus forward and SHIFT+TAB
for moving focus backward. When a Scene
is created, the system gives focus to a Node
whose focusTraversable
variable is true and that is eligible to receive the focus, unless the focus had been set explicitly via a call to requestFocus()
. See Also: @defaultValue false
/**
* Specifies whether this {@code Node} should be a part of focus traversal
* cycle. When this property is {@code true} focus can be moved to this
* {@code Node} and from this {@code Node} using regular focus traversal
* keys. On a desktop such keys are usually {@code TAB} for moving focus
* forward and {@code SHIFT+TAB} for moving focus backward.
*
* When a {@code Scene} is created, the system gives focus to a
* {@code Node} whose {@code focusTraversable} variable is true
* and that is eligible to receive the focus,
* unless the focus had been set explicitly via a call
* to {@link #requestFocus()}.
*
* @see #requestFocus()
* @defaultValue false
*/
private BooleanProperty focusTraversable;
public final void setFocusTraversable(boolean value) {
focusTraversableProperty().set(value);
}
public final boolean isFocusTraversable() {
return focusTraversable == null ? false : focusTraversable.get();
}
public final BooleanProperty focusTraversableProperty() {
if (focusTraversable == null) {
focusTraversable = new StyleableBooleanProperty(false) {
@Override
public void invalidated() {
Scene _scene = getScene();
if (_scene != null) {
if (get()) {
_scene.initializeInternalEventDispatcher();
}
focusSetDirty(_scene);
}
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.FOCUS_TRAVERSABLE;
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "focusTraversable";
}
};
}
return focusTraversable;
}
Called when something has changed on this node that *may* have made the
scene's focus dirty. This covers the cases where this node is the focus
owner and it may have lost eligibility, or it's traversable and it may
have gained eligibility. Note that we do not want to use disabled
or treeVisible here, as this function is called from their
"on invalidate" triggers, and using them will cause them to be
revalidated. The pulse will revalidate everything and make the final
determination.
/**
* Called when something has changed on this node that *may* have made the
* scene's focus dirty. This covers the cases where this node is the focus
* owner and it may have lost eligibility, or it's traversable and it may
* have gained eligibility. Note that we do not want to use disabled
* or treeVisible here, as this function is called from their
* "on invalidate" triggers, and using them will cause them to be
* revalidated. The pulse will revalidate everything and make the final
* determination.
*/
private void focusSetDirty(Scene s) {
if (s != null &&
(this == s.getFocusOwner() || isFocusTraversable())) {
s.setFocusDirty(true);
}
}
Requests that this Node
get the input focus, and that this Node
's top-level ancestor become the focused window. To be eligible to receive the focus, the node must be part of a scene, it and all of its ancestors must be visible, and it must not be disabled. If this node is eligible, this function will cause it to become this Scene
's "focus owner". Each scene has at most one focus owner node. The focus owner will not actually have the input focus, however, unless the scene belongs to a Stage
that is both visible and active. /**
* Requests that this {@code Node} get the input focus, and that this
* {@code Node}'s top-level ancestor become the focused window. To be
* eligible to receive the focus, the node must be part of a scene, it and
* all of its ancestors must be visible, and it must not be disabled.
* If this node is eligible, this function will cause it to become this
* {@code Scene}'s "focus owner". Each scene has at most one focus owner
* node. The focus owner will not actually have the input focus, however,
* unless the scene belongs to a {@code Stage} that is both visible
* and active.
*/
public void requestFocus() {
if (getScene() != null) {
getScene().requestFocus(this);
}
}
Traverses from this node in the direction indicated. Note that this
node need not actually have the focus, nor need it be focusTraversable.
However, the node must be part of a scene, otherwise this request
is ignored.
/**
* Traverses from this node in the direction indicated. Note that this
* node need not actually have the focus, nor need it be focusTraversable.
* However, the node must be part of a scene, otherwise this request
* is ignored.
*/
final boolean traverse(Direction dir) {
if (getScene() == null) {
return false;
}
return getScene().traverse(this, dir);
}
////////////////////////////
// Private Implementation
////////////////////////////
Returns a string representation for the object.
Returns: a string representation for the object.
/**
* Returns a string representation for the object.
* @return a string representation for the object.
*/
@Override
public String toString() {
String klassName = getClass().getName();
String simpleName = klassName.substring(klassName.lastIndexOf('.')+1);
StringBuilder sbuf = new StringBuilder(simpleName);
boolean hasId = id != null && !"".equals(getId());
boolean hasStyleClass = !getStyleClass().isEmpty();
if (!hasId) {
sbuf.append('@');
sbuf.append(Integer.toHexString(hashCode()));
} else {
sbuf.append("[id=");
sbuf.append(getId());
if (!hasStyleClass) sbuf.append("]");
}
if (hasStyleClass) {
if (!hasId) sbuf.append('[');
else sbuf.append(", ");
sbuf.append("styleClass=");
sbuf.append(getStyleClass());
sbuf.append("]");
}
return sbuf.toString();
}
private void preprocessMouseEvent(MouseEvent e) {
final EventType<?> eventType = e.getEventType();
if (eventType == MouseEvent.MOUSE_PRESSED) {
for (Node n = this; n != null; n = n.getParent()) {
n.setPressed(e.isPrimaryButtonDown());
}
return;
}
if (eventType == MouseEvent.MOUSE_RELEASED) {
for (Node n = this; n != null; n = n.getParent()) {
n.setPressed(e.isPrimaryButtonDown());
}
return;
}
if (e.getTarget() == this) {
// the mouse event types are translated only when the node uses
// its internal event dispatcher, so both entered / exited variants
// are possible here
if ((eventType == MouseEvent.MOUSE_ENTERED)
|| (eventType == MouseEvent.MOUSE_ENTERED_TARGET)) {
setHover(true);
return;
}
if ((eventType == MouseEvent.MOUSE_EXITED)
|| (eventType == MouseEvent.MOUSE_EXITED_TARGET)) {
setHover(false);
return;
}
}
}
void markDirtyLayoutBranch() {
Parent p = getParent();
while (p != null && p.layoutFlag == LayoutFlags.CLEAN) {
p.setLayoutFlag(LayoutFlags.DIRTY_BRANCH);
if (p.isSceneRoot()) {
Toolkit.getToolkit().requestNextPulse();
if (getSubScene() != null) {
getSubScene().setDirtyLayout(p);
}
}
p = p.getParent();
}
}
private boolean isWindowShowing() {
Scene s = getScene();
if (s == null) return false;
Window w = s.getWindow();
return w != null && w.isShowing();
}
private void updateTreeShowing() {
setTreeShowing(isTreeVisible() && isWindowShowing());
}
private boolean treeShowing;
private TreeShowingPropertyReadOnly treeShowingRO;
final void setTreeShowing(boolean value) {
if (treeShowing != value) {
treeShowing = value;
((TreeShowingPropertyReadOnly) treeShowingProperty()).invalidate();
}
}
final boolean isTreeShowing() {
return treeShowingProperty().get();
}
final BooleanExpression treeShowingProperty() {
if (treeShowingRO == null) {
treeShowingRO = new TreeShowingPropertyReadOnly();
}
return treeShowingRO;
}
class TreeShowingPropertyReadOnly extends BooleanExpression {
private ExpressionHelper<Boolean> helper;
private boolean valid;
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener<? super Boolean> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener<? super Boolean> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
protected void invalidate() {
if (valid) {
valid = false;
ExpressionHelper.fireValueChangedEvent(helper);
}
}
@Override
public boolean get() {
valid = true;
return Node.this.treeShowing;
}
}
private void updateTreeVisible(boolean parentChanged) {
boolean isTreeVisible = isVisible();
final Node parentNode = getParent() != null ? getParent() :
clipParent != null ? clipParent :
getSubScene() != null ? getSubScene() : null;
if (isTreeVisible) {
isTreeVisible = parentNode == null || parentNode.isTreeVisible();
}
// When the parent has changed to visible and we have unsynchronized visibility,
// we have to synchronize, because the rendering will now pass through the newly-visible parent
// Otherwise an invisible Node might get rendered
if (parentChanged && parentNode != null && parentNode.isTreeVisible()
&& isDirty(DirtyBits.NODE_VISIBLE)) {
addToSceneDirtyList();
}
setTreeVisible(isTreeVisible);
updateTreeShowing();
}
private boolean treeVisible;
private TreeVisiblePropertyReadOnly treeVisibleRO;
final void setTreeVisible(boolean value) {
if (treeVisible != value) {
treeVisible = value;
updateCanReceiveFocus();
focusSetDirty(getScene());
if (getClip() != null) {
getClip().updateTreeVisible(true);
}
if (treeVisible && !isDirtyEmpty()) {
addToSceneDirtyList();
}
((TreeVisiblePropertyReadOnly) treeVisibleProperty()).invalidate();
if (Node.this instanceof SubScene) {
Node subSceneRoot = ((SubScene)Node.this).getRoot();
if (subSceneRoot != null) {
// SubScene.getRoot() is only null if it's constructor
// has not finished.
subSceneRoot.setTreeVisible(value && subSceneRoot.isVisible());
}
}
}
}
final boolean isTreeVisible() {
return treeVisibleProperty().get();
}
final BooleanExpression treeVisibleProperty() {
if (treeVisibleRO == null) {
treeVisibleRO = new TreeVisiblePropertyReadOnly();
}
return treeVisibleRO;
}
class TreeVisiblePropertyReadOnly extends BooleanExpression {
private ExpressionHelper<Boolean> helper;
private boolean valid;
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener<? super Boolean> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener<? super Boolean> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
protected void invalidate() {
if (valid) {
valid = false;
ExpressionHelper.fireValueChangedEvent(helper);
}
}
@Override
public boolean get() {
valid = true;
return Node.this.treeVisible;
}
}
private boolean canReceiveFocus = false;
private void setCanReceiveFocus(boolean value) {
canReceiveFocus = value;
}
final boolean isCanReceiveFocus() {
return canReceiveFocus;
}
private void updateCanReceiveFocus() {
setCanReceiveFocus(getScene() != null
&& !isDisabled()
&& isTreeVisible());
}
// for indenting messages based on scene-graph depth
String indent() {
String indent = "";
Parent p = this.getParent();
while (p != null) {
indent += " ";
p = p.getParent();
}
return indent;
}
/*
* Should we underline the mnemonic character?
*/
private BooleanProperty showMnemonics;
final void setShowMnemonics(boolean value) {
showMnemonicsProperty().set(value);
}
final boolean isShowMnemonics() {
return showMnemonics == null ? false : showMnemonics.get();
}
final BooleanProperty showMnemonicsProperty() {
if (showMnemonics == null) {
showMnemonics = new BooleanPropertyBase(false) {
@Override
protected void invalidated() {
pseudoClassStateChanged(SHOW_MNEMONICS_PSEUDOCLASS_STATE, get());
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "showMnemonics";
}
};
}
return showMnemonics;
}
References a node that is a labelFor this node.
Accessible via a NodeAccessor. See Label.labelFor for details.
/**
* References a node that is a labelFor this node.
* Accessible via a NodeAccessor. See Label.labelFor for details.
*/
private Node labeledBy = null;
/***************************************************************************
* *
* Event Dispatch *
* *
**************************************************************************/
// PENDING_DOC_REVIEW
Specifies the event dispatcher for this node. The default event dispatcher sends the received events to the registered event handlers and filters. When replacing the value with a new EventDispatcher
, the new dispatcher should forward events to the replaced dispatcher to maintain the node's default event handling behavior. /**
* Specifies the event dispatcher for this node. The default event
* dispatcher sends the received events to the registered event handlers and
* filters. When replacing the value with a new {@code EventDispatcher},
* the new dispatcher should forward events to the replaced dispatcher
* to maintain the node's default event handling behavior.
*/
private ObjectProperty<EventDispatcher> eventDispatcher;
public final void setEventDispatcher(EventDispatcher value) {
eventDispatcherProperty().set(value);
}
public final EventDispatcher getEventDispatcher() {
return eventDispatcherProperty().get();
}
public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
initializeInternalEventDispatcher();
return eventDispatcher;
}
private NodeEventDispatcher internalEventDispatcher;
// PENDING_DOC_REVIEW
Registers an event handler to this node. The handler is called when the node receives an Event
of the specified type during the bubbling phase of event delivery. Params: - eventType – the type of the events to receive by the handler
- eventHandler – the handler to register
Type parameters: - <T> – the specific event class of the handler
Throws: - NullPointerException – if the event type or handler is null
/**
* Registers an event handler to this node. The handler is called when the
* node receives an {@code Event} of the specified type during the bubbling
* phase of event delivery.
*
* @param <T> the specific event class of the handler
* @param eventType the type of the events to receive by the handler
* @param eventHandler the handler to register
* @throws NullPointerException if the event type or handler is null
*/
public final <T extends Event> void addEventHandler(
final EventType<T> eventType,
final EventHandler<? super T> eventHandler) {
getInternalEventDispatcher().getEventHandlerManager()
.addEventHandler(eventType, eventHandler);
}
// PENDING_DOC_REVIEW
Unregisters a previously registered event handler from this node. One
handler might have been registered for different event types, so the
caller needs to specify the particular event type from which to
unregister the handler.
Params: - eventType – the event type from which to unregister
- eventHandler – the handler to unregister
Type parameters: - <T> – the specific event class of the handler
Throws: - NullPointerException – if the event type or handler is null
/**
* Unregisters a previously registered event handler from this node. One
* handler might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the handler.
*
* @param <T> the specific event class of the handler
* @param eventType the event type from which to unregister
* @param eventHandler the handler to unregister
* @throws NullPointerException if the event type or handler is null
*/
public final <T extends Event> void removeEventHandler(
final EventType<T> eventType,
final EventHandler<? super T> eventHandler) {
getInternalEventDispatcher()
.getEventHandlerManager()
.removeEventHandler(eventType, eventHandler);
}
// PENDING_DOC_REVIEW
Registers an event filter to this node. The filter is called when the node receives an Event
of the specified type during the capturing phase of event delivery. Params: - eventType – the type of the events to receive by the filter
- eventFilter – the filter to register
Type parameters: - <T> – the specific event class of the filter
Throws: - NullPointerException – if the event type or filter is null
/**
* Registers an event filter to this node. The filter is called when the
* node receives an {@code Event} of the specified type during the capturing
* phase of event delivery.
*
* @param <T> the specific event class of the filter
* @param eventType the type of the events to receive by the filter
* @param eventFilter the filter to register
* @throws NullPointerException if the event type or filter is null
*/
public final <T extends Event> void addEventFilter(
final EventType<T> eventType,
final EventHandler<? super T> eventFilter) {
getInternalEventDispatcher().getEventHandlerManager()
.addEventFilter(eventType, eventFilter);
}
// PENDING_DOC_REVIEW
Unregisters a previously registered event filter from this node. One
filter might have been registered for different event types, so the
caller needs to specify the particular event type from which to
unregister the filter.
Params: - eventType – the event type from which to unregister
- eventFilter – the filter to unregister
Type parameters: - <T> – the specific event class of the filter
Throws: - NullPointerException – if the event type or filter is null
/**
* Unregisters a previously registered event filter from this node. One
* filter might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the filter.
*
* @param <T> the specific event class of the filter
* @param eventType the event type from which to unregister
* @param eventFilter the filter to unregister
* @throws NullPointerException if the event type or filter is null
*/
public final <T extends Event> void removeEventFilter(
final EventType<T> eventType,
final EventHandler<? super T> eventFilter) {
getInternalEventDispatcher().getEventHandlerManager()
.removeEventFilter(eventType, eventFilter);
}
Sets the handler to use for this event type. There can only be one such handler specified at a time. This handler is guaranteed to be called as the last, after handlers added using addEventHandler(EventType<Event>, EventHandler<? super Event>)
. This is used for registering the user-defined onFoo event handlers. Params: - eventType – the event type to associate with the given eventHandler
- eventHandler – the handler to register, or null to unregister
Type parameters: - <T> – the specific event class of the handler
Throws: - NullPointerException – if the event type is null
/**
* Sets the handler to use for this event type. There can only be one such handler
* specified at a time. This handler is guaranteed to be called as the last, after
* handlers added using {@link #addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}.
* This is used for registering the user-defined onFoo event handlers.
*
* @param <T> the specific event class of the handler
* @param eventType the event type to associate with the given eventHandler
* @param eventHandler the handler to register, or null to unregister
* @throws NullPointerException if the event type is null
*/
protected final <T extends Event> void setEventHandler(
final EventType<T> eventType,
final EventHandler<? super T> eventHandler) {
getInternalEventDispatcher().getEventHandlerManager()
.setEventHandler(eventType, eventHandler);
}
private NodeEventDispatcher getInternalEventDispatcher() {
initializeInternalEventDispatcher();
return internalEventDispatcher;
}
private void initializeInternalEventDispatcher() {
if (internalEventDispatcher == null) {
internalEventDispatcher = createInternalEventDispatcher();
eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
Node.this,
"eventDispatcher",
internalEventDispatcher);
}
}
private NodeEventDispatcher createInternalEventDispatcher() {
return new NodeEventDispatcher(this);
}
Event dispatcher for invoking preprocessing of mouse events
/**
* Event dispatcher for invoking preprocessing of mouse events
*/
private EventDispatcher preprocessMouseEventDispatcher;
// PENDING_DOC_REVIEW
Construct an event dispatch chain for this node. The event dispatch chain
contains all event dispatchers from the stage to this node.
Params: - tail – the initial chain to build from
Returns: the resulting event dispatch chain for this node
/**
* Construct an event dispatch chain for this node. The event dispatch chain
* contains all event dispatchers from the stage to this node.
*
* @param tail the initial chain to build from
* @return the resulting event dispatch chain for this node
*/
@Override
public EventDispatchChain buildEventDispatchChain(
EventDispatchChain tail) {
if (preprocessMouseEventDispatcher == null) {
preprocessMouseEventDispatcher = (event, tail1) -> {
event = tail1.dispatchEvent(event);
if (event instanceof MouseEvent) {
preprocessMouseEvent((MouseEvent) event);
}
return event;
};
}
tail = tail.prepend(preprocessMouseEventDispatcher);
// prepend all event dispatchers from this node to the root
Node curNode = this;
do {
if (curNode.eventDispatcher != null) {
final EventDispatcher eventDispatcherValue =
curNode.eventDispatcher.get();
if (eventDispatcherValue != null) {
tail = tail.prepend(eventDispatcherValue);
}
}
final Node curParent = curNode.getParent();
curNode = curParent != null ? curParent : curNode.getSubScene();
} while (curNode != null);
if (getScene() != null) {
// prepend scene's dispatch chain
tail = getScene().buildEventDispatchChain(tail);
}
return tail;
}
// PENDING_DOC_REVIEW
Fires the specified event. By default the event will travel through the
hierarchy from the stage to this node. Any event filter encountered will
be notified and can consume the event. If not consumed by the filters,
the event handlers on this node are notified. If these don't consume the
event either, the event will travel back the same path it arrived to
this node. All event handlers encountered are called and can consume the
event.
This method must be called on the FX user thread.
Params: - event – the event to fire
/**
* Fires the specified event. By default the event will travel through the
* hierarchy from the stage to this node. Any event filter encountered will
* be notified and can consume the event. If not consumed by the filters,
* the event handlers on this node are notified. If these don't consume the
* event either, the event will travel back the same path it arrived to
* this node. All event handlers encountered are called and can consume the
* event.
* <p>
* This method must be called on the FX user thread.
*
* @param event the event to fire
*/
public final void fireEvent(Event event) {
/* Log input events. We do a coarse filter for at least the FINE
* level and then granularize from there.
*/
if (event instanceof InputEvent) {
PlatformLogger logger = Logging.getInputLogger();
if (logger.isLoggable(Level.FINE)) {
EventType eventType = event.getEventType();
if (eventType == MouseEvent.MOUSE_ENTERED ||
eventType == MouseEvent.MOUSE_EXITED) {
logger.finer(event.toString());
} else if (eventType == MouseEvent.MOUSE_MOVED ||
eventType == MouseEvent.MOUSE_DRAGGED) {
logger.finest(event.toString());
} else {
logger.fine(event.toString());
}
}
}
Event.fireEvent(this, event);
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
{@inheritDoc}
Returns: getClass().getName()
without the package nameSince: JavaFX 8.0
/**
* {@inheritDoc}
* @return {@code getClass().getName()} without the package name
* @since JavaFX 8.0
*/
@Override
public String getTypeSelector() {
final Class<?> clazz = getClass();
final Package pkg = clazz.getPackage();
// package could be null. not likely, but could be.
int plen = 0;
if (pkg != null) {
plen = pkg.getName().length();
}
final int clen = clazz.getName().length();
final int pos = (0 < plen && plen < clen) ? plen + 1 : 0;
return clazz.getName().substring(pos);
}
{@inheritDoc}
Returns: getParent()
Since: JavaFX 8.0
/**
* {@inheritDoc}
* @return {@code getParent()}
* @since JavaFX 8.0
*/
@Override
public Styleable getStyleableParent() {
return getParent();
}
Returns the initial focus traversable state of this node, for use
by the JavaFX CSS engine to correctly set its initial value. This method
can be overridden by subclasses in instances where focus traversable should
initially be true (as the default implementation of this method is to return
false).
Returns: the initial focus traversable state for this Node
. Since: 9
/**
* Returns the initial focus traversable state of this node, for use
* by the JavaFX CSS engine to correctly set its initial value. This method
* can be overridden by subclasses in instances where focus traversable should
* initially be true (as the default implementation of this method is to return
* false).
*
* @return the initial focus traversable state for this {@code Node}.
* @since 9
*/
protected Boolean getInitialFocusTraversable() {
return Boolean.FALSE;
}
Returns the initial cursor state of this node, for use
by the JavaFX CSS engine to correctly set its initial value. This method
can be overridden by subclasses in instances where the cursor should
initially be non-null (as the default implementation of this method is to return
null).
Returns: the initial cursor state for this Node
. Since: 9
/**
* Returns the initial cursor state of this node, for use
* by the JavaFX CSS engine to correctly set its initial value. This method
* can be overridden by subclasses in instances where the cursor should
* initially be non-null (as the default implementation of this method is to return
* null).
*
* @return the initial cursor state for this {@code Node}.
* @since 9
*/
protected Cursor getInitialCursor() {
return null;
}
Super-lazy instantiation pattern from Bill Pugh.
/**
* Super-lazy instantiation pattern from Bill Pugh.
*/
private static class StyleableProperties {
private static final CssMetaData<Node,Cursor> CURSOR =
new CssMetaData<Node,Cursor>("-fx-cursor", CursorConverter.getInstance()) {
@Override
public boolean isSettable(Node node) {
return node.miscProperties == null || node.miscProperties.canSetCursor();
}
@Override
public StyleableProperty<Cursor> getStyleableProperty(Node node) {
return (StyleableProperty<Cursor>)node.cursorProperty();
}
@Override
public Cursor getInitialValue(Node node) {
// Most controls default focusTraversable to true.
// Give a way to have them return the correct default value.
return node.getInitialCursor();
}
};
private static final CssMetaData<Node,Effect> EFFECT =
new CssMetaData<Node,Effect>("-fx-effect", EffectConverter.getInstance()) {
@Override
public boolean isSettable(Node node) {
return node.miscProperties == null || node.miscProperties.canSetEffect();
}
@Override
public StyleableProperty<Effect> getStyleableProperty(Node node) {
return (StyleableProperty<Effect>)node.effectProperty();
}
};
private static final CssMetaData<Node,Boolean> FOCUS_TRAVERSABLE =
new CssMetaData<Node,Boolean>("-fx-focus-traversable",
BooleanConverter.getInstance(), Boolean.FALSE) {
@Override
public boolean isSettable(Node node) {
return node.focusTraversable == null || !node.focusTraversable.isBound();
}
@Override
public StyleableProperty<Boolean> getStyleableProperty(Node node) {
return (StyleableProperty<Boolean>)node.focusTraversableProperty();
}
@Override
public Boolean getInitialValue(Node node) {
// Most controls default focusTraversable to true.
// Give a way to have them return the correct default value.
return node.getInitialFocusTraversable();
}
};
private static final CssMetaData<Node,Number> OPACITY =
new CssMetaData<Node,Number>("-fx-opacity",
SizeConverter.getInstance(), 1.0) {
@Override
public boolean isSettable(Node node) {
return node.opacity == null || !node.opacity.isBound();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.opacityProperty();
}
};
private static final CssMetaData<Node,BlendMode> BLEND_MODE =
new CssMetaData<Node,BlendMode>("-fx-blend-mode", new EnumConverter<BlendMode>(BlendMode.class)) {
@Override
public boolean isSettable(Node node) {
return node.blendMode == null || !node.blendMode.isBound();
}
@Override
public StyleableProperty<BlendMode> getStyleableProperty(Node node) {
return (StyleableProperty<BlendMode>)node.blendModeProperty();
}
};
private static final CssMetaData<Node,Number> ROTATE =
new CssMetaData<Node,Number>("-fx-rotate",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.rotate == null
|| node.nodeTransformation.canSetRotate();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.rotateProperty();
}
};
private static final CssMetaData<Node,Number> SCALE_X =
new CssMetaData<Node,Number>("-fx-scale-x",
SizeConverter.getInstance(), 1.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.scaleX == null
|| node.nodeTransformation.canSetScaleX();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.scaleXProperty();
}
};
private static final CssMetaData<Node,Number> SCALE_Y =
new CssMetaData<Node,Number>("-fx-scale-y",
SizeConverter.getInstance(), 1.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.scaleY == null
|| node.nodeTransformation.canSetScaleY();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.scaleYProperty();
}
};
private static final CssMetaData<Node,Number> SCALE_Z =
new CssMetaData<Node,Number>("-fx-scale-z",
SizeConverter.getInstance(), 1.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.scaleZ == null
|| node.nodeTransformation.canSetScaleZ();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.scaleZProperty();
}
};
private static final CssMetaData<Node,Number> TRANSLATE_X =
new CssMetaData<Node,Number>("-fx-translate-x",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.translateX == null
|| node.nodeTransformation.canSetTranslateX();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.translateXProperty();
}
};
private static final CssMetaData<Node,Number> TRANSLATE_Y =
new CssMetaData<Node,Number>("-fx-translate-y",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.translateY == null
|| node.nodeTransformation.canSetTranslateY();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.translateYProperty();
}
};
private static final CssMetaData<Node,Number> TRANSLATE_Z =
new CssMetaData<Node,Number>("-fx-translate-z",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Node node) {
return node.nodeTransformation == null
|| node.nodeTransformation.translateZ == null
|| node.nodeTransformation.canSetTranslateZ();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>)node.translateZProperty();
}
};
private static final CssMetaData<Node, Number> VIEW_ORDER
= new CssMetaData<Node, Number>("-fx-view-order",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(Node node) {
return node.miscProperties == null
|| node.miscProperties.viewOrder == null
|| !node.miscProperties.viewOrder.isBound();
}
@Override
public StyleableProperty<Number> getStyleableProperty(Node node) {
return (StyleableProperty<Number>) node.viewOrderProperty();
}
};
private static final CssMetaData<Node,Boolean> VISIBILITY =
new CssMetaData<Node,Boolean>("visibility",
new StyleConverter<String,Boolean>() {
@Override
// [ visible | hidden | collapse | inherit ]
public Boolean convert(ParsedValue<String, Boolean> value, Font font) {
final String sval = value != null ? value.getValue() : null;
return "visible".equalsIgnoreCase(sval);
}
},
Boolean.TRUE) {
@Override
public boolean isSettable(Node node) {
return node.visible == null || !node.visible.isBound();
}
@Override
public StyleableProperty<Boolean> getStyleableProperty(Node node) {
return (StyleableProperty<Boolean>)node.visibleProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables =
new ArrayList<CssMetaData<? extends Styleable, ?>>();
styleables.add(CURSOR);
styleables.add(EFFECT);
styleables.add(FOCUS_TRAVERSABLE);
styleables.add(OPACITY);
styleables.add(BLEND_MODE);
styleables.add(ROTATE);
styleables.add(SCALE_X);
styleables.add(SCALE_Y);
styleables.add(SCALE_Z);
styleables.add(VIEW_ORDER);
styleables.add(TRANSLATE_X);
styleables.add(TRANSLATE_Y);
styleables.add(TRANSLATE_Z);
styleables.add(VISIBILITY);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
Returns: The CssMetaData associated with this class, which may include the
CssMetaData of its superclasses. Since: JavaFX 8.0
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
* @since JavaFX 8.0
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
//
// Super-lazy instantiation pattern from Bill Pugh. StyleableProperties
// is referenced no earlier (and therefore loaded no earlier by the
// class loader) than the moment that getClassCssMetaData() is called.
// This avoids loading the CssMetaData instances until the point at
// which CSS needs the data.
//
return StyleableProperties.STYLEABLES;
}
This method should delegate to getClassCssMetaData()
so that a Node's CssMetaData can be accessed without the need for reflection. Returns: The CssMetaData associated with this node, which may include the
CssMetaData of its superclasses. Since: JavaFX 8.0
/**
* This method should delegate to {@link Node#getClassCssMetaData()} so that
* a Node's CssMetaData can be accessed without the need for reflection.
*
* @return The CssMetaData associated with this node, which may include the
* CssMetaData of its superclasses.
* @since JavaFX 8.0
*/
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
/*
* @return The Styles that match this CSS property for the given Node. The
* list is sorted by descending specificity.
*/
// SB-dependency: RT-21096 has been filed to track this
static List<Style> getMatchingStyles(CssMetaData cssMetaData, Styleable styleable) {
return CssStyleHelper.getMatchingStyles(styleable, cssMetaData);
}
final ObservableMap<StyleableProperty<?>, List<Style>> getStyleMap() {
ObservableMap<StyleableProperty<?>, List<Style>> map =
(ObservableMap<StyleableProperty<?>, List<Style>>)getProperties().get("STYLEMAP");
Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(map, this);
if (ret != null) {
if (ret instanceof ObservableMap) return (ObservableMap)ret;
return FXCollections.observableMap(ret);
}
return FXCollections.<StyleableProperty<?>, List<Style>>emptyObservableMap();
}
/*
* RT-17293
*/
// SB-dependency: RT-21096 has been filed to track this
final void setStyleMap(ObservableMap<StyleableProperty<?>, List<Style>> styleMap) {
if (styleMap != null) getProperties().put("STYLEMAP", styleMap);
else getProperties().remove("STYLEMAP");
}
/*
* Find CSS styles that were used to style this Node in its current pseudo-class state. The map will contain the styles from this node and,
* if the node is a Parent, its children. The node corresponding to an entry in the Map can be obtained by casting a StyleableProperty key to a
* javafx.beans.property.Property and calling getBean(). The List contains only those styles used to style the property and will contain
* styles used to resolve lookup values.
*
* @param styleMap A Map to be populated with the styles. If null, a new Map will be allocated.
* @return The Map populated with matching styles.
*/
// SB-dependency: RT-21096 has been filed to track this
Map<StyleableProperty<?>,List<Style>> findStyles(Map<StyleableProperty<?>,List<Style>> styleMap) {
Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(styleMap, this);
return (ret != null) ? ret : Collections.<StyleableProperty<?>, List<Style>>emptyMap();
}
Flags used to indicate in which way this node is dirty (or whether it
is clean) and what must happen during the next CSS cycle on the
scenegraph.
/**
* Flags used to indicate in which way this node is dirty (or whether it
* is clean) and what must happen during the next CSS cycle on the
* scenegraph.
*/
CssFlags cssFlag = CssFlags.CLEAN;
Needed for testing.
/**
* Needed for testing.
*/
final CssFlags getCSSFlags() { return cssFlag; }
Called when a CSS pseudo-class change would cause styles to be reapplied.
/**
* Called when a CSS pseudo-class change would cause styles to be reapplied.
*/
private void requestCssStateTransition() {
// If there is no scene, then we cannot make it dirty, so we'll leave
// the flag alone
if (getScene() == null) return;
// Don't bother doing anything if the cssFlag is not CLEAN.
// If the flag indicates a DIRTY_BRANCH, the flag needs to be changed
// to UPDATE to ensure that NodeHelper.processCSS is called on the node.
if (cssFlag == CssFlags.CLEAN || cssFlag == CssFlags.DIRTY_BRANCH) {
cssFlag = CssFlags.UPDATE;
notifyParentsOfInvalidatedCSS();
}
}
Used to specify that a pseudo-class of this Node has changed. If the pseudo-class is used in a CSS selector that matches this Node, CSS will be reapplied. Typically, this method is called from the invalidated
method of a property that is used as a pseudo-class. For example: private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state"); BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) { @Override public void invalidated() { pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get()); } @Override public Object getBean() { return MyControl.this; } @Override public String getName() { return "myPseudoClassState"; } };
Params: - pseudoClass – the pseudo-class that has changed state
- active – whether or not the state is active
Since: JavaFX 8.0
/**
* Used to specify that a pseudo-class of this Node has changed. If the
* pseudo-class is used in a CSS selector that matches this Node, CSS will
* be reapplied. Typically, this method is called from the {@code invalidated}
* method of a property that is used as a pseudo-class. For example:
* <pre><code>
*
* private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state");
*
* BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) {
*
* {@literal @}Override public void invalidated() {
* pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get());
* }
*
* {@literal @}Override public Object getBean() {
* return MyControl.this;
* }
*
* {@literal @}Override public String getName() {
* return "myPseudoClassState";
* }
* };
* </code></pre>
* @param pseudoClass the pseudo-class that has changed state
* @param active whether or not the state is active
* @since JavaFX 8.0
*/
public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) {
final boolean modified = active
? pseudoClassStates.add(pseudoClass)
: pseudoClassStates.remove(pseudoClass);
if (modified && styleHelper != null) {
final boolean isTransition = styleHelper.pseudoClassStateChanged(pseudoClass);
if (isTransition) {
requestCssStateTransition();
}
}
}
// package so that StyleHelper can get at it
final ObservableSet<PseudoClass> pseudoClassStates = new PseudoClassState();
Returns: The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet Since: JavaFX 8.0
/**
* @return The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet
* @since JavaFX 8.0
*/
public final ObservableSet<PseudoClass> getPseudoClassStates() {
return FXCollections.unmodifiableObservableSet(pseudoClassStates);
}
// Walks up the tree telling each parent that the pseudo class state of
// this node has changed.
final void notifyParentsOfInvalidatedCSS() {
SubScene subScene = getSubScene();
Parent root = (subScene != null) ?
subScene.getRoot() : getScene().getRoot();
if (!root.isDirty(DirtyBits.NODE_CSS)) {
// Ensure that Scene.root is marked as dirty. If the scene isn't
// dirty, nothing will get repainted. This bit is cleared from
// Scene in doCSSPass().
NodeHelper.markDirty(root, DirtyBits.NODE_CSS);
if (subScene != null) {
// If the node is part of a subscene, then we must ensure that
// the we not only mark subScene.root dirty, but continue and
// call subScene.notifyParentsOfInvalidatedCSS() until
// Scene.root gets marked dirty, via the recursive call:
subScene.cssFlag = CssFlags.UPDATE;
subScene.notifyParentsOfInvalidatedCSS();
}
}
Parent _parent = getParent();
while (_parent != null) {
if (_parent.cssFlag == CssFlags.CLEAN) {
_parent.cssFlag = CssFlags.DIRTY_BRANCH;
_parent = _parent.getParent();
} else {
_parent = null;
}
}
}
final void reapplyCSS() {
if (getScene() == null) return;
if (cssFlag == CssFlags.REAPPLY) return;
// RT-36838 - don't reapply CSS in the middle of an update
if (cssFlag == CssFlags.UPDATE) {
cssFlag = CssFlags.REAPPLY;
notifyParentsOfInvalidatedCSS();
return;
}
reapplyCss();
//
// One idiom employed by developers is to, during the layout pass,
// add or remove nodes from the scene. For example, a ScrollPane
// might add scroll bars to itself if it determines during layout
// that it needs them, or a ListView might add cells to itself if
// it determines that it needs to. In such situations we must
// apply the CSS immediately and not add it to the scene's queue
// for deferred action.
//
if (getParent() != null && getParent().isPerformingLayout()) {
NodeHelper.processCSS(this);
} else {
notifyParentsOfInvalidatedCSS();
}
}
//
// This method "reapplies" CSS to this node and all of its children. Reapplying CSS
// means that new style maps are calculated for the node. The process of reapplying
// CSS may reset the CSS properties of a node to their initial state, but the _new_
// styles are not applied as part of this process.
//
// There is no check of the CSS state of a child since reapply takes precedence
// over other CSS states.
//
private void reapplyCss() {
// Hang on to current styleHelper so we can know whether
// createStyleHelper returned the same styleHelper
final CssStyleHelper oldStyleHelper = styleHelper;
// CSS state is "REAPPLY"
cssFlag = CssFlags.REAPPLY;
styleHelper = CssStyleHelper.createStyleHelper(this);
// REAPPLY to my children, too.
if (this instanceof Parent) {
// minor optimization to avoid calling createStyleHelper on children
// when we know there will not be any change in the style maps.
final boolean visitChildren =
// If we don't have a styleHelper, then we should visit the children of this parent
// since there might be styles that depend on being a child of this parent.
// In other words, we have .a > .b { blah: blort; }, but no styles for ".a" itself.
styleHelper == null ||
// if the styleHelper changed, then we definitely need to visit the children
// since the new styles may have an effect on the children's styles calculated values.
(oldStyleHelper != styleHelper) ||
// If our parent is null, then we're the root of a scene or sub-scene, most likely,
// and we'll visit children because elsewhere the code depends on root.reapplyCSS()
// to force css to be reapplied (whether it needs to be or not).
(getParent() == null) ||
// If our parent's cssFlag is other than clean, then the parent may have just had
// CSS reapplied. If the parent just had CSS reapplied, then some of its styles
// may affect my children's styles.
(getParent().cssFlag != CssFlags.CLEAN);
if (visitChildren) {
List<Node> children = ((Parent) this).getChildren();
for (int n = 0, nMax = children.size(); n < nMax; n++) {
Node child = children.get(n);
child.reapplyCss();
}
}
} else if (this instanceof SubScene) {
// SubScene root is a Parent, but reapplyCss is a private method in Node
final Node subSceneRoot = ((SubScene)this).getRoot();
if (subSceneRoot != null) {
subSceneRoot.reapplyCss();
}
} else if (styleHelper == null) {
//
// If this is not a Parent and there is no styleHelper, then the CSS state is "CLEAN"
// since there are no styles to apply or children to update.
//
cssFlag = CssFlags.CLEAN;
return;
}
cssFlag = CssFlags.UPDATE;
}
void processCSS() {
switch (cssFlag) {
case CLEAN:
break;
case DIRTY_BRANCH:
{
Parent me = (Parent)this;
// clear the flag first in case the flag is set to something
// other than clean by downstream processing.
me.cssFlag = CssFlags.CLEAN;
List<Node> children = me.getChildren();
for (int i=0, max=children.size(); i<max; i++) {
children.get(i).processCSS();
}
break;
}
case REAPPLY:
case UPDATE:
default:
NodeHelper.processCSS(this);
}
}
If required, apply styles to this Node and its children, if any. This method does not normally need to be invoked directly but may be used in conjunction with Parent.layout()
to size a Node before the next pulse, or if the Scene
is not in a Stage
. Provided that the Node's Scene
is not null, CSS is applied to this Node regardless of whether this Node's CSS state is clean. CSS styles are applied from the top-most parent of this Node whose CSS state is other than clean, which may affect the styling of other nodes. This method is a no-op if the Node is not in a Scene. The Scene does not have to be in a Stage.
This method does not invoke the Parent.layout()
method. Typically, the caller will use the following sequence of operations.
parentNode.applyCss();
parentNode.layout();
As a more complete example, the following code uses applyCss()
and layout()
to find the width and height of the Button before the Stage has been shown. If either the call to applyCss()
or the call to layout()
is commented out, the calls to getWidth()
and getHeight()
will return zero (until some time after the Stage is shown).
@Override public void start(Stage stage) throws Exception { Group root = new Group(); Scene scene = new Scene(root); Button button = new Button("Hello World"); root.getChildren().add(button); root.applyCss(); root.layout(); double width = button.getWidth(); double height = button.getHeight(); System.out.println(width + ", " + height); stage.setScene(scene); stage.show(); }
Since: JavaFX 8.0
/**
* If required, apply styles to this Node and its children, if any. This method does not normally need to
* be invoked directly but may be used in conjunction with {@link Parent#layout()} to size a Node before the
* next pulse, or if the {@link #getScene() Scene} is not in a {@link javafx.stage.Stage}.
* <p>Provided that the Node's {@link #getScene() Scene} is not null, CSS is applied to this Node regardless
* of whether this Node's CSS state is clean. CSS styles are applied from the top-most parent
* of this Node whose CSS state is other than clean, which may affect the styling of other nodes.
* This method is a no-op if the Node is not in a Scene. The Scene does not have to be in a Stage.</p>
* <p>This method does not invoke the {@link Parent#layout()} method. Typically, the caller will use the
* following sequence of operations.</p>
* <pre>{@code
* parentNode.applyCss();
* parentNode.layout();
* }</pre>
* <p>As a more complete example, the following code uses {@code applyCss()} and {@code layout()} to find
* the width and height of the Button before the Stage has been shown. If either the call to {@code applyCss()}
* or the call to {@code layout()} is commented out, the calls to {@code getWidth()} and {@code getHeight()}
* will return zero (until some time after the Stage is shown). </p>
* <pre><code>
* {@literal @}Override
* public void start(Stage stage) throws Exception {
*
* Group root = new Group();
* Scene scene = new Scene(root);
*
* Button button = new Button("Hello World");
* root.getChildren().add(button);
*
* root.applyCss();
* root.layout();
*
* double width = button.getWidth();
* double height = button.getHeight();
*
* System.out.println(width + ", " + height);
*
* stage.setScene(scene);
* stage.show();
* }
* </code></pre>
* @since JavaFX 8.0
*/
public final void applyCss() {
if (getScene() == null) {
return;
}
// update, unless reapply
if (cssFlag != CssFlags.REAPPLY) cssFlag = CssFlags.UPDATE;
//
// RT-28394 - need to see if any ancestor has a flag UPDATE
// If so, process css from the top-most CssFlags.UPDATE node
// since my ancestor's styles may affect mine.
//
// If the scene-graph root isn't NODE_CSS dirty, then all my
// ancestor flags should be CLEAN and I can skip this lookup.
//
Node topMost = this;
final boolean dirtyRoot = getScene().getRoot().isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
if (dirtyRoot) {
Node _parent = getParent();
while (_parent != null) {
if (_parent.cssFlag == CssFlags.UPDATE || _parent.cssFlag == CssFlags.REAPPLY) {
topMost = _parent;
}
_parent = _parent.getParent();
}
// Note: this code used to mark the parent nodes with DIRTY_BRANCH,
// but that isn't necessary since UPDATE will apply css to all of
// a Parent's children.
// If we're at the root of the scene-graph, make sure the NODE_CSS
// dirty bit is cleared (see Scene#doCSSPass())
if (topMost == getScene().getRoot()) {
getScene().getRoot().clearDirty(DirtyBits.NODE_CSS);
}
}
topMost.processCSS();
}
/*
* If invoked, will update styles from here on down. This method should not be called directly. If
* overridden, the overriding method must at some point call {@code super.processCSSImpl} to ensure that
* this Node's CSS state is properly updated.
*
* Note that the difference between this method and {@link #applyCss()} is that this method
* updates styles for this node on down; whereas, {@code applyCss()} looks for the top-most ancestor that needs
* CSS update and apply styles from that node on down.
*
* Note: This method MUST only be called via its accessor method.
*/
private void doProcessCSS() {
// Nothing to do...
if (cssFlag == CssFlags.CLEAN) return;
// if REAPPLY was deferred, process it now...
if (cssFlag == CssFlags.REAPPLY) {
reapplyCss();
}
// Clear the flag first in case the flag is set to something
// other than clean by downstream processing.
cssFlag = CssFlags.CLEAN;
// Transition to the new state and apply styles
if (styleHelper != null && getScene() != null) {
styleHelper.transitionToState(this);
}
}
A StyleHelper for this node.
A StyleHelper contains all the css styles for this node
and knows how to apply them when our state changes.
/**
* A StyleHelper for this node.
* A StyleHelper contains all the css styles for this node
* and knows how to apply them when our state changes.
*/
CssStyleHelper styleHelper;
private static final PseudoClass HOVER_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("hover");
private static final PseudoClass PRESSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("pressed");
private static final PseudoClass DISABLED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("disabled");
private static final PseudoClass FOCUSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("focused");
private static final PseudoClass SHOW_MNEMONICS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("show-mnemonics");
private static abstract class LazyTransformProperty
extends ReadOnlyObjectProperty<Transform> {
protected static final int VALID = 0;
protected static final int INVALID = 1;
protected static final int VALIDITY_UNKNOWN = 2;
protected int valid = INVALID;
private ExpressionHelper<Transform> helper;
private Transform transform;
private boolean canReuse = false;
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener<? super Transform> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener<? super Transform> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
protected Transform getInternalValue() {
if (valid == INVALID ||
(valid == VALIDITY_UNKNOWN && computeValidity() == INVALID)) {
transform = computeTransform(canReuse ? transform : null);
canReuse = true;
valid = validityKnown() ? VALID : VALIDITY_UNKNOWN;
}
return transform;
}
@Override
public Transform get() {
transform = getInternalValue();
canReuse = false;
return transform;
}
public void validityUnknown() {
if (valid == VALID) {
valid = VALIDITY_UNKNOWN;
}
}
public void invalidate() {
if (valid != INVALID) {
valid = INVALID;
ExpressionHelper.fireValueChangedEvent(helper);
}
}
protected abstract boolean validityKnown();
protected abstract int computeValidity();
protected abstract Transform computeTransform(Transform reuse);
}
private static abstract class LazyBoundsProperty
extends ReadOnlyObjectProperty<Bounds> {
private ExpressionHelper<Bounds> helper;
private boolean valid;
private Bounds bounds;
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener<? super Bounds> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener<? super Bounds> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public Bounds get() {
if (!valid) {
bounds = computeBounds();
valid = true;
}
return bounds;
}
public void invalidate() {
if (valid) {
valid = false;
ExpressionHelper.fireValueChangedEvent(helper);
}
}
protected abstract Bounds computeBounds();
}
private static final BoundsAccessor boundsAccessor = (bounds, tx, node) -> node.getGeomBounds(bounds, tx);
The accessible role for this Node
.
The screen reader uses the role of a node to determine the
attributes and actions that are supported.
See Also: @defaultValue AccessibleRole.NODE
Since: JavaFX 8u40
/**
* The accessible role for this {@code Node}.
* <p>
* The screen reader uses the role of a node to determine the
* attributes and actions that are supported.
*
* @defaultValue {@link AccessibleRole#NODE}
* @see AccessibleRole
*
* @since JavaFX 8u40
*/
private ObjectProperty<AccessibleRole> accessibleRole;
public final void setAccessibleRole(AccessibleRole value) {
if (value == null) value = AccessibleRole.NODE;
accessibleRoleProperty().set(value);
}
public final AccessibleRole getAccessibleRole() {
if (accessibleRole == null) return AccessibleRole.NODE;
return accessibleRoleProperty().get();
}
public final ObjectProperty<AccessibleRole> accessibleRoleProperty() {
if (accessibleRole == null) {
accessibleRole = new SimpleObjectProperty<AccessibleRole>(this, "accessibleRole", AccessibleRole.NODE);
}
return accessibleRole;
}
public final void setAccessibleRoleDescription(String value) {
accessibleRoleDescriptionProperty().set(value);
}
public final String getAccessibleRoleDescription() {
if (accessibilityProperties == null) return null;
if (accessibilityProperties.accessibleRoleDescription == null) return null;
return accessibleRoleDescriptionProperty().get();
}
The role description of this Node
.
Normally, when a role is provided for a node, the screen reader
speaks the role as well as the contents of the node. When this
value is set, it is possible to override the default. This is
useful because the set of roles is predefined. For example,
it is possible to set the role of a node to be a button, but
have the role description be arbitrary text.
Returns: the role description of this Node
. @defaultValue null Since: JavaFX 8u40
/**
* The role description of this {@code Node}.
* <p>
* Normally, when a role is provided for a node, the screen reader
* speaks the role as well as the contents of the node. When this
* value is set, it is possible to override the default. This is
* useful because the set of roles is predefined. For example,
* it is possible to set the role of a node to be a button, but
* have the role description be arbitrary text.
*
* @return the role description of this {@code Node}.
* @defaultValue null
*
* @since JavaFX 8u40
*/
public final ObjectProperty<String> accessibleRoleDescriptionProperty() {
return getAccessibilityProperties().getAccessibleRoleDescription();
}
public final void setAccessibleText(String value) {
accessibleTextProperty().set(value);
}
public final String getAccessibleText() {
if (accessibilityProperties == null) return null;
if (accessibilityProperties.accessibleText == null) return null;
return accessibleTextProperty().get();
}
The accessible text for this Node
.
This property is used to set the text that the screen
reader will speak. If a node normally speaks text,
that text is overriden. For example, a button
usually speaks using the text in the control but will
no longer do this when this value is set.
Returns: accessible text for this Node
. @defaultValue null Since: JavaFX 8u40
/**
* The accessible text for this {@code Node}.
* <p>
* This property is used to set the text that the screen
* reader will speak. If a node normally speaks text,
* that text is overriden. For example, a button
* usually speaks using the text in the control but will
* no longer do this when this value is set.
*
* @return accessible text for this {@code Node}.
* @defaultValue null
*
* @since JavaFX 8u40
*/
public final ObjectProperty<String> accessibleTextProperty() {
return getAccessibilityProperties().getAccessibleText();
}
public final void setAccessibleHelp(String value) {
accessibleHelpProperty().set(value);
}
public final String getAccessibleHelp() {
if (accessibilityProperties == null) return null;
if (accessibilityProperties.accessibleHelp == null) return null;
return accessibleHelpProperty().get();
}
The accessible help text for this Node
.
The help text provides a more detailed description of the
accessible text for a node. By default, if the node has
a tool tip, this text is used.
Returns: the accessible help text for this Node
. @defaultValue null Since: JavaFX 8u40
/**
* The accessible help text for this {@code Node}.
* <p>
* The help text provides a more detailed description of the
* accessible text for a node. By default, if the node has
* a tool tip, this text is used.
*
* @return the accessible help text for this {@code Node}.
* @defaultValue null
*
* @since JavaFX 8u40
*/
public final ObjectProperty<String> accessibleHelpProperty() {
return getAccessibilityProperties().getAccessibleHelp();
}
AccessibilityProperties accessibilityProperties;
private AccessibilityProperties getAccessibilityProperties() {
if (accessibilityProperties == null) {
accessibilityProperties = new AccessibilityProperties();
}
return accessibilityProperties;
}
private class AccessibilityProperties {
ObjectProperty<String> accessibleRoleDescription;
ObjectProperty<String> getAccessibleRoleDescription() {
if (accessibleRoleDescription == null) {
accessibleRoleDescription = new SimpleObjectProperty<String>(Node.this, "accessibleRoleDescription", null);
}
return accessibleRoleDescription;
}
ObjectProperty<String> accessibleText;
ObjectProperty<String> getAccessibleText() {
if (accessibleText == null) {
accessibleText = new SimpleObjectProperty<String>(Node.this, "accessibleText", null);
}
return accessibleText;
}
ObjectProperty<String> accessibleHelp;
ObjectProperty<String> getAccessibleHelp() {
if (accessibleHelp == null) {
accessibleHelp = new SimpleObjectProperty<String>(Node.this, "accessibleHelp", null);
}
return accessibleHelp;
}
}
This method is called by the assistive technology to request
the value for an attribute.
This method is commonly overridden by subclasses to implement
attributes that are required for a specific role.
If a particular attribute is not handled, the superclass implementation
must be called.
Params: - attribute – the requested attribute
- parameters – optional list of parameters
See Also: Returns: the value for the requested attribute Since: JavaFX 8u40
/**
* This method is called by the assistive technology to request
* the value for an attribute.
* <p>
* This method is commonly overridden by subclasses to implement
* attributes that are required for a specific role.<br>
* If a particular attribute is not handled, the superclass implementation
* must be called.
* </p>
*
* @param attribute the requested attribute
* @param parameters optional list of parameters
* @return the value for the requested attribute
*
* @see AccessibleAttribute
*
* @since JavaFX 8u40
*/
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
switch (attribute) {
case ROLE: return getAccessibleRole();
case ROLE_DESCRIPTION: return getAccessibleRoleDescription();
case TEXT: return getAccessibleText();
case HELP: return getAccessibleHelp();
case PARENT: return getParent();
case SCENE: return getScene();
case BOUNDS: return localToScreen(getBoundsInLocal());
case DISABLED: return isDisabled();
case FOCUSED: return isFocused();
case VISIBLE: return isVisible();
case LABELED_BY: return labeledBy;
default: return null;
}
}
This method is called by the assistive technology to request the action
indicated by the argument should be executed.
This method is commonly overridden by subclasses to implement
action that are required for a specific role.
If a particular action is not handled, the superclass implementation
must be called.
Params: - action – the action to execute
- parameters – optional list of parameters
See Also: Since: JavaFX 8u40
/**
* This method is called by the assistive technology to request the action
* indicated by the argument should be executed.
* <p>
* This method is commonly overridden by subclasses to implement
* action that are required for a specific role.<br>
* If a particular action is not handled, the superclass implementation
* must be called.
* </p>
*
* @param action the action to execute
* @param parameters optional list of parameters
*
* @see AccessibleAction
*
* @since JavaFX 8u40
*/
public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
switch (action) {
case REQUEST_FOCUS:
if (isFocusTraversable()) {
requestFocus();
}
break;
case SHOW_MENU: {
Bounds b = getBoundsInLocal();
Point2D pt = localToScreen(b.getMaxX(), b.getMaxY());
ContextMenuEvent event =
new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED,
b.getMaxX(), b.getMaxY(), pt.getX(), pt.getY(),
false, new PickResult(this, b.getMaxX(), b.getMaxY()));
Event.fireEvent(this, event);
break;
}
default:
}
}
This method is called by the application to notify the assistive
technology that the value for an attribute has changed.
Params: - attributes – the attribute whose value has changed
See Also: Since: JavaFX 8u40
/**
* This method is called by the application to notify the assistive
* technology that the value for an attribute has changed.
*
* @param attributes the attribute whose value has changed
*
* @see AccessibleAttribute
*
* @since JavaFX 8u40
*/
public final void notifyAccessibleAttributeChanged(AccessibleAttribute attributes) {
if (accessible == null) {
Scene scene = getScene();
if (scene != null) {
accessible = scene.removeAccessible(this);
}
}
if (accessible != null) {
accessible.sendNotification(attributes);
}
}
Accessible accessible;
Accessible getAccessible() {
if (accessible == null) {
Scene scene = getScene();
/* It is possible the node was reparented and getAccessible()
* is called before the pulse. Try to recycle the accessible
* before creating a new one.
* Note: this code relies that an accessible can never be on
* more than one Scene#accMap. Thus, the only way
* scene#removeAccessible() returns non-null is if the node
* old scene and new scene are the same object.
*/
if (scene != null) {
accessible = scene.removeAccessible(this);
}
}
if (accessible == null) {
accessible = Application.GetApplication().createAccessible();
accessible.setEventHandler(new Accessible.EventHandler() {
@SuppressWarnings("deprecation")
@Override public AccessControlContext getAccessControlContext() {
Scene scene = getScene();
if (scene == null) {
/* This can happen during the release process of an accessible object. */
throw new RuntimeException("Accessbility requested for node not on a scene");
}
if (scene.getPeer() != null) {
return scene.getPeer().getAccessControlContext();
} else {
/* In some rare cases the accessible for a Node is needed
* before its scene is made visible. For example, the screen reader
* might ask a Menu for its ContextMenu before the ContextMenu
* is made visible. That is a problem because the Window for the
* ContextMenu is only created immediately before the first time
* it is shown.
*/
return scene.acc;
}
}
@Override public Object getAttribute(AccessibleAttribute attribute, Object... parameters) {
return queryAccessibleAttribute(attribute, parameters);
}
@Override public void executeAction(AccessibleAction action, Object... parameters) {
executeAccessibleAction(action, parameters);
}
@Override public String toString() {
String klassName = Node.this.getClass().getName();
return klassName.substring(klassName.lastIndexOf('.')+1);
}
});
}
return accessible;
}
void releaseAccessible() {
Accessible acc = this.accessible;
if (acc != null) {
accessible = null;
acc.dispose();
}
}
}