javafx.scene.layout.BorderPane Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* Copyright (c) 2011, 2013, 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 com.sun.javafx.geom.Vec2d;
import java.util.List;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ListChangeListener;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import static javafx.scene.layout.Region.positionInArea;
/**
* BorderPane lays out children in top, left, right, bottom, and center positions.
*
*
*
* The top and bottom children will be resized to their preferred heights and
* extend the width of the border pane. The left and right children will be resized
* to their preferred widths and extend the length between the top and bottom nodes.
* And the center node will be resized to fill the available space in the middle.
* Any of the positions may be null.
*
* Example:
* BorderPane borderPane = new BorderPane();
* ToolBar toolbar = new ToolBar();
* HBox statusbar = new HBox();
* Node appContent = new AppContentNode();
* borderPane.setTop(toolbar);
* borderPane.setCenter(appContent);
* borderPane.setBottom(statusbar);
*
*
* Borderpanes may be styled with backgrounds and borders using CSS. See
* {@link javafx.scene.layout.Region Region} superclass for details.
*
*
* BorderPane honors the minimum, preferred, and maximum sizes of its children.
* If the child's resizable range prevents it from be resized to fit within its
* position, it will be aligned relative to the space using a default alignment
* as follows:
*
* - top: Pos.TOP_LEFT
* - bottom: Pos.BOTTOM_LEFT
* - left: Pos.TOP_LEFT
* - right: Pos.TOP_RIGHT
* - center: Pos.CENTER
*
* See "Optional Layout Constraints" on how to customize these alignments.
*
*
* BorderPane lays out each child set in the five positions regardless of the child's
* visible property value; unmanaged children are ignored.
*
* Resizable Range
* BorderPane is commonly used as the root of a {@link javafx.scene.Scene Scene},
* in which case its size will track the size of the scene. If the scene or stage
* size has not been directly set by the application, the scene size will be
* initialized to the border pane's preferred size. However, if a border pane
* has a parent other than the scene, that parent will resize the border pane within
* the border pane's resizable range during layout. By default the border pane
* computes this range based on its content as outlined in the table below.
*
*
* width height
* minimum
* left/right insets plus width required to display right/left children at their pref widths and top/bottom/center with at least their min widths
* top/bottom insets plus height required to display top/bottom children at their pref heights and left/right/center with at least their min heights
* preferred
* left/right insets plus width required to display top/right/bottom/left/center children with at least their pref widths
* top/bottom insets plus height required to display top/right/bottom/left/center children with at least their pref heights
* maximum
* Double.MAX_VALUE Double.MAX_VALUE
*
*
* A border 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.
*
* BorderPane 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:
*
* borderPane.setPrefSize(500,400);
*
* Applications may restore the computed values by setting these properties back
* to Region.USE_COMPUTED_SIZE.
*
* BorderPane does not clip its content by default, so it is possible that childrens'
* bounds may extend outside its own bounds if a child's min size prevents it from
* being fit within it space.
*
* Optional Layout Constraints
*
* An application may set constraints on individual children to customize BorderPane's layout.
* For each constraint, BorderPane provides a static method for setting it on the child.
*
*
* Constraint Type Description
* alignment javafx.geometry.Pos The alignment of the child within its area of the border pane.
* margin javafx.geometry.Insets Margin space around the outside of the child.
*
*
* Example:
*
ListView list = new ListView();
* BorderPane.setAlignment(list, Pos.TOP_LEFT);
* BorderPane.setMargin(list, new Insets(12,12,12,12));
* borderPane.setCenter(list);
*
*
* @since JavaFX 2.0
*/
public class BorderPane extends Pane {
/********************************************************************
* BEGIN static methods
********************************************************************/
private static final String MARGIN = "borderpane-margin";
private static final String ALIGNMENT = "borderpane-alignment";
/**
* Sets the alignment for the child when contained by a border pane.
* If set, will override the border pane's default alignment for the child's position.
* Setting the value to null will remove the constraint.
* @param child the child node of a border pane
* @param value the alignment position for the child
*/
public static void setAlignment(Node child, Pos value) {
setConstraint(child, ALIGNMENT, value);
}
/**
* Returns the child's alignment constraint if set.
* @param child the child node of a border pane
* @return the alignment position for the child or null if no alignment was set
*/
public static Pos getAlignment(Node child) {
return (Pos)getConstraint(child, ALIGNMENT);
}
/**
* Sets the margin for the child when contained by a border pane.
* If set, the border pane will lay it out with the margin space around it.
* Setting the value to null will remove the constraint.
* @param child the child node of a border pane
* @param value the margin of space around the child
*/
public static void setMargin(Node child, Insets value) {
setConstraint(child, MARGIN, value);
}
/**
* Returns the child's margin constraint if set.
* @param child the child node of a border pane
* @return the margin for the child or null if no margin was set
*/
public static Insets getMargin(Node child) {
return (Insets)getConstraint(child, MARGIN);
}
// convenience for handling null margins
private static Insets getNodeMargin(Node child) {
Insets margin = getMargin(child);
return margin != null ? margin : Insets.EMPTY;
}
/**
* Removes all border pane constraints from the child node.
* @param child the child node
*/
public static void clearConstraints(Node child) {
setAlignment(child, null);
setMargin(child, null);
}
/********************************************************************
* END static methods
********************************************************************/
/**
* Creates a BorderPane layout.
*/
public BorderPane() {
super();
}
/**
* Creates an BorderPane layout with the given Node as the center of the BorderPane.
* @param center The node to set as the center of the BorderPane.
* @since JavaFX 8.0
*/
public BorderPane(Node center) {
super();
setCenter(center);
}
/**
* Creates an BorderPane layout with the given Nodes to use for each of the main
* layout areas of the Border Pane. The top, right, bottom, and left nodes are listed
* in clockwise order.
* @param center The node to set as the center of the BorderPane.
* @param top The node to set as the top of the BorderPane.
* @param right The node to set as the right of the BorderPane.
* @param bottom The node to set as the bottom of the BorderPane.
* @param left The node to set as the left of the BorderPane.
* @since JavaFX 8.0
*/
public BorderPane(Node center, Node top, Node right, Node bottom, Node left) {
super();
setCenter(center);
setTop(top);
setRight(right);
setBottom(bottom);
setLeft(left);
}
/**
* The node placed in the center of this border pane.
* If resizable, it will be resized fill the center of the border pane
* between the top, bottom, left, and right nodes. If the node cannot be
* resized to fill the center space (it's not resizable or its max size prevents
* it) then it will be center aligned unless the child's alignment constraint
* has been set.
*/
public final ObjectProperty centerProperty() {
if (center == null) {
center = new BorderPositionProperty("center");
}
return center;
}
private ObjectProperty center;
public final void setCenter(Node value) { centerProperty().set(value); }
public final Node getCenter() { return center == null ? null : center.get(); }
/**
* The node placed on the top edge of this border pane.
* If resizable, it will be resized to its preferred height and it's width
* will span the width of the border pane. If the node cannot be
* resized to fill the top space (it's not resizable or its max size prevents
* it) then it will be aligned top-left within the space unless the child's
* alignment constraint has been set.
*/
public final ObjectProperty topProperty() {
if (top == null) {
top = new BorderPositionProperty("top");
}
return top;
}
private ObjectProperty top;
public final void setTop(Node value) { topProperty().set(value); }
public final Node getTop() { return top == null ? null : top.get(); }
/**
* The node placed on the bottom edge of this border pane.
* If resizable, it will be resized to its preferred height and it's width
* will span the width of the border pane. If the node cannot be
* resized to fill the bottom space (it's not resizable or its max size prevents
* it) then it will be aligned bottom-left within the space unless the child's
* alignment constraint has been set.
*/
public final ObjectProperty bottomProperty() {
if (bottom == null) {
bottom = new BorderPositionProperty("bottom");
}
return bottom;
}
private ObjectProperty bottom;
public final void setBottom(Node value) { bottomProperty().set(value); }
public final Node getBottom() { return bottom == null ? null : bottom.get(); }
/**
* The node placed on the left edge of this border pane.
* If resizable, it will be resized to its preferred width and it's height
* will span the height of the border pane between the top and bottom nodes.
* If the node cannot be resized to fill the left space (it's not resizable
* or its max size prevents it) then it will be aligned top-left within the space
* unless the child's alignment constraint has been set.
*/
public final ObjectProperty leftProperty() {
if (left == null) {
left = new BorderPositionProperty("left");
}
return left;
}
private ObjectProperty left;
public final void setLeft(Node value) { leftProperty().set(value); }
public final Node getLeft() { return left == null ? null : left.get(); }
/**
* The node placed on the right edge of this border pane.
* If resizable, it will be resized to its preferred width and it's height
* will span the height of the border pane between the top and bottom nodes.
* If the node cannot be resized to fill the right space (it's not resizable
* or its max size prevents it) then it will be aligned top-right within the space
* unless the child's alignment constraint has been set.
*/
public final ObjectProperty rightProperty() {
if (right == null) {
right = new BorderPositionProperty("right");
}
return right;
}
private ObjectProperty right;
public final void setRight(Node value) { rightProperty().set(value); }
public final Node getRight() { return right == null ? null : right.get(); }
/**
* @return null unless the center, right, bottom, left or top has a content bias.
*/
@Override public Orientation getContentBias() {
final Node c = getCenter();
if (c != null && c.isManaged() && c.getContentBias() != null) {
return c.getContentBias();
}
final Node r = getRight();
if (r != null && r.isManaged() && r.getContentBias() == Orientation.VERTICAL) {
return r.getContentBias();
}
final Node l = getLeft();
if (l != null && l.isManaged() && l.getContentBias() == Orientation.VERTICAL) {
return l.getContentBias();
}
final Node b = getBottom();
if (b != null && b.isManaged() && b.getContentBias() == Orientation.HORIZONTAL) {
return b.getContentBias();
}
final Node t = getTop();
if (t != null && t.isManaged() && t.getContentBias() == Orientation.HORIZONTAL) {
return t.getContentBias();
}
return null;
}
@Override protected double computeMinWidth(double height) {
double topMinWidth = getAreaWidth(getTop(), -1, true);
double bottomMinWidth = getAreaWidth(getBottom(), -1, true);
double leftPrefWidth;
double rightPrefWidth;
double centerMinWidth;
if (height != -1 && (childHasContentBias(getLeft(), Orientation.VERTICAL) ||
childHasContentBias(getRight(), Orientation.VERTICAL) ||
childHasContentBias(getCenter(), Orientation.VERTICAL))) {
double topPrefHeight = getAreaHeight(getTop(), -1, false);
double bottomPrefHeight = getAreaHeight(getBottom(), -1, false);
double middleAreaHeight = Math.max(0, height - topPrefHeight - bottomPrefHeight);
leftPrefWidth = getAreaWidth(getLeft(), middleAreaHeight, false);
rightPrefWidth = getAreaWidth(getRight(), middleAreaHeight, false);
centerMinWidth = getAreaWidth(getCenter(), middleAreaHeight, true);
} else {
leftPrefWidth = getAreaWidth(getLeft(), -1, false);
rightPrefWidth = getAreaWidth(getRight(), -1, false);
centerMinWidth = getAreaWidth(getCenter(), -1, true);
}
final Insets insets = getInsets();
return insets.getLeft() +
Math.max(leftPrefWidth + centerMinWidth + rightPrefWidth, Math.max(topMinWidth,bottomMinWidth)) +
insets.getRight();
}
@Override protected double computeMinHeight(double width) {
final Insets insets = getInsets();
// Bottom and top are always at their pref height
double topPrefHeight = getAreaHeight(getTop(), width, false);
double bottomPrefHeight = getAreaHeight(getBottom(), width, false);
double leftMinHeight = getAreaHeight(getLeft(), -1, true);
double rightMinHeight = getAreaHeight(getRight(), -1, true);
double centerMinHeight;
if (width != -1 && childHasContentBias(getCenter(), Orientation.HORIZONTAL)) {
double leftPrefWidth = getAreaWidth(getLeft(), -1, false);
double rightPrefWidth = getAreaWidth(getRight(), -1, false);
centerMinHeight = getAreaHeight(getCenter(),
Math.max(0, width - leftPrefWidth - rightPrefWidth) , true);
} else {
centerMinHeight = getAreaHeight(getCenter(), -1, true);
}
double middleAreaMinHeigh = Math.max(centerMinHeight, Math.max(rightMinHeight, leftMinHeight));
return insets.getTop() + topPrefHeight + middleAreaMinHeigh + bottomPrefHeight + insets.getBottom();
}
@Override protected double computePrefWidth(double height) {
double topPrefWidth = getAreaWidth(getTop(), -1, false);
double bottomPrefWidth = getAreaWidth(getBottom(), -1, false);
double leftPrefWidth;
double rightPrefWidth;
double centerPrefWidth;
if ( height != -1 && (childHasContentBias(getLeft(), Orientation.VERTICAL) ||
childHasContentBias(getRight(), Orientation.VERTICAL) ||
childHasContentBias(getCenter(), Orientation.VERTICAL))) {
double topPrefHeight = getAreaHeight(getTop(), -1, false);
double bottomPrefHeight = getAreaHeight(getBottom(), -1, false);
double middleAreaHeight = Math.max(0, height - topPrefHeight - bottomPrefHeight);
leftPrefWidth = getAreaWidth(getLeft(), middleAreaHeight, false);
rightPrefWidth = getAreaWidth(getRight(), middleAreaHeight, false);
centerPrefWidth = getAreaWidth(getCenter(), middleAreaHeight, false);
} else {
leftPrefWidth = getAreaWidth(getLeft(), -1, false);
rightPrefWidth = getAreaWidth(getRight(), -1, false);
centerPrefWidth = getAreaWidth(getCenter(), -1, false);
}
final Insets insets = getInsets();
return insets.getLeft() +
Math.max(leftPrefWidth + centerPrefWidth + rightPrefWidth, Math.max(topPrefWidth,bottomPrefWidth)) +
insets.getRight();
}
@Override protected double computePrefHeight(double width) {
final Insets insets = getInsets();
double topPrefHeight = getAreaHeight(getTop(), width, false);
double bottomPrefHeight = getAreaHeight(getBottom(), width, false);
double leftPrefHeight = getAreaHeight(getLeft(), -1, false);
double rightPrefHeight = getAreaHeight(getRight(), -1, false);
double centerPrefHeight;
if (width != -1 && childHasContentBias(getCenter(), Orientation.HORIZONTAL)) {
double leftPrefWidth = getAreaWidth(getLeft(), -1, false);
double rightPrefWidth = getAreaWidth(getRight(), -1, false);
centerPrefHeight = getAreaHeight(getCenter(),
Math.max(0, width - leftPrefWidth - rightPrefWidth) , false);
} else {
centerPrefHeight = getAreaHeight(getCenter(), -1, false);
}
double middleAreaPrefHeigh = Math.max(centerPrefHeight, Math.max(rightPrefHeight, leftPrefHeight));
return insets.getTop() + topPrefHeight + middleAreaPrefHeigh + bottomPrefHeight + insets.getBottom();
}
@Override protected void layoutChildren() {
final Insets insets = getInsets();
double width = getWidth();
double height = getHeight();
final Orientation bias = getContentBias();
if (bias == null) {
final double minWidth = minWidth(-1);
final double minHeight = minHeight(-1);
width = width < minWidth ? minWidth : width;
height = height < minHeight ? minHeight : height;
} else if (bias == Orientation.HORIZONTAL) {
final double minWidth = minWidth(-1);
width = width < minWidth ? minWidth : width;
final double minHeight = minHeight(width);
height = height < minHeight ? minHeight : height;
} else {
final double minHeight = minHeight(-1);
height = height < minHeight ? minHeight : height;
final double minWidth = minWidth(height);
width = width < minWidth ? minWidth : width;
}
final double insideX = insets.getLeft();
final double insideY = insets.getTop();
final double insideWidth = width - insideX - insets.getRight();
final double insideHeight = height - insideY - insets.getBottom();
final Node c = getCenter();
final Node r = getRight();
final Node b = getBottom();
final Node l = getLeft();
final Node t = getTop();
double topHeight = 0;
if (t != null && t.isManaged()) {
Insets topMargin = getNodeMargin(t);
double adjustedWidth = adjustWidthByMargin(insideWidth, topMargin);
double adjustedHeight = adjustHeightByMargin(insideHeight, topMargin);
topHeight = snapSize(t.prefHeight(adjustedWidth));
topHeight = Math.min(topHeight, adjustedHeight);
Vec2d result = boundedNodeSizeWithBias(t, adjustedWidth,
topHeight, true, true, TEMP_VEC2D);
topHeight = snapSize(result.y);
t.resize(snapSize(result.x), topHeight);
topHeight = snapSpace(topMargin.getBottom()) + topHeight + snapSpace(topMargin.getTop());
Pos alignment = getAlignment(t);
positionInArea(t, insideX, insideY, insideWidth, topHeight, 0/*ignore baseline*/,
topMargin,
alignment != null? alignment.getHpos() : HPos.LEFT,
alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
}
double bottomHeight = 0;
if (b != null && b.isManaged()) {
Insets bottomMargin = getNodeMargin(b);
double adjustedWidth = adjustWidthByMargin(insideWidth, bottomMargin);
double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight, bottomMargin);
bottomHeight = snapSize(b.prefHeight(adjustedWidth));
bottomHeight = Math.min(bottomHeight, adjustedHeight);
Vec2d result = boundedNodeSizeWithBias(b, adjustedWidth,
bottomHeight, true, true, TEMP_VEC2D);
bottomHeight = snapSize(result.y);
b.resize(snapSize(result.x), bottomHeight);
bottomHeight = snapSpace(bottomMargin.getBottom()) + bottomHeight + snapSpace(bottomMargin.getTop());
Pos alignment = getAlignment(b);
positionInArea(b, insideX, insideY + insideHeight - bottomHeight,
insideWidth, bottomHeight, 0/*ignore baseline*/,
bottomMargin,
alignment != null? alignment.getHpos() : HPos.LEFT,
alignment != null? alignment.getVpos() : VPos.BOTTOM, isSnapToPixel());
}
double leftWidth = 0;
if (l != null && l.isManaged()) {
Insets leftMargin = getNodeMargin(l);
double adjustedWidth = adjustWidthByMargin(insideWidth, leftMargin);
double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight - bottomHeight, leftMargin); // ????
leftWidth = snapSize(l.prefWidth(adjustedHeight));
leftWidth = Math.min(leftWidth, adjustedWidth);
Vec2d result = boundedNodeSizeWithBias(l, leftWidth, adjustedHeight,
true, true, TEMP_VEC2D);
leftWidth = snapSize(result.x);
l.resize(leftWidth, snapSize(result.y));
leftWidth = snapSpace(leftMargin.getLeft()) + leftWidth + snapSpace(leftMargin.getRight());
Pos alignment = getAlignment(l);
positionInArea(l, insideX, insideY + topHeight,
leftWidth, insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
leftMargin,
alignment != null? alignment.getHpos() : HPos.LEFT,
alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
}
double rightWidth = 0;
if (r != null && r.isManaged()) {
Insets rightMargin = getNodeMargin(r);
double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth, rightMargin);
double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight - bottomHeight, rightMargin);
rightWidth = snapSize(r.prefWidth(adjustedHeight));
rightWidth = Math.min(rightWidth, adjustedWidth);
Vec2d result = boundedNodeSizeWithBias(r, rightWidth, adjustedHeight,
true, true, TEMP_VEC2D);
rightWidth = snapSize(result.x);
r.resize(rightWidth, snapSize(result.y));
rightWidth = snapSpace(rightMargin.getLeft()) + rightWidth + snapSpace(rightMargin.getRight());
Pos alignment = getAlignment(r);
positionInArea(r, insideX + insideWidth - rightWidth, insideY + topHeight,
rightWidth, insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
rightMargin,
alignment != null? alignment.getHpos() : HPos.RIGHT,
alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
}
if (c != null && c.isManaged()) {
Pos alignment = getAlignment(c);
layoutInArea(c, insideX + leftWidth, insideY + topHeight,
insideWidth - leftWidth - rightWidth,
insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
getNodeMargin(c),
alignment != null? alignment.getHpos() : HPos.CENTER,
alignment != null? alignment.getVpos() : VPos.CENTER);
}
}
private double getAreaWidth(Node child, double height, boolean minimum) {
if (child != null && child.isManaged()) {
Insets margin = getNodeMargin(child);
return minimum ? computeChildMinAreaWidth(child, -1, margin, height):
computeChildPrefAreaWidth(child, -1, margin, height);
}
return 0;
}
private double getAreaHeight(Node child, double width, boolean minimum) {
if (child != null && child.isManaged()) {
Insets margin = getNodeMargin(child);
return minimum ? computeChildMinAreaHeight(child, -1, margin, width):
computeChildPrefAreaHeight(child, -1, margin, width);
}
return 0;
}
private boolean childHasContentBias(Node child, Orientation orientation) {
if (child != null && child.isManaged()) {
return child.getContentBias() == orientation;
}
return false;
}
/***************************************************************************
* *
* Private Inner Class *
* *
**************************************************************************/
private final class BorderPositionProperty extends ObjectPropertyBase {
private Node oldValue = null;
private final String propertyName;
private boolean isBeingInvalidated;
BorderPositionProperty(String propertyName) {
this.propertyName = propertyName;
getChildren().addListener(new ListChangeListener() {
@Override
public void onChanged(ListChangeListener.Change extends Node> c) {
if (oldValue == null || isBeingInvalidated) {
return;
}
while (c.next()) {
if (c.wasRemoved()) {
List extends Node> removed = c.getRemoved();
for (int i = 0, sz = removed.size(); i < sz; ++i) {
if (removed.get(i) == oldValue) {
oldValue = null; // Do not remove again in invalidated
set(null);
}
}
}
}
}
});
}
@Override
protected void invalidated() {
final List children = getChildren();
isBeingInvalidated = true;
try {
if (oldValue != null) {
children.remove(oldValue);
}
final Node _value = get();
this.oldValue = _value;
if (_value != null) {
children.add(_value);
}
} finally {
isBeingInvalidated = false;
}
}
@Override
public Object getBean() {
return BorderPane.this;
}
@Override
public String getName() {
return propertyName;
}
}
}