/*
* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene;
import com.sun.javafx.geometry.
BoundsUtils;
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.beans.value.
WritableValue;
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.
Blend;
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 com.sun.javafx.css.
Selector;
import com.sun.javafx.css.
Style;
import com.sun.javafx.css.converters.
BooleanConverter;
import com.sun.javafx.css.converters.
CursorConverter;
import com.sun.javafx.css.converters.
EffectConverter;
import com.sun.javafx.css.converters.
EnumConverter;
import com.sun.javafx.css.converters.
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.jmx.
MXNodeAlgorithm;
import com.sun.javafx.jmx.
MXNodeAlgorithmContext;
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.
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 javafx.scene.shape.
Shape3D;
import sun.util.logging.
PlatformLogger;
import sun.util.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.
* <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 {@code Scene} in a {@code Window} that is showing.
* An application must attach nodes to such a Scene or modify them on the JavaFX
* Application Thread.
*
* <h4>String ID</h4>
* <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.
*
* <h4>Coordinate System</h4>
* <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.
*
* <h4>Transformations</h4>
* <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 multipled 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.
*
* <h4>Bounding Rectangles</h4>
* <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"/> </p>
* If we rotate the image by 20 degrees we get following result:
* <p> <img src="doc-files/boundsParent.png"/> </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>
*
* <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"/> </p>
*
*
* <h4>CSS</h4>
* <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 {
static {
PerformanceTracker.
logEvent("Node class loaded");
}
/**************************************************************************
* *
* 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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected void
impl_markDirty(
DirtyBits dirtyBit) {
if (
impl_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
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final boolean
impl_isDirty(
DirtyBits dirtyBit) {
return (
dirtyBits &
dirtyBit.
getMask()) != 0;
}
/**
* Clear the specified dirty bit
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final void
impl_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
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final boolean
impl_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. *
* *
*************************************************************************/
/**
* Called by the synchronizer to update the state and
* clear dirtybits of this node in the PG graph
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_syncPeer() {
// Do not synchronize invisible nodes unless their visibility has changed
// or they have requested a forced synchronization
if (!
impl_isDirtyEmpty() && (
treeVisible
||
impl_isDirty(
DirtyBits.
NODE_VISIBLE)
||
impl_isDirty(
DirtyBits.
NODE_FORCE_SYNC)))
{
impl_updatePeer();
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).
*/
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 impl_syncPeer()
if (!
treeVisible && !
impl_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 (
impl_isDirty(
DirtyBits.
NODE_TRANSFORM)
||
impl_isDirty(
DirtyBits.
NODE_TRANSFORMED_BOUNDS)
||
impl_isDirty(
DirtyBits.
NODE_BOUNDS)) {
pendingUpdateBounds = true;
}
return;
}
// Set transform and bounds dirty bits when this node becomes visible
if (
pendingUpdateBounds) {
impl_markDirty(
DirtyBits.
NODE_TRANSFORM);
impl_markDirty(
DirtyBits.
NODE_TRANSFORMED_BOUNDS);
impl_markDirty(
DirtyBits.
NODE_BOUNDS);
pendingUpdateBounds = false;
}
if (
impl_isDirty(
DirtyBits.
NODE_TRANSFORM) ||
impl_isDirty(
DirtyBits.
NODE_TRANSFORMED_BOUNDS)) {
if (
impl_isDirty(
DirtyBits.
NODE_TRANSFORM)) {
updateLocalToParentTransform();
}
_txBounds =
getTransformedBounds(
_txBounds,
BaseTransform.
IDENTITY_TRANSFORM);
}
if (
impl_isDirty(
DirtyBits.
NODE_BOUNDS)) {
_geomBounds =
getGeomBounds(
_geomBounds,
BaseTransform.
IDENTITY_TRANSFORM);
}
}
/**
* This function is called during synchronization to update the state of the
* PG Node from the FX Node. Subclasses of Node should override this method
* and must call super.impl_updatePeer()
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public void
impl_updatePeer() {
final
NGNode peer =
impl_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 &&
impl_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 (
impl_isDirty(
DirtyBits.
NODE_TRANSFORM)) {
peer.
setTransformMatrix(
localToParentTx);
}
if (
impl_isDirty(
DirtyBits.
NODE_BOUNDS)) {
peer.
setContentBounds(
_geomBounds);
}
if (
impl_isDirty(
DirtyBits.
NODE_TRANSFORMED_BOUNDS)) {
peer.
setTransformedBounds(
_txBounds, !
impl_isDirty(
DirtyBits.
NODE_BOUNDS));
}
if (
impl_isDirty(
DirtyBits.
NODE_OPACITY)) {
peer.
setOpacity((float)
Utils.
clamp(0,
getOpacity(), 1));
}
if (
impl_isDirty(
DirtyBits.
NODE_CACHE)) {
peer.
setCachedAsBitmap(
isCache(),
getCacheHint());
}
if (
impl_isDirty(
DirtyBits.
NODE_CLIP)) {
peer.
setClipNode(
getClip() != null ?
getClip().
impl_getPeer() : null);
}
if (
impl_isDirty(
DirtyBits.
EFFECT_EFFECT)) {
if (
getEffect() != null) {
getEffect().
impl_sync();
peer.
effectChanged();
}
}
if (
impl_isDirty(
DirtyBits.
NODE_EFFECT)) {
peer.
setEffect(
getEffect() != null ?
getEffect().
impl_getImpl() : null);
}
if (
impl_isDirty(
DirtyBits.
NODE_VISIBLE)) {
peer.
setVisible(
isVisible());
}
if (
impl_isDirty(
DirtyBits.
NODE_DEPTH_TEST)) {
peer.
setDepthTest(
isDerivedDepthTest());
}
if (
impl_isDirty(
DirtyBits.
NODE_BLENDMODE)) {
BlendMode mode =
getBlendMode();
peer.
setNodeBlendMode((
mode == null)
? null
:
Blend.
impl_getToolkitMode(
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.
*
* @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.
* @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 {@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 {@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 {@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.
impl_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.
impl_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.
//
impl_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 impl_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
SubScene subScene = 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);
if (
sceneChanged)
impl_reapplyCSS();
if (
sceneChanged && !
impl_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
*/
void
scenesChanged(final
Scene newScene, final
SubScene newSubScene,
final
Scene oldScene, final
SubScene oldSubScene) { }
/**
* 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 {@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() {
impl_reapplyCSS();
if (
PrismSettings.
printRenderGraph) {
impl_markDirty(
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 <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) {
impl_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 {@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 {@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 {@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.
impl_reapplyCSS();
}
@
Override
public
Object getBean() {
return
Node.this;
}
@
Override
public
String getName() {
return "style";
}
};
}
return
style;
}
/**
* 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()) {
impl_markDirty(
DirtyBits.
NODE_VISIBLE);
impl_geomChanged();
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 {@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.
*
* @defaultValue null
*/
public final
ObjectProperty<
Cursor>
cursorProperty() {
return
getMiscProperties().
cursorProperty();
}
/**
* 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() {
impl_markDirty(
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 {@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() {
impl_markDirty(
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 {@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.
*
* @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 {@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.
*
* @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.
* <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();
</code></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.
*
* @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 {@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.
*
* @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 {@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.
*
* @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.
*/
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) {
impl_markDirty(
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 {@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}.
*
* @defaultValue false
*/
public final
BooleanProperty disableProperty() {
return
getMiscProperties().
disableProperty();
}
/**************************************************************************
* *
*
* *
*************************************************************************/
/**
* 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 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.
*
* @defaultValue false
*/
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 {@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 {@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 {@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.
*
* @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 {@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().
impl_toBack(this);
}
}
/**
* 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().
impl_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
// Node.impl_processCSS(boolean)
// 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.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
this.
processCSS();
}
}
/**
* Recursive function for synchronizing a node and all descendents
*/
private static void
syncAll(
Node node) {
node.
impl_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 descendents of this node
Parent p = (
Parent)this;
for (int
i = 0;
i < 3;
i++) {
p.
layout();
}
}
}
private void
doCSSLayoutSyncForSnapshot() {
doCSSPass();
doLayoutPass();
updateBounds();
Scene.
impl_setAllowPGAccess(true);
syncAll(this);
Scene.
impl_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();
params.
getTransform().
impl_apply(
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 {@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 {@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 {@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 {@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 {@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 {@code Node} during drag and drop gesture. Transfer of data from
* the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should
* happen in this function.
*/
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 {@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.
*/
public final
ObjectProperty<
EventHandler<? super
DragEvent>>
onDragDoneProperty() {
return
getEventHandlerProperties().
onDragDoneProperty();
}
/**
* 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 {@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.
*/
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.
*/
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.
*/
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
*/
private
NGNode peer;
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
@
SuppressWarnings("CallToPrintStackTrace")
public <P extends
NGNode> P
impl_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 =
impl_createPeer();
//if (PerformanceTracker.isLoggingEnabled()) {
// PerformanceTracker.logEvent("NGNode created");
//}
}
return (P)
peer;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected abstract
NGNode impl_createPeer();
/***************************************************************************
* *
* Initialization *
* *
* To Note limit the number of bounds computations and improve startup *
* performance. *
* *
**************************************************************************/
/**
* 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 {@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.
*/
void
notifyManagedChanged() { }
/**
* 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}
* <code><pre>
* textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
* </pre></code>
* <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.
* <p>
* @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() {
impl_transformsChanged();
final
Parent p =
getParent();
if (
p != null && !
p.
performingLayout) {
if (
isManaged()) {
// Let parent fix the layout
p.
requestLayout();
} 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 {@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}
* <code><pre>
* textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
* </pre></code>
* <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() {
impl_transformsChanged();
final
Parent p =
getParent();
if (
p != null && !
p.
performingLayout) {
if (
isManaged()) {
// Let parent fix the layout
p.
requestLayout();
} 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.
* <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.
* <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.
* <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.
* <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}.
* <p>
* @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.
* <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}.
* <p>
* @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.
* <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).
* <p>
* @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.
* <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).
* <p>
* @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.
* <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}.
* <p>
* @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.
* <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}.
* <p>
* @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.
* <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.
*
* <p>
* @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.
* <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.
* <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.
* <p>
* @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 {@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 {@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 {@code Node} projected onto the
* physical screen in pixel units.
* @since JavaFX 8.0
*/
public double
computeAreaInScreen() {
return
impl_computeAreaInScreen();
}
/*
* 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 applys 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
impl_computeAreaInScreen() {
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 impl_apply is a matrix concatenation operation.
tempTx.
setToIdentity();
localToSceneTx.
impl_apply(
tempTx);
// Convert node from local coordinate to camera coordinate
tempTx.
preConcatenate(
camera.
getSceneToLocalTransform());
tempTx.
transform(
localBounds,
nodeInCameraBounds);
// Compare in camera coornidate
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 impl_apply is a matrix concatenation operation.
tempTx.
setToIdentity();
localToSceneTx.
impl_apply(
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 {@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}</li>
* <li>{@link #rotateProperty rotate}</li>
* <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li>
* <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY}</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 boundsInParent is automatically recomputed whenever the
* geometry of a node changes, or when any of the following the change:
* transforms ObservableList, translateX, translateY, layoutX, layoutY,
* scaleX, scaleY, 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.
*/
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 {@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.
*/
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. {@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 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 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 layoutBounds
* for the purpose of positioning the node.
* <p>
* The layoutBounds will never be null.
*
*/
private
LazyBoundsProperty layoutBounds = new
LazyBoundsProperty() {
@
Override
protected
Bounds computeBounds() {
return
impl_computeLayoutBounds();
}
@
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 who's 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 impl_computeGeomBounds 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.
*/
private
BaseTransform localToParentTx =
BaseTransform.
IDENTITY_TRANSFORM;
/**
* 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
*/
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.
*/
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.
*/
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.
* <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.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected
Bounds impl_computeLayoutBounds() {
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
* impl_computeLayoutBounds 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 impl_layoutBoundsChanged to
* let Node know that the layout bounds are invalid and need to be
* recomputed.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final void
impl_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
impl_transformsChanged();
}
}
/**
* 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).
*/
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 (unifrom 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).
*/
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
impl_computeGeomBounds(
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);
}
}
/**
* Computes the geometric bounds for this Node. This method is abstract
* and must be implemented by each Node subclass.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public abstract
BaseBounds impl_computeGeomBounds(
BaseBounds bounds,
BaseTransform tx);
/**
* 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 =
impl_computeGeomBounds(
geomBounds,
BaseTransform.
IDENTITY_TRANSFORM);
geomBoundsInvalid = false;
}
}
/**
* 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 =
getEffect().
impl_getBounds(
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.
*/
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.
*/
void
updateTxBounds() {
if (
txBoundsInvalid) {
updateLocalToParentTransform();
txBounds =
getLocalBounds(
txBounds,
localToParentTx);
txBoundsInvalid = false;
}
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected abstract boolean
impl_computeContains(double
localX, double
localY);
/*
* 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 impl_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), impl_geomChanged calls localBoundsChanged(). It should
* be noted that impl_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 impl_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 impl_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
* impl_geomChanged() *does not* invalidate the layout bounds.
*
* This interposition happens by providing the
* impl_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 impl_notifyLayoutBoundsChanged. The default
* implementation of impl_notifyLayoutBoundsChanged() will simply invalidate
* layoutBounds. Resizable subclasses will want to override this function
* in most cases to be a no-op.
* <p>
* 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.
* <p>
* This function should be treated as though it were final. It is not
* intended to be overridden by subclasses.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected void
impl_geomChanged() {
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.
impl_notifyLayoutBoundsChanged();
transformedBoundsChanged();
return;
}
geomBounds.
makeEmpty();
geomBoundsInvalid = true;
impl_markDirty(
DirtyBits.
NODE_BOUNDS);
impl_notifyLayoutBoundsChanged();
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.
*/
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.
*/
void
transformedBoundsChanged() {
if (!
txBoundsInvalid) {
txBounds.
makeEmpty();
txBoundsInvalid = true;
invalidateBoundsInParent();
impl_markDirty(
DirtyBits.
NODE_TRANSFORMED_BOUNDS);
}
if (
isVisible()) {
notifyParentOfBoundsChange();
}
}
/**
* Invoked by impl_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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected void
impl_notifyLayoutBoundsChanged() {
impl_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();
if (
isManaged() && (
p != null) && !(
p instanceof
Group && !
isResizable())
&& !
p.
performingLayout) {
p.
requestLayout();
}
}
/**
* 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 {@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.
*/
public boolean
contains(double
localX, double
localY) {
if (
containsBounds(
localX,
localY)) {
return (
isPickOnBounds() ||
impl_computeContains(
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.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected 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 {@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.
*/
public boolean
contains(
Point2D localPoint) {
return
contains(
localPoint.
getX(),
localPoint.
getY());
}
/**
* 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.
*/
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 {@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.
*/
public boolean
intersects(
Bounds localBounds) {
return
intersects(
localBounds.
getMinX(),
localBounds.
getMinY(),
localBounds.
getWidth(),
localBounds.
getHeight());
}
/**
* 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 {@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
* {@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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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 {@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 {@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 {@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 {@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.
*/
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
* {@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 {@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 {@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 {@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 {@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
* {@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 {@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 {@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 {@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}.
* @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 {@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}.
* @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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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 {@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()}. Othwerwise, 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
* {@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 {@code Node}.
*/
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 {@code Node}.
*/
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 {@code Node}.
* @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 {@code Node}.
* @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 {@code Node}.
*/
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 {@code Node}
* into the coordinate space of its parent.
*/
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 {@code Node}
* into the coordinate space of its parent.
*/
public
Point2D localToParent(
Point2D localPoint) {
return
localToParent(
localPoint.
getX(),
localPoint.
getY());
}
/**
* Transforms a point from the local coordinate space of this {@code Node}
* into the coordinate space of its parent.
* @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 {@code Node}
* into the coordinate space of its parent.
* @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
* {@code Node} into the coordinate space of its parent.
*/
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.
*/
BaseTransform getLocalToParentTransform(
BaseTransform tx) {
updateLocalToParentTransform();
tx.
setTransform(
localToParentTx);
return
tx;
}
/**
* Currently used only by PathTransition
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final
BaseTransform impl_getLeafTransform() {
return
getLocalToParentTransform(
TempState.
getInstance().
leafTx);
}
/**
* Invoked whenever the transforms[] ObservableList changes, or by the transforms
* in that ObservableList whenever they are changed.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public void
impl_transformsChanged() {
if (!
transformDirty) {
impl_markDirty(
DirtyBits.
NODE_TRANSFORM);
transformDirty = true;
transformedBoundsChanged();
}
invalidateLocalToParentTransform();
invalidateLocalToSceneTransform();
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final double
impl_getPivotX() {
final
Bounds bounds =
getLayoutBounds();
return
bounds.
getMinX() +
bounds.
getWidth()/2;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final double
impl_getPivotY() {
final
Bounds bounds =
getLayoutBounds();
return
bounds.
getMinY() +
bounds.
getHeight()/2;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final double
impl_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.
*/
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 =
impl_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 =
impl_getPivotX();
}
}
if (
getScaleX() != 1 ||
getScaleY() != 1 ||
getScaleZ() != 1 ||
getRotate() != 0) {
// recompute pivotX, pivotY and pivotZ
double
pivotX =
impl_getPivotX();
double
pivotY =
impl_getPivotY();
double
pivotZ =
impl_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 (
impl_hasTransforms()) {
for (
Transform t :
getTransforms()) {
localToParentTx =
t.
impl_derive(
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.
*/
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.
*/
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.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected void
impl_pickNodeLocal(
PickRay localPickRay,
PickResultChooser result) {
impl_intersects(
localPickRay,
result);
}
/**
* Finds a top-most child node that intersects the given ray.
*
* The result argument is used for storing the picking result.
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_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.
impl_pickNodeLocal(
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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final boolean
impl_intersects(
PickRay pickRay,
PickResultChooser pickResult) {
double
boundsDistance =
impl_intersectsBounds(
pickRay);
if (!
Double.
isNaN(
boundsDistance)) {
if (
isPickOnBounds()) {
if (
pickResult != null) {
pickResult.
offer(this,
boundsDistance,
PickResultChooser.
computePoint(
pickRay,
boundsDistance));
}
return true;
} else {
return
impl_computeIntersects(
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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected boolean
impl_computeIntersects(
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
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final double
impl_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.
impl_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));
}
/***************************************************************************
* *
* Transformations *
* *
**************************************************************************/
/**
* 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.
*
* @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 {@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.
*
* @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 {@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.
*
* @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 {@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.
*
* @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 {@code Node}. This is used to stretch or
* animate 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}.
*
* @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 {@code Node}. This is used to stretch or
* animate 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}.
*
* @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 {@code Node}. This is used to stretch or
* animate 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.
*
* @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 {@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.
*
* @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 {@code Node}.
* <p>
* Note that this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
* for more information.
*
* @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.
* @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 {@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>
*
* @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;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public boolean
impl_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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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() {
impl_transformsChanged();
}
@
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()) {
t.
impl_remove(
Node.this);
}
for (
Transform t :
c.
getAddedSubList()) {
t.
impl_add(
Node.this);
}
}
impl_transformsChanged();
}
};
}
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 *
* *
**************************************************************************/
private
EventHandlerProperties eventHandlerProperties;
private
EventHandlerProperties getEventHandlerProperties() {
if (
eventHandlerProperties == null) {
eventHandlerProperties =
new
EventHandlerProperties(
getInternalEventDispatcher().
getEventHandlerManager(),
this);
}
return
eventHandlerProperties;
}
/***************************************************************************
* *
* 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.
* <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.
* @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.
* <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>
* @since JavaFX 8.0
*/
public boolean
usesMirroring() {
return true;
}
final void
parentResolvedOrientationInvalidated() {
if (
getNodeOrientation() ==
NodeOrientation.
INHERIT) {
nodeResolvedOrientationInvalidated();
} else {
// mirroring changed
impl_transformsChanged();
}
}
final void
nodeResolvedOrientationInvalidated() {
final byte
oldResolvedNodeOrientation =
resolvedNodeOrientation;
resolvedNodeOrientation =
(byte) (
calcEffectiveNodeOrientation()
|
calcAutomaticNodeOrientation());
if ((
effectiveNodeOrientationProperty != null)
&& (
getEffectiveOrientation(
resolvedNodeOrientation)
!=
getEffectiveOrientation(
oldResolvedNodeOrientation))) {
effectiveNodeOrientationProperty.
invalidate();
}
// mirroring changed
impl_transformsChanged();
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 *
* *
**************************************************************************/
private
MiscProperties miscProperties;
private
MiscProperties getMiscProperties() {
if (
miscProperties == null) {
miscProperties = new
MiscProperties();
}
return
miscProperties;
}
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;
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.
*/
@
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() {
impl_markDirty(
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() {
impl_markDirty(
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);
}
impl_markDirty(
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)) {
impl_markDirty(
DirtyBits.
EFFECT_EFFECT);
}
if (
EffectDirtyBits.
isSet(
changedBits,
EffectDirtyBits.
BOUNDS_CHANGED)) {
localBoundsChanged();
}
}
};
@
Override
protected void
invalidated() {
Effect _effect =
get();
if (
oldEffect != null) {
oldEffect.
impl_effectDirtyProperty().
removeListener(
effectChangeListener.
getWeakListener());
}
oldEffect =
_effect;
if (
_effect != null) {
_effect.
impl_effectDirtyProperty()
.
addListener(
effectChangeListener.
getWeakListener());
if (
_effect.
impl_isEffectDirty()) {
impl_markDirty(
DirtyBits.
EFFECT_EFFECT);
}
oldBits =
_effect.
impl_effectDirtyProperty().
get();
}
impl_markDirty(
DirtyBits.
NODE_EFFECT);
// bounds may have changed regardeless whether
// the dirty flag on efffect 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 {@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.
*/
public final
BooleanProperty mouseTransparentProperty() {
return
getMiscProperties().
mouseTransparentProperty();
}
/**
* 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 {@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 {@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 {@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 {@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 {@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 {@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 {@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 {@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 {@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.
*/
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 {@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 {@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 {@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 {@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.
* @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.
*/
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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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 {@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.
*/
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 {@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.
*/
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 {@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.
*/
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 {@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.
*/
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.
*
* @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.
*/
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 {@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 {@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.
*/
private void
focusSetDirty(
Scene s) {
if (
s != null &&
(this ==
s.
getFocusOwner() ||
isFocusTraversable())) {
s.
setFocusDirty(true);
}
}
/**
* 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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final boolean
impl_traverse(
Direction dir) {
if (
getScene() == null) {
return false;
}
return
getScene().
traverse(this,
dir);
}
////////////////////////////
// Private Implementation
////////////////////////////
/**
* 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 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.
impl_isTreeVisible();
}
// When the parent has changed to visible and we have unsynchornized visibility,
// we have to synchronize, because the rendering will now pass throught the newly-visible parent
// Otherwise an invisible Node might get rendered
if (
parentChanged &&
parentNode != null &&
parentNode.
impl_isTreeVisible()
&&
impl_isDirty(
DirtyBits.
NODE_VISIBLE)) {
addToSceneDirtyList();
}
setTreeVisible(
isTreeVisible);
}
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 && !
impl_isDirtyEmpty()) {
addToSceneDirtyList();
}
((
TreeVisiblePropertyReadOnly)
impl_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());
}
}
}
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final boolean
impl_isTreeVisible() {
return
impl_treeVisibleProperty().
get();
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
protected final
BooleanExpression impl_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()
&&
impl_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?
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
private
BooleanProperty impl_showMnemonics;
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_setShowMnemonics(boolean
value) {
impl_showMnemonicsProperty().
set(
value);
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final boolean
impl_isShowMnemonics() {
return
impl_showMnemonics == null ? false :
impl_showMnemonics.
get();
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final
BooleanProperty impl_showMnemonicsProperty() {
if (
impl_showMnemonics == null) {
impl_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
impl_showMnemonics;
}
/**
* 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 {@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 {@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.
*
* @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 {@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.
*
* @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 {@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
*/
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.
*
* @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 eighter, 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}
* @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}
* @return {@code getParent()}
* @since JavaFX 8.0
*/
@
Override
public
Styleable getStyleableParent() {
return
getParent();
}
/**
* Not everything uses the default value of false for focusTraversable.
* This method provides a way to have them return the correct initial value.
* @treatAsPrivate implementation detail
*/
@
Deprecated
protected /*do not make final*/
Boolean impl_cssGetFocusTraversableInitialValue() {
return
Boolean.
FALSE;
}
/**
* Not everything uses the default value of null for cursor.
* This method provides a way to have them return the correct initial value.
* @treatAsPrivate implementation detail
*/
@
Deprecated
protected /*do not make final*/
Cursor impl_cssGetCursorInitialValue() {
return null;
}
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
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.
impl_cssGetCursorInitialValue();
}
};
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.
impl_cssGetFocusTraversableInitialValue();
}
};
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,
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(
TRANSLATE_X);
styleables.
add(
TRANSLATE_Y);
styleables.
add(
TRANSLATE_Z);
styleables.
add(
VISIBILITY);
STYLEABLES =
Collections.
unmodifiableList(
styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
* @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 {@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 super classes.
* @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.
* @treatAsPrivate implementation detail
* @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
*/
@
Deprecated // SB-dependency: RT-21096 has been filed to track this
public static
List<
Style>
impl_getMatchingStyles(
CssMetaData cssMetaData,
Styleable styleable) {
return
CssStyleHelper.
getMatchingStyles(
styleable,
cssMetaData);
}
/**
* RT-17293
* @treatAsPrivate implementation detail
* @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
*/
@
Deprecated // SB-dependency: RT-21096 has been filed to track this
public final
ObservableMap<
StyleableProperty<?>,
List<
Style>>
impl_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
* @treatAsPrivate implementation detail
* @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
*/
@
Deprecated // SB-dependency: RT-21096 has been filed to track this
public final void
impl_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.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated // SB-dependency: RT-21096 has been filed to track this
public
Map<
StyleableProperty<?>,
List<
Style>>
impl_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.
*/
CssFlags cssFlag =
CssFlags.
CLEAN;
/**
* Needed for testing.
*/
final
CssFlags getCSSFlags() { return
cssFlag; }
/**
* 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 impl_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 {@code invalidated}
* method of a property that is used as a pseudo-class. For example:
* <code><pre>
*
* 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";
* }
* };
* </pre><code>
* @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();
/**
* @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.
/** @treatAsPrivate */
final void
notifyParentsOfInvalidatedCSS() {
SubScene subScene =
getSubScene();
Parent root = (
subScene != null) ?
subScene.
getRoot() :
getScene().
getRoot();
if (!
root.
impl_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().
root.
impl_markDirty(
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 recurisve 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;
}
}
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_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().
performingLayout) {
impl_processCSS(null);
} 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.impl_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:
impl_processCSS(null);
}
}
/**
* This method simply calls {@link #applyCss()}
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_processCSS(boolean
reapply) {
applyCss();
}
/**
* 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();
* </code></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().
impl_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().
impl_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.impl_processCSS()} to ensure that
* this Node's CSS state is properly updated.
*
* Note that the difference between this method and {@link #impl_processCSS(boolean)} is that this method
* updates styles for this node on down; whereas, {@code impl_processCSS(boolean)} invokes
* {@link #applyCss()} which will look for the top-most ancestor that needs CSS update and apply styles
* from that node on down.
*
* The WritableValue<Boolean> parameter is no longer used.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated // SB-dependency: RT-21206 has been filed to track this
protected void
impl_processCSS(
WritableValue<
Boolean>
unused) {
// 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.
*/
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);
/**
* This method is used by Scene-graph JMX bean to obtain the Scene-graph structure.
*
* @param alg current algorithm to process this node
* @param ctx current context
* @return the algorithm specific result for this node
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public abstract
Object impl_processMXNode(
MXNodeAlgorithm alg,
MXNodeAlgorithmContext ctx);
static {
// This is used by classes in different packages to get access to
// private and package private methods.
NodeHelper.
setNodeAccessor(new
NodeHelper.
NodeAccessor() {
@
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();
}
});
}
/**
* 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 {@code Node}.
* <p>
* Noramlly, 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 possbile 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.
*
* @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 {@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.
*
* @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 {@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.
*
* @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.
* <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 super class 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.
* <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 super class 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.
*
* @param notification 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.
impl_getPeer() != null) {
return
scene.
impl_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();
}
}
}