orientation;
public final void setOrientation(Orientation value) { orientationProperty().set(value); }
public final Orientation getOrientation() { return orientation == null ? HORIZONTAL : orientation.get(); }
/**
* The preferred number of rows for a vertical tilepane.
* This value is used only to compute the preferred size of the tilepane
* and may not reflect the actual number of rows, which may change
* if the tilepane is resized to something other than its preferred height.
* This property is ignored for a horizontal tilepane.
*
* It is recommended that the application initialize this value for a
* vertical tilepane.
*/
public final IntegerProperty prefRowsProperty() {
if (prefRows == null) {
prefRows = new StyleableIntegerProperty(5) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.PREF_ROWS;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "prefRows";
}
};
}
return prefRows;
}
private IntegerProperty prefRows;
public final void setPrefRows(int value) { prefRowsProperty().set(value); }
public final int getPrefRows() { return prefRows == null ? 5 : prefRows.get(); }
/**
* The preferred number of columns for a horizontal tilepane.
* This value is used only to compute the preferred size of the tilepane
* and may not reflect the actual number of rows, which may change if the
* tilepane is resized to something other than its preferred height.
* This property is ignored for a vertical tilepane.
*
* It is recommended that the application initialize this value for a
* horizontal tilepane.
*/
public final IntegerProperty prefColumnsProperty() {
if (prefColumns == null) {
prefColumns = new StyleableIntegerProperty(5) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.PREF_COLUMNS;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "prefColumns";
}
};
}
return prefColumns;
}
private IntegerProperty prefColumns;
public final void setPrefColumns(int value) { prefColumnsProperty().set(value); }
public final int getPrefColumns() { return prefColumns == null ? 5 : prefColumns.get(); }
/**
* The preferred width of each tile.
* If equal to USE_COMPUTED_SIZE (the default) the tile width wlll be
* automatically recomputed by the tilepane when the preferred size of children
* changes to accommodate the widest child. If the application sets this property
* to value greater than 0, then tiles will be set to that width and the tilepane
* will attempt to resize children to fit within that width (if they are resizable and
* their min-max width range allows it).
*/
public final DoubleProperty prefTileWidthProperty() {
if (prefTileWidth == null) {
prefTileWidth = new StyleableDoubleProperty(USE_COMPUTED_SIZE) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.PREF_TILE_WIDTH;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "prefTileWidth";
}
};
}
return prefTileWidth;
}
private DoubleProperty prefTileWidth;
public final void setPrefTileWidth(double value) { prefTileWidthProperty().set(value); }
public final double getPrefTileWidth() { return prefTileWidth == null ? USE_COMPUTED_SIZE : prefTileWidth.get(); }
/**
* The preferred height of each tile.
* If equal to USE_COMPUTED_SIZE (the default) the tile height wlll be
* automatically recomputed by the tilepane when the preferred size of children
* changes to accommodate the tallest child. If the application sets this property
* to value greater than 0, then tiles will be set to that height and the tilepane
* will attempt to resize children to fit within that height (if they are resizable and
* their min-max height range allows it).
*/
public final DoubleProperty prefTileHeightProperty() {
if (prefTileHeight == null) {
prefTileHeight = new StyleableDoubleProperty(USE_COMPUTED_SIZE) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.PREF_TILE_HEIGHT;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "prefTileHeight";
}
};
}
return prefTileHeight;
}
private DoubleProperty prefTileHeight;
public final void setPrefTileHeight(double value) { prefTileHeightProperty().set(value); }
public final double getPrefTileHeight() { return prefTileHeight == null ? USE_COMPUTED_SIZE : prefTileHeight.get(); }
/**
* The actual width of each tile. This property is read-only.
*/
public final ReadOnlyDoubleProperty tileWidthProperty() {
if (tileWidth == null) {
tileWidth = new TileSizeProperty("tileWidth", _tileWidth) {
@Override
public double compute() {
return computeTileWidth();
}
};
}
return tileWidth;
}
private TileSizeProperty tileWidth;
private void invalidateTileWidth() {
if (tileWidth != null) {
tileWidth.invalidate();
} else {
_tileWidth = -1;
}
}
public final double getTileWidth() {
if (tileWidth != null) {
return tileWidth.get();
}
if (_tileWidth == -1) {
_tileWidth = computeTileWidth();
}
return _tileWidth;
}
/**
* The actual height of each tile. This property is read-only.
*/
public final ReadOnlyDoubleProperty tileHeightProperty() {
if (tileHeight == null) {
tileHeight = new TileSizeProperty("tileHeight", _tileHeight) {
@Override
public double compute() {
return computeTileHeight();
}
};
}
return tileHeight;
}
private TileSizeProperty tileHeight;
private void invalidateTileHeight() {
if (tileHeight != null) {
tileHeight.invalidate();
} else {
_tileHeight = -1;
}
}
public final double getTileHeight() {
if (tileHeight != null) {
return tileHeight.get();
}
if (_tileHeight == -1) {
_tileHeight = computeTileHeight();
}
return _tileHeight;
}
/**
* The amount of horizontal space between each tile in a row.
*/
public final DoubleProperty hgapProperty() {
if (hgap == null) {
hgap = new StyleableDoubleProperty() {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.HGAP;
}
@Override
public Object getBean() {
return TilePane.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 tile in a column.
*/
public final DoubleProperty vgapProperty() {
if (vgap == null) {
vgap = new StyleableDoubleProperty() {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.VGAP;
}
@Override
public Object getBean() {
return TilePane.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 overall alignment of the tilepane's content within its width and height.
* For a horizontal tilepane, each row will be aligned within the tilepane's width
* using the alignment's hpos value, and the rows will be aligned within the
* tilepane's height using the alignment's vpos value.
*
For a vertical tilepane, each column will be aligned within the tilepane's height
* using the alignment's vpos value, and the columns will be aligned within the
* tilepane's width using the alignment's hpos value.
*
*/
public final ObjectProperty alignmentProperty() {
if (alignment == null) {
alignment = new StyleableObjectProperty(Pos.TOP_LEFT) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.ALIGNMENT;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "alignment";
}
};
}
return alignment;
}
private ObjectProperty 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 default alignment of each child within its tile.
* This may be overridden on individual children by setting the child's
* alignment constraint.
*/
public final ObjectProperty tileAlignmentProperty() {
if (tileAlignment == null) {
tileAlignment = new StyleableObjectProperty(Pos.CENTER) {
@Override
public void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.TILE_ALIGNMENT;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return "tileAlignment";
}
};
}
return tileAlignment;
}
private ObjectProperty tileAlignment;
public final void setTileAlignment(Pos value) { tileAlignmentProperty().set(value); }
public final Pos getTileAlignment() { return tileAlignment == null ? Pos.CENTER : tileAlignment.get(); }
private Pos getTileAlignmentInternal() {
Pos localPos = getTileAlignment();
return localPos == null ? Pos.CENTER : localPos;
}
@Override public Orientation getContentBias() {
return getOrientation();
}
@Override public void requestLayout() {
invalidateTileWidth();
invalidateTileHeight();
super.requestLayout();
}
@Override protected double computeMinWidth(double height) {
if (getContentBias() == Orientation.HORIZONTAL) {
return getInsets().getLeft() + getTileWidth() + getInsets().getRight();
}
return computePrefWidth(height);
}
@Override protected double computeMinHeight(double width) {
if (getContentBias() == Orientation.VERTICAL) {
return getInsets().getTop() + getTileHeight() + getInsets().getBottom();
}
return computePrefHeight(width);
}
@Override protected double computePrefWidth(double forHeight) {
List managed = getManagedChildren();
final Insets insets = getInsets();
int prefCols = 0;
if (forHeight != -1) {
// first compute number of rows that will fit in given height and
// compute pref columns from that
int prefRows = computeRows(forHeight - snapSpaceY(insets.getTop()) - snapSpaceY(insets.getBottom()), getTileHeight());
prefCols = computeOther(managed.size(), prefRows);
} else {
prefCols = getOrientation() == HORIZONTAL? getPrefColumns() : computeOther(managed.size(), getPrefRows());
}
return snapSpaceX(insets.getLeft()) +
computeContentWidth(prefCols, getTileWidth()) +
snapSpaceX(insets.getRight());
}
@Override protected double computePrefHeight(double forWidth) {
List managed = getManagedChildren();
final Insets insets = getInsets();
int prefRows = 0;
if (forWidth != -1) {
// first compute number of columns that will fit in given width and
// compute pref rows from that
int prefCols = computeColumns(forWidth - snapSpaceX(insets.getLeft()) - snapSpaceX(insets.getRight()), getTileWidth());
prefRows = computeOther(managed.size(), prefCols);
} else {
prefRows = getOrientation() == HORIZONTAL? computeOther(managed.size(), getPrefColumns()) : getPrefRows();
}
return snapSpaceY(insets.getTop()) +
computeContentHeight(prefRows, getTileHeight()) +
snapSpaceY(insets.getBottom());
}
private double computeTileWidth() {
List managed = getManagedChildren();
double preftilewidth = getPrefTileWidth();
if (preftilewidth == USE_COMPUTED_SIZE) {
double h = -1;
boolean vertBias = false;
for (int i = 0, size = managed.size(); i < size; i++) {
Node child = managed.get(i);
if (child.getContentBias() == VERTICAL) {
vertBias = true;
break;
}
}
if (vertBias) {
// widest may depend on height of tile
h = computeMaxPrefAreaHeight(managed, marginAccessor, -1, getTileAlignmentInternal().getVpos());
}
return snapSizeX(computeMaxPrefAreaWidth(managed, marginAccessor, h, true));
}
return snapSizeX(preftilewidth);
}
private double computeTileHeight() {
List managed = getManagedChildren();
double preftileheight = getPrefTileHeight();
if (preftileheight == USE_COMPUTED_SIZE) {
double w = -1;
boolean horizBias = false;
for (int i = 0, size = managed.size(); i < size; i++) {
Node child = managed.get(i);
if (child.getContentBias() == Orientation.HORIZONTAL) {
horizBias = true;
break;
}
}
if (horizBias) {
// tallest may depend on width of tile
w = computeMaxPrefAreaWidth(managed, marginAccessor);
}
return snapSizeY(computeMaxPrefAreaHeight(managed, marginAccessor, w, getTileAlignmentInternal().getVpos()));
}
return snapSizeY(preftileheight);
}
private int computeOther(int numNodes, int numCells) {
double other = (double)numNodes/(double)Math.max(1, numCells);
return (int)Math.ceil(other);
}
private int computeColumns(double width, double tilewidth) {
double snappedHgap = snapSpaceX(getHgap());
return Math.max(1,(int)((width + snappedHgap) / (tilewidth + snappedHgap)));
}
private int computeRows(double height, double tileheight) {
double snappedVgap = snapSpaceY(getVgap());
return Math.max(1, (int)((height + snappedVgap) / (tileheight + snappedVgap)));
}
private double computeContentWidth(int columns, double tilewidth) {
if (columns == 0) return 0;
return columns * tilewidth + (columns - 1) * snapSpaceX(getHgap());
}
private double computeContentHeight(int rows, double tileheight) {
if (rows == 0) return 0;
return rows * tileheight + (rows - 1) * snapSpaceY(getVgap());
}
@Override protected void layoutChildren() {
List managed = getManagedChildren();
HPos hpos = getAlignmentInternal().getHpos();
VPos vpos = getAlignmentInternal().getVpos();
double width = getWidth();
double height = getHeight();
double top = snapSpaceY(getInsets().getTop());
double left = snapSpaceX(getInsets().getLeft());
double bottom = snapSpaceY(getInsets().getBottom());
double right = snapSpaceX(getInsets().getRight());
double vgap = snapSpaceY(getVgap());
double hgap = snapSpaceX(getHgap());
double insideWidth = width - left - right;
double insideHeight = height - top - bottom;
double tileWidth = getTileWidth() > insideWidth ? insideWidth : getTileWidth();
double tileHeight = getTileHeight() > insideHeight ? insideHeight : getTileHeight();
int lastRowRemainder = 0;
int lastColumnRemainder = 0;
if (getOrientation() == HORIZONTAL) {
actualColumns = computeColumns(insideWidth, tileWidth);
actualRows = computeOther(managed.size(), actualColumns);
// remainder will be 0 if last row is filled
lastRowRemainder = hpos != HPos.LEFT?
actualColumns - (actualColumns*actualRows - managed.size()) : 0;
} else {
// vertical
actualRows = computeRows(insideHeight, tileHeight);
actualColumns = computeOther(managed.size(), actualRows);
// remainder will be 0 if last column is filled
lastColumnRemainder = vpos != VPos.TOP?
actualRows - (actualColumns*actualRows - managed.size()) : 0;
}
double rowX = left + computeXOffset(insideWidth,
computeContentWidth(actualColumns, tileWidth),
hpos);
double columnY = top + computeYOffset(insideHeight,
computeContentHeight(actualRows, tileHeight),
vpos);
double lastRowX = lastRowRemainder > 0?
left + computeXOffset(insideWidth,
computeContentWidth(lastRowRemainder, tileWidth),
hpos) : rowX;
double lastColumnY = lastColumnRemainder > 0?
top + computeYOffset(insideHeight,
computeContentHeight(lastColumnRemainder, tileHeight),
vpos) : columnY;
double baselineOffset = getTileAlignmentInternal().getVpos() == VPos.BASELINE ?
getAreaBaselineOffset(managed, marginAccessor, i -> tileWidth, tileHeight, false) : -1;
int r = 0;
int c = 0;
for (int i = 0, size = managed.size(); i < size; i++) {
Node child = managed.get(i);
double xoffset = r == (actualRows - 1)? lastRowX : rowX;
double yoffset = c == (actualColumns - 1)? lastColumnY : columnY;
double tileX = xoffset + (c * (tileWidth + hgap));
double tileY = yoffset + (r * (tileHeight + vgap));
Pos childAlignment = getAlignment(child);
layoutInArea(child, tileX, tileY, tileWidth, tileHeight, baselineOffset,
getMargin(child),
childAlignment != null? childAlignment.getHpos() : getTileAlignmentInternal().getHpos(),
childAlignment != null? childAlignment.getVpos() : getTileAlignmentInternal().getVpos());
if (getOrientation() == HORIZONTAL) {
if (++c == actualColumns) {
c = 0;
r++;
}
} else {
// vertical
if (++r == actualRows) {
r = 0;
c++;
}
}
}
}
private int actualRows = 0;
private int actualColumns = 0;
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
private static class StyleableProperties {
private static final CssMetaData ALIGNMENT =
new CssMetaData("-fx-alignment",
new EnumConverter(Pos.class),
Pos.TOP_LEFT) {
@Override
public boolean isSettable(TilePane node) {
return node.alignment == null || !node.alignment.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.alignmentProperty();
}
};
private static final CssMetaData PREF_COLUMNS =
new CssMetaData("-fx-pref-columns",
SizeConverter.getInstance(), 5.0) {
@Override
public boolean isSettable(TilePane node) {
return node.prefColumns == null ||
!node.prefColumns.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.prefColumnsProperty();
}
};
private static final CssMetaData HGAP =
new CssMetaData("-fx-hgap",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(TilePane node) {
return node.hgap == null ||
!node.hgap.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.hgapProperty();
}
};
private static final CssMetaData PREF_ROWS =
new CssMetaData("-fx-pref-rows",
SizeConverter.getInstance(), 5.0) {
@Override
public boolean isSettable(TilePane node) {
return node.prefRows == null ||
!node.prefRows.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.prefRowsProperty();
}
};
private static final CssMetaData TILE_ALIGNMENT =
new CssMetaData("-fx-tile-alignment",
new EnumConverter(Pos.class),
Pos.CENTER) {
@Override
public boolean isSettable(TilePane node) {
return node.tileAlignment == null ||
!node.tileAlignment.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.tileAlignmentProperty();
}
};
private static final CssMetaData PREF_TILE_WIDTH =
new CssMetaData("-fx-pref-tile-width",
SizeConverter.getInstance(), USE_COMPUTED_SIZE) {
@Override
public boolean isSettable(TilePane node) {
return node.prefTileWidth == null ||
!node.prefTileWidth.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.prefTileWidthProperty();
}
};
private static final CssMetaData PREF_TILE_HEIGHT =
new CssMetaData("-fx-pref-tile-height",
SizeConverter.getInstance(), USE_COMPUTED_SIZE) {
@Override
public boolean isSettable(TilePane node) {
return node.prefTileHeight == null ||
!node.prefTileHeight.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.prefTileHeightProperty();
}
};
private static final CssMetaData ORIENTATION =
new CssMetaData("-fx-orientation",
new EnumConverter(Orientation.class),
Orientation.HORIZONTAL) {
@Override
public Orientation getInitialValue(TilePane node) {
// A vertical TilePane should remain vertical
return node.getOrientation();
}
@Override
public boolean isSettable(TilePane node) {
return node.orientation == null ||
!node.orientation.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.orientationProperty();
}
};
private static final CssMetaData VGAP =
new CssMetaData("-fx-vgap",
SizeConverter.getInstance(), 0.0) {
@Override
public boolean isSettable(TilePane node) {
return node.vgap == null ||
!node.vgap.isBound();
}
@Override
public StyleableProperty getStyleableProperty(TilePane node) {
return (StyleableProperty)node.vgapProperty();
}
};
private static final List> STYLEABLES;
static {
final List> styleables =
new ArrayList>(Region.getClassCssMetaData());
styleables.add(ALIGNMENT);
styleables.add(HGAP);
styleables.add(ORIENTATION);
styleables.add(PREF_COLUMNS);
styleables.add(PREF_ROWS);
styleables.add(PREF_TILE_WIDTH);
styleables.add(PREF_TILE_HEIGHT);
styleables.add(TILE_ALIGNMENT);
styleables.add(VGAP);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
* @since JavaFX 8.0
*/
public static List> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/**
* {@inheritDoc}
*
* @since JavaFX 8.0
*/
@Override
public List> getCssMetaData() {
return getClassCssMetaData();
}
private abstract class TileSizeProperty extends ReadOnlyDoubleProperty {
private final String name;
private ExpressionHelper helper;
private double value;
private boolean valid;
TileSizeProperty(String name, double initSize) {
this.name = name;
this.value = initSize;
this.valid = initSize != -1;
}
@Override
public Object getBean() {
return TilePane.this;
}
@Override
public String getName() {
return name;
}
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener super Number> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener super Number> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public double get() {
if (!valid) {
value = compute();
valid = true;
}
return value;
}
public void invalidate() {
if (valid) {
valid = false;
ExpressionHelper.fireValueChangedEvent(helper);
}
}
public abstract double compute();
}
}