modules/graphics/src/main/java/javafx/scene/layout/Region.java

Print this page




  60 import java.util.Arrays;
  61 import java.util.List;
  62 import java.util.function.Function;
  63 import com.sun.javafx.util.Logging;
  64 import com.sun.javafx.util.TempState;
  65 import com.sun.javafx.binding.ExpressionHelper;
  66 import javafx.css.converter.BooleanConverter;
  67 import javafx.css.converter.InsetsConverter;
  68 import javafx.css.converter.ShapeConverter;
  69 import javafx.css.converter.SizeConverter;
  70 import com.sun.javafx.geom.BaseBounds;
  71 import com.sun.javafx.geom.PickRay;
  72 import com.sun.javafx.geom.RectBounds;
  73 import com.sun.javafx.geom.Vec2d;
  74 import com.sun.javafx.geom.transform.BaseTransform;
  75 import com.sun.javafx.scene.DirtyBits;
  76 import com.sun.javafx.scene.input.PickResultChooser;
  77 import com.sun.javafx.sg.prism.NGNode;
  78 import com.sun.javafx.sg.prism.NGRegion;
  79 import com.sun.javafx.tk.Toolkit;


  80 import sun.util.logging.PlatformLogger;
  81 import sun.util.logging.PlatformLogger.Level;
  82 
  83 /**
  84  * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers.
  85  * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds
  86  * and borders. It is designed to support as much of the CSS3 specification for backgrounds
  87  * and borders as is relevant to JavaFX.
  88  * The full specification is available at <a href="http://www.w3.org/TR/2012/CR-css3-background-20120724/">the W3C</a>.
  89  * <p/>
  90  * Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside
  91  * these bounds. The content area of a Region is the area which is occupied for the layout of its children.
  92  * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the
  93  * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can
  94  * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region,
  95  * but does not affect the layout bounds.
  96  * <p/>
  97  * A Region has a Background, and a Border, although either or both of these might be empty. The Background
  98  * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the
  99  * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and


 191      * pref is greater than the max, then the max is returned. If the pref lies
 192      * between the min and the max, then the pref is returned.
 193      *
 194      *
 195      * @param min The minimum bound
 196      * @param pref The value to be clamped between the min and max
 197      * @param max the maximum bound
 198      * @return the size bounded by min, pref, and max.
 199      */
 200     static double boundedSize(double min, double pref, double max) {
 201         double a = pref >= min ? pref : min;
 202         double b = min >= max ? min : max;
 203         return a <= b ? a : b;
 204     }
 205 
 206     double adjustWidthByMargin(double width, Insets margin) {
 207         if (margin == null || margin == Insets.EMPTY) {
 208             return width;
 209         }
 210         boolean isSnapToPixel = isSnapToPixel();
 211         return width - snapSpace(margin.getLeft(), isSnapToPixel) - snapSpace(margin.getRight(), isSnapToPixel);
 212     }
 213 
 214     double adjustHeightByMargin(double height, Insets margin) {
 215         if (margin == null || margin == Insets.EMPTY) {
 216             return height;
 217         }
 218         boolean isSnapToPixel = isSnapToPixel();
 219         return height - snapSpace(margin.getTop(), isSnapToPixel) - snapSpace(margin.getBottom(), isSnapToPixel);

























































 220     }
 221 
 222     /**
 223      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 224      * the value is simply returned. This method will surely be JIT'd under normal
 225      * circumstances, however on an interpreter it would be better to inline this
 226      * method. However the use of Math.round here, and Math.ceil in snapSize is
 227      * not obvious, and so for code maintenance this logic is pulled out into
 228      * a separate method.
 229      *
 230      * @param value The value that needs to be snapped
 231      * @param snapToPixel Whether to snap to pixel
 232      * @return value either as passed in or rounded based on snapToPixel
 233      */
 234     private static double snapSpace(double value, boolean snapToPixel) {
 235         return snapToPixel ? Math.round(value) : value;







 236     }
 237 
 238     /**
 239      * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise,
 240      * the value is simply returned.
 241      *
 242      * @param value The value that needs to be snapped
 243      * @param snapToPixel Whether to snap to pixel
 244      * @return value either as passed in or ceil'd based on snapToPixel
 245      */
 246     private static double snapSize(double value, boolean snapToPixel) {
 247         return snapToPixel ? Math.ceil(value) : value;







 248     }
 249 
 250     /**
 251      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 252      * the value is simply returned.
 253      *
 254      * @param value The value that needs to be snapped
 255      * @param snapToPixel Whether to snap to pixel
 256      * @return value either as passed in or rounded based on snapToPixel
 257      */
 258     private static double snapPosition(double value, boolean snapToPixel) {
 259         return snapToPixel ? Math.round(value) : value;







 260     }
 261 
 262     private static double snapPortion(double value, boolean snapToPixel) {
 263         if (snapToPixel) {
 264             return value == 0 ? 0 :(value > 0 ? Math.max(1, Math.floor(value)) : Math.min(-1, Math.ceil(value)));
















 265         }
 266         return value;
 267     }
 268 
 269     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 270                                         Function<Integer, Double> positionToWidth,
 271                                         double areaHeight, boolean fillHeight) {
 272         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, isSnapToPixel());
 273     }
 274 
 275     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 276             Function<Integer, Double> positionToWidth,
 277             double areaHeight, boolean fillHeight, boolean snapToPixel) {
 278         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight,
 279                 getMinBaselineComplement(children), snapToPixel);
 280     }
 281 
 282     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 283                                  Function<Integer, Double> positionToWidth,
 284                                  double areaHeight, final boolean fillHeight, double minComplement) {
 285         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 286     }


 294     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 295                                  Function<Integer, Double> positionToWidth,
 296                                  double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement) {
 297         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 298     }
 299 
 300     /**
 301      * Returns the baseline offset of provided children, with respect to the minimum complement, computed
 302      * by {@link #getMinBaselineComplement(java.util.List)} from the same set of children.
 303      * @param children the children with baseline alignment
 304      * @param margins their margins (callback)
 305      * @param positionToWidth callback for children widths (can return -1 if no bias is used)
 306      * @param areaHeight height of the area to layout in
 307      * @param fillHeight callback to specify children that has fillHeight constraint
 308      * @param minComplement minimum complement
 309      */
 310     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 311             Function<Integer, Double> positionToWidth,
 312             double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement, boolean snapToPixel) {
 313         double b = 0;

 314         for (int i = 0;i < children.size(); ++i) {
 315             Node n = children.get(i);


 316             Insets margin = margins.call(n);
 317             double top = margin != null? snapSpace(margin.getTop(), snapToPixel) : 0;
 318             double bottom = (margin != null? snapSpace(margin.getBottom(), snapToPixel) : 0);
 319             final double bo = n.getBaselineOffset();
 320             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
 321                 double alt = -1;
 322                 if (n.getContentBias() == Orientation.HORIZONTAL) {
 323                     alt = positionToWidth.apply(i);
 324                 }
 325                 if (fillHeight.apply(i)) {
 326                     // If the children fills it's height, than it's "preferred" height is the area without the complement and insets
 327                     b = Math.max(b, top + boundedSize(n.minHeight(alt), areaHeight - minComplement - top - bottom,
 328                             n.maxHeight(alt)));
 329                 } else {
 330                     // Otherwise, we must use the area without complement and insets as a maximum for the Node
 331                     b = Math.max(b, top + boundedSize(n.minHeight(alt), n.prefHeight(alt),
 332                             Math.min(n.maxHeight(alt), areaHeight - minComplement - top - bottom)));
 333                 }
 334             } else {
 335                 b = Math.max(b, top + bo);
 336             }
 337         }
 338         return b;


