javafx.scene.layout.AnchorPane Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.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
* Constraint Type Description
* topAnchor double distance from the anchor pane's top insets to the child's top edge.
* leftAnchor double distance from the anchor pane's left insets to the child's left edge.
* bottomAnchor double distance from the anchor pane's bottom insets to the child's bottom edge.
* rightAnchor double distance 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
* width height
* minimum
* left/right insets plus width required to display children anchored at left/right with at least their min widths
* top/bottom insets plus height required to display children anchored at top/bottom with at least their min heights
* preferred
* left/right insets plus width required to display children anchored at left/right with at least their pref widths
* top/bottom insets plus height required to display children anchored at top/bottom with at least their pref heights
* maximum
* Double.MAX_VALUE Double.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 its top is always offset by that amount from the anchor pane's top
* content edge.
* Setting the value to {@code 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 {@code 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 its left is always offset by that amount from the anchor pane's left
* content edge.
* Setting the value to {@code 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 {@code 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 its bottom is always offset by that amount from the anchor pane's bottom
* content edge.
* Setting the value to {@code 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 {@code 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 its right is always offset by that amount from the anchor pane's right
* content edge.
* Setting the value to {@code 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 {@code 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 {@code AnchorPane} layout.
*/
public AnchorPane() {
super();
}
/**
* Creates an {@code 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 = getSnappedLeftAnchor(child);
Double rightAnchor = getSnappedRightAnchor(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, getSnappedTopAnchor(child), getSnappedBottomAnchor(child), contentHeight, -1);
}
max = Math.max(max, left + (minimum && leftAnchor != null && rightAnchor != null?
child.minWidth(childHeight) : computeChildPrefAreaWidth(child, -1, null, childHeight, false)) + right);
}
return snappedLeftInset() + max + snappedRightInset();
}
private Double getSnappedTopAnchor(Node child) {
Double topAnchor = getTopAnchor(child);
if (topAnchor == null) {
return null;
}
return snapPositionY(topAnchor);
}
private Double getSnappedBottomAnchor(Node child) {
Double bottomAnchor = getBottomAnchor(child);
if (bottomAnchor == null) {
return null;
}
return snapPositionY(bottomAnchor);
}
private Double getSnappedLeftAnchor(Node child) {
Double leftAnchor = getLeftAnchor(child);
if (leftAnchor == null) {
return null;
}
return snapPositionX(leftAnchor);
}
private Double getSnappedRightAnchor(Node child) {
Double rightAnchor = getRightAnchor(child);
if (rightAnchor == null) {
return null;
}
return snapPositionX(rightAnchor);
}
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 = getSnappedTopAnchor(child);
Double bottomAnchor = getSnappedBottomAnchor(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, getSnappedLeftAnchor(child), getSnappedRightAnchor(child), contentWidth, -1);
}
max = Math.max(max, top + (minimum && topAnchor != null && bottomAnchor != null?
child.minHeight(childWidth) : computeChildPrefAreaHeight(child, -1, null, childWidth)) + bottom);
}
return snappedTopInset() + max + snappedBottomInset();
}
private double computeChildWidth(Node child, Double leftAnchor, Double rightAnchor, double areaWidth, double height) {
if (leftAnchor != null && rightAnchor != null && child.isResizable()) {
return areaWidth - snappedLeftInset() - snappedRightInset() - 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()) {
return areaHeight - snappedTopInset() - snappedBottomInset() - topAnchor - bottomAnchor;
}
return computeChildPrefAreaHeight(child, -1, Insets.EMPTY, width);
}
@Override protected void layoutChildren() {
final List children = getManagedChildren();
for (Node child : children) {
final Double topAnchor = getSnappedTopAnchor(child);
final Double bottomAnchor = getSnappedBottomAnchor(child);
final Double leftAnchor = getSnappedLeftAnchor(child);
final Double rightAnchor = getSnappedRightAnchor(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 = snappedLeftInset() + leftAnchor;
} else if (rightAnchor != null) {
x = getWidth() - snappedRightInset() - rightAnchor - w;
}
if (topAnchor != null) {
y = snappedTopInset() + topAnchor;
} else if (bottomAnchor != null) {
y = getHeight() - snappedBottomInset() - bottomAnchor - h;
}
child.resizeRelocate(x, y, w, h);
}
}
}