/* * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene; import com.sun.javafx.geometry.BoundsUtils; import javafx.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 javafx.css.Selector; import javafx.css.Style; import javafx.css.converter.BooleanConverter; import javafx.css.converter.CursorConverter; import javafx.css.converter.EffectConverter; import javafx.css.converter.EnumConverter; import javafx.css.converter.SizeConverter; import com.sun.javafx.effect.EffectDirtyBits; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.BoxBounds; import com.sun.javafx.geom.PickRay; import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Vec3d; import com.sun.javafx.geom.transform.Affine3D; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.geom.transform.GeneralTransform3D; import com.sun.javafx.geom.transform.NoninvertibleTransformException; import com.sun.javafx.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. *

* 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. *

* 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. *

* 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}. *

* 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}. *

* 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. *

* If a program adds a child node to a Parent (including Group, Region, etc) * and that node is already a child of a different Parent or the root of a Scene, * the node is automatically (and silently) removed from its former parent. * If a program attempts to modify the scene graph in any other way that violates * the above rules, an exception is thrown, the modification attempt is ignored * and the scene graph is restored to its previous state. *

* It is possible to rearrange the structure of the scene graph, for * example, to move a subtree from one location in the scene graph to * another. In order to do this, one would normally remove the subtree from * its old location before inserting it at the new location. However, the * subtree will be automatically removed as described above if the application * doesn't explicitly remove it. *

* Node objects may be constructed and modified on any thread as long they are * not yet attached to a {@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. * *

String ID

*

* 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. * *

Coordinate System

*

* 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}. *

* 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)}. *

* 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. * *

Transformations

*

* Any {@code Node} can have transformations applied to it. These include * translation, rotation, scaling, or shearing. *

* A translation transformation is one which shifts the origin of the * node's coordinate space along either the x or y axis. For example, if you * create a {@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. *

* A common node transform is a translation by an integer distance, most often * used to lay out nodes on the stage. Such integer translations maintain the * device pixel mapping so that local coordinates that are integers still * map to the cracks between pixels. *

* A rotation transformation is one which rotates the coordinate space of * the node about a specified "pivot" point, causing the node to appear rotated. * For example, if you create a {@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. *

* Note that as with all transformations, the x, y, width, and height variables * of the rectangle (which remain relative to the local coordinate space) have * not changed, but rather the transformation alters the entire coordinate space * of the rectangle. *

* A scaling transformation causes a node to either appear larger or * smaller depending on the scaling factor. Scaling alters the coordinate space * of the node such that each unit of distance along the axis in local * coordinates is 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. *

* A shearing transformation, sometimes called a skew, effectively * rotates one axis so that the x and y axes are no longer perpendicular. *

* Multiple transformations may be applied to a node by specifying an ordered * chain of transforms. The order in which the transforms are applied is * defined by the ObservableList specified in the {@link #getTransforms transforms} variable. * *

Bounding Rectangles

*

* 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. *

* 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. *

* 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. *

* 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 not include the effect, * clip, or any transforms. For resizable classes (Regions and Controls) * layoutBounds will always map to {@code 0,0 width x height}. * *

The image shows a node without any transformation and its {@code boundsInLocal}: *

* If we rotate the image by 20 degrees we get following result: *

* 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.

* *

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.

*

* * *

CSS

*

* 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. *

* For further information about CSS and how to apply CSS styles * to nodes, see the CSS Reference * Guide. * @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 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 getProperties() { if (properties == null) { properties = FXCollections.observableMap(new HashMap()); } 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; final void setParent(Parent value) { parentPropertyImpl().set(value); } public final Parent getParent() { return parent == null ? null : parent.get(); } public final ReadOnlyObjectProperty parentProperty() { return parentPropertyImpl().getReadOnlyProperty(); } private ReadOnlyObjectWrapper parentPropertyImpl() { if (parent == null) { parent = new ReadOnlyObjectWrapper() { 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 = new ReadOnlyObjectWrapperManualFire(); private class ReadOnlyObjectWrapperManualFire extends ReadOnlyObjectWrapper { @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 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 * (CSS ID Specification). *

* For example, if a Node is given the id of "myId", then the lookup method can * be used to find this node as follows: scene.lookup("#myId");. *

* * @defaultValue null * @see CSS Reference Guide. */ 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 * (CSS ID Specification). * * @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 CSS Reference Guide. */ 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 CSS3 class selectors * @see CSS Reference Guide. * @defaultValue null */ private ObservableList styleClass = new TrackableObservableList() { @Override protected void onChanged(Change 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 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 CSS Reference Guide. */ 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 CSS Reference Guide. */ 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 CSS Reference Guide. */ 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 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. *

* 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%. *

* Opacity is specified as a value between 0 and 1. Values less than 0 are * treated as 0, values greater than 1 are treated as 1. *

* On some platforms ImageView might not support opacity variable. * *

* There is a known limitation of mixing opacity < 1.0 with a 3D Transform. * Opacity/Blending is essentially a 2D image operation. The result of * an opacity < 1.0 set on a {@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; public final void setBlendMode(BlendMode value) { blendModeProperty().set(value); } public final BlendMode getBlendMode() { return blendMode == null ? null : blendMode.get(); } public final ObjectProperty blendModeProperty() { if (blendMode == null) { blendMode = new StyleableObjectProperty(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}. *

* 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. *

* 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. *

* Note that this is a conditional feature. See * {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP} * for more information. *

* There is a known limitation of mixing Clip with a 3D Transform. * Clipping is essentially a 2D image operation. The result of * a Clip set on a {@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 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. * *

* 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. *

* Under certain circumstances, such as animating nodes that are very * expensive to render, it is desirable to be able to perform * transformations on the node without having to regenerate the cached * bitmap. An option in such cases is to perform the transforms on the * cached bitmap itself. *

* This technique can provide a dramatic improvement to animation * performance, though may also result in a reduction in visual quality. * The {@code cacheHint} variable provides a hint to the system about how * and when that trade-off (visual quality for animation performance) is * acceptable. *

* It is possible to enable the cacheHint only at times when your node is * animating. In this way, expensive nodes can appear on screen with full * visual quality, yet still animate smoothly. *

* Example: *


        expensiveNode.setCache(true);
        expensiveNode.setCacheHint(CacheHint.QUALITY);
        ...
        // Do an animation
        expensiveNode.setCacheHint(CacheHint.SPEED);
        new Timeline(
            new KeyFrame(Duration.seconds(2),
                new KeyValue(expensiveNode.scaleXProperty(), 2.0),
                new KeyValue(expensiveNode.scaleYProperty(), 2.0),
                new KeyValue(expensiveNode.rotateProperty(), 360),
                new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
            )
        ).play();
     
* * Note that {@code cacheHint} is only a hint to the system. Depending on * the details of the node or the transform, this hint may be ignored. * *

* 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 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}. *

* Note that this is a conditional feature. See * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT} * for more information. * *

* There is a known limitation of mixing Effect with a 3D Transform. Effect is * essentially a 2D image operation. The result of an Effect set on * a {@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 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. *

* 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) *

* Depth test comparison is only done among nodes with depthTest enabled. * A node with depthTest disabled does not read, test, or write the depth buffer, * that is to say its Z value will not be considered for depth testing * with other nodes. *

* Note that this is a conditional feature. See * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} * for more information. *

* See the constructor in Scene with depthBuffer as one of its input * arguments. * * @see javafx.scene.Scene * @defaultValue INHERIT */ public final ObjectProperty 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(); } // /** // * TODO document - null by default, could be non-null in subclasses (e.g. Control) // */ // public final ObjectProperty> inputMapProperty() { // if (inputMap == null) { // inputMap = new SimpleObjectProperty>(this, "inputMap") { // private InputMap currentMap = get(); // @Override protected void invalidated() { // if (currentMap != null) { // currentMap.dispose(); // } // currentMap = get(); // } // }; // } // return inputMap; // } // public final void setInputMap(InputMap value) { inputMapProperty().set(value); } // public final InputMap getInputMap() { return inputMapProperty().getValue(); } // private ObjectProperty> inputMap; /************************************************************************** * * * * * *************************************************************************/ /** * 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. *

* 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. *

* 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. *

* For example, if a Node is given the id of "myId", then the lookup method can * be used to find this node as follows: scene.lookup("#myId");. *

* * @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 lookupAll(String selector) { final Selector s = Selector.createSelector(selector); final Set empty = Collections.emptySet(); if (s == null) return empty; List results = lookupAll(s, null); return results == null ? empty : new UnmodifiableListSet(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 lookupAll(Selector selector, List results) { if (selector.applies(this)) { // Lazily create the set to reduce some trash. if (results == null) { results = new LinkedList(); } 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. * *

* When taking a snapshot of a scene that is being animated, either * explicitly by the application or implicitly (such as chart animation), * the snapshot will be rendered based on the state of the scene graph at * the moment the snapshot is taken and will not reflect any subsequent * animation changes. *

* *

* NOTE: In order for CSS and layout to function correctly, the node * must be part of a Scene (the Scene may be attached to a Stage, but need * not be). *

* * @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. * *

* This is an asynchronous call, which means that other * events or animation might be processed before the node is rendered. * If any such events modify the node, or any of its children, that * modification will be reflected in the rendered image (just like it * will also be reflected in the frame rendered to the Stage, if this node * is part of a live scene graph). *

* *

* When taking a snapshot of a node that is being animated, either * explicitly by the application or implicitly (such as chart animation), * the snapshot will be rendered based on the state of the scene graph at * the moment the snapshot is taken and will not reflect any subsequent * animation changes. *

* *

* NOTE: In order for CSS and layout to function correctly, the node * must be part of a Scene (the Scene may be attached to a Stage, but need * not be). *

* * @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 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 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 value) { onDragEnteredProperty().set(value); } public final EventHandler getOnDragEntered() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnDragEntered(); } /** * Defines a function to be called when drag gesture * enters this {@code Node}. */ public final ObjectProperty> onDragEnteredProperty() { return getEventHandlerProperties().onDragEnteredProperty(); } public final void setOnDragExited( EventHandler value) { onDragExitedProperty().set(value); } public final EventHandler getOnDragExited() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnDragExited(); } /** * Defines a function to be called when drag gesture * exits this {@code Node}. */ public final ObjectProperty> onDragExitedProperty() { return getEventHandlerProperties().onDragExitedProperty(); } public final void setOnDragOver( EventHandler value) { onDragOverProperty().set(value); } public final EventHandler getOnDragOver() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnDragOver(); } /** * Defines a function to be called when drag gesture progresses within * this {@code Node}. */ public final ObjectProperty> onDragOverProperty() { return getEventHandlerProperties().onDragOverProperty(); } // Do we want DRAG_TRANSFER_MODE_CHANGED event? // public final void setOnDragTransferModeChanged( // EventHandler value) { // onDragTransferModeChangedProperty().set(value); // } // // public final EventHandler 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> // onDragTransferModeChangedProperty() { // return getEventHandlerProperties().onDragTransferModeChangedProperty(); // } public final void setOnDragDropped( EventHandler value) { onDragDroppedProperty().set(value); } public final EventHandler 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> onDragDroppedProperty() { return getEventHandlerProperties().onDragDroppedProperty(); } public final void setOnDragDone( EventHandler value) { onDragDoneProperty().set(value); } public final EventHandler 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> 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 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). *

* 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. *

* By default all nodes are managed. *

* * @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. * *

For example, if {@code textnode} should be positioned at {@code finalX} *

     *     textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
     * 
*

* 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. *

* 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. *

* If the node is managed and has a {@link javafx.scene.layout.Region} * as its parent, then the layout region will set {@code layoutX} according to its * own layout policy. If the node is unmanaged or parented by a {@link Group}, * then the application may set {@code layoutX} directly to position it. *

* @see #relocate(double, double) * @see #layoutBoundsProperty() * */ private DoubleProperty layoutX; public final void setLayoutX(double value) { layoutXProperty().set(value); } public final double getLayoutX() { return layoutX == null ? 0.0 : layoutX.get(); } public final DoubleProperty layoutXProperty() { if (layoutX == null) { layoutX = new DoublePropertyBase(0.0) { @Override protected void invalidated() { 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. * *

For example, if {@code textnode} should be positioned at {@code finalY} *

     *     textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
     * 
*

* 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. *

* 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. *

* 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. *

* This method does not alter translateX or translateY, which if also set * will be added to layoutX and layoutY, adjusting the final location by * corresponding amounts. * * @param x the target x coordinate location * @param y the target y coordinate location */ public void relocate(double x, double y) { setLayoutX(x - getLayoutBounds().getMinX()); setLayoutY(y - getLayoutBounds().getMinY()); PlatformLogger logger = Logging.getLayoutLogger(); if (logger.isLoggable(Level.FINER)) { logger.finer(this.toString()+" moved to ("+x+","+y+")"); } } /** * Indicates whether this node is a type which can be resized by its parent. * If this method returns true, then the parent will resize the node (ideally * within its size range) by calling node.resize(width,height) during the * layout pass. All Regions, Controls, and WebView are resizable classes * which depend on their parents resizing them during layout once all sizing * and CSS styling information has been applied. *

* If this method returns false, then the parent cannot resize it during * layout (resize() is a no-op) and it should return its layoutBounds for * minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not * resizable and hence depend on the application to establish their sizing * by setting appropriate properties (e.g. width/height for Rectangle, * text on Text, and so on). Non-resizable nodes may still be relocated * during layout. * * @see #getContentBias() * @see #minWidth(double) * @see #minHeight(double) * @see #prefWidth(double) * @see #prefHeight(double) * @see #maxWidth(double) * @see #maxHeight(double) * @see #resize(double, double) * @see #getLayoutBounds() * * @return whether or not this node type can be resized by its parent during layout */ public boolean isResizable() { return false; } /** * Returns the orientation of a node's resizing bias for layout purposes. * If the node type has no bias, returns null. If the node is resizable and * it's height depends on its width, returns HORIZONTAL, else if its width * depends on its height, returns VERTICAL. *

* Resizable subclasses should override this method to return an * appropriate value. * * @see #isResizable() * @see #minWidth(double) * @see #minHeight(double) * @see #prefWidth(double) * @see #prefHeight(double) * @see #maxWidth(double) * @see #maxHeight(double) * * @return orientation of width/height dependency or null if there is none */ public Orientation getContentBias() { return null; } /** * Returns the node's minimum width for use in layout calculations. * If the node is resizable, its parent should not resize its width any * smaller than this value. If the node is not resizable, returns its * layoutBounds width. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a vertical content-bias, then callers * should pass in a height value that the minimum width should be based on. * If the node has either a horizontal or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a vertical content-bias should honor the height * parameter whether -1 or a positive value. All other subclasses may ignore * the height parameter (which will likely be -1). *

* If Node's {@link #maxWidth(double)} is lower than this number, * {@code minWidth} takes precedence. This means the Node should never be resized below {@code minWidth}. *

* @see #isResizable() * @see #getContentBias() * * @param height the height that should be used if minimum width depends on it * @return the minimum width that the node should be resized to during layout. * The result will never be NaN, nor will it ever be negative. */ public double minWidth(double height) { return prefWidth(height); } /** * Returns the node's minimum height for use in layout calculations. * If the node is resizable, its parent should not resize its height any * smaller than this value. If the node is not resizable, returns its * layoutBounds height. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a horizontal content-bias, then callers * should pass in a width value that the minimum height should be based on. * If the node has either a vertical or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a horizontal content-bias should honor the width * parameter whether -1 or a positive value. All other subclasses may ignore * the width parameter (which will likely be -1). *

* If Node's {@link #maxHeight(double)} is lower than this number, * {@code minHeight} takes precedence. This means the Node should never be resized below {@code minHeight}. *

* @see #isResizable() * @see #getContentBias() * * @param width the width that should be used if minimum height depends on it * @return the minimum height that the node should be resized to during layout * The result will never be NaN, nor will it ever be negative. */ public double minHeight(double width) { return prefHeight(width); } /** * Returns the node's preferred width for use in layout calculations. * If the node is resizable, its parent should treat this value as the * node's ideal width within its range. If the node is not resizable, * just returns its layoutBounds width, which should be treated as the rigid * width of the node. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a vertical content-bias, then callers * should pass in a height value that the preferred width should be based on. * If the node has either a horizontal or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a vertical content-bias should honor the height * parameter whether -1 or a positive value. All other subclasses may ignore * the height parameter (which will likely be -1). *

* @see #isResizable() * @see #getContentBias() * @see #autosize() * * @param height the height that should be used if preferred width depends on it * @return the preferred width that the node should be resized to during layout * The result will never be NaN, nor will it ever be negative. */ public double prefWidth(double height) { final double result = getLayoutBounds().getWidth(); return Double.isNaN(result) || result < 0 ? 0 : result; } /** * Returns the node's preferred height for use in layout calculations. * If the node is resizable, its parent should treat this value as the * node's ideal height within its range. If the node is not resizable, * just returns its layoutBounds height, which should be treated as the rigid * height of the node. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a horizontal content-bias, then callers * should pass in a width value that the preferred height should be based on. * If the node has either a vertical or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a horizontal content-bias should honor the height * parameter whether -1 or a positive value. All other subclasses may ignore * the height parameter (which will likely be -1). *

* @see #getContentBias() * @see #autosize() * * @param width the width that should be used if preferred height depends on it * @return the preferred height that the node should be resized to during layout * The result will never be NaN, nor will it ever be negative. */ public double prefHeight(double width) { final double result = getLayoutBounds().getHeight(); return Double.isNaN(result) || result < 0 ? 0 : result; } /** * Returns the node's maximum width for use in layout calculations. * If the node is resizable, its parent should not resize its width any * larger than this value. A value of Double.MAX_VALUE indicates the * parent may expand the node's width beyond its preferred without limits. *

* If the node is not resizable, returns its layoutBounds width. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a vertical content-bias, then callers * should pass in a height value that the maximum width should be based on. * If the node has either a horizontal or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a vertical content-bias should honor the height * parameter whether -1 or a positive value. All other subclasses may ignore * the height parameter (which will likely be -1). *

* If Node's {@link #minWidth(double)} is greater, it should take precedence * over the {@code maxWidth}. This means the Node should never be resized below {@code minWidth}. *

* @see #isResizable() * @see #getContentBias() * * @param height the height that should be used if maximum width depends on it * @return the maximum width that the node should be resized to during layout * The result will never be NaN, nor will it ever be negative. */ public double maxWidth(double height) { return prefWidth(height); } /** * Returns the node's maximum height for use in layout calculations. * If the node is resizable, its parent should not resize its height any * larger than this value. A value of Double.MAX_VALUE indicates the * parent may expand the node's height beyond its preferred without limits. *

* If the node is not resizable, returns its layoutBounds height. *

* Layout code which calls this method should first check the content-bias * of the node. If the node has a horizontal content-bias, then callers * should pass in a width value that the maximum height should be based on. * If the node has either a vertical or null content-bias, then the caller * should pass in -1. *

* Node subclasses with a horizontal content-bias should honor the width * parameter whether -1 or a positive value. All other subclasses may ignore * the width parameter (which will likely be -1). *

* If Node's {@link #minHeight(double)} is greater, it should take precedence * over the {@code maxHeight}. This means the Node should never be resized below {@code minHeight}. *

* @see #isResizable() * @see #getContentBias() * * @param width the width that should be used if maximum height depends on it * @return the maximum height that the node should be resized to during layout * The result will never be NaN, nor will it ever be negative. */ public double maxHeight(double width) { return prefHeight(width); } /** * If the node is resizable, will set its layout bounds to the specified * width and height. If the node is not resizable, this method is a no-op. *

* This method should generally only be called by parent nodes from their * layoutChildren() methods. All Parent classes will automatically resize * resizable children, so resizing done directly by the application will be * overridden by the node's parent, unless the child is unmanaged. *

* Parents are responsible for ensuring the width and height values fall * within the resizable node's preferred range. The autosize() method may * be used if the parent just needs to resize the node to its preferred size. * *

* @see #isResizable() * @see #getContentBias() * @see #autosize() * @see #minWidth(double) * @see #minHeight(double) * @see #prefWidth(double) * @see #prefHeight(double) * @see #maxWidth(double) * @see #maxHeight(double) * @see #getLayoutBounds() * * @param width the target layout bounds width * @param height the target layout bounds height */ public void resize(double width, double height) { } /** * If the node is resizable, will set its layout bounds to its current preferred * width and height. If the node is not resizable, this method is a no-op. *

* This method automatically queries the node's content-bias and if it's * horizontal, will pass in the node's preferred width to get the preferred * height; if vertical, will pass in the node's preferred height to get the width, * and if null, will compute the preferred width/height independently. *

* * @see #isResizable() * @see #getContentBias() * */ public final void autosize() { if (isResizable()) { Orientation contentBias = getContentBias(); double w, h; if (contentBias == null) { w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1)); h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1)); } else if (contentBias == Orientation.HORIZONTAL) { w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1)); h = boundedSize(prefHeight(w), minHeight(w), maxHeight(w)); } else { // bias == VERTICAL h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1)); w = boundedSize(prefWidth(h), minWidth(h), maxWidth(h)); } resize(w,h); } } double boundedSize(double value, double min, double max) { // if max < value, return max // if min > value, return min // if min > max, return min return Math.min(Math.max(value, min), Math.max(min,max)); } /** * If the node is resizable, will set its layout bounds to the specified * width and height. If the node is not resizable, the resize step is skipped. *

* Once the node has been resized (if resizable) then sets the node's layoutX * and layoutY translation properties in order to relocate it to x,y in the * parent's coordinate space. *

* This method should generally only be called by parent nodes from their * layoutChildren() methods. All Parent classes will automatically resize * resizable children, so resizing done directly by the application will be * overridden by the node's parent, unless the child is unmanaged. *

* Parents are responsible for ensuring the width and height values fall * within the resizable node's preferred range. The autosize() and relocate() * methods may be used if the parent just needs to resize the node to its * preferred size and reposition it. *

* @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 *

    *
  1. {@link #getTransforms transforms} ObservableList
  2. *
  3. {@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}
  4. *
  5. {@link #rotateProperty rotate}
  6. *
  7. {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}
  8. *
  9. {@link #translateXProperty translateX}, {@link #translateYProperty translateY}
  10. *
*

* 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. *

* 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. *

* This property will always have a non-null value. *

* Note that boundsInParent is automatically recomputed whenever the * geometry of a node changes, or when any of the following the change: * transforms ObservableList, 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 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}. * *

* 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. *

* This property will always have a non-null value. *

* Note that boundsInLocal is automatically recomputed whenever the * geometry of a node changes. For this reason, it is an error to bind any * of these values in a node to an expression that depends upon this variable. * For example, the x or y variables of a shape should never be bound * to boundsInLocal for the purpose of positioning the node. */ public final ReadOnlyObjectProperty 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. *

* 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. *

* 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. *

* 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. *

* 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 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. *

* 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. *

* This function will also invalidate the cached geom bounds, and then * invoke localBoundsChanged() which will eventually end up invoking a * chain of functions up the tree to ensure that each parent of this * Node is notified that its bounds may have also changed. *

* This function should be treated as though it were final. It is not * intended to be overridden by subclasses. * * @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)}. *

* Since 3D bounds cannot be converted with {@code rootScene} set to {@code true}, trying to convert 3D bounds will yield {@code null}. *

* @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. *

* The pickResult is updated if the found intersection is closer than * the currently held one. *

* 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 getTransforms() { return transformsProperty(); } private ObservableList 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. *

* 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. *

* 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. *

* 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. *

* 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}. *