1540      */
1541     protected double computeMaxWidth(double height) {
1542         return Double.MAX_VALUE;
1543     }
1544 
1545     /**
1546      * Computes the maximum height of this region.
1547      * Returns Double.MAX_VALUE by default.
1548      * Region subclasses may override this method to return a different
1549      * value based on their content and layout strategy.  If the subclass
1550      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1551      * ignored.
1552      *
1553      * @return the computed maximum height for this region
1554      */
1555     protected double computeMaxHeight(double width) {
1556         return Double.MAX_VALUE;
1557     }
1558 
1559     /**
1560      * If this region's snapToPixel property is true, returns a value rounded
1561      * to the nearest pixel, else returns the same value.



1562      * @param value the space value to be snapped
1563      * @return value rounded to nearest pixel

1564      */

1565     protected double snapSpace(double value) {
1566         return snapSpace(value, isSnapToPixel());
1567     }
1568 
1569     /**
1570      * If this region's snapToPixel property is true, returns a value ceiled
1571      * to the nearest pixel, else returns the same value.

























1572      * @param value the size value to be snapped
1573      * @return value ceiled to nearest pixel

1574      */

1575     protected double snapSize(double value) {
1576         return snapSize(value, isSnapToPixel());
1577     }
1578 
1579     /**
1580      * If this region's snapToPixel property is true, returns a value rounded
1581      * to the nearest pixel, else returns the same value.

























1582      * @param value the position value to be snapped
1583      * @return value rounded to nearest pixel

1584      */

