javafx.scene.Node Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2024, 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 com.sun.javafx.scene.traversal.TraversalMethod;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanPropertyBase;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectPropertyBase;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.css.CssMetaData;
import javafx.css.ParsedValue;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.StyleOrigin;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.geometry.Rectangle2D;
import javafx.scene.effect.BlendMode;
import javafx.scene.effect.Effect;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.InputEvent;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodRequests;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.input.RotateEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.SwipeEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.input.ZoomEvent;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Window;
import javafx.util.Callback;
import java.security.AccessControlContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
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.collections.TrackableObservableList;
import com.sun.javafx.collections.UnmodifiableListSet;
import com.sun.javafx.css.PseudoClassState;
import com.sun.javafx.css.TransitionDefinition;
import com.sun.javafx.css.TransitionDefinitionConverter;
import com.sun.javafx.css.TransitionDefinitionCssMetaData;
import com.sun.javafx.css.TransitionTimer;
import javafx.css.Selector;
import javafx.css.Style;
import javafx.css.converter.BooleanConverter;
import javafx.css.converter.CursorConverter;
import javafx.css.converter.EffectConverter;
import javafx.css.converter.EnumConverter;
import javafx.css.converter.SizeConverter;
import com.sun.javafx.effect.EffectDirtyBits;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.BoxBounds;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.GeneralTransform3D;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.scene.BoundsAccessor;
import com.sun.javafx.scene.CameraHelper;
import com.sun.javafx.scene.CssFlags;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.EventHandlerProperties;
import com.sun.javafx.scene.LayoutFlags;
import com.sun.javafx.scene.NodeEventDispatcher;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.scene.SceneUtils;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.scene.transform.TransformHelper;
import com.sun.javafx.scene.transform.TransformUtils;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.prism.impl.PrismSettings;
import com.sun.scenario.effect.EffectHelper;
import javafx.scene.shape.Shape3D;
import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;
/**
* Base class for scene graph nodes. A scene graph is a set of tree data structures
* where every item has zero or one parent, and each item is either
* a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
*
* Each item in the scene graph is called a {@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 {@link Scene} in a {@link Window} that is
* {@link Window#isShowing showing}.
* An application must attach nodes to such a Scene or modify them on the JavaFX
* Application Thread.
*
*
* The JavaFX Application Thread is created as part of the startup process for
* the JavaFX runtime. See the {@link javafx.application.Application} class and
* the {@link Platform#startup(Runnable)} method for more information.
*
*
*
* An application should not extend the Node class directly. Doing so may lead to
* an UnsupportedOperationException being thrown.
*
*
* String ID
*
* Each node in the scene graph can be given a unique {@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 multiplied by the scale factor. As with rotation
* transformations, scaling transformations are applied about a "pivot" point.
* You can think of this as the point in the {@code 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. Custom transforms are applied using the
* {@link #getTransforms transforms} list. Predefined transforms are applied using the properties specified below.
* The matrices that represent the transforms are multiplied in this order:
*
* - Layout ({@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}) and translate
* ({@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #translateZProperty translateZ})
* - Rotate ({@link #rotateProperty rotate})
* - Scale ({@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}, {@link #scaleZProperty scaleZ})
* - Transforms list ({@link #getTransforms transforms}) starting from element 0
*
* The transforms are applied in the reverse order of the matrix multiplication outlined above: last element of the transforms list
* to 0th element, scale, rotate, and layout and translate. By applying the transforms in this order, the bounds in the local
* coordinates of the node are transformed to the bounds in the parent coordinate of the node (see the
* Bounding Rectangles section).
*
* 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 as specified in the Transformations section.
* 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 {
/*
* Store the singleton instance of the NodeHelper subclass corresponding
* to the subclass of this instance of Node
*/
private NodeHelper nodeHelper = null;
static {
PerformanceTracker.logEvent("Node class loaded");
// This is used by classes in different packages to get access to
// private and package private methods.
NodeHelper.setNodeAccessor(new NodeHelper.NodeAccessor() {
@Override
public NodeHelper getHelper(Node node) {
return node.nodeHelper;
}
@Override
public void setHelper(Node node, NodeHelper nodeHelper) {
node.nodeHelper = nodeHelper;
}
@Override
public void doMarkDirty(Node node, DirtyBits dirtyBit) {
node.doMarkDirty(dirtyBit);
}
@Override
public void doUpdatePeer(Node node) {
node.doUpdatePeer();
}
@Override
public BaseTransform getLeafTransform(Node node) {
return node.getLeafTransform();
}
@Override
public Bounds doComputeLayoutBounds(Node node) {
return node.doComputeLayoutBounds();
}
@Override
public void doTransformsChanged(Node node) {
node.doTransformsChanged();
}
@Override
public void doPickNodeLocal(Node node, PickRay localPickRay,
PickResultChooser result) {
node.doPickNodeLocal(localPickRay, result);
}
@Override
public boolean doComputeIntersects(Node node, PickRay pickRay,
PickResultChooser pickResult) {
return node.doComputeIntersects(pickRay, pickResult);
}
@Override
public void doGeomChanged(Node node) {
node.doGeomChanged();
}
@Override
public void doNotifyLayoutBoundsChanged(Node node) {
node.doNotifyLayoutBoundsChanged();
}
@Override
public void doProcessCSS(Node node) {
node.doProcessCSS();
}
@Override
public boolean isDirty(Node node, DirtyBits dirtyBit) {
return node.isDirty(dirtyBit);
}
@Override
public boolean isDirtyEmpty(Node node) {
return node.isDirtyEmpty();
}
@Override
public void syncPeer(Node node) {
node.syncPeer();
}
@Override
public void layoutBoundsChanged(Node node) {
node.layoutBoundsChanged();
}
@Override
public
P getPeer(Node node) {
return node.getPeer();
}
@Override
public void setShowMnemonics(Node node, boolean value) {
node.setShowMnemonics(value);
}
@Override
public boolean isShowMnemonics(Node node) {
return node.isShowMnemonics();
}
@Override
public BooleanProperty showMnemonicsProperty(Node node) {
return node.showMnemonicsProperty();
}
@Override
public boolean traverse(Node node, Direction direction, TraversalMethod method) {
return node.traverse(direction, method);
}
@Override
public double getPivotX(Node node) {
return node.getPivotX();
}
@Override
public double getPivotY(Node node) {
return node.getPivotY();
}
@Override
public double getPivotZ(Node node) {
return node.getPivotZ();
}
@Override
public void pickNode(Node node,PickRay pickRay,
PickResultChooser result) {
node.pickNode(pickRay, result);
}
@Override
public boolean intersects(Node node, PickRay pickRay,
PickResultChooser pickResult) {
return node.intersects(pickRay, pickResult);
}
@Override
public double intersectsBounds(Node node, PickRay pickRay) {
return node.intersectsBounds(pickRay);
}
@Override
public void layoutNodeForPrinting(Node node) {
node.doCSSLayoutSyncForSnapshot();
}
@Override
public boolean isDerivedDepthTest(Node node) {
return node.isDerivedDepthTest();
}
@Override
public SubScene getSubScene(Node node) {
return node.getSubScene();
}
@Override
public void setLabeledBy(Node node, Node labeledBy) {
node.labeledBy = labeledBy;
}
@Override
public Accessible getAccessible(Node node) {
return node.getAccessible();
}
@Override
public void reapplyCSS(Node node) {
node.reapplyCSS();
}
@Override
public void recalculateRelativeSizeProperties(Node node, Font fontForRelativeSizes) {
node.recalculateRelativeSizeProperties(fontForRelativeSizes);
}
@Override
public boolean isTreeVisible(Node node) {
return node.isTreeVisible();
}
@Override
public BooleanExpression treeVisibleProperty(Node node) {
return node.treeVisibleProperty();
}
@Override
public boolean isTreeShowing(Node node) {
return node.isTreeShowing();
}
@Override
public List