* This variable can be used to alter the location of a Node without * disturbing its layout bounds, which makes it useful for animating a * node's location. *

* Note that this is a conditional feature. See * {@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. *

* 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. *

* 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. *

* 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. *

* 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. *

* 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. *

* 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. *

* 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}. *

* 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. *

* The pivot point about which the rotation occurs is the center of the * untransformed {@link #layoutBoundsProperty layoutBounds}. *

* 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}. *

* 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 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 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. * *

* 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. *

* * @since JavaFX 2.2 */ public final ReadOnlyObjectProperty 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 rotationAxis; private ObservableList 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 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(); } localToSceneListeners.add(listener); super.addListener(listener); } @Override public void addListener(ChangeListener listener) { incListenerReasons(); if (localToSceneListeners == null) { localToSceneListeners = new LinkedList(); } 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 listener) { if (localToSceneListeners != null && localToSceneListeners.remove(listener)) { decListenerReasons(); } super.removeListener(listener); } } public final ReadOnlyObjectProperty 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 rotationAxisProperty() { if (rotationAxis == null) { rotationAxis = new ObjectPropertyBase( 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 getTransforms() { if (transforms == null) { transforms = new TrackableObservableList() { @Override protected void onChanged(Change 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; private EffectiveOrientationProperty effectiveNodeOrientationProperty; private static final byte EFFECTIVE_ORIENTATION_LTR = 0; private static final byte EFFECTIVE_ORIENTATION_RTL = 1; private static final byte EFFECTIVE_ORIENTATION_MASK = 1; private static final byte AUTOMATIC_ORIENTATION_LTR = 0; private static final byte AUTOMATIC_ORIENTATION_RTL = 2; private static final byte AUTOMATIC_ORIENTATION_MASK = 2; private byte resolvedNodeOrientation = EFFECTIVE_ORIENTATION_LTR | AUTOMATIC_ORIENTATION_LTR; public final void setNodeOrientation(NodeOrientation orientation) { nodeOrientationProperty().set(orientation); } public final NodeOrientation getNodeOrientation() { return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get(); } /** * Property holding NodeOrientation. *

* Node orientation describes the flow of visual data within a node. * In the English speaking world, visual data normally flows from * left-to-right. In an Arabic or Hebrew world, visual data flows * from right-to-left. This is consistent with the reading order * of text in both worlds. The default value is left-to-right. *

* * @return NodeOrientation * @since JavaFX 8.0 */ public final ObjectProperty nodeOrientationProperty() { if (nodeOrientation == null) { nodeOrientation = new StyleableObjectProperty(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 effectiveNodeOrientationProperty() { if (effectiveNodeOrientationProperty == null) { effectiveNodeOrientationProperty = new EffectiveOrientationProperty(); } return effectiveNodeOrientationProperty; } /** * Determines whether a node should be mirrored when node orientation * is right-to-left. *

* When a node is mirrored, the origin is automatically moved to the * top right corner causing the node to layout children and draw from * right to left using a mirroring transformation. Some nodes may wish * to draw from right to left without using a transformation. These * nodes will will answer {@code false} and implement right-to-left * orientation without using the automatic transformation. *

* @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 { @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; private ObjectProperty clip; private ObjectProperty cursor; private ObjectProperty depthTest; private BooleanProperty disable; private ObjectProperty effect; private ObjectProperty inputMethodRequests; private BooleanProperty mouseTransparent; public final Bounds getBoundsInParent() { return boundsInParentProperty().get(); } public final ReadOnlyObjectProperty 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 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 cacheHintProperty() { if (cacheHint == null) { cacheHint = new ObjectPropertyBase(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 clipProperty() { if (clip == null) { clip = new ObjectPropertyBase(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 cursorProperty() { if (cursor == null) { cursor = new StyleableObjectProperty(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 depthTestProperty() { if (depthTest == null) { depthTest = new ObjectPropertyBase(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 effectProperty() { if (effect == null) { effect = new StyleableObjectProperty(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 inputMethodRequestsProperty() { if (inputMethodRequests == null) { inputMethodRequests = new SimpleObjectProperty( 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. * *

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 value) { onContextMenuRequestedProperty().set(value); } public final EventHandler 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> onContextMenuRequestedProperty() { return getEventHandlerProperties().onContextMenuRequestedProperty(); } public final void setOnMouseClicked( EventHandler value) { onMouseClickedProperty().set(value); } public final EventHandler 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> onMouseClickedProperty() { return getEventHandlerProperties().onMouseClickedProperty(); } public final void setOnMouseDragged( EventHandler value) { onMouseDraggedProperty().set(value); } public final EventHandler 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> onMouseDraggedProperty() { return getEventHandlerProperties().onMouseDraggedProperty(); } public final void setOnMouseEntered( EventHandler value) { onMouseEnteredProperty().set(value); } public final EventHandler getOnMouseEntered() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnMouseEntered(); } /** * Defines a function to be called when the mouse enters this {@code Node}. */ public final ObjectProperty> onMouseEnteredProperty() { return getEventHandlerProperties().onMouseEnteredProperty(); } public final void setOnMouseExited( EventHandler value) { onMouseExitedProperty().set(value); } public final EventHandler getOnMouseExited() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnMouseExited(); } /** * Defines a function to be called when the mouse exits this {@code Node}. */ public final ObjectProperty> onMouseExitedProperty() { return getEventHandlerProperties().onMouseExitedProperty(); } public final void setOnMouseMoved( EventHandler value) { onMouseMovedProperty().set(value); } public final EventHandler 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> onMouseMovedProperty() { return getEventHandlerProperties().onMouseMovedProperty(); } public final void setOnMousePressed( EventHandler value) { onMousePressedProperty().set(value); } public final EventHandler 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> onMousePressedProperty() { return getEventHandlerProperties().onMousePressedProperty(); } public final void setOnMouseReleased( EventHandler value) { onMouseReleasedProperty().set(value); } public final EventHandler 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> onMouseReleasedProperty() { return getEventHandlerProperties().onMouseReleasedProperty(); } public final void setOnDragDetected( EventHandler value) { onDragDetectedProperty().set(value); } public final EventHandler 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> onDragDetectedProperty() { return getEventHandlerProperties().onDragDetectedProperty(); } public final void setOnMouseDragOver( EventHandler value) { onMouseDragOverProperty().set(value); } public final EventHandler 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> onMouseDragOverProperty() { return getEventHandlerProperties().onMouseDragOverProperty(); } public final void setOnMouseDragReleased( EventHandler value) { onMouseDragReleasedProperty().set(value); } public final EventHandler 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> onMouseDragReleasedProperty() { return getEventHandlerProperties().onMouseDragReleasedProperty(); } public final void setOnMouseDragEntered( EventHandler value) { onMouseDragEnteredProperty().set(value); } public final EventHandler 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> onMouseDragEnteredProperty() { return getEventHandlerProperties().onMouseDragEnteredProperty(); } public final void setOnMouseDragExited( EventHandler value) { onMouseDragExitedProperty().set(value); } public final EventHandler 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> onMouseDragExitedProperty() { return getEventHandlerProperties().onMouseDragExitedProperty(); } /* ************************************************************************* * * * Gestures Handling * * * **************************************************************************/ public final void setOnScrollStarted( EventHandler value) { onScrollStartedProperty().set(value); } public final EventHandler 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> onScrollStartedProperty() { return getEventHandlerProperties().onScrollStartedProperty(); } public final void setOnScroll( EventHandler value) { onScrollProperty().set(value); } public final EventHandler getOnScroll() { return (eventHandlerProperties == null) ? null : eventHandlerProperties.getOnScroll(); } /** * Defines a function to be called when user performs a scrolling action. */ public final ObjectProperty> onScrollProperty() { return getEventHandlerProperties().onScrollProperty(); } public final void setOnScrollFinished( EventHandler value) { onScrollFinishedProperty().set(value); } public final EventHandler 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> onScrollFinishedProperty() { return getEventHandlerProperties().onScrollFinishedProperty(); } public final void setOnRotationStarted( EventHandler value) { onRotationStartedProperty().set(value); } public final EventHandler 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> onRotationStartedProperty() { return getEventHandlerProperties().onRotationStartedProperty(); } public final void setOnRotate( EventHandler value) { onRotateProperty().set(value); } public final EventHandler 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> onRotateProperty() { return getEventHandlerProperties().onRotateProperty(); } public final void setOnRotationFinished( EventHandler value) { onRotationFinishedProperty().set(value); } public final EventHandler 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> onRotationFinishedProperty() { return getEventHandlerProperties().onRotationFinishedProperty(); } public final void setOnZoomStarted( EventHandler value) { onZoomStartedProperty().set(value); } public final EventHandler 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> onZoomStartedProperty() { return getEventHandlerProperties().onZoomStartedProperty(); } public final void setOnZoom( EventHandler value) { onZoomProperty().set(value); } public final EventHandler 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> onZoomProperty() { return getEventHandlerProperties().onZoomProperty(); } public final void setOnZoomFinished( EventHandler value) { onZoomFinishedProperty().set(value); } public final EventHandler 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> onZoomFinishedProperty() { return getEventHandlerProperties().onZoomFinishedProperty(); } public final void setOnSwipeUp( EventHandler value) { onSwipeUpProperty().set(value); } public final EventHandler 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> onSwipeUpProperty() { return getEventHandlerProperties().onSwipeUpProperty(); } public final void setOnSwipeDown( EventHandler value) { onSwipeDownProperty().set(value); } public final EventHandler 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> onSwipeDownProperty() { return getEventHandlerProperties().onSwipeDownProperty(); } public final void setOnSwipeLeft( EventHandler value) { onSwipeLeftProperty().set(value); } public final EventHandler 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> onSwipeLeftProperty() { return getEventHandlerProperties().onSwipeLeftProperty(); } public final void setOnSwipeRight( EventHandler value) { onSwipeRightProperty().set(value); } public final EventHandler 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> onSwipeRightProperty() { return getEventHandlerProperties().onSwipeRightProperty(); } /* ************************************************************************* * * * Touch Handling * * * **************************************************************************/ public final void setOnTouchPressed( EventHandler value) { onTouchPressedProperty().set(value); } public final EventHandler 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> onTouchPressedProperty() { return getEventHandlerProperties().onTouchPressedProperty(); } public final void setOnTouchMoved( EventHandler value) { onTouchMovedProperty().set(value); } public final EventHandler 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> onTouchMovedProperty() { return getEventHandlerProperties().onTouchMovedProperty(); } public final void setOnTouchReleased( EventHandler value) { onTouchReleasedProperty().set(value); } public final EventHandler 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> onTouchReleasedProperty() { return getEventHandlerProperties().onTouchReleasedProperty(); } public final void setOnTouchStationary( EventHandler value) { onTouchStationaryProperty().set(value); } public final EventHandler 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> onTouchStationaryProperty() { return getEventHandlerProperties().onTouchStationaryProperty(); } /* ************************************************************************* * * * Keyboard Handling * * * **************************************************************************/ public final void setOnKeyPressed( EventHandler value) { onKeyPressedProperty().set(value); } public final EventHandler 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> onKeyPressedProperty() { return getEventHandlerProperties().onKeyPressedProperty(); } public final void setOnKeyReleased( EventHandler value) { onKeyReleasedProperty().set(value); } public final EventHandler 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> onKeyReleasedProperty() { return getEventHandlerProperties().onKeyReleasedProperty(); } public final void setOnKeyTyped( EventHandler value) { onKeyTypedProperty().set(value); } public final EventHandler 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> onKeyTypedProperty() { return getEventHandlerProperties().onKeyTypedProperty(); } /* ************************************************************************* * * * Input Method Handling * * * **************************************************************************/ public final void setOnInputMethodTextChanged( EventHandler value) { onInputMethodTextChangedProperty().set(value); } public final EventHandler 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. *

* When the {@code Node} loses the input focus, the JavaFX runtime * automatically commits the existing composed text if any. */ public final ObjectProperty> 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 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 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 listener) { helper = ExpressionHelper.addListener(helper, this, listener); } @Override public void removeListener(ChangeListener 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; public final void setEventDispatcher(EventDispatcher value) { eventDispatcherProperty().set(value); } public final EventDispatcher getEventDispatcher() { return eventDispatcherProperty().get(); } public final ObjectProperty 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 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 void addEventHandler( final EventType eventType, final EventHandler 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 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 void removeEventHandler( final EventType eventType, final EventHandler 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 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 void addEventFilter( final EventType eventType, final EventHandler 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 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 void removeEventFilter( final EventType eventType, final EventHandler 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 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 void setEventHandler( final EventType eventType, final EventHandler eventHandler) { getInternalEventDispatcher().getEventHandlerManager() .setEventHandler(eventType, eventHandler); } private NodeEventDispatcher getInternalEventDispatcher() { initializeInternalEventDispatcher(); return internalEventDispatcher; } private void initializeInternalEventDispatcher() { if (internalEventDispatcher == null) { internalEventDispatcher = createInternalEventDispatcher(); eventDispatcher = new SimpleObjectProperty( 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. *

* 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 CURSOR = new CssMetaData("-fx-cursor", CursorConverter.getInstance()) { @Override public boolean isSettable(Node node) { return node.miscProperties == null || node.miscProperties.canSetCursor(); } @Override public StyleableProperty getStyleableProperty(Node node) { return (StyleableProperty)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 EFFECT = new CssMetaData("-fx-effect", EffectConverter.getInstance()) { @Override public boolean isSettable(Node node) { return node.miscProperties == null || node.miscProperties.canSetEffect(); } @Override public StyleableProperty getStyleableProperty(Node node) { return (StyleableProperty)node.effectProperty(); } }; private static final CssMetaData FOCUS_TRAVERSABLE = new CssMetaData("-fx-focus-traversable", BooleanConverter.getInstance(), Boolean.FALSE) { @Override public boolean isSettable(Node node) { return node.focusTraversable == null || !node.focusTraversable.isBound(); } @Override public StyleableProperty getStyleableProperty(Node node) { return (StyleableProperty)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 OPACITY = new CssMetaData("-fx-opacity", SizeConverter.getInstance(), 1.0) { @Override public boolean isSettable(Node node) { return node.opacity == null || !node.opacity.isBound(); } @Override public StyleableProperty getStyleableProperty(Node node) { return (StyleableProperty)node.opacityProperty(); } }; private static final CssMetaData BLEND_MODE = new CssMetaData("-fx-blend-mode", new EnumConverter(BlendMode.class)) { @Override public boolean isSettable(Node node) { return node.blendMode == null || !node.blendMode.isBound(); } @Override public StyleableProperty getStyleableProperty(Node node) { return (StyleableProperty)node.blendModeProperty(); } }; private static final CssMetaData ROTATE = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.rotateProperty(); } }; private static final CssMetaData SCALE_X = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.scaleXProperty(); } }; private static final CssMetaData SCALE_Y = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.scaleYProperty(); } }; private static final CssMetaData SCALE_Z = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.scaleZProperty(); } }; private static final CssMetaData TRANSLATE_X = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.translateXProperty(); } }; private static final CssMetaData TRANSLATE_Y = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.translateYProperty(); } }; private static final CssMetaData TRANSLATE_Z = new CssMetaData("-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 getStyleableProperty(Node node) { return (StyleableProperty)node.translateZProperty(); } }; private static final CssMetaData VISIBILITY = new CssMetaData("visibility", new StyleConverter() { @Override // [ visible | hidden | collapse | inherit ] public Boolean convert(ParsedValue 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 getStyleableProperty(Node node) { return (StyleableProperty)node.visibleProperty(); } }; private static final List> STYLEABLES; static { final List> styleables = new ArrayList>(); 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> 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> 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