1585     protected double snapPosition(double value) {
1586         return snapPosition(value, isSnapToPixel());






















1587     }
1588 
1589     double snapPortion(double value) {
1590         return snapPortion(value, isSnapToPixel());



1591     }
1592 
1593 
1594     /**
1595      * Utility method to get the top inset which includes padding and border
1596      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1597      *
1598      * @since JavaFX 8.0
1599      * @return Rounded up insets top
1600      */
1601     public final double snappedTopInset() {
1602         return snappedTopInset;
1603     }
1604 
1605     /**
1606      * Utility method to get the bottom inset which includes padding and border
1607      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1608      *
1609      * @since JavaFX 8.0
1610      * @return Rounded up insets bottom


1625     }
1626 
1627     /**
1628      * Utility method to get the right inset which includes padding and border
1629      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1630      *
1631      * @since JavaFX 8.0
1632      * @return Rounded up insets right
1633      */
1634     public final double snappedRightInset() {
1635         return snappedRightInset;
1636     }
1637 
1638 
1639     double computeChildMinAreaWidth(Node child, Insets margin) {
1640         return computeChildMinAreaWidth(child, -1, margin, -1, false);
1641     }
1642 
1643     double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1644         final boolean snap = isSnapToPixel();
1645         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1646         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1647         double alt = -1;
1648         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1649             double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1650             double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
1651             double bo = child.getBaselineOffset();
1652             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1653                     height - top - bottom - baselineComplement :
1654                      height - top - bottom;
1655             if (fillHeight) {
1656                 alt = snapSize(boundedSize(
1657                         child.minHeight(-1), contentHeight,
1658                         child.maxHeight(-1)));
1659             } else {
1660                 alt = snapSize(boundedSize(
1661                         child.minHeight(-1),
1662                         child.prefHeight(-1),
1663                         Math.min(child.maxHeight(-1), contentHeight)));
1664             }
1665         }
1666         return left + snapSize(child.minWidth(alt)) + right;
1667     }
1668 
1669     double computeChildMinAreaHeight(Node child, Insets margin) {
1670         return computeChildMinAreaHeight(child, -1, margin, -1);
1671     }
1672 
1673     double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
1674         final boolean snap = isSnapToPixel();
1675         double top =margin != null? snapSpace(margin.getTop(), snap) : 0;
1676         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1677 
1678         double alt = -1;
1679         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1680             double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1681             double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1682             alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1683                     child.maxWidth(-1));
1684         }
1685 
1686         // For explanation, see computeChildPrefAreaHeight
1687         if (minBaselineComplement != -1) {
1688             double baseline = child.getBaselineOffset();
1689             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1690                 return top + snapSize(child.minHeight(alt)) + bottom
1691                         + minBaselineComplement;
1692             } else {
1693                 return baseline + minBaselineComplement;
1694             }
1695         } else {
1696             return top + snapSize(child.minHeight(alt)) + bottom;
1697         }
1698     }
1699 
1700     double computeChildPrefAreaWidth(Node child, Insets margin) {
1701         return computeChildPrefAreaWidth(child, -1, margin, -1, false);
1702     }
1703 
1704     double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1705         final boolean snap = isSnapToPixel();
1706         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1707         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1708         double alt = -1;
1709         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1710             double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1711             double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1712             double bo = child.getBaselineOffset();
1713             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1714                     height - top - bottom - baselineComplement :
1715                      height - top - bottom;
1716             if (fillHeight) {
1717                 alt = snapSize(boundedSize(
1718                         child.minHeight(-1), contentHeight,
1719                         child.maxHeight(-1)));
1720             } else {
1721                 alt = snapSize(boundedSize(
1722                         child.minHeight(-1),
1723                         child.prefHeight(-1),
1724                         Math.min(child.maxHeight(-1), contentHeight)));
1725             }
1726         }
1727         return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
1728     }
1729 
1730     double computeChildPrefAreaHeight(Node child, Insets margin) {
1731         return computeChildPrefAreaHeight(child, -1, margin, -1);
1732     }
1733 
1734     double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
1735         final boolean snap = isSnapToPixel();
1736         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1737         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1738 
1739         double alt = -1;
1740         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1741             double left = margin != null ? snapSpace(margin.getLeft(), snap) : 0;
1742             double right = margin != null ? snapSpace(margin.getRight(), snap) : 0;
1743             alt = snapSize(boundedSize(
1744                     child.minWidth(-1), width != -1 ? width - left - right
1745                     : child.prefWidth(-1), child.maxWidth(-1)));
1746         }
1747 
1748         if (prefBaselineComplement != -1) {
1749             double baseline = child.getBaselineOffset();
1750             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1751                 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
1752                 // the preferred complement to it
1753                 return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
1754                         + prefBaselineComplement;
1755             } else {
1756                 // For all other Nodes, it's just their baseline and the complement.
1757                 // Note that the complement already contain the Node's preferred (or fixed) height
1758                 return top + baseline + prefBaselineComplement + bottom;
1759             }
1760         } else {
1761             return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
1762         }
1763     }
1764 
1765     double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1766         double max = child.maxWidth(-1);
1767         if (max == Double.MAX_VALUE) {
1768             return max;
1769         }
1770         final boolean snap = isSnapToPixel();
1771         double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1772         double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1773         double alt = -1;
1774         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1775             double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1776             double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
1777             double bo = child.getBaselineOffset();
1778             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1779                     height - top - bottom - baselineComplement :
1780                      height - top - bottom;
1781             if (fillHeight) {
1782                 alt = snapSize(boundedSize(
1783                         child.minHeight(-1), contentHeight,
1784                         child.maxHeight(-1)));
1785             } else {
1786                 alt = snapSize(boundedSize(
1787                         child.minHeight(-1),
1788                         child.prefHeight(-1),
1789                         Math.min(child.maxHeight(-1), contentHeight)));
1790             }
1791             max = child.maxWidth(alt);
1792         }
1793         // if min > max, min wins, so still need to call boundedSize()
1794         return left + snapSize(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
1795     }
1796 
1797     double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
1798         double max = child.maxHeight(-1);
1799         if (max == Double.MAX_VALUE) {
1800             return max;
1801         }
1802 
1803         final boolean snap = isSnapToPixel();
1804         double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1805         double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1806         double alt = -1;
1807         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1808             double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1809             double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1810             alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1811                 child.minWidth(-1));
1812             max = child.maxHeight(alt);
1813         }
1814         // For explanation, see computeChildPrefAreaHeight
1815         if (maxBaselineComplement != -1) {
1816             double baseline = child.getBaselineOffset();
1817             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1818                 return top + snapSize(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
1819                         + maxBaselineComplement;
1820             } else {
1821                 return top + baseline + maxBaselineComplement + bottom;
1822             }
1823         } else {
1824             // if min > max, min wins, so still need to call boundedSize()
1825             return top + snapSize(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
1826         }
1827     }
1828 
1829     /* Max of children's minimum area widths */
1830 
1831     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins) {
1832         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, true);
1833     }
1834 
1835     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins, double height, boolean fillHeight) {
1836         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, true);
1837     }
1838 
1839     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight) {
1840         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, true);
1841     }
1842 
1843     /* Max of children's minimum area heights */
1844 
1845     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {


1935             childWidth = boundedSize(
1936                     node.minWidth(childHeight), fillWidth ? areaWidth
1937                     : Math.min(areaWidth, node.prefWidth(childHeight)),
1938                     node.maxWidth(childHeight));
1939         }
1940 
1941         result.set(childWidth, childHeight);
1942         return result;
1943     }
1944 
1945     /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */
1946     private double getMaxAreaHeight(List<Node> children, Callback<Node,Insets> childMargins,  double childWidths[], VPos valignment, boolean minimum) {
1947         final double singleChildWidth = childWidths == null ? -1 : childWidths.length == 1 ? childWidths[0] : Double.NaN;
1948         if (valignment == VPos.BASELINE) {
1949             double maxAbove = 0;
1950             double maxBelow = 0;
1951             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
1952                 final Node child = children.get(i);
1953                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
1954                 Insets margin = childMargins.call(child);
1955                 final double top = margin != null? snapSpace(margin.getTop()) : 0;
1956                 final double bottom = margin != null? snapSpace(margin.getBottom()) : 0;
1957                 final double baseline = child.getBaselineOffset();
1958 
1959                 final double childHeight = minimum? snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth));
1960                 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1961                     maxAbove = Math.max(maxAbove, childHeight + top);
1962                 } else {
1963                     maxAbove = Math.max(maxAbove, baseline + top);
1964                     maxBelow = Math.max(maxBelow,
1965                             snapSpace(minimum?snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth))) -
1966                             baseline + bottom);
1967                 }
1968             }
1969             return maxAbove + maxBelow; //remind(aim): ceil this value?
1970         } else {
1971             double max = 0;
1972             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
1973                 final Node child = children.get(i);
1974                 Insets margin = childMargins.call(child);
1975                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
1976                 max = Math.max(max, minimum?
1977                     computeChildMinAreaHeight(child, -1, margin, childWidth) :
1978                         computeChildPrefAreaHeight(child, -1, margin, childWidth));
1979             }
1980             return max;
1981         }
1982     }
1983 
1984     /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */
1985     private double getMaxAreaWidth(List<javafx.scene.Node> children,


2045      * values will be rounded to their nearest pixel boundaries.
2046      * <p>
2047      * If {@code margin} is non-null, then that space will be allocated around the
2048      * child within the layout area.  margin may be null.
2049      *
2050      * @param child the child being positioned within this region
2051      * @param areaX the horizontal offset of the layout area relative to this region
2052      * @param areaY the vertical offset of the layout area relative to this region
2053      * @param areaWidth  the width of the layout area
2054      * @param areaHeight the height of the layout area
2055      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2056      * @param margin the margin of space to be allocated around the child
2057      * @param halignment the horizontal alignment for the child within the area
2058      * @param valignment the vertical alignment for the child within the area
2059      *
2060      * @since JavaFX 8.0
2061      */
2062     public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2063                                double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
2064         Insets childMargin = margin != null? margin : Insets.EMPTY;


2065 
2066         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2067                 snapSpace(childMargin.getTop(), isSnapToPixel),
2068                 snapSpace(childMargin.getRight(), isSnapToPixel),
2069                 snapSpace(childMargin.getBottom(), isSnapToPixel),
2070                 snapSpace(childMargin.getLeft(), isSnapToPixel),
2071                 halignment, valignment, isSnapToPixel);
2072     }
2073 
2074     /**
2075      * Utility method which lays out the child within an area of this
2076      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2077      * with a baseline offset relative to that area.
2078      * <p>
2079      * If the child is resizable, this method will resize it to fill the specified
2080      * area unless the node's maximum size prevents it.  If the node's maximum
2081      * size preference is less than the area size, the maximum size will be used.
2082      * If node's maximum is greater than the area size, then the node will be
2083      * resized to fit within the area, unless its minimum size prevents it.
2084      * <p>
2085      * If the child has a non-null contentBias, then this method will use it when
2086      * resizing the child.  If the contentBias is horizontal, it will set its width
2087      * first to the area's width (up to the child's max width limit) and then pass
2088      * that value to compute the child's height.  If child's contentBias is vertical,
2089      * then it will set its height to the area height (up to child's max height limit)
2090      * and pass that height to compute the child's width.  If the child's contentBias


2279      * @param areaX the horizontal offset of the layout area relative to this region
2280      * @param areaY the vertical offset of the layout area relative to this region
2281      * @param areaWidth  the width of the layout area
2282      * @param areaHeight the height of the layout area
2283      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2284      * @param margin the margin of space to be allocated around the child
2285      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2286      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2287      * @param halignment the horizontal alignment for the child within the area
2288      * @param valignment the vertical alignment for the child within the area
2289      * @param isSnapToPixel whether to snap size and position to pixels
2290      * @since JavaFX 8.0
2291      */
2292     public static void layoutInArea(Node child, double areaX, double areaY,
2293                                double areaWidth, double areaHeight,
2294                                double areaBaselineOffset,
2295                                Insets margin, boolean fillWidth, boolean fillHeight,
2296                                HPos halignment, VPos valignment, boolean isSnapToPixel) {
2297 
2298         Insets childMargin = margin != null ? margin : Insets.EMPTY;


2299 
2300         double top = snapSpace(childMargin.getTop(), isSnapToPixel);
2301         double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel);
2302         double left = snapSpace(childMargin.getLeft(), isSnapToPixel);
2303         double right = snapSpace(childMargin.getRight(), isSnapToPixel);
2304 
2305         if (valignment == VPos.BASELINE) {
2306             double bo = child.getBaselineOffset();
2307             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2308                 if (child.isResizable()) {
2309                     // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
2310                     // be resized to this area
2311                     bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel);
2312                 } else {
2313                     top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel);
2314                 }
2315             } else {
2316                 top = snapSpace(areaBaselineOffset - bo, isSnapToPixel);
2317             }
2318         }
2319 
2320 
2321         if (child.isResizable()) {
2322             Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
2323                     fillWidth, fillHeight, TEMP_VEC2D);
2324             child.resize(snapSize(size.x, isSnapToPixel),snapSize(size.y, isSnapToPixel));

