1 /*
   2  * Copyright (c) 2011, 2013, 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 com.sun.javafx.geom.Vec2d;
  29 import java.util.List;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.beans.property.ObjectPropertyBase;
  32 import javafx.collections.ListChangeListener;
  33 import javafx.geometry.HPos;
  34 import javafx.geometry.Insets;
  35 import javafx.geometry.Orientation;
  36 import javafx.geometry.Pos;
  37 import javafx.geometry.VPos;
  38 import javafx.scene.Node;
  39 import static javafx.scene.layout.Region.positionInArea;
  40 
  41 
  42 /**
  43  * BorderPane lays out children in top, left, right, bottom, and center positions.
  44  *
  45  * <p> <img src="doc-files/borderpane.png"/> </p>
  46  *
  47  * The top and bottom children will be resized to their preferred heights and
  48  * extend the width of the border pane.  The left and right children will be resized
  49  * to their preferred widths and extend the length between the top and bottom nodes.
  50  * And the center node will be resized to fill the available space in the middle.
  51  * Any of the positions may be null.
  52  *
  53  * Example:
  54  * <pre><code>     <b>BorderPane borderPane = new BorderPane();</b>
  55  *     ToolBar toolbar = new ToolBar();
  56  *     HBox statusbar = new HBox();
  57  *     Node appContent = new AppContentNode();
  58  *     <b>borderPane.setTop(toolbar);
  59  *     borderPane.setCenter(appContent);
  60  *     borderPane.setBottom(statusbar);</b>
  61  * </code></pre>
  62  * <p>
  63  * Borderpanes may be styled with backgrounds and borders using CSS.  See
  64  * {@link javafx.scene.layout.Region Region} superclass for details.</p>
  65  *
  66  * <p>
  67  * BorderPane honors the minimum, preferred, and maximum sizes of its children.
  68  * If the child's resizable range prevents it from be resized to fit within its
  69  * position, it will be aligned relative to the space using a default alignment
  70  * as follows:
  71  * <ul>
  72  * <li>top: Pos.TOP_LEFT</li>
  73  * <li>bottom: Pos.BOTTOM_LEFT</li>
  74  * <li>left: Pos.TOP_LEFT</li>
  75  * <li>right: Pos.TOP_RIGHT</li>
  76  * <li>center: Pos.CENTER</li>
  77  * </ul>
  78  * See "Optional Layout Constraints" on how to customize these alignments.
  79  *
  80  * <p>
  81  * BorderPane lays out each child set in the five positions regardless of the child's
  82  * visible property value; unmanaged children are ignored.</p>
  83  *
  84  * <h4>Resizable Range</h4>
  85  * BorderPane is commonly used as the root of a {@link javafx.scene.Scene Scene},
  86  * in which case its size will track the size of the scene.  If the scene or stage
  87  * size has not been directly set by the application, the scene size will be
  88  * initialized to the border pane's preferred size.   However, if a border pane
  89  * has a parent other than the scene, that parent will resize the border pane within
  90  * the border pane's resizable range during layout.   By default the border pane
  91  * computes this range based on its content as outlined in the table below.
  92  * <p>
  93  * <table border="1">
  94  * <tr><td></td><th>width</th><th>height</th></tr>
  95  * <tr><th>minimum</th>
  96  * <td>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</td>
  97  * <td>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</td></tr>
  98  * <tr><th>preferred</th>
  99  * <td>left/right insets plus width required to display top/right/bottom/left/center children with at least their pref widths</td>
 100  * <td>top/bottom insets plus height required to display top/right/bottom/left/center children with at least their pref heights</td></tr>
 101  * <tr><th>maximum</th>
 102  * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
 103  * </table>
 104  * <p>
 105  * A border pane's unbounded maximum width and height are an indication to the parent that
 106  * it may be resized beyond its preferred size to fill whatever space is assigned to it.
 107  * <p>
 108  * BorderPane provides properties for setting the size range directly.  These
 109  * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the
 110  * application may set them to other values as needed:
 111  * <pre><code>
 112  *     <b>borderPane.setPrefSize(500,400);</b>
 113  * </code></pre>
 114  * Applications may restore the computed values by setting these properties back
 115  * to Region.USE_COMPUTED_SIZE.
 116  * <p>
 117  * BorderPane does not clip its content by default, so it is possible that childrens'
 118  * bounds may extend outside its own bounds if a child's min size prevents it from
 119  * being fit within it space.</p>
 120  *
 121  * <h4>Optional Layout Constraints</h4>
 122  *
 123  * An application may set constraints on individual children to customize BorderPane's layout.
 124  * For each constraint, BorderPane provides a static method for setting it on the child.
 125  * <p>
 126  * <table border="1">
 127  * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr>
 128  * <tr><td>alignment</td><td>javafx.geometry.Pos</td><td>The alignment of the child within its area of the border pane.</td></tr>
 129  * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr>
 130  * </table>
 131  * <p>
 132  * Example:
 133  * <pre><code>     ListView list = new ListView();
 134  *     <b>BorderPane.setAlignment(list, Pos.TOP_LEFT);
 135  *     BorderPane.setMargin(list, new Insets(12,12,12,12));</b>
 136  *     borderPane.setCenter(list);
 137  * </code></pre>
 138  *
 139  * @since JavaFX 2.0
 140  */
 141 public class BorderPane extends Pane {
 142     /********************************************************************
 143      *  BEGIN static methods
 144      ********************************************************************/
 145 
 146     private static final String MARGIN = "borderpane-margin";
 147     private static final String ALIGNMENT = "borderpane-alignment";
 148 
 149     /**
 150      * Sets the alignment for the child when contained by a border pane.
 151      * If set, will override the border pane's default alignment for the child's position.
 152      * Setting the value to null will remove the constraint.
 153      * @param child the child node of a border pane
 154      * @param value the alignment position for the child
 155      */
 156     public static void setAlignment(Node child, Pos value) {
 157         setConstraint(child, ALIGNMENT, value);
 158     }
 159 
 160     /**
 161      * Returns the child's alignment constraint if set.
 162      * @param child the child node of a border pane
 163      * @return the alignment position for the child or null if no alignment was set
 164      */
 165     public static Pos getAlignment(Node child) {
 166         return (Pos)getConstraint(child, ALIGNMENT);
 167     }
 168 
 169     /**
 170      * Sets the margin for the child when contained by a border pane.
 171      * If set, the border pane will lay it out with the margin space around it.
 172      * Setting the value to null will remove the constraint.
 173      * @param child the child node of a border pane
 174      * @param value the margin of space around the child
 175      */
 176     public static void setMargin(Node child, Insets value) {
 177         setConstraint(child, MARGIN, value);
 178     }
 179 
 180     /**
 181      * Returns the child's margin constraint if set.
 182      * @param child the child node of a border pane
 183      * @return the margin for the child or null if no margin was set
 184      */
 185     public static Insets getMargin(Node child) {
 186         return (Insets)getConstraint(child, MARGIN);
 187     }
 188 
 189     // convenience for handling null margins
 190     private static Insets getNodeMargin(Node child) {
 191         Insets margin = getMargin(child);
 192         return margin != null ? margin : Insets.EMPTY;
 193     }
 194 
 195     /**
 196      * Removes all border pane constraints from the child node.
 197      * @param child the child node
 198      */
 199     public static void clearConstraints(Node child) {
 200         setAlignment(child, null);
 201         setMargin(child, null);
 202     }
 203 
 204     /********************************************************************
 205      *  END static methods
 206      ********************************************************************/
 207 
 208     /**
 209      * Creates a BorderPane layout.
 210      */
 211     public BorderPane() {
 212         super();
 213     }
 214 
 215     /**
 216      * Creates an BorderPane layout with the given Node as the center of the BorderPane.
 217      * @param center The node to set as the center of the BorderPane.
 218      * @since JavaFX 8.0
 219      */
 220     public BorderPane(Node center) {
 221         super();
 222         setCenter(center);
 223     }
 224 
 225     /**
 226      * Creates an BorderPane layout with the given Nodes to use for each of the main
 227      * layout areas of the Border Pane. The top, right, bottom, and left nodes are listed
 228      * in clockwise order.
 229      * @param center The node to set as the center of the BorderPane.
 230      * @param top The node to set as the top of the BorderPane.
 231      * @param right The node to set as the right of the BorderPane.
 232      * @param bottom The node to set as the bottom of the BorderPane.
 233      * @param left The node to set as the left of the BorderPane.
 234      * @since JavaFX 8.0
 235      */
 236     public BorderPane(Node center, Node top, Node right, Node bottom, Node left) {
 237         super();
 238         setCenter(center);
 239         setTop(top);
 240         setRight(right);
 241         setBottom(bottom);
 242         setLeft(left);
 243     }
 244 
 245     /**
 246      * The node placed in the center of this border pane.
 247      * If resizable, it will be resized fill the center of the border pane
 248      * between the top, bottom, left, and right nodes.   If the node cannot be
 249      * resized to fill the center space (it's not resizable or its max size prevents
 250      * it) then it will be center aligned unless the child's alignment constraint
 251      * has been set.
 252      */
 253     public final ObjectProperty<Node> centerProperty() {
 254         if (center == null) {
 255             center = new BorderPositionProperty("center");
 256         }
 257         return center;
 258     }
 259     private ObjectProperty<Node> center;
 260     public final void setCenter(Node value) { centerProperty().set(value); }
 261     public final Node getCenter() { return center == null ? null : center.get(); }
 262 
 263     /**
 264      * The node placed on the top edge of this border pane.
 265      * If resizable, it will be resized to its preferred height and it's width
 266      * will span the width of the border pane.  If the node cannot be
 267      * resized to fill the top space (it's not resizable or its max size prevents
 268      * it) then it will be aligned top-left within the space unless the child's
 269      * alignment constraint has been set.
 270      */
 271     public final ObjectProperty<Node> topProperty() {
 272         if (top == null) {
 273             top = new BorderPositionProperty("top");
 274         }
 275         return top;
 276     }
 277     private ObjectProperty<Node> top;
 278     public final void setTop(Node value) { topProperty().set(value); }
 279     public final Node getTop() { return top == null ? null : top.get();  }
 280 
 281     /**
 282      * The node placed on the bottom edge of this border pane.
 283      * If resizable, it will be resized to its preferred height and it's width
 284      * will span the width of the border pane.  If the node cannot be
 285      * resized to fill the bottom space (it's not resizable or its max size prevents
 286      * it) then it will be aligned bottom-left within the space unless the child's
 287      * alignment constraint has been set.
 288      */
 289     public final ObjectProperty<Node> bottomProperty() {
 290         if (bottom == null) {
 291             bottom = new BorderPositionProperty("bottom");
 292         }
 293         return bottom;
 294     }
 295     private ObjectProperty<Node> bottom;
 296     public final void setBottom(Node value) { bottomProperty().set(value); }
 297     public final Node getBottom() { return bottom == null ? null : bottom.get();  }
 298 
 299     /**
 300      * The node placed on the left edge of this border pane.
 301      * If resizable, it will be resized to its preferred width and it's height
 302      * will span the height of the border pane between the top and bottom nodes.
 303      * If the node cannot be resized to fill the left space (it's not resizable
 304      * or its max size prevents it) then it will be aligned top-left within the space
 305      * unless the child's alignment constraint has been set.
 306      */
 307     public final ObjectProperty<Node> leftProperty() {
 308         if (left == null) {
 309             left = new BorderPositionProperty("left");
 310         }
 311         return left;
 312     }
 313     private ObjectProperty<Node> left;
 314     public final void setLeft(Node value) { leftProperty().set(value); }
 315     public final Node getLeft() { return left == null ? null : left.get(); }
 316 
 317     /**
 318      * The node placed on the right edge of this border pane.
 319      * If resizable, it will be resized to its preferred width and it's height
 320      * will span the height of the border pane between the top and bottom nodes.
 321      * If the node cannot be resized to fill the right space (it's not resizable
 322      * or its max size prevents it) then it will be aligned top-right within the space
 323      * unless the child's alignment constraint has been set.
 324      */
 325     public final ObjectProperty<Node> rightProperty() {
 326         if (right == null) {
 327             right = new BorderPositionProperty("right");
 328         }
 329         return right;
 330     }
 331     private ObjectProperty<Node> right;
 332     public final void setRight(Node value) { rightProperty().set(value); }
 333     public final Node getRight() { return right == null ? null : right.get(); }
 334 
 335     /**
 336      * @return null unless the center, right, bottom, left or top has a content bias.
 337      */
 338     @Override public Orientation getContentBias() {
 339         final Node c = getCenter();
 340         if (c != null && c.isManaged() && c.getContentBias() != null) {
 341             return c.getContentBias();
 342         }
 343 
 344         final Node r = getRight();
 345         if (r != null && r.isManaged() && r.getContentBias() == Orientation.VERTICAL) {
 346             return r.getContentBias();
 347         }
 348 
 349         final Node l = getLeft();
 350         if (l != null && l.isManaged() && l.getContentBias() == Orientation.VERTICAL) {
 351             return l.getContentBias();
 352         }
 353         final Node b = getBottom();
 354         if (b != null && b.isManaged() && b.getContentBias() == Orientation.HORIZONTAL) {
 355             return b.getContentBias();
 356         }
 357 
 358         final Node t = getTop();
 359         if (t != null && t.isManaged() && t.getContentBias() == Orientation.HORIZONTAL) {
 360             return t.getContentBias();
 361         }
 362 
 363 
 364         return null;
 365     }
 366 
 367     @Override protected double computeMinWidth(double height) {
 368         double topMinWidth = getAreaWidth(getTop(), -1, true);
 369         double bottomMinWidth = getAreaWidth(getBottom(), -1, true);
 370 
 371         double leftPrefWidth;
 372         double rightPrefWidth;
 373         double centerMinWidth;
 374 
 375         if (height != -1 && (childHasContentBias(getLeft(), Orientation.VERTICAL) ||
 376                 childHasContentBias(getRight(), Orientation.VERTICAL) ||
 377             childHasContentBias(getCenter(), Orientation.VERTICAL))) {
 378             double topPrefHeight = getAreaHeight(getTop(), -1, false);
 379             double bottomPrefHeight = getAreaHeight(getBottom(), -1, false);
 380 
 381             double middleAreaHeight = Math.max(0, height - topPrefHeight - bottomPrefHeight);
 382 
 383             leftPrefWidth = getAreaWidth(getLeft(), middleAreaHeight, false);
 384             rightPrefWidth = getAreaWidth(getRight(), middleAreaHeight, false);
 385             centerMinWidth = getAreaWidth(getCenter(), middleAreaHeight, true);
 386         } else {
 387             leftPrefWidth = getAreaWidth(getLeft(), -1, false);
 388             rightPrefWidth = getAreaWidth(getRight(), -1, false);
 389             centerMinWidth = getAreaWidth(getCenter(), -1, true);
 390         }
 391 
 392         final Insets insets = getInsets();
 393         return insets.getLeft() +
 394                 Math.max(leftPrefWidth + centerMinWidth + rightPrefWidth, Math.max(topMinWidth,bottomMinWidth)) +
 395                 insets.getRight();
 396     }
 397 
 398     @Override protected double computeMinHeight(double width) {
 399         final Insets insets = getInsets();
 400 
 401         // Bottom and top are always at their pref height
 402         double topPrefHeight = getAreaHeight(getTop(), width, false);
 403         double bottomPrefHeight = getAreaHeight(getBottom(), width, false);
 404 
 405         double leftMinHeight = getAreaHeight(getLeft(), -1, true);
 406         double rightMinHeight = getAreaHeight(getRight(), -1, true);
 407 
 408         double centerMinHeight;
 409         if (width != -1 && childHasContentBias(getCenter(), Orientation.HORIZONTAL)) {
 410             double leftPrefWidth = getAreaWidth(getLeft(), -1, false);
 411             double rightPrefWidth = getAreaWidth(getRight(), -1, false);
 412             centerMinHeight = getAreaHeight(getCenter(),
 413                     Math.max(0, width - leftPrefWidth - rightPrefWidth) , true);
 414         } else {
 415             centerMinHeight = getAreaHeight(getCenter(), -1, true);
 416         }
 417 
 418         double middleAreaMinHeigh = Math.max(centerMinHeight, Math.max(rightMinHeight, leftMinHeight));
 419 
 420         return insets.getTop() + topPrefHeight + middleAreaMinHeigh + bottomPrefHeight + insets.getBottom();
 421     }
 422 
 423     @Override protected double computePrefWidth(double height) {
 424         double topPrefWidth = getAreaWidth(getTop(), -1, false);
 425         double bottomPrefWidth = getAreaWidth(getBottom(), -1, false);
 426 
 427         double leftPrefWidth;
 428         double rightPrefWidth;
 429         double centerPrefWidth;
 430 
 431         if ( height != -1 && (childHasContentBias(getLeft(), Orientation.VERTICAL) ||
 432                 childHasContentBias(getRight(), Orientation.VERTICAL) ||
 433             childHasContentBias(getCenter(), Orientation.VERTICAL))) {
 434             double topPrefHeight = getAreaHeight(getTop(), -1, false);
 435             double bottomPrefHeight = getAreaHeight(getBottom(), -1, false);
 436 
 437             double middleAreaHeight = Math.max(0, height - topPrefHeight - bottomPrefHeight);
 438 
 439             leftPrefWidth = getAreaWidth(getLeft(), middleAreaHeight, false);
 440             rightPrefWidth = getAreaWidth(getRight(), middleAreaHeight, false);
 441             centerPrefWidth = getAreaWidth(getCenter(), middleAreaHeight, false);
 442         } else {
 443             leftPrefWidth = getAreaWidth(getLeft(), -1, false);
 444             rightPrefWidth = getAreaWidth(getRight(), -1, false);
 445             centerPrefWidth = getAreaWidth(getCenter(), -1, false);
 446         }
 447 
 448         final Insets insets = getInsets();
 449         return insets.getLeft() +
 450                 Math.max(leftPrefWidth + centerPrefWidth + rightPrefWidth, Math.max(topPrefWidth,bottomPrefWidth)) +
 451                 insets.getRight();
 452     }
 453 
 454     @Override protected double computePrefHeight(double width) {
 455         final Insets insets = getInsets();
 456 
 457         double topPrefHeight = getAreaHeight(getTop(), width, false);
 458         double bottomPrefHeight = getAreaHeight(getBottom(), width, false);
 459         double leftPrefHeight = getAreaHeight(getLeft(), -1, false);
 460         double rightPrefHeight = getAreaHeight(getRight(), -1, false);
 461 
 462         double centerPrefHeight;
 463         if (width != -1 && childHasContentBias(getCenter(), Orientation.HORIZONTAL)) {
 464             double leftPrefWidth = getAreaWidth(getLeft(), -1, false);
 465             double rightPrefWidth = getAreaWidth(getRight(), -1, false);
 466             centerPrefHeight = getAreaHeight(getCenter(),
 467                     Math.max(0, width - leftPrefWidth - rightPrefWidth) , false);
 468         } else {
 469             centerPrefHeight = getAreaHeight(getCenter(), -1, false);
 470         }
 471 
 472         double middleAreaPrefHeigh = Math.max(centerPrefHeight, Math.max(rightPrefHeight, leftPrefHeight));
 473 
 474         return insets.getTop() + topPrefHeight + middleAreaPrefHeigh + bottomPrefHeight + insets.getBottom();
 475     }
 476 
 477     @Override protected void layoutChildren() {
 478         final Insets insets = getInsets();
 479         double width = getWidth();
 480         double height = getHeight();
 481         final Orientation bias = getContentBias();
 482 
 483         if (bias == null) {
 484             final double minWidth = minWidth(-1);
 485             final double minHeight = minHeight(-1);
 486             width = width < minWidth ? minWidth : width;
 487             height = height < minHeight ? minHeight : height;
 488         } else if (bias == Orientation.HORIZONTAL) {
 489             final double minWidth = minWidth(-1);
 490             width = width < minWidth ? minWidth : width;
 491             final double minHeight = minHeight(width);
 492             height = height < minHeight ? minHeight : height;
 493         } else {
 494             final double minHeight = minHeight(-1);
 495             height = height < minHeight ? minHeight : height;
 496             final double minWidth = minWidth(height);
 497             width = width < minWidth ? minWidth : width;
 498         }
 499 
 500         final double insideX = insets.getLeft();
 501         final double insideY = insets.getTop();
 502         final double insideWidth = width - insideX - insets.getRight();
 503         final double insideHeight = height - insideY - insets.getBottom();
 504         final Node c = getCenter();
 505         final Node r = getRight();
 506         final Node b = getBottom();
 507         final Node l = getLeft();
 508         final Node t = getTop();
 509 
 510         double topHeight = 0;
 511         if (t != null && t.isManaged()) {
 512             Insets topMargin = getNodeMargin(t);
 513             double adjustedWidth = adjustWidthByMargin(insideWidth, topMargin);
 514             double adjustedHeight = adjustHeightByMargin(insideHeight, topMargin);
 515             topHeight = snapSize(t.prefHeight(adjustedWidth));
 516             topHeight = Math.min(topHeight, adjustedHeight);
 517             Vec2d result = boundedNodeSizeWithBias(t, adjustedWidth,
 518                    topHeight, true, true, TEMP_VEC2D);
 519             topHeight = snapSize(result.y);
 520             t.resize(snapSize(result.x), topHeight);
 521 
 522             topHeight = snapSpace(topMargin.getBottom()) + topHeight + snapSpace(topMargin.getTop());
 523             Pos alignment = getAlignment(t);
 524             positionInArea(t, insideX, insideY, insideWidth, topHeight, 0/*ignore baseline*/,
 525                     topMargin,
 526                     alignment != null? alignment.getHpos() : HPos.LEFT,
 527                     alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
 528         }
 529 
 530         double bottomHeight = 0;
 531         if (b != null && b.isManaged()) {
 532             Insets bottomMargin = getNodeMargin(b);
 533             double adjustedWidth = adjustWidthByMargin(insideWidth, bottomMargin);
 534             double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight, bottomMargin);
 535             bottomHeight = snapSize(b.prefHeight(adjustedWidth));
 536             bottomHeight = Math.min(bottomHeight, adjustedHeight);
 537             Vec2d result = boundedNodeSizeWithBias(b, adjustedWidth,
 538                     bottomHeight, true, true, TEMP_VEC2D);
 539             bottomHeight = snapSize(result.y);
 540             b.resize(snapSize(result.x), bottomHeight);
 541 
 542             bottomHeight = snapSpace(bottomMargin.getBottom()) + bottomHeight + snapSpace(bottomMargin.getTop());
 543             Pos alignment = getAlignment(b);
 544             positionInArea(b, insideX, insideY + insideHeight - bottomHeight,
 545                     insideWidth, bottomHeight, 0/*ignore baseline*/,
 546                     bottomMargin,
 547                     alignment != null? alignment.getHpos() : HPos.LEFT,
 548                     alignment != null? alignment.getVpos() : VPos.BOTTOM, isSnapToPixel());
 549         }
 550 
 551         double leftWidth = 0;
 552         if (l != null && l.isManaged()) {
 553             Insets leftMargin = getNodeMargin(l);
 554             double adjustedWidth = adjustWidthByMargin(insideWidth, leftMargin);
 555             double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight - bottomHeight, leftMargin); // ????
 556             leftWidth = snapSize(l.prefWidth(adjustedHeight));
 557             leftWidth = Math.min(leftWidth, adjustedWidth);
 558             Vec2d result = boundedNodeSizeWithBias(l, leftWidth, adjustedHeight,
 559                     true, true, TEMP_VEC2D);
 560             leftWidth = snapSize(result.x);
 561             l.resize(leftWidth, snapSize(result.y));
 562 
 563             leftWidth = snapSpace(leftMargin.getLeft()) + leftWidth + snapSpace(leftMargin.getRight());
 564             Pos alignment = getAlignment(l);
 565             positionInArea(l, insideX, insideY + topHeight,
 566                     leftWidth, insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
 567                     leftMargin,
 568                     alignment != null? alignment.getHpos() : HPos.LEFT,
 569                     alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
 570         }
 571 
 572         double rightWidth = 0;
 573         if (r != null && r.isManaged()) {
 574             Insets rightMargin = getNodeMargin(r);
 575             double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth, rightMargin);
 576             double adjustedHeight = adjustHeightByMargin(insideHeight - topHeight - bottomHeight, rightMargin);
 577 
 578             rightWidth = snapSize(r.prefWidth(adjustedHeight));
 579             rightWidth = Math.min(rightWidth, adjustedWidth);
 580             Vec2d result = boundedNodeSizeWithBias(r, rightWidth, adjustedHeight,
 581                     true, true, TEMP_VEC2D);
 582             rightWidth = snapSize(result.x);
 583             r.resize(rightWidth, snapSize(result.y));
 584 
 585             rightWidth = snapSpace(rightMargin.getLeft()) + rightWidth + snapSpace(rightMargin.getRight());
 586             Pos alignment = getAlignment(r);
 587             positionInArea(r, insideX + insideWidth - rightWidth, insideY + topHeight,
 588                     rightWidth, insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
 589                     rightMargin,
 590                     alignment != null? alignment.getHpos() : HPos.RIGHT,
 591                     alignment != null? alignment.getVpos() : VPos.TOP, isSnapToPixel());
 592         }
 593 
 594         if (c != null && c.isManaged()) {
 595             Pos alignment = getAlignment(c);
 596 
 597             layoutInArea(c, insideX + leftWidth, insideY + topHeight,
 598                     insideWidth - leftWidth - rightWidth,
 599                     insideHeight - topHeight - bottomHeight, 0/*ignore baseline*/,
 600                     getNodeMargin(c),
 601                     alignment != null? alignment.getHpos() : HPos.CENTER,
 602                     alignment != null? alignment.getVpos() : VPos.CENTER);
 603         }
 604     }
 605 
 606     private double getAreaWidth(Node child, double height, boolean minimum) {
 607         if (child != null && child.isManaged()) {
 608             Insets margin = getNodeMargin(child);
 609             return minimum ? computeChildMinAreaWidth(child, -1, margin, height, false):
 610                                    computeChildPrefAreaWidth(child, -1, margin, height, false);
 611         }
 612         return 0;
 613     }
 614 
 615     private double getAreaHeight(Node child, double width, boolean minimum) {
 616         if (child != null && child.isManaged()) {
 617             Insets margin = getNodeMargin(child);
 618             return minimum ? computeChildMinAreaHeight(child, -1, margin, width):
 619                                    computeChildPrefAreaHeight(child, -1, margin, width);
 620         }
 621         return 0;
 622     }
 623 
 624     private boolean childHasContentBias(Node child, Orientation orientation) {
 625         if (child != null && child.isManaged()) {
 626             return child.getContentBias() == orientation;
 627         }
 628         return false;
 629     }
 630 
 631     /***************************************************************************
 632      *                                                                         *
 633      *                         Private Inner Class                             *
 634      *                                                                         *
 635      **************************************************************************/
 636 
 637     private final class BorderPositionProperty extends ObjectPropertyBase<Node> {
 638         private Node oldValue = null;
 639         private final String propertyName;
 640         private boolean isBeingInvalidated;
 641 
 642         BorderPositionProperty(String propertyName) {
 643             this.propertyName = propertyName;
 644             getChildren().addListener(new ListChangeListener<Node>() {
 645 
 646                 @Override
 647                 public void onChanged(ListChangeListener.Change<? extends Node> c) {
 648                     if (oldValue == null || isBeingInvalidated) {
 649                         return;
 650                     }
 651                     while (c.next()) {
 652                         if (c.wasRemoved()) {
 653                             List<? extends Node> removed = c.getRemoved();
 654                             for (int i = 0, sz = removed.size(); i < sz; ++i) {
 655                                 if (removed.get(i) == oldValue) {
 656                                     oldValue = null; // Do not remove again in invalidated
 657                                     set(null);
 658                                 }
 659                             }
 660                         }
 661                     }
 662                 }
 663             });
 664         }
 665 
 666         @Override
 667         protected void invalidated() {
 668             final List<Node> children = getChildren();
 669 
 670             isBeingInvalidated = true;
 671             try {
 672                 if (oldValue != null) {
 673                     children.remove(oldValue);
 674                 }
 675 
 676                 final Node _value = get();
 677                 this.oldValue = _value;
 678 
 679                 if (_value != null) {
 680                     children.add(_value);
 681                 }
 682             } finally {
 683                 isBeingInvalidated = false;
 684             }
 685         }
 686 
 687         @Override
 688         public Object getBean() {
 689             return BorderPane.this;
 690         }
 691 
 692         @Override
 693         public String getName() {
 694             return propertyName;
 695         }
 696     }
 697 }