1 /*
2 * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javafx.scene;
27
28
29 import com.sun.javafx.geometry.BoundsUtils;
30 import javafx.application.Platform;
31 import javafx.beans.InvalidationListener;
32 import javafx.beans.Observable;
33 import javafx.beans.binding.BooleanExpression;
34 import javafx.beans.property.BooleanProperty;
35 import javafx.beans.property.BooleanPropertyBase;
36 import javafx.beans.property.DoubleProperty;
37 import javafx.beans.property.DoublePropertyBase;
38 import javafx.beans.property.IntegerProperty;
39 import javafx.beans.property.ObjectProperty;
40 import javafx.beans.property.ObjectPropertyBase;
41 import javafx.beans.property.ReadOnlyBooleanProperty;
42 import javafx.beans.property.ReadOnlyBooleanPropertyBase;
43 import javafx.beans.property.ReadOnlyBooleanWrapper;
44 import javafx.beans.property.ReadOnlyObjectProperty;
45 import javafx.beans.property.ReadOnlyObjectPropertyBase;
46 import javafx.beans.property.ReadOnlyObjectWrapper;
47 import javafx.beans.property.SimpleBooleanProperty;
48 import javafx.beans.property.SimpleObjectProperty;
49 import javafx.beans.property.StringProperty;
50 import javafx.beans.property.StringPropertyBase;
51 import javafx.beans.value.ChangeListener;
52 import javafx.collections.FXCollections;
53 import javafx.collections.ListChangeListener.Change;
54 import javafx.collections.ObservableList;
55 import javafx.collections.ObservableMap;
56 import javafx.collections.ObservableSet;
57 import javafx.css.CssMetaData;
58 import javafx.css.ParsedValue;
59 import javafx.css.PseudoClass;
60 import javafx.css.StyleConverter;
61 import javafx.css.Styleable;
62 import javafx.css.StyleableBooleanProperty;
63 import javafx.css.StyleableDoubleProperty;
64 import javafx.css.StyleableObjectProperty;
65 import javafx.css.StyleableProperty;
66 import javafx.event.Event;
67 import javafx.event.EventDispatchChain;
68 import javafx.event.EventDispatcher;
69 import javafx.event.EventHandler;
70 import javafx.event.EventTarget;
71 import javafx.event.EventType;
72 import javafx.geometry.BoundingBox;
73 import javafx.geometry.Bounds;
74 import javafx.geometry.NodeOrientation;
75 import javafx.geometry.Orientation;
76 import javafx.geometry.Point2D;
77 import javafx.geometry.Point3D;
78 import javafx.geometry.Rectangle2D;
79 import javafx.scene.effect.BlendMode;
80 import javafx.scene.effect.Effect;
81 import javafx.scene.image.WritableImage;
82 import javafx.scene.input.ContextMenuEvent;
83 import javafx.scene.input.DragEvent;
84 import javafx.scene.input.Dragboard;
85 import javafx.scene.input.InputEvent;
86 import javafx.scene.input.InputMethodEvent;
87 import javafx.scene.input.InputMethodRequests;
88 import javafx.scene.input.KeyEvent;
89 import javafx.scene.input.MouseDragEvent;
90 import javafx.scene.input.MouseEvent;
91 import javafx.scene.input.PickResult;
92 import javafx.scene.input.RotateEvent;
93 import javafx.scene.input.ScrollEvent;
94 import javafx.scene.input.SwipeEvent;
95 import javafx.scene.input.TouchEvent;
96 import javafx.scene.input.TransferMode;
97 import javafx.scene.input.ZoomEvent;
98 import javafx.scene.text.Font;
99 import javafx.scene.transform.Rotate;
100 import javafx.scene.transform.Transform;
101 import javafx.stage.Window;
102 import javafx.util.Callback;
103 import java.security.AccessControlContext;
104
105 import java.util.ArrayList;
106 import java.util.Collections;
107 import java.util.EnumSet;
108 import java.util.HashMap;
109 import java.util.LinkedList;
110 import java.util.List;
111 import java.util.Map;
112 import java.util.Set;
113
114 import com.sun.glass.ui.Accessible;
115 import com.sun.glass.ui.Application;
116 import com.sun.javafx.util.Logging;
117 import com.sun.javafx.util.TempState;
118 import com.sun.javafx.util.Utils;
119 import com.sun.javafx.beans.IDProperty;
120 import com.sun.javafx.beans.event.AbstractNotifyListener;
121 import com.sun.javafx.binding.ExpressionHelper;
122 import com.sun.javafx.collections.TrackableObservableList;
123 import com.sun.javafx.collections.UnmodifiableListSet;
124 import com.sun.javafx.css.PseudoClassState;
125 import javafx.css.Selector;
126 import javafx.css.Style;
127 import javafx.css.converter.BooleanConverter;
128 import javafx.css.converter.CursorConverter;
129 import javafx.css.converter.EffectConverter;
130 import javafx.css.converter.EnumConverter;
131 import javafx.css.converter.SizeConverter;
132 import com.sun.javafx.effect.EffectDirtyBits;
133 import com.sun.javafx.geom.BaseBounds;
134 import com.sun.javafx.geom.BoxBounds;
135 import com.sun.javafx.geom.PickRay;
136 import com.sun.javafx.geom.RectBounds;
137 import com.sun.javafx.geom.Vec3d;
138 import com.sun.javafx.geom.transform.Affine3D;
139 import com.sun.javafx.geom.transform.BaseTransform;
140 import com.sun.javafx.geom.transform.GeneralTransform3D;
141 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
142 import com.sun.javafx.perf.PerformanceTracker;
143 import com.sun.javafx.scene.BoundsAccessor;
144 import com.sun.javafx.scene.CameraHelper;
145 import com.sun.javafx.scene.CssFlags;
146 import com.sun.javafx.scene.DirtyBits;
147 import com.sun.javafx.scene.EventHandlerProperties;
148 import com.sun.javafx.scene.LayoutFlags;
149 import com.sun.javafx.scene.NodeEventDispatcher;
150 import com.sun.javafx.scene.NodeHelper;
151 import com.sun.javafx.scene.SceneHelper;
152 import com.sun.javafx.scene.SceneUtils;
153 import com.sun.javafx.scene.input.PickResultChooser;
154 import com.sun.javafx.scene.transform.TransformHelper;
155 import com.sun.javafx.scene.transform.TransformUtils;
156 import com.sun.javafx.scene.traversal.Direction;
157 import com.sun.javafx.sg.prism.NGNode;
158 import com.sun.javafx.tk.Toolkit;
159 import com.sun.prism.impl.PrismSettings;
160 import com.sun.scenario.effect.EffectHelper;
161
162 import javafx.scene.shape.Shape3D;
163 import com.sun.javafx.logging.PlatformLogger;
164 import com.sun.javafx.logging.PlatformLogger.Level;
165
166 /**
167 * Base class for scene graph nodes. A scene graph is a set of tree data structures
168 * where every item has zero or one parent, and each item is either
169 * a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
170 * <p>
171 * Each item in the scene graph is called a {@code Node}. Branch nodes are
172 * of type {@link Parent}, whose concrete subclasses are {@link Group},
173 * {@link javafx.scene.layout.Region}, and {@link javafx.scene.control.Control},
174 * or subclasses thereof.
175 * <p>
176 * Leaf nodes are classes such as
177 * {@link javafx.scene.shape.Rectangle}, {@link javafx.scene.text.Text},
178 * {@link javafx.scene.image.ImageView}, {@link javafx.scene.media.MediaView},
179 * or other such leaf classes which cannot have children. Only a single node within
180 * each scene graph tree will have no parent, which is referred to as the "root" node.
181 * <p>
182 * There may be several trees in the scene graph. Some trees may be part of
183 * a {@link Scene}, in which case they are eligible to be displayed.
184 * Other trees might not be part of any {@link Scene}.
185 * <p>
186 * A node may occur at most once anywhere in the scene graph. Specifically,
187 * a node must appear no more than once in all of the following:
188 * as the root node of a {@link Scene},
189 * the children ObservableList of a {@link Parent},
190 * or as the clip of a {@link Node}.
191 * <p>
192 * The scene graph must not have cycles. A cycle would exist if a node is
193 * an ancestor of itself in the tree, considering the {@link Group} content
194 * ObservableList, {@link Parent} children ObservableList, and {@link Node} clip relationships
195 * mentioned above.
196 * <p>
197 * If a program adds a child node to a Parent (including Group, Region, etc)
198 * and that node is already a child of a different Parent or the root of a Scene,
199 * the node is automatically (and silently) removed from its former parent.
200 * If a program attempts to modify the scene graph in any other way that violates
201 * the above rules, an exception is thrown, the modification attempt is ignored
202 * and the scene graph is restored to its previous state.
203 * <p>
204 * It is possible to rearrange the structure of the scene graph, for
205 * example, to move a subtree from one location in the scene graph to
206 * another. In order to do this, one would normally remove the subtree from
207 * its old location before inserting it at the new location. However, the
208 * subtree will be automatically removed as described above if the application
209 * doesn't explicitly remove it.
210 * <p>
211 * Node objects may be constructed and modified on any thread as long they are
212 * not yet attached to a {@link Scene} in a {@link Window} that is
213 * {@link Window#isShowing showing}.
214 * An application must attach nodes to such a Scene or modify them on the JavaFX
215 * Application Thread.
216 *
217 * <p>
218 * The JavaFX Application Thread is created as part of the startup process for
219 * the JavaFX runtime. See the {@link javafx.application.Application} class and
220 * the {@link Platform#startup(Runnable)} method for more information.
221 * </p>
222 *
223 * <p>
224 * An application should not extend the Node class directly. Doing so may lead to
225 * an UnsupportedOperationException being thrown.
226 * </p>
227 *
228 * <h2>String ID</h2>
229 * <p>
230 * Each node in the scene graph can be given a unique {@link #idProperty id}. This id is
231 * much like the "id" attribute of an HTML tag in that it is up to the designer
232 * and developer to ensure that the {@code id} is unique within the scene graph.
233 * A convenience function called {@link #lookup(String)} can be used to find
234 * a node with a unique id within the scene graph, or within a subtree of the
235 * scene graph. The id can also be used identify nodes for applying styles; see
236 * the CSS section below.
237 *
238 * <h2>Coordinate System</h2>
239 * <p>
240 * The {@code Node} class defines a traditional computer graphics "local"
241 * coordinate system in which the {@code x} axis increases to the right and the
242 * {@code y} axis increases downwards. The concrete node classes for shapes
243 * provide variables for defining the geometry and location of the shape
244 * within this local coordinate space. For example,
245 * {@link javafx.scene.shape.Rectangle} provides {@code x}, {@code y},
246 * {@code width}, {@code height} variables while
247 * {@link javafx.scene.shape.Circle} provides {@code centerX}, {@code centerY},
248 * and {@code radius}.
249 * <p>
250 * At the device pixel level, integer coordinates map onto the corners and
251 * cracks between the pixels and the centers of the pixels appear at the
252 * midpoints between integer pixel locations. Because all coordinate values
253 * are specified with floating point numbers, coordinates can precisely
254 * point to these corners (when the floating point values have exact integer
255 * values) or to any location on the pixel. For example, a coordinate of
256 * {@code (0.5, 0.5)} would point to the center of the upper left pixel on the
257 * {@code Stage}. Similarly, a rectangle at {@code (0, 0)} with dimensions
258 * of {@code 10} by {@code 10} would span from the upper left corner of the
259 * upper left pixel on the {@code Stage} to the lower right corner of the
260 * 10th pixel on the 10th scanline. The pixel center of the last pixel
261 * inside that rectangle would be at the coordinates {@code (9.5, 9.5)}.
262 * <p>
263 * In practice, most nodes have transformations applied to their coordinate
264 * system as mentioned below. As a result, the information above describing
265 * the alignment of device coordinates to the pixel grid is relative to
266 * the transformed coordinates, not the local coordinates of the nodes.
267 * The {@link javafx.scene.shape.Shape Shape} class describes some additional
268 * important context-specific information about coordinate mapping and how
269 * it can affect rendering.
270 *
271 * <h2>Transformations</h2>
272 * <p>
273 * Any {@code Node} can have transformations applied to it. These include
274 * translation, rotation, scaling, or shearing.
275 * <p>
276 * A <b>translation</b> transformation is one which shifts the origin of the
277 * node's coordinate space along either the x or y axis. For example, if you
278 * create a {@link javafx.scene.shape.Rectangle} which is drawn at the origin
279 * (x=0, y=0) and has a width of 100 and a height of 50, and then apply a
280 * {@link javafx.scene.transform.Translate} with a shift of 10 along the x axis
281 * (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain
282 * 100 points wide and 50 tall. Note that the origin was shifted, not the
283 * {@code x} variable of the rectangle.
284 * <p>
285 * A common node transform is a translation by an integer distance, most often
286 * used to lay out nodes on the stage. Such integer translations maintain the
287 * device pixel mapping so that local coordinates that are integers still
288 * map to the cracks between pixels.
289 * <p>
290 * A <b>rotation</b> transformation is one which rotates the coordinate space of
291 * the node about a specified "pivot" point, causing the node to appear rotated.
292 * For example, if you create a {@link javafx.scene.shape.Rectangle} which is
293 * drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and
294 * you apply a {@link javafx.scene.transform.Rotate} with a 90 degree rotation
295 * (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then
296 * the rectangle will be drawn as if its x and y were zero but its height was
297 * 100 and its width -30. That is, it is as if a pin is being stuck at the top
298 * left corner and the rectangle is rotating 90 degrees clockwise around that
299 * pin. If the pivot point is instead placed in the center of the rectangle
300 * (at point x=50, y=15) then the rectangle will instead appear to rotate about
301 * its center.
302 * <p>
303 * Note that as with all transformations, the x, y, width, and height variables
304 * of the rectangle (which remain relative to the local coordinate space) have
305 * not changed, but rather the transformation alters the entire coordinate space
306 * of the rectangle.
307 * <p>
308 * A <b>scaling</b> transformation causes a node to either appear larger or
309 * smaller depending on the scaling factor. Scaling alters the coordinate space
310 * of the node such that each unit of distance along the axis in local
311 * coordinates is multiplied by the scale factor. As with rotation
312 * transformations, scaling transformations are applied about a "pivot" point.
313 * You can think of this as the point in the Node around which you "zoom". For
314 * example, if you create a {@link javafx.scene.shape.Rectangle} with a
315 * {@code strokeWidth} of 5, and a width and height of 50, and you apply a
316 * {@link javafx.scene.transform.Scale} with scale factors (x=2.0, y=2.0) and
317 * a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle
318 * (including the stroke) will double in size, growing to the right and
319 * downwards from the origin.
320 * <p>
321 * A <b>shearing</b> transformation, sometimes called a skew, effectively
322 * rotates one axis so that the x and y axes are no longer perpendicular.
323 * <p>
324 * Multiple transformations may be applied to a node by specifying an ordered
325 * chain of transforms. The order in which the transforms are applied is
326 * defined by the ObservableList specified in the {@link #getTransforms transforms} variable.
327 *
328 * <h2>Bounding Rectangles</h2>
329 * <p>
330 * Since every {@code Node} has transformations, every Node's geometric
331 * bounding rectangle can be described differently depending on whether
332 * transformations are accounted for or not.
333 * <p>
334 * Each {@code Node} has a read-only {@link #boundsInLocalProperty boundsInLocal}
335 * variable which specifies the bounding rectangle of the {@code Node} in
336 * untransformed local coordinates. {@code boundsInLocal} includes the
337 * Node's shape geometry, including any space required for a
338 * non-zero stroke that may fall outside the local position/size variables,
339 * and its {@link #clipProperty clip} and {@link #effectProperty effect} variables.
340 * <p>
341 * Each {@code Node} also has a read-only {@link #boundsInParentProperty boundsInParent} variable which
342 * specifies the bounding rectangle of the {@code Node} after all transformations
343 * have been applied, including those set in {@link #getTransforms transforms},
344 * {@link #scaleXProperty scaleX}/{@link #scaleYProperty scaleY}, {@link #rotateProperty rotate},
345 * {@link #translateXProperty translateX}/{@link #translateYProperty translateY}, and {@link #layoutXProperty layoutX}/{@link #layoutYProperty layoutY}.
346 * It is called "boundsInParent" because the rectangle will be relative to the
347 * parent's coordinate system. This is the 'visual' bounds of the node.
348 * <p>
349 * Finally, the {@link #layoutBoundsProperty layoutBounds} variable defines the rectangular bounds of
350 * the {@code Node} that should be used as the basis for layout calculations and
351 * may differ from the visual bounds of the node. For shapes, Text, and ImageView,
352 * layoutBounds by default includes only the shape geometry, including space required
353 * for a non-zero {@code strokeWidth}, but does <i>not</i> include the effect,
354 * clip, or any transforms. For resizable classes (Regions and Controls)
355 * layoutBounds will always map to {@code 0,0 width x height}.
356 *
357 * <p> The image shows a node without any transformation and its {@code boundsInLocal}:
358 * <p> <img src="doc-files/boundsLocal.png" alt="A sine wave shape enclosed by
359 * an axis-aligned rectangular bounds"> </p>
360 * If we rotate the image by 20 degrees we get following result:
361 * <p> <img src="doc-files/boundsParent.png" alt="An axis-aligned rectangular
362 * bounds that encloses the shape rotated by 20 degrees"> </p>
363 * The red rectangle represents {@code boundsInParent} in the
364 * coordinate space of the Node's parent. The {@code boundsInLocal} stays the same
365 * as in the first image, the green rectangle in this image represents {@code boundsInLocal}
366 * in the coordinate space of the Node.
367 *
368 * <p> The images show a filled and stroked rectangle and their bounds. The
369 * first rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]}
370 * has the following bounds bounds: {@code [x:10.0 y:10.0 width:100.0 height:100.0]}.
371 *
372 * The second rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]}
373 * has the following bounds: {@code [x:7.5 y:7.5 width:105 height:105]}
374 * (the stroke is centered by default, so only half of it is outside
375 * of the original bounds; it is also possible to create inside or outside
376 * stroke).
377 *
378 * Since neither of the rectangles has any transformation applied,
379 * {@code boundsInParent} and {@code boundsInLocal} are the same. </p>
380 * <p> <img src="doc-files/bounds.png" alt="The rectangles are enclosed by their
381 * respective bounds"> </p>
382 *
383 *
384 * <h2>CSS</h2>
385 * <p>
386 * The {@code Node} class contains {@code id}, {@code styleClass}, and
387 * {@code style} variables that are used in styling this node from
388 * CSS. The {@code id} and {@code styleClass} variables are used in
389 * CSS style sheets to identify nodes to which styles should be
390 * applied. The {@code style} variable contains style properties and
391 * values that are applied directly to this node.
392 * <p>
393 * For further information about CSS and how to apply CSS styles
394 * to nodes, see the <a href="doc-files/cssref.html">CSS Reference
395 * Guide</a>.
396 * @since JavaFX 2.0
397 */
398 @IDProperty("id")
399 public abstract class Node implements EventTarget, Styleable {
400
401 /*
402 * Store the singleton instance of the NodeHelper subclass corresponding
403 * to the subclass of this instance of Node
404 */
405 private NodeHelper nodeHelper = null;
406
407 static {
408 PerformanceTracker.logEvent("Node class loaded");
409
410 // This is used by classes in different packages to get access to
411 // private and package private methods.
412 NodeHelper.setNodeAccessor(new NodeHelper.NodeAccessor() {
413 @Override
414 public NodeHelper getHelper(Node node) {
415 return node.nodeHelper;
416 }
417
418 @Override
419 public void setHelper(Node node, NodeHelper nodeHelper) {
420 node.nodeHelper = nodeHelper;
421 }
422
423 @Override
424 public void doMarkDirty(Node node, DirtyBits dirtyBit) {
425 node.doMarkDirty(dirtyBit);
426 }
427
428 @Override
429 public void doUpdatePeer(Node node) {
430 node.doUpdatePeer();
431 }
432
433 @Override
434 public BaseTransform getLeafTransform(Node node) {
435 return node.getLeafTransform();
436 }
437
438 @Override
439 public Bounds doComputeLayoutBounds(Node node) {
440 return node.doComputeLayoutBounds();
441 }
442
443 @Override
444 public void doTransformsChanged(Node node) {
445 node.doTransformsChanged();
446 }
447
448 @Override
449 public void doPickNodeLocal(Node node, PickRay localPickRay,
450 PickResultChooser result) {
451 node.doPickNodeLocal(localPickRay, result);
452 }
453
454 @Override
455 public boolean doComputeIntersects(Node node, PickRay pickRay,
456 PickResultChooser pickResult) {
457 return node.doComputeIntersects(pickRay, pickResult);
458 }
459
460 @Override
461 public void doGeomChanged(Node node) {
462 node.doGeomChanged();
463 }
464
465 @Override
466 public void doNotifyLayoutBoundsChanged(Node node) {
467 node.doNotifyLayoutBoundsChanged();
468 }
469
470 @Override
471 public void doProcessCSS(Node node) {
472 node.doProcessCSS();
473 }
474
475 @Override
476 public boolean isDirty(Node node, DirtyBits dirtyBit) {
477 return node.isDirty(dirtyBit);
478 }
479
480 @Override
481 public boolean isDirtyEmpty(Node node) {
482 return node.isDirtyEmpty();
483 }
484
485 @Override
486 public void syncPeer(Node node) {
487 node.syncPeer();
488 }
489
490 @Override
491 public void layoutBoundsChanged(Node node) {
492 node.layoutBoundsChanged();
493 }
494
495 @Override
496 public <P extends NGNode> P getPeer(Node node) {
497 return node.getPeer();
498 }
499
500 @Override
501 public void setShowMnemonics(Node node, boolean value) {
502 node.setShowMnemonics(value);
503 }
504
505 @Override
506 public boolean isShowMnemonics(Node node) {
507 return node.isShowMnemonics();
508 }
509
510 @Override
511 public BooleanProperty showMnemonicsProperty(Node node) {
512 return node.showMnemonicsProperty();
513 }
514
515 @Override
516 public boolean traverse(Node node, Direction direction) {
517 return node.traverse(direction);
518 }
519
520 @Override
521 public double getPivotX(Node node) {
522 return node.getPivotX();
523 }
524
525 @Override
526 public double getPivotY(Node node) {
527 return node.getPivotY();
528 }
529
530 @Override
531 public double getPivotZ(Node node) {
532 return node.getPivotZ();
533 }
534
535 @Override
536 public void pickNode(Node node,PickRay pickRay,
537 PickResultChooser result) {
538 node.pickNode(pickRay, result);
539 }
540
541 @Override
542 public boolean intersects(Node node, PickRay pickRay,
543 PickResultChooser pickResult) {
544 return node.intersects(pickRay, pickResult);
545 }
546
547 @Override
548 public double intersectsBounds(Node node, PickRay pickRay) {
549 return node.intersectsBounds(pickRay);
550 }
551
552 @Override
553 public void layoutNodeForPrinting(Node node) {
554 node.doCSSLayoutSyncForSnapshot();
555 }
556
557 @Override
558 public boolean isDerivedDepthTest(Node node) {
559 return node.isDerivedDepthTest();
560 }
561
562 @Override
563 public SubScene getSubScene(Node node) {
564 return node.getSubScene();
565 }
566
567 @Override
568 public void setLabeledBy(Node node, Node labeledBy) {
569 node.labeledBy = labeledBy;
570 }
571
572 @Override
573 public Accessible getAccessible(Node node) {
574 return node.getAccessible();
575 }
576
577 @Override
578 public void reapplyCSS(Node node) {
579 node.reapplyCSS();
580 }
581
582 @Override
583 public boolean isTreeVisible(Node node) {
584 return node.isTreeVisible();
585 }
586
587 @Override
588 public BooleanExpression treeVisibleProperty(Node node) {
589 return node.treeVisibleProperty();
590 }
591
592 @Override
593 public boolean isTreeShowing(Node node) {
594 return node.isTreeShowing();
595 }
596
597 @Override
598 public BooleanExpression treeShowingProperty(Node node) {
599 return node.treeShowingProperty();
600 }
601
602 @Override
603 public List<Style> getMatchingStyles(CssMetaData cssMetaData,
604 Styleable styleable) {
605 return Node.getMatchingStyles(cssMetaData, styleable);
606 }
607
608 @Override
609 public Map<StyleableProperty<?>, List<Style>> findStyles(Node node,
610 Map<StyleableProperty<?>, List<Style>> styleMap) {
611 return node.findStyles(styleMap);
612 }
613 });
614 }
615
616 /**************************************************************************
617 * *
618 * Methods and state for managing the dirty bits of a Node. The dirty *
619 * bits are flags used to keep track of what things are dirty on the *
620 * node and therefore need processing on the next pulse. Since the pulse *
621 * happens asynchronously to the change that made the node dirty (for *
622 * performance reasons), we need to keep track of what things have *
623 * changed. *
624 * *
625 *************************************************************************/
626
627 /**
628 * Set of dirty bits that are set when state is invalidated and cleared by
629 * the updateState method, which is called from the synchronizer.
630 * <p>
631 * A node starts dirty.
632 */
633 private Set<DirtyBits> dirtyBits = EnumSet.allOf(DirtyBits.class);
634
635 /**
636 * Mark the specified bit as dirty, and add this node to the scene's dirty list.
637 *
638 * Note: This method MUST only be called via its accessor method.
639 */
640 private void doMarkDirty(DirtyBits dirtyBit) {
641 if (isDirtyEmpty()) {
642 addToSceneDirtyList();
643 }
644
645 dirtyBits.add(dirtyBit);
646 }
647
648 private void addToSceneDirtyList() {
649 Scene s = getScene();
650 if (s != null) {
651 s.addToDirtyList(this);
652 if (getSubScene() != null) {
653 getSubScene().setDirty(this);
654 }
655 }
656 }
657
658 /**
659 * Test whether the specified dirty bit is set
660 */
661 final boolean isDirty(DirtyBits dirtyBit) {
662 return dirtyBits.contains(dirtyBit);
663 }
664
665 /**
666 * Clear the specified dirty bit
667 */
668 final void clearDirty(DirtyBits dirtyBit) {
669 dirtyBits.remove(dirtyBit);
670 }
671
672 /**
673 * Clear all dirty bits
674 */
675 private void clearDirty() {
676 dirtyBits.clear();
677 }
678
679 /**
680 * Test whether the set of dirty bits is empty
681 */
682 private boolean isDirtyEmpty() {
683 return dirtyBits.isEmpty();
684 }
685
686 /**************************************************************************
687 * *
688 * Methods for synchronizing state from this Node to its PG peer. This *
689 * should only *ever* be called during synchronization initialized as a *
690 * result of a pulse. Any attempt to synchronize at any other time may *
691 * cause rendering artifacts. *
692 * *
693 *************************************************************************/
694
695 /**
696 * Called by the synchronizer to update the state and
697 * clear dirtybits of this node in the PG graph
698 */
699 final void syncPeer() {
700 // Do not synchronize invisible nodes unless their visibility has changed
701 // or they have requested a forced synchronization
702 if (!isDirtyEmpty() && (treeVisible
703 || isDirty(DirtyBits.NODE_VISIBLE)
704 || isDirty(DirtyBits.NODE_FORCE_SYNC)))
705 {
706 NodeHelper.updatePeer(this);
707 clearDirty();
708 }
709 }
710
711 /**
712 * A temporary rect used for computing bounds by the various bounds
713 * variables. This bounds starts life as a RectBounds, but may be promoted
714 * to a BoxBounds if there is a 3D transform mixed into its computation.
715 * These two fields were held in a thread local, but were then pulled
716 * out of it so that we could compute bounds before holding the
717 * synchronization lock. These objects have to be per-instance so
718 * that we can pass the right data down to the PG side later during
719 * synchronization (rather than statics as they were before).
720 */
721 private BaseBounds _geomBounds = new RectBounds(0, 0, -1, -1);
722 private BaseBounds _txBounds = new RectBounds(0, 0, -1, -1);
723
724 private boolean pendingUpdateBounds = false;
725
726 // Happens before we hold the sync lock
727 void updateBounds() {
728 // Note: the clip must be handled before the visibility is checked. This is because the visiblity might be
729 // changing in the clip and it is going to be synchronized, so it needs to recompute the bounds.
730 Node n = getClip();
731 if (n != null) {
732 n.updateBounds();
733 }
734
735 // See syncPeer()
736 if (!treeVisible && !isDirty(DirtyBits.NODE_VISIBLE)) {
737
738 // Need to save the dirty bits since they will be cleared even for the
739 // case of short circuiting dirty bit processing.
740 if (isDirty(DirtyBits.NODE_TRANSFORM)
741 || isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)
742 || isDirty(DirtyBits.NODE_BOUNDS)) {
743 pendingUpdateBounds = true;
744 }
745
746 return;
747 }
748
749 // Set transform and bounds dirty bits when this node becomes visible
750 if (pendingUpdateBounds) {
751 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM);
752 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS);
753 NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS);
754
755 pendingUpdateBounds = false;
756 }
757
758 if (isDirty(DirtyBits.NODE_TRANSFORM) || isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
759 if (isDirty(DirtyBits.NODE_TRANSFORM)) {
760 updateLocalToParentTransform();
761 }
762 _txBounds = getTransformedBounds(_txBounds,
763 BaseTransform.IDENTITY_TRANSFORM);
764 }
765
766 if (isDirty(DirtyBits.NODE_BOUNDS)) {
767 _geomBounds = getGeomBounds(_geomBounds,
768 BaseTransform.IDENTITY_TRANSFORM);
769 }
770
771 }
772
773 /*
774 * This function is called during synchronization to update the state of the
775 * NG Node from the FX Node. Subclasses of Node should override this method
776 * and must call NodeHelper.updatePeer(this)
777 *
778 * Note: This method MUST only be called via its accessor method.
779 */
780 private void doUpdatePeer() {
781 final NGNode peer = getPeer();
782
783 // For debug / diagnostic purposes, we will copy across a name for this node down to
784 // the NG layer, where we can use the name to figure out what the NGNode represents.
785 // An alternative would be to have a back-reference from the NGNode back to the Node it
786 // is a peer to, however it was felt that this would make it too easy to communicate back
787 // to the Node and possibly violate thread invariants. But of course, we only need to do this
788 // if we're going to print the render graph (otherwise all the work we'd do to keep the name
789 // properly updated would be a waste).
790 if (PrismSettings.printRenderGraph && isDirty(DirtyBits.DEBUG)) {
791 final String id = getId();
792 String className = getClass().getSimpleName();
793 if (className.isEmpty()) {
794 className = getClass().getName();
795 }
796 peer.setName(id == null ? className : id + "(" + className + ")");
797 }
798
799 if (isDirty(DirtyBits.NODE_TRANSFORM)) {
800 peer.setTransformMatrix(localToParentTx);
801 }
802
803 if (isDirty(DirtyBits.NODE_VIEW_ORDER)) {
804 peer.setViewOrder(getViewOrder());
805 }
806
807 if (isDirty(DirtyBits.NODE_BOUNDS)) {
808 peer.setContentBounds(_geomBounds);
809 }
810
811 if (isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
812 peer.setTransformedBounds(_txBounds, !isDirty(DirtyBits.NODE_BOUNDS));
813 }
814
815 if (isDirty(DirtyBits.NODE_OPACITY)) {
816 peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
817 }
818
819 if (isDirty(DirtyBits.NODE_CACHE)) {
820 peer.setCachedAsBitmap(isCache(), getCacheHint());
821 }
822
823 if (isDirty(DirtyBits.NODE_CLIP)) {
824 peer.setClipNode(getClip() != null ? getClip().getPeer() : null);
825 }
826
827 if (isDirty(DirtyBits.EFFECT_EFFECT)) {
828 if (getEffect() != null) {
829 EffectHelper.sync(getEffect());
830 peer.effectChanged();
831 }
832 }
833
834 if (isDirty(DirtyBits.NODE_EFFECT)) {
835 peer.setEffect(getEffect() != null ? EffectHelper.getPeer(getEffect()) : null);
836 }
837
838 if (isDirty(DirtyBits.NODE_VISIBLE)) {
839 peer.setVisible(isVisible());
840 }
841
842 if (isDirty(DirtyBits.NODE_DEPTH_TEST)) {
843 peer.setDepthTest(isDerivedDepthTest());
844 }
845
846 if (isDirty(DirtyBits.NODE_BLENDMODE)) {
847 BlendMode mode = getBlendMode();
848 peer.setNodeBlendMode((mode == null)
849 ? null
850 : EffectHelper.getToolkitBlendMode(mode));
851 }
852 }
853
854 /*************************************************************************
855 * *
856 * *
857 * *
858 *************************************************************************/
859
860 private static final Object USER_DATA_KEY = new Object();
861 // A map containing a set of properties for this node
862 private ObservableMap<Object, Object> properties;
863
864 /**
865 * Returns an observable map of properties on this node for use primarily
866 * by application developers.
867 *
868 * @return an observable map of properties on this node for use primarily
869 * by application developers
870 */
871 public final ObservableMap<Object, Object> getProperties() {
872 if (properties == null) {
873 properties = FXCollections.observableMap(new HashMap<Object, Object>());
874 }
875 return properties;
876 }
877
878 /**
879 * Tests if Node has properties.
880 * @return true if node has properties.
881 */
882 public boolean hasProperties() {
883 return properties != null && !properties.isEmpty();
884 }
885
886 /**
887 * Convenience method for setting a single Object property that can be
888 * retrieved at a later date. This is functionally equivalent to calling
889 * the getProperties().put(Object key, Object value) method. This can later
890 * be retrieved by calling {@link Node#getUserData()}.
891 *
892 * @param value The value to be stored - this can later be retrieved by calling
893 * {@link Node#getUserData()}.
894 */
895 public void setUserData(Object value) {
896 getProperties().put(USER_DATA_KEY, value);
897 }
898
899 /**
900 * Returns a previously set Object property, or null if no such property
901 * has been set using the {@link Node#setUserData(java.lang.Object)} method.
902 *
903 * @return The Object that was previously set, or null if no property
904 * has been set or if null was set.
905 */
906 public Object getUserData() {
907 return getProperties().get(USER_DATA_KEY);
908 }
909
910 /**************************************************************************
911 * *
912 *
913 * *
914 *************************************************************************/
915
916 /**
917 * The parent of this {@code Node}. If this {@code Node} has not been added
918 * to a scene graph, then parent will be null.
919 *
920 * @defaultValue null
921 */
922 private ReadOnlyObjectWrapper<Parent> parent;
923
924 final void setParent(Parent value) {
925 parentPropertyImpl().set(value);
926 }
927
928 public final Parent getParent() {
929 return parent == null ? null : parent.get();
930 }
931
932 public final ReadOnlyObjectProperty<Parent> parentProperty() {
933 return parentPropertyImpl().getReadOnlyProperty();
934 }
935
936 private ReadOnlyObjectWrapper<Parent> parentPropertyImpl() {
937 if (parent == null) {
938 parent = new ReadOnlyObjectWrapper<Parent>() {
939 private Parent oldParent;
940
941 @Override
942 protected void invalidated() {
943 if (oldParent != null) {
944 oldParent.disabledProperty().removeListener(parentDisabledChangedListener);
945 oldParent.treeVisibleProperty().removeListener(parentTreeVisibleChangedListener);
946 if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
947 ((Node) oldParent).localToSceneTransformProperty().removeListener(
948 nodeTransformation.getLocalToSceneInvalidationListener());
949 }
950 }
951 updateDisabled();
952 computeDerivedDepthTest();
953 final Parent newParent = get();
954 if (newParent != null) {
955 newParent.disabledProperty().addListener(parentDisabledChangedListener);
956 newParent.treeVisibleProperty().addListener(parentTreeVisibleChangedListener);
957 if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
958 ((Node) newParent).localToSceneTransformProperty().addListener(
959 nodeTransformation.getLocalToSceneInvalidationListener());
960 }
961 //
962 // if parent changed, then CSS needs to be reapplied so
963 // that this node will get the right styles. This used
964 // to be done from Parent.children's onChanged method.
965 // See the comments there, also.
966 //
967 reapplyCSS();
968 } else {
969 // RT-31168: reset CssFlag to clean so css will be reapplied if the node is added back later.
970 // If flag is REAPPLY, then reapplyCSS() will just return and the call to
971 // notifyParentsOfInvalidatedCSS() will be skipped thus leaving the node un-styled.
972 cssFlag = CssFlags.CLEAN;
973 }
974 updateTreeVisible(true);
975 oldParent = newParent;
976 invalidateLocalToSceneTransform();
977 parentResolvedOrientationInvalidated();
978 notifyAccessibleAttributeChanged(AccessibleAttribute.PARENT);
979 }
980
981 @Override
982 public Object getBean() {
983 return Node.this;
984 }
985
986 @Override
987 public String getName() {
988 return "parent";
989 }
990 };
991 }
992 return parent;
993 }
994
995 private final InvalidationListener parentDisabledChangedListener = valueModel -> updateDisabled();
996
997 private final InvalidationListener parentTreeVisibleChangedListener = valueModel -> updateTreeVisible(true);
998
999 private final ChangeListener<Boolean> windowShowingChangedListener
1000 = (win, oldVal, newVal) -> updateTreeShowing();
1001
1002 private final ChangeListener<Window> sceneWindowChangedListener = (scene, oldWindow, newWindow) -> {
1003 // Replace the windowShowingListener and call updateTreeShowing()
1004 if (oldWindow != null) {
1005 oldWindow.showingProperty().removeListener(windowShowingChangedListener);
1006 }
1007 if (newWindow != null) {
1008 newWindow.showingProperty().addListener(windowShowingChangedListener);
1009 }
1010 updateTreeShowing();
1011 };
1012
1013 private SubScene subScene = null;
1014
1015 /**
1016 * The {@link Scene} that this {@code Node} is part of. If the Node is not
1017 * part of a scene, then this variable will be null.
1018 *
1019 * @defaultValue null
1020 */
1021 private ReadOnlyObjectWrapperManualFire<Scene> scene = new ReadOnlyObjectWrapperManualFire<Scene>();
1022
1023 private class ReadOnlyObjectWrapperManualFire<T> extends ReadOnlyObjectWrapper<T> {
1024 @Override
1025 public Object getBean() {
1026 return Node.this;
1027 }
1028
1029 @Override
1030 public String getName() {
1031 return "scene";
1032 }
1033
1034 @Override
1035 protected void fireValueChangedEvent() {
1036 /*
1037 * Note: This method has been intentionally made into a no-op. In
1038 * order to override the default set behavior. By default calling
1039 * set(...) on a different scene will trigger:
1040 * - invalidated();
1041 * - fireValueChangedEvent();
1042 * Both of the above are no-ops, but are handled manually via
1043 * - Node.this.setScenes(...)
1044 * - Node.this.invalidatedScenes(...)
1045 * - forceValueChangedEvent()
1046 */
1047 }
1048
1049 public void fireSuperValueChangedEvent() {
1050 super.fireValueChangedEvent();
1051 }
1052 }
1053
1054 private void invalidatedScenes(Scene oldScene, SubScene oldSubScene) {
1055 Scene newScene = sceneProperty().get();
1056 boolean sceneChanged = oldScene != newScene;
1057 SubScene newSubScene = subScene;
1058
1059 if (getClip() != null) {
1060 getClip().setScenes(newScene, newSubScene);
1061 }
1062 if (sceneChanged) {
1063 updateCanReceiveFocus();
1064 if (isFocusTraversable()) {
1065 if (newScene != null) {
1066 newScene.initializeInternalEventDispatcher();
1067 }
1068 }
1069 focusSetDirty(oldScene);
1070 focusSetDirty(newScene);
1071 }
1072 scenesChanged(newScene, newSubScene, oldScene, oldSubScene);
1073
1074 // isTreeShowing needs to take into account of Window's showing
1075 if (oldScene != null) {
1076 oldScene.windowProperty().removeListener(sceneWindowChangedListener);
1077
1078 Window window = oldScene.windowProperty().get();
1079 if (window != null) {
1080 window.showingProperty().removeListener(windowShowingChangedListener);
1081 }
1082 }
1083 if (newScene != null) {
1084 newScene.windowProperty().addListener(sceneWindowChangedListener);
1085
1086 Window window = newScene.windowProperty().get();
1087 if (window != null) {
1088 window.showingProperty().addListener(windowShowingChangedListener);
1089 }
1090
1091 }
1092 updateTreeShowing();
1093
1094 if (sceneChanged) reapplyCSS();
1095
1096 if (sceneChanged && !isDirtyEmpty()) {
1097 //Note: no need to remove from scene's dirty list
1098 //Scene's is checking if the node's scene is correct
1099 /* TODO: looks like an existing bug when a node is moved from one
1100 * location to another, setScenes will be called twice by
1101 * Parent.VetoableListDecorator onProposedChange and onChanged
1102 * respectively. Removing the node and setting setScense(null,null)
1103 * then adding it back to potentially the same scene. Causing the
1104 * same node to being added twice to the same scene.
1105 */
1106 addToSceneDirtyList();
1107 }
1108
1109 if (newScene == null && peer != null) {
1110 peer.release();
1111 }
1112
1113 if (oldScene != null) {
1114 oldScene.clearNodeMnemonics(this);
1115 }
1116 if (getParent() == null) {
1117 // if we are the root we need to handle scene change
1118 parentResolvedOrientationInvalidated();
1119 }
1120
1121 if (sceneChanged) { scene.fireSuperValueChangedEvent(); }
1122
1123 /* Dispose the accessible peer, if any. If AT ever needs this node again
1124 * a new accessible peer is created. */
1125 if (accessible != null) {
1126 /* Generally accessibility does not retain any state, therefore deleting objects
1127 * generally does not cause problems (AT just asks everything back).
1128 * The exception to this rule is when the object sends a notifications to the AT,
1129 * in which case it is expected to be around to answer request for the new values.
1130 * It is possible that a object is reparented (within the scene) in the middle of
1131 * this process. For example, when a tree item is expanded, the notification is
1132 * sent to the AT by the cell. But when the TreeView relayouts the cell can be
1133 * reparented before AT can query the relevant information about the expand event.
1134 * If the accessible was disposed, AT can't properly report the event.
1135 *
1136 * The fix is to defer the disposal of the accessible to the next pulse.
1137 * If at that time the node is placed back to the scene, then the accessible is hooked
1138 * to Node and AT requests are processed. Otherwise the accessible is disposed.
1139 */
1140 if (oldScene != null && oldScene != newScene && newScene == null) {
1141 // Strictly speaking we need some type of accessible.thaw() at this point.
1142 oldScene.addAccessible(Node.this, accessible);
1143 } else {
1144 accessible.dispose();
1145 }
1146 /* Always set to null to ensure this accessible is never on more than one
1147 * Scene#accMap at the same time (At lest not with the same accessible).
1148 */
1149 accessible = null;
1150 }
1151 }
1152
1153 final void setScenes(Scene newScene, SubScene newSubScene) {
1154 Scene oldScene = sceneProperty().get();
1155 if (newScene != oldScene || newSubScene != subScene) {
1156 scene.set(newScene);
1157 SubScene oldSubScene = subScene;
1158 subScene = newSubScene;
1159 invalidatedScenes(oldScene, oldSubScene);
1160 if (this instanceof SubScene) { // TODO: find better solution
1161 SubScene thisSubScene = (SubScene)this;
1162 thisSubScene.getRoot().setScenes(newScene, thisSubScene);
1163 }
1164 }
1165 }
1166
1167 final SubScene getSubScene() {
1168 return subScene;
1169 }
1170
1171 public final Scene getScene() {
1172 return scene.get();
1173 }
1174
1175 public final ReadOnlyObjectProperty<Scene> sceneProperty() {
1176 return scene.getReadOnlyProperty();
1177 }
1178
1179 /**
1180 * Exists for Parent and LightBase
1181 */
1182 void scenesChanged(final Scene newScene, final SubScene newSubScene,
1183 final Scene oldScene, final SubScene oldSubScene) { }
1184
1185
1186 /**
1187 * The id of this {@code Node}. This simple string identifier is useful for
1188 * finding a specific Node within the scene graph. While the id of a Node
1189 * should be unique within the scene graph, this uniqueness is not enforced.
1190 * This is analogous to the "id" attribute on an HTML element
1191 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
1192 * <p>
1193 * For example, if a Node is given the id of "myId", then the lookup method can
1194 * be used to find this node as follows: <code>scene.lookup("#myId");</code>.
1195 * </p>
1196 *
1197 * @defaultValue null
1198 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1199 */
1200 private StringProperty id;
1201
1202 public final void setId(String value) {
1203 idProperty().set(value);
1204 }
1205
1206 //TODO: this is copied from the property in order to add the @return statement.
1207 // We should have a better, general solution without the need to copy it.
1208 /**
1209 * The id of this {@code Node}. This simple string identifier is useful for
1210 * finding a specific Node within the scene graph. While the id of a Node
1211 * should be unique within the scene graph, this uniqueness is not enforced.
1212 * This is analogous to the "id" attribute on an HTML element
1213 * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
1214 *
1215 * @return the id assigned to this {@code Node} using the {@code setId}
1216 * method or {@code null}, if no id has been assigned.
1217 * @defaultValue null
1218 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
1219 */
1220 public final String getId() {
1221 return id == null ? null : id.get();
1222 }
1223
1224 public final StringProperty idProperty() {
1225 if (id == null) {
1226 id = new StringPropertyBase() {
1227
1228 @Override
1229 protected void invalidated() {
1230 reapplyCSS();
1231 if (PrismSettings.printRenderGraph) {
1232 NodeHelper.markDirty(Node.this, DirtyBits.DEBUG);
1233 }
1234 }
1235
1236 @Override
1237 public Object getBean() {
1238 return Node.this;
1239 }
1240
1241 @Override
1242 public String getName() {
1243 return "id";
1244 }
1245 };
1246 }
1247 return id;
1248 }
1249
1250 /**
1251 * A list of String identifiers which can be used to logically group
1252 * Nodes, specifically for an external style engine. This variable is
1253 * analogous to the "class" attribute on an HTML element and, as such,
1254 * each element of the list is a style class to which this Node belongs.
1255 *
1256 * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
1257 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1258 * @defaultValue null
1259 */
1260 private ObservableList<String> styleClass = new TrackableObservableList<String>() {
1261 @Override
1262 protected void onChanged(Change<String> c) {
1263 reapplyCSS();
1264 }
1265
1266 @Override
1267 public String toString() {
1268 if (size() == 0) {
1269 return "";
1270 } else if (size() == 1) {
1271 return get(0);
1272 } else {
1273 StringBuilder buf = new StringBuilder();
1274 for (int i = 0; i < size(); i++) {
1275 buf.append(get(i));
1276 if (i + 1 < size()) {
1277 buf.append(' ');
1278 }
1279 }
1280 return buf.toString();
1281 }
1282 }
1283 };
1284
1285 @Override
1286 public final ObservableList<String> getStyleClass() {
1287 return styleClass;
1288 }
1289
1290 /**
1291 * A string representation of the CSS style associated with this
1292 * specific {@code Node}. This is analogous to the "style" attribute of an
1293 * HTML element. Note that, like the HTML style attribute, this
1294 * variable contains style properties and values and not the
1295 * selector portion of a style rule.
1296 * @defaultValue empty string
1297 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1298 */
1299 private StringProperty style;
1300
1301 /**
1302 * A string representation of the CSS style associated with this
1303 * specific {@code Node}. This is analogous to the "style" attribute of an
1304 * HTML element. Note that, like the HTML style attribute, this
1305 * variable contains style properties and values and not the
1306 * selector portion of a style rule.
1307 * @param value The inline CSS style to use for this {@code Node}.
1308 * {@code null} is implicitly converted to an empty String.
1309 * @defaultValue empty string
1310 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
1311 */
1312 public final void setStyle(String value) {
1313 styleProperty().set(value);
1314 }
1315
1316 // TODO: javadoc copied from property for the sole purpose of providing a return tag
1317 /**
1318 * A string representation of the CSS style associated with this
1319 * specific {@code Node}. This is analogous to the "style" attribute of an
1320 * HTML element. Note that, like the HTML style attribute, this
1321 * variable contains style properties and values and not the
1322 * selector portion of a style rule.
1323 * @defaultValue empty string
1324 * @return The inline CSS style associated with this {@code Node}.
1325 * If this {@code Node} does not have an inline style,
1326 * an empty String is returned.
1327 * @see <a href="doc-files/cssref.html">CSS Reference Guide</a>
1328 */
1329 public final String getStyle() {
1330 return style == null ? "" : style.get();
1331 }
1332
1333 public final StringProperty styleProperty() {
1334 if (style == null) {
1335 style = new StringPropertyBase("") {
1336
1337 @Override public void set(String value) {
1338 // getStyle returns an empty string if the style property
1339 // is null. To be consistent, getStyle should also return
1340 // an empty string when the style property's value is null.
1341 super.set((value != null) ? value : "");
1342 }
1343
1344 @Override
1345 protected void invalidated() {
1346 // If the style has changed, then styles of this node
1347 // and child nodes might be affected.
1348 reapplyCSS();
1349 }
1350
1351 @Override
1352 public Object getBean() {
1353 return Node.this;
1354 }
1355
1356 @Override
1357 public String getName() {
1358 return "style";
1359 }
1360 };
1361 }
1362 return style;
1363 }
1364
1365 /**
1366 * Specifies whether this {@code Node} and any subnodes should be rendered
1367 * as part of the scene graph. A node may be visible and yet not be shown
1368 * in the rendered scene if, for instance, it is off the screen or obscured
1369 * by another Node. Invisible nodes never receive mouse events or
1370 * keyboard focus and never maintain keyboard focus when they become
1371 * invisible.
1372 *
1373 * @defaultValue true
1374 */
1375 private BooleanProperty visible;
1376
1377 public final void setVisible(boolean value) {
1378 visibleProperty().set(value);
1379 }
1380
1381 public final boolean isVisible() {
1382 return visible == null ? true : visible.get();
1383 }
1384
1385 public final BooleanProperty visibleProperty() {
1386 if (visible == null) {
1387 visible = new StyleableBooleanProperty(true) {
1388 boolean oldValue = true;
1389 @Override
1390 protected void invalidated() {
1391 if (oldValue != get()) {
1392 NodeHelper.markDirty(Node.this, DirtyBits.NODE_VISIBLE);
1393 NodeHelper.geomChanged(Node.this);
1394 updateTreeVisible(false);
1395 if (getParent() != null) {
1396 // notify the parent of the potential change in visibility
1397 // of this node, since visibility affects bounds of the
1398 // parent node
1399 getParent().childVisibilityChanged(Node.this);
1400 }
1401 oldValue = get();
1402 }
1403 }
1404
1405 @Override
1406 public CssMetaData getCssMetaData() {
1407 return StyleableProperties.VISIBILITY;
1408 }
1409
1410 @Override
1411 public Object getBean() {
1412 return Node.this;
1413 }
1414
1415 @Override
1416 public String getName() {
1417 return "visible";
1418 }
1419 };
1420 }
1421 return visible;
1422 }
1423
1424 public final void setCursor(Cursor value) {
1425 cursorProperty().set(value);
1426 }
1427
1428 public final Cursor getCursor() {
1429 return (miscProperties == null) ? DEFAULT_CURSOR
1430 : miscProperties.getCursor();
1431 }
1432
1433 /**
1434 * Defines the mouse cursor for this {@code Node} and subnodes. If null,
1435 * then the cursor of the first parent node with a non-null cursor will be
1436 * used. If no Node in the scene graph defines a cursor, then the cursor
1437 * of the {@code Scene} will be used.
1438 *
1439 * @return the mouse cursor for this {@code Node} and subnodes
1440 * @defaultValue null
1441 */
1442 public final ObjectProperty<Cursor> cursorProperty() {
1443 return getMiscProperties().cursorProperty();
1444 }
1445
1446 /**
1447 * Specifies how opaque (that is, solid) the {@code Node} appears. A Node
1448 * with 0% opacity is fully translucent. That is, while it is still
1449 * {@link #visibleProperty visible} and rendered, you generally won't be able to see it. The
1450 * exception to this rule is when the {@code Node} is combined with a
1451 * blending mode and blend effect in which case a translucent Node may still
1452 * have an impact in rendering. An opacity of 50% will render the node as
1453 * being 50% transparent.
1454 * <p>
1455 * A {@link #visibleProperty visible} node with any opacity setting still receives mouse
1456 * events and can receive keyboard focus. For example, if you want to have
1457 * a large invisible rectangle overlay all {@code Node}s in the scene graph
1458 * in order to intercept mouse events but not be visible to the user, you could
1459 * create a large {@code Rectangle} that had an opacity of 0%.
1460 * <p>
1461 * Opacity is specified as a value between 0 and 1. Values less than 0 are
1462 * treated as 0, values greater than 1 are treated as 1.
1463 * <p>
1464 * On some platforms ImageView might not support opacity variable.
1465 *
1466 * <p>
1467 * There is a known limitation of mixing opacity < 1.0 with a 3D Transform.
1468 * Opacity/Blending is essentially a 2D image operation. The result of
1469 * an opacity < 1.0 set on a {@link Group} node with 3D transformed children
1470 * will cause its children to be rendered in order without Z-buffering
1471 * applied between those children.
1472 *
1473 * @defaultValue 1.0
1474 */
1475 private DoubleProperty opacity;
1476
1477 public final void setOpacity(double value) {
1478 opacityProperty().set(value);
1479 }
1480 public final double getOpacity() {
1481 return opacity == null ? 1 : opacity.get();
1482 }
1483
1484 public final DoubleProperty opacityProperty() {
1485 if (opacity == null) {
1486 opacity = new StyleableDoubleProperty(1) {
1487
1488 @Override
1489 public void invalidated() {
1490 NodeHelper.markDirty(Node.this, DirtyBits.NODE_OPACITY);
1491 }
1492
1493 @Override
1494 public CssMetaData getCssMetaData() {
1495 return StyleableProperties.OPACITY;
1496 }
1497
1498 @Override
1499 public Object getBean() {
1500 return Node.this;
1501 }
1502
1503 @Override
1504 public String getName() {
1505 return "opacity";
1506 }
1507 };
1508 }
1509 return opacity;
1510 }
1511
1512 /**
1513 * The {@link javafx.scene.effect.BlendMode} used to blend this individual node
1514 * into the scene behind it. If this node is a {@code Group}, then all of the
1515 * children will be composited individually into a temporary buffer using their
1516 * own blend modes and then that temporary buffer will be composited into the
1517 * scene using the specified blend mode.
1518 *
1519 * A value of {@code null} is treated as pass-through. This means no effect on a
1520 * parent (such as a {@code Group}), and the equivalent of {@code SRC_OVER} for a single {@code Node}.
1521 *
1522 * @defaultValue {@code null}
1523 */
1524 private javafx.beans.property.ObjectProperty<BlendMode> blendMode;
1525
1526 public final void setBlendMode(BlendMode value) {
1527 blendModeProperty().set(value);
1528 }
1529 public final BlendMode getBlendMode() {
1530 return blendMode == null ? null : blendMode.get();
1531 }
1532
1533 public final ObjectProperty<BlendMode> blendModeProperty() {
1534 if (blendMode == null) {
1535 blendMode = new StyleableObjectProperty<BlendMode>(null) {
1536 @Override public void invalidated() {
1537 NodeHelper.markDirty(Node.this, DirtyBits.NODE_BLENDMODE);
1538 }
1539
1540 @Override
1541 public CssMetaData getCssMetaData() {
1542 return StyleableProperties.BLEND_MODE;
1543 }
1544
1545 @Override
1546 public Object getBean() {
1547 return Node.this;
1548 }
1549
1550 @Override
1551 public String getName() {
1552 return "blendMode";
1553 }
1554 };
1555 }
1556 return blendMode;
1557 }
1558
1559 public final void setClip(Node value) {
1560 clipProperty().set(value);
1561 }
1562
1563 public final Node getClip() {
1564 return (miscProperties == null) ? DEFAULT_CLIP
1565 : miscProperties.getClip();
1566 }
1567
1568 /**
1569 * Specifies a {@code Node} to use to define the clipping shape for this
1570 * Node. This clipping Node is not a child of this {@code Node} in the scene
1571 * graph sense. Rather, it is used to define the clip for this {@code Node}.
1572 * <p>
1573 * For example, you can use an {@link javafx.scene.image.ImageView} Node as
1574 * a mask to represent the Clip. Or you could use one of the geometric shape
1575 * Nodes such as {@link javafx.scene.shape.Rectangle} or
1576 * {@link javafx.scene.shape.Circle}. Or you could use a
1577 * {@link javafx.scene.text.Text} node to represent the Clip.
1578 * <p>
1579 * See the class documentation for {@link Node} for scene graph structure
1580 * restrictions on setting the clip. If these restrictions are violated by
1581 * a change to the clip variable, the change is ignored and the
1582 * previous value of the clip variable is restored.
1583 * <p>
1584 * Note that this is a conditional feature. See
1585 * {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP}
1586 * for more information.
1587 * <p>
1588 * There is a known limitation of mixing Clip with a 3D Transform.
1589 * Clipping is essentially a 2D image operation. The result of
1590 * a Clip set on a {@link Group} node with 3D transformed children
1591 * will cause its children to be rendered in order without Z-buffering
1592 * applied between those children.
1593 *
1594 * @return the the clipping shape for this {@code Node}
1595 * @defaultValue null
1596 */
1597 public final ObjectProperty<Node> clipProperty() {
1598 return getMiscProperties().clipProperty();
1599 }
1600
1601 public final void setCache(boolean value) {
1602 cacheProperty().set(value);
1603 }
1604
1605 public final boolean isCache() {
1606 return (miscProperties == null) ? DEFAULT_CACHE
1607 : miscProperties.isCache();
1608 }
1609
1610 /**
1611 * A performance hint to the system to indicate that this {@code Node}
1612 * should be cached as a bitmap. Rendering a bitmap representation of a node
1613 * will be faster than rendering primitives in many cases, especially in the
1614 * case of primitives with effects applied (such as a blur). However, it
1615 * also increases memory usage. This hint indicates whether that trade-off
1616 * (increased memory usage for increased performance) is worthwhile. Also
1617 * note that on some platforms such as GPU accelerated platforms there is
1618 * little benefit to caching Nodes as bitmaps when blurs and other effects
1619 * are used since they are very fast to render on the GPU.
1620 *
1621 * The {@link #cacheHintProperty} variable provides additional options for enabling
1622 * more aggressive bitmap caching.
1623 *
1624 * <p>
1625 * Caching may be disabled for any node that has a 3D transform on itself,
1626 * any of its ancestors, or any of its descendants.
1627 *
1628 * @return the hint to cache for this {@code Node}
1629 * @see #cacheHintProperty
1630 * @defaultValue false
1631 */
1632 public final BooleanProperty cacheProperty() {
1633 return getMiscProperties().cacheProperty();
1634 }
1635
1636 public final void setCacheHint(CacheHint value) {
1637 cacheHintProperty().set(value);
1638 }
1639
1640 public final CacheHint getCacheHint() {
1641 return (miscProperties == null) ? DEFAULT_CACHE_HINT
1642 : miscProperties.getCacheHint();
1643 }
1644
1645 /**
1646 * Additional hint for controlling bitmap caching.
1647 * <p>
1648 * Under certain circumstances, such as animating nodes that are very
1649 * expensive to render, it is desirable to be able to perform
1650 * transformations on the node without having to regenerate the cached
1651 * bitmap. An option in such cases is to perform the transforms on the
1652 * cached bitmap itself.
1653 * <p>
1654 * This technique can provide a dramatic improvement to animation
1655 * performance, though may also result in a reduction in visual quality.
1656 * The {@code cacheHint} variable provides a hint to the system about how
1657 * and when that trade-off (visual quality for animation performance) is
1658 * acceptable.
1659 * <p>
1660 * It is possible to enable the cacheHint only at times when your node is
1661 * animating. In this way, expensive nodes can appear on screen with full
1662 * visual quality, yet still animate smoothly.
1663 * <p>
1664 * Example:
1665 * <pre>{@code
1666 expensiveNode.setCache(true);
1667 expensiveNode.setCacheHint(CacheHint.QUALITY);
1668 ...
1669 // Do an animation
1670 expensiveNode.setCacheHint(CacheHint.SPEED);
1671 new Timeline(
1672 new KeyFrame(Duration.seconds(2),
1673 new KeyValue(expensiveNode.scaleXProperty(), 2.0),
1674 new KeyValue(expensiveNode.scaleYProperty(), 2.0),
1675 new KeyValue(expensiveNode.rotateProperty(), 360),
1676 new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
1677 )
1678 ).play();
1679 }</pre>
1680 *
1681 * Note that {@code cacheHint} is only a hint to the system. Depending on
1682 * the details of the node or the transform, this hint may be ignored.
1683 *
1684 * <p>
1685 * If {@code Node.cache} is false, cacheHint is ignored.
1686 * Caching may be disabled for any node that has a 3D transform on itself,
1687 * any of its ancestors, or any of its descendants.
1688 *
1689 * @return the {@code CacheHint} for this {@code Node}
1690 * @see #cacheProperty
1691 * @defaultValue CacheHint.DEFAULT
1692 */
1693 public final ObjectProperty<CacheHint> cacheHintProperty() {
1694 return getMiscProperties().cacheHintProperty();
1695 }
1696
1697 public final void setEffect(Effect value) {
1698 effectProperty().set(value);
1699 }
1700
1701 public final Effect getEffect() {
1702 return (miscProperties == null) ? DEFAULT_EFFECT
1703 : miscProperties.getEffect();
1704 }
1705
1706 /**
1707 * Specifies an effect to apply to this {@code Node}.
1708 * <p>
1709 * Note that this is a conditional feature. See
1710 * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT}
1711 * for more information.
1712 *
1713 * <p>
1714 * There is a known limitation of mixing Effect with a 3D Transform. Effect is
1715 * essentially a 2D image operation. The result of an Effect set on
1716 * a {@link Group} node with 3D transformed children will cause its children
1717 * to be rendered in order without Z-buffering applied between those
1718 * children.
1719 *
1720 * @return the effect for this {@code Node}
1721 * @defaultValue null
1722 */
1723 public final ObjectProperty<Effect> effectProperty() {
1724 return getMiscProperties().effectProperty();
1725 }
1726
1727 public final void setDepthTest(DepthTest value) {
1728 depthTestProperty().set(value);
1729 }
1730
1731 public final DepthTest getDepthTest() {
1732 return (miscProperties == null) ? DEFAULT_DEPTH_TEST
1733 : miscProperties.getDepthTest();
1734 }
1735
1736 /**
1737 * Indicates whether depth testing is used when rendering this node.
1738 * If the depthTest flag is {@code DepthTest.DISABLE}, then depth testing
1739 * is disabled for this node.
1740 * If the depthTest flag is {@code DepthTest.ENABLE}, then depth testing
1741 * is enabled for this node.
1742 * If the depthTest flag is {@code DepthTest.INHERIT}, then depth testing
1743 * is enabled for this node if it is enabled for the parent node or the
1744 * parent node is null.
1745 * <p>
1746 * The depthTest flag is only used when the depthBuffer flag for
1747 * the {@link Scene} is true (meaning that the
1748 * {@link Scene} has an associated depth buffer)
1749 * <p>
1750 * Depth test comparison is only done among nodes with depthTest enabled.
1751 * A node with depthTest disabled does not read, test, or write the depth buffer,
1752 * that is to say its Z value will not be considered for depth testing
1753 * with other nodes.
1754 * <p>
1755 * Note that this is a conditional feature. See
1756 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
1757 * for more information.
1758 * <p>
1759 * See the constructor in Scene with depthBuffer as one of its input
1760 * arguments.
1761 *
1762 * @return the depth test setting for this {@code Node}
1763 * @see javafx.scene.Scene
1764 * @defaultValue INHERIT
1765 */
1766 public final ObjectProperty<DepthTest> depthTestProperty() {
1767 return getMiscProperties().depthTestProperty();
1768 }
1769
1770 /**
1771 * Recompute the derived depth test flag. This flag is true
1772 * if the depthTest flag for this node is true and the
1773 * depth test flag for each ancestor node is true. It is false
1774 * otherwise. Equivalently, the derived depth flag is true
1775 * if the depthTest flag for this node is true and the derivedDepthTest
1776 * flag for its parent is true.
1777 */
1778 void computeDerivedDepthTest() {
1779 boolean newDDT;
1780 if (getDepthTest() == DepthTest.INHERIT) {
1781 if (getParent() != null) {
1782 newDDT = getParent().isDerivedDepthTest();
1783 } else {
1784 newDDT = true;
1785 }
1786 } else if (getDepthTest() == DepthTest.ENABLE) {
1787 newDDT = true;
1788 } else {
1789 newDDT = false;
1790 }
1791
1792 if (isDerivedDepthTest() != newDDT) {
1793 NodeHelper.markDirty(this, DirtyBits.NODE_DEPTH_TEST);
1794 setDerivedDepthTest(newDDT);
1795 }
1796 }
1797
1798 // This is the derived depthTest value to pass to PG level
1799 private boolean derivedDepthTest = true;
1800
1801 void setDerivedDepthTest(boolean value) {
1802 derivedDepthTest = value;
1803 }
1804
1805 boolean isDerivedDepthTest() {
1806 return derivedDepthTest;
1807 }
1808
1809 public final void setDisable(boolean value) {
1810 disableProperty().set(value);
1811 }
1812
1813 public final boolean isDisable() {
1814 return (miscProperties == null) ? DEFAULT_DISABLE
1815 : miscProperties.isDisable();
1816 }
1817
1818 /**
1819 * Defines the individual disabled state of this {@code Node}. Setting
1820 * {@code disable} to true will cause this {@code Node} and any subnodes to
1821 * become disabled. This property should be used only to set the disabled
1822 * state of a {@code Node}. For querying the disabled state of a
1823 * {@code Node}, the {@link #disabledProperty disabled} property should instead be used,
1824 * since it is possible that a {@code Node} was disabled as a result of an
1825 * ancestor being disabled even if the individual {@code disable} state on
1826 * this {@code Node} is {@code false}.
1827 *
1828 * @return the disabled state for this {@code Node}
1829 * @defaultValue false
1830 */
1831 public final BooleanProperty disableProperty() {
1832 return getMiscProperties().disableProperty();
1833 }
1834
1835
1836 // /**
1837 // * TODO document - null by default, could be non-null in subclasses (e.g. Control)
1838 // */
1839 // public final ObjectProperty<InputMap<?>> inputMapProperty() {
1840 // if (inputMap == null) {
1841 // inputMap = new SimpleObjectProperty<InputMap<?>>(this, "inputMap") {
1842 // private InputMap<?> currentMap = get();
1843 // @Override protected void invalidated() {
1844 // if (currentMap != null) {
1845 // currentMap.dispose();
1846 // }
1847 // currentMap = get();
1848 // }
1849 // };
1850 // }
1851 // return inputMap;
1852 // }
1853 // public final void setInputMap(InputMap<?> value) { inputMapProperty().set(value); }
1854 // public final InputMap<?> getInputMap() { return inputMapProperty().getValue(); }
1855 // private ObjectProperty<InputMap<?>> inputMap;
1856
1857
1858 /**************************************************************************
1859 * *
1860 *
1861 * *
1862 *************************************************************************/
1863 /**
1864 * Defines how the picking computation is done for this node when
1865 * triggered by a {@code MouseEvent} or a {@code contains} function call.
1866 *
1867 * If {@code pickOnBounds} is {@code true}, then picking is computed by
1868 * intersecting with the bounds of this node, else picking is computed
1869 * by intersecting with the geometric shape of this node.
1870 *
1871 * The default value of this property is {@code false} unless
1872 * overridden by a subclass. The default value is {@code true}
1873 * for {@link javafx.scene.layout.Region}.
1874 *
1875 * @defaultValue false; true for {@code Region}
1876 */
1877 private BooleanProperty pickOnBounds;
1878
1879 public final void setPickOnBounds(boolean value) {
1880 pickOnBoundsProperty().set(value);
1881 }
1882
1883 public final boolean isPickOnBounds() {
1884 return pickOnBounds == null ? false : pickOnBounds.get();
1885 }
1886
1887 public final BooleanProperty pickOnBoundsProperty() {
1888 if (pickOnBounds == null) {
1889 pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");
1890 }
1891 return pickOnBounds;
1892 }
1893
1894 /**
1895 * Indicates whether or not this {@code Node} is disabled. A {@code Node}
1896 * will become disabled if {@link #disableProperty disable} is set to {@code true} on either
1897 * itself or one of its ancestors in the scene graph.
1898 * <p>
1899 * A disabled {@code Node} should render itself differently to indicate its
1900 * disabled state to the user.
1901 * Such disabled rendering is dependent on the implementation of the
1902 * {@code Node}. The shape classes contained in {@code javafx.scene.shape}
1903 * do not implement such rendering by default, therefore applications using
1904 * shapes for handling input must implement appropriate disabled rendering
1905 * themselves. The user-interface controls defined in
1906 * {@code javafx.scene.control} will implement disabled-sensitive rendering,
1907 * however.
1908 * <p>
1909 * A disabled {@code Node} does not receive mouse or key events.
1910 *
1911 * @defaultValue false
1912 */
1913 private ReadOnlyBooleanWrapper disabled;
1914
1915 protected final void setDisabled(boolean value) {
1916 disabledPropertyImpl().set(value);
1917 }
1918
1919 public final boolean isDisabled() {
1920 return disabled == null ? false : disabled.get();
1921 }
1922
1923 public final ReadOnlyBooleanProperty disabledProperty() {
1924 return disabledPropertyImpl().getReadOnlyProperty();
1925 }
1926
1927 private ReadOnlyBooleanWrapper disabledPropertyImpl() {
1928 if (disabled == null) {
1929 disabled = new ReadOnlyBooleanWrapper() {
1930
1931 @Override
1932 protected void invalidated() {
1933 pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, get());
1934 updateCanReceiveFocus();
1935 focusSetDirty(getScene());
1936 }
1937
1938 @Override
1939 public Object getBean() {
1940 return Node.this;
1941 }
1942
1943 @Override
1944 public String getName() {
1945 return "disabled";
1946 }
1947 };
1948 }
1949 return disabled;
1950 }
1951
1952 private void updateDisabled() {
1953 boolean isDisabled = isDisable();
1954 if (!isDisabled) {
1955 isDisabled = getParent() != null ? getParent().isDisabled() :
1956 getSubScene() != null && getSubScene().isDisabled();
1957 }
1958 setDisabled(isDisabled);
1959 if (this instanceof SubScene) {
1960 ((SubScene)this).getRoot().setDisabled(isDisabled);
1961 }
1962 }
1963
1964 /**
1965 * Finds this {@code Node}, or the first sub-node, based on the given CSS selector.
1966 * If this node is a {@code Parent}, then this function will traverse down
1967 * into the branch until it finds a match. If more than one sub-node matches the
1968 * specified selector, this function returns the first of them.
1969 * <p>
1970 * For example, if a Node is given the id of "myId", then the lookup method can
1971 * be used to find this node as follows: <code>scene.lookup("#myId");</code>.
1972 * </p>
1973 *
1974 * @param selector The css selector of the node to find
1975 * @return The first node, starting from this {@code Node}, which matches
1976 * the CSS {@code selector}, null if none is found.
1977 */
1978 public Node lookup(String selector) {
1979 if (selector == null) return null;
1980 Selector s = Selector.createSelector(selector);
1981 return s != null && s.applies(this) ? this : null;
1982 }
1983
1984 /**
1985 * Finds all {@code Node}s, including this one and any children, which match
1986 * the given CSS selector. If no matches are found, an empty unmodifiable set is
1987 * returned. The set is explicitly unordered.
1988 *
1989 * @param selector The css selector of the nodes to find
1990 * @return All nodes, starting from and including this {@code Node}, which match
1991 * the CSS {@code selector}. The returned set is always unordered and
1992 * unmodifiable, and never null.
1993 */
1994 public Set<Node> lookupAll(String selector) {
1995 final Selector s = Selector.createSelector(selector);
1996 final Set<Node> empty = Collections.emptySet();
1997 if (s == null) return empty;
1998 List<Node> results = lookupAll(s, null);
1999 return results == null ? empty : new UnmodifiableListSet<Node>(results);
2000 }
2001
2002 /**
2003 * Used by Node and Parent for traversing the tree and adding all nodes which
2004 * match the given selector.
2005 *
2006 * @param selector The Selector. This will never be null.
2007 * @param results The results. This will never be null.
2008 */
2009 List<Node> lookupAll(Selector selector, List<Node> results) {
2010 if (selector.applies(this)) {
2011 // Lazily create the set to reduce some trash.
2012 if (results == null) {
2013 results = new LinkedList<Node>();
2014 }
2015 results.add(this);
2016 }
2017 return results;
2018 }
2019
2020 /**
2021 * Moves this {@code Node} to the back of its sibling nodes in terms of
2022 * z-order. This is accomplished by moving this {@code Node} to the
2023 * first position in its parent's {@code content} ObservableList.
2024 * This function has no effect if this {@code Node} is not part of a group.
2025 */
2026 public void toBack() {
2027 if (getParent() != null) {
2028 getParent().toBack(this);
2029 }
2030 }
2031
2032 /**
2033 * Moves this {@code Node} to the front of its sibling nodes in terms of
2034 * z-order. This is accomplished by moving this {@code Node} to the
2035 * last position in its parent's {@code content} ObservableList.
2036 * This function has no effect if this {@code Node} is not part of a group.
2037 */
2038 public void toFront() {
2039 if (getParent() != null) {
2040 getParent().toFront(this);
2041 }
2042 }
2043
2044 // TODO: need to verify whether this is OK to do starting from a node in
2045 // the scene graph other than the root.
2046 private void doCSSPass() {
2047 if (this.cssFlag != CssFlags.CLEAN) {
2048 // The dirty bit isn't checked but we must ensure it is cleared.
2049 // The cssFlag is set to clean in either Node.processCSS or
2050 // NodeHelper.processCSS
2051
2052 // Don't clear the dirty bit in case it will cause problems
2053 // with a full CSS pass on the scene.
2054 // TODO: is this the right thing to do?
2055 // this.clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
2056
2057 this.processCSS();
2058 }
2059 }
2060
2061 /**
2062 * Recursive function for synchronizing a node and all descendents
2063 */
2064 private static void syncAll(Node node) {
2065 node.syncPeer();
2066 if (node instanceof Parent) {
2067 Parent p = (Parent) node;
2068 final int childrenCount = p.getChildren().size();
2069
2070 for (int i = 0; i < childrenCount; i++) {
2071 Node n = p.getChildren().get(i);
2072 if (n != null) {
2073 syncAll(n);
2074 }
2075 }
2076 }
2077 if (node.getClip() != null) {
2078 syncAll(node.getClip());
2079 }
2080 }
2081
2082 private void doLayoutPass() {
2083 if (this instanceof Parent) {
2084 // TODO: As an optimization we only need to layout those dirty
2085 // roots that are descendants of this node
2086 Parent p = (Parent)this;
2087 for (int i = 0; i < 3; i++) {
2088 p.layout();
2089 }
2090 }
2091 }
2092
2093 private void doCSSLayoutSyncForSnapshot() {
2094 doCSSPass();
2095 doLayoutPass();
2096 updateBounds();
2097 Scene.setAllowPGAccess(true);
2098 syncAll(this);
2099 Scene.setAllowPGAccess(false);
2100 }
2101
2102 private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) {
2103 if (getScene() != null) {
2104 getScene().doCSSLayoutSyncForSnapshot(this);
2105 } else {
2106 doCSSLayoutSyncForSnapshot();
2107 }
2108
2109 BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
2110 if (params.getTransform() != null) {
2111 Affine3D tempTx = new Affine3D();
2112 TransformHelper.apply(params.getTransform(), tempTx);
2113 transform = tempTx;
2114 }
2115 double x;
2116 double y;
2117 double w;
2118 double h;
2119 Rectangle2D viewport = params.getViewport();
2120 if (viewport != null) {
2121 // Use the specified viewport
2122 x = viewport.getMinX();
2123 y = viewport.getMinY();
2124 w = viewport.getWidth();
2125 h = viewport.getHeight();
2126 } else {
2127 // Get the bounds in parent of this node, transformed by the
2128 // specified transform.
2129 BaseBounds tempBounds = TempState.getInstance().bounds;
2130 tempBounds = getTransformedBounds(tempBounds, transform);
2131 x = tempBounds.getMinX();
2132 y = tempBounds.getMinY();
2133 w = tempBounds.getWidth();
2134 h = tempBounds.getHeight();
2135 }
2136 WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h,
2137 this, transform, params.isDepthBufferInternal(),
2138 params.getFill(), params.getEffectiveCamera(), img);
2139
2140 return result;
2141 }
2142
2143 /**
2144 * Takes a snapshot of this node and returns the rendered image when
2145 * it is ready.
2146 * CSS and layout processing will be done for the node, and any of its
2147 * children, prior to rendering it.
2148 * The entire destination image is cleared to the fill {@code Paint}
2149 * specified by the SnapshotParameters. This node is then rendered to
2150 * the image.
2151 * If the viewport specified by the SnapshotParameters is null, the
2152 * upper-left pixel of the {@code boundsInParent} of this
2153 * node, after first applying the transform specified by the
2154 * SnapshotParameters,
2155 * is mapped to the upper-left pixel (0,0) in the image.
2156 * If a non-null viewport is specified,
2157 * the upper-left pixel of the viewport is mapped to upper-left pixel
2158 * (0,0) in the image.
2159 * In both cases, this mapping to (0,0) of the image is done with an integer
2160 * translation. The portion of the node that is outside of the rendered
2161 * image will be clipped by the image.
2162 *
2163 * <p>
2164 * When taking a snapshot of a scene that is being animated, either
2165 * explicitly by the application or implicitly (such as chart animation),
2166 * the snapshot will be rendered based on the state of the scene graph at
2167 * the moment the snapshot is taken and will not reflect any subsequent
2168 * animation changes.
2169 * </p>
2170 *
2171 * <p>
2172 * NOTE: In order for CSS and layout to function correctly, the node
2173 * must be part of a Scene (the Scene may be attached to a Stage, but need
2174 * not be).
2175 * </p>
2176 *
2177 * @param params the snapshot parameters containing attributes that
2178 * will control the rendering. If the SnapshotParameters object is null,
2179 * then the Scene's attributes will be used if this node is part of a scene,
2180 * or default attributes will be used if this node is not part of a scene.
2181 *
2182 * @param image the writable image that will be used to hold the rendered node.
2183 * It may be null in which case a new WritableImage will be constructed.
2184 * The new image is constructed using integer width and
2185 * height values that are derived either from the transformed bounds of this
2186 * Node or from the size of the viewport as specified in the
2187 * SnapShotParameters. These integer values are chosen such that the image
2188 * will wholly contain the bounds of this Node or the specified viewport.
2189 * If the image is non-null, the node will be rendered into the
2190 * existing image.
2191 * In this case, the width and height of the image determine the area
2192 * that is rendered instead of the width and height of the bounds or
2193 * viewport.
2194 *
2195 * @throws IllegalStateException if this method is called on a thread
2196 * other than the JavaFX Application Thread.
2197 *
2198 * @return the rendered image
2199 * @since JavaFX 2.2
2200 */
2201 public WritableImage snapshot(SnapshotParameters params, WritableImage image) {
2202 Toolkit.getToolkit().checkFxUserThread();
2203
2204 if (params == null) {
2205 params = new SnapshotParameters();
2206 Scene s = getScene();
2207 if (s != null) {
2208 params.setCamera(s.getEffectiveCamera());
2209 params.setDepthBuffer(s.isDepthBufferInternal());
2210 params.setFill(s.getFill());
2211 }
2212 }
2213
2214 return doSnapshot(params, image);
2215 }
2216
2217 /**
2218 * Takes a snapshot of this node at the next frame and calls the
2219 * specified callback method when the image is ready.
2220 * CSS and layout processing will be done for the node, and any of its
2221 * children, prior to rendering it.
2222 * The entire destination image is cleared to the fill {@code Paint}
2223 * specified by the SnapshotParameters. This node is then rendered to
2224 * the image.
2225 * If the viewport specified by the SnapshotParameters is null, the
2226 * upper-left pixel of the {@code boundsInParent} of this
2227 * node, after first applying the transform specified by the
2228 * SnapshotParameters,
2229 * is mapped to the upper-left pixel (0,0) in the image.
2230 * If a non-null viewport is specified,
2231 * the upper-left pixel of the viewport is mapped to upper-left pixel
2232 * (0,0) in the image.
2233 * In both cases, this mapping to (0,0) of the image is done with an integer
2234 * translation. The portion of the node that is outside of the rendered
2235 * image will be clipped by the image.
2236 *
2237 * <p>
2238 * This is an asynchronous call, which means that other
2239 * events or animation might be processed before the node is rendered.
2240 * If any such events modify the node, or any of its children, that
2241 * modification will be reflected in the rendered image (just like it
2242 * will also be reflected in the frame rendered to the Stage, if this node
2243 * is part of a live scene graph).
2244 * </p>
2245 *
2246 * <p>
2247 * When taking a snapshot of a node that is being animated, either
2248 * explicitly by the application or implicitly (such as chart animation),
2249 * the snapshot will be rendered based on the state of the scene graph at
2250 * the moment the snapshot is taken and will not reflect any subsequent
2251 * animation changes.
2252 * </p>
2253 *
2254 * <p>
2255 * NOTE: In order for CSS and layout to function correctly, the node
2256 * must be part of a Scene (the Scene may be attached to a Stage, but need
2257 * not be).
2258 * </p>
2259 *
2260 * @param callback a class whose call method will be called when the image
2261 * is ready. The SnapshotResult that is passed into the call method of
2262 * the callback will contain the rendered image, the source node
2263 * that was rendered, and a copy of the SnapshotParameters.
2264 * The callback parameter must not be null.
2265 *
2266 * @param params the snapshot parameters containing attributes that
2267 * will control the rendering. If the SnapshotParameters object is null,
2268 * then the Scene's attributes will be used if this node is part of a scene,
2269 * or default attributes will be used if this node is not part of a scene.
2270 *
2271 * @param image the writable image that will be used to hold the rendered node.
2272 * It may be null in which case a new WritableImage will be constructed.
2273 * The new image is constructed using integer width and
2274 * height values that are derived either from the transformed bounds of this
2275 * Node or from the size of the viewport as specified in the
2276 * SnapShotParameters. These integer values are chosen such that the image
2277 * will wholly contain the bounds of this Node or the specified viewport.
2278 * If the image is non-null, the node will be rendered into the
2279 * existing image.
2280 * In this case, the width and height of the image determine the area
2281 * that is rendered instead of the width and height of the bounds or
2282 * viewport.
2283 *
2284 * @throws IllegalStateException if this method is called on a thread
2285 * other than the JavaFX Application Thread.
2286 *
2287 * @throws NullPointerException if the callback parameter is null.
2288 * @since JavaFX 2.2
2289 */
2290 public void snapshot(Callback<SnapshotResult, Void> callback,
2291 SnapshotParameters params, WritableImage image) {
2292
2293 Toolkit.getToolkit().checkFxUserThread();
2294 if (callback == null) {
2295 throw new NullPointerException("The callback must not be null");
2296 }
2297
2298 if (params == null) {
2299 params = new SnapshotParameters();
2300 Scene s = getScene();
2301 if (s != null) {
2302 params.setCamera(s.getEffectiveCamera());
2303 params.setDepthBuffer(s.isDepthBufferInternal());
2304 params.setFill(s.getFill());
2305 }
2306 } else {
2307 params = params.copy();
2308 }
2309
2310 final SnapshotParameters theParams = params;
2311 final Callback<SnapshotResult, Void> theCallback = callback;
2312 final WritableImage theImage = image;
2313
2314 // Create a deferred runnable that will be run from a pulse listener
2315 // that is called after all of the scenes have been synced but before
2316 // any of them have been rendered.
2317 final Runnable snapshotRunnable = () -> {
2318 WritableImage img = doSnapshot(theParams, theImage);
2319 SnapshotResult result = new SnapshotResult(img, Node.this, theParams);
2320 // System.err.println("Calling snapshot callback");
2321 try {
2322 Void v = theCallback.call(result);
2323 } catch (Throwable th) {
2324 System.err.println("Exception in snapshot callback");
2325 th.printStackTrace(System.err);
2326 }
2327 };
2328
2329 // System.err.println("Schedule a snapshot in the future");
2330 Scene.addSnapshotRunnable(snapshotRunnable);
2331 }
2332
2333 /* ************************************************************************
2334 * *
2335 *
2336 * *
2337 *************************************************************************/
2338
2339 public final void setOnDragEntered(
2340 EventHandler<? super DragEvent> value) {
2341 onDragEnteredProperty().set(value);
2342 }
2343
2344 public final EventHandler<? super DragEvent> getOnDragEntered() {
2345 return (eventHandlerProperties == null)
2346 ? null : eventHandlerProperties.getOnDragEntered();
2347 }
2348
2349 /**
2350 * Defines a function to be called when drag gesture
2351 * enters this {@code Node}.
2352 * @return the event handler that is called when drag gesture enters this
2353 * {@code Node}
2354 */
2355 public final ObjectProperty<EventHandler<? super DragEvent>>
2356 onDragEnteredProperty() {
2357 return getEventHandlerProperties().onDragEnteredProperty();
2358 }
2359
2360 public final void setOnDragExited(
2361 EventHandler<? super DragEvent> value) {
2362 onDragExitedProperty().set(value);
2363 }
2364
2365 public final EventHandler<? super DragEvent> getOnDragExited() {
2366 return (eventHandlerProperties == null)
2367 ? null : eventHandlerProperties.getOnDragExited();
2368 }
2369
2370 /**
2371 * Defines a function to be called when drag gesture
2372 * exits this {@code Node}.
2373 * @return the event handler that is called when drag gesture exits this
2374 * {@code Node}
2375 */
2376 public final ObjectProperty<EventHandler<? super DragEvent>>
2377 onDragExitedProperty() {
2378 return getEventHandlerProperties().onDragExitedProperty();
2379 }
2380
2381 public final void setOnDragOver(
2382 EventHandler<? super DragEvent> value) {
2383 onDragOverProperty().set(value);
2384 }
2385
2386 public final EventHandler<? super DragEvent> getOnDragOver() {
2387 return (eventHandlerProperties == null)
2388 ? null : eventHandlerProperties.getOnDragOver();
2389 }
2390
2391 /**
2392 * Defines a function to be called when drag gesture progresses within
2393 * this {@code Node}.
2394 * @return the event handler that is called when drag gesture progresses
2395 * within this {@code Node}
2396 */
2397 public final ObjectProperty<EventHandler<? super DragEvent>>
2398 onDragOverProperty() {
2399 return getEventHandlerProperties().onDragOverProperty();
2400 }
2401
2402 // Do we want DRAG_TRANSFER_MODE_CHANGED event?
2403 // public final void setOnDragTransferModeChanged(
2404 // EventHandler<? super DragEvent> value) {
2405 // onDragTransferModeChangedProperty().set(value);
2406 // }
2407 //
2408 // public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() {
2409 // return (eventHandlerProperties == null)
2410 // ? null : eventHandlerProperties.getOnDragTransferModeChanged();
2411 // }
2412 //
2413 // /**
2414 // * Defines a function to be called this {@code Node} if it is a potential
2415 // * drag-and-drop target when the user takes action to change the intended
2416 // * {@code TransferMode}.
2417 // * The user can change the intended {@link TransferMode} by holding down
2418 // * or releasing key modifiers.
2419 // */
2420 // public final ObjectProperty<EventHandler<? super DragEvent>>
2421 // onDragTransferModeChangedProperty() {
2422 // return getEventHandlerProperties().onDragTransferModeChangedProperty();
2423 // }
2424
2425 public final void setOnDragDropped(
2426 EventHandler<? super DragEvent> value) {
2427 onDragDroppedProperty().set(value);
2428 }
2429
2430 public final EventHandler<? super DragEvent> getOnDragDropped() {
2431 return (eventHandlerProperties == null)
2432 ? null : eventHandlerProperties.getOnDragDropped();
2433 }
2434
2435 /**
2436 * Defines a function to be called when the mouse button is released
2437 * on this {@code Node} during drag and drop gesture. Transfer of data from
2438 * the {@link DragEvent}'s {@link DragEvent#getDragboard() dragboard} should
2439 * happen in this function.
2440 * @return the event handler that is called when the mouse button is
2441 * released on this {@code Node}
2442 */
2443 public final ObjectProperty<EventHandler<? super DragEvent>>
2444 onDragDroppedProperty() {
2445 return getEventHandlerProperties().onDragDroppedProperty();
2446 }
2447
2448 public final void setOnDragDone(
2449 EventHandler<? super DragEvent> value) {
2450 onDragDoneProperty().set(value);
2451 }
2452
2453 public final EventHandler<? super DragEvent> getOnDragDone() {
2454 return (eventHandlerProperties == null)
2455 ? null : eventHandlerProperties.getOnDragDone();
2456 }
2457
2458 /**
2459 * Defines a function to be called when this {@code Node} is a
2460 * drag and drop gesture source after its data has
2461 * been dropped on a drop target. The {@code transferMode} of the
2462 * event shows what just happened at the drop target.
2463 * If {@code transferMode} has the value {@code MOVE}, then the source can
2464 * clear out its data. Clearing the source's data gives the appropriate
2465 * appearance to a user that the data has been moved by the drag and drop
2466 * gesture. A {@code transferMode} that has the value {@code NONE}
2467 * indicates that no data was transferred during the drag and drop gesture.
2468 * @return the event handler that is called when this {@code Node} is a drag
2469 * and drop gesture source after its data has been dropped on a drop target
2470 */
2471 public final ObjectProperty<EventHandler<? super DragEvent>>
2472 onDragDoneProperty() {
2473 return getEventHandlerProperties().onDragDoneProperty();
2474 }
2475
2476 /**
2477 * Confirms a potential drag and drop gesture that is recognized over this
2478 * {@code Node}.
2479 * Can be called only from a DRAG_DETECTED event handler. The returned
2480 * {@link Dragboard} is used to transfer data during
2481 * the drag and drop gesture. Placing this {@code Node}'s data on the
2482 * {@link Dragboard} also identifies this {@code Node} as the source of
2483 * the drag and drop gesture.
2484 * More detail about drag and drop gestures is described in the overivew
2485 * of {@link DragEvent}.
2486 *
2487 * @see DragEvent
2488 * @param transferModes The supported {@code TransferMode}(s) of this {@code Node}
2489 * @return A {@code Dragboard} to place this {@code Node}'s data on
2490 * @throws IllegalStateException if drag and drop cannot be started at this
2491 * moment (it's called outside of {@code DRAG_DETECTED} event handling or
2492 * this node is not in scene).
2493 */
2494 public Dragboard startDragAndDrop(TransferMode... transferModes) {
2495 if (getScene() != null) {
2496 return getScene().startDragAndDrop(this, transferModes);
2497 }
2498
2499 throw new IllegalStateException("Cannot start drag and drop on node "
2500 + "that is not in scene");
2501 }
2502
2503 /**
2504 * Starts a full press-drag-release gesture with this node as gesture
2505 * source. This method can be called only from a {@code DRAG_DETECTED} mouse
2506 * event handler. More detail about dragging gestures can be found
2507 * in the overview of {@link MouseEvent} and {@link MouseDragEvent}.
2508 *
2509 * @see MouseEvent
2510 * @see MouseDragEvent
2511 * @throws IllegalStateException if the full press-drag-release gesture
2512 * cannot be started at this moment (it's called outside of
2513 * {@code DRAG_DETECTED} event handling or this node is not in scene).
2514 * @since JavaFX 2.1
2515 */
2516 public void startFullDrag() {
2517 if (getScene() != null) {
2518 getScene().startFullDrag(this);
2519 return;
2520 }
2521
2522 throw new IllegalStateException("Cannot start full drag on node "
2523 + "that is not in scene");
2524 }
2525
2526 ////////////////////////////
2527 // Private Implementation
2528 ////////////////////////////
2529
2530 /**
2531 * If this Node is being used as the clip of another Node, that other node
2532 * is referred to as the clipParent. If the boundsInParent of this Node
2533 * changes, it must update the clipParent's bounds as well.
2534 */
2535 private Node clipParent;
2536 // Use a getter function instead of giving clipParent package access,
2537 // so that clipParent doesn't get turned into a Location.
2538 final Node getClipParent() {
2539 return clipParent;
2540 }
2541
2542 /**
2543 * Determines whether this node is connected anywhere in the scene graph.
2544 */
2545 boolean isConnected() {
2546 // don't need to check scene, because if scene is non-null
2547 // parent must also be non-null
2548 return getParent() != null || clipParent != null;
2549 }
2550
2551 /**
2552 * Tests whether creating a parent-child relationship between these
2553 * nodes would cause a cycle. The parent relationship includes not only
2554 * the "real" parent (child of Group) but also the clipParent.
2555 */
2556 boolean wouldCreateCycle(Node parent, Node child) {
2557 if (child != null && child.getClip() == null && (!(child instanceof Parent))) {
2558 return false;
2559 }
2560
2561 Node n = parent;
2562 while (n != child) {
2563 if (n.getParent() != null) {
2564 n = n.getParent();
2565 } else if (n.getSubScene() != null) {
2566 n = n.getSubScene();
2567 } else if (n.clipParent != null) {
2568 n = n.clipParent;
2569 } else {
2570 return false;
2571 }
2572 }
2573 return true;
2574 }
2575
2576 /**
2577 * The peer node created by the graphics Toolkit/Pipeline implementation
2578 */
2579 private NGNode peer;
2580
2581 @SuppressWarnings("CallToPrintStackTrace")
2582 <P extends NGNode> P getPeer() {
2583 if (Utils.assertionEnabled()) {
2584 // Assertion checking code
2585 if (getScene() != null && !Scene.isPGAccessAllowed()) {
2586 java.lang.System.err.println();
2587 java.lang.System.err.println("*** unexpected PG access");
2588 java.lang.Thread.dumpStack();
2589 }
2590 }
2591
2592 if (peer == null) {
2593 //if (PerformanceTracker.isLoggingEnabled()) {
2594 // PerformanceTracker.logEvent("Creating NGNode for [{this}, id=\"{id}\"]");
2595 //}
2596 peer = NodeHelper.createPeer(this);
2597 //if (PerformanceTracker.isLoggingEnabled()) {
2598 // PerformanceTracker.logEvent("NGNode created");
2599 //}
2600 }
2601 return (P) peer;
2602 }
2603
2604 /***************************************************************************
2605 * *
2606 * Initialization *
2607 * *
2608 * To Note limit the number of bounds computations and improve startup *
2609 * performance. *
2610 * *
2611 **************************************************************************/
2612
2613 /**
2614 * Creates a new instance of Node.
2615 */
2616 protected Node() {
2617 //if (PerformanceTracker.isLoggingEnabled()) {
2618 // PerformanceTracker.logEvent("Node.init for [{this}, id=\"{id}\"]");
2619 //}
2620 updateTreeVisible(false);
2621 //if (PerformanceTracker.isLoggingEnabled()) {
2622 // PerformanceTracker.logEvent("Node.postinit " +
2623 // "for [{this}, id=\"{id}\"] finished");
2624 //}
2625 }
2626
2627 /***************************************************************************
2628 * *
2629 * Layout related APIs. *
2630 * *
2631 **************************************************************************/
2632 /**
2633 * Defines whether or not this node's layout will be managed by it's parent.
2634 * If the node is managed, it's parent will factor the node's geometry
2635 * into its own preferred size and {@link #layoutBoundsProperty layoutBounds}
2636 * calculations and will lay it
2637 * out during the scene's layout pass. If a managed node's layoutBounds
2638 * changes, it will automatically trigger relayout up the scene-graph
2639 * to the nearest layout root (which is typically the scene's root node).
2640 * <p>
2641 * If the node is unmanaged, its parent will ignore the child in both preferred
2642 * size computations and layout. Changes in layoutBounds will not trigger
2643 * relayout above it. If an unmanaged node is of type {@link javafx.scene.Parent Parent},
2644 * it will act as a "layout root", meaning that calls to {@link Parent#requestLayout()}
2645 * beneath it will cause only the branch rooted by the node to be relayed out,
2646 * thereby isolating layout changes to that root and below. It's the application's
2647 * responsibility to set the size and position of an unmanaged node.
2648 * <p>
2649 * By default all nodes are managed.
2650 * </p>
2651 *
2652 * @see #isResizable()
2653 * @see #layoutBoundsProperty()
2654 * @see Parent#requestLayout()
2655 *
2656 */
2657 private BooleanProperty managed;
2658
2659 public final void setManaged(boolean value) {
2660 managedProperty().set(value);
2661 }
2662
2663 public final boolean isManaged() {
2664 return managed == null ? true : managed.get();
2665 }
2666
2667 public final BooleanProperty managedProperty() {
2668 if (managed == null) {
2669 managed = new BooleanPropertyBase(true) {
2670
2671 @Override
2672 protected void invalidated() {
2673 final Parent parent = getParent();
2674 if (parent != null) {
2675 parent.managedChildChanged();
2676 }
2677 notifyManagedChanged();
2678 }
2679
2680 @Override
2681 public Object getBean() {
2682 return Node.this;
2683 }
2684
2685 @Override
2686 public String getName() {
2687 return "managed";
2688 }
2689
2690 };
2691 }
2692 return managed;
2693 }
2694
2695 /**
2696 * Called whenever the "managed" flag has changed. This is only
2697 * used by Parent as an optimization to keep track of whether a
2698 * Parent node is a layout root or not.
2699 */
2700 void notifyManagedChanged() { }
2701
2702 /**
2703 * Defines the x coordinate of the translation that is added to this {@code Node}'s
2704 * transform for the purpose of layout. The value should be computed as the
2705 * offset required to adjust the position of the node from its current
2706 * {@link #layoutBoundsProperty() layoutBounds minX} position (which might not be 0) to the desired location.
2707 *
2708 * <p>For example, if {@code textnode} should be positioned at {@code finalX}
2709 * <pre>{@code
2710 * textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
2711 * }</pre>
2712 * <p>
2713 * Failure to subtract {@code layoutBounds minX} may result in misplacement
2714 * of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
2715 * correct computation and should generally be used over setting layoutX directly.
2716 * <p>
2717 * The node's final translation will be computed as {@code layoutX} + {@link #translateXProperty translateX},
2718 * where {@code layoutX} establishes the node's stable position
2719 * and {@code translateX} optionally makes dynamic adjustments to that
2720 * position.
2721 * <p>
2722 * If the node is managed and has a {@link javafx.scene.layout.Region}
2723 * as its parent, then the layout region will set {@code layoutX} according to its
2724 * own layout policy. If the node is unmanaged or parented by a {@link Group},
2725 * then the application may set {@code layoutX} directly to position it.
2726 *
2727 * @see #relocate(double, double)
2728 * @see #layoutBoundsProperty()
2729 *
2730 */
2731 private DoubleProperty layoutX;
2732
2733 public final void setLayoutX(double value) {
2734 layoutXProperty().set(value);
2735 }
2736
2737 public final double getLayoutX() {
2738 return layoutX == null ? 0.0 : layoutX.get();
2739 }
2740
2741 public final DoubleProperty layoutXProperty() {
2742 if (layoutX == null) {
2743 layoutX = new DoublePropertyBase(0.0) {
2744
2745 @Override
2746 protected void invalidated() {
2747 NodeHelper.transformsChanged(Node.this);
2748 final Parent p = getParent();
2749
2750 // Propagate layout if this change isn't triggered by its parent
2751 if (p != null && !p.isCurrentLayoutChild(Node.this)) {
2752 if (isManaged()) {
2753 // Force its parent to fix the layout since it is a managed child.
2754 p.requestLayout(true);
2755 } else {
2756 // Parent size changed, parent's parent might need to re-layout
2757 p.clearSizeCache();
2758 p.requestParentLayout();
2759 }
2760 }
2761 }
2762
2763 @Override
2764 public Object getBean() {
2765 return Node.this;
2766 }
2767
2768 @Override
2769 public String getName() {
2770 return "layoutX";
2771 }
2772 };
2773 }
2774 return layoutX;
2775 }
2776
2777 /**
2778 * Defines the y coordinate of the translation that is added to this {@code Node}'s
2779 * transform for the purpose of layout. The value should be computed as the
2780 * offset required to adjust the position of the node from its current
2781 * {@link #layoutBoundsProperty() layoutBounds minY} position (which might not be 0) to the desired location.
2782 *
2783 * <p>For example, if {@code textnode} should be positioned at {@code finalY}
2784 * <pre>{@code
2785 * textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
2786 * }</pre>
2787 * <p>
2788 * Failure to subtract {@code layoutBounds minY} may result in misplacement
2789 * of the node. The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
2790 * correct computation and should generally be used over setting layoutY directly.
2791 * <p>
2792 * The node's final translation will be computed as {@code layoutY} + {@link #translateYProperty translateY},
2793 * where {@code layoutY} establishes the node's stable position
2794 * and {@code translateY} optionally makes dynamic adjustments to that
2795 * position.
2796 * <p>
2797 * If the node is managed and has a {@link javafx.scene.layout.Region}
2798 * as its parent, then the region will set {@code layoutY} according to its
2799 * own layout policy. If the node is unmanaged or parented by a {@link Group},
2800 * then the application may set {@code layoutY} directly to position it.
2801 *
2802 * @see #relocate(double, double)
2803 * @see #layoutBoundsProperty()
2804 */
2805 private DoubleProperty layoutY;
2806
2807 public final void setLayoutY(double value) {
2808 layoutYProperty().set(value);
2809 }
2810
2811 public final double getLayoutY() {
2812 return layoutY == null ? 0.0 : layoutY.get();
2813 }
2814
2815 public final DoubleProperty layoutYProperty() {
2816 if (layoutY == null) {
2817 layoutY = new DoublePropertyBase(0.0) {
2818
2819 @Override
2820 protected void invalidated() {
2821 NodeHelper.transformsChanged(Node.this);
2822 final Parent p = getParent();
2823
2824 // Propagate layout if this change isn't triggered by its parent
2825 if (p != null && !p.isCurrentLayoutChild(Node.this)) {
2826 if (isManaged()) {
2827 // Force its parent to fix the layout since it is a managed child.
2828 p.requestLayout(true);
2829 } else {
2830 // Parent size changed, parent's parent might need to re-layout
2831 p.clearSizeCache();
2832 p.requestParentLayout();
2833 }
2834 }
2835 }
2836
2837 @Override
2838 public Object getBean() {
2839 return Node.this;
2840 }
2841
2842 @Override
2843 public String getName() {
2844 return "layoutY";
2845 }
2846
2847 };
2848 }
2849 return layoutY;
2850 }
2851
2852 /**
2853 * Sets the node's layoutX and layoutY translation properties in order to
2854 * relocate this node to the x,y location in the parent.
2855 * <p>
2856 * This method does not alter translateX or translateY, which if also set
2857 * will be added to layoutX and layoutY, adjusting the final location by
2858 * corresponding amounts.
2859 *
2860 * @param x the target x coordinate location
2861 * @param y the target y coordinate location
2862 */
2863 public void relocate(double x, double y) {
2864 setLayoutX(x - getLayoutBounds().getMinX());
2865 setLayoutY(y - getLayoutBounds().getMinY());
2866
2867 PlatformLogger logger = Logging.getLayoutLogger();
2868 if (logger.isLoggable(Level.FINER)) {
2869 logger.finer(this.toString()+" moved to ("+x+","+y+")");
2870 }
2871 }
2872
2873 /**
2874 * Indicates whether this node is a type which can be resized by its parent.
2875 * If this method returns true, then the parent will resize the node (ideally
2876 * within its size range) by calling node.resize(width,height) during the
2877 * layout pass. All Regions, Controls, and WebView are resizable classes
2878 * which depend on their parents resizing them during layout once all sizing
2879 * and CSS styling information has been applied.
2880 * <p>
2881 * If this method returns false, then the parent cannot resize it during
2882 * layout (resize() is a no-op) and it should return its layoutBounds for
2883 * minimum, preferred, and maximum sizes. Group, Text, and all Shapes are not
2884 * resizable and hence depend on the application to establish their sizing
2885 * by setting appropriate properties (e.g. width/height for Rectangle,
2886 * text on Text, and so on). Non-resizable nodes may still be relocated
2887 * during layout.
2888 *
2889 * @see #getContentBias()
2890 * @see #minWidth(double)
2891 * @see #minHeight(double)
2892 * @see #prefWidth(double)
2893 * @see #prefHeight(double)
2894 * @see #maxWidth(double)
2895 * @see #maxHeight(double)
2896 * @see #resize(double, double)
2897 * @see #getLayoutBounds()
2898 *
2899 * @return whether or not this node type can be resized by its parent during layout
2900 */
2901 public boolean isResizable() {
2902 return false;
2903 }
2904
2905 /**
2906 * Returns the orientation of a node's resizing bias for layout purposes.
2907 * If the node type has no bias, returns null. If the node is resizable and
2908 * it's height depends on its width, returns HORIZONTAL, else if its width
2909 * depends on its height, returns VERTICAL.
2910 * <p>
2911 * Resizable subclasses should override this method to return an
2912 * appropriate value.
2913 *
2914 * @see #isResizable()
2915 * @see #minWidth(double)
2916 * @see #minHeight(double)
2917 * @see #prefWidth(double)
2918 * @see #prefHeight(double)
2919 * @see #maxWidth(double)
2920 * @see #maxHeight(double)
2921 *
2922 * @return orientation of width/height dependency or null if there is none
2923 */
2924 public Orientation getContentBias() {
2925 return null;
2926 }
2927
2928 /**
2929 * Returns the node's minimum width for use in layout calculations.
2930 * If the node is resizable, its parent should not resize its width any
2931 * smaller than this value. If the node is not resizable, returns its
2932 * layoutBounds width.
2933 * <p>
2934 * Layout code which calls this method should first check the content-bias
2935 * of the node. If the node has a vertical content-bias, then callers
2936 * should pass in a height value that the minimum width should be based on.
2937 * If the node has either a horizontal or null content-bias, then the caller
2938 * should pass in -1.
2939 * <p>
2940 * Node subclasses with a vertical content-bias should honor the height
2941 * parameter whether -1 or a positive value. All other subclasses may ignore
2942 * the height parameter (which will likely be -1).
2943 * <p>
2944 * If Node's {@link #maxWidth(double)} is lower than this number,
2945 * {@code minWidth} takes precedence. This means the Node should never be resized below {@code minWidth}.
2946 *
2947 * @see #isResizable()
2948 * @see #getContentBias()
2949 *
2950 * @param height the height that should be used if minimum width depends on it
2951 * @return the minimum width that the node should be resized to during layout.
2952 * The result will never be NaN, nor will it ever be negative.
2953 */
2954 public double minWidth(double height) {
2955 return prefWidth(height);
2956 }
2957
2958 /**
2959 * Returns the node's minimum height for use in layout calculations.
2960 * If the node is resizable, its parent should not resize its height any
2961 * smaller than this value. If the node is not resizable, returns its
2962 * layoutBounds height.
2963 * <p>
2964 * Layout code which calls this method should first check the content-bias
2965 * of the node. If the node has a horizontal content-bias, then callers
2966 * should pass in a width value that the minimum height should be based on.
2967 * If the node has either a vertical or null content-bias, then the caller
2968 * should pass in -1.
2969 * <p>
2970 * Node subclasses with a horizontal content-bias should honor the width
2971 * parameter whether -1 or a positive value. All other subclasses may ignore
2972 * the width parameter (which will likely be -1).
2973 * <p>
2974 * If Node's {@link #maxHeight(double)} is lower than this number,
2975 * {@code minHeight} takes precedence. This means the Node should never be resized below {@code minHeight}.
2976 *
2977 * @see #isResizable()
2978 * @see #getContentBias()
2979 *
2980 * @param width the width that should be used if minimum height depends on it
2981 * @return the minimum height that the node should be resized to during layout
2982 * The result will never be NaN, nor will it ever be negative.
2983 */
2984 public double minHeight(double width) {
2985 return prefHeight(width);
2986 }
2987
2988 /**
2989 * Returns the node's preferred width for use in layout calculations.
2990 * If the node is resizable, its parent should treat this value as the
2991 * node's ideal width within its range. If the node is not resizable,
2992 * just returns its layoutBounds width, which should be treated as the rigid
2993 * width of the node.
2994 * <p>
2995 * Layout code which calls this method should first check the content-bias
2996 * of the node. If the node has a vertical content-bias, then callers
2997 * should pass in a height value that the preferred width should be based on.
2998 * If the node has either a horizontal or null content-bias, then the caller
2999 * should pass in -1.
3000 * <p>
3001 * Node subclasses with a vertical content-bias should honor the height
3002 * parameter whether -1 or a positive value. All other subclasses may ignore
3003 * the height parameter (which will likely be -1).
3004 *
3005 * @see #isResizable()
3006 * @see #getContentBias()
3007 * @see #autosize()
3008 *
3009 * @param height the height that should be used if preferred width depends on it
3010 * @return the preferred width that the node should be resized to during layout
3011 * The result will never be NaN, nor will it ever be negative.
3012 */
3013 public double prefWidth(double height) {
3014 final double result = getLayoutBounds().getWidth();
3015 return Double.isNaN(result) || result < 0 ? 0 : result;
3016 }
3017
3018 /**
3019 * Returns the node's preferred height for use in layout calculations.
3020 * If the node is resizable, its parent should treat this value as the
3021 * node's ideal height within its range. If the node is not resizable,
3022 * just returns its layoutBounds height, which should be treated as the rigid
3023 * height of the node.
3024 * <p>
3025 * Layout code which calls this method should first check the content-bias
3026 * of the node. If the node has a horizontal content-bias, then callers
3027 * should pass in a width value that the preferred height should be based on.
3028 * If the node has either a vertical or null content-bias, then the caller
3029 * should pass in -1.
3030 * <p>
3031 * Node subclasses with a horizontal content-bias should honor the height
3032 * parameter whether -1 or a positive value. All other subclasses may ignore
3033 * the height parameter (which will likely be -1).
3034 *
3035 * @see #getContentBias()
3036 * @see #autosize()
3037 *
3038 * @param width the width that should be used if preferred height depends on it
3039 * @return the preferred height that the node should be resized to during layout
3040 * The result will never be NaN, nor will it ever be negative.
3041 */
3042 public double prefHeight(double width) {
3043 final double result = getLayoutBounds().getHeight();
3044 return Double.isNaN(result) || result < 0 ? 0 : result;
3045 }
3046
3047 /**
3048 * Returns the node's maximum width for use in layout calculations.
3049 * If the node is resizable, its parent should not resize its width any
3050 * larger than this value. A value of Double.MAX_VALUE indicates the
3051 * parent may expand the node's width beyond its preferred without limits.
3052 * <p>
3053 * If the node is not resizable, returns its layoutBounds width.
3054 * <p>
3055 * Layout code which calls this method should first check the content-bias
3056 * of the node. If the node has a vertical content-bias, then callers
3057 * should pass in a height value that the maximum width should be based on.
3058 * If the node has either a horizontal or null content-bias, then the caller
3059 * should pass in -1.
3060 * <p>
3061 * Node subclasses with a vertical content-bias should honor the height
3062 * parameter whether -1 or a positive value. All other subclasses may ignore
3063 * the height parameter (which will likely be -1).
3064 * <p>
3065 * If Node's {@link #minWidth(double)} is greater, it should take precedence
3066 * over the {@code maxWidth}. This means the Node should never be resized below {@code minWidth}.
3067 *
3068 * @see #isResizable()
3069 * @see #getContentBias()
3070 *
3071 * @param height the height that should be used if maximum width depends on it
3072 * @return the maximum width that the node should be resized to during layout
3073 * The result will never be NaN, nor will it ever be negative.
3074 */
3075 public double maxWidth(double height) {
3076 return prefWidth(height);
3077 }
3078
3079 /**
3080 * Returns the node's maximum height for use in layout calculations.
3081 * If the node is resizable, its parent should not resize its height any
3082 * larger than this value. A value of Double.MAX_VALUE indicates the
3083 * parent may expand the node's height beyond its preferred without limits.
3084 * <p>
3085 * If the node is not resizable, returns its layoutBounds height.
3086 * <p>
3087 * Layout code which calls this method should first check the content-bias
3088 * of the node. If the node has a horizontal content-bias, then callers
3089 * should pass in a width value that the maximum height should be based on.
3090 * If the node has either a vertical or null content-bias, then the caller
3091 * should pass in -1.
3092 * <p>
3093 * Node subclasses with a horizontal content-bias should honor the width
3094 * parameter whether -1 or a positive value. All other subclasses may ignore
3095 * the width parameter (which will likely be -1).
3096 * <p>
3097 * If Node's {@link #minHeight(double)} is greater, it should take precedence
3098 * over the {@code maxHeight}. This means the Node should never be resized below {@code minHeight}.
3099 *
3100 * @see #isResizable()
3101 * @see #getContentBias()
3102 *
3103 * @param width the width that should be used if maximum height depends on it
3104 * @return the maximum height that the node should be resized to during layout
3105 * The result will never be NaN, nor will it ever be negative.
3106 */
3107 public double maxHeight(double width) {
3108 return prefHeight(width);
3109 }
3110
3111 /**
3112 * If the node is resizable, will set its layout bounds to the specified
3113 * width and height. If the node is not resizable, this method is a no-op.
3114 * <p>
3115 * This method should generally only be called by parent nodes from their
3116 * layoutChildren() methods. All Parent classes will automatically resize
3117 * resizable children, so resizing done directly by the application will be
3118 * overridden by the node's parent, unless the child is unmanaged.
3119 * <p>
3120 * Parents are responsible for ensuring the width and height values fall
3121 * within the resizable node's preferred range. The autosize() method may
3122 * be used if the parent just needs to resize the node to its preferred size.
3123 *
3124 * @see #isResizable()
3125 * @see #getContentBias()
3126 * @see #autosize()
3127 * @see #minWidth(double)
3128 * @see #minHeight(double)
3129 * @see #prefWidth(double)
3130 * @see #prefHeight(double)
3131 * @see #maxWidth(double)
3132 * @see #maxHeight(double)
3133 * @see #getLayoutBounds()
3134 *
3135 * @param width the target layout bounds width
3136 * @param height the target layout bounds height
3137 */
3138 public void resize(double width, double height) {
3139 }
3140
3141 /**
3142 * If the node is resizable, will set its layout bounds to its current preferred
3143 * width and height. If the node is not resizable, this method is a no-op.
3144 * <p>
3145 * This method automatically queries the node's content-bias and if it's
3146 * horizontal, will pass in the node's preferred width to get the preferred
3147 * height; if vertical, will pass in the node's preferred height to get the width,
3148 * and if null, will compute the preferred width/height independently.
3149 * </p>
3150 *
3151 * @see #isResizable()
3152 * @see #getContentBias()
3153 *
3154 */
3155 public final void autosize() {
3156 if (isResizable()) {
3157 Orientation contentBias = getContentBias();
3158 double w, h;
3159 if (contentBias == null) {
3160 w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
3161 h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
3162 } else if (contentBias == Orientation.HORIZONTAL) {
3163 w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
3164 h = boundedSize(prefHeight(w), minHeight(w), maxHeight(w));
3165 } else { // bias == VERTICAL
3166 h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
3167 w = boundedSize(prefWidth(h), minWidth(h), maxWidth(h));
3168 }
3169 resize(w,h);
3170 }
3171 }
3172
3173 double boundedSize(double value, double min, double max) {
3174 // if max < value, return max
3175 // if min > value, return min
3176 // if min > max, return min
3177 return Math.min(Math.max(value, min), Math.max(min,max));
3178 }
3179
3180 /**
3181 * If the node is resizable, will set its layout bounds to the specified
3182 * width and height. If the node is not resizable, the resize step is skipped.
3183 * <p>
3184 * Once the node has been resized (if resizable) then sets the node's layoutX
3185 * and layoutY translation properties in order to relocate it to x,y in the
3186 * parent's coordinate space.
3187 * <p>
3188 * This method should generally only be called by parent nodes from their
3189 * layoutChildren() methods. All Parent classes will automatically resize
3190 * resizable children, so resizing done directly by the application will be
3191 * overridden by the node's parent, unless the child is unmanaged.
3192 * <p>
3193 * Parents are responsible for ensuring the width and height values fall
3194 * within the resizable node's preferred range. The autosize() and relocate()
3195 * methods may be used if the parent just needs to resize the node to its
3196 * preferred size and reposition it.
3197 *
3198 * @see #isResizable()
3199 * @see #getContentBias()
3200 * @see #autosize()
3201 * @see #minWidth(double)
3202 * @see #minHeight(double)
3203 * @see #prefWidth(double)
3204 * @see #prefHeight(double)
3205 * @see #maxWidth(double)
3206 * @see #maxHeight(double)
3207 *
3208 * @param x the target x coordinate location
3209 * @param y the target y coordinate location
3210 * @param width the target layout bounds width
3211 * @param height the target layout bounds height
3212 *
3213 */
3214 public void resizeRelocate(double x, double y, double width, double height) {
3215 resize(width, height);
3216 relocate(x,y);
3217 }
3218
3219 /**
3220 * This is a special value that might be returned by {@link #getBaselineOffset()}.
3221 * This means that the Parent (layout Pane) of this Node should use the height of this Node as a baseline.
3222 */
3223 public static final double BASELINE_OFFSET_SAME_AS_HEIGHT = Double.NEGATIVE_INFINITY;
3224
3225 /**
3226 * The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location
3227 * that should be used when this node is being vertically aligned by baseline with
3228 * other nodes. By default this returns {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} for resizable Nodes
3229 * and layoutBounds height for non-resizable. Subclasses
3230 * which contain text should override this method to return their actual text baseline offset.
3231 *
3232 * @return offset of text baseline from layoutBounds.minY for non-resizable Nodes or {@link #BASELINE_OFFSET_SAME_AS_HEIGHT} otherwise
3233 */
3234 public double getBaselineOffset() {
3235 if (isResizable()) {
3236 return BASELINE_OFFSET_SAME_AS_HEIGHT;
3237 } else {
3238 return getLayoutBounds().getHeight();
3239 }
3240 }
3241
3242 /**
3243 * Returns the area of this {@code Node} projected onto the
3244 * physical screen in pixel units.
3245 * @return the area of this {@code Node} projected onto the physical screen
3246 * @since JavaFX 8.0
3247 */
3248 public double computeAreaInScreen() {
3249 return doComputeAreaInScreen();
3250 }
3251
3252 /*
3253 * Help application or utility to implement LOD support by returning the
3254 * projected area of a Node in pixel unit. The projected area is not clipped.
3255 *
3256 * For perspective camera, this method first exams node's bounds against
3257 * camera's clipping plane to cut off those out of viewing frustrum. After
3258 * computing areaInScreen, it applies a tight viewing frustrum check using
3259 * canonical view volume.
3260 *
3261 * The result of areaInScreen comes from the product of
3262 * (projViewTx x localToSceneTransform x localBounds).
3263 *
3264 * Returns 0 for those fall outside viewing frustrum.
3265 */
3266 private double doComputeAreaInScreen() {
3267 Scene tmpScene = getScene();
3268 if (tmpScene != null) {
3269 Bounds bounds = getBoundsInLocal();
3270 Camera camera = tmpScene.getEffectiveCamera();
3271 boolean isPerspective = camera instanceof PerspectiveCamera ? true : false;
3272 Transform localToSceneTx = getLocalToSceneTransform();
3273 Affine3D tempTx = TempState.getInstance().tempTx;
3274 BaseBounds localBounds = new BoxBounds((float) bounds.getMinX(),
3275 (float) bounds.getMinY(),
3276 (float) bounds.getMinZ(),
3277 (float) bounds.getMaxX(),
3278 (float) bounds.getMaxY(),
3279 (float) bounds.getMaxZ());
3280
3281 // NOTE: Viewing frustrum check on camera's clipping plane is now only
3282 // for perspective camera.
3283 // TODO: Need to hook up parallel camera's nearClip and farClip.
3284 if (isPerspective) {
3285 Transform cameraL2STx = camera.getLocalToSceneTransform();
3286
3287 // If camera transform only contains translate, compare in scene
3288 // coordinate. Otherwise, compare in camera coordinate.
3289 if (cameraL2STx.getMxx() == 1.0
3290 && cameraL2STx.getMxy() == 0.0
3291 && cameraL2STx.getMxz() == 0.0
3292 && cameraL2STx.getMyx() == 0.0
3293 && cameraL2STx.getMyy() == 1.0
3294 && cameraL2STx.getMyz() == 0.0
3295 && cameraL2STx.getMzx() == 0.0
3296 && cameraL2STx.getMzy() == 0.0
3297 && cameraL2STx.getMzz() == 1.0) {
3298
3299 double minZ, maxZ;
3300
3301 // If node transform only contains translate, only convert
3302 // minZ and maxZ to scene coordinate. Otherwise, convert
3303 // node bounds to scene coordinate.
3304 if (localToSceneTx.getMxx() == 1.0
3305 && localToSceneTx.getMxy() == 0.0
3306 && localToSceneTx.getMxz() == 0.0
3307 && localToSceneTx.getMyx() == 0.0
3308 && localToSceneTx.getMyy() == 1.0
3309 && localToSceneTx.getMyz() == 0.0
3310 && localToSceneTx.getMzx() == 0.0
3311 && localToSceneTx.getMzy() == 0.0
3312 && localToSceneTx.getMzz() == 1.0) {
3313
3314 Vec3d tempV3D = TempState.getInstance().vec3d;
3315 tempV3D.set(0, 0, bounds.getMinZ());
3316 localToScene(tempV3D);
3317 minZ = tempV3D.z;
3318
3319 tempV3D.set(0, 0, bounds.getMaxZ());
3320 localToScene(tempV3D);
3321 maxZ = tempV3D.z;
3322 } else {
3323 Bounds nodeInSceneBounds = localToScene(bounds);
3324 minZ = nodeInSceneBounds.getMinZ();
3325 maxZ = nodeInSceneBounds.getMaxZ();
3326 }
3327
3328 if (minZ > camera.getFarClipInScene()
3329 || maxZ < camera.getNearClipInScene()) {
3330 return 0;
3331 }
3332
3333 } else {
3334 BaseBounds nodeInCameraBounds = new BoxBounds();
3335
3336 // We need to set tempTx to identity since it is a recycled transform.
3337 // This is because TransformHelper.apply() is a matrix concatenation operation.
3338 tempTx.setToIdentity();
3339 TransformHelper.apply(localToSceneTx, tempTx);
3340
3341 // Convert node from local coordinate to camera coordinate
3342 tempTx.preConcatenate(camera.getSceneToLocalTransform());
3343 tempTx.transform(localBounds, nodeInCameraBounds);
3344
3345 // Compare in camera coordinate
3346 if (nodeInCameraBounds.getMinZ() > camera.getFarClip()
3347 || nodeInCameraBounds.getMaxZ() < camera.getNearClip()) {
3348 return 0;
3349 }
3350 }
3351 }
3352
3353 GeneralTransform3D projViewTx = TempState.getInstance().projViewTx;
3354 projViewTx.set(camera.getProjViewTransform());
3355
3356 // We need to set tempTx to identity since it is a recycled transform.
3357 // This is because TransformHelper.apply() is a matrix concatenation operation.
3358 tempTx.setToIdentity();
3359 TransformHelper.apply(localToSceneTx, tempTx);
3360
3361 // The product of projViewTx * localToSceneTransform
3362 GeneralTransform3D tx = projViewTx.mul(tempTx);
3363
3364 // Transform localBounds to projected bounds
3365 localBounds = tx.transform(localBounds, localBounds);
3366 double area = localBounds.getWidth() * localBounds.getHeight();
3367
3368 // Use canonical view volume to check whether object is outside the
3369 // viewing frustrum
3370 if (isPerspective) {
3371 localBounds.intersectWith(-1, -1, 0, 1, 1, 1);
3372 area = (localBounds.getWidth() < 0 || localBounds.getHeight() < 0) ? 0 : area;
3373 }
3374 return area * (camera.getViewWidth() / 2 * camera.getViewHeight() / 2);
3375 }
3376 return 0;
3377 }
3378
3379 /* *************************************************************************
3380 * *
3381 * Bounds related APIs *
3382 * *
3383 **************************************************************************/
3384
3385 public final Bounds getBoundsInParent() {
3386 return boundsInParentProperty().get();
3387 }
3388
3389 /**
3390 * The rectangular bounds of this {@code Node} which include its transforms.
3391 * {@code boundsInParent} is calculated by
3392 * taking the local bounds (defined by {@link #boundsInLocalProperty boundsInLocal}) and applying
3393 * the transform created by setting the following additional variables
3394 * <ol>
3395 * <li>{@link #getTransforms transforms} ObservableList</li>
3396 * <li>{@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}, {@link #scaleZProperty scaleZ}</li>
3397 * <li>{@link #rotateProperty rotate}</li>
3398 * <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li>
3399 * <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY},
3400 * {@link #translateZProperty translateZ}</li>
3401 * </ol>
3402 * <p>
3403 * The resulting bounds will be conceptually in the coordinate space of the
3404 * {@code Node}'s parent, however the node need not have a parent to calculate
3405 * these bounds.
3406 * <p>
3407 * Note that this method does not take the node's visibility into account;
3408 * the computation is based on the geometry of this {@code Node} only.
3409 * <p>
3410 * This property will always have a non-null value.
3411 * <p>
3412 * Note that {@code boundsInParent} is automatically recomputed whenever the
3413 * geometry of a node changes, or when any of the following the change:
3414 * transforms {@code ObservableList}, any of the translate, layout or scale
3415 * variables, or the rotate variable. For this reason, it is an error
3416 * to bind any of these values in a node to an expression that depends upon
3417 * this variable. For example, the x or y variables of a shape, or
3418 * {@code translateX}, {@code translateY} should never be bound to
3419 * {@code boundsInParent} for the purpose of positioning the node.
3420 * @return the boundsInParent for this {@code Node}
3421 */
3422 public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
3423 return getMiscProperties().boundsInParentProperty();
3424 }
3425
3426 private void invalidateBoundsInParent() {
3427 if (miscProperties != null) {
3428 miscProperties.invalidateBoundsInParent();
3429 }
3430 }
3431
3432 public final Bounds getBoundsInLocal() {
3433 return boundsInLocalProperty().get();
3434 }
3435
3436 /**
3437 * The rectangular bounds of this {@code Node} in the node's
3438 * untransformed local coordinate space. For nodes that extend
3439 * {@link javafx.scene.shape.Shape}, the local bounds will also include
3440 * space required for a non-zero stroke that may fall outside the shape's
3441 * geometry that is defined by position and size attributes.
3442 * The local bounds will also include any clipping set with {@link #clipProperty clip}
3443 * as well as effects set with {@link #effectProperty effect}.
3444 *
3445 * <p>
3446 * Note that this method does not take the node's visibility into account;
3447 * the computation is based on the geometry of this {@code Node} only.
3448 * <p>
3449 * This property will always have a non-null value.
3450 * <p>
3451 * Note that boundsInLocal is automatically recomputed whenever the
3452 * geometry of a node changes. For this reason, it is an error to bind any
3453 * of these values in a node to an expression that depends upon this variable.
3454 * For example, the x or y variables of a shape should never be bound
3455 * to boundsInLocal for the purpose of positioning the node.
3456 * @return the boundsInLocal for this {@code Node}
3457 */
3458 public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
3459 return getMiscProperties().boundsInLocalProperty();
3460 }
3461
3462 private void invalidateBoundsInLocal() {
3463 if (miscProperties != null) {
3464 miscProperties.invalidateBoundsInLocal();
3465 }
3466 }
3467
3468 /**
3469 * The rectangular bounds that should be used for layout calculations for
3470 * this node. {@code layoutBounds} may differ from the visual bounds
3471 * of the node and is computed differently depending on the node type.
3472 * <p>
3473 * If the node type is resizable ({@link javafx.scene.layout.Region Region},
3474 * {@link javafx.scene.control.Control Control}, or {@link javafx.scene.web.WebView WebView})
3475 * then the layoutBounds will always be {@code 0,0 width x height}.
3476 * If the node type is not resizable ({@link javafx.scene.shape.Shape Shape},
3477 * {@link javafx.scene.text.Text Text}, or {@link Group}), then the {@code layoutBounds}
3478 * are computed based on the node's geometric properties and does not include the
3479 * node's clip, effect, or transforms. See individual class documentation
3480 * for details.
3481 * <p>
3482 * Note that the {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY},
3483 * {@link #translateXProperty translateX}, and {@link #translateYProperty translateY}
3484 * variables are not included in the layoutBounds.
3485 * This is important because layout code must first determine the current
3486 * size and location of the node (using {@code layoutBounds}) and then set
3487 * {@code layoutX} and {@code layoutY} to adjust the translation of the
3488 * node so that it will have the desired layout position.
3489 * <p>
3490 * Because the computation of layoutBounds is often tied to a node's
3491 * geometric variables, it is an error to bind any such variables to an
3492 * expression that depends upon {@code layoutBounds}. For example, the
3493 * x or y variables of a shape should never be bound to {@code layoutBounds}
3494 * for the purpose of positioning the node.
3495 * <p>
3496 * Note that for 3D shapes, the layout bounds is actually a rectangular box
3497 * with X, Y, and Z values, although only X and Y are used in layout calculations.
3498 * <p>
3499 * The {@code layoutBounds} will never be null.
3500 *
3501 */
3502 private LazyBoundsProperty layoutBounds = new LazyBoundsProperty() {
3503 @Override
3504 protected Bounds computeBounds() {
3505 return NodeHelper.computeLayoutBounds(Node.this);
3506 }
3507
3508 @Override
3509 public Object getBean() {
3510 return Node.this;
3511 }
3512
3513 @Override
3514 public String getName() {
3515 return "layoutBounds";
3516 }
3517 };
3518
3519 public final Bounds getLayoutBounds() {
3520 return layoutBoundsProperty().get();
3521 }
3522
3523 public final ReadOnlyObjectProperty<Bounds> layoutBoundsProperty() {
3524 return layoutBounds;
3525 }
3526
3527 /*
3528 * Bounds And Transforms Computation
3529 *
3530 * This section of the code is responsible for computing and caching
3531 * various bounds and transforms. For optimal performance and minimal
3532 * recomputation of bounds (which can be quite expensive), we cache
3533 * values on two different levels. We expose two public immutable
3534 * Bounds boundsInParent objects and boundsInLocal. Because they are
3535 * immutable and because they may change quite frequently (especially
3536 * in the case of a Parent whose children are animated), it is
3537 * important that the system does not rely on these variables, because
3538 * doing so would produce a large amount of garbage. Rather, these
3539 * variables are provided solely for the convenience of application
3540 * developers and, being lazily bound, should generally be created at
3541 * most once per frame.
3542 *
3543 * The second level of caching are within local Bounds2D variables.
3544 * These variables, txBounds and geomBounds, are mutable and as such
3545 * can be cached and updated as frequently as necessary without creating
3546 * excessive garbage. However, since the computation of bounds is still
3547 * expensive, it is desirable to cache both the geometric bounds and
3548 * the "complete" transformed bounds (essentially, boundsInParent).
3549 * Cached txBounds is particularly useful when computing the geometric
3550 * bounds of a Parent since it would not require complete or partial
3551 * recomputation of each child.
3552 *
3553 * Finally, we cache the complete transform for this node which converts
3554 * its coord system from local to parent coords. This is useful both for
3555 * minimizing bounds recomputations in the case of the geometry having
3556 * changed but the transform not having changed, and also because the tx
3557 * is required for several different computations (for example, it must
3558 * be computed once during state synchronization with the PG peer, and
3559 * must also be computed when the pivot point changes, and also when
3560 * deriving the txBounds of the Node).
3561 *
3562 * As with any caching system, a subtle and non-trivial amount of code
3563 * is devoted to invalidating the bounds / transforms at appropriate
3564 * times and in appropriate places to make sure bounds / transforms
3565 * are recomputed at all necessary times.
3566 *
3567 * There are three computeXXX functions. One is for computing the
3568 * boundsInParent, the second for computing boundsInLocal, and the
3569 * third for computing the default layout bounds (which, by default,
3570 * is based on the geometric bounds). These functions are all prefixed
3571 * with "compute" because they create and return new immutable
3572 * Bounds objects.
3573 *
3574 * There are three getXXXBounds functions. One is for returning the
3575 * complete transformed bounds. The second is for returning the
3576 * local bounds. The last is for returning the geometric bounds. These
3577 * functions are all prefixed with "get" because they may well return
3578 * a cached value, or may actually compute the bounds if necessary. These
3579 * functions all have the same signature. They take a Bounds2D and
3580 * BaseTransform, and return a Bounds2D (the same as they took). These
3581 * functions essentially populate the supplied bounds2D with the
3582 * appropriate bounds information, leveraging cached bounds if possible.
3583 *
3584 * There is a single NodeHelper.computeGeomBoundsImpl function which is abstract.
3585 * This must be implemented in each subclass, and is responsible for
3586 * computing the actual geometric bounds for the Node. For example, Parent
3587 * is written such that this function is the union of the transformed
3588 * bounds of each child. Rectangle is written such that this takes into
3589 * account the size and stroke. Text is written such that it is computed
3590 * based on the actual glyphs.
3591 *
3592 * There are two updateXXX functions, updateGeomBounds and updateTxBounds.
3593 * These functions are for ensuring that geomBounds and txBounds are
3594 * valid. They only execute in the case of the cached value being invalid,
3595 * so the function call is very cheap in cases where the cached bounds
3596 * values are still valid.
3597 */
3598
3599 /**
3600 * An affine transform that holds the computed local-to-parent transform.
3601 * This is the concatenation of all transforms in this node, including all
3602 * of the convenience transforms.
3603 */
3604 private BaseTransform localToParentTx = BaseTransform.IDENTITY_TRANSFORM;
3605
3606 /**
3607 * This flag is used to indicate that localToParentTx is dirty and needs
3608 * to be recomputed.
3609 */
3610 private boolean transformDirty = true;
3611
3612 /**
3613 * The cached transformed bounds. This is never null, but is frequently set
3614 * to be invalid whenever the bounds for the node have changed. These are
3615 * "complete" bounds, that is, with transforms and effect and clip applied.
3616 * Note that this is equivalent to boundsInParent
3617 */
3618 private BaseBounds txBounds = new RectBounds();
3619
3620 /**
3621 * The cached bounds. This is never null, but is frequently set to be
3622 * invalid whenever the bounds for the node have changed. These are the
3623 * "content" bounds, that is, without transforms or effects applied.
3624 */
3625 private BaseBounds geomBounds = new RectBounds();
3626
3627 /**
3628 * The cached local bounds (without transforms, with clip and effects).
3629 * If there is neither clip nor effect
3630 * local bounds are equal to geom bounds, so in this case we don't keep
3631 * the extra instance and set null to this variable.
3632 */
3633 private BaseBounds localBounds = null;
3634
3635 /**
3636 * This special flag is used only by Parent to flag whether or not
3637 * the *parent* has processed the fact that bounds have changed for this
3638 * child Node. We need some way of flagging this on a per-node basis to
3639 * enable the significant performance optimizations and fast paths that
3640 * are in the Parent code.
3641 * <p>
3642 * To reduce confusion, although this variable is defined on Node, it
3643 * really belongs to the Parent of the node and should *only* be modified
3644 * by the parent.
3645 */
3646 boolean boundsChanged;
3647
3648 /*
3649 * Returns geometric bounds, but may be over-ridden by a subclass.
3650 */
3651 private Bounds doComputeLayoutBounds() {
3652 BaseBounds tempBounds = TempState.getInstance().bounds;
3653 tempBounds = getGeomBounds(tempBounds,
3654 BaseTransform.IDENTITY_TRANSFORM);
3655 return new BoundingBox(tempBounds.getMinX(),
3656 tempBounds.getMinY(),
3657 tempBounds.getMinZ(),
3658 tempBounds.getWidth(),
3659 tempBounds.getHeight(),
3660 tempBounds.getDepth());
3661 }
3662
3663 /*
3664 * Subclasses may customize the layoutBounds by means of overriding the
3665 * NodeHelper.computeLayoutBoundsImpl method. If the layout bounds need to be
3666 * recomputed, the subclass must notify the Node implementation of this
3667 * fact so that appropriate notifications and internal state can be
3668 * kept in sync. Subclasses must call NodeHelper.layoutBoundsChanged to
3669 * let Node know that the layout bounds are invalid and need to be
3670 * recomputed.
3671 */
3672 final void layoutBoundsChanged() {
3673 if (!layoutBounds.valid) {
3674 return;
3675 }
3676 layoutBounds.invalidate();
3677 if ((nodeTransformation != null && nodeTransformation.hasScaleOrRotate()) || hasMirroring()) {
3678 // if either the scale or rotate convenience variables are used,
3679 // then we need a valid pivot point. Since the layoutBounds
3680 // affects the pivot we need to invalidate the transform
3681 NodeHelper.transformsChanged(this);
3682 }
3683 }
3684
3685 /**
3686 * Loads the given bounds object with the transformed bounds relative to,
3687 * and based on, the given transform. That is, this is the local bounds
3688 * with the local-to-parent transform applied.
3689 *
3690 * We *never* pass null in as a bounds. This method will
3691 * NOT take a null bounds object. The returned value may be
3692 * the same bounds object passed in, or it may be a new object.
3693 * The reason for this object promotion is in the case of needing
3694 * to promote from a RectBounds to a BoxBounds (3D).
3695 */
3696 BaseBounds getTransformedBounds(BaseBounds bounds, BaseTransform tx) {
3697 updateLocalToParentTransform();
3698 if (tx.isTranslateOrIdentity()) {
3699 updateTxBounds();
3700 bounds = bounds.deriveWithNewBounds(txBounds);
3701 if (!tx.isIdentity()) {
3702 final double translateX = tx.getMxt();
3703 final double translateY = tx.getMyt();
3704 final double translateZ = tx.getMzt();
3705 bounds = bounds.deriveWithNewBounds(
3706 (float) (bounds.getMinX() + translateX),
3707 (float) (bounds.getMinY() + translateY),
3708 (float) (bounds.getMinZ() + translateZ),
3709 (float) (bounds.getMaxX() + translateX),
3710 (float) (bounds.getMaxY() + translateY),
3711 (float) (bounds.getMaxZ() + translateZ));
3712 }
3713 return bounds;
3714 } else if (localToParentTx.isIdentity()) {
3715 return getLocalBounds(bounds, tx);
3716 } else {
3717 double mxx = tx.getMxx();
3718 double mxy = tx.getMxy();
3719 double mxz = tx.getMxz();
3720 double mxt = tx.getMxt();
3721 double myx = tx.getMyx();
3722 double myy = tx.getMyy();
3723 double myz = tx.getMyz();
3724 double myt = tx.getMyt();
3725 double mzx = tx.getMzx();
3726 double mzy = tx.getMzy();
3727 double mzz = tx.getMzz();
3728 double mzt = tx.getMzt();
3729 BaseTransform boundsTx = tx.deriveWithConcatenation(localToParentTx);
3730 bounds = getLocalBounds(bounds, boundsTx);
3731 if (boundsTx == tx) {
3732 tx.restoreTransform(mxx, mxy, mxz, mxt,
3733 myx, myy, myz, myt,
3734 mzx, mzy, mzz, mzt);
3735 }
3736 return bounds;
3737 }
3738 }
3739
3740 /**
3741 * Loads the given bounds object with the local bounds relative to,
3742 * and based on, the given transform. That is, these are the geometric
3743 * bounds + clip and effect.
3744 *
3745 * We *never* pass null in as a bounds. This method will
3746 * NOT take a null bounds object. The returned value may be
3747 * the same bounds object passed in, or it may be a new object.
3748 * The reason for this object promotion is in the case of needing
3749 * to promote from a RectBounds to a BoxBounds (3D).
3750 */
3751 BaseBounds getLocalBounds(BaseBounds bounds, BaseTransform tx) {
3752 if (getEffect() == null && getClip() == null) {
3753 return getGeomBounds(bounds, tx);
3754 }
3755
3756 if (tx.isTranslateOrIdentity()) {
3757 // we can take a fast path since we know tx is either a simple
3758 // translation or is identity
3759 updateLocalBounds();
3760 bounds = bounds.deriveWithNewBounds(localBounds);
3761 if (!tx.isIdentity()) {
3762 double translateX = tx.getMxt();
3763 double translateY = tx.getMyt();
3764 double translateZ = tx.getMzt();
3765 bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
3766 (float) (bounds.getMinY() + translateY),
3767 (float) (bounds.getMinZ() + translateZ),
3768 (float) (bounds.getMaxX() + translateX),
3769 (float) (bounds.getMaxY() + translateY),
3770 (float) (bounds.getMaxZ() + translateZ));
3771 }
3772 return bounds;
3773 } else if (tx.is2D()
3774 && (tx.getType()
3775 & ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION
3776 | BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) {
3777 // this is a non-uniform scale / non-quadrant rotate / skew transform
3778 return computeLocalBounds(bounds, tx);
3779 } else {
3780 // 3D transformations and
3781 // selected 2D transformations (uniform transform, flip, quadrant rotation).
3782 // These 2D transformation will yield tight bounds when applied on the pre-computed
3783 // geomBounds
3784 // Note: Transforming the local bounds into a 3D space will yield a bounds
3785 // that isn't as tight as transforming its geometry and compute it bounds.
3786 updateLocalBounds();
3787 return tx.transform(localBounds, bounds);
3788 }
3789 }
3790
3791 /**
3792 * Loads the given bounds object with the geometric bounds relative to,
3793 * and based on, the given transform.
3794 *
3795 * We *never* pass null in as a bounds. This method will
3796 * NOT take a null bounds object. The returned value may be
3797 * the same bounds object passed in, or it may be a new object.
3798 * The reason for this object promotion is in the case of needing
3799 * to promote from a RectBounds to a BoxBounds (3D).
3800 */
3801 BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx) {
3802 if (tx.isTranslateOrIdentity()) {
3803 // we can take a fast path since we know tx is either a simple
3804 // translation or is identity
3805 updateGeomBounds();
3806 bounds = bounds.deriveWithNewBounds(geomBounds);
3807 if (!tx.isIdentity()) {
3808 double translateX = tx.getMxt();
3809 double translateY = tx.getMyt();
3810 double translateZ = tx.getMzt();
3811 bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
3812 (float) (bounds.getMinY() + translateY),
3813 (float) (bounds.getMinZ() + translateZ),
3814 (float) (bounds.getMaxX() + translateX),
3815 (float) (bounds.getMaxY() + translateY),
3816 (float) (bounds.getMaxZ() + translateZ));
3817 }
3818 return bounds;
3819 } else if (tx.is2D()
3820 && (tx.getType()
3821 & ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION
3822 | BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) {
3823 // this is a non-uniform scale / non-quadrant rotate / skew transform
3824 return NodeHelper.computeGeomBounds(this, bounds, tx);
3825 } else {
3826 // 3D transformations and
3827 // selected 2D transformations (unifrom transform, flip, quadrant rotation).
3828 // These 2D transformation will yield tight bounds when applied on the pre-computed
3829 // geomBounds
3830 // Note: Transforming the local geomBounds into a 3D space will yield a bounds
3831 // that isn't as tight as transforming its geometry and compute it bounds.
3832 updateGeomBounds();
3833 return tx.transform(geomBounds, bounds);
3834 }
3835 }
3836
3837 /**
3838 * If necessary, recomputes the cached geom bounds. If the bounds are not
3839 * invalid, then this method is a no-op.
3840 */
3841 void updateGeomBounds() {
3842 if (geomBoundsInvalid) {
3843 geomBounds = NodeHelper.computeGeomBounds(this, geomBounds, BaseTransform.IDENTITY_TRANSFORM);
3844 geomBoundsInvalid = false;
3845 }
3846 }
3847
3848 /**
3849 * Computes the local bounds of this Node.
3850 */
3851 private BaseBounds computeLocalBounds(BaseBounds bounds, BaseTransform tx) {
3852 // We either get the bounds of the effect (if it isn't null)
3853 // or we get the geom bounds (if effect is null). We will then
3854 // intersect this with the clip.
3855 if (getEffect() != null) {
3856 BaseBounds b = EffectHelper.getBounds(getEffect(), bounds, tx, this, boundsAccessor);
3857 bounds = bounds.deriveWithNewBounds(b);
3858 } else {
3859 bounds = getGeomBounds(bounds, tx);
3860 }
3861 // intersect with the clip. Take care with "bounds" as it may
3862 // actually be TEMP_BOUNDS, so we save off state
3863 if (getClip() != null
3864 // FIXME: All 3D picking is currently ignored by rendering.
3865 // Until this is fixed or defined differently (RT-28510),
3866 // we follow this behavior.
3867 && !(this instanceof Shape3D) && !(getClip() instanceof Shape3D)) {
3868 double x1 = bounds.getMinX();
3869 double y1 = bounds.getMinY();
3870 double x2 = bounds.getMaxX();
3871 double y2 = bounds.getMaxY();
3872 double z1 = bounds.getMinZ();
3873 double z2 = bounds.getMaxZ();
3874 bounds = getClip().getTransformedBounds(bounds, tx);
3875 bounds.intersectWith((float)x1, (float)y1, (float)z1,
3876 (float)x2, (float)y2, (float)z2);
3877 }
3878 return bounds;
3879 }
3880
3881
3882 /**
3883 * If necessary, recomputes the cached local bounds. If the bounds are not
3884 * invalid, then this method is a no-op.
3885 */
3886 private void updateLocalBounds() {
3887 if (localBoundsInvalid) {
3888 if (getClip() != null || getEffect() != null) {
3889 localBounds = computeLocalBounds(
3890 localBounds == null ? new RectBounds() : localBounds,
3891 BaseTransform.IDENTITY_TRANSFORM);
3892 } else {
3893 localBounds = null;
3894 }
3895 localBoundsInvalid = false;
3896 }
3897 }
3898
3899 /**
3900 * If necessary, recomputes the cached transformed bounds.
3901 * If the cached transformed bounds are not invalid, then
3902 * this method is a no-op.
3903 */
3904 void updateTxBounds() {
3905 if (txBoundsInvalid) {
3906 updateLocalToParentTransform();
3907 txBounds = getLocalBounds(txBounds, localToParentTx);
3908 txBoundsInvalid = false;
3909 }
3910 }
3911
3912 /*
3913 * Bounds Invalidation And Notification
3914 *
3915 * The goal of this section is to efficiently propagate bounds
3916 * invalidation through the scenegraph while also being semantically
3917 * correct.
3918 *
3919 * The code path for invalidation of layout bounds is somewhat confusing
3920 * primarily due to performance enhancements and the desire to reduce the
3921 * number of requestLayout() calls that are performed when layout bounds
3922 * change. Before diving into layout bounds, I will first describe how
3923 * normal bounds invalidation occurs.
3924 *
3925 * When a node's geometry changes (for example, if the width of a
3926 * Rectangle is changed) then the Node must call NodeHelper.geomChanged().
3927 * Invoking this function will eventually clear all cached bounds and
3928 * notify to each parent up the tree that their bounds may have changed.
3929 *
3930 * After invalidating geomBounds (and after kicking off layout bounds
3931 * notification), NodeHelper.geomChanged calls localBoundsChanged(). It should
3932 * be noted that NodeHelper.geomChanged should only be called when the geometry
3933 * of the node has changed such that it may result in the geom bounds
3934 * actually changing.
3935 *
3936 * localBoundsChanged() simply invalidates boundsInLocal and then calls
3937 * transformedBoundsChanged().
3938 *
3939 * transformedBoundsChanged() is responsible for invalidating
3940 * boundsInParent and txBounds. If the Node is not visible, then there is
3941 * no need to notify the parent of the bounds change because the parent's
3942 * bounds do not include invisible nodes. If the node is visible, then
3943 * it must tell the parent that this child node's bounds have changed.
3944 * It is up to the parent to eventually invoke its own NodeHelper.geomChanged
3945 * function. If instead of a parent this node has a clipParent, then the
3946 * clipParent's localBoundsChanged() is called instead.
3947 *
3948 * There are a few other ways in which we enter the invalidate steps
3949 * beyond just the geometry changes. If the visibility of a Node changes,
3950 * its own bounds are not affected but its parent's bounds are. So a
3951 * special call to parent.childVisibilityChanged is made so the parent
3952 * can react accordingly.
3953 *
3954 * If a transform is changed (layoutX, layoutY, rotate, transforms, etc)
3955 * then the transform must be invalidated. When a transform is invalidated,
3956 * it must also invalidate the txBounds by invoking
3957 * transformedBoundsChanged, which will in turn notify the parent as
3958 * before.
3959 *
3960 * If an effect is changed or replaced then the local bounds must be
3961 * invalidated, as well as the transformedBounds and the parent notified
3962 * of the change in bounds.
3963 *
3964 * layoutBound is somewhat unique in that it can be redefined in
3965 * subclasses. By default, the layoutBounds is the geomBounds, and so
3966 * whenever the geomBounds() function is called the layoutBounds
3967 * must be invalidated. However in subclasses, especially Resizables,
3968 * the layout bounds may not be defined to be the same as the geometric
3969 * bounds. This is both useful and provides a very nice performance
3970 * optimization for regions and controls. In this case, subclasses
3971 * need some way to interpose themselves such that a call to
3972 * NodeHelper.geomChanged() *does not* invalidate the layout bounds.
3973 *
3974 * This interposition happens by providing the
3975 * NodeHelper.notifyLayoutBoundsChanged function. The default implementation
3976 * simply invalidates boundsInLocal. Subclasses (such as Region and
3977 * Control) can override this function so that it does not invalidate
3978 * the layout bounds.
3979 *
3980 * An on invalidate trigger on layoutBounds handles kicking off the rest
3981 * of the invalidate process for layoutBounds. Because the layout bounds
3982 * define the pivot point, if scaleX, scaleY, or rotate contain
3983 * non-identity values then whenever the layoutBounds change the
3984 * transformed bounds also change. Finally, if this node's parent is
3985 * a Region and if the Node is being managed by the Region, then
3986 * we must call requestLayout on the Region whenever the layout bounds
3987 * have changed.
3988 */
3989
3990 /*
3991 * Invoked by subclasses whenever their geometric bounds have changed.
3992 * Because the default layout bounds is based on the node geometry, this
3993 * function will invoke NodeHelper.notifyLayoutBoundsChanged. The default
3994 * implementation of NodeHelper.notifyLayoutBoundsChanged() will simply invalidate
3995 * layoutBounds. Resizable subclasses will want to override this function
3996 * in most cases to be a no-op.
3997 *
3998 * This function will also invalidate the cached geom bounds, and then
3999 * invoke localBoundsChanged() which will eventually end up invoking a
4000 * chain of functions up the tree to ensure that each parent of this
4001 * Node is notified that its bounds may have also changed.
4002 *
4003 * This function should be treated as though it were final. It is not
4004 * intended to be overridden by subclasses.
4005 *
4006 * Note: This method MUST only be called via its accessor method.
4007 */
4008 private void doGeomChanged() {
4009 if (geomBoundsInvalid) {
4010 // GeomBoundsInvalid is false when node geometry changed and
4011 // the untransformed node bounds haven't been recalculated yet.
4012 // Most of the time, the recalculation of layout and transformed
4013 // node bounds don't require validation of untransformed bounds
4014 // and so we can not skip the following notifications.
4015 NodeHelper.notifyLayoutBoundsChanged(this);
4016 transformedBoundsChanged();
4017 return;
4018 }
4019 geomBounds.makeEmpty();
4020 geomBoundsInvalid = true;
4021 NodeHelper.markDirty(this, DirtyBits.NODE_BOUNDS);
4022 NodeHelper.notifyLayoutBoundsChanged(this);
4023 localBoundsChanged();
4024 }
4025
4026 private boolean geomBoundsInvalid = true;
4027 private boolean localBoundsInvalid = true;
4028 private boolean txBoundsInvalid = true;
4029
4030 /**
4031 * Responds to changes in the local bounds by invalidating boundsInLocal
4032 * and notifying this node that its transformed bounds have changed.
4033 */
4034 void localBoundsChanged() {
4035 localBoundsInvalid = true;
4036 invalidateBoundsInLocal();
4037 transformedBoundsChanged();
4038 }
4039
4040 /**
4041 * Responds to changes in the transformed bounds by invalidating txBounds
4042 * and boundsInParent. If this Node is not visible, then we have no need
4043 * to walk further up the tree but can instead simply invalidate state.
4044 * Otherwise, this function will notify parents (either the parent or the
4045 * clipParent) that this child Node's bounds have changed.
4046 */
4047 void transformedBoundsChanged() {
4048 if (!txBoundsInvalid) {
4049 txBounds.makeEmpty();
4050 txBoundsInvalid = true;
4051 invalidateBoundsInParent();
4052 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORMED_BOUNDS);
4053 }
4054 if (isVisible()) {
4055 notifyParentOfBoundsChange();
4056 }
4057 }
4058
4059 /*
4060 * Invoked by geomChanged(). Since layoutBounds is by default based
4061 * on the geometric bounds, the default implementation of this function will
4062 * invalidate the layoutBounds. Resizable Node subclasses generally base
4063 * layoutBounds on the width/height instead of the geometric bounds, and so
4064 * will generally want to override this function to be a no-op.
4065 *
4066 * Note: This method MUST only be called via its accessor method.
4067 */
4068 private void doNotifyLayoutBoundsChanged() {
4069 layoutBoundsChanged();
4070 // notify the parent
4071 // Group instanceof check a little hoaky, but it allows us to disable
4072 // unnecessary layout for the case of a non-resizable within a group
4073 Parent p = getParent();
4074
4075 // Need to propagate layout if parent isn't part of performing layout
4076 if (isManaged() && (p != null) && !(p instanceof Group && !isResizable())
4077 && !p.isPerformingLayout()) {
4078 // Force its parent to fix the layout since it is a managed child.
4079 p.requestLayout(true);
4080 }
4081 }
4082
4083 /**
4084 * Notifies both the real parent and the clip parent (if they exist) that
4085 * the bounds of the child has changed. Note that since FX doesn't throw
4086 * NPE's, things actually are faster if we don't check twice for Null
4087 * (we check once, the compiler checks again)
4088 */
4089 void notifyParentOfBoundsChange() {
4090 // let the parent know which node has changed and the parent will
4091 // deal with marking itself invalid correctly
4092 Parent p = getParent();
4093 if (p != null) {
4094 p.childBoundsChanged(this);
4095 }
4096 // since the clip is used to compute the local bounds (and not the
4097 // geom bounds), we just need to notify that local bounds on the
4098 // clip parent have changed
4099 if (clipParent != null) {
4100 clipParent.localBoundsChanged();
4101 }
4102 }
4103
4104 /***************************************************************************
4105 * *
4106 * Geometry and coordinate system related APIs. For example, methods *
4107 * related to containment, intersection, coordinate space conversion, etc. *
4108 * *
4109 **************************************************************************/
4110
4111 /**
4112 * Returns {@code true} if the given point (specified in the local
4113 * coordinate space of this {@code Node}) is contained within the shape of
4114 * this {@code Node}. Note that this method does not take visibility into
4115 * account; the test is based on the geometry of this {@code Node} only.
4116 * @param localX the x coordinate of the point in Node's space
4117 * @param localY the y coordinate of the point in Node's space
4118 * @return the result of contains for this {@code Node}
4119 */
4120 public boolean contains(double localX, double localY) {
4121 if (containsBounds(localX, localY)) {
4122 return (isPickOnBounds() || NodeHelper.computeContains(this, localX, localY));
4123 }
4124 return false;
4125 }
4126
4127 /*
4128 * This method only does the contains check based on the bounds, clip and
4129 * effect of this node, excluding its shape (or geometry).
4130 *
4131 * Returns true if the given point (specified in the local
4132 * coordinate space of this {@code Node}) is contained within the bounds,
4133 * clip and effect of this node.
4134 */
4135 private boolean containsBounds(double localX, double localY) {
4136 final TempState tempState = TempState.getInstance();
4137 BaseBounds tempBounds = tempState.bounds;
4138
4139 // first, we do a quick test to see if the point is contained in
4140 // our local bounds. If so, then we will go the next step and check
4141 // the clip, effect, and geometry for containment.
4142 tempBounds = getLocalBounds(tempBounds,
4143 BaseTransform.IDENTITY_TRANSFORM);
4144 if (tempBounds.contains((float)localX, (float)localY)) {
4145 // if the clip is defined, then check it for containment, being
4146 // sure to convert from this node's local coordinate system
4147 // to the local coordinate system of the clip node
4148 if (getClip() != null) {
4149 tempState.point.x = (float)localX;
4150 tempState.point.y = (float)localY;
4151 try {
4152 getClip().parentToLocal(tempState.point);
4153 } catch (NoninvertibleTransformException e) {
4154 return false;
4155 }
4156 if (!getClip().contains(tempState.point.x, tempState.point.y)) {
4157 return false;
4158 }
4159 }
4160 return true;
4161 }
4162 return false;
4163 }
4164
4165 /**
4166 * Returns {@code true} if the given point (specified in the local
4167 * coordinate space of this {@code Node}) is contained within the shape of
4168 * this {@code Node}. Note that this method does not take visibility into
4169 * account; the test is based on the geometry of this {@code Node} only.
4170 * @param localPoint the 2D point in Node's space
4171 * @return the result of contains for this {@code Node}
4172 */
4173 public boolean contains(Point2D localPoint) {
4174 return contains(localPoint.getX(), localPoint.getY());
4175 }
4176
4177 /**
4178 * Returns {@code true} if the given rectangle (specified in the local
4179 * coordinate space of this {@code Node}) intersects the shape of this
4180 * {@code Node}. Note that this method does not take visibility into
4181 * account; the test is based on the geometry of this {@code Node} only.
4182 * The default behavior of this function is simply to check if the
4183 * given coordinates intersect with the local bounds.
4184 * @param localX the x coordinate of a rectangle in Node's space
4185 * @param localY the y coordinate of a rectangle in Node's space
4186 * @param localWidth the width of a rectangle in Node's space
4187 * @param localHeight the height of a rectangle in Node's space
4188 * @return the result of intersects for this {@code Node}
4189 */
4190 public boolean intersects(double localX, double localY, double localWidth, double localHeight) {
4191 BaseBounds tempBounds = TempState.getInstance().bounds;
4192 tempBounds = getLocalBounds(tempBounds,
4193 BaseTransform.IDENTITY_TRANSFORM);
4194 return tempBounds.intersects((float)localX,
4195 (float)localY,
4196 (float)localWidth,
4197 (float)localHeight);
4198 }
4199
4200 /**
4201 * Returns {@code true} if the given bounds (specified in the local
4202 * coordinate space of this {@code Node}) intersects the shape of this
4203 * {@code Node}. Note that this method does not take visibility into
4204 * account; the test is based on the geometry of this {@code Node} only.
4205 * The default behavior of this function is simply to check if the
4206 * given coordinates intersect with the local bounds.
4207 * @param localBounds the bounds
4208 * @return the result of intersects for this {@code Node}
4209 */
4210 public boolean intersects(Bounds localBounds) {
4211 return intersects(localBounds.getMinX(), localBounds.getMinY(), localBounds.getWidth(), localBounds.getHeight());
4212 }
4213
4214 /**
4215 * Transforms a point from the coordinate space of the {@link javafx.stage.Screen}
4216 * into the local coordinate space of this {@code Node}.
4217 * @param screenX x coordinate of a point on a Screen
4218 * @param screenY y coordinate of a point on a Screen
4219 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4220 * Null is also returned if the transformation from local to Scene is not invertible.
4221 * @since JavaFX 8.0
4222 */
4223 public Point2D screenToLocal(double screenX, double screenY) {
4224 Scene scene = getScene();
4225 if (scene == null) return null;
4226 Window window = scene.getWindow();
4227 if (window == null) return null;
4228
4229 final com.sun.javafx.geom.Point2D tempPt =
4230 TempState.getInstance().point;
4231
4232 tempPt.setLocation((float)(screenX - scene.getX() - window.getX()),
4233 (float)(screenY - scene.getY() - window.getY()));
4234
4235 final SubScene subScene = getSubScene();
4236 if (subScene != null) {
4237 final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene,
4238 new Point2D(tempPt.x, tempPt.y));
4239 if (ssCoord == null) {
4240 return null;
4241 }
4242 tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY());
4243 }
4244
4245 final Point3D ppIntersect =
4246 scene.getEffectiveCamera().pickProjectPlane(tempPt.x, tempPt.y);
4247 tempPt.setLocation((float) ppIntersect.getX(), (float) ppIntersect.getY());
4248
4249 try {
4250 sceneToLocal(tempPt);
4251 } catch (NoninvertibleTransformException e) {
4252 return null;
4253 }
4254 return new Point2D(tempPt.x, tempPt.y);
4255 }
4256
4257 /**
4258 * Transforms a point from the coordinate space of the {@link javafx.stage.Screen}
4259 * into the local coordinate space of this {@code Node}.
4260 * @param screenPoint a point on a Screen
4261 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4262 * Null is also returned if the transformation from local to Scene is not invertible.
4263 * @since JavaFX 8.0
4264 */
4265 public Point2D screenToLocal(Point2D screenPoint) {
4266 return screenToLocal(screenPoint.getX(), screenPoint.getY());
4267 }
4268
4269 /**
4270 * Transforms a rectangle from the coordinate space of the
4271 * {@link javafx.stage.Screen} into the local coordinate space of this
4272 * {@code Node}. Returns reasonable result only in 2D space.
4273 * @param screenBounds bounds on a Screen
4274 * @return bounds in the local Node'space or null if Node is not in a {@link Window}.
4275 * Null is also returned if the transformation from local to Scene is not invertible.
4276 * @since JavaFX 8.0
4277 */
4278 public Bounds screenToLocal(Bounds screenBounds) {
4279 final Point2D p1 = screenToLocal(screenBounds.getMinX(), screenBounds.getMinY());
4280 final Point2D p2 = screenToLocal(screenBounds.getMinX(), screenBounds.getMaxY());
4281 final Point2D p3 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMinY());
4282 final Point2D p4 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMaxY());
4283
4284 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4285 }
4286
4287
4288 /**
4289 * Transforms a point from the coordinate space of the scene
4290 * into the local coordinate space of this {@code Node}.
4291 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4292 * arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4293 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4294 * {@link #sceneToLocal(double, double)}.
4295 *
4296 * @param x the x coordinate
4297 * @param y the y coordinate
4298 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4299 * @return local coordinates of the point
4300 * @since JavaFX 8u40
4301 */
4302 public Point2D sceneToLocal(double x, double y, boolean rootScene) {
4303 if (!rootScene) {
4304 return sceneToLocal(x, y);
4305 }
4306 final com.sun.javafx.geom.Point2D tempPt =
4307 TempState.getInstance().point;
4308
4309 tempPt.setLocation((float)(x), (float)y);
4310
4311 final SubScene subScene = getSubScene();
4312 if (subScene != null) {
4313 final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene,
4314 new Point2D(tempPt.x, tempPt.y));
4315 if (ssCoord == null) {
4316 return null;
4317 }
4318 tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY());
4319 }
4320
4321 try {
4322 sceneToLocal(tempPt);
4323 return new Point2D(tempPt.x, tempPt.y);
4324 } catch (NoninvertibleTransformException e) {
4325 return null;
4326 }
4327 }
4328
4329 /**
4330 * Transforms a point from the coordinate space of the scene
4331 * into the local coordinate space of this {@code Node}.
4332 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4333 * arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4334 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4335 * {@link #sceneToLocal(javafx.geometry.Point2D)}.
4336 *
4337 * @param point the point
4338 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4339 * @return local coordinates of the point
4340 * @since JavaFX 8u40
4341 */
4342 public Point2D sceneToLocal(Point2D point, boolean rootScene) {
4343 return sceneToLocal(point.getX(), point.getY(), rootScene);
4344 }
4345
4346 /**
4347 * Transforms a bounds from the coordinate space of the scene
4348 * into the local coordinate space of this {@code Node}.
4349 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4350 * arguments are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4351 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4352 * {@link #sceneToLocal(javafx.geometry.Bounds)}.
4353 * <p>
4354 * Since 3D bounds cannot be converted with {@code rootScene} set to {@code true}, trying to convert 3D bounds will yield {@code null}.
4355 * </p>
4356 * @param bounds the bounds
4357 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4358 * @return local coordinates of the bounds
4359 * @since JavaFX 8u40
4360 */
4361 public Bounds sceneToLocal(Bounds bounds, boolean rootScene) {
4362 if (!rootScene) {
4363 return sceneToLocal(bounds);
4364 }
4365 if (bounds.getMinZ() != 0 || bounds.getMaxZ() != 0) {
4366 return null;
4367 }
4368 final Point2D p1 = sceneToLocal(bounds.getMinX(), bounds.getMinY(), true);
4369 final Point2D p2 = sceneToLocal(bounds.getMinX(), bounds.getMaxY(), true);
4370 final Point2D p3 = sceneToLocal(bounds.getMaxX(), bounds.getMinY(), true);
4371 final Point2D p4 = sceneToLocal(bounds.getMaxX(), bounds.getMaxY(), true);
4372
4373 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4374 }
4375
4376 /**
4377 * Transforms a point from the coordinate space of the scene
4378 * into the local coordinate space of this {@code Node}.
4379 *
4380 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
4381 * not that of {@link javafx.scene.Scene}.
4382 *
4383 * @param sceneX x coordinate of a point on a Scene
4384 * @param sceneY y coordinate of a point on a Scene
4385 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4386 * Null is also returned if the transformation from local to Scene is not invertible.
4387 */
4388 public Point2D sceneToLocal(double sceneX, double sceneY) {
4389 final com.sun.javafx.geom.Point2D tempPt =
4390 TempState.getInstance().point;
4391 tempPt.setLocation((float)sceneX, (float)sceneY);
4392 try {
4393 sceneToLocal(tempPt);
4394 } catch (NoninvertibleTransformException e) {
4395 return null;
4396 }
4397 return new Point2D(tempPt.x, tempPt.y);
4398 }
4399
4400 /**
4401 * Transforms a point from the coordinate space of the scene
4402 * into the local coordinate space of this {@code Node}.
4403 *
4404 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
4405 * not that of {@link javafx.scene.Scene}.
4406 *
4407 * @param scenePoint a point on a Scene
4408 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4409 * Null is also returned if the transformation from local to Scene is not invertible.
4410 */
4411 public Point2D sceneToLocal(Point2D scenePoint) {
4412 return sceneToLocal(scenePoint.getX(), scenePoint.getY());
4413 }
4414
4415 /**
4416 * Transforms a point from the coordinate space of the scene
4417 * into the local coordinate space of this {@code Node}.
4418 *
4419 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
4420 * not that of {@link javafx.scene.Scene}.
4421 *
4422 * @param scenePoint a point on a Scene
4423 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4424 * Null is also returned if the transformation from local to Scene is not invertible.
4425 * @since JavaFX 8.0
4426 */
4427 public Point3D sceneToLocal(Point3D scenePoint) {
4428 return sceneToLocal(scenePoint.getX(), scenePoint.getY(), scenePoint.getZ());
4429 }
4430
4431 /**
4432 * Transforms a point from the coordinate space of the scene
4433 * into the local coordinate space of this {@code Node}.
4434 *
4435 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
4436 * not that of {@link javafx.scene.Scene}.
4437 *
4438 * @param sceneX x coordinate of a point on a Scene
4439 * @param sceneY y coordinate of a point on a Scene
4440 * @param sceneZ z coordinate of a point on a Scene
4441 * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
4442 * Null is also returned if the transformation from local to Scene is not invertible.
4443 * @since JavaFX 8.0
4444 */
4445 public Point3D sceneToLocal(double sceneX, double sceneY, double sceneZ) {
4446 try {
4447 return sceneToLocal0(sceneX, sceneY, sceneZ);
4448 } catch (NoninvertibleTransformException ex) {
4449 return null;
4450 }
4451 }
4452
4453 /**
4454 * Internal method to transform a point from scene to local coordinates.
4455 */
4456 private Point3D sceneToLocal0(double x, double y, double z) throws NoninvertibleTransformException {
4457 final com.sun.javafx.geom.Vec3d tempV3D =
4458 TempState.getInstance().vec3d;
4459 tempV3D.set(x, y, z);
4460 sceneToLocal(tempV3D);
4461 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4462 }
4463
4464 /**
4465 * Transforms a rectangle from the coordinate space of the
4466 * scene into the local coordinate space of this
4467 * {@code Node}.
4468 *
4469 * Note that if this node is in a {@link SubScene}, the arguments should be in the subscene coordinates,
4470 * not that of {@link javafx.scene.Scene}.
4471 *
4472 * @param sceneBounds bounds on a Scene
4473 * @return bounds in the local Node'space or null if Node is not in a {@link Window}.
4474 * Null is also returned if the transformation from local to Scene is not invertible.
4475 */
4476 public Bounds sceneToLocal(Bounds sceneBounds) {
4477 // Do a quick update of localToParentTransform so that we can determine
4478 // if this tx is 2D transform
4479 updateLocalToParentTransform();
4480 if (localToParentTx.is2D() && (sceneBounds.getMinZ() == 0) && (sceneBounds.getMaxZ() == 0)) {
4481 Point2D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY());
4482 Point2D p2 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY());
4483 Point2D p3 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY());
4484 Point2D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY());
4485
4486 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4487 }
4488 try {
4489 Point3D p1 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
4490 Point3D p2 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
4491 Point3D p3 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
4492 Point3D p4 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
4493 Point3D p5 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
4494 Point3D p6 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
4495 Point3D p7 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
4496 Point3D p8 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
4497 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4498 } catch (NoninvertibleTransformException e) {
4499 return null;
4500 }
4501 }
4502
4503 /**
4504 * Transforms a point from the local coordinate space of this {@code Node}
4505 * into the coordinate space of its {@link javafx.stage.Screen}.
4506 * @param localX x coordinate of a point in Node's space
4507 * @param localY y coordinate of a point in Node's space
4508 * @return screen coordinates of the point or null if Node is not in a {@link Window}
4509 * @since JavaFX 8.0
4510 */
4511 public Point2D localToScreen(double localX, double localY) {
4512 return localToScreen(localX, localY, 0.0);
4513 }
4514
4515 /**
4516 * Transforms a point from the local coordinate space of this {@code Node}
4517 * into the coordinate space of its {@link javafx.stage.Screen}.
4518 * @param localPoint a point in Node's space
4519 * @return screen coordinates of the point or null if Node is not in a {@link Window}
4520 * @since JavaFX 8.0
4521 */
4522 public Point2D localToScreen(Point2D localPoint) {
4523 return localToScreen(localPoint.getX(), localPoint.getY());
4524 }
4525
4526 /**
4527 * Transforms a point from the local coordinate space of this {@code Node}
4528 * into the coordinate space of its {@link javafx.stage.Screen}.
4529 * @param localX x coordinate of a point in Node's space
4530 * @param localY y coordinate of a point in Node's space
4531 * @param localZ z coordinate of a point in Node's space
4532 * @return screen coordinates of the point or null if Node is not in a {@link Window}
4533 * @since JavaFX 8.0
4534 */
4535 public Point2D localToScreen(double localX, double localY, double localZ) {
4536 Scene scene = getScene();
4537 if (scene == null) return null;
4538 Window window = scene.getWindow();
4539 if (window == null) return null;
4540
4541 Point3D pt = localToScene(localX, localY, localZ);
4542 final SubScene subScene = getSubScene();
4543 if (subScene != null) {
4544 pt = SceneUtils.subSceneToScene(subScene, pt);
4545 }
4546 final Point2D projection = CameraHelper.project(
4547 SceneHelper.getEffectiveCamera(getScene()), pt);
4548
4549 return new Point2D(projection.getX() + scene.getX() + window.getX(),
4550 projection.getY() + scene.getY() + window.getY());
4551 }
4552
4553 /**
4554 * Transforms a point from the local coordinate space of this {@code Node}
4555 * into the coordinate space of its {@link javafx.stage.Screen}.
4556 * @param localPoint a point in Node's space
4557 * @return screen coordinates of the point or null if Node is not in a {@link Window}
4558 * @since JavaFX 8.0
4559 */
4560 public Point2D localToScreen(Point3D localPoint) {
4561 return localToScreen(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4562 }
4563
4564 /**
4565 * Transforms a bounds from the local coordinate space of this
4566 * {@code Node} into the coordinate space of its {@link javafx.stage.Screen}.
4567 * @param localBounds bounds in Node's space
4568 * @return the bounds in screen coordinates or null if Node is not in a {@link Window}
4569 * @since JavaFX 8.0
4570 */
4571 public Bounds localToScreen(Bounds localBounds) {
4572 final Point2D p1 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4573 final Point2D p2 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4574 final Point2D p3 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4575 final Point2D p4 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4576 final Point2D p5 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4577 final Point2D p6 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4578 final Point2D p7 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4579 final Point2D p8 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4580
4581 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4582 }
4583
4584 /**
4585 * Transforms a point from the local coordinate space of this {@code Node}
4586 * into the coordinate space of its scene.
4587 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
4588 * not that of {@link javafx.scene.Scene}.
4589 * @param localX x coordinate of a point in Node's space
4590 * @param localY y coordinate of a point in Node's space
4591 * @return scene coordinates of the point or null if Node is not in a {@link Window}
4592 */
4593 public Point2D localToScene(double localX, double localY) {
4594 final com.sun.javafx.geom.Point2D tempPt =
4595 TempState.getInstance().point;
4596 tempPt.setLocation((float)localX, (float)localY);
4597 localToScene(tempPt);
4598 return new Point2D(tempPt.x, tempPt.y);
4599 }
4600
4601 /**
4602 * Transforms a point from the local coordinate space of this {@code Node}
4603 * into the coordinate space of its scene.
4604 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
4605 * not that of {@link javafx.scene.Scene}.
4606 * @param localPoint a point in Node's space
4607 * @return scene coordinates of the point or null if Node is not in a {@link Window}
4608 */
4609 public Point2D localToScene(Point2D localPoint) {
4610 return localToScene(localPoint.getX(), localPoint.getY());
4611 }
4612
4613 /**
4614 * Transforms a point from the local coordinate space of this {@code Node}
4615 * into the coordinate space of its scene.
4616 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
4617 * not that of {@link javafx.scene.Scene}.
4618 * @param localPoint a 3D point in Node's space
4619 * @return the transformed 3D point in Scene's space
4620 * @see #localToScene(javafx.geometry.Point3D, boolean)
4621 * @since JavaFX 8.0
4622 */
4623 public Point3D localToScene(Point3D localPoint) {
4624 return localToScene(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4625 }
4626
4627 /**
4628 * Transforms a point from the local coordinate space of this {@code Node}
4629 * into the coordinate space of its scene.
4630 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
4631 * not that of {@link javafx.scene.Scene}.
4632 * @param x the x coordinate of a point in Node's space
4633 * @param y the y coordinate of a point in Node's space
4634 * @param z the z coordinate of a point in Node's space
4635 * @return the transformed 3D point in Scene's space
4636 * @see #localToScene(double, double, double, boolean)
4637 * @since JavaFX 8.0
4638 */
4639 public Point3D localToScene(double x, double y, double z) {
4640 final com.sun.javafx.geom.Vec3d tempV3D =
4641 TempState.getInstance().vec3d;
4642 tempV3D.set(x, y, z);
4643 localToScene(tempV3D);
4644 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4645 }
4646
4647 /**
4648 * Transforms a point from the local coordinate space of this {@code Node}
4649 * into the coordinate space of its scene.
4650 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4651 * result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4652 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4653 * {@link #localToScene(javafx.geometry.Point3D)}.
4654 *
4655 * @param localPoint the point in local coordinates
4656 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4657 * @return transformed point
4658 *
4659 * @see #localToScene(javafx.geometry.Point3D)
4660 * @since JavaFX 8u40
4661 */
4662 public Point3D localToScene(Point3D localPoint, boolean rootScene) {
4663 Point3D pt = localToScene(localPoint);
4664 if (rootScene) {
4665 final SubScene subScene = getSubScene();
4666 if (subScene != null) {
4667 pt = SceneUtils.subSceneToScene(subScene, pt);
4668 }
4669 }
4670 return pt;
4671 }
4672
4673 /**
4674 * Transforms a point from the local coordinate space of this {@code Node}
4675 * into the coordinate space of its scene.
4676 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4677 * result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4678 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4679 * {@link #localToScene(double, double, double)}.
4680 *
4681 * @param x the x coordinate of the point in local coordinates
4682 * @param y the y coordinate of the point in local coordinates
4683 * @param z the z coordinate of the point in local coordinates
4684 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4685 * @return transformed point
4686 *
4687 * @see #localToScene(double, double, double)
4688 * @since JavaFX 8u40
4689 */
4690 public Point3D localToScene(double x, double y, double z, boolean rootScene) {
4691 return localToScene(new Point3D(x, y, z), rootScene);
4692 }
4693
4694 /**
4695 * Transforms a point from the local coordinate space of this {@code Node}
4696 * into the coordinate space of its scene.
4697 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4698 * result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4699 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4700 * {@link #localToScene(javafx.geometry.Point2D)}.
4701 *
4702 * @param localPoint the point in local coordinates
4703 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4704 * @return transformed point
4705 *
4706 * @see #localToScene(javafx.geometry.Point2D)
4707 * @since JavaFX 8u40
4708 */
4709 public Point2D localToScene(Point2D localPoint, boolean rootScene) {
4710 if (!rootScene) {
4711 return localToScene(localPoint);
4712 }
4713 Point3D pt = localToScene(localPoint.getX(), localPoint.getY(), 0, rootScene);
4714 return new Point2D(pt.getX(), pt.getY());
4715 }
4716
4717 /**
4718 * Transforms a point from the local coordinate space of this {@code Node}
4719 * into the coordinate space of its scene.
4720 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4721 * result point is in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4722 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4723 * {@link #localToScene(double, double)}.
4724 *
4725 * @param x the x coordinate of the point in local coordinates
4726 * @param y the y coordinate of the point in local coordinates
4727 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4728 * @return transformed point
4729 *
4730 * @see #localToScene(double, double)
4731 * @since JavaFX 8u40
4732 */
4733 public Point2D localToScene(double x, double y, boolean rootScene) {
4734 return localToScene(new Point2D(x, y), rootScene);
4735 }
4736
4737 /**
4738 * Transforms a bounds from the local coordinate space of this {@code Node}
4739 * into the coordinate space of its scene.
4740 * If the Node does not have any {@link SubScene} or {@code rootScene} is set to true, the
4741 * result bounds are in {@link Scene} coordinates of the Node returned by {@link #getScene()}.
4742 * Otherwise, the subscene coordinates are used, which is equivalent to calling
4743 * {@link #localToScene(javafx.geometry.Bounds)}.
4744 *
4745 * @param localBounds the bounds in local coordinates
4746 * @param rootScene whether Scene coordinates should be used even if the Node is in a SubScene
4747 * @return transformed bounds
4748 *
4749 * @see #localToScene(javafx.geometry.Bounds)
4750 * @since JavaFX 8u40
4751 */
4752 public Bounds localToScene(Bounds localBounds, boolean rootScene) {
4753 if (!rootScene) {
4754 return localToScene(localBounds);
4755 }
4756 Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ(), true);
4757 Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ(), true);
4758 Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ(), true);
4759 Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ(), true);
4760 Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ(), true);
4761 Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ(), true);
4762 Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ(), true);
4763 Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ(), true);
4764 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4765 }
4766
4767 /**
4768 * Transforms a bounds from the local coordinate space of this
4769 * {@code Node} into the coordinate space of its scene.
4770 * Note that if this node is in a {@link SubScene}, the result is in the subscene coordinates,
4771 * not that of {@link javafx.scene.Scene}.
4772 * @param localBounds bounds in Node's space
4773 * @return the bounds in the scene coordinates or null if Node is not in a {@link Window}
4774 * @see #localToScene(javafx.geometry.Bounds, boolean)
4775 */
4776 public Bounds localToScene(Bounds localBounds) {
4777 // Do a quick update of localToParentTransform so that we can determine
4778 // if this tx is 2D transform
4779 updateLocalToParentTransform();
4780 if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
4781 Point2D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY());
4782 Point2D p2 = localToScene(localBounds.getMaxX(), localBounds.getMinY());
4783 Point2D p3 = localToScene(localBounds.getMaxX(), localBounds.getMaxY());
4784 Point2D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY());
4785
4786 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4787 }
4788 Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4789 Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4790 Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4791 Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4792 Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4793 Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4794 Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4795 Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4796 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4797
4798 }
4799
4800 /**
4801 * Transforms a point from the coordinate space of the parent into the
4802 * local coordinate space of this {@code Node}.
4803 * @param parentX the x coordinate in Parent's space
4804 * @param parentY the y coordinate in Parent's space
4805 * @return the transformed 2D point in Node's space
4806 */
4807 public Point2D parentToLocal(double parentX, double parentY) {
4808 final com.sun.javafx.geom.Point2D tempPt =
4809 TempState.getInstance().point;
4810 tempPt.setLocation((float)parentX, (float)parentY);
4811 try {
4812 parentToLocal(tempPt);
4813 } catch (NoninvertibleTransformException e) {
4814 return null;
4815 }
4816 return new Point2D(tempPt.x, tempPt.y);
4817 }
4818
4819 /**
4820 * Transforms a point from the coordinate space of the parent into the
4821 * local coordinate space of this {@code Node}.
4822 * @param parentPoint the 2D point in Parent's space
4823 * @return the transformed 2D point in Node's space
4824 */
4825 public Point2D parentToLocal(Point2D parentPoint) {
4826 return parentToLocal(parentPoint.getX(), parentPoint.getY());
4827 }
4828
4829 /**
4830 * Transforms a point from the coordinate space of the parent into the
4831 * local coordinate space of this {@code Node}.
4832 * @param parentPoint parentPoint the 3D point in Parent's space
4833 * @return the transformed 3D point in Node's space
4834 * @since JavaFX 8.0
4835 */
4836 public Point3D parentToLocal(Point3D parentPoint) {
4837 return parentToLocal(parentPoint.getX(), parentPoint.getY(), parentPoint.getZ());
4838 }
4839
4840 /**
4841 * Transforms a point from the coordinate space of the parent into the
4842 * local coordinate space of this {@code Node}.
4843 * @param parentX the x coordinate in Parent's space
4844 * @param parentY the y coordinate in Parent's space
4845 * @param parentZ the z coordinate in Parent's space
4846 * @return the transformed 3D point in Node's space
4847 * @since JavaFX 8.0
4848 */
4849 public Point3D parentToLocal(double parentX, double parentY, double parentZ) {
4850 final com.sun.javafx.geom.Vec3d tempV3D =
4851 TempState.getInstance().vec3d;
4852 tempV3D.set(parentX, parentY, parentZ);
4853 try {
4854 parentToLocal(tempV3D);
4855 } catch (NoninvertibleTransformException e) {
4856 return null;
4857 }
4858 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4859 }
4860
4861 /**
4862 * Transforms a rectangle from the coordinate space of the parent into the
4863 * local coordinate space of this {@code Node}.
4864 * @param parentBounds the bounds in Parent's space
4865 * @return the transformed bounds in Node's space
4866 */
4867 public Bounds parentToLocal(Bounds parentBounds) {
4868 // Do a quick update of localToParentTransform so that we can determine
4869 // if this tx is 2D transform
4870 updateLocalToParentTransform();
4871 if (localToParentTx.is2D() && (parentBounds.getMinZ() == 0) && (parentBounds.getMaxZ() == 0)) {
4872 Point2D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY());
4873 Point2D p2 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY());
4874 Point2D p3 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY());
4875 Point2D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY());
4876
4877 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4878 }
4879 Point3D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMinZ());
4880 Point3D p2 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMaxZ());
4881 Point3D p3 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMinZ());
4882 Point3D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
4883 Point3D p5 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMinZ());
4884 Point3D p6 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
4885 Point3D p7 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMinZ());
4886 Point3D p8 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMaxZ());
4887 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4888 }
4889
4890 /**
4891 * Transforms a point from the local coordinate space of this {@code Node}
4892 * into the coordinate space of its parent.
4893 * @param localX the x coordinate of the point in Node's space
4894 * @param localY the y coordinate of the point in Node's space
4895 * @return the transformed 2D point in Parent's space
4896 */
4897 public Point2D localToParent(double localX, double localY) {
4898 final com.sun.javafx.geom.Point2D tempPt =
4899 TempState.getInstance().point;
4900 tempPt.setLocation((float)localX, (float)localY);
4901 localToParent(tempPt);
4902 return new Point2D(tempPt.x, tempPt.y);
4903 }
4904
4905 /**
4906 * Transforms a point from the local coordinate space of this {@code Node}
4907 * into the coordinate space of its parent.
4908 * @param localPoint the 2D point in Node's space
4909 * @return the transformed 2D point in Parent's space
4910 */
4911 public Point2D localToParent(Point2D localPoint) {
4912 return localToParent(localPoint.getX(), localPoint.getY());
4913 }
4914
4915 /**
4916 * Transforms a point from the local coordinate space of this {@code Node}
4917 * into the coordinate space of its parent.
4918 * @param localPoint the 3D point in Node's space
4919 * @return the transformed 3D point in Parent's space
4920 * @since JavaFX 8.0
4921 */
4922 public Point3D localToParent(Point3D localPoint) {
4923 return localToParent(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4924 }
4925
4926 /**
4927 * Transforms a point from the local coordinate space of this {@code Node}
4928 * into the coordinate space of its parent.
4929 * @param x the x coordinate of the point in Node's space
4930 * @param y the y coordinate of the point in Node's space
4931 * @param z the z coordinate of the point in Node's space
4932 * @return the transformed 3D point in Parent's space
4933 * @since JavaFX 8.0
4934 */
4935 public Point3D localToParent(double x, double y, double z) {
4936 final com.sun.javafx.geom.Vec3d tempV3D =
4937 TempState.getInstance().vec3d;
4938 tempV3D.set(x, y, z);
4939 localToParent(tempV3D);
4940 return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4941 }
4942
4943 /**
4944 * Transforms a bounds from the local coordinate space of this
4945 * {@code Node} into the coordinate space of its parent.
4946 * @param localBounds the bounds in Node's space
4947 * @return the transformed bounds in Parent's space
4948 */
4949 public Bounds localToParent(Bounds localBounds) {
4950 // Do a quick update of localToParentTransform so that we can determine
4951 // if this tx is 2D transform
4952 updateLocalToParentTransform();
4953 if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
4954 Point2D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY());
4955 Point2D p2 = localToParent(localBounds.getMaxX(), localBounds.getMinY());
4956 Point2D p3 = localToParent(localBounds.getMaxX(), localBounds.getMaxY());
4957 Point2D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY());
4958
4959 return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
4960 }
4961 Point3D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4962 Point3D p2 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4963 Point3D p3 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4964 Point3D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4965 Point3D p5 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4966 Point3D p6 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4967 Point3D p7 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4968 Point3D p8 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4969 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4970 }
4971
4972 /**
4973 * Copy the localToParent transform into specified transform.
4974 */
4975 BaseTransform getLocalToParentTransform(BaseTransform tx) {
4976 updateLocalToParentTransform();
4977 tx.setTransform(localToParentTx);
4978 return tx;
4979 }
4980
4981 /*
4982 * Currently used only by PathTransition
4983 */
4984 final BaseTransform getLeafTransform() {
4985 return getLocalToParentTransform(TempState.getInstance().leafTx);
4986 }
4987
4988 /*
4989 * Invoked whenever the transforms[] ObservableList changes, or by the transforms
4990 * in that ObservableList whenever they are changed.
4991 *
4992 * Note: This method MUST only be called via its accessor method.
4993 */
4994 private void doTransformsChanged() {
4995 if (!transformDirty) {
4996 NodeHelper.markDirty(this, DirtyBits.NODE_TRANSFORM);
4997 transformDirty = true;
4998 transformedBoundsChanged();
4999 }
5000 invalidateLocalToParentTransform();
5001 invalidateLocalToSceneTransform();
5002 }
5003
5004 final double getPivotX() {
5005 final Bounds bounds = getLayoutBounds();
5006 return bounds.getMinX() + bounds.getWidth()/2;
5007 }
5008
5009 final double getPivotY() {
5010 final Bounds bounds = getLayoutBounds();
5011 return bounds.getMinY() + bounds.getHeight()/2;
5012 }
5013
5014 final double getPivotZ() {
5015 final Bounds bounds = getLayoutBounds();
5016 return bounds.getMinZ() + bounds.getDepth()/2;
5017 }
5018
5019 /**
5020 * This helper function will update the transform matrix on the peer based
5021 * on the "complete" transform for this node.
5022 */
5023 void updateLocalToParentTransform() {
5024 if (transformDirty) {
5025 localToParentTx.setToIdentity();
5026
5027 boolean mirror = false;
5028 double mirroringCenter = 0;
5029 if (hasMirroring()) {
5030 final Scene sceneValue = getScene();
5031 if ((sceneValue != null) && (sceneValue.getRoot() == this)) {
5032 // handle scene mirroring in this branch
5033 // (must be the last transformation)
5034 mirroringCenter = sceneValue.getWidth() / 2;
5035 if (mirroringCenter == 0.0) {
5036 mirroringCenter = getPivotX();
5037 }
5038
5039 localToParentTx = localToParentTx.deriveWithTranslation(
5040 mirroringCenter, 0.0);
5041 localToParentTx = localToParentTx.deriveWithScale(
5042 -1.0, 1.0, 1.0);
5043 localToParentTx = localToParentTx.deriveWithTranslation(
5044 -mirroringCenter, 0.0);
5045 } else {
5046 // mirror later
5047 mirror = true;
5048 mirroringCenter = getPivotX();
5049 }
5050 }
5051
5052 if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) {
5053 // recompute pivotX, pivotY and pivotZ
5054 double pivotX = getPivotX();
5055 double pivotY = getPivotY();
5056 double pivotZ = getPivotZ();
5057
5058 localToParentTx = localToParentTx.deriveWithTranslation(
5059 getTranslateX() + getLayoutX() + pivotX,
5060 getTranslateY() + getLayoutY() + pivotY,
5061 getTranslateZ() + pivotZ);
5062 localToParentTx = localToParentTx.deriveWithRotation(
5063 Math.toRadians(getRotate()), getRotationAxis().getX(),
5064 getRotationAxis().getY(), getRotationAxis().getZ());
5065 localToParentTx = localToParentTx.deriveWithScale(
5066 getScaleX(), getScaleY(), getScaleZ());
5067 localToParentTx = localToParentTx.deriveWithTranslation(
5068 -pivotX, -pivotY, -pivotZ);
5069 } else {
5070 localToParentTx = localToParentTx.deriveWithTranslation(
5071 getTranslateX() + getLayoutX(),
5072 getTranslateY() + getLayoutY(),
5073 getTranslateZ());
5074 }
5075
5076 if (hasTransforms()) {
5077 for (Transform t : getTransforms()) {
5078 localToParentTx = TransformHelper.derive(t, localToParentTx);
5079 }
5080 }
5081
5082 // Check to see whether the node requires mirroring
5083 if (mirror) {
5084 localToParentTx = localToParentTx.deriveWithTranslation(
5085 mirroringCenter, 0);
5086 localToParentTx = localToParentTx.deriveWithScale(
5087 -1.0, 1.0, 1.0);
5088 localToParentTx = localToParentTx.deriveWithTranslation(
5089 -mirroringCenter, 0);
5090 }
5091
5092 transformDirty = false;
5093 }
5094 }
5095
5096 /**
5097 * Transforms in place the specified point from parent coords to local
5098 * coords. Made package private for the sake of testing.
5099 */
5100 void parentToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
5101 updateLocalToParentTransform();
5102 localToParentTx.inverseTransform(pt, pt);
5103 }
5104
5105 void parentToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
5106 updateLocalToParentTransform();
5107 localToParentTx.inverseTransform(pt, pt);
5108 }
5109
5110 void sceneToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
5111 if (getParent() != null) {
5112 getParent().sceneToLocal(pt);
5113 }
5114 parentToLocal(pt);
5115 }
5116
5117 void sceneToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
5118 if (getParent() != null) {
5119 getParent().sceneToLocal(pt);
5120 }
5121 parentToLocal(pt);
5122 }
5123
5124 void localToScene(com.sun.javafx.geom.Point2D pt) {
5125 localToParent(pt);
5126 if (getParent() != null) {
5127 getParent().localToScene(pt);
5128 }
5129 }
5130
5131 void localToScene(com.sun.javafx.geom.Vec3d pt) {
5132 localToParent(pt);
5133 if (getParent() != null) {
5134 getParent().localToScene(pt);
5135 }
5136 }
5137
5138 /***************************************************************************
5139 * *
5140 * Mouse event related APIs *
5141 * *
5142 **************************************************************************/
5143
5144 /**
5145 * Transforms in place the specified point from local coords to parent
5146 * coords. Made package private for the sake of testing.
5147 */
5148 void localToParent(com.sun.javafx.geom.Point2D pt) {
5149 updateLocalToParentTransform();
5150 localToParentTx.transform(pt, pt);
5151 }
5152
5153 void localToParent(com.sun.javafx.geom.Vec3d pt) {
5154 updateLocalToParentTransform();
5155 localToParentTx.transform(pt, pt);
5156 }
5157
5158 /*
5159 * Finds a top-most child node that contains the given local coordinates.
5160 *
5161 * The result argument is used for storing the picking result.
5162 *
5163 * Note: This method MUST only be called via its accessor method.
5164 */
5165 private void doPickNodeLocal(PickRay localPickRay, PickResultChooser result) {
5166 intersects(localPickRay, result);
5167 }
5168
5169 /*
5170 * Finds a top-most child node that intersects the given ray.
5171 *
5172 * The result argument is used for storing the picking result.
5173 */
5174 final void pickNode(PickRay pickRay, PickResultChooser result) {
5175
5176 // In some conditions we can omit picking this node or subgraph
5177 if (!isVisible() || isDisable() || isMouseTransparent()) {
5178 return;
5179 }
5180
5181 final Vec3d o = pickRay.getOriginNoClone();
5182 final double ox = o.x;
5183 final double oy = o.y;
5184 final double oz = o.z;
5185 final Vec3d d = pickRay.getDirectionNoClone();
5186 final double dx = d.x;
5187 final double dy = d.y;
5188 final double dz = d.z;
5189
5190 updateLocalToParentTransform();
5191 try {
5192 localToParentTx.inverseTransform(o, o);
5193 localToParentTx.inverseDeltaTransform(d, d);
5194
5195 // Delegate to a function which can be overridden by subclasses which
5196 // actually does the pick. The implementation is markedly different
5197 // for leaf nodes vs. parent nodes vs. region nodes.
5198 NodeHelper.pickNodeLocal(this, pickRay, result);
5199 } catch (NoninvertibleTransformException e) {
5200 // in this case we just don't pick anything
5201 }
5202
5203 pickRay.setOrigin(ox, oy, oz);
5204 pickRay.setDirection(dx, dy, dz);
5205 }
5206
5207 /*
5208 * Returns {@code true} if the given ray (start, dir), specified in the
5209 * local coordinate space of this {@code Node}, intersects the
5210 * shape of this {@code Node}. Note that this method does not take visibility
5211 * into account; the test is based on the geometry of this {@code Node} only.
5212 * <p>
5213 * The pickResult is updated if the found intersection is closer than
5214 * the currently held one.
5215 * <p>
5216 * Note that this is a conditional feature. See
5217 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
5218 * for more information.
5219 */
5220 final boolean intersects(PickRay pickRay, PickResultChooser pickResult) {
5221 double boundsDistance = intersectsBounds(pickRay);
5222 if (!Double.isNaN(boundsDistance)) {
5223 if (isPickOnBounds()) {
5224 if (pickResult != null) {
5225 pickResult.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
5226 }
5227 return true;
5228 } else {
5229 return NodeHelper.computeIntersects(this, pickRay, pickResult);
5230 }
5231 }
5232 return false;
5233 }
5234
5235 /*
5236 * Computes the intersection of the pickRay with this node.
5237 * The pickResult argument is updated if the found intersection
5238 * is closer than the passed one. On the other hand, the return value
5239 * specifies whether the intersection exists, regardless of its comparison
5240 * with the given pickResult.
5241 */
5242 private boolean doComputeIntersects(PickRay pickRay, PickResultChooser pickResult) {
5243 double origZ = pickRay.getOriginNoClone().z;
5244 double dirZ = pickRay.getDirectionNoClone().z;
5245 // Handle the case where pickRay is almost parallel to the Z-plane
5246 if (almostZero(dirZ)) {
5247 return false;
5248 }
5249 double t = -origZ / dirZ;
5250 if (t < pickRay.getNearClip() || t > pickRay.getFarClip()) {
5251 return false;
5252 }
5253 double x = pickRay.getOriginNoClone().x + (pickRay.getDirectionNoClone().x * t);
5254 double y = pickRay.getOriginNoClone().y + (pickRay.getDirectionNoClone().y * t);
5255
5256 if (contains((float) x, (float) y)) {
5257 if (pickResult != null) {
5258 pickResult.offer(this, t, PickResultChooser.computePoint(pickRay, t));
5259 }
5260 return true;
5261 }
5262 return false;
5263 }
5264
5265 /*
5266 * Computes the intersection of the pickRay with the bounds of this node.
5267 * The return value is the distance between the camera and the intersection
5268 * point, measured in pickRay direction magnitudes. If there is
5269 * no intersection, it returns NaN.
5270 *
5271 * @param pickRay The pick ray
5272 * @return Distance of the intersection point, a NaN if there
5273 * is no intersection
5274 */
5275 final double intersectsBounds(PickRay pickRay) {
5276
5277 final Vec3d dir = pickRay.getDirectionNoClone();
5278 double tmin, tmax;
5279
5280 final Vec3d origin = pickRay.getOriginNoClone();
5281 final double originX = origin.x;
5282 final double originY = origin.y;
5283 final double originZ = origin.z;
5284
5285 final TempState tempState = TempState.getInstance();
5286 BaseBounds tempBounds = tempState.bounds;
5287
5288 tempBounds = getLocalBounds(tempBounds,
5289 BaseTransform.IDENTITY_TRANSFORM);
5290
5291 if (dir.x == 0.0 && dir.y == 0.0) {
5292 // fast path for the usual 2D picking
5293
5294 if (dir.z == 0.0) {
5295 return Double.NaN;
5296 }
5297
5298 if (originX < tempBounds.getMinX() ||
5299 originX > tempBounds.getMaxX() ||
5300 originY < tempBounds.getMinY() ||
5301 originY > tempBounds.getMaxY()) {
5302 return Double.NaN;
5303 }
5304
5305 final double invDirZ = 1.0 / dir.z;
5306 final boolean signZ = invDirZ < 0.0;
5307
5308 final double minZ = tempBounds.getMinZ();
5309 final double maxZ = tempBounds.getMaxZ();
5310 tmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
5311 tmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
5312
5313 } else if (tempBounds.getDepth() == 0.0) {
5314 // fast path for 3D picking of 2D bounds
5315
5316 if (almostZero(dir.z)) {
5317 return Double.NaN;
5318 }
5319
5320 final double t = (tempBounds.getMinZ() - originZ) / dir.z;
5321 final double x = originX + (dir.x * t);
5322 final double y = originY + (dir.y * t);
5323
5324 if (x < tempBounds.getMinX() ||
5325 x > tempBounds.getMaxX() ||
5326 y < tempBounds.getMinY() ||
5327 y > tempBounds.getMaxY()) {
5328 return Double.NaN;
5329 }
5330
5331 tmin = tmax = t;
5332
5333 } else {
5334
5335 final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x);
5336 final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y);
5337 final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z);
5338 final boolean signX = invDirX < 0.0;
5339 final boolean signY = invDirY < 0.0;
5340 final boolean signZ = invDirZ < 0.0;
5341 final double minX = tempBounds.getMinX();
5342 final double minY = tempBounds.getMinY();
5343 final double maxX = tempBounds.getMaxX();
5344 final double maxY = tempBounds.getMaxY();
5345
5346 tmin = Double.NEGATIVE_INFINITY;
5347 tmax = Double.POSITIVE_INFINITY;
5348 if (Double.isInfinite(invDirX)) {
5349 if (minX <= originX && maxX >= originX) {
5350 // move on, we are inside for the whole length
5351 } else {
5352 return Double.NaN;
5353 }
5354 } else {
5355 tmin = ((signX ? maxX : minX) - originX) * invDirX;
5356 tmax = ((signX ? minX : maxX) - originX) * invDirX;
5357 }
5358
5359 if (Double.isInfinite(invDirY)) {
5360 if (minY <= originY && maxY >= originY) {
5361 // move on, we are inside for the whole length
5362 } else {
5363 return Double.NaN;
5364 }
5365 } else {
5366 final double tymin = ((signY ? maxY : minY) - originY) * invDirY;
5367 final double tymax = ((signY ? minY : maxY) - originY) * invDirY;
5368
5369 if ((tmin > tymax) || (tymin > tmax)) {
5370 return Double.NaN;
5371 }
5372 if (tymin > tmin) {
5373 tmin = tymin;
5374 }
5375 if (tymax < tmax) {
5376 tmax = tymax;
5377 }
5378 }
5379
5380 final double minZ = tempBounds.getMinZ();
5381 final double maxZ = tempBounds.getMaxZ();
5382 if (Double.isInfinite(invDirZ)) {
5383 if (minZ <= originZ && maxZ >= originZ) {
5384 // move on, we are inside for the whole length
5385 } else {
5386 return Double.NaN;
5387 }
5388 } else {
5389 final double tzmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
5390 final double tzmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
5391
5392 if ((tmin > tzmax) || (tzmin > tmax)) {
5393 return Double.NaN;
5394 }
5395 if (tzmin > tmin) {
5396 tmin = tzmin;
5397 }
5398 if (tzmax < tmax) {
5399 tmax = tzmax;
5400 }
5401 }
5402 }
5403
5404 // For clip we use following semantics: pick the node normally
5405 // if there is an intersection with the clip node. We don't consider
5406 // clip node distance.
5407 Node clip = getClip();
5408 if (clip != null
5409 // FIXME: All 3D picking is currently ignored by rendering.
5410 // Until this is fixed or defined differently (RT-28510),
5411 // we follow this behavior.
5412 && !(this instanceof Shape3D) && !(clip instanceof Shape3D)) {
5413 final double dirX = dir.x;
5414 final double dirY = dir.y;
5415 final double dirZ = dir.z;
5416
5417 clip.updateLocalToParentTransform();
5418
5419 boolean hitClip = true;
5420 try {
5421 clip.localToParentTx.inverseTransform(origin, origin);
5422 clip.localToParentTx.inverseDeltaTransform(dir, dir);
5423 } catch (NoninvertibleTransformException e) {
5424 hitClip = false;
5425 }
5426 hitClip = hitClip && clip.intersects(pickRay, null);
5427 pickRay.setOrigin(originX, originY, originZ);
5428 pickRay.setDirection(dirX, dirY, dirZ);
5429
5430 if (!hitClip) {
5431 return Double.NaN;
5432 }
5433 }
5434
5435 if (Double.isInfinite(tmin) || Double.isNaN(tmin)) {
5436 // We've got a nonsense pick ray or bounds.
5437 return Double.NaN;
5438 }
5439
5440 final double minDistance = pickRay.getNearClip();
5441 final double maxDistance = pickRay.getFarClip();
5442 if (tmin < minDistance) {
5443 if (tmax >= minDistance) {
5444 // we are inside bounds
5445 return 0.0;
5446 } else {
5447 return Double.NaN;
5448 }
5449 } else if (tmin > maxDistance) {
5450 return Double.NaN;
5451 }
5452
5453 return tmin;
5454 }
5455
5456
5457 // Good to find a home for commonly use util. code such as EPS.
5458 // and almostZero. This code currently defined in multiple places,
5459 // such as Affine3D and GeneralTransform3D.
5460 private static final double EPSILON_ABSOLUTE = 1.0e-5;
5461
5462 static boolean almostZero(double a) {
5463 return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
5464 }
5465
5466 /***************************************************************************
5467 * *
5468 * viewOrder property handling *
5469 * *
5470 **************************************************************************/
5471
5472 /**
5473 * Defines the rendering and picking order of this {@code Node} within its
5474 * parent.
5475 * <p>
5476 * This property is used to alter the rendering and picking order of a node
5477 * within its parent without reordering the parent's {@code children} list.
5478 * For example, this can be used as a more efficient way to implement
5479 * transparency sorting. To do this, an application can assign the viewOrder
5480 * value of each node to the computed distance between that node and the
5481 * viewer.
5482 * </p>
5483 * <p>
5484 * The parent will traverse its {@code children} in decreasing
5485 * {@code viewOrder} order. This means that a child with a lower
5486 * {@code viewOrder} will be in front of a child with a higher
5487 * {@code viewOrder}. If two children have the same {@code viewOrder}, the
5488 * parent will traverse them in the order they appear in the parent's
5489 * {@code children} list.
5490 * </p>
5491 * <p>
5492 * However, {@code viewOrder} does not alter the layout and focus traversal
5493 * order of this Node within its parent. A parent always traverses its
5494 * {@code children} list in order when doing layout or focus traversal.
5495 * </p>
5496 *
5497 * @return the view order for this {@code Node}
5498 * @defaultValue 0.0
5499 *
5500 * @since 9
5501 */
5502 public final DoubleProperty viewOrderProperty() {
5503 return getMiscProperties().viewOrderProperty();
5504 }
5505
5506 public final void setViewOrder(double value) {
5507 viewOrderProperty().set(value);
5508 }
5509
5510 public final double getViewOrder() {
5511 return (miscProperties == null) ? DEFAULT_VIEW_ORDER
5512 : miscProperties.getViewOrder();
5513 }
5514
5515 /***************************************************************************
5516 * *
5517 * Transformations *
5518 * *
5519 **************************************************************************/
5520 /**
5521 * Defines the ObservableList of {@link javafx.scene.transform.Transform} objects
5522 * to be applied to this {@code Node}. This ObservableList of transforms is applied
5523 * before {@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #scaleXProperty scaleX}, and
5524 * {@link #scaleYProperty scaleY}, {@link #rotateProperty rotate} transforms.
5525 *
5526 * @return the transforms for this {@code Node}
5527 * @defaultValue empty
5528 */
5529 public final ObservableList<Transform> getTransforms() {
5530 return transformsProperty();
5531 }
5532
5533 private ObservableList<Transform> transformsProperty() {
5534 return getNodeTransformation().getTransforms();
5535 }
5536
5537 public final void setTranslateX(double value) {
5538 translateXProperty().set(value);
5539 }
5540
5541 public final double getTranslateX() {
5542 return (nodeTransformation == null)
5543 ? DEFAULT_TRANSLATE_X
5544 : nodeTransformation.getTranslateX();
5545 }
5546
5547 /**
5548 * Defines the x coordinate of the translation that is added to this {@code Node}'s
5549 * transform.
5550 * <p>
5551 * The node's final translation will be computed as {@link #layoutXProperty layoutX} + {@code translateX},
5552 * where {@code layoutX} establishes the node's stable position and {@code translateX}
5553 * optionally makes dynamic adjustments to that position.
5554 *<p>
5555 * This variable can be used to alter the location of a node without disturbing
5556 * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
5557 *
5558 * @return the translateX for this {@code Node}
5559 * @defaultValue 0
5560 */
5561 public final DoubleProperty translateXProperty() {
5562 return getNodeTransformation().translateXProperty();
5563 }
5564
5565 public final void setTranslateY(double value) {
5566 translateYProperty().set(value);
5567 }
5568
5569 public final double getTranslateY() {
5570 return (nodeTransformation == null)
5571 ? DEFAULT_TRANSLATE_Y
5572 : nodeTransformation.getTranslateY();
5573 }
5574
5575 /**
5576 * Defines the y coordinate of the translation that is added to this {@code Node}'s
5577 * transform.
5578 * <p>
5579 * The node's final translation will be computed as {@link #layoutYProperty layoutY} + {@code translateY},
5580 * where {@code layoutY} establishes the node's stable position and {@code translateY}
5581 * optionally makes dynamic adjustments to that position.
5582 * <p>
5583 * This variable can be used to alter the location of a node without disturbing
5584 * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
5585 *
5586 * @return the translateY for this {@code Node}
5587 * @defaultValue 0
5588 */
5589 public final DoubleProperty translateYProperty() {
5590 return getNodeTransformation().translateYProperty();
5591 }
5592
5593 public final void setTranslateZ(double value) {
5594 translateZProperty().set(value);
5595 }
5596
5597 public final double getTranslateZ() {
5598 return (nodeTransformation == null)
5599 ? DEFAULT_TRANSLATE_Z
5600 : nodeTransformation.getTranslateZ();
5601 }
5602
5603 /**
5604 * Defines the Z coordinate of the translation that is added to the
5605 * transformed coordinates of this {@code Node}. This value will be added
5606 * to any translation defined by the {@code transforms} ObservableList and
5607 * {@code layoutZ}.
5608 * <p>
5609 * This variable can be used to alter the location of a Node without
5610 * disturbing its layout bounds, which makes it useful for animating a
5611 * node's location.
5612 * <p>
5613 * Note that this is a conditional feature. See
5614 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
5615 * for more information.
5616 *
5617 * @return the translateZ for this {@code Node}
5618 * @defaultValue 0
5619 */
5620 public final DoubleProperty translateZProperty() {
5621 return getNodeTransformation().translateZProperty();
5622 }
5623
5624 public final void setScaleX(double value) {
5625 scaleXProperty().set(value);
5626 }
5627
5628 public final double getScaleX() {
5629 return (nodeTransformation == null) ? DEFAULT_SCALE_X
5630 : nodeTransformation.getScaleX();
5631 }
5632
5633 /**
5634 * Defines the factor by which coordinates are scaled about the center of the
5635 * object along the X axis of this {@code Node}. This is used to stretch or
5636 * shrink the node either manually or by using an animation.
5637 * <p>
5638 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
5639 * default, which makes it ideal for scaling the entire node after
5640 * all effects and transforms have been taken into account.
5641 * <p>
5642 * The pivot point about which the scale occurs is the center of the
5643 * untransformed {@link #layoutBoundsProperty layoutBounds}.
5644 *
5645 * @return the scaleX for this {@code Node}
5646 * @defaultValue 1.0
5647 */
5648 public final DoubleProperty scaleXProperty() {
5649 return getNodeTransformation().scaleXProperty();
5650 }
5651
5652 public final void setScaleY(double value) {
5653 scaleYProperty().set(value);
5654 }
5655
5656 public final double getScaleY() {
5657 return (nodeTransformation == null) ? DEFAULT_SCALE_Y
5658 : nodeTransformation.getScaleY();
5659 }
5660
5661 /**
5662 * Defines the factor by which coordinates are scaled about the center of the
5663 * object along the Y axis of this {@code Node}. This is used to stretch or
5664 * shrink the node either manually or by using an animation.
5665 * <p>
5666 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
5667 * default, which makes it ideal for scaling the entire node after
5668 * all effects and transforms have been taken into account.
5669 * <p>
5670 * The pivot point about which the scale occurs is the center of the
5671 * untransformed {@link #layoutBoundsProperty layoutBounds}.
5672 *
5673 * @return the scaleY for this {@code Node}
5674 * @defaultValue 1.0
5675 */
5676 public final DoubleProperty scaleYProperty() {
5677 return getNodeTransformation().scaleYProperty();
5678 }
5679
5680 public final void setScaleZ(double value) {
5681 scaleZProperty().set(value);
5682 }
5683
5684 public final double getScaleZ() {
5685 return (nodeTransformation == null) ? DEFAULT_SCALE_Z
5686 : nodeTransformation.getScaleZ();
5687 }
5688
5689 /**
5690 * Defines the factor by which coordinates are scaled about the center of the
5691 * object along the Z axis of this {@code Node}. This is used to stretch or
5692 * shrink the node either manually or by using an animation.
5693 * <p>
5694 * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
5695 * default, which makes it ideal for scaling the entire node after
5696 * all effects and transforms have been taken into account.
5697 * <p>
5698 * The pivot point about which the scale occurs is the center of the
5699 * rectangular bounds formed by taking {@link #boundsInLocalProperty boundsInLocal} and applying
5700 * all the transforms in the {@link #getTransforms transforms} ObservableList.
5701 * <p>
5702 * Note that this is a conditional feature. See
5703 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
5704 * for more information.
5705 *
5706 * @return the scaleZ for this {@code Node}
5707 * @defaultValue 1.0
5708 */
5709 public final DoubleProperty scaleZProperty() {
5710 return getNodeTransformation().scaleZProperty();
5711 }
5712
5713 public final void setRotate(double value) {
5714 rotateProperty().set(value);
5715 }
5716
5717 public final double getRotate() {
5718 return (nodeTransformation == null) ? DEFAULT_ROTATE
5719 : nodeTransformation.getRotate();
5720 }
5721
5722 /**
5723 * Defines the angle of rotation about the {@code Node}'s center, measured in
5724 * degrees. This is used to rotate the {@code Node}.
5725 * <p>
5726 * This rotation factor is not included in {@link #layoutBoundsProperty layoutBounds} by
5727 * default, which makes it ideal for rotating the entire node after
5728 * all effects and transforms have been taken into account.
5729 * <p>
5730 * The pivot point about which the rotation occurs is the center of the
5731 * untransformed {@link #layoutBoundsProperty layoutBounds}.
5732 * <p>
5733 * Note that because the pivot point is computed as the center of this
5734 * {@code Node}'s layout bounds, any change to the layout bounds will cause
5735 * the pivot point to change, which can move the object. For a leaf node,
5736 * any change to the geometry will cause the layout bounds to change.
5737 * For a group node, any change to any of its children, including a
5738 * change in a child's geometry, clip, effect, position, orientation, or
5739 * scale, will cause the group's layout bounds to change. If this movement
5740 * of the pivot point is not
5741 * desired, applications should instead use the Node's {@link #getTransforms transforms}
5742 * ObservableList, and add a {@link javafx.scene.transform.Rotate} transform,
5743 * which has a user-specifiable pivot point.
5744 *
5745 * @return the rotate for this {@code Node}
5746 * @defaultValue 0.0
5747 */
5748 public final DoubleProperty rotateProperty() {
5749 return getNodeTransformation().rotateProperty();
5750 }
5751
5752 public final void setRotationAxis(Point3D value) {
5753 rotationAxisProperty().set(value);
5754 }
5755
5756 public final Point3D getRotationAxis() {
5757 return (nodeTransformation == null)
5758 ? DEFAULT_ROTATION_AXIS
5759 : nodeTransformation.getRotationAxis();
5760 }
5761
5762 /**
5763 * Defines the axis of rotation of this {@code Node}.
5764 * <p>
5765 * Note that this is a conditional feature. See
5766 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
5767 * for more information.
5768 *
5769 * @return the rotationAxis for this {@code Node}
5770 * @defaultValue Rotate.Z_AXIS
5771 */
5772 public final ObjectProperty<Point3D> rotationAxisProperty() {
5773 return getNodeTransformation().rotationAxisProperty();
5774 }
5775
5776 /**
5777 * An affine transform that holds the computed local-to-parent transform.
5778 * This is the concatenation of all transforms in this node, including all
5779 * of the convenience transforms.
5780 * @return the localToParent transform for this {@code Node}
5781 * @since JavaFX 2.2
5782 */
5783 public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
5784 return getNodeTransformation().localToParentTransformProperty();
5785 }
5786
5787 private void invalidateLocalToParentTransform() {
5788 if (nodeTransformation != null) {
5789 nodeTransformation.invalidateLocalToParentTransform();
5790 }
5791 }
5792
5793 public final Transform getLocalToParentTransform() {
5794 return localToParentTransformProperty().get();
5795 }
5796
5797 /**
5798 * An affine transform that holds the computed local-to-scene transform.
5799 * This is the concatenation of all transforms in this node's parents and
5800 * in this node, including all of the convenience transforms up to the root.
5801 * If this node is in a {@link javafx.scene.SubScene}, this property represents
5802 * transforms up to the subscene, not the root scene.
5803 *
5804 * <p>
5805 * Note that when you register a listener or a binding to this property,
5806 * it needs to listen for invalidation on all its parents to the root node.
5807 * This means that registering a listener on this
5808 * property on many nodes may negatively affect performance of
5809 * transformation changes in their common parents.
5810 * </p>
5811 *
5812 * @return the localToScene transform for this {@code Node}
5813 * @since JavaFX 2.2
5814 */
5815 public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
5816 return getNodeTransformation().localToSceneTransformProperty();
5817 }
5818
5819 private void invalidateLocalToSceneTransform() {
5820 if (nodeTransformation != null) {
5821 nodeTransformation.invalidateLocalToSceneTransform();
5822 }
5823 }
5824
5825 public final Transform getLocalToSceneTransform() {
5826 return localToSceneTransformProperty().get();
5827 }
5828
5829 private NodeTransformation nodeTransformation;
5830
5831 private NodeTransformation getNodeTransformation() {
5832 if (nodeTransformation == null) {
5833 nodeTransformation = new NodeTransformation();
5834 }
5835
5836 return nodeTransformation;
5837 }
5838
5839 private boolean hasTransforms() {
5840 return (nodeTransformation != null)
5841 && nodeTransformation.hasTransforms();
5842 }
5843
5844 // for tests only
5845 Transform getCurrentLocalToSceneTransformState() {
5846 if (nodeTransformation == null ||
5847 nodeTransformation.localToSceneTransform == null) {
5848 return null;
5849 }
5850
5851 return nodeTransformation.localToSceneTransform.transform;
5852 }
5853
5854 private static final double DEFAULT_TRANSLATE_X = 0;
5855 private static final double DEFAULT_TRANSLATE_Y = 0;
5856 private static final double DEFAULT_TRANSLATE_Z = 0;
5857 private static final double DEFAULT_SCALE_X = 1;
5858 private static final double DEFAULT_SCALE_Y = 1;
5859 private static final double DEFAULT_SCALE_Z = 1;
5860 private static final double DEFAULT_ROTATE = 0;
5861 private static final Point3D DEFAULT_ROTATION_AXIS = Rotate.Z_AXIS;
5862
5863 private final class NodeTransformation {
5864 private DoubleProperty translateX;
5865 private DoubleProperty translateY;
5866 private DoubleProperty translateZ;
5867 private DoubleProperty scaleX;
5868 private DoubleProperty scaleY;
5869 private DoubleProperty scaleZ;
5870 private DoubleProperty rotate;
5871 private ObjectProperty<Point3D> rotationAxis;
5872 private ObservableList<Transform> transforms;
5873 private LazyTransformProperty localToParentTransform;
5874 private LazyTransformProperty localToSceneTransform;
5875 private int listenerReasons = 0;
5876 private InvalidationListener localToSceneInvLstnr;
5877
5878 private InvalidationListener getLocalToSceneInvalidationListener() {
5879 if (localToSceneInvLstnr == null) {
5880 localToSceneInvLstnr = observable -> invalidateLocalToSceneTransform();
5881 }
5882 return localToSceneInvLstnr;
5883 }
5884
5885 public void incListenerReasons() {
5886 if (listenerReasons == 0) {
5887 Node n = Node.this.getParent();
5888 if (n != null) {
5889 n.localToSceneTransformProperty().addListener(
5890 getLocalToSceneInvalidationListener());
5891 }
5892 }
5893 listenerReasons++;
5894 }
5895
5896 public void decListenerReasons() {
5897 listenerReasons--;
5898 if (listenerReasons == 0) {
5899 Node n = Node.this.getParent();
5900 if (n != null) {
5901 n.localToSceneTransformProperty().removeListener(
5902 getLocalToSceneInvalidationListener());
5903 }
5904 if (localToSceneTransform != null) {
5905 localToSceneTransform.validityUnknown();
5906 }
5907 }
5908 }
5909
5910 public final Transform getLocalToParentTransform() {
5911 return localToParentTransformProperty().get();
5912 }
5913
5914 public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
5915 if (localToParentTransform == null) {
5916 localToParentTransform = new LazyTransformProperty() {
5917 @Override
5918 protected Transform computeTransform(Transform reuse) {
5919 updateLocalToParentTransform();
5920 return TransformUtils.immutableTransform(reuse,
5921 localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(),
5922 localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(),
5923 localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt());
5924 }
5925
5926 @Override
5927 protected boolean validityKnown() {
5928 return true;
5929 }
5930
5931 @Override
5932 protected int computeValidity() {
5933 return valid;
5934 }
5935
5936 @Override
5937 public Object getBean() {
5938 return Node.this;
5939 }
5940
5941 @Override
5942 public String getName() {
5943 return "localToParentTransform";
5944 }
5945 };
5946 }
5947
5948 return localToParentTransform;
5949 }
5950
5951 public void invalidateLocalToParentTransform() {
5952 if (localToParentTransform != null) {
5953 localToParentTransform.invalidate();
5954 }
5955 }
5956
5957 public final Transform getLocalToSceneTransform() {
5958 return localToSceneTransformProperty().get();
5959 }
5960
5961 class LocalToSceneTransformProperty extends LazyTransformProperty {
5962 // need this to track number of listeners
5963 private List localToSceneListeners;
5964 // stamps to watch for parent changes when the listeners
5965 // are not present
5966 private long stamp, parentStamp;
5967
5968 @Override
5969 protected Transform computeTransform(Transform reuse) {
5970 stamp++;
5971 updateLocalToParentTransform();
5972
5973 Node parentNode = Node.this.getParent();
5974 if (parentNode != null) {
5975 final LocalToSceneTransformProperty parentProperty =
5976 (LocalToSceneTransformProperty) parentNode.localToSceneTransformProperty();
5977 final Transform parentTransform = parentProperty.getInternalValue();
5978
5979 parentStamp = parentProperty.stamp;
5980
5981 return TransformUtils.immutableTransform(reuse,
5982 parentTransform,
5983 ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
5984 } else {
5985 return TransformUtils.immutableTransform(reuse,
5986 ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
5987 }
5988 }
5989
5990 @Override
5991 public Object getBean() {
5992 return Node.this;
5993 }
5994
5995 @Override
5996 public String getName() {
5997 return "localToSceneTransform";
5998 }
5999
6000 @Override
6001 protected boolean validityKnown() {
6002 return listenerReasons > 0;
6003 }
6004
6005 @Override
6006 protected int computeValidity() {
6007 if (valid != VALIDITY_UNKNOWN) {
6008 return valid;
6009 }
6010
6011 Node n = (Node) getBean();
6012 Node parent = n.getParent();
6013
6014 if (parent != null) {
6015 final LocalToSceneTransformProperty parentProperty =
6016 (LocalToSceneTransformProperty) parent.localToSceneTransformProperty();
6017
6018 if (parentStamp != parentProperty.stamp) {
6019 valid = INVALID;
6020 return INVALID;
6021 }
6022
6023 int parentValid = parentProperty.computeValidity();
6024 if (parentValid == INVALID) {
6025 valid = INVALID;
6026 }
6027 return parentValid;
6028 }
6029
6030 // Validity unknown for root means it is valid
6031 return VALID;
6032 }
6033
6034 @Override
6035 public void addListener(InvalidationListener listener) {
6036 incListenerReasons();
6037 if (localToSceneListeners == null) {
6038 localToSceneListeners = new LinkedList<Object>();
6039 }
6040 localToSceneListeners.add(listener);
6041 super.addListener(listener);
6042 }
6043
6044 @Override
6045 public void addListener(ChangeListener<? super Transform> listener) {
6046 incListenerReasons();
6047 if (localToSceneListeners == null) {
6048 localToSceneListeners = new LinkedList<Object>();
6049 }
6050 localToSceneListeners.add(listener);
6051 super.addListener(listener);
6052 }
6053
6054 @Override
6055 public void removeListener(InvalidationListener listener) {
6056 if (localToSceneListeners != null &&
6057 localToSceneListeners.remove(listener)) {
6058 decListenerReasons();
6059 }
6060 super.removeListener(listener);
6061 }
6062
6063 @Override
6064 public void removeListener(ChangeListener<? super Transform> listener) {
6065 if (localToSceneListeners != null &&
6066 localToSceneListeners.remove(listener)) {
6067 decListenerReasons();
6068 }
6069 super.removeListener(listener);
6070 }
6071 }
6072
6073 public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
6074 if (localToSceneTransform == null) {
6075 localToSceneTransform = new LocalToSceneTransformProperty();
6076 }
6077
6078 return localToSceneTransform;
6079 }
6080
6081 public void invalidateLocalToSceneTransform() {
6082 if (localToSceneTransform != null) {
6083 localToSceneTransform.invalidate();
6084 }
6085 }
6086
6087 public double getTranslateX() {
6088 return (translateX == null) ? DEFAULT_TRANSLATE_X
6089 : translateX.get();
6090 }
6091
6092 public final DoubleProperty translateXProperty() {
6093 if (translateX == null) {
6094 translateX = new StyleableDoubleProperty(DEFAULT_TRANSLATE_X) {
6095 @Override
6096 public void invalidated() {
6097 NodeHelper.transformsChanged(Node.this);
6098 }
6099
6100 @Override
6101 public CssMetaData getCssMetaData() {
6102 return StyleableProperties.TRANSLATE_X;
6103 }
6104
6105 @Override
6106 public Object getBean() {
6107 return Node.this;
6108 }
6109
6110 @Override
6111 public String getName() {
6112 return "translateX";
6113 }
6114 };
6115 }
6116 return translateX;
6117 }
6118
6119 public double getTranslateY() {
6120 return (translateY == null) ? DEFAULT_TRANSLATE_Y : translateY.get();
6121 }
6122
6123 public final DoubleProperty translateYProperty() {
6124 if (translateY == null) {
6125 translateY = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Y) {
6126 @Override
6127 public void invalidated() {
6128 NodeHelper.transformsChanged(Node.this);
6129 }
6130
6131 @Override
6132 public CssMetaData getCssMetaData() {
6133 return StyleableProperties.TRANSLATE_Y;
6134 }
6135
6136 @Override
6137 public Object getBean() {
6138 return Node.this;
6139 }
6140
6141 @Override
6142 public String getName() {
6143 return "translateY";
6144 }
6145 };
6146 }
6147 return translateY;
6148 }
6149
6150 public double getTranslateZ() {
6151 return (translateZ == null) ? DEFAULT_TRANSLATE_Z : translateZ.get();
6152 }
6153
6154 public final DoubleProperty translateZProperty() {
6155 if (translateZ == null) {
6156 translateZ = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Z) {
6157 @Override
6158 public void invalidated() {
6159 NodeHelper.transformsChanged(Node.this);
6160 }
6161
6162 @Override
6163 public CssMetaData getCssMetaData() {
6164 return StyleableProperties.TRANSLATE_Z;
6165 }
6166
6167 @Override
6168 public Object getBean() {
6169 return Node.this;
6170 }
6171
6172 @Override
6173 public String getName() {
6174 return "translateZ";
6175 }
6176 };
6177 }
6178 return translateZ;
6179 }
6180
6181 public double getScaleX() {
6182 return (scaleX == null) ? DEFAULT_SCALE_X : scaleX.get();
6183 }
6184
6185 public final DoubleProperty scaleXProperty() {
6186 if (scaleX == null) {
6187 scaleX = new StyleableDoubleProperty(DEFAULT_SCALE_X) {
6188 @Override
6189 public void invalidated() {
6190 NodeHelper.transformsChanged(Node.this);
6191 }
6192
6193 @Override
6194 public CssMetaData getCssMetaData() {
6195 return StyleableProperties.SCALE_X;
6196 }
6197
6198 @Override
6199 public Object getBean() {
6200 return Node.this;
6201 }
6202
6203 @Override
6204 public String getName() {
6205 return "scaleX";
6206 }
6207 };
6208 }
6209 return scaleX;
6210 }
6211
6212 public double getScaleY() {
6213 return (scaleY == null) ? DEFAULT_SCALE_Y : scaleY.get();
6214 }
6215
6216 public final DoubleProperty scaleYProperty() {
6217 if (scaleY == null) {
6218 scaleY = new StyleableDoubleProperty(DEFAULT_SCALE_Y) {
6219 @Override
6220 public void invalidated() {
6221 NodeHelper.transformsChanged(Node.this);
6222 }
6223
6224 @Override
6225 public CssMetaData getCssMetaData() {
6226 return StyleableProperties.SCALE_Y;
6227 }
6228
6229 @Override
6230 public Object getBean() {
6231 return Node.this;
6232 }
6233
6234 @Override
6235 public String getName() {
6236 return "scaleY";
6237 }
6238 };
6239 }
6240 return scaleY;
6241 }
6242
6243 public double getScaleZ() {
6244 return (scaleZ == null) ? DEFAULT_SCALE_Z : scaleZ.get();
6245 }
6246
6247 public final DoubleProperty scaleZProperty() {
6248 if (scaleZ == null) {
6249 scaleZ = new StyleableDoubleProperty(DEFAULT_SCALE_Z) {
6250 @Override
6251 public void invalidated() {
6252 NodeHelper.transformsChanged(Node.this);
6253 }
6254
6255 @Override
6256 public CssMetaData getCssMetaData() {
6257 return StyleableProperties.SCALE_Z;
6258 }
6259
6260 @Override
6261 public Object getBean() {
6262 return Node.this;
6263 }
6264
6265 @Override
6266 public String getName() {
6267 return "scaleZ";
6268 }
6269 };
6270 }
6271 return scaleZ;
6272 }
6273
6274 public double getRotate() {
6275 return (rotate == null) ? DEFAULT_ROTATE : rotate.get();
6276 }
6277
6278 public final DoubleProperty rotateProperty() {
6279 if (rotate == null) {
6280 rotate = new StyleableDoubleProperty(DEFAULT_ROTATE) {
6281 @Override
6282 public void invalidated() {
6283 NodeHelper.transformsChanged(Node.this);
6284 }
6285
6286 @Override
6287 public CssMetaData getCssMetaData() {
6288 return StyleableProperties.ROTATE;
6289 }
6290
6291 @Override
6292 public Object getBean() {
6293 return Node.this;
6294 }
6295
6296 @Override
6297 public String getName() {
6298 return "rotate";
6299 }
6300 };
6301 }
6302 return rotate;
6303 }
6304
6305 public Point3D getRotationAxis() {
6306 return (rotationAxis == null) ? DEFAULT_ROTATION_AXIS
6307 : rotationAxis.get();
6308 }
6309
6310 public final ObjectProperty<Point3D> rotationAxisProperty() {
6311 if (rotationAxis == null) {
6312 rotationAxis = new ObjectPropertyBase<Point3D>(
6313 DEFAULT_ROTATION_AXIS) {
6314 @Override
6315 protected void invalidated() {
6316 NodeHelper.transformsChanged(Node.this);
6317 }
6318
6319 @Override
6320 public Object getBean() {
6321 return Node.this;
6322 }
6323
6324 @Override
6325 public String getName() {
6326 return "rotationAxis";
6327 }
6328 };
6329 }
6330 return rotationAxis;
6331 }
6332
6333 public ObservableList<Transform> getTransforms() {
6334 if (transforms == null) {
6335 transforms = new TrackableObservableList<Transform>() {
6336 @Override
6337 protected void onChanged(Change<Transform> c) {
6338 while (c.next()) {
6339 for (Transform t : c.getRemoved()) {
6340 TransformHelper.remove(t, Node.this);
6341 }
6342 for (Transform t : c.getAddedSubList()) {
6343 TransformHelper.add(t, Node.this);
6344 }
6345 }
6346
6347 NodeHelper.transformsChanged(Node.this);
6348 }
6349 };
6350 }
6351
6352 return transforms;
6353 }
6354
6355 public boolean canSetTranslateX() {
6356 return (translateX == null) || !translateX.isBound();
6357 }
6358
6359 public boolean canSetTranslateY() {
6360 return (translateY == null) || !translateY.isBound();
6361 }
6362
6363 public boolean canSetTranslateZ() {
6364 return (translateZ == null) || !translateZ.isBound();
6365 }
6366
6367 public boolean canSetScaleX() {
6368 return (scaleX == null) || !scaleX.isBound();
6369 }
6370
6371 public boolean canSetScaleY() {
6372 return (scaleY == null) || !scaleY.isBound();
6373 }
6374
6375 public boolean canSetScaleZ() {
6376 return (scaleZ == null) || !scaleZ.isBound();
6377 }
6378
6379 public boolean canSetRotate() {
6380 return (rotate == null) || !rotate.isBound();
6381 }
6382
6383 public boolean hasTransforms() {
6384 return (transforms != null && !transforms.isEmpty());
6385 }
6386
6387 public boolean hasScaleOrRotate() {
6388 if (scaleX != null && scaleX.get() != DEFAULT_SCALE_X) {
6389 return true;
6390 }
6391 if (scaleY != null && scaleY.get() != DEFAULT_SCALE_Y) {
6392 return true;
6393 }
6394 if (scaleZ != null && scaleZ.get() != DEFAULT_SCALE_Z) {
6395 return true;
6396 }
6397 if (rotate != null && rotate.get() != DEFAULT_ROTATE) {
6398 return true;
6399 }
6400 return false;
6401 }
6402
6403 }
6404
6405 ////////////////////////////
6406 // Private Implementation
6407 ////////////////////////////
6408
6409 /***************************************************************************
6410 * *
6411 * Event Handler Properties *
6412 * *
6413 **************************************************************************/
6414
6415 private EventHandlerProperties eventHandlerProperties;
6416
6417 private EventHandlerProperties getEventHandlerProperties() {
6418 if (eventHandlerProperties == null) {
6419 eventHandlerProperties =
6420 new EventHandlerProperties(
6421 getInternalEventDispatcher().getEventHandlerManager(),
6422 this);
6423 }
6424
6425 return eventHandlerProperties;
6426 }
6427
6428 /***************************************************************************
6429 * *
6430 * Component Orientation Properties *
6431 * *
6432 **************************************************************************/
6433
6434 private ObjectProperty<NodeOrientation> nodeOrientation;
6435 private EffectiveOrientationProperty effectiveNodeOrientationProperty;
6436
6437 private static final byte EFFECTIVE_ORIENTATION_LTR = 0;
6438 private static final byte EFFECTIVE_ORIENTATION_RTL = 1;
6439 private static final byte EFFECTIVE_ORIENTATION_MASK = 1;
6440 private static final byte AUTOMATIC_ORIENTATION_LTR = 0;
6441 private static final byte AUTOMATIC_ORIENTATION_RTL = 2;
6442 private static final byte AUTOMATIC_ORIENTATION_MASK = 2;
6443
6444 private byte resolvedNodeOrientation =
6445 EFFECTIVE_ORIENTATION_LTR | AUTOMATIC_ORIENTATION_LTR;
6446
6447 public final void setNodeOrientation(NodeOrientation orientation) {
6448 nodeOrientationProperty().set(orientation);
6449 }
6450
6451 public final NodeOrientation getNodeOrientation() {
6452 return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get();
6453 }
6454 /**
6455 * Property holding NodeOrientation.
6456 * <p>
6457 * Node orientation describes the flow of visual data within a node.
6458 * In the English speaking world, visual data normally flows from
6459 * left-to-right. In an Arabic or Hebrew world, visual data flows
6460 * from right-to-left. This is consistent with the reading order
6461 * of text in both worlds. The default value is left-to-right.
6462 * </p>
6463 *
6464 * @return NodeOrientation
6465 * @since JavaFX 8.0
6466 */
6467 public final ObjectProperty<NodeOrientation> nodeOrientationProperty() {
6468 if (nodeOrientation == null) {
6469 nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) {
6470 @Override
6471 protected void invalidated() {
6472 nodeResolvedOrientationInvalidated();
6473 }
6474
6475 @Override
6476 public Object getBean() {
6477 return Node.this;
6478 }
6479
6480 @Override
6481 public String getName() {
6482 return "nodeOrientation";
6483 }
6484
6485 @Override
6486 public CssMetaData getCssMetaData() {
6487 //TODO - not supported
6488 throw new UnsupportedOperationException("Not supported yet.");
6489 }
6490
6491 };
6492 }
6493 return nodeOrientation;
6494 }
6495
6496 public final NodeOrientation getEffectiveNodeOrientation() {
6497 return (getEffectiveOrientation(resolvedNodeOrientation)
6498 == EFFECTIVE_ORIENTATION_LTR)
6499 ? NodeOrientation.LEFT_TO_RIGHT
6500 : NodeOrientation.RIGHT_TO_LEFT;
6501 }
6502
6503 /**
6504 * The effective orientation of a node resolves the inheritance of
6505 * node orientation, returning either left-to-right or right-to-left.
6506 * @return the node orientation for this {@code Node}
6507 * @since JavaFX 8.0
6508 */
6509 public final ReadOnlyObjectProperty<NodeOrientation>
6510 effectiveNodeOrientationProperty() {
6511 if (effectiveNodeOrientationProperty == null) {
6512 effectiveNodeOrientationProperty =
6513 new EffectiveOrientationProperty();
6514 }
6515
6516 return effectiveNodeOrientationProperty;
6517 }
6518
6519 /**
6520 * Determines whether a node should be mirrored when node orientation
6521 * is right-to-left.
6522 * <p>
6523 * When a node is mirrored, the origin is automatically moved to the
6524 * top right corner causing the node to layout children and draw from
6525 * right to left using a mirroring transformation. Some nodes may wish
6526 * to draw from right to left without using a transformation. These
6527 * nodes will will answer {@code false} and implement right-to-left
6528 * orientation without using the automatic transformation.
6529 * </p>
6530 * @return true if this {@code Node} should be mirrored
6531 * @since JavaFX 8.0
6532 */
6533 public boolean usesMirroring() {
6534 return true;
6535 }
6536
6537 final void parentResolvedOrientationInvalidated() {
6538 if (getNodeOrientation() == NodeOrientation.INHERIT) {
6539 nodeResolvedOrientationInvalidated();
6540 } else {
6541 // mirroring changed
6542 NodeHelper.transformsChanged(this);
6543 }
6544 }
6545
6546 final void nodeResolvedOrientationInvalidated() {
6547 final byte oldResolvedNodeOrientation =
6548 resolvedNodeOrientation;
6549
6550 resolvedNodeOrientation =
6551 (byte) (calcEffectiveNodeOrientation()
6552 | calcAutomaticNodeOrientation());
6553
6554 if ((effectiveNodeOrientationProperty != null)
6555 && (getEffectiveOrientation(resolvedNodeOrientation)
6556 != getEffectiveOrientation(
6557 oldResolvedNodeOrientation))) {
6558 effectiveNodeOrientationProperty.invalidate();
6559 }
6560
6561 // mirroring changed
6562 NodeHelper.transformsChanged(this);
6563
6564 if (resolvedNodeOrientation != oldResolvedNodeOrientation) {
6565 nodeResolvedOrientationChanged();
6566 }
6567 }
6568
6569 void nodeResolvedOrientationChanged() {
6570 // overriden in Parent
6571 }
6572
6573 private Node getMirroringOrientationParent() {
6574 Node parentValue = getParent();
6575 while (parentValue != null) {
6576 if (parentValue.usesMirroring()) {
6577 return parentValue;
6578 }
6579 parentValue = parentValue.getParent();
6580 }
6581
6582 final Node subSceneValue = getSubScene();
6583 if (subSceneValue != null) {
6584 return subSceneValue;
6585 }
6586
6587 return null;
6588 }
6589
6590 private Node getOrientationParent() {
6591 final Node parentValue = getParent();
6592 if (parentValue != null) {
6593 return parentValue;
6594 }
6595
6596 final Node subSceneValue = getSubScene();
6597 if (subSceneValue != null) {
6598 return subSceneValue;
6599 }
6600
6601 return null;
6602 }
6603
6604 private byte calcEffectiveNodeOrientation() {
6605 final NodeOrientation nodeOrientationValue = getNodeOrientation();
6606 if (nodeOrientationValue != NodeOrientation.INHERIT) {
6607 return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
6608 ? EFFECTIVE_ORIENTATION_LTR
6609 : EFFECTIVE_ORIENTATION_RTL;
6610 }
6611
6612 final Node parentValue = getOrientationParent();
6613 if (parentValue != null) {
6614 return getEffectiveOrientation(parentValue.resolvedNodeOrientation);
6615 }
6616
6617 final Scene sceneValue = getScene();
6618 if (sceneValue != null) {
6619 return (sceneValue.getEffectiveNodeOrientation()
6620 == NodeOrientation.LEFT_TO_RIGHT)
6621 ? EFFECTIVE_ORIENTATION_LTR
6622 : EFFECTIVE_ORIENTATION_RTL;
6623 }
6624
6625 return EFFECTIVE_ORIENTATION_LTR;
6626 }
6627
6628 private byte calcAutomaticNodeOrientation() {
6629 if (!usesMirroring()) {
6630 return AUTOMATIC_ORIENTATION_LTR;
6631 }
6632
6633 final NodeOrientation nodeOrientationValue = getNodeOrientation();
6634 if (nodeOrientationValue != NodeOrientation.INHERIT) {
6635 return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
6636 ? AUTOMATIC_ORIENTATION_LTR
6637 : AUTOMATIC_ORIENTATION_RTL;
6638 }
6639
6640 final Node parentValue = getMirroringOrientationParent();
6641 if (parentValue != null) {
6642 // automatic node orientation is inherited
6643 return getAutomaticOrientation(parentValue.resolvedNodeOrientation);
6644 }
6645
6646 final Scene sceneValue = getScene();
6647 if (sceneValue != null) {
6648 return (sceneValue.getEffectiveNodeOrientation()
6649 == NodeOrientation.LEFT_TO_RIGHT)
6650 ? AUTOMATIC_ORIENTATION_LTR
6651 : AUTOMATIC_ORIENTATION_RTL;
6652 }
6653
6654 return AUTOMATIC_ORIENTATION_LTR;
6655 }
6656
6657 // Return true if the node needs to be mirrored.
6658 // A node has mirroring if the orientation differs from the parent
6659 // package private for testing
6660 final boolean hasMirroring() {
6661 final Node parentValue = getOrientationParent();
6662
6663 final byte thisOrientation =
6664 getAutomaticOrientation(resolvedNodeOrientation);
6665 final byte parentOrientation =
6666 (parentValue != null)
6667 ? getAutomaticOrientation(
6668 parentValue.resolvedNodeOrientation)
6669 : AUTOMATIC_ORIENTATION_LTR;
6670
6671 return thisOrientation != parentOrientation;
6672 }
6673
6674 private static byte getEffectiveOrientation(
6675 final byte resolvedNodeOrientation) {
6676 return (byte) (resolvedNodeOrientation & EFFECTIVE_ORIENTATION_MASK);
6677 }
6678
6679 private static byte getAutomaticOrientation(
6680 final byte resolvedNodeOrientation) {
6681 return (byte) (resolvedNodeOrientation & AUTOMATIC_ORIENTATION_MASK);
6682 }
6683
6684 private final class EffectiveOrientationProperty
6685 extends ReadOnlyObjectPropertyBase<NodeOrientation> {
6686 @Override
6687 public NodeOrientation get() {
6688 return getEffectiveNodeOrientation();
6689 }
6690
6691 @Override
6692 public Object getBean() {
6693 return Node.this;
6694 }
6695
6696 @Override
6697 public String getName() {
6698 return "effectiveNodeOrientation";
6699 }
6700
6701 public void invalidate() {
6702 fireValueChangedEvent();
6703 }
6704 }
6705
6706 /***************************************************************************
6707 * *
6708 * Misc Seldom Used Properties *
6709 * *
6710 **************************************************************************/
6711
6712 private MiscProperties miscProperties;
6713
6714 private MiscProperties getMiscProperties() {
6715 if (miscProperties == null) {
6716 miscProperties = new MiscProperties();
6717 }
6718
6719 return miscProperties;
6720 }
6721
6722 private static final double DEFAULT_VIEW_ORDER = 0;
6723 private static final boolean DEFAULT_CACHE = false;
6724 private static final CacheHint DEFAULT_CACHE_HINT = CacheHint.DEFAULT;
6725 private static final Node DEFAULT_CLIP = null;
6726 private static final Cursor DEFAULT_CURSOR = null;
6727 private static final DepthTest DEFAULT_DEPTH_TEST = DepthTest.INHERIT;
6728 private static final boolean DEFAULT_DISABLE = false;
6729 private static final Effect DEFAULT_EFFECT = null;
6730 private static final InputMethodRequests DEFAULT_INPUT_METHOD_REQUESTS =
6731 null;
6732 private static final boolean DEFAULT_MOUSE_TRANSPARENT = false;
6733
6734 private final class MiscProperties {
6735 private LazyBoundsProperty boundsInParent;
6736 private LazyBoundsProperty boundsInLocal;
6737 private BooleanProperty cache;
6738 private ObjectProperty<CacheHint> cacheHint;
6739 private ObjectProperty<Node> clip;
6740 private ObjectProperty<Cursor> cursor;
6741 private ObjectProperty<DepthTest> depthTest;
6742 private BooleanProperty disable;
6743 private ObjectProperty<Effect> effect;
6744 private ObjectProperty<InputMethodRequests> inputMethodRequests;
6745 private BooleanProperty mouseTransparent;
6746 private DoubleProperty viewOrder;
6747
6748 public double getViewOrder() {
6749 return (viewOrder == null) ? DEFAULT_VIEW_ORDER : viewOrder.get();
6750 }
6751
6752 public final DoubleProperty viewOrderProperty() {
6753 if (viewOrder == null) {
6754 viewOrder = new StyleableDoubleProperty(DEFAULT_VIEW_ORDER) {
6755 @Override
6756 public void invalidated() {
6757 Parent p = getParent();
6758 if (p != null) {
6759 // Parent will be responsible to update sorted children list
6760 p.markViewOrderChildrenDirty();
6761 }
6762 NodeHelper.markDirty(Node.this, DirtyBits.NODE_VIEW_ORDER);
6763 }
6764
6765 @Override
6766 public CssMetaData getCssMetaData() {
6767 return StyleableProperties.VIEW_ORDER;
6768 }
6769
6770 @Override
6771 public Object getBean() {
6772 return Node.this;
6773 }
6774
6775 @Override
6776 public String getName() {
6777 return "viewOrder";
6778 }
6779 };
6780 }
6781 return viewOrder;
6782 }
6783
6784 public final Bounds getBoundsInParent() {
6785 return boundsInParentProperty().get();
6786 }
6787
6788 public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
6789 if (boundsInParent == null) {
6790 boundsInParent = new LazyBoundsProperty() {
6791 /**
6792 * Computes the bounds including the clip, effects, and all
6793 * transforms. This function is essentially how to compute
6794 * the boundsInParent. Optimizations are made to compute as
6795 * little as possible and create as little trash as
6796 * possible.
6797 */
6798 @Override
6799 protected Bounds computeBounds() {
6800 BaseBounds tempBounds = TempState.getInstance().bounds;
6801 tempBounds = getTransformedBounds(
6802 tempBounds,
6803 BaseTransform.IDENTITY_TRANSFORM);
6804 return new BoundingBox(tempBounds.getMinX(),
6805 tempBounds.getMinY(),
6806 tempBounds.getMinZ(),
6807 tempBounds.getWidth(),
6808 tempBounds.getHeight(),
6809 tempBounds.getDepth());
6810 }
6811
6812 @Override
6813 public Object getBean() {
6814 return Node.this;
6815 }
6816
6817 @Override
6818 public String getName() {
6819 return "boundsInParent";
6820 }
6821 };
6822 }
6823
6824 return boundsInParent;
6825 }
6826
6827 public void invalidateBoundsInParent() {
6828 if (boundsInParent != null) {
6829 boundsInParent.invalidate();
6830 }
6831 }
6832
6833 public final Bounds getBoundsInLocal() {
6834 return boundsInLocalProperty().get();
6835 }
6836
6837 public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
6838 if (boundsInLocal == null) {
6839 boundsInLocal = new LazyBoundsProperty() {
6840 @Override
6841 protected Bounds computeBounds() {
6842 BaseBounds tempBounds = TempState.getInstance().bounds;
6843 tempBounds = getLocalBounds(
6844 tempBounds,
6845 BaseTransform.IDENTITY_TRANSFORM);
6846 return new BoundingBox(tempBounds.getMinX(),
6847 tempBounds.getMinY(),
6848 tempBounds.getMinZ(),
6849 tempBounds.getWidth(),
6850 tempBounds.getHeight(),
6851 tempBounds.getDepth());
6852 }
6853
6854 @Override
6855 public Object getBean() {
6856 return Node.this;
6857 }
6858
6859 @Override
6860 public String getName() {
6861 return "boundsInLocal";
6862 }
6863 };
6864 }
6865
6866 return boundsInLocal;
6867 }
6868
6869 public void invalidateBoundsInLocal() {
6870 if (boundsInLocal != null) {
6871 boundsInLocal.invalidate();
6872 }
6873 }
6874
6875 public final boolean isCache() {
6876 return (cache == null) ? DEFAULT_CACHE
6877 : cache.get();
6878 }
6879
6880 public final BooleanProperty cacheProperty() {
6881 if (cache == null) {
6882 cache = new BooleanPropertyBase(DEFAULT_CACHE) {
6883 @Override
6884 protected void invalidated() {
6885 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE);
6886 }
6887
6888 @Override
6889 public Object getBean() {
6890 return Node.this;
6891 }
6892
6893 @Override
6894 public String getName() {
6895 return "cache";
6896 }
6897 };
6898 }
6899 return cache;
6900 }
6901
6902 public final CacheHint getCacheHint() {
6903 return (cacheHint == null) ? DEFAULT_CACHE_HINT
6904 : cacheHint.get();
6905 }
6906
6907 public final ObjectProperty<CacheHint> cacheHintProperty() {
6908 if (cacheHint == null) {
6909 cacheHint = new ObjectPropertyBase<CacheHint>(DEFAULT_CACHE_HINT) {
6910 @Override
6911 protected void invalidated() {
6912 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CACHE);
6913 }
6914
6915 @Override
6916 public Object getBean() {
6917 return Node.this;
6918 }
6919
6920 @Override
6921 public String getName() {
6922 return "cacheHint";
6923 }
6924 };
6925 }
6926 return cacheHint;
6927 }
6928
6929 public final Node getClip() {
6930 return (clip == null) ? DEFAULT_CLIP : clip.get();
6931 }
6932
6933 public final ObjectProperty<Node> clipProperty() {
6934 if (clip == null) {
6935 clip = new ObjectPropertyBase<Node>(DEFAULT_CLIP) {
6936
6937 //temp variables used when clip was invalid to rollback to
6938 // last value
6939 private Node oldClip;
6940
6941 @Override
6942 protected void invalidated() {
6943 final Node newClip = get();
6944 if ((newClip != null)
6945 && ((newClip.isConnected()
6946 && newClip.clipParent != Node.this)
6947 || wouldCreateCycle(Node.this,
6948 newClip))) {
6949 // Assigning this node to clip is illegal.
6950 // Roll back to the previous state and throw an
6951 // exception.
6952 final String cause =
6953 newClip.isConnected()
6954 && (newClip.clipParent != Node.this)
6955 ? "node already connected"
6956 : "cycle detected";
6957
6958 if (isBound()) {
6959 unbind();
6960 set(oldClip);
6961 throw new IllegalArgumentException(
6962 "Node's clip set to incorrect value "
6963 + " through binding"
6964 + " (" + cause + ", node = "
6965 + Node.this + ", clip = "
6966 + clip + ")."
6967 + " Binding has been removed.");
6968 } else {
6969 set(oldClip);
6970 throw new IllegalArgumentException(
6971 "Node's clip set to incorrect value"
6972 + " (" + cause + ", node = "
6973 + Node.this + ", clip = "
6974 + clip + ").");
6975 }
6976 } else {
6977 if (oldClip != null) {
6978 oldClip.clipParent = null;
6979 oldClip.setScenes(null, null);
6980 oldClip.updateTreeVisible(false);
6981 }
6982
6983 if (newClip != null) {
6984 newClip.clipParent = Node.this;
6985 newClip.setScenes(getScene(), getSubScene());
6986 newClip.updateTreeVisible(true);
6987 }
6988
6989 NodeHelper.markDirty(Node.this, DirtyBits.NODE_CLIP);
6990
6991 // the local bounds have (probably) changed
6992 localBoundsChanged();
6993
6994 oldClip = newClip;
6995 }
6996 }
6997
6998 @Override
6999 public Object getBean() {
7000 return Node.this;
7001 }
7002
7003 @Override
7004 public String getName() {
7005 return "clip";
7006 }
7007 };
7008 }
7009 return clip;
7010 }
7011
7012 public final Cursor getCursor() {
7013 return (cursor == null) ? DEFAULT_CURSOR : cursor.get();
7014 }
7015
7016 public final ObjectProperty<Cursor> cursorProperty() {
7017 if (cursor == null) {
7018 cursor = new StyleableObjectProperty<Cursor>(DEFAULT_CURSOR) {
7019
7020 @Override
7021 protected void invalidated() {
7022 final Scene sceneValue = getScene();
7023 if (sceneValue != null) {
7024 sceneValue.markCursorDirty();
7025 }
7026 }
7027
7028 @Override
7029 public CssMetaData getCssMetaData() {
7030 return StyleableProperties.CURSOR;
7031 }
7032
7033 @Override
7034 public Object getBean() {
7035 return Node.this;
7036 }
7037
7038 @Override
7039 public String getName() {
7040 return "cursor";
7041 }
7042
7043 };
7044 }
7045 return cursor;
7046 }
7047
7048 public final DepthTest getDepthTest() {
7049 return (depthTest == null) ? DEFAULT_DEPTH_TEST
7050 : depthTest.get();
7051 }
7052
7053 public final ObjectProperty<DepthTest> depthTestProperty() {
7054 if (depthTest == null) {
7055 depthTest = new ObjectPropertyBase<DepthTest>(DEFAULT_DEPTH_TEST) {
7056 @Override protected void invalidated() {
7057 computeDerivedDepthTest();
7058 }
7059
7060 @Override
7061 public Object getBean() {
7062 return Node.this;
7063 }
7064
7065 @Override
7066 public String getName() {
7067 return "depthTest";
7068 }
7069 };
7070 }
7071 return depthTest;
7072 }
7073
7074 public final boolean isDisable() {
7075 return (disable == null) ? DEFAULT_DISABLE : disable.get();
7076 }
7077
7078 public final BooleanProperty disableProperty() {
7079 if (disable == null) {
7080 disable = new BooleanPropertyBase(DEFAULT_DISABLE) {
7081 @Override
7082 protected void invalidated() {
7083 updateDisabled();
7084 }
7085
7086 @Override
7087 public Object getBean() {
7088 return Node.this;
7089 }
7090
7091 @Override
7092 public String getName() {
7093 return "disable";
7094 }
7095 };
7096 }
7097 return disable;
7098 }
7099
7100 public final Effect getEffect() {
7101 return (effect == null) ? DEFAULT_EFFECT : effect.get();
7102 }
7103
7104 public final ObjectProperty<Effect> effectProperty() {
7105 if (effect == null) {
7106 effect = new StyleableObjectProperty<Effect>(DEFAULT_EFFECT) {
7107 private Effect oldEffect = null;
7108 private int oldBits;
7109
7110 private final AbstractNotifyListener effectChangeListener =
7111 new AbstractNotifyListener() {
7112
7113 @Override
7114 public void invalidated(Observable valueModel) {
7115 int newBits = ((IntegerProperty) valueModel).get();
7116 int changedBits = newBits ^ oldBits;
7117 oldBits = newBits;
7118 if (EffectDirtyBits.isSet(
7119 changedBits,
7120 EffectDirtyBits.EFFECT_DIRTY)
7121 && EffectDirtyBits.isSet(
7122 newBits,
7123 EffectDirtyBits.EFFECT_DIRTY)) {
7124 NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT);
7125 }
7126 if (EffectDirtyBits.isSet(
7127 changedBits,
7128 EffectDirtyBits.BOUNDS_CHANGED)) {
7129 localBoundsChanged();
7130 }
7131 }
7132 };
7133
7134 @Override
7135 protected void invalidated() {
7136 Effect _effect = get();
7137 if (oldEffect != null) {
7138 EffectHelper.effectDirtyProperty(oldEffect).removeListener(
7139 effectChangeListener.getWeakListener());
7140 }
7141 oldEffect = _effect;
7142 if (_effect != null) {
7143 EffectHelper.effectDirtyProperty(_effect)
7144 .addListener(
7145 effectChangeListener.getWeakListener());
7146 if (EffectHelper.isEffectDirty(_effect)) {
7147 NodeHelper.markDirty(Node.this, DirtyBits.EFFECT_EFFECT);
7148 }
7149 oldBits = EffectHelper.effectDirtyProperty(_effect).get();
7150 }
7151
7152 NodeHelper.markDirty(Node.this, DirtyBits.NODE_EFFECT);
7153 // bounds may have changed regardless whether
7154 // the dirty flag on effect is set
7155 localBoundsChanged();
7156 }
7157
7158 @Override
7159 public CssMetaData getCssMetaData() {
7160 return StyleableProperties.EFFECT;
7161 }
7162
7163 @Override
7164 public Object getBean() {
7165 return Node.this;
7166 }
7167
7168 @Override
7169 public String getName() {
7170 return "effect";
7171 }
7172 };
7173 }
7174 return effect;
7175 }
7176
7177 public final InputMethodRequests getInputMethodRequests() {
7178 return (inputMethodRequests == null) ? DEFAULT_INPUT_METHOD_REQUESTS
7179 : inputMethodRequests.get();
7180 }
7181
7182 public ObjectProperty<InputMethodRequests>
7183 inputMethodRequestsProperty() {
7184 if (inputMethodRequests == null) {
7185 inputMethodRequests =
7186 new SimpleObjectProperty<InputMethodRequests>(
7187 Node.this,
7188 "inputMethodRequests",
7189 DEFAULT_INPUT_METHOD_REQUESTS);
7190 }
7191 return inputMethodRequests;
7192 }
7193
7194 public final boolean isMouseTransparent() {
7195 return (mouseTransparent == null) ? DEFAULT_MOUSE_TRANSPARENT
7196 : mouseTransparent.get();
7197 }
7198
7199 public final BooleanProperty mouseTransparentProperty() {
7200 if (mouseTransparent == null) {
7201 mouseTransparent =
7202 new SimpleBooleanProperty(
7203 Node.this,
7204 "mouseTransparent",
7205 DEFAULT_MOUSE_TRANSPARENT);
7206 }
7207 return mouseTransparent;
7208 }
7209
7210 public boolean canSetCursor() {
7211 return (cursor == null) || !cursor.isBound();
7212 }
7213
7214 public boolean canSetEffect() {
7215 return (effect == null) || !effect.isBound();
7216 }
7217 }
7218
7219 /* *************************************************************************
7220 * *
7221 * Mouse Handling *
7222 * *
7223 **************************************************************************/
7224
7225 public final void setMouseTransparent(boolean value) {
7226 mouseTransparentProperty().set(value);
7227 }
7228
7229 public final boolean isMouseTransparent() {
7230 return (miscProperties == null) ? DEFAULT_MOUSE_TRANSPARENT
7231 : miscProperties.isMouseTransparent();
7232 }
7233
7234 /**
7235 * If {@code true}, this node (together with all its children) is completely
7236 * transparent to mouse events. When choosing target for mouse event, nodes
7237 * with {@code mouseTransparent} set to {@code true} and their subtrees
7238 * won't be taken into account.
7239 * @return is this {@code Node} (together with all its children) is completely
7240 * transparent to mouse events.
7241 */
7242 public final BooleanProperty mouseTransparentProperty() {
7243 return getMiscProperties().mouseTransparentProperty();
7244 }
7245
7246 /**
7247 * Whether or not this {@code Node} is being hovered over. Typically this is
7248 * due to the mouse being over the node, though it could be due to a pen
7249 * hovering on a graphics tablet or other form of input.
7250 *
7251 * <p>Note that current implementation of hover relies on mouse enter and
7252 * exit events to determine whether this Node is in the hover state; this
7253 * means that this feature is currently supported only on systems that
7254 * have a mouse. Future implementations may provide alternative means of
7255 * supporting hover.
7256 *
7257 * @defaultValue false
7258 */
7259 private ReadOnlyBooleanWrapper hover;
7260
7261 protected final void setHover(boolean value) {
7262 hoverPropertyImpl().set(value);
7263 }
7264
7265 public final boolean isHover() {
7266 return hover == null ? false : hover.get();
7267 }
7268
7269 public final ReadOnlyBooleanProperty hoverProperty() {
7270 return hoverPropertyImpl().getReadOnlyProperty();
7271 }
7272
7273 private ReadOnlyBooleanWrapper hoverPropertyImpl() {
7274 if (hover == null) {
7275 hover = new ReadOnlyBooleanWrapper() {
7276
7277 @Override
7278 protected void invalidated() {
7279 PlatformLogger logger = Logging.getInputLogger();
7280 if (logger.isLoggable(Level.FINER)) {
7281 logger.finer(this + " hover=" + get());
7282 }
7283 pseudoClassStateChanged(HOVER_PSEUDOCLASS_STATE, get());
7284 }
7285
7286 @Override
7287 public Object getBean() {
7288 return Node.this;
7289 }
7290
7291 @Override
7292 public String getName() {
7293 return "hover";
7294 }
7295 };
7296 }
7297 return hover;
7298 }
7299
7300 /**
7301 * Whether or not the {@code Node} is pressed. Typically this is true when
7302 * the primary mouse button is down, though subclasses may define other
7303 * mouse button state or key state to cause the node to be "pressed".
7304 *
7305 * @defaultValue false
7306 */
7307 private ReadOnlyBooleanWrapper pressed;
7308
7309 protected final void setPressed(boolean value) {
7310 pressedPropertyImpl().set(value);
7311 }
7312
7313 public final boolean isPressed() {
7314 return pressed == null ? false : pressed.get();
7315 }
7316
7317 public final ReadOnlyBooleanProperty pressedProperty() {
7318 return pressedPropertyImpl().getReadOnlyProperty();
7319 }
7320
7321 private ReadOnlyBooleanWrapper pressedPropertyImpl() {
7322 if (pressed == null) {
7323 pressed = new ReadOnlyBooleanWrapper() {
7324
7325 @Override
7326 protected void invalidated() {
7327 PlatformLogger logger = Logging.getInputLogger();
7328 if (logger.isLoggable(Level.FINER)) {
7329 logger.finer(this + " pressed=" + get());
7330 }
7331 pseudoClassStateChanged(PRESSED_PSEUDOCLASS_STATE, get());
7332 }
7333
7334 @Override
7335 public Object getBean() {
7336 return Node.this;
7337 }
7338
7339 @Override
7340 public String getName() {
7341 return "pressed";
7342 }
7343 };
7344 }
7345 return pressed;
7346 }
7347
7348 public final void setOnContextMenuRequested(
7349 EventHandler<? super ContextMenuEvent> value) {
7350 onContextMenuRequestedProperty().set(value);
7351 }
7352
7353 public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() {
7354 return (eventHandlerProperties == null)
7355 ? null : eventHandlerProperties.onContextMenuRequested();
7356 }
7357
7358 /**
7359 * Defines a function to be called when a context menu
7360 * has been requested on this {@code Node}.
7361 * @return the event handler that is called when a context menu has been
7362 * requested on this {@code Node}
7363 * @since JavaFX 2.1
7364 */
7365 public final ObjectProperty<EventHandler<? super ContextMenuEvent>>
7366 onContextMenuRequestedProperty() {
7367 return getEventHandlerProperties().onContextMenuRequestedProperty();
7368 }
7369
7370 public final void setOnMouseClicked(
7371 EventHandler<? super MouseEvent> value) {
7372 onMouseClickedProperty().set(value);
7373 }
7374
7375 public final EventHandler<? super MouseEvent> getOnMouseClicked() {
7376 return (eventHandlerProperties == null)
7377 ? null : eventHandlerProperties.getOnMouseClicked();
7378 }
7379
7380 /**
7381 * Defines a function to be called when a mouse button has been clicked
7382 * (pressed and released) on this {@code Node}.
7383 * @return the event handler that is called when a mouse button has been
7384 * clicked (pressed and released) on this {@code Node}
7385 */
7386 public final ObjectProperty<EventHandler<? super MouseEvent>>
7387 onMouseClickedProperty() {
7388 return getEventHandlerProperties().onMouseClickedProperty();
7389 }
7390
7391 public final void setOnMouseDragged(
7392 EventHandler<? super MouseEvent> value) {
7393 onMouseDraggedProperty().set(value);
7394 }
7395
7396 public final EventHandler<? super MouseEvent> getOnMouseDragged() {
7397 return (eventHandlerProperties == null)
7398 ? null : eventHandlerProperties.getOnMouseDragged();
7399 }
7400
7401 /**
7402 * Defines a function to be called when a mouse button is pressed
7403 * on this {@code Node} and then dragged.
7404 * @return the event handler that is called when a mouse button is pressed
7405 * on this {@code Node} and then dragged
7406 */
7407 public final ObjectProperty<EventHandler<? super MouseEvent>>
7408 onMouseDraggedProperty() {
7409 return getEventHandlerProperties().onMouseDraggedProperty();
7410 }
7411
7412 public final void setOnMouseEntered(
7413 EventHandler<? super MouseEvent> value) {
7414 onMouseEnteredProperty().set(value);
7415 }
7416
7417 public final EventHandler<? super MouseEvent> getOnMouseEntered() {
7418 return (eventHandlerProperties == null)
7419 ? null : eventHandlerProperties.getOnMouseEntered();
7420 }
7421
7422 /**
7423 * Defines a function to be called when the mouse enters this {@code Node}.
7424 * @return the event handler that is called when a mouse enters this
7425 * {@code Node}
7426 */
7427 public final ObjectProperty<EventHandler<? super MouseEvent>>
7428 onMouseEnteredProperty() {
7429 return getEventHandlerProperties().onMouseEnteredProperty();
7430 }
7431
7432 public final void setOnMouseExited(
7433 EventHandler<? super MouseEvent> value) {
7434 onMouseExitedProperty().set(value);
7435 }
7436
7437 public final EventHandler<? super MouseEvent> getOnMouseExited() {
7438 return (eventHandlerProperties == null)
7439 ? null : eventHandlerProperties.getOnMouseExited();
7440 }
7441
7442 /**
7443 * Defines a function to be called when the mouse exits this {@code Node}.
7444 * @return the event handler that is called when a mouse exits this
7445 * {@code Node}
7446 */
7447 public final ObjectProperty<EventHandler<? super MouseEvent>>
7448 onMouseExitedProperty() {
7449 return getEventHandlerProperties().onMouseExitedProperty();
7450 }
7451
7452 public final void setOnMouseMoved(
7453 EventHandler<? super MouseEvent> value) {
7454 onMouseMovedProperty().set(value);
7455 }
7456
7457 public final EventHandler<? super MouseEvent> getOnMouseMoved() {
7458 return (eventHandlerProperties == null)
7459 ? null : eventHandlerProperties.getOnMouseMoved();
7460 }
7461
7462 /**
7463 * Defines a function to be called when mouse cursor moves within
7464 * this {@code Node} but no buttons have been pushed.
7465 * @return the event handler that is called when a mouse cursor moves
7466 * within this {@code Node} but no buttons have been pushed
7467 */
7468 public final ObjectProperty<EventHandler<? super MouseEvent>>
7469 onMouseMovedProperty() {
7470 return getEventHandlerProperties().onMouseMovedProperty();
7471 }
7472
7473 public final void setOnMousePressed(
7474 EventHandler<? super MouseEvent> value) {
7475 onMousePressedProperty().set(value);
7476 }
7477
7478 public final EventHandler<? super MouseEvent> getOnMousePressed() {
7479 return (eventHandlerProperties == null)
7480 ? null : eventHandlerProperties.getOnMousePressed();
7481 }
7482
7483 /**
7484 * Defines a function to be called when a mouse button
7485 * has been pressed on this {@code Node}.
7486 * @return the event handler that is called when a mouse button has been
7487 * pressed on this {@code Node}
7488 */
7489 public final ObjectProperty<EventHandler<? super MouseEvent>>
7490 onMousePressedProperty() {
7491 return getEventHandlerProperties().onMousePressedProperty();
7492 }
7493
7494 public final void setOnMouseReleased(
7495 EventHandler<? super MouseEvent> value) {
7496 onMouseReleasedProperty().set(value);
7497 }
7498
7499 public final EventHandler<? super MouseEvent> getOnMouseReleased() {
7500 return (eventHandlerProperties == null)
7501 ? null : eventHandlerProperties.getOnMouseReleased();
7502 }
7503
7504 /**
7505 * Defines a function to be called when a mouse button
7506 * has been released on this {@code Node}.
7507 * @return the event handler that is called when a mouse button has been
7508 * released on this {@code Node}
7509 */
7510 public final ObjectProperty<EventHandler<? super MouseEvent>>
7511 onMouseReleasedProperty() {
7512 return getEventHandlerProperties().onMouseReleasedProperty();
7513 }
7514
7515 public final void setOnDragDetected(
7516 EventHandler<? super MouseEvent> value) {
7517 onDragDetectedProperty().set(value);
7518 }
7519
7520 public final EventHandler<? super MouseEvent> getOnDragDetected() {
7521 return (eventHandlerProperties == null)
7522 ? null : eventHandlerProperties.getOnDragDetected();
7523 }
7524
7525 /**
7526 * Defines a function to be called when drag gesture has been
7527 * detected. This is the right place to start drag and drop operation.
7528 * @return the event handler that is called when drag gesture has been
7529 * detected
7530 */
7531 public final ObjectProperty<EventHandler<? super MouseEvent>>
7532 onDragDetectedProperty() {
7533 return getEventHandlerProperties().onDragDetectedProperty();
7534 }
7535
7536 public final void setOnMouseDragOver(
7537 EventHandler<? super MouseDragEvent> value) {
7538 onMouseDragOverProperty().set(value);
7539 }
7540
7541 public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
7542 return (eventHandlerProperties == null)
7543 ? null : eventHandlerProperties.getOnMouseDragOver();
7544 }
7545
7546 /**
7547 * Defines a function to be called when a full press-drag-release gesture
7548 * progresses within this {@code Node}.
7549 * @return the event handler that is called when a full press-drag-release
7550 * gesture progresses within this {@code Node}
7551 * @since JavaFX 2.1
7552 */
7553 public final ObjectProperty<EventHandler<? super MouseDragEvent>>
7554 onMouseDragOverProperty() {
7555 return getEventHandlerProperties().onMouseDragOverProperty();
7556 }
7557
7558 public final void setOnMouseDragReleased(
7559 EventHandler<? super MouseDragEvent> value) {
7560 onMouseDragReleasedProperty().set(value);
7561 }
7562
7563 public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
7564 return (eventHandlerProperties == null)
7565 ? null : eventHandlerProperties.getOnMouseDragReleased();
7566 }
7567
7568 /**
7569 * Defines a function to be called when a full press-drag-release gesture
7570 * ends (by releasing mouse button) within this {@code Node}.
7571 * @return the event handler that is called when a full press-drag-release
7572 * gesture ends (by releasing mouse button) within this {@code Node}
7573 * @since JavaFX 2.1
7574 */
7575 public final ObjectProperty<EventHandler<? super MouseDragEvent>>
7576 onMouseDragReleasedProperty() {
7577 return getEventHandlerProperties().onMouseDragReleasedProperty();
7578 }
7579
7580 public final void setOnMouseDragEntered(
7581 EventHandler<? super MouseDragEvent> value) {
7582 onMouseDragEnteredProperty().set(value);
7583 }
7584
7585 public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
7586 return (eventHandlerProperties == null)
7587 ? null : eventHandlerProperties.getOnMouseDragEntered();
7588 }
7589
7590 /**
7591 * Defines a function to be called when a full press-drag-release gesture
7592 * enters this {@code Node}.
7593 * @return the event handler that is called when a full press-drag-release
7594 * gesture enters this {@code Node}
7595 * @since JavaFX 2.1
7596 */
7597 public final ObjectProperty<EventHandler<? super MouseDragEvent>>
7598 onMouseDragEnteredProperty() {
7599 return getEventHandlerProperties().onMouseDragEnteredProperty();
7600 }
7601
7602 public final void setOnMouseDragExited(
7603 EventHandler<? super MouseDragEvent> value) {
7604 onMouseDragExitedProperty().set(value);
7605 }
7606
7607 public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
7608 return (eventHandlerProperties == null)
7609 ? null : eventHandlerProperties.getOnMouseDragExited();
7610 }
7611
7612 /**
7613 * Defines a function to be called when a full press-drag-release gesture
7614 * leaves this {@code Node}.
7615 * @return the event handler that is called when a full press-drag-release
7616 * gesture leaves this {@code Node}
7617 * @since JavaFX 2.1
7618 */
7619 public final ObjectProperty<EventHandler<? super MouseDragEvent>>
7620 onMouseDragExitedProperty() {
7621 return getEventHandlerProperties().onMouseDragExitedProperty();
7622 }
7623
7624
7625 /* *************************************************************************
7626 * *
7627 * Gestures Handling *
7628 * *
7629 **************************************************************************/
7630
7631 public final void setOnScrollStarted(
7632 EventHandler<? super ScrollEvent> value) {
7633 onScrollStartedProperty().set(value);
7634 }
7635
7636 public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
7637 return (eventHandlerProperties == null)
7638 ? null : eventHandlerProperties.getOnScrollStarted();
7639 }
7640
7641 /**
7642 * Defines a function to be called when a scrolling gesture is detected.
7643 * @return the event handler that is called when a scrolling gesture is
7644 * detected
7645 * @since JavaFX 2.2
7646 */
7647 public final ObjectProperty<EventHandler<? super ScrollEvent>>
7648 onScrollStartedProperty() {
7649 return getEventHandlerProperties().onScrollStartedProperty();
7650 }
7651
7652 public final void setOnScroll(
7653 EventHandler<? super ScrollEvent> value) {
7654 onScrollProperty().set(value);
7655 }
7656
7657 public final EventHandler<? super ScrollEvent> getOnScroll() {
7658 return (eventHandlerProperties == null)
7659 ? null : eventHandlerProperties.getOnScroll();
7660 }
7661
7662 /**
7663 * Defines a function to be called when user performs a scrolling action.
7664 * @return the event handler that is called when user performs a scrolling
7665 * action
7666 */
7667 public final ObjectProperty<EventHandler<? super ScrollEvent>>
7668 onScrollProperty() {
7669 return getEventHandlerProperties().onScrollProperty();
7670 }
7671
7672 public final void setOnScrollFinished(
7673 EventHandler<? super ScrollEvent> value) {
7674 onScrollFinishedProperty().set(value);
7675 }
7676
7677 public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
7678 return (eventHandlerProperties == null)
7679 ? null : eventHandlerProperties.getOnScrollFinished();
7680 }
7681
7682 /**
7683 * Defines a function to be called when a scrolling gesture ends.
7684 * @return the event handler that is called when a scrolling gesture ends
7685 * @since JavaFX 2.2
7686 */
7687 public final ObjectProperty<EventHandler<? super ScrollEvent>>
7688 onScrollFinishedProperty() {
7689 return getEventHandlerProperties().onScrollFinishedProperty();
7690 }
7691
7692 public final void setOnRotationStarted(
7693 EventHandler<? super RotateEvent> value) {
7694 onRotationStartedProperty().set(value);
7695 }
7696
7697 public final EventHandler<? super RotateEvent> getOnRotationStarted() {
7698 return (eventHandlerProperties == null)
7699 ? null : eventHandlerProperties.getOnRotationStarted();
7700 }
7701
7702 /**
7703 * Defines a function to be called when a rotation gesture is detected.
7704 * @return the event handler that is called when a rotation gesture is
7705 * detected
7706 * @since JavaFX 2.2
7707 */
7708 public final ObjectProperty<EventHandler<? super RotateEvent>>
7709 onRotationStartedProperty() {
7710 return getEventHandlerProperties().onRotationStartedProperty();
7711 }
7712
7713 public final void setOnRotate(
7714 EventHandler<? super RotateEvent> value) {
7715 onRotateProperty().set(value);
7716 }
7717
7718 public final EventHandler<? super RotateEvent> getOnRotate() {
7719 return (eventHandlerProperties == null)
7720 ? null : eventHandlerProperties.getOnRotate();
7721 }
7722
7723 /**
7724 * Defines a function to be called when user performs a rotation action.
7725 * @return the event handler that is called when user performs a rotation
7726 * action
7727 * @since JavaFX 2.2
7728 */
7729 public final ObjectProperty<EventHandler<? super RotateEvent>>
7730 onRotateProperty() {
7731 return getEventHandlerProperties().onRotateProperty();
7732 }
7733
7734 public final void setOnRotationFinished(
7735 EventHandler<? super RotateEvent> value) {
7736 onRotationFinishedProperty().set(value);
7737 }
7738
7739 public final EventHandler<? super RotateEvent> getOnRotationFinished() {
7740 return (eventHandlerProperties == null)
7741 ? null : eventHandlerProperties.getOnRotationFinished();
7742 }
7743
7744 /**
7745 * Defines a function to be called when a rotation gesture ends.
7746 * @return the event handler that is called when a rotation gesture ends
7747 * @since JavaFX 2.2
7748 */
7749 public final ObjectProperty<EventHandler<? super RotateEvent>>
7750 onRotationFinishedProperty() {
7751 return getEventHandlerProperties().onRotationFinishedProperty();
7752 }
7753
7754 public final void setOnZoomStarted(
7755 EventHandler<? super ZoomEvent> value) {
7756 onZoomStartedProperty().set(value);
7757 }
7758
7759 public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
7760 return (eventHandlerProperties == null)
7761 ? null : eventHandlerProperties.getOnZoomStarted();
7762 }
7763
7764 /**
7765 * Defines a function to be called when a zooming gesture is detected.
7766 * @return the event handler that is called when a zooming gesture is
7767 * detected
7768 * @since JavaFX 2.2
7769 */
7770 public final ObjectProperty<EventHandler<? super ZoomEvent>>
7771 onZoomStartedProperty() {
7772 return getEventHandlerProperties().onZoomStartedProperty();
7773 }
7774
7775 public final void setOnZoom(
7776 EventHandler<? super ZoomEvent> value) {
7777 onZoomProperty().set(value);
7778 }
7779
7780 public final EventHandler<? super ZoomEvent> getOnZoom() {
7781 return (eventHandlerProperties == null)
7782 ? null : eventHandlerProperties.getOnZoom();
7783 }
7784
7785 /**
7786 * Defines a function to be called when user performs a zooming action.
7787 * @return the event handler that is called when user performs a zooming
7788 * action
7789 * @since JavaFX 2.2
7790 */
7791 public final ObjectProperty<EventHandler<? super ZoomEvent>>
7792 onZoomProperty() {
7793 return getEventHandlerProperties().onZoomProperty();
7794 }
7795
7796 public final void setOnZoomFinished(
7797 EventHandler<? super ZoomEvent> value) {
7798 onZoomFinishedProperty().set(value);
7799 }
7800
7801 public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
7802 return (eventHandlerProperties == null)
7803 ? null : eventHandlerProperties.getOnZoomFinished();
7804 }
7805
7806 /**
7807 * Defines a function to be called when a zooming gesture ends.
7808 * @return the event handler that is called when a zooming gesture ends
7809 * @since JavaFX 2.2
7810 */
7811 public final ObjectProperty<EventHandler<? super ZoomEvent>>
7812 onZoomFinishedProperty() {
7813 return getEventHandlerProperties().onZoomFinishedProperty();
7814 }
7815
7816 public final void setOnSwipeUp(
7817 EventHandler<? super SwipeEvent> value) {
7818 onSwipeUpProperty().set(value);
7819 }
7820
7821 public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
7822 return (eventHandlerProperties == null)
7823 ? null : eventHandlerProperties.getOnSwipeUp();
7824 }
7825
7826 /**
7827 * Defines a function to be called when an upward swipe gesture
7828 * centered over this node happens.
7829 * @return the event handler that is called when an upward swipe gesture
7830 * centered over this node happens
7831 * @since JavaFX 2.2
7832 */
7833 public final ObjectProperty<EventHandler<? super SwipeEvent>>
7834 onSwipeUpProperty() {
7835 return getEventHandlerProperties().onSwipeUpProperty();
7836 }
7837
7838 public final void setOnSwipeDown(
7839 EventHandler<? super SwipeEvent> value) {
7840 onSwipeDownProperty().set(value);
7841 }
7842
7843 public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
7844 return (eventHandlerProperties == null)
7845 ? null : eventHandlerProperties.getOnSwipeDown();
7846 }
7847
7848 /**
7849 * Defines a function to be called when a downward swipe gesture
7850 * centered over this node happens.
7851 * @return the event handler that is called when a downward swipe gesture
7852 * centered over this node happens
7853 * @since JavaFX 2.2
7854 */
7855 public final ObjectProperty<EventHandler<? super SwipeEvent>>
7856 onSwipeDownProperty() {
7857 return getEventHandlerProperties().onSwipeDownProperty();
7858 }
7859
7860 public final void setOnSwipeLeft(
7861 EventHandler<? super SwipeEvent> value) {
7862 onSwipeLeftProperty().set(value);
7863 }
7864
7865 public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
7866 return (eventHandlerProperties == null)
7867 ? null : eventHandlerProperties.getOnSwipeLeft();
7868 }
7869
7870 /**
7871 * Defines a function to be called when a leftward swipe gesture
7872 * centered over this node happens.
7873 * @return the event handler that is called when a leftward swipe gesture
7874 * centered over this node happens
7875 * @since JavaFX 2.2
7876 */
7877 public final ObjectProperty<EventHandler<? super SwipeEvent>>
7878 onSwipeLeftProperty() {
7879 return getEventHandlerProperties().onSwipeLeftProperty();
7880 }
7881
7882 public final void setOnSwipeRight(
7883 EventHandler<? super SwipeEvent> value) {
7884 onSwipeRightProperty().set(value);
7885 }
7886
7887 public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
7888 return (eventHandlerProperties == null)
7889 ? null : eventHandlerProperties.getOnSwipeRight();
7890 }
7891
7892 /**
7893 * Defines a function to be called when an rightward swipe gesture
7894 * centered over this node happens.
7895 * @return the event handler that is called when an rightward swipe gesture
7896 * centered over this node happens
7897 * @since JavaFX 2.2
7898 */
7899 public final ObjectProperty<EventHandler<? super SwipeEvent>>
7900 onSwipeRightProperty() {
7901 return getEventHandlerProperties().onSwipeRightProperty();
7902 }
7903
7904
7905 /* *************************************************************************
7906 * *
7907 * Touch Handling *
7908 * *
7909 **************************************************************************/
7910
7911 public final void setOnTouchPressed(
7912 EventHandler<? super TouchEvent> value) {
7913 onTouchPressedProperty().set(value);
7914 }
7915
7916 public final EventHandler<? super TouchEvent> getOnTouchPressed() {
7917 return (eventHandlerProperties == null)
7918 ? null : eventHandlerProperties.getOnTouchPressed();
7919 }
7920
7921 /**
7922 * Defines a function to be called when a new touch point is pressed.
7923 * @return the event handler that is called when a new touch point is pressed
7924 * @since JavaFX 2.2
7925 */
7926 public final ObjectProperty<EventHandler<? super TouchEvent>>
7927 onTouchPressedProperty() {
7928 return getEventHandlerProperties().onTouchPressedProperty();
7929 }
7930
7931 public final void setOnTouchMoved(
7932 EventHandler<? super TouchEvent> value) {
7933 onTouchMovedProperty().set(value);
7934 }
7935
7936 public final EventHandler<? super TouchEvent> getOnTouchMoved() {
7937 return (eventHandlerProperties == null)
7938 ? null : eventHandlerProperties.getOnTouchMoved();
7939 }
7940
7941 /**
7942 * Defines a function to be called when a touch point is moved.
7943 * @return the event handler that is called when a touch point is moved
7944 * @since JavaFX 2.2
7945 */
7946 public final ObjectProperty<EventHandler<? super TouchEvent>>
7947 onTouchMovedProperty() {
7948 return getEventHandlerProperties().onTouchMovedProperty();
7949 }
7950
7951 public final void setOnTouchReleased(
7952 EventHandler<? super TouchEvent> value) {
7953 onTouchReleasedProperty().set(value);
7954 }
7955
7956 public final EventHandler<? super TouchEvent> getOnTouchReleased() {
7957 return (eventHandlerProperties == null)
7958 ? null : eventHandlerProperties.getOnTouchReleased();
7959 }
7960
7961 /**
7962 * Defines a function to be called when a touch point is released.
7963 * @return the event handler that is called when a touch point is released
7964 * @since JavaFX 2.2
7965 */
7966 public final ObjectProperty<EventHandler<? super TouchEvent>>
7967 onTouchReleasedProperty() {
7968 return getEventHandlerProperties().onTouchReleasedProperty();
7969 }
7970
7971 public final void setOnTouchStationary(
7972 EventHandler<? super TouchEvent> value) {
7973 onTouchStationaryProperty().set(value);
7974 }
7975
7976 public final EventHandler<? super TouchEvent> getOnTouchStationary() {
7977 return (eventHandlerProperties == null)
7978 ? null : eventHandlerProperties.getOnTouchStationary();
7979 }
7980
7981 /**
7982 * Defines a function to be called when a touch point stays pressed and
7983 * still.
7984 * @return the event handler that is called when a touch point stays pressed
7985 * and still
7986 * @since JavaFX 2.2
7987 */
7988 public final ObjectProperty<EventHandler<? super TouchEvent>>
7989 onTouchStationaryProperty() {
7990 return getEventHandlerProperties().onTouchStationaryProperty();
7991 }
7992
7993 /* *************************************************************************
7994 * *
7995 * Keyboard Handling *
7996 * *
7997 **************************************************************************/
7998
7999 public final void setOnKeyPressed(
8000 EventHandler<? super KeyEvent> value) {
8001 onKeyPressedProperty().set(value);
8002 }
8003
8004 public final EventHandler<? super KeyEvent> getOnKeyPressed() {
8005 return (eventHandlerProperties == null)
8006 ? null : eventHandlerProperties.getOnKeyPressed();
8007 }
8008
8009 /**
8010 * Defines a function to be called when this {@code Node} or its child
8011 * {@code Node} has input focus and a key has been pressed. The function
8012 * is called only if the event hasn't been already consumed during its
8013 * capturing or bubbling phase.
8014 * @return the event handler that is called when this {@code Node} or its
8015 * child {@code Node} has input focus and a key has been pressed
8016 */
8017 public final ObjectProperty<EventHandler<? super KeyEvent>>
8018 onKeyPressedProperty() {
8019 return getEventHandlerProperties().onKeyPressedProperty();
8020 }
8021
8022 public final void setOnKeyReleased(
8023 EventHandler<? super KeyEvent> value) {
8024 onKeyReleasedProperty().set(value);
8025 }
8026
8027 public final EventHandler<? super KeyEvent> getOnKeyReleased() {
8028 return (eventHandlerProperties == null)
8029 ? null : eventHandlerProperties.getOnKeyReleased();
8030 }
8031
8032 /**
8033 * Defines a function to be called when this {@code Node} or its child
8034 * {@code Node} has input focus and a key has been released. The function
8035 * is called only if the event hasn't been already consumed during its
8036 * capturing or bubbling phase.
8037 * @return the event handler that is called when this {@code Node} or its
8038 * child {@code Node} has input focus and a key has been released
8039 */
8040 public final ObjectProperty<EventHandler<? super KeyEvent>>
8041 onKeyReleasedProperty() {
8042 return getEventHandlerProperties().onKeyReleasedProperty();
8043 }
8044
8045 public final void setOnKeyTyped(
8046 EventHandler<? super KeyEvent> value) {
8047 onKeyTypedProperty().set(value);
8048 }
8049
8050 public final EventHandler<? super KeyEvent> getOnKeyTyped() {
8051 return (eventHandlerProperties == null)
8052 ? null : eventHandlerProperties.getOnKeyTyped();
8053 }
8054
8055 /**
8056 * Defines a function to be called when this {@code Node} or its child
8057 * {@code Node} has input focus and a key has been typed. The function
8058 * is called only if the event hasn't been already consumed during its
8059 * capturing or bubbling phase.
8060 * @return the event handler that is called when this {@code Node} or its
8061 * child {@code Node} has input focus and a key has been typed
8062 */
8063 public final ObjectProperty<EventHandler<? super KeyEvent>>
8064 onKeyTypedProperty() {
8065 return getEventHandlerProperties().onKeyTypedProperty();
8066 }
8067
8068 /* *************************************************************************
8069 * *
8070 * Input Method Handling *
8071 * *
8072 **************************************************************************/
8073
8074 public final void setOnInputMethodTextChanged(
8075 EventHandler<? super InputMethodEvent> value) {
8076 onInputMethodTextChangedProperty().set(value);
8077 }
8078
8079 public final EventHandler<? super InputMethodEvent>
8080 getOnInputMethodTextChanged() {
8081 return (eventHandlerProperties == null)
8082 ? null : eventHandlerProperties.getOnInputMethodTextChanged();
8083 }
8084
8085 /**
8086 * Defines a function to be called when this {@code Node}
8087 * has input focus and the input method text has changed. If this
8088 * function is not defined in this {@code Node}, then it
8089 * receives the result string of the input method composition as a
8090 * series of {@code onKeyTyped} function calls.
8091 * <p>
8092 * When the {@code Node} loses the input focus, the JavaFX runtime
8093 * automatically commits the existing composed text if any.
8094 * </p>
8095 * @return the event handler that is called when this {@code Node} has input
8096 * focus and the input method text has changed
8097 */
8098 public final ObjectProperty<EventHandler<? super InputMethodEvent>>
8099 onInputMethodTextChangedProperty() {
8100 return getEventHandlerProperties().onInputMethodTextChangedProperty();
8101 }
8102
8103 public final void setInputMethodRequests(InputMethodRequests value) {
8104 inputMethodRequestsProperty().set(value);
8105 }
8106
8107 public final InputMethodRequests getInputMethodRequests() {
8108 return (miscProperties == null)
8109 ? DEFAULT_INPUT_METHOD_REQUESTS
8110 : miscProperties.getInputMethodRequests();
8111 }
8112
8113 /**
8114 * Property holding InputMethodRequests.
8115 *
8116 * @return InputMethodRequestsProperty
8117 */
8118 public final ObjectProperty<InputMethodRequests> inputMethodRequestsProperty() {
8119 return getMiscProperties().inputMethodRequestsProperty();
8120 }
8121
8122 /***************************************************************************
8123 * *
8124 * Focus Traversal *
8125 * *
8126 **************************************************************************/
8127
8128 /**
8129 * Special boolean property which allows for atomic focus change.
8130 * Focus change means defocusing the old focus owner and focusing a new
8131 * one. With a usual property, defocusing the old node fires the value
8132 * changed event and user code can react with something that breaks
8133 * focusability of the new node, or even remove the new node from the scene.
8134 * This leads to various error states. This property allows for setting
8135 * the state without firing the event. The focus change first sets both
8136 * properties and then fires both events. This makes the focus change look
8137 * like an atomic operation - when the old node is notified to loose focus,
8138 * the new node is already focused.
8139 */
8140 final class FocusedProperty extends ReadOnlyBooleanPropertyBase {
8141 private boolean value;
8142 private boolean valid = true;
8143 private boolean needsChangeEvent = false;
8144
8145 public void store(final boolean value) {
8146 if (value != this.value) {
8147 this.value = value;
8148 markInvalid();
8149 }
8150 }
8151
8152 public void notifyListeners() {
8153 if (needsChangeEvent) {
8154 fireValueChangedEvent();
8155 needsChangeEvent = false;
8156 }
8157 }
8158
8159 private void markInvalid() {
8160 if (valid) {
8161 valid = false;
8162
8163 pseudoClassStateChanged(FOCUSED_PSEUDOCLASS_STATE, get());
8164 PlatformLogger logger = Logging.getFocusLogger();
8165 if (logger.isLoggable(Level.FINE)) {
8166 logger.fine(this + " focused=" + get());
8167 }
8168
8169 needsChangeEvent = true;
8170
8171 notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUSED);
8172 }
8173 }
8174
8175 @Override
8176 public boolean get() {
8177 valid = true;
8178 return value;
8179 }
8180
8181 @Override
8182 public Object getBean() {
8183 return Node.this;
8184 }
8185
8186 @Override
8187 public String getName() {
8188 return "focused";
8189 }
8190 }
8191
8192 /**
8193 * Indicates whether this {@code Node} currently has the input focus.
8194 * To have the input focus, a node must be the {@code Scene}'s focus
8195 * owner, and the scene must be in a {@code Stage} that is visible
8196 * and active. See {@link #requestFocus()} for more information.
8197 *
8198 * @see #requestFocus()
8199 * @defaultValue false
8200 */
8201 private FocusedProperty focused;
8202
8203 protected final void setFocused(boolean value) {
8204 FocusedProperty fp = focusedPropertyImpl();
8205 if (fp.value != value) {
8206 fp.store(value);
8207 fp.notifyListeners();
8208 }
8209 }
8210
8211 public final boolean isFocused() {
8212 return focused == null ? false : focused.get();
8213 }
8214
8215 public final ReadOnlyBooleanProperty focusedProperty() {
8216 return focusedPropertyImpl();
8217 }
8218
8219 private FocusedProperty focusedPropertyImpl() {
8220 if (focused == null) {
8221 focused = new FocusedProperty();
8222 }
8223 return focused;
8224 }
8225
8226 /**
8227 * Specifies whether this {@code Node} should be a part of focus traversal
8228 * cycle. When this property is {@code true} focus can be moved to this
8229 * {@code Node} and from this {@code Node} using regular focus traversal
8230 * keys. On a desktop such keys are usually {@code TAB} for moving focus
8231 * forward and {@code SHIFT+TAB} for moving focus backward.
8232 *
8233 * When a {@code Scene} is created, the system gives focus to a
8234 * {@code Node} whose {@code focusTraversable} variable is true
8235 * and that is eligible to receive the focus,
8236 * unless the focus had been set explicitly via a call
8237 * to {@link #requestFocus()}.
8238 *
8239 * @see #requestFocus()
8240 * @defaultValue false
8241 */
8242 private BooleanProperty focusTraversable;
8243
8244 public final void setFocusTraversable(boolean value) {
8245 focusTraversableProperty().set(value);
8246 }
8247 public final boolean isFocusTraversable() {
8248 return focusTraversable == null ? false : focusTraversable.get();
8249 }
8250
8251 public final BooleanProperty focusTraversableProperty() {
8252 if (focusTraversable == null) {
8253 focusTraversable = new StyleableBooleanProperty(false) {
8254
8255 @Override
8256 public void invalidated() {
8257 Scene _scene = getScene();
8258 if (_scene != null) {
8259 if (get()) {
8260 _scene.initializeInternalEventDispatcher();
8261 }
8262 focusSetDirty(_scene);
8263 }
8264 }
8265
8266 @Override
8267 public CssMetaData getCssMetaData() {
8268 return StyleableProperties.FOCUS_TRAVERSABLE;
8269 }
8270
8271 @Override
8272 public Object getBean() {
8273 return Node.this;
8274 }
8275
8276 @Override
8277 public String getName() {
8278 return "focusTraversable";
8279 }
8280 };
8281 }
8282 return focusTraversable;
8283 }
8284
8285 /**
8286 * Called when something has changed on this node that *may* have made the
8287 * scene's focus dirty. This covers the cases where this node is the focus
8288 * owner and it may have lost eligibility, or it's traversable and it may
8289 * have gained eligibility. Note that we do not want to use disabled
8290 * or treeVisible here, as this function is called from their
8291 * "on invalidate" triggers, and using them will cause them to be
8292 * revalidated. The pulse will revalidate everything and make the final
8293 * determination.
8294 */
8295 private void focusSetDirty(Scene s) {
8296 if (s != null &&
8297 (this == s.getFocusOwner() || isFocusTraversable())) {
8298 s.setFocusDirty(true);
8299 }
8300 }
8301
8302 /**
8303 * Requests that this {@code Node} get the input focus, and that this
8304 * {@code Node}'s top-level ancestor become the focused window. To be
8305 * eligible to receive the focus, the node must be part of a scene, it and
8306 * all of its ancestors must be visible, and it must not be disabled.
8307 * If this node is eligible, this function will cause it to become this
8308 * {@code Scene}'s "focus owner". Each scene has at most one focus owner
8309 * node. The focus owner will not actually have the input focus, however,
8310 * unless the scene belongs to a {@code Stage} that is both visible
8311 * and active.
8312 */
8313 public void requestFocus() {
8314 if (getScene() != null) {
8315 getScene().requestFocus(this);
8316 }
8317 }
8318
8319 /**
8320 * Traverses from this node in the direction indicated. Note that this
8321 * node need not actually have the focus, nor need it be focusTraversable.
8322 * However, the node must be part of a scene, otherwise this request
8323 * is ignored.
8324 */
8325 final boolean traverse(Direction dir) {
8326 if (getScene() == null) {
8327 return false;
8328 }
8329 return getScene().traverse(this, dir);
8330 }
8331
8332 ////////////////////////////
8333 // Private Implementation
8334 ////////////////////////////
8335
8336 /**
8337 * Returns a string representation for the object.
8338 * @return a string representation for the object.
8339 */
8340 @Override
8341 public String toString() {
8342 String klassName = getClass().getName();
8343 String simpleName = klassName.substring(klassName.lastIndexOf('.')+1);
8344 StringBuilder sbuf = new StringBuilder(simpleName);
8345 boolean hasId = id != null && !"".equals(getId());
8346 boolean hasStyleClass = !getStyleClass().isEmpty();
8347
8348 if (!hasId) {
8349 sbuf.append('@');
8350 sbuf.append(Integer.toHexString(hashCode()));
8351 } else {
8352 sbuf.append("[id=");
8353 sbuf.append(getId());
8354 if (!hasStyleClass) sbuf.append("]");
8355 }
8356 if (hasStyleClass) {
8357 if (!hasId) sbuf.append('[');
8358 else sbuf.append(", ");
8359 sbuf.append("styleClass=");
8360 sbuf.append(getStyleClass());
8361 sbuf.append("]");
8362 }
8363 return sbuf.toString();
8364 }
8365
8366 private void preprocessMouseEvent(MouseEvent e) {
8367 final EventType<?> eventType = e.getEventType();
8368 if (eventType == MouseEvent.MOUSE_PRESSED) {
8369 for (Node n = this; n != null; n = n.getParent()) {
8370 n.setPressed(e.isPrimaryButtonDown());
8371 }
8372 return;
8373 }
8374 if (eventType == MouseEvent.MOUSE_RELEASED) {
8375 for (Node n = this; n != null; n = n.getParent()) {
8376 n.setPressed(e.isPrimaryButtonDown());
8377 }
8378 return;
8379 }
8380
8381 if (e.getTarget() == this) {
8382 // the mouse event types are translated only when the node uses
8383 // its internal event dispatcher, so both entered / exited variants
8384 // are possible here
8385
8386 if ((eventType == MouseEvent.MOUSE_ENTERED)
8387 || (eventType == MouseEvent.MOUSE_ENTERED_TARGET)) {
8388 setHover(true);
8389 return;
8390 }
8391
8392 if ((eventType == MouseEvent.MOUSE_EXITED)
8393 || (eventType == MouseEvent.MOUSE_EXITED_TARGET)) {
8394 setHover(false);
8395 return;
8396 }
8397 }
8398 }
8399
8400 void markDirtyLayoutBranch() {
8401 Parent p = getParent();
8402 while (p != null && p.layoutFlag == LayoutFlags.CLEAN) {
8403 p.setLayoutFlag(LayoutFlags.DIRTY_BRANCH);
8404 if (p.isSceneRoot()) {
8405 Toolkit.getToolkit().requestNextPulse();
8406 if (getSubScene() != null) {
8407 getSubScene().setDirtyLayout(p);
8408 }
8409 }
8410 p = p.getParent();
8411 }
8412
8413 }
8414
8415 private boolean isWindowShowing() {
8416 Scene s = getScene();
8417 if (s == null) return false;
8418 Window w = s.getWindow();
8419 return w != null && w.isShowing();
8420 }
8421
8422 private void updateTreeShowing() {
8423 setTreeShowing(isTreeVisible() && isWindowShowing());
8424 }
8425
8426 private boolean treeShowing;
8427 private TreeShowingPropertyReadOnly treeShowingRO;
8428
8429 final void setTreeShowing(boolean value) {
8430 if (treeShowing != value) {
8431 treeShowing = value;
8432 ((TreeShowingPropertyReadOnly) treeShowingProperty()).invalidate();
8433 }
8434 }
8435
8436 final boolean isTreeShowing() {
8437 return treeShowingProperty().get();
8438 }
8439
8440 final BooleanExpression treeShowingProperty() {
8441 if (treeShowingRO == null) {
8442 treeShowingRO = new TreeShowingPropertyReadOnly();
8443 }
8444 return treeShowingRO;
8445 }
8446
8447 class TreeShowingPropertyReadOnly extends BooleanExpression {
8448
8449 private ExpressionHelper<Boolean> helper;
8450 private boolean valid;
8451
8452 @Override
8453 public void addListener(InvalidationListener listener) {
8454 helper = ExpressionHelper.addListener(helper, this, listener);
8455 }
8456
8457 @Override
8458 public void removeListener(InvalidationListener listener) {
8459 helper = ExpressionHelper.removeListener(helper, listener);
8460 }
8461
8462 @Override
8463 public void addListener(ChangeListener<? super Boolean> listener) {
8464 helper = ExpressionHelper.addListener(helper, this, listener);
8465 }
8466
8467 @Override
8468 public void removeListener(ChangeListener<? super Boolean> listener) {
8469 helper = ExpressionHelper.removeListener(helper, listener);
8470 }
8471
8472 protected void invalidate() {
8473 if (valid) {
8474 valid = false;
8475 ExpressionHelper.fireValueChangedEvent(helper);
8476 }
8477 }
8478
8479 @Override
8480 public boolean get() {
8481 valid = true;
8482 return Node.this.treeShowing;
8483 }
8484
8485 }
8486
8487 private void updateTreeVisible(boolean parentChanged) {
8488 boolean isTreeVisible = isVisible();
8489 final Node parentNode = getParent() != null ? getParent() :
8490 clipParent != null ? clipParent :
8491 getSubScene() != null ? getSubScene() : null;
8492 if (isTreeVisible) {
8493 isTreeVisible = parentNode == null || parentNode.isTreeVisible();
8494 }
8495 // When the parent has changed to visible and we have unsynchronized visibility,
8496 // we have to synchronize, because the rendering will now pass through the newly-visible parent
8497 // Otherwise an invisible Node might get rendered
8498 if (parentChanged && parentNode != null && parentNode.isTreeVisible()
8499 && isDirty(DirtyBits.NODE_VISIBLE)) {
8500 addToSceneDirtyList();
8501 }
8502 setTreeVisible(isTreeVisible);
8503
8504 updateTreeShowing();
8505 }
8506
8507 private boolean treeVisible;
8508 private TreeVisiblePropertyReadOnly treeVisibleRO;
8509
8510 final void setTreeVisible(boolean value) {
8511 if (treeVisible != value) {
8512 treeVisible = value;
8513 updateCanReceiveFocus();
8514 focusSetDirty(getScene());
8515 if (getClip() != null) {
8516 getClip().updateTreeVisible(true);
8517 }
8518 if (treeVisible && !isDirtyEmpty()) {
8519 addToSceneDirtyList();
8520 }
8521 ((TreeVisiblePropertyReadOnly) treeVisibleProperty()).invalidate();
8522 if (Node.this instanceof SubScene) {
8523 Node subSceneRoot = ((SubScene)Node.this).getRoot();
8524 if (subSceneRoot != null) {
8525 // SubScene.getRoot() is only null if it's constructor
8526 // has not finished.
8527 subSceneRoot.setTreeVisible(value && subSceneRoot.isVisible());
8528 }
8529 }
8530 }
8531 }
8532
8533 final boolean isTreeVisible() {
8534 return treeVisibleProperty().get();
8535 }
8536
8537 final BooleanExpression treeVisibleProperty() {
8538 if (treeVisibleRO == null) {
8539 treeVisibleRO = new TreeVisiblePropertyReadOnly();
8540 }
8541 return treeVisibleRO;
8542 }
8543
8544 class TreeVisiblePropertyReadOnly extends BooleanExpression {
8545
8546 private ExpressionHelper<Boolean> helper;
8547 private boolean valid;
8548
8549 @Override
8550 public void addListener(InvalidationListener listener) {
8551 helper = ExpressionHelper.addListener(helper, this, listener);
8552 }
8553
8554 @Override
8555 public void removeListener(InvalidationListener listener) {
8556 helper = ExpressionHelper.removeListener(helper, listener);
8557 }
8558
8559 @Override
8560 public void addListener(ChangeListener<? super Boolean> listener) {
8561 helper = ExpressionHelper.addListener(helper, this, listener);
8562 }
8563
8564 @Override
8565 public void removeListener(ChangeListener<? super Boolean> listener) {
8566 helper = ExpressionHelper.removeListener(helper, listener);
8567 }
8568
8569 protected void invalidate() {
8570 if (valid) {
8571 valid = false;
8572 ExpressionHelper.fireValueChangedEvent(helper);
8573 }
8574 }
8575
8576 @Override
8577 public boolean get() {
8578 valid = true;
8579 return Node.this.treeVisible;
8580 }
8581
8582 }
8583
8584 private boolean canReceiveFocus = false;
8585
8586 private void setCanReceiveFocus(boolean value) {
8587 canReceiveFocus = value;
8588 }
8589
8590 final boolean isCanReceiveFocus() {
8591 return canReceiveFocus;
8592 }
8593
8594 private void updateCanReceiveFocus() {
8595 setCanReceiveFocus(getScene() != null
8596 && !isDisabled()
8597 && isTreeVisible());
8598 }
8599
8600 // for indenting messages based on scene-graph depth
8601 String indent() {
8602 String indent = "";
8603 Parent p = this.getParent();
8604 while (p != null) {
8605 indent += " ";
8606 p = p.getParent();
8607 }
8608 return indent;
8609 }
8610
8611 /*
8612 * Should we underline the mnemonic character?
8613 */
8614 private BooleanProperty showMnemonics;
8615
8616 final void setShowMnemonics(boolean value) {
8617 showMnemonicsProperty().set(value);
8618 }
8619
8620 final boolean isShowMnemonics() {
8621 return showMnemonics == null ? false : showMnemonics.get();
8622 }
8623
8624 final BooleanProperty showMnemonicsProperty() {
8625 if (showMnemonics == null) {
8626 showMnemonics = new BooleanPropertyBase(false) {
8627
8628 @Override
8629 protected void invalidated() {
8630 pseudoClassStateChanged(SHOW_MNEMONICS_PSEUDOCLASS_STATE, get());
8631 }
8632
8633 @Override
8634 public Object getBean() {
8635 return Node.this;
8636 }
8637
8638 @Override
8639 public String getName() {
8640 return "showMnemonics";
8641 }
8642 };
8643 }
8644 return showMnemonics;
8645 }
8646
8647
8648 /**
8649 * References a node that is a labelFor this node.
8650 * Accessible via a NodeAccessor. See Label.labelFor for details.
8651 */
8652 private Node labeledBy = null;
8653
8654
8655 /***************************************************************************
8656 * *
8657 * Event Dispatch *
8658 * *
8659 **************************************************************************/
8660
8661 // PENDING_DOC_REVIEW
8662 /**
8663 * Specifies the event dispatcher for this node. The default event
8664 * dispatcher sends the received events to the registered event handlers and
8665 * filters. When replacing the value with a new {@code EventDispatcher},
8666 * the new dispatcher should forward events to the replaced dispatcher
8667 * to maintain the node's default event handling behavior.
8668 */
8669 private ObjectProperty<EventDispatcher> eventDispatcher;
8670
8671 public final void setEventDispatcher(EventDispatcher value) {
8672 eventDispatcherProperty().set(value);
8673 }
8674
8675 public final EventDispatcher getEventDispatcher() {
8676 return eventDispatcherProperty().get();
8677 }
8678
8679 public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
8680 initializeInternalEventDispatcher();
8681 return eventDispatcher;
8682 }
8683
8684 private NodeEventDispatcher internalEventDispatcher;
8685
8686 // PENDING_DOC_REVIEW
8687 /**
8688 * Registers an event handler to this node. The handler is called when the
8689 * node receives an {@code Event} of the specified type during the bubbling
8690 * phase of event delivery.
8691 *
8692 * @param <T> the specific event class of the handler
8693 * @param eventType the type of the events to receive by the handler
8694 * @param eventHandler the handler to register
8695 * @throws NullPointerException if the event type or handler is null
8696 */
8697 public final <T extends Event> void addEventHandler(
8698 final EventType<T> eventType,
8699 final EventHandler<? super T> eventHandler) {
8700 getInternalEventDispatcher().getEventHandlerManager()
8701 .addEventHandler(eventType, eventHandler);
8702 }
8703
8704 // PENDING_DOC_REVIEW
8705 /**
8706 * Unregisters a previously registered event handler from this node. One
8707 * handler might have been registered for different event types, so the
8708 * caller needs to specify the particular event type from which to
8709 * unregister the handler.
8710 *
8711 * @param <T> the specific event class of the handler
8712 * @param eventType the event type from which to unregister
8713 * @param eventHandler the handler to unregister
8714 * @throws NullPointerException if the event type or handler is null
8715 */
8716 public final <T extends Event> void removeEventHandler(
8717 final EventType<T> eventType,
8718 final EventHandler<? super T> eventHandler) {
8719 getInternalEventDispatcher()
8720 .getEventHandlerManager()
8721 .removeEventHandler(eventType, eventHandler);
8722 }
8723
8724 // PENDING_DOC_REVIEW
8725 /**
8726 * Registers an event filter to this node. The filter is called when the
8727 * node receives an {@code Event} of the specified type during the capturing
8728 * phase of event delivery.
8729 *
8730 * @param <T> the specific event class of the filter
8731 * @param eventType the type of the events to receive by the filter
8732 * @param eventFilter the filter to register
8733 * @throws NullPointerException if the event type or filter is null
8734 */
8735 public final <T extends Event> void addEventFilter(
8736 final EventType<T> eventType,
8737 final EventHandler<? super T> eventFilter) {
8738 getInternalEventDispatcher().getEventHandlerManager()
8739 .addEventFilter(eventType, eventFilter);
8740 }
8741
8742 // PENDING_DOC_REVIEW
8743 /**
8744 * Unregisters a previously registered event filter from this node. One
8745 * filter might have been registered for different event types, so the
8746 * caller needs to specify the particular event type from which to
8747 * unregister the filter.
8748 *
8749 * @param <T> the specific event class of the filter
8750 * @param eventType the event type from which to unregister
8751 * @param eventFilter the filter to unregister
8752 * @throws NullPointerException if the event type or filter is null
8753 */
8754 public final <T extends Event> void removeEventFilter(
8755 final EventType<T> eventType,
8756 final EventHandler<? super T> eventFilter) {
8757 getInternalEventDispatcher().getEventHandlerManager()
8758 .removeEventFilter(eventType, eventFilter);
8759 }
8760
8761 /**
8762 * Sets the handler to use for this event type. There can only be one such handler
8763 * specified at a time. This handler is guaranteed to be called as the last, after
8764 * handlers added using {@link #addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}.
8765 * This is used for registering the user-defined onFoo event handlers.
8766 *
8767 * @param <T> the specific event class of the handler
8768 * @param eventType the event type to associate with the given eventHandler
8769 * @param eventHandler the handler to register, or null to unregister
8770 * @throws NullPointerException if the event type is null
8771 */
8772 protected final <T extends Event> void setEventHandler(
8773 final EventType<T> eventType,
8774 final EventHandler<? super T> eventHandler) {
8775 getInternalEventDispatcher().getEventHandlerManager()
8776 .setEventHandler(eventType, eventHandler);
8777 }
8778
8779 private NodeEventDispatcher getInternalEventDispatcher() {
8780 initializeInternalEventDispatcher();
8781 return internalEventDispatcher;
8782 }
8783
8784 private void initializeInternalEventDispatcher() {
8785 if (internalEventDispatcher == null) {
8786 internalEventDispatcher = createInternalEventDispatcher();
8787 eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
8788 Node.this,
8789 "eventDispatcher",
8790 internalEventDispatcher);
8791 }
8792 }
8793
8794 private NodeEventDispatcher createInternalEventDispatcher() {
8795 return new NodeEventDispatcher(this);
8796 }
8797
8798 /**
8799 * Event dispatcher for invoking preprocessing of mouse events
8800 */
8801 private EventDispatcher preprocessMouseEventDispatcher;
8802
8803 // PENDING_DOC_REVIEW
8804 /**
8805 * Construct an event dispatch chain for this node. The event dispatch chain
8806 * contains all event dispatchers from the stage to this node.
8807 *
8808 * @param tail the initial chain to build from
8809 * @return the resulting event dispatch chain for this node
8810 */
8811 @Override
8812 public EventDispatchChain buildEventDispatchChain(
8813 EventDispatchChain tail) {
8814
8815 if (preprocessMouseEventDispatcher == null) {
8816 preprocessMouseEventDispatcher = (event, tail1) -> {
8817 event = tail1.dispatchEvent(event);
8818 if (event instanceof MouseEvent) {
8819 preprocessMouseEvent((MouseEvent) event);
8820 }
8821
8822 return event;
8823 };
8824 }
8825
8826 tail = tail.prepend(preprocessMouseEventDispatcher);
8827
8828 // prepend all event dispatchers from this node to the root
8829 Node curNode = this;
8830 do {
8831 if (curNode.eventDispatcher != null) {
8832 final EventDispatcher eventDispatcherValue =
8833 curNode.eventDispatcher.get();
8834 if (eventDispatcherValue != null) {
8835 tail = tail.prepend(eventDispatcherValue);
8836 }
8837 }
8838 final Node curParent = curNode.getParent();
8839 curNode = curParent != null ? curParent : curNode.getSubScene();
8840 } while (curNode != null);
8841
8842 if (getScene() != null) {
8843 // prepend scene's dispatch chain
8844 tail = getScene().buildEventDispatchChain(tail);
8845 }
8846
8847 return tail;
8848 }
8849
8850 // PENDING_DOC_REVIEW
8851 /**
8852 * Fires the specified event. By default the event will travel through the
8853 * hierarchy from the stage to this node. Any event filter encountered will
8854 * be notified and can consume the event. If not consumed by the filters,
8855 * the event handlers on this node are notified. If these don't consume the
8856 * event either, the event will travel back the same path it arrived to
8857 * this node. All event handlers encountered are called and can consume the
8858 * event.
8859 * <p>
8860 * This method must be called on the FX user thread.
8861 *
8862 * @param event the event to fire
8863 */
8864 public final void fireEvent(Event event) {
8865
8866 /* Log input events. We do a coarse filter for at least the FINE
8867 * level and then granularize from there.
8868 */
8869 if (event instanceof InputEvent) {
8870 PlatformLogger logger = Logging.getInputLogger();
8871 if (logger.isLoggable(Level.FINE)) {
8872 EventType eventType = event.getEventType();
8873 if (eventType == MouseEvent.MOUSE_ENTERED ||
8874 eventType == MouseEvent.MOUSE_EXITED) {
8875 logger.finer(event.toString());
8876 } else if (eventType == MouseEvent.MOUSE_MOVED ||
8877 eventType == MouseEvent.MOUSE_DRAGGED) {
8878 logger.finest(event.toString());
8879 } else {
8880 logger.fine(event.toString());
8881 }
8882 }
8883 }
8884
8885 Event.fireEvent(this, event);
8886 }
8887
8888 /***************************************************************************
8889 * *
8890 * Stylesheet Handling *
8891 * *
8892 **************************************************************************/
8893
8894
8895 /**
8896 * {@inheritDoc}
8897 * @return {@code getClass().getName()} without the package name
8898 * @since JavaFX 8.0
8899 */
8900 @Override
8901 public String getTypeSelector() {
8902
8903 final Class<?> clazz = getClass();
8904 final Package pkg = clazz.getPackage();
8905
8906 // package could be null. not likely, but could be.
8907 int plen = 0;
8908 if (pkg != null) {
8909 plen = pkg.getName().length();
8910 }
8911
8912 final int clen = clazz.getName().length();
8913 final int pos = (0 < plen && plen < clen) ? plen + 1 : 0;
8914
8915 return clazz.getName().substring(pos);
8916 }
8917
8918 /**
8919 * {@inheritDoc}
8920 * @return {@code getParent()}
8921 * @since JavaFX 8.0
8922 */
8923 @Override
8924 public Styleable getStyleableParent() {
8925 return getParent();
8926 }
8927
8928
8929 /**
8930 * Returns the initial focus traversable state of this node, for use
8931 * by the JavaFX CSS engine to correctly set its initial value. This method
8932 * can be overridden by subclasses in instances where focus traversable should
8933 * initially be true (as the default implementation of this method is to return
8934 * false).
8935 *
8936 * @return the initial focus traversable state for this {@code Node}.
8937 * @since 9
8938 */
8939 protected Boolean getInitialFocusTraversable() {
8940 return Boolean.FALSE;
8941 }
8942
8943 /**
8944 * Returns the initial cursor state of this node, for use
8945 * by the JavaFX CSS engine to correctly set its initial value. This method
8946 * can be overridden by subclasses in instances where the cursor should
8947 * initially be non-null (as the default implementation of this method is to return
8948 * null).
8949 *
8950 * @return the initial cursor state for this {@code Node}.
8951 * @since 9
8952 */
8953 protected Cursor getInitialCursor() {
8954 return null;
8955 }
8956
8957 /**
8958 * Super-lazy instantiation pattern from Bill Pugh.
8959 */
8960 private static class StyleableProperties {
8961
8962 private static final CssMetaData<Node,Cursor> CURSOR =
8963 new CssMetaData<Node,Cursor>("-fx-cursor", CursorConverter.getInstance()) {
8964
8965 @Override
8966 public boolean isSettable(Node node) {
8967 return node.miscProperties == null || node.miscProperties.canSetCursor();
8968 }
8969
8970 @Override
8971 public StyleableProperty<Cursor> getStyleableProperty(Node node) {
8972 return (StyleableProperty<Cursor>)node.cursorProperty();
8973 }
8974
8975 @Override
8976 public Cursor getInitialValue(Node node) {
8977 // Most controls default focusTraversable to true.
8978 // Give a way to have them return the correct default value.
8979 return node.getInitialCursor();
8980 }
8981
8982 };
8983 private static final CssMetaData<Node,Effect> EFFECT =
8984 new CssMetaData<Node,Effect>("-fx-effect", EffectConverter.getInstance()) {
8985
8986 @Override
8987 public boolean isSettable(Node node) {
8988 return node.miscProperties == null || node.miscProperties.canSetEffect();
8989 }
8990
8991 @Override
8992 public StyleableProperty<Effect> getStyleableProperty(Node node) {
8993 return (StyleableProperty<Effect>)node.effectProperty();
8994 }
8995 };
8996 private static final CssMetaData<Node,Boolean> FOCUS_TRAVERSABLE =
8997 new CssMetaData<Node,Boolean>("-fx-focus-traversable",
8998 BooleanConverter.getInstance(), Boolean.FALSE) {
8999
9000 @Override
9001 public boolean isSettable(Node node) {
9002 return node.focusTraversable == null || !node.focusTraversable.isBound();
9003 }
9004
9005 @Override
9006 public StyleableProperty<Boolean> getStyleableProperty(Node node) {
9007 return (StyleableProperty<Boolean>)node.focusTraversableProperty();
9008 }
9009
9010 @Override
9011 public Boolean getInitialValue(Node node) {
9012 // Most controls default focusTraversable to true.
9013 // Give a way to have them return the correct default value.
9014 return node.getInitialFocusTraversable();
9015 }
9016
9017 };
9018 private static final CssMetaData<Node,Number> OPACITY =
9019 new CssMetaData<Node,Number>("-fx-opacity",
9020 SizeConverter.getInstance(), 1.0) {
9021
9022 @Override
9023 public boolean isSettable(Node node) {
9024 return node.opacity == null || !node.opacity.isBound();
9025 }
9026
9027 @Override
9028 public StyleableProperty<Number> getStyleableProperty(Node node) {
9029 return (StyleableProperty<Number>)node.opacityProperty();
9030 }
9031 };
9032 private static final CssMetaData<Node,BlendMode> BLEND_MODE =
9033 new CssMetaData<Node,BlendMode>("-fx-blend-mode", new EnumConverter<BlendMode>(BlendMode.class)) {
9034
9035 @Override
9036 public boolean isSettable(Node node) {
9037 return node.blendMode == null || !node.blendMode.isBound();
9038 }
9039
9040 @Override
9041 public StyleableProperty<BlendMode> getStyleableProperty(Node node) {
9042 return (StyleableProperty<BlendMode>)node.blendModeProperty();
9043 }
9044 };
9045 private static final CssMetaData<Node,Number> ROTATE =
9046 new CssMetaData<Node,Number>("-fx-rotate",
9047 SizeConverter.getInstance(), 0.0) {
9048
9049 @Override
9050 public boolean isSettable(Node node) {
9051 return node.nodeTransformation == null
9052 || node.nodeTransformation.rotate == null
9053 || node.nodeTransformation.canSetRotate();
9054 }
9055
9056 @Override
9057 public StyleableProperty<Number> getStyleableProperty(Node node) {
9058 return (StyleableProperty<Number>)node.rotateProperty();
9059 }
9060 };
9061 private static final CssMetaData<Node,Number> SCALE_X =
9062 new CssMetaData<Node,Number>("-fx-scale-x",
9063 SizeConverter.getInstance(), 1.0) {
9064
9065 @Override
9066 public boolean isSettable(Node node) {
9067 return node.nodeTransformation == null
9068 || node.nodeTransformation.scaleX == null
9069 || node.nodeTransformation.canSetScaleX();
9070 }
9071
9072 @Override
9073 public StyleableProperty<Number> getStyleableProperty(Node node) {
9074 return (StyleableProperty<Number>)node.scaleXProperty();
9075 }
9076 };
9077 private static final CssMetaData<Node,Number> SCALE_Y =
9078 new CssMetaData<Node,Number>("-fx-scale-y",
9079 SizeConverter.getInstance(), 1.0) {
9080
9081 @Override
9082 public boolean isSettable(Node node) {
9083 return node.nodeTransformation == null
9084 || node.nodeTransformation.scaleY == null
9085 || node.nodeTransformation.canSetScaleY();
9086 }
9087
9088 @Override
9089 public StyleableProperty<Number> getStyleableProperty(Node node) {
9090 return (StyleableProperty<Number>)node.scaleYProperty();
9091 }
9092 };
9093 private static final CssMetaData<Node,Number> SCALE_Z =
9094 new CssMetaData<Node,Number>("-fx-scale-z",
9095 SizeConverter.getInstance(), 1.0) {
9096
9097 @Override
9098 public boolean isSettable(Node node) {
9099 return node.nodeTransformation == null
9100 || node.nodeTransformation.scaleZ == null
9101 || node.nodeTransformation.canSetScaleZ();
9102 }
9103
9104 @Override
9105 public StyleableProperty<Number> getStyleableProperty(Node node) {
9106 return (StyleableProperty<Number>)node.scaleZProperty();
9107 }
9108 };
9109 private static final CssMetaData<Node,Number> TRANSLATE_X =
9110 new CssMetaData<Node,Number>("-fx-translate-x",
9111 SizeConverter.getInstance(), 0.0) {
9112
9113 @Override
9114 public boolean isSettable(Node node) {
9115 return node.nodeTransformation == null
9116 || node.nodeTransformation.translateX == null
9117 || node.nodeTransformation.canSetTranslateX();
9118 }
9119
9120 @Override
9121 public StyleableProperty<Number> getStyleableProperty(Node node) {
9122 return (StyleableProperty<Number>)node.translateXProperty();
9123 }
9124 };
9125 private static final CssMetaData<Node,Number> TRANSLATE_Y =
9126 new CssMetaData<Node,Number>("-fx-translate-y",
9127 SizeConverter.getInstance(), 0.0) {
9128
9129 @Override
9130 public boolean isSettable(Node node) {
9131 return node.nodeTransformation == null
9132 || node.nodeTransformation.translateY == null
9133 || node.nodeTransformation.canSetTranslateY();
9134 }
9135
9136 @Override
9137 public StyleableProperty<Number> getStyleableProperty(Node node) {
9138 return (StyleableProperty<Number>)node.translateYProperty();
9139 }
9140 };
9141 private static final CssMetaData<Node,Number> TRANSLATE_Z =
9142 new CssMetaData<Node,Number>("-fx-translate-z",
9143 SizeConverter.getInstance(), 0.0) {
9144
9145 @Override
9146 public boolean isSettable(Node node) {
9147 return node.nodeTransformation == null
9148 || node.nodeTransformation.translateZ == null
9149 || node.nodeTransformation.canSetTranslateZ();
9150 }
9151
9152 @Override
9153 public StyleableProperty<Number> getStyleableProperty(Node node) {
9154 return (StyleableProperty<Number>)node.translateZProperty();
9155 }
9156 };
9157 private static final CssMetaData<Node, Number> VIEW_ORDER
9158 = new CssMetaData<Node, Number>("-fx-view-order",
9159 SizeConverter.getInstance(), 0.0) {
9160
9161 @Override
9162 public boolean isSettable(Node node) {
9163 return node.miscProperties == null
9164 || node.miscProperties.viewOrder == null
9165 || !node.miscProperties.viewOrder.isBound();
9166 }
9167
9168 @Override
9169 public StyleableProperty<Number> getStyleableProperty(Node node) {
9170 return (StyleableProperty<Number>) node.viewOrderProperty();
9171 }
9172 };
9173 private static final CssMetaData<Node,Boolean> VISIBILITY =
9174 new CssMetaData<Node,Boolean>("visibility",
9175 new StyleConverter<String,Boolean>() {
9176
9177 @Override
9178 // [ visible | hidden | collapse | inherit ]
9179 public Boolean convert(ParsedValue<String, Boolean> value, Font font) {
9180 final String sval = value != null ? value.getValue() : null;
9181 return "visible".equalsIgnoreCase(sval);
9182 }
9183
9184 },
9185 Boolean.TRUE) {
9186
9187 @Override
9188 public boolean isSettable(Node node) {
9189 return node.visible == null || !node.visible.isBound();
9190 }
9191
9192 @Override
9193 public StyleableProperty<Boolean> getStyleableProperty(Node node) {
9194 return (StyleableProperty<Boolean>)node.visibleProperty();
9195 }
9196 };
9197
9198 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
9199
9200 static {
9201
9202 final List<CssMetaData<? extends Styleable, ?>> styleables =
9203 new ArrayList<CssMetaData<? extends Styleable, ?>>();
9204 styleables.add(CURSOR);
9205 styleables.add(EFFECT);
9206 styleables.add(FOCUS_TRAVERSABLE);
9207 styleables.add(OPACITY);
9208 styleables.add(BLEND_MODE);
9209 styleables.add(ROTATE);
9210 styleables.add(SCALE_X);
9211 styleables.add(SCALE_Y);
9212 styleables.add(SCALE_Z);
9213 styleables.add(VIEW_ORDER);
9214 styleables.add(TRANSLATE_X);
9215 styleables.add(TRANSLATE_Y);
9216 styleables.add(TRANSLATE_Z);
9217 styleables.add(VISIBILITY);
9218 STYLEABLES = Collections.unmodifiableList(styleables);
9219
9220 }
9221 }
9222
9223 /**
9224 * @return The CssMetaData associated with this class, which may include the
9225 * CssMetaData of its superclasses.
9226 * @since JavaFX 8.0
9227 */
9228 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
9229 //
9230 // Super-lazy instantiation pattern from Bill Pugh. StyleableProperties
9231 // is referenced no earlier (and therefore loaded no earlier by the
9232 // class loader) than the moment that getClassCssMetaData() is called.
9233 // This avoids loading the CssMetaData instances until the point at
9234 // which CSS needs the data.
9235 //
9236 return StyleableProperties.STYLEABLES;
9237 }
9238
9239 /**
9240 * This method should delegate to {@link Node#getClassCssMetaData()} so that
9241 * a Node's CssMetaData can be accessed without the need for reflection.
9242 *
9243 * @return The CssMetaData associated with this node, which may include the
9244 * CssMetaData of its superclasses.
9245 * @since JavaFX 8.0
9246 */
9247
9248 @Override
9249 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
9250 return getClassCssMetaData();
9251 }
9252
9253 /*
9254 * @return The Styles that match this CSS property for the given Node. The
9255 * list is sorted by descending specificity.
9256 */
9257 // SB-dependency: RT-21096 has been filed to track this
9258 static List<Style> getMatchingStyles(CssMetaData cssMetaData, Styleable styleable) {
9259 return CssStyleHelper.getMatchingStyles(styleable, cssMetaData);
9260 }
9261
9262 final ObservableMap<StyleableProperty<?>, List<Style>> getStyleMap() {
9263 ObservableMap<StyleableProperty<?>, List<Style>> map =
9264 (ObservableMap<StyleableProperty<?>, List<Style>>)getProperties().get("STYLEMAP");
9265 Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(map, this);
9266 if (ret != null) {
9267 if (ret instanceof ObservableMap) return (ObservableMap)ret;
9268 return FXCollections.observableMap(ret);
9269 }
9270 return FXCollections.<StyleableProperty<?>, List<Style>>emptyObservableMap();
9271 }
9272
9273 /*
9274 * RT-17293
9275 */
9276 // SB-dependency: RT-21096 has been filed to track this
9277 final void setStyleMap(ObservableMap<StyleableProperty<?>, List<Style>> styleMap) {
9278 if (styleMap != null) getProperties().put("STYLEMAP", styleMap);
9279 else getProperties().remove("STYLEMAP");
9280 }
9281
9282 /*
9283 * Find CSS styles that were used to style this Node in its current pseudo-class state. The map will contain the styles from this node and,
9284 * if the node is a Parent, its children. The node corresponding to an entry in the Map can be obtained by casting a StyleableProperty key to a
9285 * javafx.beans.property.Property and calling getBean(). The List contains only those styles used to style the property and will contain
9286 * styles used to resolve lookup values.
9287 *
9288 * @param styleMap A Map to be populated with the styles. If null, a new Map will be allocated.
9289 * @return The Map populated with matching styles.
9290 */
9291 // SB-dependency: RT-21096 has been filed to track this
9292 Map<StyleableProperty<?>,List<Style>> findStyles(Map<StyleableProperty<?>,List<Style>> styleMap) {
9293
9294 Map<StyleableProperty<?>, List<Style>> ret = CssStyleHelper.getMatchingStyles(styleMap, this);
9295 return (ret != null) ? ret : Collections.<StyleableProperty<?>, List<Style>>emptyMap();
9296 }
9297
9298 /**
9299 * Flags used to indicate in which way this node is dirty (or whether it
9300 * is clean) and what must happen during the next CSS cycle on the
9301 * scenegraph.
9302 */
9303 CssFlags cssFlag = CssFlags.CLEAN;
9304
9305 /**
9306 * Needed for testing.
9307 */
9308 final CssFlags getCSSFlags() { return cssFlag; }
9309
9310 /**
9311 * Called when a CSS pseudo-class change would cause styles to be reapplied.
9312 */
9313 private void requestCssStateTransition() {
9314 // If there is no scene, then we cannot make it dirty, so we'll leave
9315 // the flag alone
9316 if (getScene() == null) return;
9317 // Don't bother doing anything if the cssFlag is not CLEAN.
9318 // If the flag indicates a DIRTY_BRANCH, the flag needs to be changed
9319 // to UPDATE to ensure that NodeHelper.processCSS is called on the node.
9320 if (cssFlag == CssFlags.CLEAN || cssFlag == CssFlags.DIRTY_BRANCH) {
9321 cssFlag = CssFlags.UPDATE;
9322 notifyParentsOfInvalidatedCSS();
9323 }
9324 }
9325
9326 /**
9327 * Used to specify that a pseudo-class of this Node has changed. If the
9328 * pseudo-class is used in a CSS selector that matches this Node, CSS will
9329 * be reapplied. Typically, this method is called from the {@code invalidated}
9330 * method of a property that is used as a pseudo-class. For example:
9331 * <pre><code>
9332 *
9333 * private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state");
9334 *
9335 * BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) {
9336 *
9337 * {@literal @}Override public void invalidated() {
9338 * pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get());
9339 * }
9340 *
9341 * {@literal @}Override public Object getBean() {
9342 * return MyControl.this;
9343 * }
9344 *
9345 * {@literal @}Override public String getName() {
9346 * return "myPseudoClassState";
9347 * }
9348 * };
9349 * </code></pre>
9350 * @param pseudoClass the pseudo-class that has changed state
9351 * @param active whether or not the state is active
9352 * @since JavaFX 8.0
9353 */
9354 public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) {
9355
9356 final boolean modified = active
9357 ? pseudoClassStates.add(pseudoClass)
9358 : pseudoClassStates.remove(pseudoClass);
9359
9360 if (modified && styleHelper != null) {
9361 final boolean isTransition = styleHelper.pseudoClassStateChanged(pseudoClass);
9362 if (isTransition) {
9363 requestCssStateTransition();
9364 }
9365 }
9366 }
9367
9368 // package so that StyleHelper can get at it
9369 final ObservableSet<PseudoClass> pseudoClassStates = new PseudoClassState();
9370 final ObservableSet<PseudoClass> unmodifiablePseudoClassStates =
9371 FXCollections.unmodifiableObservableSet(pseudoClassStates);
9372 /**
9373 * @return The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet
9374 * @since JavaFX 8.0
9375 */
9376 public final ObservableSet<PseudoClass> getPseudoClassStates() {
9377 return unmodifiablePseudoClassStates;
9378 }
9379
9380 // Walks up the tree telling each parent that the pseudo class state of
9381 // this node has changed.
9382 final void notifyParentsOfInvalidatedCSS() {
9383 SubScene subScene = getSubScene();
9384 Parent root = (subScene != null) ?
9385 subScene.getRoot() : getScene().getRoot();
9386
9387 if (!root.isDirty(DirtyBits.NODE_CSS)) {
9388 // Ensure that Scene.root is marked as dirty. If the scene isn't
9389 // dirty, nothing will get repainted. This bit is cleared from
9390 // Scene in doCSSPass().
9391 NodeHelper.markDirty(root, DirtyBits.NODE_CSS);
9392 if (subScene != null) {
9393 // If the node is part of a subscene, then we must ensure that
9394 // the we not only mark subScene.root dirty, but continue and
9395 // call subScene.notifyParentsOfInvalidatedCSS() until
9396 // Scene.root gets marked dirty, via the recursive call:
9397 subScene.cssFlag = CssFlags.UPDATE;
9398 subScene.notifyParentsOfInvalidatedCSS();
9399 }
9400 }
9401 Parent _parent = getParent();
9402 while (_parent != null) {
9403 if (_parent.cssFlag == CssFlags.CLEAN) {
9404 _parent.cssFlag = CssFlags.DIRTY_BRANCH;
9405 _parent = _parent.getParent();
9406 } else {
9407 _parent = null;
9408 }
9409 }
9410 }
9411
9412 final void reapplyCSS() {
9413
9414 if (getScene() == null) return;
9415
9416 if (cssFlag == CssFlags.REAPPLY) return;
9417
9418 if (cssFlag == CssFlags.DIRTY_BRANCH) {
9419 // JDK-8193445 - don't reapply CSS from here
9420 // Defer CSS application to this Node by marking cssFlag as REAPPLY
9421 cssFlag = CssFlags.REAPPLY;
9422 return;
9423 }
9424
9425 // RT-36838 - don't reapply CSS in the middle of an update
9426 if (cssFlag == CssFlags.UPDATE) {
9427 cssFlag = CssFlags.REAPPLY;
9428 notifyParentsOfInvalidatedCSS();
9429 return;
9430 }
9431
9432 reapplyCss();
9433
9434 //
9435 // One idiom employed by developers is to, during the layout pass,
9436 // add or remove nodes from the scene. For example, a ScrollPane
9437 // might add scroll bars to itself if it determines during layout
9438 // that it needs them, or a ListView might add cells to itself if
9439 // it determines that it needs to. In such situations we must
9440 // apply the CSS immediately and not add it to the scene's queue
9441 // for deferred action.
9442 //
9443 if (getParent() != null && getParent().isPerformingLayout()) {
9444 NodeHelper.processCSS(this);
9445 } else {
9446 notifyParentsOfInvalidatedCSS();
9447 }
9448
9449 }
9450
9451 //
9452 // This method "reapplies" CSS to this node and all of its children. Reapplying CSS
9453 // means that new style maps are calculated for the node. The process of reapplying
9454 // CSS may reset the CSS properties of a node to their initial state, but the _new_
9455 // styles are not applied as part of this process.
9456 //
9457 // There is no check of the CSS state of a child since reapply takes precedence
9458 // over other CSS states.
9459 //
9460 private void reapplyCss() {
9461
9462 // Hang on to current styleHelper so we can know whether
9463 // createStyleHelper returned the same styleHelper
9464 final CssStyleHelper oldStyleHelper = styleHelper;
9465
9466 // CSS state is "REAPPLY"
9467 cssFlag = CssFlags.REAPPLY;
9468
9469 styleHelper = CssStyleHelper.createStyleHelper(this);
9470
9471 // REAPPLY to my children, too.
9472 if (this instanceof Parent) {
9473
9474 // minor optimization to avoid calling createStyleHelper on children
9475 // when we know there will not be any change in the style maps.
9476 final boolean visitChildren =
9477 // If we don't have a styleHelper, then we should visit the children of this parent
9478 // since there might be styles that depend on being a child of this parent.
9479 // In other words, we have .a > .b { blah: blort; }, but no styles for ".a" itself.
9480 styleHelper == null ||
9481 // if the styleHelper changed, then we definitely need to visit the children
9482 // since the new styles may have an effect on the children's styles calculated values.
9483 (oldStyleHelper != styleHelper) ||
9484 // If our parent is null, then we're the root of a scene or sub-scene, most likely,
9485 // and we'll visit children because elsewhere the code depends on root.reapplyCSS()
9486 // to force css to be reapplied (whether it needs to be or not).
9487 (getParent() == null) ||
9488 // If our parent's cssFlag is other than clean, then the parent may have just had
9489 // CSS reapplied. If the parent just had CSS reapplied, then some of its styles
9490 // may affect my children's styles.
9491 (getParent().cssFlag != CssFlags.CLEAN);
9492
9493 if (visitChildren) {
9494
9495 List<Node> children = ((Parent) this).getChildren();
9496 for (int n = 0, nMax = children.size(); n < nMax; n++) {
9497 Node child = children.get(n);
9498 child.reapplyCss();
9499 }
9500 }
9501
9502 } else if (this instanceof SubScene) {
9503
9504 // SubScene root is a Parent, but reapplyCss is a private method in Node
9505 final Node subSceneRoot = ((SubScene)this).getRoot();
9506 if (subSceneRoot != null) {
9507 subSceneRoot.reapplyCss();
9508 }
9509
9510 } else if (styleHelper == null) {
9511 //
9512 // If this is not a Parent and there is no styleHelper, then the CSS state is "CLEAN"
9513 // since there are no styles to apply or children to update.
9514 //
9515 cssFlag = CssFlags.CLEAN;
9516 return;
9517 }
9518
9519 cssFlag = CssFlags.UPDATE;
9520
9521 }
9522
9523 void processCSS() {
9524 switch (cssFlag) {
9525 case CLEAN:
9526 break;
9527 case DIRTY_BRANCH:
9528 {
9529 Parent me = (Parent)this;
9530 // clear the flag first in case the flag is set to something
9531 // other than clean by downstream processing.
9532 me.cssFlag = CssFlags.CLEAN;
9533 List<Node> children = me.getChildren();
9534 for (int i=0, max=children.size(); i<max; i++) {
9535 children.get(i).processCSS();
9536 }
9537 break;
9538 }
9539 case REAPPLY:
9540 case UPDATE:
9541 default:
9542 NodeHelper.processCSS(this);
9543 }
9544 }
9545
9546 /**
9547 * If required, apply styles to this Node and its children, if any. This method does not normally need to
9548 * be invoked directly but may be used in conjunction with {@link Parent#layout()} to size a Node before the
9549 * next pulse, or if the {@link #getScene() Scene} is not in a {@link javafx.stage.Stage}.
9550 * <p>Provided that the Node's {@link #getScene() Scene} is not null, CSS is applied to this Node regardless
9551 * of whether this Node's CSS state is clean. CSS styles are applied from the top-most parent
9552 * of this Node whose CSS state is other than clean, which may affect the styling of other nodes.
9553 * This method is a no-op if the Node is not in a Scene. The Scene does not have to be in a Stage.</p>
9554 * <p>This method does not invoke the {@link Parent#layout()} method. Typically, the caller will use the
9555 * following sequence of operations.</p>
9556 * <pre>{@code
9557 * parentNode.applyCss();
9558 * parentNode.layout();
9559 * }</pre>
9560 * <p>As a more complete example, the following code uses {@code applyCss()} and {@code layout()} to find
9561 * the width and height of the Button before the Stage has been shown. If either the call to {@code applyCss()}
9562 * or the call to {@code layout()} is commented out, the calls to {@code getWidth()} and {@code getHeight()}
9563 * will return zero (until some time after the Stage is shown). </p>
9564 * <pre><code>
9565 * {@literal @}Override
9566 * public void start(Stage stage) throws Exception {
9567 *
9568 * Group root = new Group();
9569 * Scene scene = new Scene(root);
9570 *
9571 * Button button = new Button("Hello World");
9572 * root.getChildren().add(button);
9573 *
9574 * root.applyCss();
9575 * root.layout();
9576 *
9577 * double width = button.getWidth();
9578 * double height = button.getHeight();
9579 *
9580 * System.out.println(width + ", " + height);
9581 *
9582 * stage.setScene(scene);
9583 * stage.show();
9584 * }
9585 * </code></pre>
9586 * @since JavaFX 8.0
9587 */
9588 public final void applyCss() {
9589
9590 if (getScene() == null) {
9591 return;
9592 }
9593
9594 // update, unless reapply
9595 if (cssFlag != CssFlags.REAPPLY) cssFlag = CssFlags.UPDATE;
9596
9597 //
9598 // RT-28394 - need to see if any ancestor has a flag UPDATE
9599 // If so, process css from the top-most CssFlags.UPDATE node
9600 // since my ancestor's styles may affect mine.
9601 //
9602 // If the scene-graph root isn't NODE_CSS dirty, then all my
9603 // ancestor flags should be CLEAN and I can skip this lookup.
9604 //
9605 Node topMost = this;
9606
9607 final boolean dirtyRoot = getScene().getRoot().isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
9608 if (dirtyRoot) {
9609
9610 Node _parent = getParent();
9611 while (_parent != null) {
9612 if (_parent.cssFlag == CssFlags.UPDATE || _parent.cssFlag == CssFlags.REAPPLY) {
9613 topMost = _parent;
9614 }
9615 _parent = _parent.getParent();
9616 }
9617
9618 // Note: this code used to mark the parent nodes with DIRTY_BRANCH,
9619 // but that isn't necessary since UPDATE will apply css to all of
9620 // a Parent's children.
9621
9622 // If we're at the root of the scene-graph, make sure the NODE_CSS
9623 // dirty bit is cleared (see Scene#doCSSPass())
9624 if (topMost == getScene().getRoot()) {
9625 getScene().getRoot().clearDirty(DirtyBits.NODE_CSS);
9626 }
9627 }
9628
9629 topMost.processCSS();
9630
9631 }
9632
9633 /*
9634 * If invoked, will update styles from here on down. This method should not be called directly. If
9635 * overridden, the overriding method must at some point call {@code super.processCSSImpl} to ensure that
9636 * this Node's CSS state is properly updated.
9637 *
9638 * Note that the difference between this method and {@link #applyCss()} is that this method
9639 * updates styles for this node on down; whereas, {@code applyCss()} looks for the top-most ancestor that needs
9640 * CSS update and apply styles from that node on down.
9641 *
9642 * Note: This method MUST only be called via its accessor method.
9643 */
9644 private void doProcessCSS() {
9645
9646 // Nothing to do...
9647 if (cssFlag == CssFlags.CLEAN) return;
9648
9649 // if REAPPLY was deferred, process it now...
9650 if (cssFlag == CssFlags.REAPPLY) {
9651 reapplyCss();
9652 }
9653
9654 // Clear the flag first in case the flag is set to something
9655 // other than clean by downstream processing.
9656 cssFlag = CssFlags.CLEAN;
9657
9658 // Transition to the new state and apply styles
9659 if (styleHelper != null && getScene() != null) {
9660 styleHelper.transitionToState(this);
9661 }
9662 }
9663
9664
9665 /**
9666 * A StyleHelper for this node.
9667 * A StyleHelper contains all the css styles for this node
9668 * and knows how to apply them when our state changes.
9669 */
9670 CssStyleHelper styleHelper;
9671
9672 private static final PseudoClass HOVER_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("hover");
9673 private static final PseudoClass PRESSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("pressed");
9674 private static final PseudoClass DISABLED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("disabled");
9675 private static final PseudoClass FOCUSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("focused");
9676 private static final PseudoClass SHOW_MNEMONICS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("show-mnemonics");
9677
9678 private static abstract class LazyTransformProperty
9679 extends ReadOnlyObjectProperty<Transform> {
9680
9681 protected static final int VALID = 0;
9682 protected static final int INVALID = 1;
9683 protected static final int VALIDITY_UNKNOWN = 2;
9684 protected int valid = INVALID;
9685
9686 private ExpressionHelper<Transform> helper;
9687
9688 private Transform transform;
9689 private boolean canReuse = false;
9690
9691 @Override
9692 public void addListener(InvalidationListener listener) {
9693 helper = ExpressionHelper.addListener(helper, this, listener);
9694 }
9695
9696 @Override
9697 public void removeListener(InvalidationListener listener) {
9698 helper = ExpressionHelper.removeListener(helper, listener);
9699 }
9700
9701 @Override
9702 public void addListener(ChangeListener<? super Transform> listener) {
9703 helper = ExpressionHelper.addListener(helper, this, listener);
9704 }
9705
9706 @Override
9707 public void removeListener(ChangeListener<? super Transform> listener) {
9708 helper = ExpressionHelper.removeListener(helper, listener);
9709 }
9710
9711 protected Transform getInternalValue() {
9712 if (valid == INVALID ||
9713 (valid == VALIDITY_UNKNOWN && computeValidity() == INVALID)) {
9714 transform = computeTransform(canReuse ? transform : null);
9715 canReuse = true;
9716 valid = validityKnown() ? VALID : VALIDITY_UNKNOWN;
9717 }
9718
9719 return transform;
9720 }
9721
9722 @Override
9723 public Transform get() {
9724 transform = getInternalValue();
9725 canReuse = false;
9726 return transform;
9727 }
9728
9729 public void validityUnknown() {
9730 if (valid == VALID) {
9731 valid = VALIDITY_UNKNOWN;
9732 }
9733 }
9734
9735 public void invalidate() {
9736 if (valid != INVALID) {
9737 valid = INVALID;
9738 ExpressionHelper.fireValueChangedEvent(helper);
9739 }
9740 }
9741
9742 protected abstract boolean validityKnown();
9743 protected abstract int computeValidity();
9744 protected abstract Transform computeTransform(Transform reuse);
9745 }
9746
9747 private static abstract class LazyBoundsProperty
9748 extends ReadOnlyObjectProperty<Bounds> {
9749 private ExpressionHelper<Bounds> helper;
9750 private boolean valid;
9751
9752 private Bounds bounds;
9753
9754 @Override
9755 public void addListener(InvalidationListener listener) {
9756 helper = ExpressionHelper.addListener(helper, this, listener);
9757 }
9758
9759 @Override
9760 public void removeListener(InvalidationListener listener) {
9761 helper = ExpressionHelper.removeListener(helper, listener);
9762 }
9763
9764 @Override
9765 public void addListener(ChangeListener<? super Bounds> listener) {
9766 helper = ExpressionHelper.addListener(helper, this, listener);
9767 }
9768
9769 @Override
9770 public void removeListener(ChangeListener<? super Bounds> listener) {
9771 helper = ExpressionHelper.removeListener(helper, listener);
9772 }
9773
9774 @Override
9775 public Bounds get() {
9776 if (!valid) {
9777 bounds = computeBounds();
9778 valid = true;
9779 }
9780
9781 return bounds;
9782 }
9783
9784 public void invalidate() {
9785 if (valid) {
9786 valid = false;
9787 ExpressionHelper.fireValueChangedEvent(helper);
9788 }
9789 }
9790
9791 protected abstract Bounds computeBounds();
9792 }
9793
9794 private static final BoundsAccessor boundsAccessor = (bounds, tx, node) -> node.getGeomBounds(bounds, tx);
9795
9796 /**
9797 * The accessible role for this {@code Node}.
9798 * <p>
9799 * The screen reader uses the role of a node to determine the
9800 * attributes and actions that are supported.
9801 *
9802 * @defaultValue {@link AccessibleRole#NODE}
9803 * @see AccessibleRole
9804 *
9805 * @since JavaFX 8u40
9806 */
9807 private ObjectProperty<AccessibleRole> accessibleRole;
9808
9809 public final void setAccessibleRole(AccessibleRole value) {
9810 if (value == null) value = AccessibleRole.NODE;
9811 accessibleRoleProperty().set(value);
9812 }
9813
9814 public final AccessibleRole getAccessibleRole() {
9815 if (accessibleRole == null) return AccessibleRole.NODE;
9816 return accessibleRoleProperty().get();
9817 }
9818
9819 public final ObjectProperty<AccessibleRole> accessibleRoleProperty() {
9820 if (accessibleRole == null) {
9821 accessibleRole = new SimpleObjectProperty<AccessibleRole>(this, "accessibleRole", AccessibleRole.NODE);
9822 }
9823 return accessibleRole;
9824 }
9825
9826 public final void setAccessibleRoleDescription(String value) {
9827 accessibleRoleDescriptionProperty().set(value);
9828 }
9829
9830 public final String getAccessibleRoleDescription() {
9831 if (accessibilityProperties == null) return null;
9832 if (accessibilityProperties.accessibleRoleDescription == null) return null;
9833 return accessibleRoleDescriptionProperty().get();
9834 }
9835
9836 /**
9837 * The role description of this {@code Node}.
9838 * <p>
9839 * Normally, when a role is provided for a node, the screen reader
9840 * speaks the role as well as the contents of the node. When this
9841 * value is set, it is possible to override the default. This is
9842 * useful because the set of roles is predefined. For example,
9843 * it is possible to set the role of a node to be a button, but
9844 * have the role description be arbitrary text.
9845 *
9846 * @return the role description of this {@code Node}.
9847 * @defaultValue null
9848 *
9849 * @since JavaFX 8u40
9850 */
9851 public final ObjectProperty<String> accessibleRoleDescriptionProperty() {
9852 return getAccessibilityProperties().getAccessibleRoleDescription();
9853 }
9854
9855 public final void setAccessibleText(String value) {
9856 accessibleTextProperty().set(value);
9857 }
9858
9859 public final String getAccessibleText() {
9860 if (accessibilityProperties == null) return null;
9861 if (accessibilityProperties.accessibleText == null) return null;
9862 return accessibleTextProperty().get();
9863 }
9864
9865 /**
9866 * The accessible text for this {@code Node}.
9867 * <p>
9868 * This property is used to set the text that the screen
9869 * reader will speak. If a node normally speaks text,
9870 * that text is overriden. For example, a button
9871 * usually speaks using the text in the control but will
9872 * no longer do this when this value is set.
9873 *
9874 * @return accessible text for this {@code Node}.
9875 * @defaultValue null
9876 *
9877 * @since JavaFX 8u40
9878 */
9879 public final ObjectProperty<String> accessibleTextProperty() {
9880 return getAccessibilityProperties().getAccessibleText();
9881 }
9882
9883 public final void setAccessibleHelp(String value) {
9884 accessibleHelpProperty().set(value);
9885 }
9886
9887 public final String getAccessibleHelp() {
9888 if (accessibilityProperties == null) return null;
9889 if (accessibilityProperties.accessibleHelp == null) return null;
9890 return accessibleHelpProperty().get();
9891 }
9892
9893 /**
9894 * The accessible help text for this {@code Node}.
9895 * <p>
9896 * The help text provides a more detailed description of the
9897 * accessible text for a node. By default, if the node has
9898 * a tool tip, this text is used.
9899 *
9900 * @return the accessible help text for this {@code Node}.
9901 * @defaultValue null
9902 *
9903 * @since JavaFX 8u40
9904 */
9905 public final ObjectProperty<String> accessibleHelpProperty() {
9906 return getAccessibilityProperties().getAccessibleHelp();
9907 }
9908
9909 AccessibilityProperties accessibilityProperties;
9910 private AccessibilityProperties getAccessibilityProperties() {
9911 if (accessibilityProperties == null) {
9912 accessibilityProperties = new AccessibilityProperties();
9913 }
9914 return accessibilityProperties;
9915 }
9916
9917 private class AccessibilityProperties {
9918 ObjectProperty<String> accessibleRoleDescription;
9919 ObjectProperty<String> getAccessibleRoleDescription() {
9920 if (accessibleRoleDescription == null) {
9921 accessibleRoleDescription = new SimpleObjectProperty<String>(Node.this, "accessibleRoleDescription", null);
9922 }
9923 return accessibleRoleDescription;
9924 }
9925 ObjectProperty<String> accessibleText;
9926 ObjectProperty<String> getAccessibleText() {
9927 if (accessibleText == null) {
9928 accessibleText = new SimpleObjectProperty<String>(Node.this, "accessibleText", null);
9929 }
9930 return accessibleText;
9931 }
9932 ObjectProperty<String> accessibleHelp;
9933 ObjectProperty<String> getAccessibleHelp() {
9934 if (accessibleHelp == null) {
9935 accessibleHelp = new SimpleObjectProperty<String>(Node.this, "accessibleHelp", null);
9936 }
9937 return accessibleHelp;
9938 }
9939 }
9940
9941 /**
9942 * This method is called by the assistive technology to request
9943 * the value for an attribute.
9944 * <p>
9945 * This method is commonly overridden by subclasses to implement
9946 * attributes that are required for a specific role.<br>
9947 * If a particular attribute is not handled, the superclass implementation
9948 * must be called.
9949 * </p>
9950 *
9951 * @param attribute the requested attribute
9952 * @param parameters optional list of parameters
9953 * @return the value for the requested attribute
9954 *
9955 * @see AccessibleAttribute
9956 *
9957 * @since JavaFX 8u40
9958 */
9959 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
9960 switch (attribute) {
9961 case ROLE: return getAccessibleRole();
9962 case ROLE_DESCRIPTION: return getAccessibleRoleDescription();
9963 case TEXT: return getAccessibleText();
9964 case HELP: return getAccessibleHelp();
9965 case PARENT: return getParent();
9966 case SCENE: return getScene();
9967 case BOUNDS: return localToScreen(getBoundsInLocal());
9968 case DISABLED: return isDisabled();
9969 case FOCUSED: return isFocused();
9970 case VISIBLE: return isVisible();
9971 case LABELED_BY: return labeledBy;
9972 default: return null;
9973 }
9974 }
9975
9976 /**
9977 * This method is called by the assistive technology to request the action
9978 * indicated by the argument should be executed.
9979 * <p>
9980 * This method is commonly overridden by subclasses to implement
9981 * action that are required for a specific role.<br>
9982 * If a particular action is not handled, the superclass implementation
9983 * must be called.
9984 * </p>
9985 *
9986 * @param action the action to execute
9987 * @param parameters optional list of parameters
9988 *
9989 * @see AccessibleAction
9990 *
9991 * @since JavaFX 8u40
9992 */
9993 public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
9994 switch (action) {
9995 case REQUEST_FOCUS:
9996 if (isFocusTraversable()) {
9997 requestFocus();
9998 }
9999 break;
10000 case SHOW_MENU: {
10001 Bounds b = getBoundsInLocal();
10002 Point2D pt = localToScreen(b.getMaxX(), b.getMaxY());
10003 ContextMenuEvent event =
10004 new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED,
10005 b.getMaxX(), b.getMaxY(), pt.getX(), pt.getY(),
10006 false, new PickResult(this, b.getMaxX(), b.getMaxY()));
10007 Event.fireEvent(this, event);
10008 break;
10009 }
10010 default:
10011 }
10012 }
10013
10014 /**
10015 * This method is called by the application to notify the assistive
10016 * technology that the value for an attribute has changed.
10017 *
10018 * @param attributes the attribute whose value has changed
10019 *
10020 * @see AccessibleAttribute
10021 *
10022 * @since JavaFX 8u40
10023 */
10024 public final void notifyAccessibleAttributeChanged(AccessibleAttribute attributes) {
10025 if (accessible == null) {
10026 Scene scene = getScene();
10027 if (scene != null) {
10028 accessible = scene.removeAccessible(this);
10029 }
10030 }
10031 if (accessible != null) {
10032 accessible.sendNotification(attributes);
10033 }
10034 }
10035
10036 Accessible accessible;
10037 Accessible getAccessible() {
10038 if (accessible == null) {
10039 Scene scene = getScene();
10040 /* It is possible the node was reparented and getAccessible()
10041 * is called before the pulse. Try to recycle the accessible
10042 * before creating a new one.
10043 * Note: this code relies that an accessible can never be on
10044 * more than one Scene#accMap. Thus, the only way
10045 * scene#removeAccessible() returns non-null is if the node
10046 * old scene and new scene are the same object.
10047 */
10048 if (scene != null) {
10049 accessible = scene.removeAccessible(this);
10050 }
10051 }
10052 if (accessible == null) {
10053 accessible = Application.GetApplication().createAccessible();
10054 accessible.setEventHandler(new Accessible.EventHandler() {
10055 @SuppressWarnings("deprecation")
10056 @Override public AccessControlContext getAccessControlContext() {
10057 Scene scene = getScene();
10058 if (scene == null) {
10059 /* This can happen during the release process of an accessible object. */
10060 throw new RuntimeException("Accessbility requested for node not on a scene");
10061 }
10062 if (scene.getPeer() != null) {
10063 return scene.getPeer().getAccessControlContext();
10064 } else {
10065 /* In some rare cases the accessible for a Node is needed
10066 * before its scene is made visible. For example, the screen reader
10067 * might ask a Menu for its ContextMenu before the ContextMenu
10068 * is made visible. That is a problem because the Window for the
10069 * ContextMenu is only created immediately before the first time
10070 * it is shown.
10071 */
10072 return scene.acc;
10073 }
10074 }
10075 @Override public Object getAttribute(AccessibleAttribute attribute, Object... parameters) {
10076 return queryAccessibleAttribute(attribute, parameters);
10077 }
10078 @Override public void executeAction(AccessibleAction action, Object... parameters) {
10079 executeAccessibleAction(action, parameters);
10080 }
10081 @Override public String toString() {
10082 String klassName = Node.this.getClass().getName();
10083 return klassName.substring(klassName.lastIndexOf('.')+1);
10084 }
10085 });
10086 }
10087 return accessible;
10088 }
10089
10090 void releaseAccessible() {
10091 Accessible acc = this.accessible;
10092 if (acc != null) {
10093 accessible = null;
10094 acc.dispose();
10095 }
10096 }
10097
10098 }