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