2325         }
2326         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2327                 top, right, bottom, left, halignment, valignment, isSnapToPixel);
2328     }
2329 
2330     private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2331                           double areaBaselineOffset,
2332                           double topMargin, double rightMargin, double bottomMargin, double leftMargin,
2333                           HPos hpos, VPos vpos, boolean isSnapToPixel) {
2334         final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
2335                                                      child.getLayoutBounds().getWidth(), hpos);
2336         final double yoffset;
2337         if (vpos == VPos.BASELINE) {
2338             double bo = child.getBaselineOffset();
2339             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2340                 // We already know the layout bounds at this stage, so we can use them
2341                 yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
2342             } else {
2343                 yoffset = areaBaselineOffset - bo;
2344             }
2345         } else {
2346             yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
2347                                          child.getLayoutBounds().getHeight(), vpos);
2348         }
2349         final double x = snapPosition(areaX + xoffset, isSnapToPixel);
2350         final double y = snapPosition(areaY + yoffset, isSnapToPixel);




2351 
2352         child.relocate(x,y);
2353     }
2354 
2355      /**************************************************************************
2356      *                                                                         *
2357      * PG Implementation                                                       *
2358      *                                                                         *
2359      **************************************************************************/
2360 
2361     /** @treatAsPrivate */
2362     @Override public void impl_updatePeer() {
2363         // TODO I think we have a bug, where if you create a Region with an Image that hasn't
2364         // been loaded, we have no listeners on that image so as to cause a pulse & repaint
2365         // to happen once the image is loaded. We just assume the image has been loaded
2366         // (since when the image is created using new Image(url) or CSS it happens eagerly).
2367         super.impl_updatePeer();
2368         if (_shape != null) _shape.impl_syncPeer();
2369         NGRegion pg = impl_getPeer();
2370 




  60 import java.util.Arrays;
  61 import java.util.List;
  62 import java.util.function.Function;
  63 import com.sun.javafx.util.Logging;
  64 import com.sun.javafx.util.TempState;
  65 import com.sun.javafx.binding.ExpressionHelper;
  66 import javafx.css.converter.BooleanConverter;
  67 import javafx.css.converter.InsetsConverter;
  68 import javafx.css.converter.ShapeConverter;
  69 import javafx.css.converter.SizeConverter;
  70 import com.sun.javafx.geom.BaseBounds;
  71 import com.sun.javafx.geom.PickRay;
  72 import com.sun.javafx.geom.RectBounds;
  73 import com.sun.javafx.geom.Vec2d;
  74 import com.sun.javafx.geom.transform.BaseTransform;
  75 import com.sun.javafx.scene.DirtyBits;
  76 import com.sun.javafx.scene.input.PickResultChooser;
  77 import com.sun.javafx.sg.prism.NGNode;
  78 import com.sun.javafx.sg.prism.NGRegion;
  79 import com.sun.javafx.tk.Toolkit;
  80 import javafx.scene.Scene;
  81 import javafx.stage.Window;
  82 import sun.util.logging.PlatformLogger;
  83 import sun.util.logging.PlatformLogger.Level;
  84 
  85 /**
  86  * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers.
  87  * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds
  88  * and borders. It is designed to support as much of the CSS3 specification for backgrounds
  89  * and borders as is relevant to JavaFX.
  90  * The full specification is available at <a href="http://www.w3.org/TR/2012/CR-css3-background-20120724/">the W3C</a>.
  91  * <p/>
  92  * Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside
  93  * these bounds. The content area of a Region is the area which is occupied for the layout of its children.
  94  * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the
  95  * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can
  96  * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region,
  97  * but does not affect the layout bounds.
  98  * <p/>
  99  * A Region has a Background, and a Border, although either or both of these might be empty. The Background
 100  * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the
 101  * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and


 193      * pref is greater than the max, then the max is returned. If the pref lies
 194      * between the min and the max, then the pref is returned.
 195      *
 196      *
 197      * @param min The minimum bound
 198      * @param pref The value to be clamped between the min and max
 199      * @param max the maximum bound
 200      * @return the size bounded by min, pref, and max.
 201      */
 202     static double boundedSize(double min, double pref, double max) {
 203         double a = pref >= min ? pref : min;
 204         double b = min >= max ? min : max;
 205         return a <= b ? a : b;
 206     }
 207 
 208     double adjustWidthByMargin(double width, Insets margin) {
 209         if (margin == null || margin == Insets.EMPTY) {
 210             return width;
 211         }
 212         boolean isSnapToPixel = isSnapToPixel();
 213         return width - snapSpaceX(margin.getLeft(), isSnapToPixel) - snapSpaceX(margin.getRight(), isSnapToPixel);
 214     }
 215 
 216     double adjustHeightByMargin(double height, Insets margin) {
 217         if (margin == null || margin == Insets.EMPTY) {
 218             return height;
 219         }
 220         boolean isSnapToPixel = isSnapToPixel();
 221         return height - snapSpaceY(margin.getTop(), isSnapToPixel) - snapSpaceY(margin.getBottom(), isSnapToPixel);
 222     }
 223 
 224     private static final boolean snapVerbose = false;
 225     private static double getSnapScaleX(Node n) {
 226         final double sx = _getSnapScaleXimpl(n.getScene());
 227         if (snapVerbose) System.out.println("static:"+n+" scale is "+sx);
 228         return sx;
 229     }
 230     private static double _getSnapScaleXimpl(Scene scene) {
 231         if (scene == null) return 1.0;
 232         Window window = scene.getWindow();
 233         if (window == null) return 1.0;
 234         return window.getRenderScaleX();
 235     }
 236 
 237     private static double getSnapScaleY(Node n) {
 238         double sy = _getSnapScaleYimpl(n.getScene());
 239         if (snapVerbose) System.out.println("static:"+n+" scale is "+sy);
 240         return sy;
 241     }
 242     private static double _getSnapScaleYimpl(Scene scene) {
 243         if (scene == null) return 1.0;
 244         Window window = scene.getWindow();
 245         if (window == null) return 1.0;
 246         return window.getRenderScaleY();
 247     }
 248 
 249     private double lastScaleX = -1;
 250     private double getSnapScaleX() {
 251         double sx = _getSnapScaleXimpl(getScene());
 252         if (snapVerbose && sx != lastScaleX) {
 253             System.out.println(this+" scale is "+sx);
 254             lastScaleX = sx;
 255         }
 256         return sx;
 257     }
 258 
 259     private double lastScaleY = -1;
 260     private double getSnapScaleY() {
 261         double sy = _getSnapScaleYimpl(getScene());
 262         if (snapVerbose && sy != lastScaleY) {
 263             System.out.println(this+" scale is "+sy);
 264             lastScaleY = sy;
 265         }
 266         return sy;
 267     }
 268 
 269     private static double scaledRound(double value, double scale) {
 270         return Math.round(value * scale) / scale;
 271     }
 272 
 273     private static double scaledFloor(double value, double scale) {
 274         return Math.floor(value * scale) / scale;
 275     }
 276 
 277     private static double scaledCeil(double value, double scale) {
 278         return Math.ceil(value * scale) / scale;
 279     }
 280 
 281     /**
 282      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 283      * the value is simply returned. This method will surely be JIT'd under normal
 284      * circumstances, however on an interpreter it would be better to inline this
 285      * method. However the use of Math.round here, and Math.ceil in snapSize is
 286      * not obvious, and so for code maintenance this logic is pulled out into
 287      * a separate method.
 288      *
 289      * @param value The value that needs to be snapped
 290      * @param snapToPixel Whether to snap to pixel
 291      * @return value either as passed in or rounded based on snapToPixel
 292      */
 293     private double snapSpaceX(double value, boolean snapToPixel) {
 294         return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
 295     }
 296     private double snapSpaceY(double value, boolean snapToPixel) {
 297         return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
 298     }
 299 
 300     private static double snapSpace(double value, boolean snapToPixel, double snapScale) {
 301         return snapToPixel ? scaledRound(value, snapScale) : value;
 302     }
 303 
 304     /**
 305      * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise,
 306      * the value is simply returned.
 307      *
 308      * @param value The value that needs to be snapped
 309      * @param snapToPixel Whether to snap to pixel
 310      * @return value either as passed in or ceil'd based on snapToPixel
 311      */
 312     private double snapSizeX(double value, boolean snapToPixel) {
 313         return snapToPixel ? scaledCeil(value, getSnapScaleX()) : value;
 314     }
 315     private double snapSizeY(double value, boolean snapToPixel) {
 316         return snapToPixel ? scaledCeil(value, getSnapScaleY()) : value;
 317     }
 318 
 319     private static double snapSize(double value, boolean snapToPixel, double snapScale) {
 320         return snapToPixel ? scaledCeil(value, snapScale) : value;
 321     }
 322 
 323     /**
 324      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 325      * the value is simply returned.
 326      *
 327      * @param value The value that needs to be snapped
 328      * @param snapToPixel Whether to snap to pixel
 329      * @return value either as passed in or rounded based on snapToPixel
 330      */
 331     private double snapPositionX(double value, boolean snapToPixel) {
 332         return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
 333     }
 334     private double snapPositionY(double value, boolean snapToPixel) {
 335         return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
 336     }
 337 
 338     private static double snapPosition(double value, boolean snapToPixel, double snapScale) {
 339         return snapToPixel ? scaledRound(value, snapScale) : value;
 340     }
 341 
 342     private double snapPortionX(double value, boolean snapToPixel) {
 343         if (!snapToPixel || value == 0) return value;
 344         double s = getSnapScaleX();
 345         value *= s;
 346         if (value > 0) {
 347             value = Math.max(1, Math.floor(value));
 348         } else {
 349             value = Math.min(-1, Math.ceil(value));
 350         }
 351         return value / s;
 352     }
 353     private double snapPortionY(double value, boolean snapToPixel) {
 354         if (!snapToPixel || value == 0) return value;
 355         double s = getSnapScaleY();
 356         value *= s;
 357         if (value > 0) {
 358             value = Math.max(1, Math.floor(value));
 359         } else {
 360             value = Math.min(-1, Math.ceil(value));
 361         }
 362         return value / s;
 363     }
 364 
 365     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 366                                         Function<Integer, Double> positionToWidth,
 367                                         double areaHeight, boolean fillHeight) {
 368         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, isSnapToPixel());
 369     }
 370 
 371     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 372             Function<Integer, Double> positionToWidth,
 373             double areaHeight, boolean fillHeight, boolean snapToPixel) {
 374         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight,
 375                 getMinBaselineComplement(children), snapToPixel);
 376     }
 377 
 378     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 379                                  Function<Integer, Double> positionToWidth,
 380                                  double areaHeight, final boolean fillHeight, double minComplement) {
 381         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 382     }


 390     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 391                                  Function<Integer, Double> positionToWidth,
 392                                  double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement) {
 393         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 394     }
 395 
 396     /**
 397      * Returns the baseline offset of provided children, with respect to the minimum complement, computed
 398      * by {@link #getMinBaselineComplement(java.util.List)} from the same set of children.
 399      * @param children the children with baseline alignment
 400      * @param margins their margins (callback)
 401      * @param positionToWidth callback for children widths (can return -1 if no bias is used)
 402      * @param areaHeight height of the area to layout in
 403      * @param fillHeight callback to specify children that has fillHeight constraint
 404      * @param minComplement minimum complement
 405      */
 406     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 407             Function<Integer, Double> positionToWidth,
 408             double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement, boolean snapToPixel) {
 409         double b = 0;
 410         double snapScaleV = 0.0;
 411         for (int i = 0;i < children.size(); ++i) {
 412             Node n = children.get(i);
 413             // Note: all children should be coming from the same parent so they should all have the same snapScale
 414             if (snapToPixel && i == 0) snapScaleV = getSnapScaleY(n.getParent());
 415             Insets margin = margins.call(n);
 416             double top = margin != null ? snapSpace(margin.getTop(), snapToPixel, snapScaleV) : 0;
 417             double bottom = (margin != null ? snapSpace(margin.getBottom(), snapToPixel, snapScaleV) : 0);
 418             final double bo = n.getBaselineOffset();
 419             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
 420                 double alt = -1;
 421                 if (n.getContentBias() == Orientation.HORIZONTAL) {
 422                     alt = positionToWidth.apply(i);
 423                 }
 424                 if (fillHeight.apply(i)) {
 425                     // If the children fills it's height, than it's "preferred" height is the area without the complement and insets
 426                     b = Math.max(b, top + boundedSize(n.minHeight(alt), areaHeight - minComplement - top - bottom,
 427                             n.maxHeight(alt)));
 428                 } else {
 429                     // Otherwise, we must use the area without complement and insets as a maximum for the Node
 430                     b = Math.max(b, top + boundedSize(n.minHeight(alt), n.prefHeight(alt),
 431                             Math.min(n.maxHeight(alt), areaHeight - minComplement - top - bottom)));
 432                 }
 433             } else {
 434                 b = Math.max(b, top + bo);
 435             }
 436         }
 437         return b;


