/* * Copyright (c) 2011, 2018, 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.layout; import java.util.List; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.scene.Node; /** * AnchorPane allows the edges of child nodes to be anchored to an offset from * the anchor pane's edges. If the anchor pane has a border and/or padding set, the * offsets will be measured from the inside edge of those insets. *

* AnchorPane lays out each managed child regardless of the child's visible property value; * unmanaged children are ignored for all layout calculations.

*

* AnchorPanes may be styled with backgrounds and borders using CSS. See * {@link javafx.scene.layout.Region Region} superclass for details.

* *

Anchor Constraints

*

* The application sets anchor constraints on each child to configure the anchors * on one or more sides. If a child is anchored on opposite sides (and is resizable), the * anchor pane will resize it to maintain both offsets, otherwise the anchor pane * will resize it to its preferred size. If in the former case (anchored on opposite * sides) and the child is not resizable, then only the top/left anchor will be honored. * AnchorPane provides a static method for setting each anchor constraint. *

* * * * * * * * *
AnchorPane Constraint Table
ConstraintTypeDescription
topAnchordoubledistance from the anchor pane's top insets to the child's top edge.
leftAnchordoubledistance from the anchor pane's left insets to the child's left edge.
bottomAnchordoubledistance from the anchor pane's bottom insets to the child's bottom edge.
rightAnchordoubledistance from the anchor pane's right insets to the child's right edge.
*

* AnchorPane Example: *

     AnchorPane anchorPane = new AnchorPane();
 *     // List should stretch as anchorPane is resized
 *     ListView list = new ListView();
 *     AnchorPane.setTopAnchor(list, 10.0);
 *     AnchorPane.setLeftAnchor(list, 10.0);
 *     AnchorPane.setRightAnchor(list, 65.0);
 *     // Button will float on right edge
 *     Button button = new Button("Add");
 *     AnchorPane.setTopAnchor(button, 10.0);
 *     AnchorPane.setRightAnchor(button, 10.0);
 *     anchorPane.getChildren().addAll(list, button);
 * 
* *

Resizable Range

*

* An anchor pane's parent will resize the anchor pane within the anchor pane's resizable range * during layout. By default the anchor pane computes this range based on its content * as outlined in the table below. *

* * * * * * * * * * * * *
AnchorPane Resize Table
widthheight
minimumleft/right insets plus width required to display children anchored at left/right with at least their min widthstop/bottom insets plus height required to display children anchored at top/bottom with at least their min heights
preferredleft/right insets plus width required to display children anchored at left/right with at least their pref widthstop/bottom insets plus height required to display children anchored at top/bottom with at least their pref heights
maximumDouble.MAX_VALUEDouble.MAX_VALUE
*

* An anchor pane's unbounded maximum width and height are an indication to the parent that * it may be resized beyond its preferred size to fill whatever space is assigned * to it. *

* AnchorPane provides properties for setting the size range directly. These * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the * application may set them to other values as needed: *

     anchorPane.setPrefSize(300, 300);
 * 
* Applications may restore the computed values by setting these properties back * to Region.USE_COMPUTED_SIZE. *

* AnchorPane does not clip its content by default, so it is possible that children's * bounds may extend outside its own bounds if the anchor pane is resized smaller * than its preferred size.

* * @since JavaFX 2.0 */ public class AnchorPane extends Pane { private static final String TOP_ANCHOR = "pane-top-anchor"; private static final String LEFT_ANCHOR = "pane-left-anchor"; private static final String BOTTOM_ANCHOR = "pane-bottom-anchor"; private static final String RIGHT_ANCHOR = "pane-right-anchor"; /******************************************************************** * BEGIN static methods ********************************************************************/ /** * Sets the top anchor for the child when contained by an anchor pane. * If set, the anchor pane will maintain the child's size and position so * that it's top is always offset by that amount from the anchor pane's top * content edge. * Setting the value to null will remove the constraint. * @param child the child node of an anchor pane * @param value the offset from the top of the anchor pane */ public static void setTopAnchor(Node child, Double value) { setConstraint(child, TOP_ANCHOR, value); } /** * Returns the child's top anchor constraint if set. * @param child the child node of an anchor pane * @return the offset from the top of the anchor pane or null if no top anchor was set */ public static Double getTopAnchor(Node child) { return (Double)getConstraint(child, TOP_ANCHOR); } /** * Sets the left anchor for the child when contained by an anchor pane. * If set, the anchor pane will maintain the child's size and position so * that it's left is always offset by that amount from the anchor pane's left * content edge. * Setting the value to null will remove the constraint. * @param child the child node of an anchor pane * @param value the offset from the left of the anchor pane */ public static void setLeftAnchor(Node child, Double value) { setConstraint(child, LEFT_ANCHOR, value); } /** * Returns the child's left anchor constraint if set. * @param child the child node of an anchor pane * @return the offset from the left of the anchor pane or null if no left anchor was set */ public static Double getLeftAnchor(Node child) { return (Double)getConstraint(child, LEFT_ANCHOR); } /** * Sets the bottom anchor for the child when contained by an anchor pane. * If set, the anchor pane will maintain the child's size and position so * that it's bottom is always offset by that amount from the anchor pane's bottom * content edge. * Setting the value to null will remove the constraint. * @param child the child node of an anchor pane * @param value the offset from the bottom of the anchor pane */ public static void setBottomAnchor(Node child, Double value) { setConstraint(child, BOTTOM_ANCHOR, value); } /** * Returns the child's bottom anchor constraint if set. * @param child the child node of an anchor pane * @return the offset from the bottom of the anchor pane or null if no bottom anchor was set */ public static Double getBottomAnchor(Node child) { return (Double)getConstraint(child, BOTTOM_ANCHOR); } /** * Sets the right anchor for the child when contained by an anchor pane. * If set, the anchor pane will maintain the child's size and position so * that it's right is always offset by that amount from the anchor pane's right * content edge. * Setting the value to null will remove the constraint. * @param child the child node of an anchor pane * @param value the offset from the right of the anchor pane */ public static void setRightAnchor(Node child, Double value) { setConstraint(child, RIGHT_ANCHOR, value); } /** * Returns the child's right anchor constraint if set. * @param child the child node of an anchor pane * @return the offset from the right of the anchor pane or null if no right anchor was set */ public static Double getRightAnchor(Node child) { return (Double)getConstraint(child, RIGHT_ANCHOR); } /** * Removes all anchor pane constraints from the child node. * @param child the child node */ public static void clearConstraints(Node child) { setTopAnchor(child, null); setRightAnchor(child, null); setBottomAnchor(child, null); setLeftAnchor(child, null); } /******************************************************************** * END static methods ********************************************************************/ /** * Creates an AnchorPane layout. */ public AnchorPane() { super(); } /** * Creates an AnchorPane layout with the given children. * @param children The initial set of children for this pane. * @since JavaFX 8.0 */ public AnchorPane(Node... children) { super(); getChildren().addAll(children); } @Override protected double computeMinWidth(double height) { return computeWidth(true, height); } @Override protected double computeMinHeight(double width) { return computeHeight(true, width); } @Override protected double computePrefWidth(double height) { return computeWidth(false, height); } @Override protected double computePrefHeight(double width) { return computeHeight(false, width); } private double computeWidth(final boolean minimum, final double height) { double max = 0; double contentHeight = height != -1 ? height - getInsets().getTop() - getInsets().getBottom() : -1; final List children = getManagedChildren(); for (Node child : children) { Double leftAnchor = getLeftAnchor(child); Double rightAnchor = getRightAnchor(child); double left = leftAnchor != null? leftAnchor : (rightAnchor != null? 0 : child.getLayoutBounds().getMinX() + child.getLayoutX()); double right = rightAnchor != null? rightAnchor : 0; double childHeight = -1; if (child.getContentBias() == Orientation.VERTICAL && contentHeight != -1) { // The width depends on the node's height! childHeight = computeChildHeight(child, getTopAnchor(child), getBottomAnchor(child), contentHeight, -1); } max = Math.max(max, left + (minimum && leftAnchor != null && rightAnchor != null? child.minWidth(childHeight) : computeChildPrefAreaWidth(child, -1, null, childHeight, false)) + right); } final Insets insets = getInsets(); return insets.getLeft() + max + insets.getRight(); } private double computeHeight(final boolean minimum, final double width) { double max = 0; double contentWidth = width != -1 ? width - getInsets().getLeft()- getInsets().getRight() : -1; final List children = getManagedChildren(); for (Node child : children) { Double topAnchor = getTopAnchor(child); Double bottomAnchor = getBottomAnchor(child); double top = topAnchor != null? topAnchor : (bottomAnchor != null? 0 : child.getLayoutBounds().getMinY() + child.getLayoutY()); double bottom = bottomAnchor != null? bottomAnchor : 0; double childWidth = -1; if (child.getContentBias() == Orientation.HORIZONTAL && contentWidth != -1) { childWidth = computeChildWidth(child, getLeftAnchor(child), getRightAnchor(child), contentWidth, -1); } max = Math.max(max, top + (minimum && topAnchor != null && bottomAnchor != null? child.minHeight(childWidth) : computeChildPrefAreaHeight(child, -1, null, childWidth)) + bottom); } final Insets insets = getInsets(); return insets.getTop() + max + insets.getBottom(); } private double computeChildWidth(Node child, Double leftAnchor, Double rightAnchor, double areaWidth, double height) { if (leftAnchor != null && rightAnchor != null && child.isResizable()) { final Insets insets = getInsets(); return areaWidth - insets.getLeft() - insets.getRight() - leftAnchor - rightAnchor; } return computeChildPrefAreaWidth(child, -1, Insets.EMPTY, height, true); } private double computeChildHeight(Node child, Double topAnchor, Double bottomAnchor, double areaHeight, double width) { if (topAnchor != null && bottomAnchor != null && child.isResizable()) { final Insets insets = getInsets(); return areaHeight - insets.getTop() - insets.getBottom() - topAnchor - bottomAnchor; } return computeChildPrefAreaHeight(child, -1, Insets.EMPTY, width); } @Override protected void layoutChildren() { final Insets insets = getInsets(); final List children = getManagedChildren(); for (Node child : children) { final Double topAnchor = getTopAnchor(child); final Double bottomAnchor = getBottomAnchor(child); final Double leftAnchor = getLeftAnchor(child); final Double rightAnchor = getRightAnchor(child); final Bounds childLayoutBounds = child.getLayoutBounds(); final Orientation bias = child.getContentBias(); double x = child.getLayoutX() + childLayoutBounds.getMinX(); double y = child.getLayoutY() + childLayoutBounds.getMinY(); double w; double h; if (bias == Orientation.VERTICAL) { // width depends on height // WARNING: The order of these calls is crucial, there is some // hidden ordering dependency here! h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1); w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), h); } else if (bias == Orientation.HORIZONTAL) { w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1); h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), w); } else { // bias may be null w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1); h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1); } if (leftAnchor != null) { x = insets.getLeft() + leftAnchor; } else if (rightAnchor != null) { x = getWidth() - insets.getRight() - rightAnchor - w; } if (topAnchor != null) { y = insets.getTop() + topAnchor; } else if (bottomAnchor != null) { y = getHeight() - insets.getBottom() - bottomAnchor - h; } child.resizeRelocate(x, y, w, h); } } }