1 /*
   2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.layout;
  27 
  28 import java.util.List;
  29 import javafx.geometry.Bounds;
  30 import javafx.geometry.Insets;
  31 import javafx.geometry.Orientation;
  32 import javafx.scene.Node;
  33 
  34 /**
  35  * AnchorPane allows the edges of child nodes to be anchored to an offset from
  36  * the anchor pane's edges.  If the anchor pane has a border and/or padding set, the
  37  * offsets will be measured from the inside edge of those insets.
  38  * <p>
  39  * AnchorPane lays out each managed child regardless of the child's visible property value;
  40  * unmanaged children are ignored for all layout calculations.</p>
  41  * <p>
  42  * AnchorPanes may be styled with backgrounds and borders using CSS.  See
  43  * {@link javafx.scene.layout.Region Region} superclass for details.</p>
  44  *
  45  * <h3>Anchor Constraints</h3>
  46  * <p>
  47  * The application sets anchor constraints on each child to configure the anchors
  48  * on one or more sides.  If a child is anchored on opposite sides (and is resizable), the
  49  * anchor pane will resize it to maintain both offsets, otherwise the anchor pane
  50  * will resize it to its preferred size.  If in the former case (anchored on opposite
  51  * sides) and the child is not resizable, then only the top/left anchor will be honored.
  52  * AnchorPane provides a static method for setting each anchor constraint.
  53  * </p>
  54  *
  55  * <table border="1">
  56  * <caption>AnchorPane Constraint Table</caption>
  57  * <tr><th scope="col">Constraint</th><th scope="col">Type</th><th scope="col">Description</th></tr>
  58  * <tr><th scope="row">topAnchor</th><td>double</td><td>distance from the anchor pane's top insets to the child's top edge.</td></tr>
  59  * <tr><th scope="row">leftAnchor</th><td>double</td><td>distance from the anchor pane's left insets to the child's left edge.</td></tr>
  60  * <tr><th scope="row">bottomAnchor</th><td>double</td><td>distance from the anchor pane's bottom insets to the child's bottom edge.</td></tr>
  61  * <tr><th scope="row">rightAnchor</th><td>double</td><td>distance from the anchor pane's right insets to the child's right edge.</td></tr>
  62  * </table>
  63  * <p>
  64  * AnchorPane Example:
  65  * <pre><code>     AnchorPane anchorPane = new AnchorPane();
  66  *     // List should stretch as anchorPane is resized
  67  *     ListView list = new ListView();
  68  *    <b> AnchorPane.setTopAnchor(list, 10.0);
  69  *     AnchorPane.setLeftAnchor(list, 10.0);
  70  *     AnchorPane.setRightAnchor(list, 65.0);</b>
  71  *     // Button will float on right edge
  72  *     Button button = new Button("Add");
  73  *     <b>AnchorPane.setTopAnchor(button, 10.0);
  74  *     AnchorPane.setRightAnchor(button, 10.0);</b>
  75  *     anchorPane.getChildren().addAll(list, button);
  76  * </code></pre>
  77  *
  78  * <h3>Resizable Range</h3>
  79  * <p>
  80  * An anchor pane's parent will resize the anchor pane within the anchor pane's resizable range
  81  * during layout.   By default the anchor pane computes this range based on its content
  82  * as outlined in the table below.
  83  * </p>
  84  *
  85  * <table border="1">
  86  * <caption>AnchorPane Resize Table</caption>
  87  * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr>
  88  * <tr><th scope="row">minimum</th>
  89  * <td>left/right insets plus width required to display children anchored at left/right with at least their min widths</td>
  90  * <td>top/bottom insets plus height required to display children anchored at top/bottom with at least their min heights</td></tr>
  91  * <tr><th scope="row">preferred</th>
  92  * <td>left/right insets plus width required to display children anchored at left/right with at least their pref widths</td>
  93  * <td>top/bottom insets plus height required to display children anchored at top/bottom with at least their pref heights</td></tr>
  94  * <tr><th scope="row">maximum</th>
  95  * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
  96  * </table>
  97  * <p>
  98  * An anchor pane's unbounded maximum width and height are an indication to the parent that
  99  * it may be resized beyond its preferred size to fill whatever space is assigned
 100  * to it.
 101  * <p>
 102  * AnchorPane provides properties for setting the size range directly.  These
 103  * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the
 104  * application may set them to other values as needed:
 105  * <pre><code>     <b>anchorPane.setPrefSize(300, 300);</b>
 106  * </code></pre>
 107  * Applications may restore the computed values by setting these properties back
 108  * to Region.USE_COMPUTED_SIZE.
 109  * <p>
 110  * AnchorPane does not clip its content by default, so it is possible that children's
 111  * bounds may extend outside its own bounds if the anchor pane is resized smaller
 112  * than its preferred size.</p>
 113  *
 114  * @since JavaFX 2.0
 115  */
 116 public class AnchorPane extends Pane {
 117 
 118     private static final String TOP_ANCHOR = "pane-top-anchor";
 119     private static final String LEFT_ANCHOR = "pane-left-anchor";
 120     private static final String BOTTOM_ANCHOR = "pane-bottom-anchor";
 121     private static final String RIGHT_ANCHOR = "pane-right-anchor";
 122 
 123     /********************************************************************
 124      *  BEGIN static methods
 125      ********************************************************************/
 126 
 127     /**
 128      * Sets the top anchor for the child when contained by an anchor pane.
 129      * If set, the anchor pane will maintain the child's size and position so
 130      * that it's top is always offset by that amount from the anchor pane's top
 131      * content edge.
 132      * Setting the value to null will remove the constraint.
 133      * @param child the child node of an anchor pane
 134      * @param value the offset from the top of the anchor pane
 135      */
 136     public static void setTopAnchor(Node child, Double value) {
 137         setConstraint(child, TOP_ANCHOR, value);
 138     }
 139 
 140     /**
 141      * Returns the child's top anchor constraint if set.
 142      * @param child the child node of an anchor pane
 143      * @return the offset from the top of the anchor pane or null if no top anchor was set
 144      */
 145     public static Double getTopAnchor(Node child) {
 146         return (Double)getConstraint(child, TOP_ANCHOR);
 147     }
 148 
 149     /**
 150      * Sets the left anchor for the child when contained by an anchor pane.
 151      * If set, the anchor pane will maintain the child's size and position so
 152      * that it's left is always offset by that amount from the anchor pane's left
 153      * content edge.
 154      * Setting the value to null will remove the constraint.
 155      * @param child the child node of an anchor pane
 156      * @param value the offset from the left of the anchor pane
 157      */
 158     public static void setLeftAnchor(Node child, Double value) {
 159         setConstraint(child, LEFT_ANCHOR, value);
 160     }
 161 
 162     /**
 163      * Returns the child's left anchor constraint if set.
 164      * @param child the child node of an anchor pane
 165      * @return the offset from the left of the anchor pane or null if no left anchor was set
 166      */
 167     public static Double getLeftAnchor(Node child) {
 168         return (Double)getConstraint(child, LEFT_ANCHOR);
 169     }
 170 
 171     /**
 172      * Sets the bottom anchor for the child when contained by an anchor pane.
 173      * If set, the anchor pane will maintain the child's size and position so
 174      * that it's bottom is always offset by that amount from the anchor pane's bottom
 175      * content edge.
 176      * Setting the value to null will remove the constraint.
 177      * @param child the child node of an anchor pane
 178      * @param value the offset from the bottom of the anchor pane
 179      */
 180     public static void setBottomAnchor(Node child, Double value) {
 181         setConstraint(child, BOTTOM_ANCHOR, value);
 182     }
 183 
 184     /**
 185      * Returns the child's bottom anchor constraint if set.
 186      * @param child the child node of an anchor pane
 187      * @return the offset from the bottom of the anchor pane or null if no bottom anchor was set
 188      */
 189     public static Double getBottomAnchor(Node child) {
 190         return (Double)getConstraint(child, BOTTOM_ANCHOR);
 191     }
 192 
 193     /**
 194      * Sets the right anchor for the child when contained by an anchor pane.
 195      * If set, the anchor pane will maintain the child's size and position so
 196      * that it's right is always offset by that amount from the anchor pane's right
 197      * content edge.
 198      * Setting the value to null will remove the constraint.
 199      * @param child the child node of an anchor pane
 200      * @param value the offset from the right of the anchor pane
 201      */
 202     public static void setRightAnchor(Node child, Double value) {
 203         setConstraint(child, RIGHT_ANCHOR, value);
 204     }
 205 
 206     /**
 207      * Returns the child's right anchor constraint if set.
 208      * @param child the child node of an anchor pane
 209      * @return the offset from the right of the anchor pane or null if no right anchor was set
 210      */
 211     public static Double getRightAnchor(Node child) {
 212         return (Double)getConstraint(child, RIGHT_ANCHOR);
 213     }
 214 
 215     /**
 216      * Removes all anchor pane constraints from the child node.
 217      * @param child the child node
 218      */
 219     public static void clearConstraints(Node child) {
 220         setTopAnchor(child, null);
 221         setRightAnchor(child, null);
 222         setBottomAnchor(child, null);
 223         setLeftAnchor(child, null);
 224     }
 225 
 226     /********************************************************************
 227      *  END static methods
 228      ********************************************************************/
 229 
 230     /**
 231      * Creates an AnchorPane layout.
 232      */
 233     public AnchorPane() {
 234         super();
 235     }
 236 
 237     /**
 238      * Creates an AnchorPane layout with the given children.
 239      * @param children    The initial set of children for this pane.
 240      * @since JavaFX 8.0
 241      */
 242     public AnchorPane(Node... children) {
 243         super();
 244         getChildren().addAll(children);
 245     }
 246 
 247     @Override protected double computeMinWidth(double height) {
 248         return computeWidth(true, height);
 249     }
 250 
 251     @Override protected double computeMinHeight(double width) {
 252         return computeHeight(true, width);
 253     }
 254 
 255     @Override protected double computePrefWidth(double height) {
 256         return computeWidth(false, height);
 257     }
 258 
 259     @Override protected double computePrefHeight(double width) {
 260         return computeHeight(false, width);
 261     }
 262 
 263     private double computeWidth(final boolean minimum, final double height) {
 264         double max = 0;
 265         double contentHeight = height != -1 ? height - getInsets().getTop() - getInsets().getBottom() : -1;
 266         final List<Node> children = getManagedChildren();
 267         for (Node child : children) {
 268             Double leftAnchor = getLeftAnchor(child);
 269             Double rightAnchor = getRightAnchor(child);
 270 
 271             double left = leftAnchor != null? leftAnchor :
 272                 (rightAnchor != null? 0 : child.getLayoutBounds().getMinX() + child.getLayoutX());
 273             double right = rightAnchor != null? rightAnchor : 0;
 274             double childHeight = -1;
 275             if (child.getContentBias() == Orientation.VERTICAL && contentHeight != -1) {
 276                 // The width depends on the node's height!
 277                 childHeight = computeChildHeight(child, getTopAnchor(child), getBottomAnchor(child), contentHeight, -1);
 278             }
 279             max = Math.max(max, left + (minimum && leftAnchor != null && rightAnchor != null?
 280                     child.minWidth(childHeight) : computeChildPrefAreaWidth(child, -1, null, childHeight, false)) + right);
 281         }
 282 
 283         final Insets insets = getInsets();
 284         return insets.getLeft() + max + insets.getRight();
 285     }
 286 
 287     private double computeHeight(final boolean minimum, final double width) {
 288         double max = 0;
 289         double contentWidth = width != -1 ? width - getInsets().getLeft()- getInsets().getRight() : -1;
 290         final List<Node> children = getManagedChildren();
 291         for (Node child : children) {
 292             Double topAnchor = getTopAnchor(child);
 293             Double bottomAnchor = getBottomAnchor(child);
 294 
 295             double top = topAnchor != null? topAnchor :
 296                 (bottomAnchor != null? 0 : child.getLayoutBounds().getMinY() + child.getLayoutY());
 297             double bottom = bottomAnchor != null? bottomAnchor : 0;
 298             double childWidth = -1;
 299             if (child.getContentBias() == Orientation.HORIZONTAL && contentWidth != -1) {
 300                 childWidth = computeChildWidth(child, getLeftAnchor(child), getRightAnchor(child), contentWidth, -1);
 301             }
 302             max = Math.max(max, top + (minimum && topAnchor != null && bottomAnchor != null?
 303                     child.minHeight(childWidth) : computeChildPrefAreaHeight(child, -1, null, childWidth)) + bottom);
 304         }
 305 
 306         final Insets insets = getInsets();
 307         return insets.getTop() + max + insets.getBottom();
 308     }
 309 
 310     private double computeChildWidth(Node child, Double leftAnchor, Double rightAnchor, double areaWidth, double height) {
 311         if (leftAnchor != null && rightAnchor != null && child.isResizable()) {
 312             final Insets insets = getInsets();
 313             return areaWidth - insets.getLeft() - insets.getRight() - leftAnchor - rightAnchor;
 314         }
 315         return computeChildPrefAreaWidth(child, -1, Insets.EMPTY, height, true);
 316     }
 317 
 318     private double computeChildHeight(Node child, Double topAnchor, Double bottomAnchor, double areaHeight, double width) {
 319         if (topAnchor != null && bottomAnchor != null && child.isResizable()) {
 320             final Insets insets = getInsets();
 321             return areaHeight - insets.getTop() - insets.getBottom() - topAnchor - bottomAnchor;
 322         }
 323         return computeChildPrefAreaHeight(child, -1, Insets.EMPTY, width);
 324     }
 325 
 326     @Override protected void layoutChildren() {
 327         final Insets insets = getInsets();
 328         final List<Node> children = getManagedChildren();
 329         for (Node child : children) {
 330             final Double topAnchor = getTopAnchor(child);
 331             final Double bottomAnchor = getBottomAnchor(child);
 332             final Double leftAnchor = getLeftAnchor(child);
 333             final Double rightAnchor = getRightAnchor(child);
 334             final Bounds childLayoutBounds = child.getLayoutBounds();
 335             final Orientation bias = child.getContentBias();
 336 
 337             double x = child.getLayoutX() + childLayoutBounds.getMinX();
 338             double y = child.getLayoutY() + childLayoutBounds.getMinY();
 339             double w;
 340             double h;
 341 
 342             if (bias == Orientation.VERTICAL) {
 343                 // width depends on height
 344                 // WARNING: The order of these calls is crucial, there is some
 345                 // hidden ordering dependency here!
 346                 h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1);
 347                 w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), h);
 348             } else if (bias == Orientation.HORIZONTAL) {
 349                 w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1);
 350                 h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), w);
 351             } else {
 352                 // bias may be null
 353                 w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1);
 354                 h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1);
 355             }
 356 
 357             if (leftAnchor != null) {
 358                 x = insets.getLeft() + leftAnchor;
 359             } else if (rightAnchor != null) {
 360                 x = getWidth() - insets.getRight() - rightAnchor - w;
 361             }
 362 
 363             if (topAnchor != null) {
 364                 y = insets.getTop() + topAnchor;
 365             } else if (bottomAnchor != null) {
 366                 y = getHeight() - insets.getBottom() - bottomAnchor - h;
 367             }
 368 
 369             child.resizeRelocate(x, y, w, h);
 370         }
 371     }
 372 }