1639      */
1640     protected double computeMaxWidth(double height) {
1641         return Double.MAX_VALUE;
1642     }
1643 
1644     /**
1645      * Computes the maximum height of this region.
1646      * Returns Double.MAX_VALUE by default.
1647      * Region subclasses may override this method to return a different
1648      * value based on their content and layout strategy.  If the subclass
1649      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1650      * ignored.
1651      *
1652      * @return the computed maximum height for this region
1653      */
1654     protected double computeMaxHeight(double width) {
1655         return Double.MAX_VALUE;
1656     }
1657 
1658     /**
1659      * If this region's snapToPixel property is false, this method returns the
1660      * same value, else it tries to return a value rounded to the nearest
1661      * pixel, but since there is no indication if the value is a vertical
1662      * or horizontal measurement then it may be snapped to the wrong pixel
1663      * size metric on screens with different horizontal and vertical scales.
1664      * @param value the space value to be snapped
1665      * @return value rounded to nearest pixel
1666      * @deprecated replaced by {@code snapSpaceX()} and {@code snapSpaceY()}
1667      */
1668     @Deprecated
1669     protected double snapSpace(double value) {
1670         return snapSpaceX(value, isSnapToPixel());
1671     }
1672 
1673     /**
1674      * If this region's snapToPixel property is true, returns a value rounded
1675      * to the nearest pixel in the horizontal direction, else returns the
1676      * same value.
1677      * @param value the space value to be snapped
1678      * @return value rounded to nearest pixel
1679      */
1680     protected double snapSpaceX(double value) {
1681         return snapSpaceX(value, isSnapToPixel());
1682     }
1683 
1684     /**
1685      * If this region's snapToPixel property is true, returns a value rounded
1686      * to the nearest pixel in the vertical direction, else returns the
1687      * same value.
1688      * @param value the space value to be snapped
1689      * @return value rounded to nearest pixel
1690      */
1691     protected double snapSpaceY(double value) {
1692         return snapSpaceY(value, isSnapToPixel());
1693     }
1694 
1695     /**
1696      * If this region's snapToPixel property is false, this method returns the
1697      * same value, else it tries to return a value ceiled to the nearest
1698      * pixel, but since there is no indication if the value is a vertical
1699      * or horizontal measurement then it may be snapped to the wrong pixel
1700      * size metric on screens with different horizontal and vertical scales.
1701      * @param value the size value to be snapped
1702      * @return value ceiled to nearest pixel
1703      * @deprecated replaced by {@code snapSizeX()} and {@code snapSizeY()}
1704      */
1705     @Deprecated
1706     protected double snapSize(double value) {
1707         return snapSizeX(value, isSnapToPixel());
1708     }
1709 
1710     /**
1711      * If this region's snapToPixel property is true, returns a value ceiled
1712      * to the nearest pixel in the horizontal direction, else returns the
1713      * same value.
1714      * @param value the size value to be snapped
1715      * @return value ceiled to nearest pixel
1716      */
1717     protected double snapSizeX(double value) {
1718         return snapSizeX(value, isSnapToPixel());
1719     }
1720 
1721     /**
1722      * If this region's snapToPixel property is true, returns a value ceiled
1723      * to the nearest pixel in the vertical direction, else returns the
1724      * same value.
1725      * @param value the size value to be snapped
1726      * @return value ceiled to nearest pixel
1727      */
1728     protected double snapSizeY(double value) {
1729         return snapSizeY(value, isSnapToPixel());
1730     }
1731 
1732     /**
1733      * If this region's snapToPixel property is false, this method returns the
1734      * same value, else it tries to return a value rounded to the nearest
1735      * pixel, but since there is no indication if the value is a vertical
1736      * or horizontal measurement then it may be snapped to the wrong pixel
1737      * size metric on screens with different horizontal and vertical scales.
1738      * @param value the position value to be snapped
1739      * @return value rounded to nearest pixel
1740      * @deprecated replaced by {@code snapPositionX()} and {@code snapPositionY()}
1741      */
1742     @Deprecated
1743     protected double snapPosition(double value) {
1744         return snapPositionX(value, isSnapToPixel());
1745     }
1746 
1747     /**
1748      * If this region's snapToPixel property is true, returns a value rounded
1749      * to the nearest pixel in the horizontal direction, else returns the
1750      * same value.
1751      * @param value the position value to be snapped
1752      * @return value rounded to nearest pixel
1753      */
1754     protected double snapPositionX(double value) {
1755         return snapPositionX(value, isSnapToPixel());
1756     }
1757 
1758     /**
1759      * If this region's snapToPixel property is true, returns a value rounded
1760      * to the nearest pixel in the vertical direction, else returns the
1761      * same value.
1762      * @param value the position value to be snapped
1763      * @return value rounded to nearest pixel
1764      */
1765     protected double snapPositionY(double value) {
1766         return snapPositionY(value, isSnapToPixel());
1767     }
1768 
1769     double snapPortionX(double value) {
1770         return snapPortionX(value, isSnapToPixel());
1771     }
1772     double snapPortionY(double value) {
1773         return snapPortionY(value, isSnapToPixel());
1774     }
1775 
1776 
1777     /**
1778      * Utility method to get the top inset which includes padding and border
1779      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1780      *
1781      * @since JavaFX 8.0
1782      * @return Rounded up insets top
1783      */
1784     public final double snappedTopInset() {
1785         return snappedTopInset;
1786     }
1787 
1788     /**
1789      * Utility method to get the bottom inset which includes padding and border
1790      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1791      *
1792      * @since JavaFX 8.0
1793      * @return Rounded up insets bottom


1808     }
1809 
1810     /**
1811      * Utility method to get the right inset which includes padding and border
1812      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1813      *
1814      * @since JavaFX 8.0
1815      * @return Rounded up insets right
1816      */
1817     public final double snappedRightInset() {
1818         return snappedRightInset;
1819     }
1820 
1821 
1822     double computeChildMinAreaWidth(Node child, Insets margin) {
1823         return computeChildMinAreaWidth(child, -1, margin, -1, false);
1824     }
1825 
1826     double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1827         final boolean snap = isSnapToPixel();
1828         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1829         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1830         double alt = -1;
1831         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1832             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1833             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1834             double bo = child.getBaselineOffset();
1835             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1836                     height - top - bottom - baselineComplement :
1837                      height - top - bottom;
1838             if (fillHeight) {
1839                 alt = snapSizeY(boundedSize(
1840                         child.minHeight(-1), contentHeight,
1841                         child.maxHeight(-1)));
1842             } else {
1843                 alt = snapSizeY(boundedSize(
1844                         child.minHeight(-1),
1845                         child.prefHeight(-1),
1846                         Math.min(child.maxHeight(-1), contentHeight)));
1847             }
1848         }
1849         return left + snapSizeX(child.minWidth(alt)) + right;
1850     }
1851 
1852     double computeChildMinAreaHeight(Node child, Insets margin) {
1853         return computeChildMinAreaHeight(child, -1, margin, -1);
1854     }
1855 
1856     double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
1857         final boolean snap = isSnapToPixel();
1858         double top =margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1859         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1860 
1861         double alt = -1;
1862         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1863             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1864             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1865             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1866                     child.maxWidth(-1));
1867         }
1868 
1869         // For explanation, see computeChildPrefAreaHeight
1870         if (minBaselineComplement != -1) {
1871             double baseline = child.getBaselineOffset();
1872             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1873                 return top + snapSizeY(child.minHeight(alt)) + bottom
1874                         + minBaselineComplement;
1875             } else {
1876                 return baseline + minBaselineComplement;
1877             }
1878         } else {
1879             return top + snapSizeY(child.minHeight(alt)) + bottom;
1880         }
1881     }
1882 
1883     double computeChildPrefAreaWidth(Node child, Insets margin) {
1884         return computeChildPrefAreaWidth(child, -1, margin, -1, false);
1885     }
1886 
1887     double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1888         final boolean snap = isSnapToPixel();
1889         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1890         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1891         double alt = -1;
1892         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1893             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1894             double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1895             double bo = child.getBaselineOffset();
1896             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1897                     height - top - bottom - baselineComplement :
1898                      height - top - bottom;
1899             if (fillHeight) {
1900                 alt = snapSizeY(boundedSize(
1901                         child.minHeight(-1), contentHeight,
1902                         child.maxHeight(-1)));
1903             } else {
1904                 alt = snapSizeY(boundedSize(
1905                         child.minHeight(-1),
1906                         child.prefHeight(-1),
1907                         Math.min(child.maxHeight(-1), contentHeight)));
1908             }
1909         }
1910         return left + snapSizeX(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
1911     }
1912 
1913     double computeChildPrefAreaHeight(Node child, Insets margin) {
1914         return computeChildPrefAreaHeight(child, -1, margin, -1);
1915     }
1916 
1917     double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
1918         final boolean snap = isSnapToPixel();
1919         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1920         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1921 
1922         double alt = -1;
1923         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1924             double left = margin != null ? snapSpaceX(margin.getLeft(), snap) : 0;
1925             double right = margin != null ? snapSpaceX(margin.getRight(), snap) : 0;
1926             alt = snapSizeX(boundedSize(
1927                     child.minWidth(-1), width != -1 ? width - left - right
1928                     : child.prefWidth(-1), child.maxWidth(-1)));
1929         }
1930 
1931         if (prefBaselineComplement != -1) {
1932             double baseline = child.getBaselineOffset();
1933             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1934                 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
1935                 // the preferred complement to it
1936                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
1937                         + prefBaselineComplement;
1938             } else {
1939                 // For all other Nodes, it's just their baseline and the complement.
1940                 // Note that the complement already contain the Node's preferred (or fixed) height
1941                 return top + baseline + prefBaselineComplement + bottom;
1942             }
1943         } else {
1944             return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
1945         }
1946     }
1947 
1948     double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1949         double max = child.maxWidth(-1);
1950         if (max == Double.MAX_VALUE) {
1951             return max;
1952         }
1953         final boolean snap = isSnapToPixel();
1954         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1955         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1956         double alt = -1;
1957         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1958             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1959             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1960             double bo = child.getBaselineOffset();
1961             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1962                     height - top - bottom - baselineComplement :
1963                      height - top - bottom;
1964             if (fillHeight) {
1965                 alt = snapSizeY(boundedSize(
1966                         child.minHeight(-1), contentHeight,
1967                         child.maxHeight(-1)));
1968             } else {
1969                 alt = snapSizeY(boundedSize(
1970                         child.minHeight(-1),
1971                         child.prefHeight(-1),
1972                         Math.min(child.maxHeight(-1), contentHeight)));
1973             }
1974             max = child.maxWidth(alt);
1975         }
1976         // if min > max, min wins, so still need to call boundedSize()
1977         return left + snapSizeX(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
1978     }
1979 
1980     double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
1981         double max = child.maxHeight(-1);
1982         if (max == Double.MAX_VALUE) {
1983             return max;
1984         }
1985 
1986         final boolean snap = isSnapToPixel();
1987         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1988         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1989         double alt = -1;
1990         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1991             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1992             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1993             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1994                 child.minWidth(-1));
1995             max = child.maxHeight(alt);
1996         }
1997         // For explanation, see computeChildPrefAreaHeight
1998         if (maxBaselineComplement != -1) {
1999             double baseline = child.getBaselineOffset();
2000             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2001                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
2002                         + maxBaselineComplement;
2003             } else {
2004                 return top + baseline + maxBaselineComplement + bottom;
2005             }
2006         } else {
2007             // if min > max, min wins, so still need to call boundedSize()
2008             return top + snapSizeY(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
2009         }
2010     }
2011 
2012     /* Max of children's minimum area widths */
2013 
2014     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins) {
2015         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, true);
2016     }
2017 
2018     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins, double height, boolean fillHeight) {
2019         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, true);
2020     }
2021 
2022     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight) {
2023         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, true);
2024     }
2025 
2026     /* Max of children's minimum area heights */
2027 
2028     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {


2118             childWidth = boundedSize(
2119                     node.minWidth(childHeight), fillWidth ? areaWidth
2120                     : Math.min(areaWidth, node.prefWidth(childHeight)),
2121                     node.maxWidth(childHeight));
2122         }
2123 
2124         result.set(childWidth, childHeight);
2125         return result;
2126     }
2127 
2128     /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */
2129     private double getMaxAreaHeight(List<Node> children, Callback<Node,Insets> childMargins,  double childWidths[], VPos valignment, boolean minimum) {
2130         final double singleChildWidth = childWidths == null ? -1 : childWidths.length == 1 ? childWidths[0] : Double.NaN;
2131         if (valignment == VPos.BASELINE) {
2132             double maxAbove = 0;
2133             double maxBelow = 0;
2134             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2135                 final Node child = children.get(i);
2136                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2137                 Insets margin = childMargins.call(child);
2138                 final double top = margin != null? snapSpaceY(margin.getTop()) : 0;
2139                 final double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
2140                 final double baseline = child.getBaselineOffset();
2141 
2142                 final double childHeight = minimum? snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth));
2143                 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2144                     maxAbove = Math.max(maxAbove, childHeight + top);
2145                 } else {
2146                     maxAbove = Math.max(maxAbove, baseline + top);
2147                     maxBelow = Math.max(maxBelow,
2148                             snapSpaceY(minimum?snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth))) -
2149                             baseline + bottom);
2150                 }
2151             }
2152             return maxAbove + maxBelow; //remind(aim): ceil this value?
2153         } else {
2154             double max = 0;
2155             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2156                 final Node child = children.get(i);
2157                 Insets margin = childMargins.call(child);
2158                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2159                 max = Math.max(max, minimum?
2160                     computeChildMinAreaHeight(child, -1, margin, childWidth) :
2161                         computeChildPrefAreaHeight(child, -1, margin, childWidth));
2162             }
2163             return max;
2164         }
2165     }
2166 
2167     /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */
2168     private double getMaxAreaWidth(List<javafx.scene.Node> children,


2228      * values will be rounded to their nearest pixel boundaries.
2229      * <p>
2230      * If {@code margin} is non-null, then that space will be allocated around the
2231      * child within the layout area.  margin may be null.
2232      *
2233      * @param child the child being positioned within this region
2234      * @param areaX the horizontal offset of the layout area relative to this region
2235      * @param areaY the vertical offset of the layout area relative to this region
2236      * @param areaWidth  the width of the layout area
2237      * @param areaHeight the height of the layout area
2238      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2239      * @param margin the margin of space to be allocated around the child
2240      * @param halignment the horizontal alignment for the child within the area
2241      * @param valignment the vertical alignment for the child within the area
2242      *
2243      * @since JavaFX 8.0
2244      */
2245     public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2246                                double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
2247         Insets childMargin = margin != null? margin : Insets.EMPTY;
2248         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2249         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2250 
2251         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2252                 snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY),
2253                 snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX),
2254                 snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY),
2255                 snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX),
2256                 halignment, valignment, isSnapToPixel);
2257     }
2258 
2259     /**
2260      * Utility method which lays out the child within an area of this
2261      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2262      * with a baseline offset relative to that area.
2263      * <p>
2264      * If the child is resizable, this method will resize it to fill the specified
2265      * area unless the node's maximum size prevents it.  If the node's maximum
2266      * size preference is less than the area size, the maximum size will be used.
2267      * If node's maximum is greater than the area size, then the node will be
2268      * resized to fit within the area, unless its minimum size prevents it.
2269      * <p>
2270      * If the child has a non-null contentBias, then this method will use it when
2271      * resizing the child.  If the contentBias is horizontal, it will set its width
2272      * first to the area's width (up to the child's max width limit) and then pass
2273      * that value to compute the child's height.  If child's contentBias is vertical,
2274      * then it will set its height to the area height (up to child's max height limit)
2275      * and pass that height to compute the child's width.  If the child's contentBias


2464      * @param areaX the horizontal offset of the layout area relative to this region
2465      * @param areaY the vertical offset of the layout area relative to this region
2466      * @param areaWidth  the width of the layout area
2467      * @param areaHeight the height of the layout area
2468      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2469      * @param margin the margin of space to be allocated around the child
2470      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2471      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2472      * @param halignment the horizontal alignment for the child within the area
2473      * @param valignment the vertical alignment for the child within the area
2474      * @param isSnapToPixel whether to snap size and position to pixels
2475      * @since JavaFX 8.0
2476      */
2477     public static void layoutInArea(Node child, double areaX, double areaY,
2478                                double areaWidth, double areaHeight,
2479                                double areaBaselineOffset,
2480                                Insets margin, boolean fillWidth, boolean fillHeight,
2481                                HPos halignment, VPos valignment, boolean isSnapToPixel) {
2482 
2483         Insets childMargin = margin != null ? margin : Insets.EMPTY;
2484         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2485         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2486 
2487         double top = snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY);
2488         double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY);
2489         double left = snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX);
2490         double right = snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX);
2491 
2492         if (valignment == VPos.BASELINE) {
2493             double bo = child.getBaselineOffset();
2494             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2495                 if (child.isResizable()) {
2496                     // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
2497                     // be resized to this area
2498                     bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel, snapScaleY);
2499                 } else {
2500                     top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel, snapScaleY);
2501                 }
2502             } else {
2503                 top = snapSpace(areaBaselineOffset - bo, isSnapToPixel, snapScaleY);
2504             }
2505         }
2506 
2507 
2508         if (child.isResizable()) {
2509             Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
2510                     fillWidth, fillHeight, TEMP_VEC2D);
2511             child.resize(snapSize(size.x, isSnapToPixel, snapScaleX),
2512                          snapSize(size.y, isSnapToPixel, snapScaleX));
2513         }
2514         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2515                 top, right, bottom, left, halignment, valignment, isSnapToPixel);
2516     }
2517 
2518     private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2519                           double areaBaselineOffset,
2520                           double topMargin, double rightMargin, double bottomMargin, double leftMargin,
2521                           HPos hpos, VPos vpos, boolean isSnapToPixel) {
2522         final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
2523                                                      child.getLayoutBounds().getWidth(), hpos);
2524         final double yoffset;
2525         if (vpos == VPos.BASELINE) {
2526             double bo = child.getBaselineOffset();
2527             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2528                 // We already know the layout bounds at this stage, so we can use them
2529                 yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
2530             } else {
2531                 yoffset = areaBaselineOffset - bo;
2532             }
2533         } else {
2534             yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
2535                                          child.getLayoutBounds().getHeight(), vpos);
2536         }
2537         double x = areaX + xoffset;
2538         double y = areaY + yoffset;
2539         if (isSnapToPixel) {
2540             x = snapPosition(x, true, getSnapScaleX(child));
2541             y = snapPosition(y, true, getSnapScaleY(child));
2542         }
2543 
2544         child.relocate(x,y);
2545     }
2546 
2547      /**************************************************************************
2548      *                                                                         *
2549      * PG Implementation                                                       *
2550      *                                                                         *
2551      **************************************************************************/
2552 
2553     /** @treatAsPrivate */
2554     @Override public void impl_updatePeer() {
2555         // TODO I think we have a bug, where if you create a Region with an Image that hasn't
2556         // been loaded, we have no listeners on that image so as to cause a pulse & repaint
2557         // to happen once the image is loaded. We just assume the image has been loaded
2558         // (since when the image is created using new Image(url) or CSS it happens eagerly).
2559         super.impl_updatePeer();
2560         if (_shape != null) _shape.impl_syncPeer();
2561         NGRegion pg = impl_getPeer();
2562