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 |