1 /*
   2  * Copyright (c) 2011, 2015, 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.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import javafx.beans.property.DoubleProperty;
  32 import javafx.beans.property.DoublePropertyBase;
  33 import javafx.beans.property.ObjectProperty;
  34 import javafx.css.CssMetaData;
  35 import javafx.css.StyleableDoubleProperty;
  36 import javafx.css.StyleableObjectProperty;
  37 import javafx.css.StyleableProperty;
  38 import javafx.geometry.HPos;
  39 import javafx.geometry.Insets;
  40 import javafx.geometry.Orientation;
  41 import javafx.geometry.Pos;
  42 import javafx.geometry.VPos;
  43 import javafx.scene.Node;
  44 import javafx.css.converter.EnumConverter;
  45 import javafx.css.converter.SizeConverter;
  46 import javafx.css.Styleable;
  47 
  48 import static javafx.geometry.Orientation.*;
  49 import javafx.util.Callback;
  50 
  51 /**
  52  * FlowPane lays out its children in a flow that wraps at the flowpane's boundary.
  53  * <p>
  54  * A horizontal flowpane (the default) will layout nodes in rows, wrapping at the
  55  * flowpane's width.  A vertical flowpane lays out nodes in columns,
  56  * wrapping at the flowpane's height.  If the flowpane has a border and/or padding set,
  57  * the content will be flowed within those insets.
  58  * <p>
  59  * FlowPane's prefWrapLength property establishes it's preferred width
  60  * (for horizontal) or preferred height (for vertical). Applications should set
  61  * prefWrapLength if the default value (400) doesn't suffice.  Note that prefWrapLength
  62  * is used only for calculating the preferred size and may not reflect the actual
  63  * wrapping dimension, which tracks the actual size of the flowpane.
  64  * <p>
  65  * The alignment property controls how the rows and columns are aligned
  66  * within the bounds of the flowpane and defaults to Pos.TOP_LEFT.  It is also possible
  67  * to control the alignment of nodes within the rows and columns by setting
  68  * rowValignment for horizontal or columnHalignment for vertical.
  69  * <p>
  70  * Example of a horizontal flowpane:
  71  * <pre><code>     Image images[] = { ... };
  72  *     FlowPane flow = new FlowPane();
  73  *     flow.setVgap(8);
  74  *     flow.setHgap(4);
  75  *     flow.setPrefWrapLength(300); // preferred width = 300
  76  *     for (int i = 0; i < images.length; i++) {
  77  *         flow.getChildren().add(new ImageView(image[i]);
  78  *     }
  79  * </code></pre>
  80  *
  81  *<p>
  82  * Example of a vertical flowpane:
  83  * <pre><code>     FlowPane flow = new FlowPane(Orientation.VERTICAL);
  84  *     flow.setColumnHalignment(HPos.LEFT); // align labels on left
  85  *     flow.setPrefWrapLength(200); // preferred height = 200
  86  *     for (int i = 0; i < titles.size(); i++) {
  87  *         flow.getChildren().add(new Label(titles[i]);
  88  *     }
  89  * </code></pre>
  90  *
  91  * <p>
  92  * FlowPane lays out each managed child regardless of the child's visible property value;
  93  * unmanaged children are ignored for all layout calculations.</p>
  94  *
  95  * <p>
  96  * FlowPane may be styled with backgrounds and borders using CSS.  See
  97  * {@link javafx.scene.layout.Region Region} superclass for details.</p>
  98  *
  99  * <h4>Resizable Range</h4>
 100  *
 101  * A flowpane's parent will resize the flowpane within the flowpane's resizable range
 102  * during layout.   By default the flowpane computes this range based on its content
 103  * as outlined in the tables below.
 104  * <p>
 105  * horizontal:
 106  * <table border="1">
 107  * <tr><td></td><th>width</th><th>height</th></tr>
 108  * <tr><th>minimum</th>
 109  * <td>left/right insets plus largest of children's pref widths</td>
 110  * <td>top/bottom insets plus height required to display all children at their preferred heights when wrapped at a specified width</td></tr>
 111  * <tr><th>preferred</th>
 112  * <td>left/right insets plus prefWrapLength</td>
 113  * <td>top/bottom insets plus height required to display all children at their pref heights when wrapped at a specified width</td></tr>
 114  * <tr><th>maximum</th>
 115  * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
 116  * </table>
 117  * <p>
 118  * vertical:
 119  * <table border="1">
 120  * <tr><td></td><th>width</th><th>height</th></tr>
 121  * <tr><th>minimum</th>
 122  * <td>left/right insets plus width required to display all children at their preferred widths when wrapped at a specified height</td>
 123  * <td>top/bottom insets plus largest of children's pref heights</td><tr>
 124  * <tr><th>preferred</th>
 125  * <td>left/right insets plus width required to display all children at their pref widths when wrapped at the specified height</td>
 126  * <td>top/bottom insets plus prefWrapLength</td><tr>
 127  * <tr><th>maximum</th>
 128  * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
 129  * </table>
 130  * <p>
 131  * A flowpane's unbounded maximum width and height are an indication to the parent that
 132  * it may be resized beyond its preferred size to fill whatever space is assigned to it.
 133  * <p>
 134  * FlowPane provides properties for setting the size range directly.  These
 135  * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the
 136  * application may set them to other values as needed:
 137  * <pre><code>
 138  *     <b>flowpane.setMaxWidth(500);</b>
 139  * </code></pre>
 140  * Applications may restore the computed values by setting these properties back
 141  * to Region.USE_COMPUTED_SIZE.
 142  * <p>
 143  * FlowPane does not clip its content by default, so it is possible that childrens'
 144  * bounds may extend outside its own bounds if a child's pref size is larger than
 145  * the space flowpane has to allocate for it.</p>
 146  *
 147  * @since JavaFX 2.0
 148  */
 149 public class FlowPane extends Pane {
 150 
 151     /********************************************************************
 152      *  BEGIN static methods
 153      ********************************************************************/
 154     private static final String MARGIN_CONSTRAINT = "flowpane-margin";
 155 
 156     /**
 157      * Sets the margin for the child when contained by a flowpane.
 158      * If set, the flowpane will layout it out with the margin space around it.
 159      * Setting the value to null will remove the constraint.
 160      * @param child the child node of a flowpane
 161      * @param value the margin of space around the child
 162      */
 163     public static void setMargin(Node child, Insets value) {
 164         setConstraint(child, MARGIN_CONSTRAINT, value);
 165     }
 166 
 167     /**
 168      * Returns the child's margin constraint if set.
 169      * @param child the child node of a flowpane
 170      * @return the margin for the child or null if no margin was set
 171      */
 172     public static Insets getMargin(Node child) {
 173         return (Insets)getConstraint(child, MARGIN_CONSTRAINT);
 174     }
 175 
 176     private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n);
 177 
 178     /**
 179      * Removes all flowpane constraints from the child node.
 180      * @param child the child node
 181      */
 182     public static void clearConstraints(Node child) {
 183         setMargin(child, null);
 184     }
 185 
 186     /********************************************************************
 187      *  END static methods
 188      ********************************************************************/
 189 
 190     /**
 191      * Creates a horizontal FlowPane layout with hgap/vgap = 0.
 192      */
 193     public FlowPane() {
 194         super();
 195     }
 196 
 197     /**
 198      * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0.
 199      * @param orientation the direction the tiles should flow & wrap
 200      */
 201     public FlowPane(Orientation orientation) {
 202         this();
 203         setOrientation(orientation);
 204     }
 205 
 206     /**
 207      * Creates a horizontal FlowPane layout with the specified hgap/vgap.
 208      * @param hgap the amount of horizontal space between each tile
 209      * @param vgap the amount of vertical space between each tile
 210      */
 211     public FlowPane(double hgap, double vgap) {
 212         this();
 213         setHgap(hgap);
 214         setVgap(vgap);
 215     }
 216 
 217     /**
 218      * Creates a FlowPane layout with the specified orientation and hgap/vgap.
 219      * @param orientation the direction the tiles should flow & wrap
 220      * @param hgap the amount of horizontal space between each tile
 221      * @param vgap the amount of vertical space between each tile
 222      */
 223     public FlowPane(Orientation orientation, double hgap, double vgap) {
 224         this();
 225         setOrientation(orientation);
 226         setHgap(hgap);
 227         setVgap(vgap);
 228     }
 229 
 230     /**
 231      * Creates a horizontal FlowPane layout with hgap/vgap = 0.
 232      * @param children The initial set of children for this pane.
 233      * @since JavaFX 8.0
 234      */
 235     public FlowPane(Node... children) {
 236         super();
 237         getChildren().addAll(children);
 238     }
 239 
 240     /**
 241      * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0.
 242      * @param orientation the direction the tiles should flow & wrap
 243      * @param children The initial set of children for this pane.
 244      * @since JavaFX 8.0
 245      */
 246     public FlowPane(Orientation orientation, Node... children) {
 247         this();
 248         setOrientation(orientation);
 249         getChildren().addAll(children);
 250     }
 251 
 252     /**
 253      * Creates a horizontal FlowPane layout with the specified hgap/vgap.
 254      * @param hgap the amount of horizontal space between each tile
 255      * @param vgap the amount of vertical space between each tile
 256      * @param children The initial set of children for this pane.
 257      * @since JavaFX 8.0
 258      */
 259     public FlowPane(double hgap, double vgap, Node... children) {
 260         this();
 261         setHgap(hgap);
 262         setVgap(vgap);
 263         getChildren().addAll(children);
 264     }
 265 
 266     /**
 267      * Creates a FlowPane layout with the specified orientation and hgap/vgap.
 268      * @param orientation the direction the tiles should flow & wrap
 269      * @param hgap the amount of horizontal space between each tile
 270      * @param vgap the amount of vertical space between each tile
 271      * @param children The initial set of children for this pane.
 272      * @since JavaFX 8.0
 273      */
 274     public FlowPane(Orientation orientation, double hgap, double vgap, Node... children) {
 275         this();
 276         setOrientation(orientation);
 277         setHgap(hgap);
 278         setVgap(vgap);
 279         getChildren().addAll(children);
 280     }
 281 
 282     /**
 283      * The orientation of this flowpane.
 284      * A horizontal flowpane lays out children left to right, wrapping at the
 285      * flowpane's width boundary.   A vertical flowpane lays out children top to
 286      * bottom, wrapping at the flowpane's height.
 287      * The default is horizontal.
 288      */
 289     public final ObjectProperty<Orientation> orientationProperty() {
 290         if (orientation == null) {
 291             orientation = new StyleableObjectProperty(HORIZONTAL) {
 292                 @Override
 293                 public void invalidated() {
 294                     requestLayout();
 295                 }
 296 
 297                 @Override
 298                 public CssMetaData<FlowPane, Orientation> getCssMetaData() {
 299                     return StyleableProperties.ORIENTATION;
 300                 }
 301 
 302                 @Override
 303                 public Object getBean() {
 304                     return FlowPane.this;
 305                 }
 306 
 307                 @Override
 308                 public String getName() {
 309                     return "orientation";
 310                 }
 311             };
 312         }
 313         return orientation;
 314     }
 315 
 316     private ObjectProperty<Orientation> orientation;
 317     public final void setOrientation(Orientation value) { orientationProperty().set(value); }
 318     public final Orientation getOrientation() { return orientation == null ? HORIZONTAL : orientation.get();  }
 319 
 320     /**
 321      * The amount of horizontal space between each node in a horizontal flowpane
 322      * or the space between columns in a vertical flowpane.
 323      */
 324     public final DoubleProperty hgapProperty() {
 325         if (hgap == null) {
 326             hgap = new StyleableDoubleProperty() {
 327 
 328                 @Override
 329                 public void invalidated() {
 330                     requestLayout();
 331                 }
 332 
 333                 @Override
 334                 public CssMetaData<FlowPane, Number> getCssMetaData() {
 335                     return StyleableProperties.HGAP;
 336                 }
 337 
 338                 @Override
 339                 public Object getBean() {
 340                     return FlowPane.this;
 341                 }
 342 
 343                 @Override
 344                 public String getName() {
 345                     return "hgap";
 346                 }
 347             };
 348         }
 349         return hgap;
 350     }
 351 
 352     private DoubleProperty hgap;
 353     public final void setHgap(double value) { hgapProperty().set(value); }
 354     public final double getHgap() { return hgap == null ? 0 : hgap.get(); }
 355 
 356     /**
 357      * The amount of vertical space between each node in a vertical flowpane
 358      * or the space between rows in a horizontal flowpane.
 359      */
 360     public final DoubleProperty vgapProperty() {
 361         if (vgap == null) {
 362             vgap = new StyleableDoubleProperty() {
 363                 @Override
 364                 public void invalidated() {
 365                     requestLayout();
 366                 }
 367 
 368                 @Override
 369                 public CssMetaData<FlowPane, Number> getCssMetaData() {
 370                     return StyleableProperties.VGAP;
 371                 }
 372 
 373                 @Override
 374                 public Object getBean() {
 375                     return FlowPane.this;
 376                 }
 377 
 378                 @Override
 379                 public String getName() {
 380                     return "vgap";
 381                 }
 382             };
 383         }
 384         return vgap;
 385     }
 386 
 387     private DoubleProperty vgap;
 388     public final void setVgap(double value) { vgapProperty().set(value); }
 389     public final double getVgap() { return vgap == null ? 0 : vgap.get(); }
 390 
 391 
 392     /**
 393      * The preferred width where content should wrap in a horizontal flowpane or
 394      * the preferred height where content should wrap in a vertical flowpane.
 395      * <p>
 396      * This value is used only to compute the preferred size of the flowpane and may
 397      * not reflect the actual width or height, which may change if the flowpane is
 398      * resized to something other than its preferred size.
 399      * <p>
 400      * Applications should initialize this value to define a reasonable span
 401      * for wrapping the content.
 402      *
 403      */
 404     public final DoubleProperty prefWrapLengthProperty() {
 405         if (prefWrapLength == null) {
 406             prefWrapLength = new DoublePropertyBase(400) {
 407                 @Override
 408                 protected void invalidated() {
 409                     requestLayout();
 410                 }
 411 
 412                 @Override
 413                 public Object getBean() {
 414                     return FlowPane.this;
 415                 }
 416 
 417                 @Override
 418                 public String getName() {
 419                     return "prefWrapLength";
 420                 }
 421             };
 422         }
 423         return prefWrapLength;
 424     }
 425     private DoubleProperty prefWrapLength;
 426     public final void setPrefWrapLength(double value) { prefWrapLengthProperty().set(value); }
 427     public final double getPrefWrapLength() { return prefWrapLength == null ? 400 : prefWrapLength.get(); }
 428 
 429 
 430     /**
 431      * The overall alignment of the flowpane's content within its width and height.
 432      * <p>For a horizontal flowpane, each row will be aligned within the flowpane's width
 433      * using the alignment's hpos value, and the rows will be aligned within the
 434      * flowpane's height using the alignment's vpos value.
 435      * <p>For a vertical flowpane, each column will be aligned within the flowpane's height
 436      * using the alignment's vpos value, and the columns will be aligned within the
 437      * flowpane's width using the alignment's hpos value.
 438      */
 439     public final ObjectProperty<Pos> alignmentProperty() {
 440         if (alignment == null) {
 441             alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 442 
 443                 @Override
 444                 public void invalidated() {
 445                     requestLayout();
 446                 }
 447 
 448                 @Override
 449                 public CssMetaData<FlowPane, Pos> getCssMetaData() {
 450                     return StyleableProperties.ALIGNMENT;
 451                 }
 452 
 453                 @Override
 454                 public Object getBean() {
 455                     return FlowPane.this;
 456                 }
 457 
 458                 @Override
 459                 public String getName() {
 460                     return "alignment";
 461                 }
 462             };
 463         }
 464         return alignment;
 465     }
 466 
 467     private ObjectProperty<Pos> alignment;
 468     public final void setAlignment(Pos value) { alignmentProperty().set(value); }
 469     public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); }
 470     private Pos getAlignmentInternal() {
 471         Pos localPos = getAlignment();
 472         return localPos == null ? Pos.TOP_LEFT : localPos;
 473     }
 474 
 475     /**
 476      * The horizontal alignment of nodes within each column of a vertical flowpane.
 477      * The property is ignored for horizontal flowpanes.
 478      */
 479     public final ObjectProperty<HPos> columnHalignmentProperty() {
 480         if (columnHalignment == null) {
 481             columnHalignment = new StyleableObjectProperty<HPos>(HPos.LEFT) {
 482 
 483                 @Override
 484                 public void invalidated() {
 485                     requestLayout();
 486                 }
 487 
 488                 @Override
 489                 public CssMetaData<FlowPane, HPos> getCssMetaData() {
 490                     return StyleableProperties.COLUMN_HALIGNMENT;
 491                 }
 492 
 493                 @Override
 494                 public Object getBean() {
 495                     return FlowPane.this;
 496                 }
 497 
 498                 @Override
 499                 public String getName() {
 500                     return "columnHalignment";
 501                 }
 502             };
 503         }
 504         return columnHalignment;
 505     }
 506 
 507     private ObjectProperty<HPos> columnHalignment;
 508     public final void setColumnHalignment(HPos value) { columnHalignmentProperty().set(value); }
 509     public final HPos getColumnHalignment() { return columnHalignment == null ? HPos.LEFT : columnHalignment.get(); }
 510     private HPos getColumnHalignmentInternal() {
 511         HPos localPos = getColumnHalignment();
 512         return localPos == null ? HPos.LEFT : localPos;
 513     }
 514 
 515     /**
 516      * The vertical alignment of nodes within each row of a horizontal flowpane.
 517      * If this property is set to VPos.BASELINE, then the flowpane will always
 518      * resize children to their preferred heights, rather than expanding heights
 519      * to fill the row height.
 520      * The property is ignored for vertical flowpanes.
 521      */
 522     public final ObjectProperty<VPos> rowValignmentProperty() {
 523         if (rowValignment == null) {
 524             rowValignment = new StyleableObjectProperty<VPos>(VPos.CENTER) {
 525                 @Override
 526                 public void invalidated() {
 527                     requestLayout();
 528                 }
 529 
 530                 @Override
 531                 public CssMetaData<FlowPane, VPos> getCssMetaData() {
 532                     return StyleableProperties.ROW_VALIGNMENT;
 533                 }
 534 
 535                 @Override
 536                 public Object getBean() {
 537                     return FlowPane.this;
 538                 }
 539 
 540                 @Override
 541                 public String getName() {
 542                     return "rowValignment";
 543                 }
 544             };
 545         }
 546         return rowValignment;
 547     }
 548 
 549     private ObjectProperty<VPos> rowValignment;
 550     public final void setRowValignment(VPos value) { rowValignmentProperty().set(value); }
 551     public final VPos getRowValignment() { return rowValignment == null ? VPos.CENTER : rowValignment.get(); }
 552     private VPos getRowValignmentInternal() {
 553         VPos localPos =  getRowValignment();
 554         return localPos == null ? VPos.CENTER : localPos;
 555     }
 556 
 557     @Override public Orientation getContentBias() {
 558         return getOrientation();
 559     }
 560 
 561     @Override protected double computeMinWidth(double height) {
 562         if (getContentBias() == HORIZONTAL) {
 563             double maxPref = 0;
 564             final List<Node> children = getChildren();
 565             for (int i=0, size=children.size(); i<size; i++) {
 566                 Node child = children.get(i);
 567                 if (child.isManaged()) {
 568                     maxPref = Math.max(maxPref, child.prefWidth(-1));
 569                 }
 570             }
 571             final Insets insets = getInsets();
 572             return insets.getLeft() + snapSize(maxPref) + insets.getRight();
 573         }
 574         return computePrefWidth(height);
 575     }
 576 
 577     @Override protected double computeMinHeight(double width) {
 578         if (getContentBias() == VERTICAL) {
 579             double maxPref = 0;
 580             final List<Node> children = getChildren();
 581             for (int i=0, size=children.size(); i<size; i++) {
 582                 Node child = children.get(i);
 583                 if (child.isManaged()) {
 584                     maxPref = Math.max(maxPref, child.prefHeight(-1));
 585                 }
 586             }
 587             final Insets insets = getInsets();
 588             return insets.getTop() + snapSize(maxPref) + insets.getBottom();
 589         }
 590         return computePrefHeight(width);
 591     }
 592 
 593     @Override protected double computePrefWidth(double forHeight) {
 594         final Insets insets = getInsets();
 595         if (getOrientation() == HORIZONTAL) {
 596             // horizontal
 597             double maxRunWidth = getPrefWrapLength();
 598             List<Run> hruns = getRuns(maxRunWidth);
 599             double w = computeContentWidth(hruns);
 600             w = getPrefWrapLength() > w ? getPrefWrapLength() : w;
 601             return insets.getLeft() + snapSize(w) + insets.getRight();
 602         } else {
 603             // vertical
 604             double maxRunHeight = forHeight != -1?
 605                 forHeight - insets.getTop() - insets.getBottom() : getPrefWrapLength();
 606             List<Run> vruns = getRuns(maxRunHeight);
 607             return insets.getLeft() + computeContentWidth(vruns) + insets.getRight();
 608         }
 609     }
 610 
 611     @Override protected double computePrefHeight(double forWidth) {
 612         final Insets insets = getInsets();
 613         if (getOrientation() == HORIZONTAL) {
 614             // horizontal
 615             double maxRunWidth = forWidth != -1?
 616                 forWidth - insets.getLeft() - insets.getRight() : getPrefWrapLength();
 617             List<Run> hruns = getRuns(maxRunWidth);
 618             return insets.getTop() + computeContentHeight(hruns) + insets.getBottom();
 619         } else {
 620             // vertical
 621             double maxRunHeight = getPrefWrapLength();
 622             List<Run> vruns = getRuns(maxRunHeight);
 623             double h = computeContentHeight(vruns);
 624             h = getPrefWrapLength() > h ? getPrefWrapLength() : h;
 625             return insets.getTop() + snapSize(h) + insets.getBottom();
 626         }
 627     }
 628 
 629     @Override public void requestLayout() {
 630         if (!computingRuns) {
 631             runs = null;
 632         }
 633         super.requestLayout();
 634     }
 635 
 636     private List<Run> runs = null;
 637     private double lastMaxRunLength = -1;
 638     boolean computingRuns = false;
 639 
 640     private List<Run> getRuns(double maxRunLength) {
 641         if (runs == null || maxRunLength != lastMaxRunLength) {
 642             computingRuns = true;
 643             lastMaxRunLength = maxRunLength;
 644             runs = new ArrayList();
 645             double runLength = 0;
 646             double runOffset = 0;
 647             Run run = new Run();
 648             double vgap = snapSpace(this.getVgap());
 649             double hgap = snapSpace(this.getHgap());
 650 
 651             final List<Node> children = getChildren();
 652             for (int i=0, size=children.size(); i<size; i++) {
 653                 Node child = children.get(i);
 654                 if (child.isManaged()) {
 655                     LayoutRect nodeRect = new LayoutRect();
 656                     nodeRect.node = child;
 657                     Insets margin = getMargin(child);
 658                     nodeRect.width = computeChildPrefAreaWidth(child, margin);
 659                     nodeRect.height = computeChildPrefAreaHeight(child, margin);
 660                     double nodeLength = getOrientation() == HORIZONTAL ? nodeRect.width : nodeRect.height;
 661                     if (runLength + nodeLength > maxRunLength && runLength > 0) {
 662                         // wrap to next run *unless* its the only node in the run
 663                         normalizeRun(run, runOffset);
 664                         if (getOrientation() == HORIZONTAL) {
 665                             // horizontal
 666                             runOffset += run.height + vgap;
 667                         } else {
 668                             // vertical
 669                             runOffset += run.width + hgap;
 670                         }
 671                         runs.add(run);
 672                         runLength = 0;
 673                         run = new Run();
 674                     }
 675                     if (getOrientation() == HORIZONTAL) {
 676                         // horizontal
 677                         nodeRect.x = runLength;
 678                         runLength += nodeRect.width + hgap;
 679                     } else {
 680                         // vertical
 681                         nodeRect.y = runLength;
 682                         runLength += nodeRect.height + vgap;
 683                     }
 684                     run.rects.add(nodeRect);
 685                 }
 686 
 687             }
 688             // insert last run
 689             normalizeRun(run, runOffset);
 690             runs.add(run);
 691             computingRuns = false;
 692         }
 693         return runs;
 694     }
 695 
 696     private void normalizeRun(final Run run, double runOffset) {
 697         if (getOrientation() == HORIZONTAL) {
 698             // horizontal
 699             ArrayList<Node> rownodes = new ArrayList();
 700             run.width = (run.rects.size()-1)*snapSpace(getHgap());
 701             for (int i=0, max=run.rects.size(); i<max; i++) {
 702                 LayoutRect lrect = run.rects.get(i);
 703                 rownodes.add(lrect.node);
 704                 run.width += lrect.width;
 705                 lrect.y = runOffset;
 706             }
 707             run.height = computeMaxPrefAreaHeight(rownodes, marginAccessor, getRowValignment());
 708             run.baselineOffset = getRowValignment() == VPos.BASELINE?
 709                     getAreaBaselineOffset(rownodes, marginAccessor, i -> run.rects.get(i).width, run.height, true) : 0;
 710 
 711         } else {
 712             // vertical
 713             run.height = (run.rects.size()-1)*snapSpace(getVgap());
 714             double maxw = 0;
 715             for (int i=0, max=run.rects.size(); i<max; i++) {
 716                 LayoutRect lrect = run.rects.get(i);
 717                 run.height += lrect.height;
 718                 lrect.x = runOffset;
 719                 maxw = Math.max(maxw, lrect.width);
 720             }
 721 
 722             run.width = maxw;
 723             run.baselineOffset = run.height;
 724         }
 725     }
 726 
 727     private double computeContentWidth(List<Run> runs) {
 728         double cwidth = getOrientation() == HORIZONTAL ? 0 : (runs.size()-1)*snapSpace(getHgap());
 729         for (int i=0, max=runs.size(); i<max; i++) {
 730             Run run = runs.get(i);
 731             if (getOrientation() == HORIZONTAL) {
 732                 cwidth = Math.max(cwidth, run.width);
 733             } else {
 734                 // vertical
 735                 cwidth += run.width;
 736             }
 737         }
 738         return cwidth;
 739     }
 740 
 741     private double computeContentHeight(List<Run> runs) {
 742         double cheight = getOrientation() == VERTICAL ? 0 : (runs.size()-1)*snapSpace(getVgap());
 743         for (int i=0, max=runs.size(); i<max; i++) {
 744             Run run = runs.get(i);
 745             if (getOrientation() == VERTICAL) {
 746                 cheight = Math.max(cheight, run.height);
 747             } else {
 748                 // horizontal
 749                 cheight += run.height;
 750             }
 751         }
 752         return cheight;
 753     }
 754 
 755     @Override protected void layoutChildren() {
 756         final Insets insets = getInsets();
 757         final double width = getWidth();
 758         final double height = getHeight();
 759         final double top = insets.getTop();
 760         final double left = insets.getLeft();
 761         final double bottom = insets.getBottom();
 762         final double right = insets.getRight();
 763         final double insideWidth = width - left - right;
 764         final double insideHeight = height - top - bottom;
 765 
 766         //REMIND(aim): need to figure out how to cache the runs to avoid over-calculation
 767         final List<Run> runs = getRuns(getOrientation() == HORIZONTAL ? insideWidth : insideHeight);
 768 
 769         // Now that the nodes are broken into runs, figure out alignments
 770         for (int i=0, max=runs.size(); i<max; i++) {
 771             final Run run = runs.get(i);
 772             final double xoffset = left + computeXOffset(insideWidth,
 773                                      getOrientation() == HORIZONTAL ? run.width : computeContentWidth(runs),
 774                                      getAlignmentInternal().getHpos());
 775             final double yoffset = top + computeYOffset(insideHeight,
 776                                     getOrientation() == VERTICAL ? run.height : computeContentHeight(runs),
 777                                     getAlignmentInternal().getVpos());
 778             for (int j = 0; j < run.rects.size(); j++) {
 779                 final LayoutRect lrect = run.rects.get(j);
 780 //              System.out.println("flowpane.layout: run="+i+" "+run.width+"x"+run.height+" xoffset="+xoffset+" yoffset="+yoffset+" lrect="+lrect);
 781                 final double x = xoffset + lrect.x;
 782                 final double y = yoffset + lrect.y;
 783                 layoutInArea(lrect.node, x, y,
 784                            getOrientation() == HORIZONTAL? lrect.width : run.width,
 785                            getOrientation() == VERTICAL? lrect.height : run.height,
 786                            run.baselineOffset, getMargin(lrect.node),
 787                            getColumnHalignmentInternal(), getRowValignmentInternal());
 788             }
 789         }
 790     }
 791 
 792     /***************************************************************************
 793      *                                                                         *
 794      *                         Stylesheet Handling                             *
 795      *                                                                         *
 796      **************************************************************************/
 797 
 798 
 799      /**
 800       * Super-lazy instantiation pattern from Bill Pugh.
 801       * @treatAsPrivate implementation detail
 802       */
 803      private static class StyleableProperties {
 804 
 805          private static final CssMetaData<FlowPane,Pos> ALIGNMENT =
 806              new CssMetaData<FlowPane,Pos>("-fx-alignment",
 807                  new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT) {
 808 
 809             @Override
 810             public boolean isSettable(FlowPane node) {
 811                 return node.alignment == null || !node.alignment.isBound();
 812             }
 813 
 814             @Override
 815             public StyleableProperty<Pos> getStyleableProperty(FlowPane node) {
 816                 return (StyleableProperty<Pos>)node.alignmentProperty();
 817             }
 818 
 819          };
 820 
 821          private static final CssMetaData<FlowPane,HPos> COLUMN_HALIGNMENT =
 822              new CssMetaData<FlowPane,HPos>("-fx-column-halignment",
 823                  new EnumConverter<HPos>(HPos.class), HPos.LEFT) {
 824 
 825             @Override
 826             public boolean isSettable(FlowPane node) {
 827                 return node.columnHalignment == null || !node.columnHalignment.isBound();
 828             }
 829 
 830             @Override
 831             public StyleableProperty<HPos> getStyleableProperty(FlowPane node) {
 832                 return (StyleableProperty<HPos>)node.columnHalignmentProperty();
 833             }
 834 
 835          };
 836 
 837          private static final CssMetaData<FlowPane,Number> HGAP =
 838              new CssMetaData<FlowPane,Number>("-fx-hgap",
 839                  SizeConverter.getInstance(), 0.0){
 840 
 841             @Override
 842             public boolean isSettable(FlowPane node) {
 843                 return node.hgap == null || !node.hgap.isBound();
 844             }
 845 
 846             @Override
 847             public StyleableProperty<Number> getStyleableProperty(FlowPane node) {
 848                 return (StyleableProperty<Number>)node.hgapProperty();
 849             }
 850 
 851          };
 852 
 853          private static final CssMetaData<FlowPane,VPos> ROW_VALIGNMENT =
 854              new CssMetaData<FlowPane,VPos>("-fx-row-valignment",
 855                  new EnumConverter<VPos>(VPos.class), VPos.CENTER) {
 856 
 857             @Override
 858             public boolean isSettable(FlowPane node) {
 859                 return node.rowValignment == null || !node.rowValignment.isBound();
 860             }
 861 
 862             @Override
 863             public StyleableProperty<VPos> getStyleableProperty(FlowPane node) {
 864                 return (StyleableProperty<VPos>)node.rowValignmentProperty();
 865             }
 866 
 867          };
 868 
 869          private static final CssMetaData<FlowPane,Orientation> ORIENTATION =
 870              new CssMetaData<FlowPane,Orientation>("-fx-orientation",
 871                  new EnumConverter<Orientation>(Orientation.class),
 872                  Orientation.HORIZONTAL) {
 873 
 874             @Override
 875             public Orientation getInitialValue(FlowPane node) {
 876                 // A vertical flow pane should remain vertical
 877                 return node.getOrientation();
 878             }
 879 
 880             @Override
 881             public boolean isSettable(FlowPane node) {
 882                 return node.orientation == null || !node.orientation.isBound();
 883             }
 884 
 885             @Override
 886             public StyleableProperty<Orientation> getStyleableProperty(FlowPane node) {
 887                 return (StyleableProperty<Orientation>)node.orientationProperty();
 888             }
 889 
 890          };
 891 
 892          private static final CssMetaData<FlowPane,Number> VGAP =
 893              new CssMetaData<FlowPane,Number>("-fx-vgap",
 894                  SizeConverter.getInstance(), 0.0){
 895 
 896             @Override
 897             public boolean isSettable(FlowPane node) {
 898                 return node.vgap == null || !node.vgap.isBound();
 899             }
 900 
 901             @Override
 902             public StyleableProperty<Number> getStyleableProperty(FlowPane node) {
 903                 return (StyleableProperty<Number>)node.vgapProperty();
 904             }
 905 
 906          };
 907 
 908          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 909          static {
 910 
 911             final List<CssMetaData<? extends Styleable, ?>> styleables =
 912                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
 913             styleables.add(ALIGNMENT);
 914             styleables.add(COLUMN_HALIGNMENT);
 915             styleables.add(HGAP);
 916             styleables.add(ROW_VALIGNMENT);
 917             styleables.add(ORIENTATION);
 918             styleables.add(VGAP);
 919 
 920             STYLEABLES = Collections.unmodifiableList(styleables);
 921          }
 922     }
 923 
 924 
 925     /**
 926      * @return The CssMetaData associated with this class, which may include the
 927      * CssMetaData of its super classes.
 928      * @since JavaFX 8.0
 929      */
 930     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 931         return StyleableProperties.STYLEABLES;
 932     }
 933 
 934     /**
 935      * {@inheritDoc}
 936      *
 937      * @since JavaFX 8.0
 938      */
 939 
 940 
 941     @Override
 942     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 943         return getClassCssMetaData();
 944     }
 945 
 946     //REMIND(aim); replace when we get mutable rects
 947     private static class LayoutRect {
 948         public Node node;
 949         double x;
 950         double y;
 951         double width;
 952         double height;
 953 
 954         @Override public String toString() {
 955             return "LayoutRect node id="+node.getId()+" "+x+","+y+" "+width+"x"+height;
 956         }
 957     }
 958 
 959     private static class Run {
 960         ArrayList<LayoutRect> rects = new ArrayList();
 961         double width;
 962         double height;
 963         double baselineOffset;
 964     }
 965 }