All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.scene.Parent Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene;

import com.sun.javafx.scene.traversal.ParentTraversalEngine;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.sun.javafx.util.TempState;
import com.sun.javafx.util.Utils;
import com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.collections.VetoableListDecorator;
import javafx.css.Selector;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.scene.CssFlags;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.sg.prism.NGGroup;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.javafx.scene.LayoutFlags;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.ParentHelper;
import com.sun.javafx.stage.WindowHelper;
import java.util.Collections;
import javafx.stage.Window;

/**
 * The base class for all nodes that have children in the scene graph.
 * 

* This class handles all hierarchical scene graph operations, including adding/removing * child nodes, marking branches dirty for layout and rendering, picking, * bounds calculations, and executing the layout pass on each pulse. *

* There are two direct concrete Parent subclasses *

    *
  • {@link Group} effects and transforms to be applied to a collection of child nodes.
  • *
  • {@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children.
  • *
* * @since JavaFX 2.0 */ public abstract class Parent extends Node { // package private for testing static final int DIRTY_CHILDREN_THRESHOLD = 10; // If set to true, generate a warning message whenever adding a node to a // parent if it is currently a child of another parent. private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn"); /** * Threshold when it's worth to populate list of removed children. */ private static final int REMOVED_CHILDREN_THRESHOLD = 20; /** * Do not populate list of removed children when its number exceeds threshold, * but mark whole parent dirty. */ private boolean removedChildrenOptimizationDisabled = false; static { // This is used by classes in different packages to get access to // private and package private methods. ParentHelper.setParentAccessor(new ParentHelper.ParentAccessor() { @Override public NGNode doCreatePeer(Node node) { return ((Parent) node).doCreatePeer(); } @Override public void doUpdatePeer(Node node) { ((Parent) node).doUpdatePeer(); } @Override public BaseBounds doComputeGeomBounds(Node node, BaseBounds bounds, BaseTransform tx) { return ((Parent) node).doComputeGeomBounds(bounds, tx); } @Override public boolean doComputeContains(Node node, double localX, double localY) { return ((Parent) node).doComputeContains(localX, localY); } @Override public void doProcessCSS(Node node) { ((Parent) node).doProcessCSS(); } @Override public void doPickNodeLocal(Node node, PickRay localPickRay, PickResultChooser result) { ((Parent) node).doPickNodeLocal(localPickRay, result); } @Override public boolean pickChildrenNode(Parent parent, PickRay pickRay, PickResultChooser result) { return parent.pickChildrenNode(pickRay, result); } @Override public void setTraversalEngine(Parent parent, ParentTraversalEngine value) { parent.setTraversalEngine(value); } @Override public ParentTraversalEngine getTraversalEngine(Parent parent) { return parent.getTraversalEngine(); } @Override public List doGetAllParentStylesheets(Parent parent) { return parent.doGetAllParentStylesheets(); } }); } /* * Note: This method MUST only be called via its accessor method. */ private void doUpdatePeer() { final NGGroup peer = getPeer(); if (Utils.assertionEnabled()) { List pgnodes = peer.getChildren(); if (pgnodes.size() != pgChildrenSize) { java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]"); } } if (isDirty(DirtyBits.PARENT_CHILDREN)) { // Whether a permutation, or children having been added or // removed, we'll want to clear out the PG side starting // from startIdx. We know that everything up to but not // including startIdx is identical between the FX and PG // sides, so we only need to update the remaining portion. peer.clearFrom(startIdx); for (int idx = startIdx; idx < children.size(); idx++) { peer.add(idx, children.get(idx).getPeer()); } if (removedChildrenOptimizationDisabled) { peer.markDirty(); removedChildrenOptimizationDisabled = false; } else { if (removed != null && !removed.isEmpty()) { for(int i = 0; i < removed.size(); i++) { peer.addToRemoved(removed.get(i).getPeer()); } } } if (removed != null) { removed.clear(); } pgChildrenSize = children.size(); startIdx = pgChildrenSize; } if (isDirty(DirtyBits.PARENT_CHILDREN_VIEW_ORDER)) { computeViewOrderChildren(); peer.setViewOrderChildren(viewOrderChildren); } if (Utils.assertionEnabled()) validatePG(); } /* ********************************************************************* * Scenegraph Structure * * * * Functions and variables related to the scenegraph structure, * * modifying the structure, and walking the structure. * * * **********************************************************************/ // Used to check for duplicate nodes private final Set childSet = new HashSet(); // starting child index from which we need to send the children to the PGGroup private int startIdx = 0; // double of children in the PGGroup as of the last update private int pgChildrenSize = 0; void validatePG() { boolean assertionFailed = false; final NGGroup peer = getPeer(); List pgnodes = peer.getChildren(); if (pgnodes.size() != children.size()) { java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]"); assertionFailed = true; } else { for (int idx = 0; idx < children.size(); idx++) { Node n = children.get(idx); if (n.getParent() != this) { java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent()); assertionFailed = true; } if (n.getPeer() != pgnodes.get(idx)) { java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]"); assertionFailed = true; } } } if (assertionFailed) { throw new java.lang.AssertionError("validation of PGGroup children failed"); } } void printSeq(String prefix, List nodes) { String str = prefix; for (Node nn : nodes) { str += nn + " "; } System.out.println(str); } /** * The viewOrderChildren is a list children sorted in decreasing viewOrder * order if it is not empty. Its size should always be equal to * children.size(). If viewOrderChildren is empty it implies that the * rendering order of the children is the same as the order in the children * list. */ private final List viewOrderChildren = new ArrayList(1); void markViewOrderChildrenDirty() { viewOrderChildren.clear(); NodeHelper.markDirty(this, DirtyBits.PARENT_CHILDREN_VIEW_ORDER); } private void computeViewOrderChildren() { boolean viewOrderSet = false; for (Node child : children) { double vo = child.getViewOrder(); if (!viewOrderSet && vo != 0) { viewOrderSet = true; } } viewOrderChildren.clear(); if (viewOrderSet) { viewOrderChildren.addAll(children); // Sort in descending order (or big-to-small order) Collections.sort(viewOrderChildren, (Node a, Node b) -> a.getViewOrder() < b.getViewOrder() ? 1 : a.getViewOrder() == b.getViewOrder() ? 0 : -1); } } // Call this method if children view order is needed for picking. // The returned list should be treated as read only. private List getOrderedChildren() { if (isDirty(DirtyBits.PARENT_CHILDREN_VIEW_ORDER)) { //Fix for JDK-8205092 computeViewOrderChildren(); } if (!viewOrderChildren.isEmpty()) { return viewOrderChildren; } return children; } // Variable used to indicate that the change to the children ObservableList is // a simple permutation as the result of a toFront or toBack operation. // We can avoid almost all of the processing of the on replace trigger in // this case. private boolean childrenTriggerPermutation = false; //accumulates all removed nodes between pulses, for dirty area calculation. private List removed; // set to true if either childRemoved or childAdded returns // true. These functions will indicate whether the geom // bounds for the parent have changed private boolean geomChanged; private boolean childSetModified; private final ObservableList children = new VetoableListDecorator(new TrackableObservableList() { protected void onChanged(Change c) { // proceed with updating the scene graph unmodifiableManagedChildren = null; boolean relayout = false; boolean viewOrderChildrenDirty = false; if (childSetModified) { while (c.next()) { int from = c.getFrom(); int to = c.getTo(); for (int i = from; i < to; ++i) { Node n = children.get(i); if (n.getParent() != null && n.getParent() != Parent.this) { if (warnOnAutoMove) { java.lang.System.err.println("WARNING added to a new parent without first removing it from its current"); java.lang.System.err.println(" parent. It will be automatically removed from its current parent."); java.lang.System.err.println(" node=" + n + " oldparent= " + n.getParent() + " newparent=" + this); } n.getParent().children.remove(n); if (warnOnAutoMove) { Thread.dumpStack(); } } } List removed = c.getRemoved(); int removedSize = removed.size(); for (int i = 0; i < removedSize; ++i) { final Node n = removed.get(i); if (n.isManaged()) { relayout = true; } } // Mark viewOrderChildrenDirty if there is modification to children list // and view order was set on one or more of the children prior to this change if (((removedSize > 0) || (to - from) > 0) && !viewOrderChildren.isEmpty()) { viewOrderChildrenDirty = true; } // update the parent and scene for each new node for (int i = from; i < to; ++i) { Node node = children.get(i); // Newly added node has view order set. if (node.getViewOrder() != 0) { viewOrderChildrenDirty = true; } if (node.isManaged() || (node instanceof Parent && ((Parent) node).layoutFlag != LayoutFlags.CLEAN)) { relayout = true; } node.setParent(Parent.this); node.setScenes(getScene(), getSubScene()); // assert !node.boundsChanged; if (node.isVisible()) { geomChanged = true; childIncluded(node); } } } // check to see if the number of children exceeds // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null. // If so, then we need to create dirtyChildren and // populate it. if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) { dirtyChildren = new ArrayList(2 * DIRTY_CHILDREN_THRESHOLD); // only bother populating children if geom has // changed, otherwise there is no need if (dirtyChildrenCount > 0) { int size = children.size(); for (int i = 0; i < size; ++i) { Node ch = children.get(i); if (ch.isVisible() && ch.boundsChanged) { dirtyChildren.add(ch); } } } } } else { // If childSet was not modified, we still need to check whether the permutation // did change the layout layout_loop:while (c.next()) { List removed = c.getRemoved(); for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) { if (removed.get(i).isManaged()) { relayout = true; break layout_loop; } } for (int i = c.getFrom(), to = c.getTo(); i < to; ++i) { if (children.get(i).isManaged()) { relayout = true; break layout_loop; } } } } // // Note that the styles of a child do not affect the parent or // its siblings. Thus, it is only necessary to reapply css to // the Node just added and not to this parent and all of its // children. So the following call to reapplyCSS was moved // to Node.parentProperty. The original comment and code were // purposely left here as documentation should there be any // question about how the code used to work and why the change // was made. // // if children have changed then I need to reapply // CSS from this node on down // reapplyCSS(); // // request layout if a Group subclass has overridden doLayout OR // if one of the new children needs layout, in which case need to ensure // the needsLayout flag is set all the way to the root so the next layout // pass will reach the child. if (relayout) { requestLayout(); } if (geomChanged) { NodeHelper.geomChanged(Parent.this); } // Note the starting index at which we need to update the // PGGroup on the next update, and mark the children dirty c.reset(); c.next(); if (startIdx > c.getFrom()) { startIdx = c.getFrom(); } NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN); // Force synchronization to include the handling of invisible node // so that removed list will get cleanup to prevent memory leak. NodeHelper.markDirty(Parent.this, DirtyBits.NODE_FORCE_SYNC); if (viewOrderChildrenDirty) { markViewOrderChildrenDirty(); } } }) { @Override protected void onProposedChange(final List newNodes, int[] toBeRemoved) { final Scene scene = getScene(); if (scene != null) { Window w = scene.getWindow(); if (w != null && WindowHelper.getPeer(w) != null) { Toolkit.getToolkit().checkFxUserThread(); } } geomChanged = false; long newLength = children.size() + newNodes.size(); int removedLength = 0; for (int i = 0; i < toBeRemoved.length; i += 2) { removedLength += toBeRemoved[i + 1] - toBeRemoved[i]; } newLength -= removedLength; // If the childrenTriggerPermutation flag is set, then we know it // is a simple permutation and no further checking is needed. if (childrenTriggerPermutation) { childSetModified = false; return; } // If the childrenTriggerPermutation flag is not set, then we will // check to see whether any element in the ObservableList has changed, // or whether the new ObservableList is a permutation on the existing // ObservableList. Note that even if the childrenModified flag is false, // we still have to check for duplicates. If it is a simple // permutation, we can avoid checking for cycles or other parents. childSetModified = true; if (newLength == childSet.size()) { childSetModified = false; for (int i = newNodes.size() - 1; i >= 0; --i ) { Node n = newNodes.get(i); if (!childSet.contains(n)) { childSetModified = true; break; } } } // Enforce scene graph invariants, and check for structural errors. // // 1. If a child has been added to this parent more than once, // then it is an error // // 2. If a child is a target of a clip, then it is an error. // // 3. If a node would cause a cycle, then it is an error. // // 4. If a node is null // // Note that if a node is the child of another parent, we will // implicitly remove the node from its former Parent after first // checking for errors. // iterate over the nodes that were removed and remove them from // the hash set. for (int i = 0; i < toBeRemoved.length; i += 2) { for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) { childSet.remove(children.get(j)); } } try { if (childSetModified) { // check individual children before duplication test // if done in this order, the exception is more specific for (int i = newNodes.size() - 1; i >= 0; --i ) { Node node = newNodes.get(i); if (node == null) { throw new NullPointerException( constructExceptionMessage( "child node is null", null)); } if (node.getClipParent() != null) { throw new IllegalArgumentException( constructExceptionMessage( "node already used as a clip", node)); } if (wouldCreateCycle(Parent.this, node)) { throw new IllegalArgumentException( constructExceptionMessage( "cycle detected", node)); } } } childSet.addAll(newNodes); if (childSet.size() != newLength) { throw new IllegalArgumentException( constructExceptionMessage( "duplicate children added", null)); } } catch (RuntimeException e) { //Return children to it's original state childSet.clear(); childSet.addAll(children); // rethrow throw e; } // Done with error checking if (!childSetModified) { return; } // iterate over the nodes that were removed and clear their // parent and scene. Add to them also to removed list for further // dirty regions calculation. if (removed == null) { removed = new ArrayList(); } if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD || !isTreeVisible()) { //do not populate too many children in removed list removedChildrenOptimizationDisabled = true; } for (int i = 0; i < toBeRemoved.length; i += 2) { for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) { Node old = children.get(j); final Scene oldScene = old.getScene(); if (oldScene != null) { oldScene.generateMouseExited(old); } if (dirtyChildren != null) { dirtyChildren.remove(old); } if (old.isVisible()) { geomChanged = true; childExcluded(old); } if (old.getParent() == Parent.this) { old.setParent(null); old.setScenes(null, null); } // Do not add node with null scene to the removed list. // It will not be processed in the list and its memory // will not be freed. if (scene != null && !removedChildrenOptimizationDisabled) { removed.add(old); } } } } private String constructExceptionMessage( String cause, Node offendingNode) { final StringBuilder sb = new StringBuilder("Children: "); sb.append(cause); sb.append(": parent = ").append(Parent.this); if (offendingNode != null) { sb.append(", node = ").append(offendingNode); } return sb.toString(); } }; /** * A constant reference to an unmodifiable view of the children, such that every time * we ask for an unmodifiable list of children, we don't actually create a new * collection and return it. The memory overhead is pretty lightweight compared * to all the garbage we would otherwise generate. */ private final ObservableList unmodifiableChildren = FXCollections.unmodifiableObservableList(children); /** * A cached reference to the unmodifiable managed children of this Parent. This is * created whenever first asked for, and thrown away whenever children are added * or removed or when their managed state changes. This could be written * differently, such that this list is essentially a filtered copy of the * main children, but that additional overhead might not be worth it. */ private List unmodifiableManagedChildren = null; /** * Gets the list of children of this {@code Parent}. * *

* See the class documentation for {@link Node} for scene graph structure * restrictions on setting a {@link Parent}'s children list. * If these restrictions are violated by a change to the list of children, * the change is ignored and the previous value of the children list is * restored. An {@link IllegalArgumentException} is thrown in this case. * *

* If this {@link Parent} node is attached to a {@link Scene} attached to a {@link Window} * that is showning ({@link javafx.stage.Window#isShowing()}), then its * list of children must only be modified on the JavaFX Application Thread. * An {@link IllegalStateException} is thrown if this restriction is * violated. * *

* Note to subclasses: if you override this method, you must return from * your implementation the result of calling this super method. The actual * list instance returned from any getChildren() implementation must be * the list owned and managed by this Parent. The only typical purpose * for overriding this method is to promote the method to be public. * * @return the list of children of this {@code Parent}. */ protected ObservableList getChildren() { return children; } /** * Gets the list of children of this {@code Parent} as a read-only * list. * * @return read-only access to this parent's children ObservableList */ public ObservableList getChildrenUnmodifiable() { return unmodifiableChildren; } /** * Gets the list of all managed children of this {@code Parent}. * * @param the type of the children nodes * @return list of all managed children in this parent */ protected List getManagedChildren() { if (unmodifiableManagedChildren == null) { unmodifiableManagedChildren = new ArrayList(); for (int i=0, max=children.size(); i)unmodifiableManagedChildren; } /** * Called by Node whenever its managed state may have changed, this * method will cause the view of managed children to be updated * such that it properly includes or excludes this child. */ final void managedChildChanged() { requestLayout(); unmodifiableManagedChildren = null; } // implementation of Node.toFront function final void toFront(Node node) { if (Utils.assertionEnabled()) { if (!childSet.contains(node)) { throw new java.lang.AssertionError( "specified node is not in the list of children"); } } if (children.get(children.size() - 1) != node) { childrenTriggerPermutation = true; try { children.remove(node); children.add(node); } finally { childrenTriggerPermutation = false; } } } // implementation of Node.toBack function final void toBack(Node node) { if (Utils.assertionEnabled()) { if (!childSet.contains(node)) { throw new java.lang.AssertionError( "specified node is not in the list of children"); } } if (children.get(0) != node) { childrenTriggerPermutation = true; try { children.remove(node); children.add(0, node); } finally { childrenTriggerPermutation = false; } } } @Override void scenesChanged(final Scene newScene, final SubScene newSubScene, final Scene oldScene, final SubScene oldSubScene) { if (oldScene != null && newScene == null) { // RT-34863 - clean up CSS cache when Parent is removed from scene-graph StyleManager.getInstance().forget(this); // Clear removed list on parent who is no longer in a scene if (removed != null) { removed.clear(); } } for (int i=0; i orderedChildren = getOrderedChildren(); for (int i = orderedChildren.size() - 1; i >= 0; i--) { orderedChildren.get(i).pickNode(pickRay, result); if (result.isClosed()) { return false; } } return true; } /* * Note: This method MUST only be called via its accessor method. */ private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) { double boundsDistance = intersectsBounds(pickRay); if (!Double.isNaN(boundsDistance) && pickChildrenNode(pickRay, result)) { if (isPickOnBounds()) { result.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance)); } } } @Override boolean isConnected() { return super.isConnected() || sceneRoot; } @Override public Node lookup(String selector) { Node n = super.lookup(selector); if (n == null) { for (int i=0, max=children.size(); i lookupAll(Selector selector, List results) { results = super.lookupAll(selector, results); for (int i=0, max=children.size(); i * If this parent is either a layout root or unmanaged, then it will be * added directly to the scene's dirty layout list, otherwise requestParentLayout * will be invoked. * @since JavaFX 8.0 */ public void requestLayout() { clearSizeCache(); markDirtyLayout(false, forceParentLayout); } private boolean forceParentLayout = false; /** * A package scope method used by Node and serves as a helper method for * requestLayout() (see above). If forceParentLayout is true it will * propagate this force layout flag to its parent. */ void requestLayout(boolean forceParentLayout) { boolean savedForceParentLayout = this.forceParentLayout; this.forceParentLayout = forceParentLayout; requestLayout(); this.forceParentLayout = savedForceParentLayout; } /** * Requests a layout pass of the parent to be performed before the next scene is * rendered. This is batched up asynchronously to happen once per * "pulse", or frame of animation. *

* This may be used when the current parent have changed it's min/max/preferred width/height, * but doesn't know yet if the change will lead to it's actual size change. This will be determined * when it's parent recomputes the layout with the new hints. */ protected final void requestParentLayout() { requestParentLayout(false); } /** * A package scope method used by Node and serves as a helper method for * requestParentLayout() (see above). If forceParentLayout is true it will * force a request layout call on its parent if its parent is not null. */ void requestParentLayout(boolean forceParentLayout) { if (!layoutRoot) { final Parent p = getParent(); if (p != null && (!p.performingLayout || forceParentLayout)) { p.requestLayout(); } } } void clearSizeCache() { if (sizeCacheClear) { return; } sizeCacheClear = true; prefWidthCache = -1; prefHeightCache = -1; minWidthCache = -1; minHeightCache = -1; } @Override public double prefWidth(double height) { if (height == -1) { if (prefWidthCache == -1) { prefWidthCache = computePrefWidth(-1); if (Double.isNaN(prefWidthCache) || prefWidthCache < 0) prefWidthCache = 0; sizeCacheClear = false; } return prefWidthCache; } else { double result = computePrefWidth(height); return Double.isNaN(result) || result < 0 ? 0 : result; } } @Override public double prefHeight(double width) { if (width == -1) { if (prefHeightCache == -1) { prefHeightCache = computePrefHeight(-1); if (Double.isNaN(prefHeightCache) || prefHeightCache < 0) prefHeightCache = 0; sizeCacheClear = false; } return prefHeightCache; } else { double result = computePrefHeight(width); return Double.isNaN(result) || result < 0 ? 0 : result; } } @Override public double minWidth(double height) { if (height == -1) { if (minWidthCache == -1) { minWidthCache = computeMinWidth(-1); if (Double.isNaN(minWidthCache) || minWidthCache < 0) minWidthCache = 0; sizeCacheClear = false; } return minWidthCache; } else { double result = computeMinWidth(height); return Double.isNaN(result) || result < 0 ? 0 : result; } } @Override public double minHeight(double width) { if (width == -1) { if (minHeightCache == -1) { minHeightCache = computeMinHeight(-1); if (Double.isNaN(minHeightCache) || minHeightCache < 0) minHeightCache = 0; sizeCacheClear = false; } return minHeightCache; } else { double result = computeMinHeight(width); return Double.isNaN(result) || result < 0 ? 0 : result; } } // PENDING_DOC_REVIEW /** * Calculates the preferred width of this {@code Parent}. The default * implementation calculates this width as the width of the area occupied * by its managed children when they are positioned at their * current positions at their preferred widths. * * @param height the height that should be used if preferred width depends * on it * @return the calculated preferred width */ protected double computePrefWidth(double height) { double minX = 0; double maxX = 0; for (int i=0, max=children.size(); i * Subclasses should override this function to layout content as needed. */ protected void layoutChildren() { for (int i=0, max=children.size(); iCSS Reference * Guide. */ private final ObservableList stylesheets = new TrackableObservableList() { @Override protected void onChanged(Change c) { final Scene scene = getScene(); if (scene != null) { // Notify the StyleManager if stylesheets change. This Parent's // styleManager will get recreated in NodeHelper.processCSS. StyleManager.getInstance().stylesheetsChanged(Parent.this, c); // RT-9784 - if stylesheet is removed, reset styled properties to // their initial value. c.reset(); while(c.next()) { if (c.wasRemoved() == false) { continue; } break; // no point in resetting more than once... } reapplyCSS(); } } }; /** * Gets an observable list of string URLs linking to the stylesheets to use * with this Parent's contents. See {@link Scene#getStylesheets()} for details. *

For additional information about using CSS * with the scene graph, see the CSS Reference * Guide.

* * @return the list of stylesheets to use with this Parent * @since JavaFX 2.1 */ public final ObservableList getStylesheets() { return stylesheets; } /* * This method recurses up the parent chain until parent is null. As the * stack unwinds, if the Parent has stylesheets, they are added to the * list. * * It is possible to override this method to stop the recursion. This allows * a Parent to have a set of stylesheets distinct from its Parent. * * Note: This method MUST only be called via its accessor method. */ // SB-dependency: RT-21247 has been filed to track this private List doGetAllParentStylesheets() { List list = null; final Parent myParent = getParent(); if (myParent != null) { // // recurse so that stylesheets of Parents closest to the root are // added to the list first. The ensures that declarations for // stylesheets further down the tree (closer to the leaf) have // a higer ordinal in the cascade. // list = ParentHelper.getAllParentStylesheets(myParent); } if (stylesheets != null && stylesheets.isEmpty() == false) { if (list == null) { list = new ArrayList(stylesheets.size()); } for (int n=0,nMax=stylesheets.size(); n 0) { child.cssFlag = CssFlags.UPDATE; } NodeHelper.processCSS(child); } } /* ********************************************************************* * Misc * * * * Initialization and other functions * * * **********************************************************************/ { // To initialize the class helper at the begining each constructor of this class ParentHelper.initHelper(this); } /** * Constructs a new {@code Parent}. */ protected Parent() { layoutFlag = LayoutFlags.NEEDS_LAYOUT; setAccessibleRole(AccessibleRole.PARENT); } private NGNode doCreatePeer() { return new NGGroup(); } @Override void nodeResolvedOrientationChanged() { for (int i = 0, max = children.size(); i < max; ++i) { children.get(i).parentResolvedOrientationInvalidated(); } } /* ************************************************************************* * * * Bounds Computations * * * * This code originated in GroupBoundsHelper (part of javafx-sg-common) * * but has been ported here to the FX side since we cannot rely on the PG * * side for computing the bounds (due to the decoupling of the two * * scenegraphs for threading and other purposes). * * * * Unfortunately, we cannot simply reuse GroupBoundsHelper without some * * major (and hacky) modification due to the fact that GroupBoundsHelper * * relies on PG state and we need to do similar things here that rely on * * core scenegraph state. Unfortunately, that means we made a port. * * * **************************************************************************/ private BaseBounds tmp = new RectBounds(); /** * The cached bounds for the Group. If the cachedBounds are invalid * then we have no history of what the bounds are, or were. */ private BaseBounds cachedBounds = new RectBounds(); /** * Indicates that the cachedBounds is invalid (or old) and need to be recomputed. * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero, * then when we recompute the cachedBounds we can consider the * values in cachedBounds to represent the last valid bounds for the group. * This is useful for several fast paths. */ private boolean cachedBoundsInvalid; /** * The number of dirty children which bounds haven't been incorporated * into the cached bounds yet. Can be used even when dirtyChildren is null. */ private int dirtyChildrenCount; /** * This set is used to track all of the children of this group which are * dirty. It is only used in cases where the number of children is > some * value (currently 10). For very wide trees, this can provide a very * important speed boost. For the sake of memory consumption, this is * null unless the number of children ever crosses the threshold where * it will be activated. */ private ArrayList dirtyChildren; private Node top; private Node left; private Node bottom; private Node right; private Node near; private Node far; private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { // If we have no children, our bounds are invalid if (children.isEmpty()) { return bounds.makeEmpty(); } if (tx.isTranslateOrIdentity()) { // this is a transform which is only doing translations, or nothing // at all (no scales, rotates, or shears) // so in this case we can easily use the cached bounds if (cachedBoundsInvalid) { recomputeBounds(); if (dirtyChildren != null) { dirtyChildren.clear(); } cachedBoundsInvalid = false; dirtyChildrenCount = 0; } if (!tx.isIdentity()) { bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()), (float)(cachedBounds.getMinY() + tx.getMyt()), (float)(cachedBounds.getMinZ() + tx.getMzt()), (float)(cachedBounds.getMaxX() + tx.getMxt()), (float)(cachedBounds.getMaxY() + tx.getMyt()), (float)(cachedBounds.getMaxZ() + tx.getMzt())); } else { bounds = bounds.deriveWithNewBounds(cachedBounds); } return bounds; } else { // there is a scale, shear, or rotation happening, so need to // do the full transform! double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE; double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE; boolean first = true; for (int i=0, max=children.size(); i dirtyNodes, int remainingDirtyNodes) { // fast path for untransformed bounds calculation if (cachedBounds.isEmpty()) { createCachedBounds(dirtyNodes); return true; } int invalidEdges = 0; if ((left == null) || left.boundsChanged) { invalidEdges |= LEFT_INVALID; } if ((top == null) || top.boundsChanged) { invalidEdges |= TOP_INVALID; } if ((near == null) || near.boundsChanged) { invalidEdges |= NEAR_INVALID; } if ((right == null) || right.boundsChanged) { invalidEdges |= RIGHT_INVALID; } if ((bottom == null) || bottom.boundsChanged) { invalidEdges |= BOTTOM_INVALID; } if ((far == null) || far.boundsChanged) { invalidEdges |= FAR_INVALID; } // These indicate the bounds of the Group as computed by this // function float minX = cachedBounds.getMinX(); float minY = cachedBounds.getMinY(); float minZ = cachedBounds.getMinZ(); float maxX = cachedBounds.getMaxX(); float maxY = cachedBounds.getMaxY(); float maxZ = cachedBounds.getMaxZ(); // this checks the newly added nodes first, so if dirtyNodes is the // whole children list, we can end early for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) { final Node node = dirtyNodes.get(i); if (node.boundsChanged) { // assert node.isVisible(); node.boundsChanged = false; --remainingDirtyNodes; tmp = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, tmp); if (!tmp.isEmpty()) { float tmpx = tmp.getMinX(); float tmpy = tmp.getMinY(); float tmpz = tmp.getMinZ(); float tmpx2 = tmp.getMaxX(); float tmpy2 = tmp.getMaxY(); float tmpz2 = tmp.getMaxZ(); // If this node forms an edge, then we will set it to be the // node for this edge and update the min/max values if (tmpx <= minX) { minX = tmpx; left = node; invalidEdges &= ~LEFT_INVALID; } if (tmpy <= minY) { minY = tmpy; top = node; invalidEdges &= ~TOP_INVALID; } if (tmpz <= minZ) { minZ = tmpz; near = node; invalidEdges &= ~NEAR_INVALID; } if (tmpx2 >= maxX) { maxX = tmpx2; right = node; invalidEdges &= ~RIGHT_INVALID; } if (tmpy2 >= maxY) { maxY = tmpy2; bottom = node; invalidEdges &= ~BOTTOM_INVALID; } if (tmpz2 >= maxZ) { maxZ = tmpz2; far = node; invalidEdges &= ~FAR_INVALID; } } } } if (invalidEdges != 0) { // failed to validate some edges return false; } cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ, maxX, maxY, maxZ); return true; } private void createCachedBounds(final List fromNodes) { // These indicate the bounds of the Group as computed by this function float minX, minY, minZ; float maxX, maxY, maxZ; final int nodeCount = fromNodes.size(); int i; // handle first visible non-empty node for (i = 0; i < nodeCount; ++i) { final Node node = fromNodes.get(i); node.boundsChanged = false; if (node.isVisible()) { tmp = node.getTransformedBounds( tmp, BaseTransform.IDENTITY_TRANSFORM); if (!tmp.isEmpty()) { left = top = near = right = bottom = far = node; break; } } } if (i == nodeCount) { left = top = near = right = bottom = far = null; cachedBounds.makeEmpty(); return; } minX = tmp.getMinX(); minY = tmp.getMinY(); minZ = tmp.getMinZ(); maxX = tmp.getMaxX(); maxY = tmp.getMaxY(); maxZ = tmp.getMaxZ(); // handle remaining visible non-empty nodes for (++i; i < nodeCount; ++i) { final Node node = fromNodes.get(i); node.boundsChanged = false; if (node.isVisible()) { tmp = node.getTransformedBounds( tmp, BaseTransform.IDENTITY_TRANSFORM); if (!tmp.isEmpty()) { final float tmpx = tmp.getMinX(); final float tmpy = tmp.getMinY(); final float tmpz = tmp.getMinZ(); final float tmpx2 = tmp.getMaxX(); final float tmpy2 = tmp.getMaxY(); final float tmpz2 = tmp.getMaxZ(); if (tmpx < minX) { minX = tmpx; left = node; } if (tmpy < minY) { minY = tmpy; top = node; } if (tmpz < minZ) { minZ = tmpz; near = node; } if (tmpx2 > maxX) { maxX = tmpx2; right = node; } if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; } if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; } } } } cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ, maxX, maxY, maxZ); } /** * Updates the bounds of this {@code Parent} and its children. */ @Override protected void updateBounds() { for (int i=0, max=children.size(); i test_getRemoved() { return removed; } /** * Note: The only user of this method is in unit test: * Parent_viewOrderChildren_sync_Test. */ List test_getViewOrderChildren() { return viewOrderChildren; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy