/*
 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.layout;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.css.converter.EnumConverter;
import javafx.css.converter.SizeConverter;
import javafx.css.Styleable;

import static javafx.geometry.Orientation.*;
import javafx.util.Callback;

FlowPane lays out its children in a flow that wraps at the flowpane's boundary.

A horizontal flowpane (the default) will layout nodes in rows, wrapping at the flowpane's width. A vertical flowpane lays out nodes in columns, wrapping at the flowpane's height. If the flowpane has a border and/or padding set, the content will be flowed within those insets.

FlowPane's prefWrapLength property establishes its preferred width (for horizontal) or preferred height (for vertical). Applications should set prefWrapLength if the default value (400) doesn't suffice. Note that prefWrapLength is used only for calculating the preferred size and may not reflect the actual wrapping dimension, which tracks the actual size of the flowpane.

The alignment property controls how the rows and columns are aligned within the bounds of the flowpane and defaults to Pos.TOP_LEFT. It is also possible to control the alignment of nodes within the rows and columns by setting rowValignment for horizontal or columnHalignment for vertical.

Example of a horizontal flowpane:


    Image images[] = { ... };
    FlowPane flow = new FlowPane();
    flow.setVgap(8);
    flow.setHgap(4);
    flow.setPrefWrapLength(300); // preferred width = 300
    for (int i = 0; i < images.length; i++) {
        flow.getChildren().add(new ImageView(image[i]);
    }

Example of a vertical flowpane:


    FlowPane flow = new FlowPane(Orientation.VERTICAL);
    flow.setColumnHalignment(HPos.LEFT); // align labels on left
    flow.setPrefWrapLength(200); // preferred height = 200
    for (int i = 0; i < titles.size(); i++) {
        flow.getChildren().add(new Label(titles[i]);
    }

FlowPane lays out each managed child regardless of the child's visible property value; unmanaged children are ignored for all layout calculations.

FlowPane may be styled with backgrounds and borders using CSS. See Region superclass for details.

Resizable Range

A flowpane's parent will resize the flowpane within the flowpane's resizable range during layout. By default the flowpane computes this range based on its content as outlined in the tables below.

Horizontal
widthheight
minimum left/right insets plus largest of children's pref widths top/bottom insets plus height required to display all children at their preferred heights when wrapped at a specified width
preferred left/right insets plus prefWrapLength top/bottom insets plus height required to display all children at their pref heights when wrapped at a specified width
maximum Double.MAX_VALUEDouble.MAX_VALUE

Vertical
widthheight
minimum left/right insets plus width required to display all children at their preferred widths when wrapped at a specified height top/bottom insets plus largest of children's pref heights
preferred left/right insets plus width required to display all children at their pref widths when wrapped at the specified height top/bottom insets plus prefWrapLength
maximum Double.MAX_VALUEDouble.MAX_VALUE

A flowpane's unbounded maximum width and height are an indication to the parent that it may be resized beyond its preferred size to fill whatever space is assigned to it.

FlowPane provides properties for setting the size range directly. These properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the application may set them to other values as needed:


    flowPane.setMaxWidth(500);
Applications may restore the computed values by setting these properties back to Region.USE_COMPUTED_SIZE.

FlowPane does not clip its content by default, so it is possible that children's bounds may extend outside its own bounds if a child's pref size is larger than the space flowpane has to allocate for it.

Since:JavaFX 2.0
/** * FlowPane lays out its children in a flow that wraps at the flowpane's boundary. * <p> * A horizontal flowpane (the default) will layout nodes in rows, wrapping at the * flowpane's width. A vertical flowpane lays out nodes in columns, * wrapping at the flowpane's height. If the flowpane has a border and/or padding set, * the content will be flowed within those insets. * <p> * FlowPane's prefWrapLength property establishes its preferred width * (for horizontal) or preferred height (for vertical). Applications should set * prefWrapLength if the default value (400) doesn't suffice. Note that prefWrapLength * is used only for calculating the preferred size and may not reflect the actual * wrapping dimension, which tracks the actual size of the flowpane. * <p> * The alignment property controls how the rows and columns are aligned * within the bounds of the flowpane and defaults to Pos.TOP_LEFT. It is also possible * to control the alignment of nodes within the rows and columns by setting * rowValignment for horizontal or columnHalignment for vertical. * <p> * Example of a horizontal flowpane: * <pre>{@code * Image images[] = { ... }; * FlowPane flow = new FlowPane(); * flow.setVgap(8); * flow.setHgap(4); * flow.setPrefWrapLength(300); // preferred width = 300 * for (int i = 0; i < images.length; i++) { * flow.getChildren().add(new ImageView(image[i]); * } * }</pre> * *<p> * Example of a vertical flowpane: * <pre>{@code * FlowPane flow = new FlowPane(Orientation.VERTICAL); * flow.setColumnHalignment(HPos.LEFT); // align labels on left * flow.setPrefWrapLength(200); // preferred height = 200 * for (int i = 0; i < titles.size(); i++) { * flow.getChildren().add(new Label(titles[i]); * } * }</pre> * * <p> * FlowPane lays out each managed child regardless of the child's visible property value; * unmanaged children are ignored for all layout calculations.</p> * * <p> * FlowPane may be styled with backgrounds and borders using CSS. See * {@link javafx.scene.layout.Region Region} superclass for details.</p> * * <h3>Resizable Range</h3> * * <p> * A flowpane's parent will resize the flowpane within the flowpane's resizable range * during layout. By default the flowpane computes this range based on its content * as outlined in the tables below. * </p> * <table border="1"> * <caption>Horizontal</caption> * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr> * <tr><th scope="row">minimum</th> * <td>left/right insets plus largest of children's pref widths</td> * <td>top/bottom insets plus height required to display all children at their preferred heights when wrapped at a specified width</td></tr> * <tr><th scope="row">preferred</th> * <td>left/right insets plus prefWrapLength</td> * <td>top/bottom insets plus height required to display all children at their pref heights when wrapped at a specified width</td></tr> * <tr><th scope="row">maximum</th> * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> * </table> * <br> * <table border="1"> * <caption>Vertical</caption> * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr> * <tr><th scope="row">minimum</th> * <td>left/right insets plus width required to display all children at their preferred widths when wrapped at a specified height</td> * <td>top/bottom insets plus largest of children's pref heights</td></tr> * <tr><th scope="row">preferred</th> * <td>left/right insets plus width required to display all children at their pref widths when wrapped at the specified height</td> * <td>top/bottom insets plus prefWrapLength</td></tr> * <tr><th scope="row">maximum</th> * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> * </table> * <p> * A flowpane's unbounded maximum width and height are an indication to the parent that * it may be resized beyond its preferred size to fill whatever space is assigned to it. * <p> * FlowPane provides properties for setting the size range directly. These * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the * application may set them to other values as needed: * <pre><code> * <b>flowPane.setMaxWidth(500);</b> * </code></pre> * Applications may restore the computed values by setting these properties back * to Region.USE_COMPUTED_SIZE. * <p> * FlowPane does not clip its content by default, so it is possible that children's * bounds may extend outside its own bounds if a child's pref size is larger than * the space flowpane has to allocate for it.</p> * * @since JavaFX 2.0 */
public class FlowPane extends Pane {
BEGIN static methods
/******************************************************************** * BEGIN static methods ********************************************************************/
private static final String MARGIN_CONSTRAINT = "flowpane-margin";
Sets the margin for the child when contained by a flowpane. If set, the flowpane will layout it out with the margin space around it. Setting the value to null will remove the constraint.
Params:
  • child – the child node of a flowpane
  • value – the margin of space around the child
/** * Sets the margin for the child when contained by a flowpane. * If set, the flowpane will layout it out with the margin space around it. * Setting the value to null will remove the constraint. * @param child the child node of a flowpane * @param value the margin of space around the child */
public static void setMargin(Node child, Insets value) { setConstraint(child, MARGIN_CONSTRAINT, value); }
Returns the child's margin constraint if set.
Params:
  • child – the child node of a flowpane
Returns:the margin for the child or null if no margin was set
/** * Returns the child's margin constraint if set. * @param child the child node of a flowpane * @return the margin for the child or null if no margin was set */
public static Insets getMargin(Node child) { return (Insets)getConstraint(child, MARGIN_CONSTRAINT); } private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n);
Removes all flowpane constraints from the child node.
Params:
  • child – the child node
/** * Removes all flowpane constraints from the child node. * @param child the child node */
public static void clearConstraints(Node child) { setMargin(child, null); } /******************************************************************** * END static methods ********************************************************************/
Creates a horizontal FlowPane layout with hgap/vgap = 0.
/** * Creates a horizontal FlowPane layout with hgap/vgap = 0. */
public FlowPane() { super(); }
Creates a FlowPane layout with the specified orientation and hgap/vgap = 0.
Params:
  • orientation – the direction the tiles should flow & wrap
/** * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0. * @param orientation the direction the tiles should flow &amp; wrap */
public FlowPane(Orientation orientation) { this(); setOrientation(orientation); }
Creates a horizontal FlowPane layout with the specified hgap/vgap.
Params:
  • hgap – the amount of horizontal space between each tile
  • vgap – the amount of vertical space between each tile
/** * Creates a horizontal FlowPane layout with the specified hgap/vgap. * @param hgap the amount of horizontal space between each tile * @param vgap the amount of vertical space between each tile */
public FlowPane(double hgap, double vgap) { this(); setHgap(hgap); setVgap(vgap); }
Creates a FlowPane layout with the specified orientation and hgap/vgap.
Params:
  • orientation – the direction the tiles should flow & wrap
  • hgap – the amount of horizontal space between each tile
  • vgap – the amount of vertical space between each tile
/** * Creates a FlowPane layout with the specified orientation and hgap/vgap. * @param orientation the direction the tiles should flow &amp; wrap * @param hgap the amount of horizontal space between each tile * @param vgap the amount of vertical space between each tile */
public FlowPane(Orientation orientation, double hgap, double vgap) { this(); setOrientation(orientation); setHgap(hgap); setVgap(vgap); }
Creates a horizontal FlowPane layout with hgap/vgap = 0.
Params:
  • children – The initial set of children for this pane.
Since:JavaFX 8.0
/** * Creates a horizontal FlowPane layout with hgap/vgap = 0. * @param children The initial set of children for this pane. * @since JavaFX 8.0 */
public FlowPane(Node... children) { super(); getChildren().addAll(children); }
Creates a FlowPane layout with the specified orientation and hgap/vgap = 0.
Params:
  • orientation – the direction the tiles should flow & wrap
  • children – The initial set of children for this pane.
Since:JavaFX 8.0
/** * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0. * @param orientation the direction the tiles should flow &amp; wrap * @param children The initial set of children for this pane. * @since JavaFX 8.0 */
public FlowPane(Orientation orientation, Node... children) { this(); setOrientation(orientation); getChildren().addAll(children); }
Creates a horizontal FlowPane layout with the specified hgap/vgap.
Params:
  • hgap – the amount of horizontal space between each tile
  • vgap – the amount of vertical space between each tile
  • children – The initial set of children for this pane.
Since:JavaFX 8.0
/** * Creates a horizontal FlowPane layout with the specified hgap/vgap. * @param hgap the amount of horizontal space between each tile * @param vgap the amount of vertical space between each tile * @param children The initial set of children for this pane. * @since JavaFX 8.0 */
public FlowPane(double hgap, double vgap, Node... children) { this(); setHgap(hgap); setVgap(vgap); getChildren().addAll(children); }
Creates a FlowPane layout with the specified orientation and hgap/vgap.
Params:
  • orientation – the direction the tiles should flow & wrap
  • hgap – the amount of horizontal space between each tile
  • vgap – the amount of vertical space between each tile
  • children – The initial set of children for this pane.
Since:JavaFX 8.0
/** * Creates a FlowPane layout with the specified orientation and hgap/vgap. * @param orientation the direction the tiles should flow &amp; wrap * @param hgap the amount of horizontal space between each tile * @param vgap the amount of vertical space between each tile * @param children The initial set of children for this pane. * @since JavaFX 8.0 */
public FlowPane(Orientation orientation, double hgap, double vgap, Node... children) { this(); setOrientation(orientation); setHgap(hgap); setVgap(vgap); getChildren().addAll(children); }
The orientation of this flowpane. A horizontal flowpane lays out children left to right, wrapping at the flowpane's width boundary. A vertical flowpane lays out children top to bottom, wrapping at the flowpane's height. The default is horizontal.
Returns:the orientation of this flowpane
/** * The orientation of this flowpane. * A horizontal flowpane lays out children left to right, wrapping at the * flowpane's width boundary. A vertical flowpane lays out children top to * bottom, wrapping at the flowpane's height. * The default is horizontal. * @return the orientation of this flowpane */
public final ObjectProperty<Orientation> orientationProperty() { if (orientation == null) { orientation = new StyleableObjectProperty(HORIZONTAL) { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, Orientation> getCssMetaData() { return StyleableProperties.ORIENTATION; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "orientation"; } }; } return orientation; } private ObjectProperty<Orientation> orientation; public final void setOrientation(Orientation value) { orientationProperty().set(value); } public final Orientation getOrientation() { return orientation == null ? HORIZONTAL : orientation.get(); }
The amount of horizontal space between each node in a horizontal flowpane or the space between columns in a vertical flowpane.
Returns:the amount of horizontal space between each node in a horizontal flowpane or the space between columns in a vertical flowpane
/** * The amount of horizontal space between each node in a horizontal flowpane * or the space between columns in a vertical flowpane. * @return the amount of horizontal space between each node in a horizontal * flowpane or the space between columns in a vertical flowpane */
public final DoubleProperty hgapProperty() { if (hgap == null) { hgap = new StyleableDoubleProperty() { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, Number> getCssMetaData() { return StyleableProperties.HGAP; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "hgap"; } }; } return hgap; } private DoubleProperty hgap; public final void setHgap(double value) { hgapProperty().set(value); } public final double getHgap() { return hgap == null ? 0 : hgap.get(); }
The amount of vertical space between each node in a vertical flowpane or the space between rows in a horizontal flowpane.
Returns:the amount of vertical space between each node in a vertical flowpane or the space between rows in a horizontal flowpane
/** * The amount of vertical space between each node in a vertical flowpane * or the space between rows in a horizontal flowpane. * @return the amount of vertical space between each node in a vertical * flowpane or the space between rows in a horizontal flowpane */
public final DoubleProperty vgapProperty() { if (vgap == null) { vgap = new StyleableDoubleProperty() { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, Number> getCssMetaData() { return StyleableProperties.VGAP; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "vgap"; } }; } return vgap; } private DoubleProperty vgap; public final void setVgap(double value) { vgapProperty().set(value); } public final double getVgap() { return vgap == null ? 0 : vgap.get(); }
The preferred width where content should wrap in a horizontal flowpane or the preferred height where content should wrap in a vertical flowpane.

This value is used only to compute the preferred size of the flowpane and may not reflect the actual width or height, which may change if the flowpane is resized to something other than its preferred size.

Applications should initialize this value to define a reasonable span for wrapping the content.

Returns:the preferred width where content should wrap in a horizontal flowpane or the preferred height where content should wrap in a vertical flowpane
/** * The preferred width where content should wrap in a horizontal flowpane or * the preferred height where content should wrap in a vertical flowpane. * <p> * This value is used only to compute the preferred size of the flowpane and may * not reflect the actual width or height, which may change if the flowpane is * resized to something other than its preferred size. * <p> * Applications should initialize this value to define a reasonable span * for wrapping the content. * * @return the preferred width where content should wrap in a horizontal * flowpane or the preferred height where content should wrap in a vertical * flowpane */
public final DoubleProperty prefWrapLengthProperty() { if (prefWrapLength == null) { prefWrapLength = new DoublePropertyBase(400) { @Override protected void invalidated() { requestLayout(); } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "prefWrapLength"; } }; } return prefWrapLength; } private DoubleProperty prefWrapLength; public final void setPrefWrapLength(double value) { prefWrapLengthProperty().set(value); } public final double getPrefWrapLength() { return prefWrapLength == null ? 400 : prefWrapLength.get(); }
The overall alignment of the flowpane's content within its width and height.

For a horizontal flowpane, each row will be aligned within the flowpane's width using the alignment's hpos value, and the rows will be aligned within the flowpane's height using the alignment's vpos value.

For a vertical flowpane, each column will be aligned within the flowpane's height using the alignment's vpos value, and the columns will be aligned within the flowpane's width using the alignment's hpos value.

Returns:the overall alignment of the flowpane's content within its width and height
/** * The overall alignment of the flowpane's content within its width and height. * <p>For a horizontal flowpane, each row will be aligned within the flowpane's width * using the alignment's hpos value, and the rows will be aligned within the * flowpane's height using the alignment's vpos value. * <p>For a vertical flowpane, each column will be aligned within the flowpane's height * using the alignment's vpos value, and the columns will be aligned within the * flowpane's width using the alignment's hpos value. * @return the overall alignment of the flowpane's content within its width * and height */
public final ObjectProperty<Pos> alignmentProperty() { if (alignment == null) { alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, Pos> getCssMetaData() { return StyleableProperties.ALIGNMENT; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "alignment"; } }; } return alignment; } private ObjectProperty<Pos> alignment; public final void setAlignment(Pos value) { alignmentProperty().set(value); } public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); } private Pos getAlignmentInternal() { Pos localPos = getAlignment(); return localPos == null ? Pos.TOP_LEFT : localPos; }
The horizontal alignment of nodes within each column of a vertical flowpane. The property is ignored for horizontal flowpanes.
Returns:the horizontal alignment of nodes within each column of a vertical flowpane
/** * The horizontal alignment of nodes within each column of a vertical flowpane. * The property is ignored for horizontal flowpanes. * @return the horizontal alignment of nodes within each column of a * vertical flowpane */
public final ObjectProperty<HPos> columnHalignmentProperty() { if (columnHalignment == null) { columnHalignment = new StyleableObjectProperty<HPos>(HPos.LEFT) { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, HPos> getCssMetaData() { return StyleableProperties.COLUMN_HALIGNMENT; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "columnHalignment"; } }; } return columnHalignment; } private ObjectProperty<HPos> columnHalignment; public final void setColumnHalignment(HPos value) { columnHalignmentProperty().set(value); } public final HPos getColumnHalignment() { return columnHalignment == null ? HPos.LEFT : columnHalignment.get(); } private HPos getColumnHalignmentInternal() { HPos localPos = getColumnHalignment(); return localPos == null ? HPos.LEFT : localPos; }
The vertical alignment of nodes within each row of a horizontal flowpane. If this property is set to VPos.BASELINE, then the flowpane will always resize children to their preferred heights, rather than expanding heights to fill the row height. The property is ignored for vertical flowpanes.
Returns:the vertical alignment of nodes within each row of a horizontal flowpane
/** * The vertical alignment of nodes within each row of a horizontal flowpane. * If this property is set to VPos.BASELINE, then the flowpane will always * resize children to their preferred heights, rather than expanding heights * to fill the row height. * The property is ignored for vertical flowpanes. * @return the vertical alignment of nodes within each row of a horizontal * flowpane */
public final ObjectProperty<VPos> rowValignmentProperty() { if (rowValignment == null) { rowValignment = new StyleableObjectProperty<VPos>(VPos.CENTER) { @Override public void invalidated() { requestLayout(); } @Override public CssMetaData<FlowPane, VPos> getCssMetaData() { return StyleableProperties.ROW_VALIGNMENT; } @Override public Object getBean() { return FlowPane.this; } @Override public String getName() { return "rowValignment"; } }; } return rowValignment; } private ObjectProperty<VPos> rowValignment; public final void setRowValignment(VPos value) { rowValignmentProperty().set(value); } public final VPos getRowValignment() { return rowValignment == null ? VPos.CENTER : rowValignment.get(); } private VPos getRowValignmentInternal() { VPos localPos = getRowValignment(); return localPos == null ? VPos.CENTER : localPos; } @Override public Orientation getContentBias() { return getOrientation(); } @Override protected double computeMinWidth(double height) { if (getContentBias() == HORIZONTAL) { double maxPref = 0; final List<Node> children = getChildren(); for (int i=0, size=children.size(); i<size; i++) { Node child = children.get(i); if (child.isManaged()) { maxPref = Math.max(maxPref, child.prefWidth(-1)); } } final Insets insets = getInsets(); return insets.getLeft() + snapSizeX(maxPref) + insets.getRight(); } return computePrefWidth(height); } @Override protected double computeMinHeight(double width) { if (getContentBias() == VERTICAL) { double maxPref = 0; final List<Node> children = getChildren(); for (int i=0, size=children.size(); i<size; i++) { Node child = children.get(i); if (child.isManaged()) { maxPref = Math.max(maxPref, child.prefHeight(-1)); } } final Insets insets = getInsets(); return insets.getTop() + snapSizeY(maxPref) + insets.getBottom(); } return computePrefHeight(width); } @Override protected double computePrefWidth(double forHeight) { final Insets insets = getInsets(); if (getOrientation() == HORIZONTAL) { // horizontal double maxRunWidth = getPrefWrapLength(); List<Run> hruns = getRuns(maxRunWidth); double w = computeContentWidth(hruns); w = getPrefWrapLength() > w ? getPrefWrapLength() : w; return insets.getLeft() + snapSizeX(w) + insets.getRight(); } else { // vertical double maxRunHeight = forHeight != -1? forHeight - insets.getTop() - insets.getBottom() : getPrefWrapLength(); List<Run> vruns = getRuns(maxRunHeight); return insets.getLeft() + computeContentWidth(vruns) + insets.getRight(); } } @Override protected double computePrefHeight(double forWidth) { final Insets insets = getInsets(); if (getOrientation() == HORIZONTAL) { // horizontal double maxRunWidth = forWidth != -1? forWidth - insets.getLeft() - insets.getRight() : getPrefWrapLength(); List<Run> hruns = getRuns(maxRunWidth); return insets.getTop() + computeContentHeight(hruns) + insets.getBottom(); } else { // vertical double maxRunHeight = getPrefWrapLength(); List<Run> vruns = getRuns(maxRunHeight); double h = computeContentHeight(vruns); h = getPrefWrapLength() > h ? getPrefWrapLength() : h; return insets.getTop() + snapSizeY(h) + insets.getBottom(); } } @Override public void requestLayout() { if (!computingRuns) { runs = null; } super.requestLayout(); } private List<Run> runs = null; private double lastMaxRunLength = -1; boolean computingRuns = false; private List<Run> getRuns(double maxRunLength) { if (runs == null || maxRunLength != lastMaxRunLength) { computingRuns = true; lastMaxRunLength = maxRunLength; runs = new ArrayList(); double runLength = 0; double runOffset = 0; Run run = new Run(); double vgap = snapSpaceY(this.getVgap()); double hgap = snapSpaceX(this.getHgap()); final List<Node> children = getChildren(); for (int i=0, size=children.size(); i<size; i++) { Node child = children.get(i); if (child.isManaged()) { LayoutRect nodeRect = new LayoutRect(); nodeRect.node = child; Insets margin = getMargin(child); nodeRect.width = computeChildPrefAreaWidth(child, margin); nodeRect.height = computeChildPrefAreaHeight(child, margin); double nodeLength = getOrientation() == HORIZONTAL ? nodeRect.width : nodeRect.height; if (runLength + nodeLength > maxRunLength && runLength > 0) { // wrap to next run *unless* its the only node in the run normalizeRun(run, runOffset); if (getOrientation() == HORIZONTAL) { // horizontal runOffset += run.height + vgap; } else { // vertical runOffset += run.width + hgap; } runs.add(run); runLength = 0; run = new Run(); } if (getOrientation() == HORIZONTAL) { // horizontal nodeRect.x = runLength; runLength += nodeRect.width + hgap; } else { // vertical nodeRect.y = runLength; runLength += nodeRect.height + vgap; } run.rects.add(nodeRect); } } // insert last run normalizeRun(run, runOffset); runs.add(run); computingRuns = false; } return runs; } private void normalizeRun(final Run run, double runOffset) { if (getOrientation() == HORIZONTAL) { // horizontal ArrayList<Node> rownodes = new ArrayList(); run.width = (run.rects.size()-1)*snapSpaceX(getHgap()); for (int i=0, max=run.rects.size(); i<max; i++) { LayoutRect lrect = run.rects.get(i); rownodes.add(lrect.node); run.width += lrect.width; lrect.y = runOffset; } run.height = computeMaxPrefAreaHeight(rownodes, marginAccessor, getRowValignment()); run.baselineOffset = getRowValignment() == VPos.BASELINE? getAreaBaselineOffset(rownodes, marginAccessor, i -> run.rects.get(i).width, run.height, true) : 0; } else { // vertical run.height = (run.rects.size()-1)*snapSpaceY(getVgap()); double maxw = 0; for (int i=0, max=run.rects.size(); i<max; i++) { LayoutRect lrect = run.rects.get(i); run.height += lrect.height; lrect.x = runOffset; maxw = Math.max(maxw, lrect.width); } run.width = maxw; run.baselineOffset = run.height; } } private double computeContentWidth(List<Run> runs) { double cwidth = getOrientation() == HORIZONTAL ? 0 : (runs.size()-1)*snapSpaceX(getHgap()); for (int i=0, max=runs.size(); i<max; i++) { Run run = runs.get(i); if (getOrientation() == HORIZONTAL) { cwidth = Math.max(cwidth, run.width); } else { // vertical cwidth += run.width; } } return cwidth; } private double computeContentHeight(List<Run> runs) { double cheight = getOrientation() == VERTICAL ? 0 : (runs.size()-1)*snapSpaceY(getVgap()); for (int i=0, max=runs.size(); i<max; i++) { Run run = runs.get(i); if (getOrientation() == VERTICAL) { cheight = Math.max(cheight, run.height); } else { // horizontal cheight += run.height; } } return cheight; } @Override protected void layoutChildren() { final Insets insets = getInsets(); final double width = getWidth(); final double height = getHeight(); final double top = insets.getTop(); final double left = insets.getLeft(); final double bottom = insets.getBottom(); final double right = insets.getRight(); final double insideWidth = width - left - right; final double insideHeight = height - top - bottom; //REMIND(aim): need to figure out how to cache the runs to avoid over-calculation final List<Run> runs = getRuns(getOrientation() == HORIZONTAL ? insideWidth : insideHeight); // Now that the nodes are broken into runs, figure out alignments for (int i=0, max=runs.size(); i<max; i++) { final Run run = runs.get(i); final double xoffset = left + computeXOffset(insideWidth, getOrientation() == HORIZONTAL ? run.width : computeContentWidth(runs), getAlignmentInternal().getHpos()); final double yoffset = top + computeYOffset(insideHeight, getOrientation() == VERTICAL ? run.height : computeContentHeight(runs), getAlignmentInternal().getVpos()); for (int j = 0; j < run.rects.size(); j++) { final LayoutRect lrect = run.rects.get(j); // System.out.println("flowpane.layout: run="+i+" "+run.width+"x"+run.height+" xoffset="+xoffset+" yoffset="+yoffset+" lrect="+lrect); final double x = xoffset + lrect.x; final double y = yoffset + lrect.y; layoutInArea(lrect.node, x, y, getOrientation() == HORIZONTAL? lrect.width : run.width, getOrientation() == VERTICAL? lrect.height : run.height, run.baselineOffset, getMargin(lrect.node), getColumnHalignmentInternal(), getRowValignmentInternal()); } } }
* Stylesheet Handling * *
/*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/
/* * Super-lazy instantiation pattern from Bill Pugh. */ private static class StyleableProperties { private static final CssMetaData<FlowPane,Pos> ALIGNMENT = new CssMetaData<FlowPane,Pos>("-fx-alignment", new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT) { @Override public boolean isSettable(FlowPane node) { return node.alignment == null || !node.alignment.isBound(); } @Override public StyleableProperty<Pos> getStyleableProperty(FlowPane node) { return (StyleableProperty<Pos>)node.alignmentProperty(); } }; private static final CssMetaData<FlowPane,HPos> COLUMN_HALIGNMENT = new CssMetaData<FlowPane,HPos>("-fx-column-halignment", new EnumConverter<HPos>(HPos.class), HPos.LEFT) { @Override public boolean isSettable(FlowPane node) { return node.columnHalignment == null || !node.columnHalignment.isBound(); } @Override public StyleableProperty<HPos> getStyleableProperty(FlowPane node) { return (StyleableProperty<HPos>)node.columnHalignmentProperty(); } }; private static final CssMetaData<FlowPane,Number> HGAP = new CssMetaData<FlowPane,Number>("-fx-hgap", SizeConverter.getInstance(), 0.0){ @Override public boolean isSettable(FlowPane node) { return node.hgap == null || !node.hgap.isBound(); } @Override public StyleableProperty<Number> getStyleableProperty(FlowPane node) { return (StyleableProperty<Number>)node.hgapProperty(); } }; private static final CssMetaData<FlowPane,VPos> ROW_VALIGNMENT = new CssMetaData<FlowPane,VPos>("-fx-row-valignment", new EnumConverter<VPos>(VPos.class), VPos.CENTER) { @Override public boolean isSettable(FlowPane node) { return node.rowValignment == null || !node.rowValignment.isBound(); } @Override public StyleableProperty<VPos> getStyleableProperty(FlowPane node) { return (StyleableProperty<VPos>)node.rowValignmentProperty(); } }; private static final CssMetaData<FlowPane,Orientation> ORIENTATION = new CssMetaData<FlowPane,Orientation>("-fx-orientation", new EnumConverter<Orientation>(Orientation.class), Orientation.HORIZONTAL) { @Override public Orientation getInitialValue(FlowPane node) { // A vertical flow pane should remain vertical return node.getOrientation(); } @Override public boolean isSettable(FlowPane node) { return node.orientation == null || !node.orientation.isBound(); } @Override public StyleableProperty<Orientation> getStyleableProperty(FlowPane node) { return (StyleableProperty<Orientation>)node.orientationProperty(); } }; private static final CssMetaData<FlowPane,Number> VGAP = new CssMetaData<FlowPane,Number>("-fx-vgap", SizeConverter.getInstance(), 0.0){ @Override public boolean isSettable(FlowPane node) { return node.vgap == null || !node.vgap.isBound(); } @Override public StyleableProperty<Number> getStyleableProperty(FlowPane node) { return (StyleableProperty<Number>)node.vgapProperty(); } }; private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; static { final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); styleables.add(ALIGNMENT); styleables.add(COLUMN_HALIGNMENT); styleables.add(HGAP); styleables.add(ROW_VALIGNMENT); styleables.add(ORIENTATION); styleables.add(VGAP); STYLEABLES = Collections.unmodifiableList(styleables); } }
Returns:The CssMetaData associated with this class, which may include the CssMetaData of its superclasses.
Since:JavaFX 8.0
/** * @return The CssMetaData associated with this class, which may include the * CssMetaData of its superclasses. * @since JavaFX 8.0 */
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { return StyleableProperties.STYLEABLES; }
{@inheritDoc}
Since:JavaFX 8.0
/** * {@inheritDoc} * * @since JavaFX 8.0 */
@Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { return getClassCssMetaData(); } //REMIND(aim); replace when we get mutable rects private static class LayoutRect { public Node node; double x; double y; double width; double height; @Override public String toString() { return "LayoutRect node id="+node.getId()+" "+x+","+y+" "+width+"x"+height; } } private static class Run { ArrayList<LayoutRect> rects = new ArrayList(); double width; double height; double baselineOffset; } }