/* * Copyright (c) 2010, 2015, 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 com.sun.javafx.util.Utils; import javafx.beans.InvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectPropertyBase; import javafx.beans.value.ChangeListener; import javafx.collections.ObservableList; import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.image.Image; import javafx.scene.shape.Shape; import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineJoin; import javafx.scene.shape.StrokeType; import javafx.util.Callback; import java.util.ArrayList; import java.util.Collections; import java.util.Arrays; import java.util.List; import java.util.function.Function; import com.sun.javafx.util.Logging; import com.sun.javafx.util.TempState; import com.sun.javafx.binding.ExpressionHelper; import com.sun.javafx.css.converters.BooleanConverter; import com.sun.javafx.css.converters.InsetsConverter; import com.sun.javafx.css.converters.ShapeConverter; import com.sun.javafx.css.converters.SizeConverter; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.PickRay; import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Vec2d; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.scene.DirtyBits; import com.sun.javafx.scene.input.PickResultChooser; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.sg.prism.NGRegion; import com.sun.javafx.tk.Toolkit; import sun.util.logging.PlatformLogger; import sun.util.logging.PlatformLogger.Level; /** * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers. * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds * and borders. It is designed to support as much of the CSS3 specification for backgrounds * and borders as is relevant to JavaFX. * The full specification is available at the W3C. *
* Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside * these bounds. The content area of a Region is the area which is occupied for the layout of its children. * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region, * but does not affect the layout bounds. * * A Region has a Background, and a Border, although either or both of these might be empty. The Background * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and * zero or more BorderImages. All BackgroundFills are drawn first, followed by BackgroundImages, BorderStrokes, * and finally BorderImages. The content is drawn above all backgrounds and borders. If a BorderImage is * present (and loaded all images properly), then no BorderStrokes are actually drawn, although they are * considered for computing the position of the content area (see the stroke width property of a BorderStroke). * These semantics are in line with the CSS 3 specification. The purpose of these semantics are to allow an * application to specify a fallback BorderStroke to be displayed in the case that an ImageStroke fails to * download or load. * * By default a Region appears as a Rectangle. A BackgroundFill radii might cause the Rectangle to appear rounded. * This affects not only making the visuals look like a rounded rectangle, but it also causes the picking behavior * of the Region to act like a rounded rectangle, such that locations outside the corner radii are ignored. A * Region can be made to use any shape, however, by specifing the {@code shape} property. If a shape is specified, * then all BackgroundFills, BackgroundImages, and BorderStrokes will be applied to the shape. BorderImages are * not used for Regions which have a shape specified. * * A Region with a shape * * Although the layout bounds of a Region are not influenced by any Border or Background, the content area * insets and the picking area of the Region are. The {@code insets} of the Region define the distance * between the edge of the layout bounds and the edge of the content area. For example, if the Region * layout bounds are (x=0, y=0, width=200, height=100), and the insets are (top=10, right=20, bottom=30, left=40), * then the content area bounds will be (x=40, y=10, width=140, height=60). A Region subclass which is laying * out its children should compute and honor these content area bounds. * * By default a Region inherits the layout behavior of its superclass, {@link Parent}, * which means that it will resize any resizable child nodes to their preferred * size, but will not reposition them. If an application needs more specific * layout behavior, then it should use one of the Region subclasses: * {@link StackPane}, {@link HBox}, {@link VBox}, {@link TilePane}, {@link FlowPane}, * {@link BorderPane}, {@link GridPane}, or {@link AnchorPane}. * * To implement a more custom layout, a Region subclass must override * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, and * {@link #layoutChildren() layoutChildren}. Note that {@link #layoutChildren() layoutChildren} is called automatically * by the scene graph while executing a top-down layout pass and it should not be invoked directly by the * region subclass. * * Region subclasses which layout their children will position nodes by setting * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for * adjustments and animation. * @since JavaFX 2.0 */ public class Region extends Parent { /** * Sentinel value which can be passed to a region's * {@link #setMinWidth(double) setMinWidth}, * {@link #setMinHeight(double) setMinHeight}, * {@link #setMaxWidth(double) setMaxWidth} or * {@link #setMaxHeight(double) setMaxHeight} * methods to indicate that the preferred dimension should be used for that max and/or min constraint. */ public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY; /** * Sentinel value which can be passed to a region's * {@link #setMinWidth(double) setMinWidth}, * {@link #setMinHeight(double) setMinHeight}, * {@link #setPrefWidth(double) setPrefWidth}, * {@link #setPrefHeight(double) setPrefHeight}, * {@link #setMaxWidth(double) setMaxWidth}, * {@link #setMaxHeight(double) setMaxHeight} methods * to reset the region's size constraint back to it's intrinsic size returned * by {@link #computeMinWidth(double) computeMinWidth}, {@link #computeMinHeight(double) computeMinHeight}, * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, * {@link #computeMaxWidth(double) computeMaxWidth}, or {@link #computeMaxHeight(double) computeMaxHeight}. */ public static final double USE_COMPUTED_SIZE = -1; static Vec2d TEMP_VEC2D = new Vec2d(); /*************************************************************************** * * * Static convenience methods for layout * * * **************************************************************************/ /** * Computes the value based on the given min and max values. We encode in this * method the logic surrounding various edge cases, such as when the min is * specified as greater than the max, or the max less than the min, or a pref * value that exceeds either the max or min in their extremes. * * If the min is greater than the max, then we want to make sure the returned * value is the min. In other words, in such a case, the min becomes the only * acceptable return value. * * If the min and max values are well ordered, and the pref is less than the min * then the min is returned. Likewise, if the values are well ordered and the * pref is greater than the max, then the max is returned. If the pref lies * between the min and the max, then the pref is returned. * * * @param min The minimum bound * @param pref The value to be clamped between the min and max * @param max the maximum bound * @return the size bounded by min, pref, and max. */ static double boundedSize(double min, double pref, double max) { double a = pref >= min ? pref : min; double b = min >= max ? min : max; return a <= b ? a : b; } double adjustWidthByMargin(double width, Insets margin) { if (margin == null || margin == Insets.EMPTY) { return width; } boolean isSnapToPixel = isSnapToPixel(); return width - snapSpace(margin.getLeft(), isSnapToPixel) - snapSpace(margin.getRight(), isSnapToPixel); } double adjustHeightByMargin(double height, Insets margin) { if (margin == null || margin == Insets.EMPTY) { return height; } boolean isSnapToPixel = isSnapToPixel(); return height - snapSpace(margin.getTop(), isSnapToPixel) - snapSpace(margin.getBottom(), isSnapToPixel); } /** * If snapToPixel is true, then the value is rounded using Math.round. Otherwise, * the value is simply returned. This method will surely be JIT'd under normal * circumstances, however on an interpreter it would be better to inline this * method. However the use of Math.round here, and Math.ceil in snapSize is * not obvious, and so for code maintenance this logic is pulled out into * a separate method. * * @param value The value that needs to be snapped * @param snapToPixel Whether to snap to pixel * @return value either as passed in or rounded based on snapToPixel */ private static double snapSpace(double value, boolean snapToPixel) { return snapToPixel ? Math.round(value) : value; } /** * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise, * the value is simply returned. * * @param value The value that needs to be snapped * @param snapToPixel Whether to snap to pixel * @return value either as passed in or ceil'd based on snapToPixel */ private static double snapSize(double value, boolean snapToPixel) { return snapToPixel ? Math.ceil(value) : value; } /** * If snapToPixel is true, then the value is rounded using Math.round. Otherwise, * the value is simply returned. * * @param value The value that needs to be snapped * @param snapToPixel Whether to snap to pixel * @return value either as passed in or rounded based on snapToPixel */ private static double snapPosition(double value, boolean snapToPixel) { return snapToPixel ? Math.round(value) : value; } private static double snapPortion(double value, boolean snapToPixel) { if (snapToPixel) { return value == 0 ? 0 :(value > 0 ? Math.max(1, Math.floor(value)) : Math.min(-1, Math.ceil(value))); } return value; } double getAreaBaselineOffset(ListminWidth
, prefWidth
,
* and maxWidth
properties.
*/
private ReadOnlyDoubleWrapper width;
/**
* Because the width is very often set and very often read but only sometimes
* listened to, it is beneficial to use the super-lazy pattern property, where we
* only inflate the property object when widthProperty() is explicitly invoked.
*/
private double _width;
// Note that it is OK for this method to be protected so long as the width
// property is never bound. Only Region could do so because only Region has
// access to a writable property for "width", but since there is now a protected
// set method, it is impossible for Region to ever bind this property.
protected void setWidth(double value) {
if(width == null) {
widthChanged(value);
} else {
width.set(value);
}
}
private void widthChanged(double value) {
// It is possible that somebody sets the width of the region to a value which
// it previously held. If this is the case, we want to avoid excessive layouts.
// Note that I have biased this for layout over binding, because the widthProperty
// is now going to recompute the width eagerly. The cost of excessive and
// unnecessary bounds changes, however, is relatively high.
if (value != _width) {
_width = value;
cornersValid = false;
boundingBox = null;
impl_layoutBoundsChanged();
impl_geomChanged();
impl_markDirty(DirtyBits.NODE_GEOMETRY);
setNeedsLayout(true);
requestParentLayout();
}
}
public final double getWidth() { return width == null ? _width : width.get(); }
public final ReadOnlyDoubleProperty widthProperty() {
if (width == null) {
width = new ReadOnlyDoubleWrapper(_width) {
@Override protected void invalidated() { widthChanged(get()); }
@Override public Object getBean() { return Region.this; }
@Override public String getName() { return "width"; }
};
}
return width.getReadOnlyProperty();
}
/**
* The height of this resizable node. This property is set by the region's parent
* during layout and may not be set by the application. If an application
* needs to explicitly control the size of a region, it should override its
* preferred size range by setting the minHeight
, prefHeight
,
* and maxHeight
properties.
*/
private ReadOnlyDoubleWrapper height;
/**
* Because the height is very often set and very often read but only sometimes
* listened to, it is beneficial to use the super-lazy pattern property, where we
* only inflate the property object when heightProperty() is explicitly invoked.
*/
private double _height;
// Note that it is OK for this method to be protected so long as the height
// property is never bound. Only Region could do so because only Region has
// access to a writable property for "height", but since there is now a protected
// set method, it is impossible for Region to ever bind this property.
protected void setHeight(double value) {
if (height == null) {
heightChanged(value);
} else {
height.set(value);
}
}
private void heightChanged(double value) {
if (_height != value) {
_height = value;
cornersValid = false;
// It is possible that somebody sets the height of the region to a value which
// it previously held. If this is the case, we want to avoid excessive layouts.
// Note that I have biased this for layout over binding, because the heightProperty
// is now going to recompute the height eagerly. The cost of excessive and
// unnecessary bounds changes, however, is relatively high.
boundingBox = null;
// Note: although impl_geomChanged will usually also invalidate the
// layout bounds, that is not the case for Regions, and both must
// be called separately.
impl_geomChanged();
impl_layoutBoundsChanged();
// We use "NODE_GEOMETRY" to mean that the bounds have changed and
// need to be sync'd with the render tree
impl_markDirty(DirtyBits.NODE_GEOMETRY);
// Change of the height (or width) won't change the preferred size.
// So we don't need to flush the cache. We should however mark this node
// as needs layout to be internally layouted.
setNeedsLayout(true);
// This call is only needed when this was not called from the parent during the layout.
// Otherwise it would flush the cache of the parent, which is not necessary
requestParentLayout();
}
}
public final double getHeight() { return height == null ? _height : height.get(); }
public final ReadOnlyDoubleProperty heightProperty() {
if (height == null) {
height = new ReadOnlyDoubleWrapper(_height) {
@Override protected void invalidated() { heightChanged(get()); }
@Override public Object getBean() { return Region.this; }
@Override public String getName() { return "height"; }
};
}
return height.getReadOnlyProperty();
}
/**
* This class is reused for the min, pref, and max properties since
* they all performed the same function (to call requestParentLayout).
*/
private final class MinPrefMaxProperty extends StyleableDoubleProperty {
private final String name;
private final CssMetaData extends Styleable, Number> cssMetaData;
MinPrefMaxProperty(String name, double initialValue, CssMetaData extends Styleable, Number> cssMetaData) {
super(initialValue);
this.name = name;
this.cssMetaData = cssMetaData;
}
@Override public void invalidated() { requestParentLayout(); }
@Override public Object getBean() { return Region.this; }
@Override public String getName() { return name; }
@Override
public CssMetaData extends Styleable, Number> getCssMetaData() {
return cssMetaData;
}
}
/**
* Property for overriding the region's computed minimum width.
* This should only be set if the region's internally computed minimum width
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* minWidth(forHeight)
will return the region's internally
* computed minimum width.
*
* Setting this value to the USE_PREF_SIZE
flag will cause
* minWidth(forHeight)
to return the region's preferred width,
* enabling applications to easily restrict the resizability of the region.
*/
private DoubleProperty minWidth;
private double _minWidth = USE_COMPUTED_SIZE;
public final void setMinWidth(double value) {
if (minWidth == null) {
_minWidth = value;
requestParentLayout();
} else {
minWidth.set(value);
}
}
public final double getMinWidth() { return minWidth == null ? _minWidth : minWidth.get(); }
public final DoubleProperty minWidthProperty() {
if (minWidth == null) minWidth = new MinPrefMaxProperty("minWidth", _minWidth, StyleableProperties.MIN_WIDTH);
return minWidth;
}
/**
* Property for overriding the region's computed minimum height.
* This should only be set if the region's internally computed minimum height
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* minHeight(forWidth)
will return the region's internally
* computed minimum height.
*
* Setting this value to the USE_PREF_SIZE
flag will cause
* minHeight(forWidth)
to return the region's preferred height,
* enabling applications to easily restrict the resizability of the region.
*
*/
private DoubleProperty minHeight;
private double _minHeight = USE_COMPUTED_SIZE;
public final void setMinHeight(double value) {
if (minHeight == null) {
_minHeight = value;
requestParentLayout();
} else {
minHeight.set(value);
}
}
public final double getMinHeight() { return minHeight == null ? _minHeight : minHeight.get(); }
public final DoubleProperty minHeightProperty() {
if (minHeight == null) minHeight = new MinPrefMaxProperty("minHeight", _minHeight, StyleableProperties.MIN_HEIGHT);
return minHeight;
}
/**
* Convenience method for overriding the region's computed minimum width and height.
* This should only be called if the region's internally computed minimum size
* doesn't meet the application's layout needs.
*
* @see #setMinWidth
* @see #setMinHeight
* @param minWidth the override value for minimum width
* @param minHeight the override value for minimum height
*/
public void setMinSize(double minWidth, double minHeight) {
setMinWidth(minWidth);
setMinHeight(minHeight);
}
/**
* Property for overriding the region's computed preferred width.
* This should only be set if the region's internally computed preferred width
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* getPrefWidth(forHeight)
will return the region's internally
* computed preferred width.
*/
private DoubleProperty prefWidth;
private double _prefWidth = USE_COMPUTED_SIZE;
public final void setPrefWidth(double value) {
if (prefWidth == null) {
_prefWidth = value;
requestParentLayout();
} else {
prefWidth.set(value);
}
}
public final double getPrefWidth() { return prefWidth == null ? _prefWidth : prefWidth.get(); }
public final DoubleProperty prefWidthProperty() {
if (prefWidth == null) prefWidth = new MinPrefMaxProperty("prefWidth", _prefWidth, StyleableProperties.PREF_WIDTH);
return prefWidth;
}
/**
* Property for overriding the region's computed preferred height.
* This should only be set if the region's internally computed preferred height
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* getPrefHeight(forWidth)
will return the region's internally
* computed preferred width.
*/
private DoubleProperty prefHeight;
private double _prefHeight = USE_COMPUTED_SIZE;
public final void setPrefHeight(double value) {
if (prefHeight == null) {
_prefHeight = value;
requestParentLayout();
} else {
prefHeight.set(value);
}
}
public final double getPrefHeight() { return prefHeight == null ? _prefHeight : prefHeight.get(); }
public final DoubleProperty prefHeightProperty() {
if (prefHeight == null) prefHeight = new MinPrefMaxProperty("prefHeight", _prefHeight, StyleableProperties.PREF_HEIGHT);
return prefHeight;
}
/**
* Convenience method for overriding the region's computed preferred width and height.
* This should only be called if the region's internally computed preferred size
* doesn't meet the application's layout needs.
*
* @see #setPrefWidth
* @see #setPrefHeight
* @param prefWidth the override value for preferred width
* @param prefHeight the override value for preferred height
*/
public void setPrefSize(double prefWidth, double prefHeight) {
setPrefWidth(prefWidth);
setPrefHeight(prefHeight);
}
/**
* Property for overriding the region's computed maximum width.
* This should only be set if the region's internally computed maximum width
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* getMaxWidth(forHeight)
will return the region's internally
* computed maximum width.
*
* Setting this value to the USE_PREF_SIZE
flag will cause
* getMaxWidth(forHeight)
to return the region's preferred width,
* enabling applications to easily restrict the resizability of the region.
*/
private DoubleProperty maxWidth;
private double _maxWidth = USE_COMPUTED_SIZE;
public final void setMaxWidth(double value) {
if (maxWidth == null) {
_maxWidth = value;
requestParentLayout();
} else {
maxWidth.set(value);
}
}
public final double getMaxWidth() { return maxWidth == null ? _maxWidth : maxWidth.get(); }
public final DoubleProperty maxWidthProperty() {
if (maxWidth == null) maxWidth = new MinPrefMaxProperty("maxWidth", _maxWidth, StyleableProperties.MAX_WIDTH);
return maxWidth;
}
/**
* Property for overriding the region's computed maximum height.
* This should only be set if the region's internally computed maximum height
* doesn't meet the application's layout needs.
*
* Defaults to the USE_COMPUTED_SIZE
flag, which means that
* getMaxHeight(forWidth)
will return the region's internally
* computed maximum height.
*
* Setting this value to the
* This function does not resize the node and uses the node's layout bounds
* width and height to determine how it should be positioned within the area.
*
* If the vertical alignment is {@code VPos.BASELINE} then it
* will position the node so that its own baseline aligns with the passed in
* {@code baselineOffset}, otherwise the baseline parameter is ignored.
*
* If {@code snapToPixel} is {@code true} for this region, then the x/y position
* values will be rounded to their nearest pixel boundaries.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
*/
protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
double areaBaselineOffset, HPos halignment, VPos valignment) {
positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
Insets.EMPTY, halignment, valignment, isSnapToPixel());
}
/**
* Utility method which positions the child within an area of this
* region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* This function does not resize the node and uses the node's layout bounds
* width and height to determine how it should be positioned within the area.
*
* If the vertical alignment is {@code VPos.BASELINE} then it
* will position the node so that its own baseline aligns with the passed in
* {@code baselineOffset}, otherwise the baseline parameter is ignored.
*
* If {@code snapToPixel} is {@code true} for this region, then the x/y position
* values will be rounded to their nearest pixel boundaries.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
* @since JavaFX 8.0
*/
public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
Insets childMargin = margin != null? margin : Insets.EMPTY;
position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
snapSpace(childMargin.getTop(), isSnapToPixel),
snapSpace(childMargin.getRight(), isSnapToPixel),
snapSpace(childMargin.getBottom(), isSnapToPixel),
snapSpace(childMargin.getLeft(), isSnapToPixel),
halignment, valignment, isSnapToPixel);
}
/**
* Utility method which lays out the child within an area of this
* region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will resize it to fill the specified
* area unless the node's maximum size prevents it. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first to the area's width (up to the child's max width limit) and then pass
* that value to compute the child's height. If child's contentBias is vertical,
* then it will set its height to the area height (up to child's max height limit)
* and pass that height to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
HPos halignment, VPos valignment) {
layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
Insets.EMPTY, halignment, valignment);
}
/**
* Utility method which lays out the child within an area of this
* region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will resize it to fill the specified
* area unless the node's maximum size prevents it. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first to the area's width (up to the child's max width limit) and then pass
* that value to compute the child's height. If child's contentBias is vertical,
* then it will set its height to the area height (up to child's max height limit)
* and pass that height to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
Insets margin,
HPos halignment, VPos valignment) {
layoutInArea(child, areaX, areaY, areaWidth, areaHeight,
areaBaselineOffset, margin, true, true, halignment, valignment);
}
/**
* Utility method which lays out the child within an area of this
* region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
* to determine whether to resize it to fill the area or keep the child at its
* preferred dimension. If fillWidth/fillHeight are true, then this method
* will only resize the child up to its max size limits. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first and then pass that value to compute the child's height. If child's
* contentBias is vertical, then it will set its height first
* and pass that value to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
* @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
Insets margin, boolean fillWidth, boolean fillHeight,
HPos halignment, VPos valignment) {
layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, isSnapToPixel());
}
/**
* Utility method which lays out the child within an area of it's
* parent defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
* to determine whether to resize it to fill the area or keep the child at its
* preferred dimension. If fillWidth/fillHeight are true, then this method
* will only resize the child up to its max size limits. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first and then pass that value to compute the child's height. If child's
* contentBias is vertical, then it will set its height first
* and pass that value to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this region
* @param areaX the horizontal offset of the layout area relative to this region
* @param areaY the vertical offset of the layout area relative to this region
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
* @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
* @param isSnapToPixel whether to snap size and position to pixels
* @since JavaFX 8.0
*/
public static void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
Insets margin, boolean fillWidth, boolean fillHeight,
HPos halignment, VPos valignment, boolean isSnapToPixel) {
Insets childMargin = margin != null ? margin : Insets.EMPTY;
double top = snapSpace(childMargin.getTop(), isSnapToPixel);
double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel);
double left = snapSpace(childMargin.getLeft(), isSnapToPixel);
double right = snapSpace(childMargin.getRight(), isSnapToPixel);
if (valignment == VPos.BASELINE) {
double bo = child.getBaselineOffset();
if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
if (child.isResizable()) {
// Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
// be resized to this area
bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel);
} else {
top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel);
}
} else {
top = snapSpace(areaBaselineOffset - bo, isSnapToPixel);
}
}
if (child.isResizable()) {
Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
fillWidth, fillHeight, TEMP_VEC2D);
child.resize(snapSize(size.x, isSnapToPixel),snapSize(size.y, isSnapToPixel));
}
position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
top, right, bottom, left, halignment, valignment, isSnapToPixel);
}
private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
double areaBaselineOffset,
double topMargin, double rightMargin, double bottomMargin, double leftMargin,
HPos hpos, VPos vpos, boolean isSnapToPixel) {
final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
child.getLayoutBounds().getWidth(), hpos);
final double yoffset;
if (vpos == VPos.BASELINE) {
double bo = child.getBaselineOffset();
if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
// We already know the layout bounds at this stage, so we can use them
yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
} else {
yoffset = areaBaselineOffset - bo;
}
} else {
yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
child.getLayoutBounds().getHeight(), vpos);
}
final double x = snapPosition(areaX + xoffset, isSnapToPixel);
final double y = snapPosition(areaY + yoffset, isSnapToPixel);
child.relocate(x,y);
}
/**************************************************************************
* *
* PG Implementation *
* *
**************************************************************************/
/** @treatAsPrivate */
@Override public void impl_updatePeer() {
// TODO I think we have a bug, where if you create a Region with an Image that hasn't
// been loaded, we have no listeners on that image so as to cause a pulse & repaint
// to happen once the image is loaded. We just assume the image has been loaded
// (since when the image is created using new Image(url) or CSS it happens eagerly).
super.impl_updatePeer();
if (_shape != null) _shape.impl_syncPeer();
NGRegion pg = impl_getPeer();
if (!cornersValid) {
validateCorners();
}
final boolean sizeChanged = impl_isDirty(DirtyBits.NODE_GEOMETRY);
if (sizeChanged) {
pg.setSize((float)getWidth(), (float)getHeight());
}
// NOTE: The order here is very important. There is logic in NGRegion which determines
// whether we can cache an image representing this region, and for this to work correctly,
// the shape must be specified before the background which is before the border.
final boolean shapeChanged = impl_isDirty(DirtyBits.REGION_SHAPE);
if (shapeChanged) {
pg.updateShape(_shape, isScaleShape(), isCenterShape(), isCacheShape());
}
// The normalized corners can always be updated since they require no
// processing at the NG layer.
pg.updateFillCorners(normalizedFillCorners);
final boolean backgroundChanged = impl_isDirty(DirtyBits.SHAPE_FILL);
final Background bg = getBackground();
if (backgroundChanged) {
pg.updateBackground(bg);
}
// This will be true if an image that makes up the background or border of this
// region has changed, such that we need to redraw the region.
if (impl_isDirty(DirtyBits.NODE_CONTENTS)) {
pg.imagesUpdated();
}
// The normalized corners can always be updated since they require no
// processing at the NG layer.
pg.updateStrokeCorners(normalizedStrokeCorners);
if (impl_isDirty(DirtyBits.SHAPE_STROKE)) {
pg.updateBorder(getBorder());
}
// TODO given the note above, this *must* be called when an image which makes up the
// background images and border images changes (is loaded) if it was being loaded asynchronously
// Also note, one day we can add support for automatic opaque insets determination for border images.
// However right now it is impractical because the image pixel format is almost undoubtedly going
// to have alpha, and so without inspecting the source image's actual pixels for the filled center
// we can't automatically determine whether the interior is filled.
if (sizeChanged || backgroundChanged || shapeChanged) {
// These are the opaque insets, as specified by the developer in code or CSS. If null,
// then we must compute the opaque insets. If not null, then we will still compute the
// opaque insets and combine them with these insets, as appropriate. We do ignore these
// developer specified insets in cases where we know without a doubt that the developer
// gave us bad data.
final Insets i = getOpaqueInsets();
// If the background is determined by a shape, then we don't attempt to calculate the
// opaque insets. If the developer specified opaque insets, we will use them, otherwise
// we will make sure the opaque insets are cleared
if (_shape != null) {
if (i != null) {
pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(),
(float) i.getBottom(), (float) i.getLeft());
} else {
pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
}
} else {
// This is a rectangle (not shape) region. The opaque insets must be calculated,
// even if the developer has supplied their own opaque insets. The first (and cheapest)
// check is whether the region has any backgrounds at all. If not, then
// we will ignore the developer supplied insets because they are clearly wrong.
if (bg == null || bg.isEmpty()) {
pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
} else {
// There is a background, so it is conceivable that there are
// opaque insets. From this point on, we have to honor the developer's supplied
// insets, only expanding them if we know for certain the opaque insets are
// bigger than what was supplied by the developer. Start by defining our initial
// values for top, right, bottom, and left. If the developer supplied us
// insets, use those. Otherwise initialize to NaN. Note that the developer may
// also have given us NaN values (so we'd have to check for these anyway). We use
// NaN to mean "not defined".
final double[] trbl = new double[4];
bg.computeOpaqueInsets(getWidth(), getHeight(), trbl);
if (i != null) {
trbl[0] = Double.isNaN(trbl[0]) ? i.getTop() : Double.isNaN(i.getTop()) ? trbl[0] : Math.min(trbl[0], i.getTop());
trbl[1] = Double.isNaN(trbl[1]) ? i.getRight() : Double.isNaN(i.getRight()) ? trbl[1] : Math.min(trbl[1], i.getRight());
trbl[2] = Double.isNaN(trbl[2]) ? i.getBottom() : Double.isNaN(i.getBottom()) ? trbl[2] : Math.min(trbl[2], i.getBottom());
trbl[3] = Double.isNaN(trbl[3]) ? i.getLeft() : Double.isNaN(i.getLeft()) ? trbl[3] : Math.min(trbl[3], i.getLeft());
}
// Now set the insets onto the peer. Passing NaN here is perfectly
// acceptable (even encouraged, to mean "unknown" or "disabled").
pg.setOpaqueInsets((float) trbl[0], (float) trbl[1], (float) trbl[2], (float) trbl[3]);
}
}
}
}
/** @treatAsPrivate */
@Override public NGNode impl_createPeer() {
return new NGRegion();
}
/**
* Transform x, y in local Region coordinates to local coordinates of scaled/centered shape and
* check if the shape contains the coordinates.
* The transformations here are basically an inversion of transformations being done in NGShape#resizeShape.
*/
private boolean shapeContains(com.sun.javafx.geom.Shape shape,
final double x, final double y,
double topOffset, double rightOffset, double bottomOffset, double leftOffset) {
double resX = x;
double resY = y;
// The bounds of the shape, before any centering / scaling takes place
final RectBounds bounds = shape.getBounds();
if (isScaleShape()) {
// Modify the transform to scale the shape so that it will fit
// within the insets.
resX -= leftOffset;
resY -= topOffset;
//denominator represents the width and height of the box within which the new shape must fit.
resX *= bounds.getWidth() / (getWidth() - leftOffset - rightOffset);
resY *= bounds.getHeight() / (getHeight() - topOffset - bottomOffset);
// If we also need to center it, we need to adjust the transform so as to place
// the shape in the center of the bounds
if (isCenterShape()) {
resX += bounds.getMinX();
resY += bounds.getMinY();
}
} else if (isCenterShape()) {
// We are only centering. In this case, what we want is for the
// original shape to be centered. If there are offsets (insets)
// then we must pre-scale about the center to account for it.
double boundsWidth = bounds.getWidth();
double boundsHeight = bounds.getHeight();
double scaleFactorX = boundsWidth / (boundsWidth - leftOffset - rightOffset);
double scaleFactorY = boundsHeight / (boundsHeight - topOffset - bottomOffset);
//This is equivalent to:
// translate(bounds.getMinX(), bounds.getMinY())
// scale(scaleFactorX, scaleFactorY)
// translate(-bounds.getMinX(), -bounds.getMinY())
// translate(-leftOffset - (getWidth() - boundsWidth)/2 + bounds.getMinX(),
// -topOffset - (getHeight() - boundsHeight)/2 + bounds.getMinY());
// which is an inversion of an transformation done to the shape
// This gives us
//
//resX = resX * scaleFactorX - scaleFactorX * bounds.getMinX() - scaleFactorX * (leftOffset + (getWidth() - boundsWidth) / 2 - bounds.getMinX()) + bounds.getMinX();
//resY = resY * scaleFactorY - scaleFactorY * bounds.getMinY() - scaleFactorY * (topOffset + (getHeight() - boundsHeight) / 2 - bounds.getMinY()) + bounds.getMinY();
//
// which can further reduced to
resX = scaleFactorX * (resX -(leftOffset + (getWidth() - boundsWidth) / 2)) + bounds.getMinX();
resY = scaleFactorY * (resY -(topOffset + (getHeight() - boundsHeight) / 2)) + bounds.getMinY();
} else if (topOffset != 0 || rightOffset != 0 || bottomOffset != 0 || leftOffset != 0) {
// We are neither centering nor scaling, but we still have to resize the
// shape because we have to fit within the bounds defined by the offsets
double scaleFactorX = bounds.getWidth() / (bounds.getWidth() - leftOffset - rightOffset);
double scaleFactorY = bounds.getHeight() / (bounds.getHeight() - topOffset - bottomOffset);
// This is equivalent to:
// translate(bounds.getMinX(), bounds.getMinY())
// scale(scaleFactorX, scaleFactorY)
// translate(-bounds.getMinX(), -bounds.getMinY())
// translate(-leftOffset, -topOffset)
//
// which is an inversion of an transformation done to the shape
// This gives us
//
//resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX();
//resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY();
//
// which can be further reduceD to
resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX();
resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY();
}
return shape.contains((float)resX, (float)resY);
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
@Override protected boolean impl_computeContains(double localX, double localY) {
// NOTE: This method only gets called if a quick check of bounds has already
// occurred, so there is no need to test against bound again. We know that the
// point (localX, localY) falls within the bounds of this node, now we need
// to determine if it falls within the geometry of this node.
// Also note that because Region defaults pickOnBounds to true, this code is
// not usually executed. It will only be executed if pickOnBounds is set to false.
final double x2 = getWidth();
final double y2 = getHeight();
final Background background = getBackground();
// First check the shape. Shape could be impacted by scaleShape & positionShape properties.
if (_shape != null) {
if (background != null && !background.getFills().isEmpty()) {
final List
* The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL
* does not have a [scheme:] component, the URL is considered to be the [path] component only.
* Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to
* the root of the application's classpath.
* USE_PREF_SIZE
flag will cause
* getMaxHeight(forWidth)
to return the region's preferred height,
* enabling applications to easily restrict the resizability of the region.
*/
private DoubleProperty maxHeight;
private double _maxHeight = USE_COMPUTED_SIZE;
public final void setMaxHeight(double value) {
if (maxHeight == null) {
_maxHeight = value;
requestParentLayout();
} else {
maxHeight.set(value);
}
}
public final double getMaxHeight() { return maxHeight == null ? _maxHeight : maxHeight.get(); }
public final DoubleProperty maxHeightProperty() {
if (maxHeight == null) maxHeight = new MinPrefMaxProperty("maxHeight", _maxHeight, StyleableProperties.MAX_HEIGHT);
return maxHeight;
}
/**
* Convenience method for overriding the region's computed maximum width and height.
* This should only be called if the region's internally computed maximum size
* doesn't meet the application's layout needs.
*
* @see #setMaxWidth
* @see #setMaxHeight
* @param maxWidth the override value for maximum width
* @param maxHeight the override value for maximum height
*/
public void setMaxSize(double maxWidth, double maxHeight) {
setMaxWidth(maxWidth);
setMaxHeight(maxHeight);
}
/**
* When specified, the {@code shape} will cause the region to be
* rendered as the specified shape rather than as a rounded rectangle.
* When null, the Region is rendered as a rounded rectangle. When rendered
* as a Shape, any Background is used to fill the shape, although any
* background insets are ignored as are background radii. Any BorderStrokes
* defined are used for stroking the shape. Any BorderImages are ignored.
*
* @default null
* @css shape SVG shape string
* @since JavaFX 8.0
*/
private ObjectPropertytrue
since all Regions are resizable.
* @return whether this node can be resized by its parent during layout
*/
@Override public boolean isResizable() {
return true;
}
/**
* Invoked by the region's parent during layout to set the region's
* width and height. Applications should not invoke this method directly.
* If an application needs to directly set the size of the region, it should
* override its size constraints by calling setMinSize()
,
* setPrefSize()
, or setMaxSize()
and it's parent
* will honor those overrides during layout.
*
* @param width the target layout bounds width
* @param height the target layout bounds height
*/
@Override public void resize(double width, double height) {
setWidth(width);
setHeight(height);
PlatformLogger logger = Logging.getLayoutLogger();
if (logger.isLoggable(Level.FINER)) {
logger.finer(this.toString() + " resized to " + width + " x " + height);
}
}
/**
* Called during layout to determine the minimum width for this node.
* Returns the value from computeMinWidth(forHeight)
unless
* the application overrode the minimum width by setting the minWidth property.
*
* @see #setMinWidth(double)
* @return the minimum width that this node should be resized to during layout
*/
@Override public final double minWidth(double height) {
final double override = getMinWidth();
if (override == USE_COMPUTED_SIZE) {
return super.minWidth(height);
} else if (override == USE_PREF_SIZE) {
return prefWidth(height);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Called during layout to determine the minimum height for this node.
* Returns the value from computeMinHeight(forWidth)
unless
* the application overrode the minimum height by setting the minHeight property.
*
* @see #setMinHeight
* @return the minimum height that this node should be resized to during layout
*/
@Override public final double minHeight(double width) {
final double override = getMinHeight();
if (override == USE_COMPUTED_SIZE) {
return super.minHeight(width);
} else if (override == USE_PREF_SIZE) {
return prefHeight(width);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Called during layout to determine the preferred width for this node.
* Returns the value from computePrefWidth(forHeight)
unless
* the application overrode the preferred width by setting the prefWidth property.
*
* @see #setPrefWidth
* @return the preferred width that this node should be resized to during layout
*/
@Override public final double prefWidth(double height) {
final double override = getPrefWidth();
if (override == USE_COMPUTED_SIZE) {
return super.prefWidth(height);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Called during layout to determine the preferred height for this node.
* Returns the value from computePrefHeight(forWidth)
unless
* the application overrode the preferred height by setting the prefHeight property.
*
* @see #setPrefHeight
* @return the preferred height that this node should be resized to during layout
*/
@Override public final double prefHeight(double width) {
final double override = getPrefHeight();
if (override == USE_COMPUTED_SIZE) {
return super.prefHeight(width);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Called during layout to determine the maximum width for this node.
* Returns the value from computeMaxWidth(forHeight)
unless
* the application overrode the maximum width by setting the maxWidth property.
*
* @see #setMaxWidth
* @return the maximum width that this node should be resized to during layout
*/
@Override public final double maxWidth(double height) {
final double override = getMaxWidth();
if (override == USE_COMPUTED_SIZE) {
return computeMaxWidth(height);
} else if (override == USE_PREF_SIZE) {
return prefWidth(height);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Called during layout to determine the maximum height for this node.
* Returns the value from computeMaxHeight(forWidth)
unless
* the application overrode the maximum height by setting the maxHeight property.
*
* @see #setMaxHeight
* @return the maximum height that this node should be resized to during layout
*/
@Override public final double maxHeight(double width) {
final double override = getMaxHeight();
if (override == USE_COMPUTED_SIZE) {
return computeMaxHeight(width);
} else if (override == USE_PREF_SIZE) {
return prefHeight(width);
}
return Double.isNaN(override) || override < 0 ? 0 : override;
}
/**
* Computes the minimum width of this region.
* Returns the sum of the left and right insets by default.
* region subclasses should override this method to return an appropriate
* value based on their content and layout strategy. If the subclass
* doesn't have a VERTICAL content bias, then the height parameter can be
* ignored.
*
* @return the computed minimum width of this region
*/
@Override protected double computeMinWidth(double height) {
return getInsets().getLeft() + getInsets().getRight();
}
/**
* Computes the minimum height of this region.
* Returns the sum of the top and bottom insets by default.
* Region subclasses should override this method to return an appropriate
* value based on their content and layout strategy. If the subclass
* doesn't have a HORIZONTAL content bias, then the width parameter can be
* ignored.
*
* @return the computed minimum height for this region
*/
@Override protected double computeMinHeight(double width) {
return getInsets().getTop() + getInsets().getBottom();
}
/**
* Computes the preferred width of this region for the given height.
* Region subclasses should override this method to return an appropriate
* value based on their content and layout strategy. If the subclass
* doesn't have a VERTICAL content bias, then the height parameter can be
* ignored.
*
* @return the computed preferred width for this region
*/
@Override protected double computePrefWidth(double height) {
final double w = super.computePrefWidth(height);
return getInsets().getLeft() + w + getInsets().getRight();
}
/**
* Computes the preferred height of this region for the given width;
* Region subclasses should override this method to return an appropriate
* value based on their content and layout strategy. If the subclass
* doesn't have a HORIZONTAL content bias, then the width parameter can be
* ignored.
*
* @return the computed preferred height for this region
*/
@Override protected double computePrefHeight(double width) {
final double h = super.computePrefHeight(width);
return getInsets().getTop() + h + getInsets().getBottom();
}
/**
* Computes the maximum width for this region.
* Returns Double.MAX_VALUE by default.
* Region subclasses may override this method to return an different
* value based on their content and layout strategy. If the subclass
* doesn't have a VERTICAL content bias, then the height parameter can be
* ignored.
*
* @return the computed maximum width for this region
*/
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
/**
* Computes the maximum height of this region.
* Returns Double.MAX_VALUE by default.
* Region subclasses may override this method to return a different
* value based on their content and layout strategy. If the subclass
* doesn't have a HORIZONTAL content bias, then the width parameter can be
* ignored.
*
* @return the computed maximum height for this region
*/
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
/**
* If this region's snapToPixel property is true, returns a value rounded
* to the nearest pixel, else returns the same value.
* @param value the space value to be snapped
* @return value rounded to nearest pixel
*/
protected double snapSpace(double value) {
return snapSpace(value, isSnapToPixel());
}
/**
* If this region's snapToPixel property is true, returns a value ceiled
* to the nearest pixel, else returns the same value.
* @param value the size value to be snapped
* @return value ceiled to nearest pixel
*/
protected double snapSize(double value) {
return snapSize(value, isSnapToPixel());
}
/**
* If this region's snapToPixel property is true, returns a value rounded
* to the nearest pixel, else returns the same value.
* @param value the position value to be snapped
* @return value rounded to nearest pixel
*/
protected double snapPosition(double value) {
return snapPosition(value, isSnapToPixel());
}
double snapPortion(double value) {
return snapPortion(value, isSnapToPixel());
}
/**
* Utility method to get the top inset which includes padding and border
* inset. Then snapped to whole pixels if isSnapToPixel() is true.
*
* @since JavaFX 8.0
* @return Rounded up insets top
*/
public final double snappedTopInset() {
return snappedTopInset;
}
/**
* Utility method to get the bottom inset which includes padding and border
* inset. Then snapped to whole pixels if isSnapToPixel() is true.
*
* @since JavaFX 8.0
* @return Rounded up insets bottom
*/
public final double snappedBottomInset() {
return snappedBottomInset;
}
/**
* Utility method to get the left inset which includes padding and border
* inset. Then snapped to whole pixels if isSnapToPixel() is true.
*
* @since JavaFX 8.0
* @return Rounded up insets left
*/
public final double snappedLeftInset() {
return snappedLeftInset;
}
/**
* Utility method to get the right inset which includes padding and border
* inset. Then snapped to whole pixels if isSnapToPixel() is true.
*
* @since JavaFX 8.0
* @return Rounded up insets right
*/
public final double snappedRightInset() {
return snappedRightInset;
}
double computeChildMinAreaWidth(Node child, Insets margin) {
return computeChildMinAreaWidth(child, -1, margin, -1, false);
}
double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
final boolean snap = isSnapToPixel();
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
double alt = -1;
if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
double bo = child.getBaselineOffset();
final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
height - top - bottom - baselineComplement :
height - top - bottom;
if (fillHeight) {
alt = snapSize(boundedSize(
child.minHeight(-1), contentHeight,
child.maxHeight(-1)));
} else {
alt = snapSize(boundedSize(
child.minHeight(-1),
child.prefHeight(-1),
Math.min(child.maxHeight(-1), contentHeight)));
}
}
return left + snapSize(child.minWidth(alt)) + right;
}
double computeChildMinAreaHeight(Node child, Insets margin) {
return computeChildMinAreaHeight(child, -1, margin, -1);
}
double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
final boolean snap = isSnapToPixel();
double top =margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
double alt = -1;
if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
child.maxWidth(-1));
}
// For explanation, see computeChildPrefAreaHeight
if (minBaselineComplement != -1) {
double baseline = child.getBaselineOffset();
if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
return top + snapSize(child.minHeight(alt)) + bottom
+ minBaselineComplement;
} else {
return baseline + minBaselineComplement;
}
} else {
return top + snapSize(child.minHeight(alt)) + bottom;
}
}
double computeChildPrefAreaWidth(Node child, Insets margin) {
return computeChildPrefAreaWidth(child, -1, margin, -1, false);
}
double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
final boolean snap = isSnapToPixel();
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
double alt = -1;
if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
double bo = child.getBaselineOffset();
final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
height - top - bottom - baselineComplement :
height - top - bottom;
if (fillHeight) {
alt = snapSize(boundedSize(
child.minHeight(-1), contentHeight,
child.maxHeight(-1)));
} else {
alt = snapSize(boundedSize(
child.minHeight(-1),
child.prefHeight(-1),
Math.min(child.maxHeight(-1), contentHeight)));
}
}
return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
}
double computeChildPrefAreaHeight(Node child, Insets margin) {
return computeChildPrefAreaHeight(child, -1, margin, -1);
}
double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
final boolean snap = isSnapToPixel();
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
double alt = -1;
if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
double left = margin != null ? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null ? snapSpace(margin.getRight(), snap) : 0;
alt = snapSize(boundedSize(
child.minWidth(-1), width != -1 ? width - left - right
: child.prefWidth(-1), child.maxWidth(-1)));
}
if (prefBaselineComplement != -1) {
double baseline = child.getBaselineOffset();
if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
// When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
// the preferred complement to it
return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
+ prefBaselineComplement;
} else {
// For all other Nodes, it's just their baseline and the complement.
// Note that the complement already contain the Node's preferred (or fixed) height
return top + baseline + prefBaselineComplement + bottom;
}
} else {
return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
}
}
double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
double max = child.maxWidth(-1);
if (max == Double.MAX_VALUE) {
return max;
}
final boolean snap = isSnapToPixel();
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
double alt = -1;
if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
double bo = child.getBaselineOffset();
final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
height - top - bottom - baselineComplement :
height - top - bottom;
if (fillHeight) {
alt = snapSize(boundedSize(
child.minHeight(-1), contentHeight,
child.maxHeight(-1)));
} else {
alt = snapSize(boundedSize(
child.minHeight(-1),
child.prefHeight(-1),
Math.min(child.maxHeight(-1), contentHeight)));
}
max = child.maxWidth(alt);
}
// if min > max, min wins, so still need to call boundedSize()
return left + snapSize(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
}
double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
double max = child.maxHeight(-1);
if (max == Double.MAX_VALUE) {
return max;
}
final boolean snap = isSnapToPixel();
double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
double alt = -1;
if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
child.minWidth(-1));
max = child.maxHeight(alt);
}
// For explanation, see computeChildPrefAreaHeight
if (maxBaselineComplement != -1) {
double baseline = child.getBaselineOffset();
if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
return top + snapSize(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
+ maxBaselineComplement;
} else {
return top + baseline + maxBaselineComplement + bottom;
}
} else {
// if min > max, min wins, so still need to call boundedSize()
return top + snapSize(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
}
}
/* Max of children's minimum area widths */
double computeMaxMinAreaWidth(List
* For additional information about using CSS with the scene graph,
* see the CSS Reference Guide.
*
* @return A string URL
* @since JavaFX 8u40
*/
public String getUserAgentStylesheet() {
return null;
}
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
private static class StyleableProperties {
private static final CssMetaData
*
* package com.example.javafx.app;
*
* import javafx.application.Application;
* import javafx.scene.Group;
* import javafx.scene.Scene;
* import javafx.stage.Stage;
*
* public class MyApp extends Application {
*
* {@literal @}Override public void start(Stage stage) {
* Scene scene = new Scene(new Group());
* scene.getStylesheets().add("/com/example/javafx/app/mystyles.css");
* stage.setScene(scene);
* stage.show();
* }
*
* public static void main(String[] args) {
* launch(args);
* }
* }
*