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 }