1 /*
   2  * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.layout;
  27 
  28 import javafx.beans.InvalidationListener;
  29 import javafx.beans.property.BooleanProperty;
  30 import javafx.beans.property.DoubleProperty;
  31 import javafx.beans.property.ObjectProperty;
  32 import javafx.beans.property.ReadOnlyDoubleProperty;
  33 import javafx.beans.property.ReadOnlyDoubleWrapper;
  34 import javafx.beans.property.ReadOnlyObjectProperty;
  35 import javafx.beans.property.ReadOnlyObjectPropertyBase;
  36 import javafx.beans.value.ChangeListener;
  37 import javafx.collections.ObservableList;
  38 import javafx.css.CssMetaData;
  39 import javafx.css.Styleable;
  40 import javafx.css.StyleableBooleanProperty;
  41 import javafx.css.StyleableDoubleProperty;
  42 import javafx.css.StyleableObjectProperty;
  43 import javafx.css.StyleableProperty;
  44 import javafx.geometry.BoundingBox;
  45 import javafx.geometry.Bounds;
  46 import javafx.geometry.HPos;
  47 import javafx.geometry.Insets;
  48 import javafx.geometry.Orientation;
  49 import javafx.geometry.VPos;
  50 import javafx.scene.Node;
  51 import javafx.scene.Parent;
  52 import javafx.scene.image.Image;
  53 import javafx.scene.shape.Shape;
  54 import javafx.scene.shape.StrokeLineCap;
  55 import javafx.scene.shape.StrokeLineJoin;
  56 import javafx.scene.shape.StrokeType;
  57 import javafx.util.Callback;
  58 import java.util.ArrayList;
  59 import java.util.Collections;
  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.NodeHelper;
  77 import com.sun.javafx.scene.ParentHelper;
  78 import com.sun.javafx.scene.input.PickResultChooser;
  79 import com.sun.javafx.scene.layout.RegionHelper;
  80 import com.sun.javafx.scene.shape.ShapeHelper;
  81 import com.sun.javafx.sg.prism.NGNode;
  82 import com.sun.javafx.sg.prism.NGRegion;
  83 import com.sun.javafx.tk.Toolkit;
  84 import javafx.scene.Scene;
  85 import javafx.stage.Window;
  86 import com.sun.javafx.logging.PlatformLogger;
  87 import com.sun.javafx.logging.PlatformLogger.Level;
  88 
  89 /**
  90  * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers.
  91  * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds
  92  * and borders. It is designed to support as much of the CSS3 specification for backgrounds
  93  * and borders as is relevant to JavaFX.
  94  * The full specification is available at <a href="http://www.w3.org/TR/2012/CR-css3-background-20120724/">the W3C</a>.
  95  * <p>
  96  * Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside
  97  * these bounds. The content area of a Region is the area which is occupied for the layout of its children.
  98  * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the
  99  * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can
 100  * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region,
 101  * but does not affect the layout bounds.
 102  * <p>
 103  * A Region has a Background, and a Border, although either or both of these might be empty. The Background
 104  * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the
 105  * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and
 106  * zero or more BorderImages. All BackgroundFills are drawn first, followed by BackgroundImages, BorderStrokes,
 107  * and finally BorderImages. The content is drawn above all backgrounds and borders. If a BorderImage is
 108  * present (and loaded all images properly), then no BorderStrokes are actually drawn, although they are
 109  * considered for computing the position of the content area (see the stroke width property of a BorderStroke).
 110  * These semantics are in line with the CSS 3 specification. The purpose of these semantics are to allow an
 111  * application to specify a fallback BorderStroke to be displayed in the case that an ImageStroke fails to
 112  * download or load.
 113  * <p>
 114  * By default a Region appears as a Rectangle. A BackgroundFill radii might cause the Rectangle to appear rounded.
 115  * This affects not only making the visuals look like a rounded rectangle, but it also causes the picking behavior
 116  * of the Region to act like a rounded rectangle, such that locations outside the corner radii are ignored. A
 117  * Region can be made to use any shape, however, by specifing the {@code shape} property. If a shape is specified,
 118  * then all BackgroundFills, BackgroundImages, and BorderStrokes will be applied to the shape. BorderImages are
 119  * not used for Regions which have a shape specified.
 120  * <p>
 121  * A Region with a shape
 122  * <p>
 123  * Although the layout bounds of a Region are not influenced by any Border or Background, the content area
 124  * insets and the picking area of the Region are. The {@code insets} of the Region define the distance
 125  * between the edge of the layout bounds and the edge of the content area. For example, if the Region
 126  * layout bounds are (x=0, y=0, width=200, height=100), and the insets are (top=10, right=20, bottom=30, left=40),
 127  * then the content area bounds will be (x=40, y=10, width=140, height=60). A Region subclass which is laying
 128  * out its children should compute and honor these content area bounds.
 129  * <p>
 130  * By default a Region inherits the layout behavior of its superclass, {@link Parent},
 131  * which means that it will resize any resizable child nodes to their preferred
 132  * size, but will not reposition them.  If an application needs more specific
 133  * layout behavior, then it should use one of the Region subclasses:
 134  * {@link StackPane}, {@link HBox}, {@link VBox}, {@link TilePane}, {@link FlowPane},
 135  * {@link BorderPane}, {@link GridPane}, or {@link AnchorPane}.
 136  * <p>
 137  * To implement a more custom layout, a Region subclass must override
 138  * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, and
 139  * {@link #layoutChildren() layoutChildren}. Note that {@link #layoutChildren() layoutChildren} is called automatically
 140  * by the scene graph while executing a top-down layout pass and it should not be invoked directly by the
 141  * region subclass.
 142  * <p>
 143  * Region subclasses which layout their children will position nodes by setting
 144  * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter
 145  * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for
 146  * adjustments and animation.
 147  * @since JavaFX 2.0
 148  */
 149 public class Region extends Parent {
 150     static {
 151         RegionHelper.setRegionAccessor(new RegionHelper.RegionAccessor() {
 152             @Override
 153             public NGNode doCreatePeer(Node node) {
 154                 return ((Region) node).doCreatePeer();
 155             }
 156 
 157             @Override
 158             public void doUpdatePeer(Node node) {
 159                 ((Region) node).doUpdatePeer();
 160             }
 161 
 162             @Override
 163             public Bounds doComputeLayoutBounds(Node node) {
 164                 return ((Region) node).doComputeLayoutBounds();
 165             }
 166 
 167             @Override
 168             public BaseBounds doComputeGeomBounds(Node node,
 169                     BaseBounds bounds, BaseTransform tx) {
 170                 return ((Region) node).doComputeGeomBounds(bounds, tx);
 171             }
 172 
 173             @Override
 174             public boolean doComputeContains(Node node, double localX, double localY) {
 175                 return ((Region) node).doComputeContains(localX, localY);
 176             }
 177 
 178             @Override
 179             public void doNotifyLayoutBoundsChanged(Node node) {
 180                 ((Region) node).doNotifyLayoutBoundsChanged();
 181             }
 182 
 183             @Override
 184             public void doPickNodeLocal(Node node, PickRay localPickRay,
 185                     PickResultChooser result) {
 186                 ((Region) node).doPickNodeLocal(localPickRay, result);
 187             }
 188         });
 189     }
 190 
 191     /**
 192      * Sentinel value which can be passed to a region's
 193      * {@link #setMinWidth(double) setMinWidth},
 194      * {@link #setMinHeight(double) setMinHeight},
 195      * {@link #setMaxWidth(double) setMaxWidth} or
 196      * {@link #setMaxHeight(double) setMaxHeight}
 197      * methods to indicate that the preferred dimension should be used for that max and/or min constraint.
 198      */
 199     public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY;
 200 
 201     /**
 202      * Sentinel value which can be passed to a region's
 203      * {@link #setMinWidth(double) setMinWidth},
 204      * {@link #setMinHeight(double) setMinHeight},
 205      * {@link #setPrefWidth(double) setPrefWidth},
 206      * {@link #setPrefHeight(double) setPrefHeight},
 207      * {@link #setMaxWidth(double) setMaxWidth},
 208      * {@link #setMaxHeight(double) setMaxHeight} methods
 209      * to reset the region's size constraint back to it's intrinsic size returned
 210      * by {@link #computeMinWidth(double) computeMinWidth}, {@link #computeMinHeight(double) computeMinHeight},
 211      * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight},
 212      * {@link #computeMaxWidth(double) computeMaxWidth}, or {@link #computeMaxHeight(double) computeMaxHeight}.
 213      */
 214     public static final double USE_COMPUTED_SIZE = -1;
 215 
 216     static Vec2d TEMP_VEC2D = new Vec2d();
 217 
 218     /***************************************************************************
 219      *                                                                         *
 220      * Static convenience methods for layout                                   *
 221      *                                                                         *
 222      **************************************************************************/
 223 
 224     /**
 225      * Computes the value based on the given min and max values. We encode in this
 226      * method the logic surrounding various edge cases, such as when the min is
 227      * specified as greater than the max, or the max less than the min, or a pref
 228      * value that exceeds either the max or min in their extremes.
 229      * <p/>
 230      * If the min is greater than the max, then we want to make sure the returned
 231      * value is the min. In other words, in such a case, the min becomes the only
 232      * acceptable return value.
 233      * <p/>
 234      * If the min and max values are well ordered, and the pref is less than the min
 235      * then the min is returned. Likewise, if the values are well ordered and the
 236      * pref is greater than the max, then the max is returned. If the pref lies
 237      * between the min and the max, then the pref is returned.
 238      *
 239      *
 240      * @param min The minimum bound
 241      * @param pref The value to be clamped between the min and max
 242      * @param max the maximum bound
 243      * @return the size bounded by min, pref, and max.
 244      */
 245     static double boundedSize(double min, double pref, double max) {
 246         double a = pref >= min ? pref : min;
 247         double b = min >= max ? min : max;
 248         return a <= b ? a : b;
 249     }
 250 
 251     double adjustWidthByMargin(double width, Insets margin) {
 252         if (margin == null || margin == Insets.EMPTY) {
 253             return width;
 254         }
 255         boolean isSnapToPixel = isSnapToPixel();
 256         return width - snapSpaceX(margin.getLeft(), isSnapToPixel) - snapSpaceX(margin.getRight(), isSnapToPixel);
 257     }
 258 
 259     double adjustHeightByMargin(double height, Insets margin) {
 260         if (margin == null || margin == Insets.EMPTY) {
 261             return height;
 262         }
 263         boolean isSnapToPixel = isSnapToPixel();
 264         return height - snapSpaceY(margin.getTop(), isSnapToPixel) - snapSpaceY(margin.getBottom(), isSnapToPixel);
 265     }
 266 
 267     private static double getSnapScaleX(Node n) {
 268         return _getSnapScaleXimpl(n.getScene());
 269     }
 270     private static double _getSnapScaleXimpl(Scene scene) {
 271         if (scene == null) return 1.0;
 272         Window window = scene.getWindow();
 273         if (window == null) return 1.0;
 274         return window.getRenderScaleX();
 275     }
 276 
 277     private static double getSnapScaleY(Node n) {
 278         return _getSnapScaleYimpl(n.getScene());
 279     }
 280     private static double _getSnapScaleYimpl(Scene scene) {
 281         if (scene == null) return 1.0;
 282         Window window = scene.getWindow();
 283         if (window == null) return 1.0;
 284         return window.getRenderScaleY();
 285     }
 286 
 287     private double getSnapScaleX() {
 288         return _getSnapScaleXimpl(getScene());
 289     }
 290 
 291     private double getSnapScaleY() {
 292         return _getSnapScaleYimpl(getScene());
 293     }
 294 
 295     private static double scaledRound(double value, double scale) {
 296         return Math.round(value * scale) / scale;
 297     }
 298 
 299     private static double scaledFloor(double value, double scale) {
 300         return Math.floor(value * scale) / scale;
 301     }
 302 
 303     private static double scaledCeil(double value, double scale) {
 304         return Math.ceil(value * scale) / scale;
 305     }
 306 
 307     /**
 308      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 309      * the value is simply returned. This method will surely be JIT'd under normal
 310      * circumstances, however on an interpreter it would be better to inline this
 311      * method. However the use of Math.round here, and Math.ceil in snapSize is
 312      * not obvious, and so for code maintenance this logic is pulled out into
 313      * a separate method.
 314      *
 315      * @param value The value that needs to be snapped
 316      * @param snapToPixel Whether to snap to pixel
 317      * @return value either as passed in or rounded based on snapToPixel
 318      */
 319     private double snapSpaceX(double value, boolean snapToPixel) {
 320         return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
 321     }
 322     private double snapSpaceY(double value, boolean snapToPixel) {
 323         return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
 324     }
 325 
 326     private static double snapSpace(double value, boolean snapToPixel, double snapScale) {
 327         return snapToPixel ? scaledRound(value, snapScale) : value;
 328     }
 329 
 330     /**
 331      * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise,
 332      * the value is simply returned.
 333      *
 334      * @param value The value that needs to be snapped
 335      * @param snapToPixel Whether to snap to pixel
 336      * @return value either as passed in or ceil'd based on snapToPixel
 337      */
 338     private double snapSizeX(double value, boolean snapToPixel) {
 339         return snapToPixel ? scaledCeil(value, getSnapScaleX()) : value;
 340     }
 341     private double snapSizeY(double value, boolean snapToPixel) {
 342         return snapToPixel ? scaledCeil(value, getSnapScaleY()) : value;
 343     }
 344 
 345     private static double snapSize(double value, boolean snapToPixel, double snapScale) {
 346         return snapToPixel ? scaledCeil(value, snapScale) : value;
 347     }
 348 
 349     /**
 350      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
 351      * the value is simply returned.
 352      *
 353      * @param value The value that needs to be snapped
 354      * @param snapToPixel Whether to snap to pixel
 355      * @return value either as passed in or rounded based on snapToPixel
 356      */
 357     private double snapPositionX(double value, boolean snapToPixel) {
 358         return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
 359     }
 360     private double snapPositionY(double value, boolean snapToPixel) {
 361         return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
 362     }
 363 
 364     private static double snapPosition(double value, boolean snapToPixel, double snapScale) {
 365         return snapToPixel ? scaledRound(value, snapScale) : value;
 366     }
 367 
 368     private double snapPortionX(double value, boolean snapToPixel) {
 369         if (!snapToPixel || value == 0) return value;
 370         double s = getSnapScaleX();
 371         value *= s;
 372         if (value > 0) {
 373             value = Math.max(1, Math.floor(value));
 374         } else {
 375             value = Math.min(-1, Math.ceil(value));
 376         }
 377         return value / s;
 378     }
 379     private double snapPortionY(double value, boolean snapToPixel) {
 380         if (!snapToPixel || value == 0) return value;
 381         double s = getSnapScaleY();
 382         value *= s;
 383         if (value > 0) {
 384             value = Math.max(1, Math.floor(value));
 385         } else {
 386             value = Math.min(-1, Math.ceil(value));
 387         }
 388         return value / s;
 389     }
 390 
 391     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 392                                         Function<Integer, Double> positionToWidth,
 393                                         double areaHeight, boolean fillHeight) {
 394         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, isSnapToPixel());
 395     }
 396 
 397     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 398             Function<Integer, Double> positionToWidth,
 399             double areaHeight, boolean fillHeight, boolean snapToPixel) {
 400         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight,
 401                 getMinBaselineComplement(children), snapToPixel);
 402     }
 403 
 404     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 405                                  Function<Integer, Double> positionToWidth,
 406                                  double areaHeight, final boolean fillHeight, double minComplement) {
 407         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 408     }
 409 
 410     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 411             Function<Integer, Double> positionToWidth,
 412             double areaHeight, final boolean fillHeight, double minComplement, boolean snapToPixel) {
 413         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, t -> fillHeight, minComplement, snapToPixel);
 414     }
 415 
 416     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 417                                  Function<Integer, Double> positionToWidth,
 418                                  double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement) {
 419         return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel());
 420     }
 421 
 422     /**
 423      * Returns the baseline offset of provided children, with respect to the minimum complement, computed
 424      * by {@link #getMinBaselineComplement(java.util.List)} from the same set of children.
 425      * @param children the children with baseline alignment
 426      * @param margins their margins (callback)
 427      * @param positionToWidth callback for children widths (can return -1 if no bias is used)
 428      * @param areaHeight height of the area to layout in
 429      * @param fillHeight callback to specify children that has fillHeight constraint
 430      * @param minComplement minimum complement
 431      */
 432     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
 433             Function<Integer, Double> positionToWidth,
 434             double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement, boolean snapToPixel) {
 435         double b = 0;
 436         double snapScaleV = 0.0;
 437         for (int i = 0;i < children.size(); ++i) {
 438             Node n = children.get(i);
 439             // Note: all children should be coming from the same parent so they should all have the same snapScale
 440             if (snapToPixel && i == 0) snapScaleV = getSnapScaleY(n.getParent());
 441             Insets margin = margins.call(n);
 442             double top = margin != null ? snapSpace(margin.getTop(), snapToPixel, snapScaleV) : 0;
 443             double bottom = (margin != null ? snapSpace(margin.getBottom(), snapToPixel, snapScaleV) : 0);
 444             final double bo = n.getBaselineOffset();
 445             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
 446                 double alt = -1;
 447                 if (n.getContentBias() == Orientation.HORIZONTAL) {
 448                     alt = positionToWidth.apply(i);
 449                 }
 450                 if (fillHeight.apply(i)) {
 451                     // If the children fills it's height, than it's "preferred" height is the area without the complement and insets
 452                     b = Math.max(b, top + boundedSize(n.minHeight(alt), areaHeight - minComplement - top - bottom,
 453                             n.maxHeight(alt)));
 454                 } else {
 455                     // Otherwise, we must use the area without complement and insets as a maximum for the Node
 456                     b = Math.max(b, top + boundedSize(n.minHeight(alt), n.prefHeight(alt),
 457                             Math.min(n.maxHeight(alt), areaHeight - minComplement - top - bottom)));
 458                 }
 459             } else {
 460                 b = Math.max(b, top + bo);
 461             }
 462         }
 463         return b;
 464     }
 465 
 466     /**
 467      * Return the minimum complement of baseline
 468      * @param children
 469      * @return
 470      */
 471     static double getMinBaselineComplement(List<Node> children) {
 472         return getBaselineComplement(children, true, false);
 473     }
 474 
 475     /**
 476      * Return the preferred complement of baseline
 477      * @param children
 478      * @return
 479      */
 480     static double getPrefBaselineComplement(List<Node> children) {
 481         return getBaselineComplement(children, false, false);
 482     }
 483 
 484     /**
 485      * Return the maximal complement of baseline
 486      * @param children
 487      * @return
 488      */
 489     static double getMaxBaselineComplement(List<Node> children) {
 490         return getBaselineComplement(children, false, true);
 491     }
 492 
 493     private static double getBaselineComplement(List<Node> children, boolean min, boolean max) {
 494         double bc = 0;
 495         for (Node n : children) {
 496             final double bo = n.getBaselineOffset();
 497             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
 498                 continue;
 499             }
 500             if (n.isResizable()) {
 501                 bc = Math.max(bc, (min ? n.minHeight(-1) : max ? n.maxHeight(-1) : n.prefHeight(-1)) - bo);
 502             } else {
 503                 bc = Math.max(bc, n.getLayoutBounds().getHeight() - bo);
 504             }
 505         }
 506         return bc;
 507     }
 508 
 509 
 510     static double computeXOffset(double width, double contentWidth, HPos hpos) {
 511         switch(hpos) {
 512             case LEFT:
 513                 return 0;
 514             case CENTER:
 515                 return (width - contentWidth) / 2;
 516             case RIGHT:
 517                 return width - contentWidth;
 518             default:
 519                 throw new AssertionError("Unhandled hPos");
 520         }
 521     }
 522 
 523     static double computeYOffset(double height, double contentHeight, VPos vpos) {
 524         switch(vpos) {
 525             case BASELINE:
 526             case TOP:
 527                 return 0;
 528             case CENTER:
 529                 return (height - contentHeight) / 2;
 530             case BOTTOM:
 531                 return height - contentHeight;
 532             default:
 533                 throw new AssertionError("Unhandled vPos");
 534         }
 535     }
 536 
 537     static double[] createDoubleArray(int length, double value) {
 538         double[] array = new double[length];
 539         for (int i = 0; i < length; i++) {
 540             array[i] = value;
 541         }
 542         return array;
 543     }
 544 
 545     /***************************************************************************
 546      *                                                                         *
 547      * Constructors                                                            *
 548      *                                                                         *
 549      **************************************************************************/
 550 
 551     /**
 552      * At the time that a Background or Border is set on a Region, we inspect any
 553      * BackgroundImage or BorderImage objects, to see if the Image backing them
 554      * is background loading and not yet complete, or is animated. In such cases
 555      * we attach the imageChangeListener to them, so that when the image finishes,
 556      * the Region will be redrawn. If the particular image object is not animating
 557      * (but was just background loading), then we also remove the listener.
 558      * We also are sure to remove this listener from any old BackgroundImage or
 559      * BorderImage images in the background and border property invalidation code.
 560      */
 561     private InvalidationListener imageChangeListener = observable -> {
 562         final ReadOnlyObjectPropertyBase imageProperty = (ReadOnlyObjectPropertyBase) observable;
 563         final Image image = (Image) imageProperty.getBean();
 564         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 565         if (image.getProgress() == 1 && !acc.isAnimation(image)) {
 566             // We can go ahead and remove the listener since loading is done.
 567             removeImageListener(image);
 568         }
 569         // Cause the region to repaint
 570         NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
 571     };
 572 
 573     {
 574         // To initialize the class helper at the begining each constructor of this class
 575         RegionHelper.initHelper(this);
 576     }
 577 
 578     /**
 579      * Creates a new Region with an empty Background and and empty Border. The
 580      * Region defaults to having pickOnBounds set to true, meaning that any pick
 581      * (mouse picking or touch picking etc) that occurs within the bounds in local
 582      * of the Region will return true, regardless of whether the Region is filled
 583      * or transparent.
 584      */
 585     public Region() {
 586         super();
 587         setPickOnBounds(true);
 588     }
 589 
 590     /***************************************************************************
 591      *                                                                         *
 592      * Region properties                                                       *
 593      *                                                                         *
 594      **************************************************************************/
 595 
 596     /**
 597      * Defines whether this region adjusts position, spacing, and size values of
 598      * its children to pixel boundaries. This defaults to true, which is generally
 599      * the expected behavior in order to have crisp user interfaces. A value of
 600      * false will allow for fractional alignment, which may lead to "fuzzy"
 601      * looking borders.
 602      */
 603     private BooleanProperty snapToPixel;
 604     /**
 605      * I'm using a super-lazy property pattern here, so as to only create the
 606      * property object when needed for listeners or when being set from CSS,
 607      * but also making sure that we only call requestParentLayout in the case
 608      * that the snapToPixel value has actually changed, whether set via the setter
 609      * or set via the property object.
 610      */
 611     private boolean _snapToPixel = true;
 612     public final boolean isSnapToPixel() { return _snapToPixel; }
 613     public final void setSnapToPixel(boolean value) {
 614         if (snapToPixel == null) {
 615             if (_snapToPixel != value) {
 616                 _snapToPixel = value;
 617                 updateSnappedInsets();
 618                 requestParentLayout();
 619             }
 620         } else {
 621             snapToPixel.set(value);
 622         }
 623     }
 624     public final BooleanProperty snapToPixelProperty() {
 625         // Note: snapToPixel is virtually never set, and never listened to.
 626         // Because of this, it works reasonably well as a lazy property,
 627         // since this logic is just about never going to be called.
 628         if (snapToPixel == null) {
 629             snapToPixel = new StyleableBooleanProperty(_snapToPixel) {
 630                 @Override public Object getBean() { return Region.this; }
 631                 @Override public String getName() { return "snapToPixel"; }
 632                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
 633                     return StyleableProperties.SNAP_TO_PIXEL;
 634                 }
 635                 @Override public void invalidated() {
 636                     boolean value = get();
 637                     if (_snapToPixel != value) {
 638                         _snapToPixel = value;
 639                         updateSnappedInsets();
 640                         requestParentLayout();
 641                     }
 642                 }
 643             };
 644         }
 645         return snapToPixel;
 646     }
 647 
 648     /**
 649      * The top, right, bottom, and left padding around the region's content.
 650      * This space will be included in the calculation of the region's
 651      * minimum and preferred sizes. By default padding is Insets.EMPTY. Setting the
 652      * value to null should be avoided.
 653      */
 654     private ObjectProperty<Insets> padding = new StyleableObjectProperty<Insets>(Insets.EMPTY) {
 655         // Keep track of the last valid value for the sake of
 656         // rollback in case padding is set to null. Note that
 657         // Richard really does not like this pattern because
 658         // it essentially means that binding the padding property
 659         // is not possible since a binding expression could very
 660         // easily produce an intermediate null value.
 661 
 662         // Also note that because padding is set virtually everywhere via CSS, and CSS
 663         // requires a property object in order to set it, there is no benefit to having
 664         // lazy initialization here.
 665 
 666         private Insets lastValidValue = Insets.EMPTY;
 667 
 668         @Override public Object getBean() { return Region.this; }
 669         @Override public String getName() { return "padding"; }
 670         @Override public CssMetaData<Region, Insets> getCssMetaData() {
 671             return StyleableProperties.PADDING;
 672         }
 673         @Override public void invalidated() {
 674             final Insets newValue = get();
 675             if (newValue == null) {
 676                 // rollback
 677                 if (isBound()) {
 678                     unbind();
 679                 }
 680                 set(lastValidValue);
 681                 throw new NullPointerException("cannot set padding to null");
 682             } else if (!newValue.equals(lastValidValue)) {
 683                 lastValidValue = newValue;
 684                 insets.fireValueChanged();
 685             }
 686         }
 687     };
 688     public final void setPadding(Insets value) { padding.set(value); }
 689     public final Insets getPadding() { return padding.get(); }
 690     public final ObjectProperty<Insets> paddingProperty() { return padding; }
 691 
 692     /**
 693      * The background of the Region, which is made up of zero or more BackgroundFills, and
 694      * zero or more BackgroundImages. It is possible for a Background to be empty, where it
 695      * has neither fills nor images, and is semantically equivalent to null.
 696      * @since JavaFX 8.0
 697      */
 698     private final ObjectProperty<Background> background = new StyleableObjectProperty<Background>(null) {
 699         private Background old = null;
 700         @Override public Object getBean() { return Region.this; }
 701         @Override public String getName() { return "background"; }
 702         @Override public CssMetaData<Region, Background> getCssMetaData() {
 703             return StyleableProperties.BACKGROUND;
 704         }
 705 
 706         @Override protected void invalidated() {
 707             final Background b = get();
 708             if(old != null ? !old.equals(b) : b != null) {
 709                 // They are different! Both cannot be null
 710                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 711                     // We have determined that the outsets of these two different background
 712                     // objects is different, and therefore the bounds have changed.
 713                     NodeHelper.geomChanged(Region.this);
 714                     insets.fireValueChanged();
 715                 }
 716 
 717                 // If the Background is made up of any BackgroundImage objects, then we must
 718                 // inspect the images of those BackgroundImage objects to see if they are still
 719                 // being loaded in the background or if they are animated. If so, then we need
 720                 // to attach a listener, so that when the image finishes loading or changes,
 721                 // we can repaint the region.
 722                 if (b != null) {
 723                     for (BackgroundImage i : b.getImages()) {
 724                         final Image image = i.image;
 725                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 726                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 727                             addImageListener(image);
 728                         }
 729                     }
 730                 }
 731 
 732                 // And we must remove this listener from any old images
 733                 if (old != null) {
 734                     for (BackgroundImage i : old.getImages()) {
 735                         removeImageListener(i.image);
 736                     }
 737                 }
 738 
 739                 // No matter what, the fill has changed, so we have to update it
 740                 NodeHelper.markDirty(Region.this, DirtyBits.SHAPE_FILL);
 741                 cornersValid = false;
 742                 old = b;
 743             }
 744         }
 745     };
 746     public final void setBackground(Background value) { background.set(value); }
 747     public final Background getBackground() { return background.get(); }
 748     public final ObjectProperty<Background> backgroundProperty() { return background; }
 749 
 750     /**
 751      * The border of the Region, which is made up of zero or more BorderStrokes, and
 752      * zero or more BorderImages. It is possible for a Border to be empty, where it
 753      * has neither strokes nor images, and is semantically equivalent to null.
 754      * @since JavaFX 8.0
 755      */
 756     private final ObjectProperty<Border> border = new StyleableObjectProperty<Border>(null) {
 757         private Border old = null;
 758         @Override public Object getBean() { return Region.this; }
 759         @Override public String getName() { return "border"; }
 760         @Override public CssMetaData<Region, Border> getCssMetaData() {
 761             return StyleableProperties.BORDER;
 762         }
 763         @Override protected void invalidated() {
 764             final Border b = get();
 765             if(old != null ? !old.equals(b) : b != null) {
 766                 // They are different! Both cannot be null
 767                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 768                     // We have determined that the outsets of these two different border
 769                     // objects is different, and therefore the bounds have changed.
 770                     NodeHelper.geomChanged(Region.this);
 771                 }
 772                 if (old == null || b == null || !old.getInsets().equals(b.getInsets())) {
 773                     insets.fireValueChanged();
 774                 }
 775 
 776                 // If the Border is made up of any BorderImage objects, then we must
 777                 // inspect the images of those BorderImage objects to see if they are still
 778                 // being loaded in the background or if they are animated. If so, then we need
 779                 // to attach a listener, so that when the image finishes loading or changes,
 780                 // we can repaint the region.
 781                 if (b != null) {
 782                     for (BorderImage i : b.getImages()) {
 783                         final Image image = i.image;
 784                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 785                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 786                             addImageListener(image);
 787                         }
 788                     }
 789                 }
 790 
 791                 // And we must remove this listener from any old images
 792                 if (old != null) {
 793                     for (BorderImage i : old.getImages()) {
 794                         removeImageListener(i.image);
 795                     }
 796                 }
 797 
 798                 // No matter what, the fill has changed, so we have to update it
 799                 NodeHelper.markDirty(Region.this, DirtyBits.SHAPE_STROKE);
 800                 cornersValid = false;
 801                 old = b;
 802             }
 803         }
 804     };
 805     public final void setBorder(Border value) { border.set(value); }
 806     public final Border getBorder() { return border.get(); }
 807     public final ObjectProperty<Border> borderProperty() { return border; }
 808 
 809     /**
 810      * Adds the imageChangeListener to this image. This method was broken out and made
 811      * package private for testing purposes.
 812      *
 813      * @param image a non-null image
 814      */
 815     void addImageListener(Image image) {
 816         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 817         acc.getImageProperty(image).addListener(imageChangeListener);
 818     }
 819 
 820     /**
 821      * Removes the imageChangeListener from this image. This method was broken out and made
 822      * package private for testing purposes.
 823      *
 824      * @param image a non-null image
 825      */
 826     void removeImageListener(Image image) {
 827         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 828         acc.getImageProperty(image).removeListener(imageChangeListener);
 829     }
 830 
 831     /**
 832      * Defines the area of the region within which completely opaque pixels
 833      * are drawn. This is used for various performance optimizations.
 834      * The pixels within this area MUST BE fully opaque, or rendering
 835      * artifacts will result. It is the responsibility of the application, either
 836      * via code or via CSS, to ensure that the opaqueInsets is correct for
 837      * a Region based on the backgrounds and borders of that region. The values
 838      * for each of the insets must be real numbers, not NaN or Infinity. If
 839      * no known insets exist, then the opaqueInsets should be set to null.
 840      * @return the opaque insets property
 841      * @since JavaFX 8.0
 842      */
 843     public final ObjectProperty<Insets> opaqueInsetsProperty() {
 844         if (opaqueInsets == null) {
 845             opaqueInsets = new StyleableObjectProperty<Insets>() {
 846                 @Override public Object getBean() { return Region.this; }
 847                 @Override public String getName() { return "opaqueInsets"; }
 848                 @Override public CssMetaData<Region, Insets> getCssMetaData() {
 849                     return StyleableProperties.OPAQUE_INSETS;
 850                 }
 851                 @Override protected void invalidated() {
 852                     // This causes the background to be updated, which
 853                     // is the code block where we also compute the opaque insets
 854                     // since updating the background is super fast even when
 855                     // nothing has changed.
 856                     NodeHelper.markDirty(Region.this, DirtyBits.SHAPE_FILL);
 857                 }
 858             };
 859         }
 860         return opaqueInsets;
 861     }
 862     private ObjectProperty<Insets> opaqueInsets;
 863     public final void setOpaqueInsets(Insets value) { opaqueInsetsProperty().set(value); }
 864     public final Insets getOpaqueInsets() { return opaqueInsets == null ? null : opaqueInsets.get(); }
 865 
 866     /**
 867      * The insets of the Region define the distance from the edge of the region (its layout bounds,
 868      * or (0, 0, width, height)) to the edge of the content area. All child nodes should be laid out
 869      * within the content area. The insets are computed based on the Border which has been specified,
 870      * if any, and also the padding.
 871      * @since JavaFX 8.0
 872      */
 873     private final InsetsProperty insets = new InsetsProperty();
 874     public final Insets getInsets() { return insets.get(); }
 875     public final ReadOnlyObjectProperty<Insets> insetsProperty() { return insets; }
 876     private final class InsetsProperty extends ReadOnlyObjectProperty<Insets> {
 877         private Insets cache = null;
 878         private ExpressionHelper<Insets> helper = null;
 879 
 880         @Override public Object getBean() { return Region.this; }
 881         @Override public String getName() { return "insets"; }
 882 
 883         @Override public void addListener(InvalidationListener listener) {
 884             helper = ExpressionHelper.addListener(helper, this, listener);
 885         }
 886 
 887         @Override public void removeListener(InvalidationListener listener) {
 888             helper = ExpressionHelper.removeListener(helper, listener);
 889         }
 890 
 891         @Override public void addListener(ChangeListener<? super Insets> listener) {
 892             helper = ExpressionHelper.addListener(helper, this, listener);
 893         }
 894 
 895         @Override public void removeListener(ChangeListener<? super Insets> listener) {
 896             helper = ExpressionHelper.removeListener(helper, listener);
 897         }
 898 
 899         void fireValueChanged() {
 900             cache = null;
 901             updateSnappedInsets();
 902             requestLayout();
 903             ExpressionHelper.fireValueChangedEvent(helper);
 904         }
 905 
 906         @Override public Insets get() {
 907             // If a shape is specified, then we don't really care whether there are any borders
 908             // specified, since borders of shapes do not contribute to the insets.
 909             if (_shape != null) return getPadding();
 910 
 911             // If there is no border or the border has no insets itself, then the only thing
 912             // affecting the insets is the padding, so we can just return it directly.
 913             final Border b = getBorder();
 914             if (b == null || Insets.EMPTY.equals(b.getInsets())) {
 915                 return getPadding();
 916             }
 917 
 918             // There is a border with some non-zero insets and we do not have a _shape, so we need
 919             // to take the border's insets into account
 920             if (cache == null) {
 921                 // Combine the padding and the border insets.
 922                 // TODO note that negative border insets were being ignored, but
 923                 // I'm not sure that that made sense or was reasonable, so I have
 924                 // changed it so that we just do simple math.
 925                 // TODO Stroke borders should NOT contribute to the insets. Ensure via tests.
 926                 final Insets borderInsets = b.getInsets();
 927                 final Insets paddingInsets = getPadding();
 928                 cache = new Insets(
 929                         borderInsets.getTop() + paddingInsets.getTop(),
 930                         borderInsets.getRight() + paddingInsets.getRight(),
 931                         borderInsets.getBottom() + paddingInsets.getBottom(),
 932                         borderInsets.getLeft() + paddingInsets.getLeft()
 933                 );
 934             }
 935             return cache;
 936         }
 937     };
 938 
 939     /**
 940      * cached results of snapped insets, this are used a lot during layout so makes sense
 941      * to keep fast access cached copies here.
 942      */
 943     private double snappedTopInset = 0;
 944     private double snappedRightInset = 0;
 945     private double snappedBottomInset = 0;
 946     private double snappedLeftInset = 0;
 947 
 948     /** Called to update the cached snapped insets */
 949     private void updateSnappedInsets() {
 950         final Insets insets = getInsets();
 951         if (_snapToPixel) {
 952             snappedTopInset = Math.ceil(insets.getTop());
 953             snappedRightInset = Math.ceil(insets.getRight());
 954             snappedBottomInset = Math.ceil(insets.getBottom());
 955             snappedLeftInset = Math.ceil(insets.getLeft());
 956         } else {
 957             snappedTopInset = insets.getTop();
 958             snappedRightInset = insets.getRight();
 959             snappedBottomInset = insets.getBottom();
 960             snappedLeftInset = insets.getLeft();
 961         }
 962     }
 963 
 964     /**
 965     * The width of this resizable node.  This property is set by the region's parent
 966     * during layout and may not be set by the application.  If an application
 967     * needs to explicitly control the size of a region, it should override its
 968     * preferred size range by setting the <code>minWidth</code>, <code>prefWidth</code>,
 969     * and <code>maxWidth</code> properties.
 970     */
 971     private ReadOnlyDoubleWrapper width;
 972 
 973     /**
 974      * Because the width is very often set and very often read but only sometimes
 975      * listened to, it is beneficial to use the super-lazy pattern property, where we
 976      * only inflate the property object when widthProperty() is explicitly invoked.
 977      */
 978     private double _width;
 979 
 980     // Note that it is OK for this method to be protected so long as the width
 981     // property is never bound. Only Region could do so because only Region has
 982     // access to a writable property for "width", but since there is now a protected
 983     // set method, it is impossible for Region to ever bind this property.
 984     protected void setWidth(double value) {
 985         if(width == null) {
 986             widthChanged(value);
 987         } else {
 988             width.set(value);
 989         }
 990     }
 991 
 992     private void widthChanged(double value) {
 993         // It is possible that somebody sets the width of the region to a value which
 994         // it previously held. If this is the case, we want to avoid excessive layouts.
 995         // Note that I have biased this for layout over binding, because the widthProperty
 996         // is now going to recompute the width eagerly. The cost of excessive and
 997         // unnecessary bounds changes, however, is relatively high.
 998         if (value != _width) {
 999             _width = value;
1000             cornersValid = false;
1001             boundingBox = null;
1002             NodeHelper.layoutBoundsChanged(this);
1003             NodeHelper.geomChanged(this);
1004             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1005             setNeedsLayout(true);
1006             requestParentLayout();
1007         }
1008     }
1009 
1010     public final double getWidth() { return width == null ? _width : width.get(); }
1011 
1012     public final ReadOnlyDoubleProperty widthProperty() {
1013         if (width == null) {
1014             width = new ReadOnlyDoubleWrapper(_width) {
1015                 @Override protected void invalidated() { widthChanged(get()); }
1016                 @Override public Object getBean() { return Region.this; }
1017                 @Override public String getName() { return "width"; }
1018             };
1019         }
1020         return width.getReadOnlyProperty();
1021     }
1022 
1023     /**
1024      * The height of this resizable node.  This property is set by the region's parent
1025      * during layout and may not be set by the application.  If an application
1026      * needs to explicitly control the size of a region, it should override its
1027      * preferred size range by setting the <code>minHeight</code>, <code>prefHeight</code>,
1028      * and <code>maxHeight</code> properties.
1029      */
1030     private ReadOnlyDoubleWrapper height;
1031 
1032     /**
1033      * Because the height is very often set and very often read but only sometimes
1034      * listened to, it is beneficial to use the super-lazy pattern property, where we
1035      * only inflate the property object when heightProperty() is explicitly invoked.
1036      */
1037     private double _height;
1038 
1039     // Note that it is OK for this method to be protected so long as the height
1040     // property is never bound. Only Region could do so because only Region has
1041     // access to a writable property for "height", but since there is now a protected
1042     // set method, it is impossible for Region to ever bind this property.
1043     protected void setHeight(double value) {
1044         if (height == null) {
1045             heightChanged(value);
1046         } else {
1047             height.set(value);
1048         }
1049     }
1050 
1051     private void heightChanged(double value) {
1052         if (_height != value) {
1053             _height = value;
1054             cornersValid = false;
1055             // It is possible that somebody sets the height of the region to a value which
1056             // it previously held. If this is the case, we want to avoid excessive layouts.
1057             // Note that I have biased this for layout over binding, because the heightProperty
1058             // is now going to recompute the height eagerly. The cost of excessive and
1059             // unnecessary bounds changes, however, is relatively high.
1060             boundingBox = null;
1061             // Note: although NodeHelper.geomChanged will usually also invalidate the
1062             // layout bounds, that is not the case for Regions, and both must
1063             // be called separately.
1064             NodeHelper.geomChanged(this);
1065             NodeHelper.layoutBoundsChanged(this);
1066             // We use "NODE_GEOMETRY" to mean that the bounds have changed and
1067             // need to be sync'd with the render tree
1068             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1069             // Change of the height (or width) won't change the preferred size.
1070             // So we don't need to flush the cache. We should however mark this node
1071             // as needs layout to be internally layouted.
1072             setNeedsLayout(true);
1073             // This call is only needed when this was not called from the parent during the layout.
1074             // Otherwise it would flush the cache of the parent, which is not necessary
1075             requestParentLayout();
1076         }
1077     }
1078 
1079     public final double getHeight() { return height == null ? _height : height.get(); }
1080 
1081     public final ReadOnlyDoubleProperty heightProperty() {
1082         if (height == null) {
1083             height = new ReadOnlyDoubleWrapper(_height) {
1084                 @Override protected void invalidated() { heightChanged(get()); }
1085                 @Override public Object getBean() { return Region.this; }
1086                 @Override public String getName() { return "height"; }
1087             };
1088         }
1089         return height.getReadOnlyProperty();
1090     }
1091 
1092     /**
1093      * This class is reused for the min, pref, and max properties since
1094      * they all performed the same function (to call requestParentLayout).
1095      */
1096     private final class MinPrefMaxProperty extends StyleableDoubleProperty {
1097         private final String name;
1098         private final CssMetaData<? extends Styleable, Number> cssMetaData;
1099 
1100         MinPrefMaxProperty(String name, double initialValue, CssMetaData<? extends Styleable, Number> cssMetaData) {
1101             super(initialValue);
1102             this.name = name;
1103             this.cssMetaData = cssMetaData;
1104         }
1105 
1106         @Override public void invalidated() { requestParentLayout(); }
1107         @Override public Object getBean() { return Region.this; }
1108         @Override public String getName() { return name; }
1109 
1110         @Override
1111         public CssMetaData<? extends Styleable, Number> getCssMetaData() {
1112             return cssMetaData;
1113         }
1114     }
1115 
1116     /**
1117      * Property for overriding the region's computed minimum width.
1118      * This should only be set if the region's internally computed minimum width
1119      * doesn't meet the application's layout needs.
1120      * <p>
1121      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1122      * <code>minWidth(forHeight)</code> will return the region's internally
1123      * computed minimum width.
1124      * <p>
1125      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1126      * <code>minWidth(forHeight)</code> to return the region's preferred width,
1127      * enabling applications to easily restrict the resizability of the region.
1128      */
1129     private DoubleProperty minWidth;
1130     private double _minWidth = USE_COMPUTED_SIZE;
1131     public final void setMinWidth(double value) {
1132         if (minWidth == null) {
1133             _minWidth = value;
1134             requestParentLayout();
1135         } else {
1136             minWidth.set(value);
1137         }
1138     }
1139     public final double getMinWidth() { return minWidth == null ? _minWidth : minWidth.get(); }
1140     public final DoubleProperty minWidthProperty() {
1141         if (minWidth == null) minWidth = new MinPrefMaxProperty("minWidth", _minWidth, StyleableProperties.MIN_WIDTH);
1142         return minWidth;
1143     }
1144 
1145     /**
1146      * Property for overriding the region's computed minimum height.
1147      * This should only be set if the region's internally computed minimum height
1148      * doesn't meet the application's layout needs.
1149      * <p>
1150      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1151      * <code>minHeight(forWidth)</code> will return the region's internally
1152      * computed minimum height.
1153      * <p>
1154      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1155      * <code>minHeight(forWidth)</code> to return the region's preferred height,
1156      * enabling applications to easily restrict the resizability of the region.
1157      *
1158      */
1159     private DoubleProperty minHeight;
1160     private double _minHeight = USE_COMPUTED_SIZE;
1161     public final void setMinHeight(double value) {
1162         if (minHeight == null) {
1163             _minHeight = value;
1164             requestParentLayout();
1165         } else {
1166             minHeight.set(value);
1167         }
1168     }
1169     public final double getMinHeight() { return minHeight == null ? _minHeight : minHeight.get(); }
1170     public final DoubleProperty minHeightProperty() {
1171         if (minHeight == null) minHeight = new MinPrefMaxProperty("minHeight", _minHeight, StyleableProperties.MIN_HEIGHT);
1172         return minHeight;
1173     }
1174 
1175     /**
1176      * Convenience method for overriding the region's computed minimum width and height.
1177      * This should only be called if the region's internally computed minimum size
1178      * doesn't meet the application's layout needs.
1179      *
1180      * @see #setMinWidth
1181      * @see #setMinHeight
1182      * @param minWidth  the override value for minimum width
1183      * @param minHeight the override value for minimum height
1184      */
1185     public void setMinSize(double minWidth, double minHeight) {
1186         setMinWidth(minWidth);
1187         setMinHeight(minHeight);
1188     }
1189 
1190     /**
1191      * Property for overriding the region's computed preferred width.
1192      * This should only be set if the region's internally computed preferred width
1193      * doesn't meet the application's layout needs.
1194      * <p>
1195      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1196      * <code>getPrefWidth(forHeight)</code> will return the region's internally
1197      * computed preferred width.
1198      */
1199     private DoubleProperty prefWidth;
1200     private double _prefWidth = USE_COMPUTED_SIZE;
1201     public final void setPrefWidth(double value) {
1202         if (prefWidth == null) {
1203             _prefWidth = value;
1204             requestParentLayout();
1205         } else {
1206             prefWidth.set(value);
1207         }
1208     }
1209     public final double getPrefWidth() { return prefWidth == null ? _prefWidth : prefWidth.get(); }
1210     public final DoubleProperty prefWidthProperty() {
1211         if (prefWidth == null) prefWidth = new MinPrefMaxProperty("prefWidth", _prefWidth, StyleableProperties.PREF_WIDTH);
1212         return prefWidth;
1213     }
1214 
1215     /**
1216      * Property for overriding the region's computed preferred height.
1217      * This should only be set if the region's internally computed preferred height
1218      * doesn't meet the application's layout needs.
1219      * <p>
1220      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1221      * <code>getPrefHeight(forWidth)</code> will return the region's internally
1222      * computed preferred width.
1223      */
1224     private DoubleProperty prefHeight;
1225     private double _prefHeight = USE_COMPUTED_SIZE;
1226     public final void setPrefHeight(double value) {
1227         if (prefHeight == null) {
1228             _prefHeight = value;
1229             requestParentLayout();
1230         } else {
1231             prefHeight.set(value);
1232         }
1233     }
1234     public final double getPrefHeight() { return prefHeight == null ? _prefHeight : prefHeight.get(); }
1235     public final DoubleProperty prefHeightProperty() {
1236         if (prefHeight == null) prefHeight = new MinPrefMaxProperty("prefHeight", _prefHeight, StyleableProperties.PREF_HEIGHT);
1237         return prefHeight;
1238     }
1239 
1240     /**
1241      * Convenience method for overriding the region's computed preferred width and height.
1242      * This should only be called if the region's internally computed preferred size
1243      * doesn't meet the application's layout needs.
1244      *
1245      * @see #setPrefWidth
1246      * @see #setPrefHeight
1247      * @param prefWidth the override value for preferred width
1248      * @param prefHeight the override value for preferred height
1249      */
1250     public void setPrefSize(double prefWidth, double prefHeight) {
1251         setPrefWidth(prefWidth);
1252         setPrefHeight(prefHeight);
1253     }
1254 
1255     /**
1256      * Property for overriding the region's computed maximum width.
1257      * This should only be set if the region's internally computed maximum width
1258      * doesn't meet the application's layout needs.
1259      * <p>
1260      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1261      * <code>getMaxWidth(forHeight)</code> will return the region's internally
1262      * computed maximum width.
1263      * <p>
1264      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1265      * <code>getMaxWidth(forHeight)</code> to return the region's preferred width,
1266      * enabling applications to easily restrict the resizability of the region.
1267      */
1268     private DoubleProperty maxWidth;
1269     private double _maxWidth = USE_COMPUTED_SIZE;
1270     public final void setMaxWidth(double value) {
1271         if (maxWidth == null) {
1272             _maxWidth = value;
1273             requestParentLayout();
1274         } else {
1275             maxWidth.set(value);
1276         }
1277     }
1278     public final double getMaxWidth() { return maxWidth == null ? _maxWidth : maxWidth.get(); }
1279     public final DoubleProperty maxWidthProperty() {
1280         if (maxWidth == null) maxWidth = new MinPrefMaxProperty("maxWidth", _maxWidth, StyleableProperties.MAX_WIDTH);
1281         return maxWidth;
1282     }
1283 
1284     /**
1285      * Property for overriding the region's computed maximum height.
1286      * This should only be set if the region's internally computed maximum height
1287      * doesn't meet the application's layout needs.
1288      * <p>
1289      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1290      * <code>getMaxHeight(forWidth)</code> will return the region's internally
1291      * computed maximum height.
1292      * <p>
1293      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1294      * <code>getMaxHeight(forWidth)</code> to return the region's preferred height,
1295      * enabling applications to easily restrict the resizability of the region.
1296      */
1297     private DoubleProperty maxHeight;
1298     private double _maxHeight = USE_COMPUTED_SIZE;
1299     public final void setMaxHeight(double value) {
1300         if (maxHeight == null) {
1301             _maxHeight = value;
1302             requestParentLayout();
1303         } else {
1304             maxHeight.set(value);
1305         }
1306     }
1307     public final double getMaxHeight() { return maxHeight == null ? _maxHeight : maxHeight.get(); }
1308     public final DoubleProperty maxHeightProperty() {
1309         if (maxHeight == null) maxHeight = new MinPrefMaxProperty("maxHeight", _maxHeight, StyleableProperties.MAX_HEIGHT);
1310         return maxHeight;
1311     }
1312 
1313     /**
1314      * Convenience method for overriding the region's computed maximum width and height.
1315      * This should only be called if the region's internally computed maximum size
1316      * doesn't meet the application's layout needs.
1317      *
1318      * @see #setMaxWidth
1319      * @see #setMaxHeight
1320      * @param maxWidth  the override value for maximum width
1321      * @param maxHeight the override value for maximum height
1322      */
1323     public void setMaxSize(double maxWidth, double maxHeight) {
1324         setMaxWidth(maxWidth);
1325         setMaxHeight(maxHeight);
1326     }
1327 
1328     /**
1329      * When specified, the {@code Shape} will cause the region to be
1330      * rendered as the specified shape rather than as a rounded rectangle.
1331      * When null, the Region is rendered as a rounded rectangle. When rendered
1332      * as a Shape, any Background is used to fill the shape, although any
1333      * background insets are ignored as are background radii. Any BorderStrokes
1334      * defined are used for stroking the shape. Any BorderImages are ignored.
1335      *
1336      * @defaultValue null
1337      * @since JavaFX 8.0
1338      */
1339     private ObjectProperty<Shape> shape = null;
1340     private Shape _shape;
1341     public final Shape getShape() { return shape == null ? _shape : shape.get(); }
1342     public final void setShape(Shape value) { shapeProperty().set(value); }
1343     public final ObjectProperty<Shape> shapeProperty() {
1344         if (shape == null) {
1345             shape = new ShapeProperty();
1346         }
1347         return shape;
1348     }
1349 
1350     /**
1351      * An implementation for the ShapeProperty. This is also a ShapeChangeListener.
1352      */
1353     private final class ShapeProperty extends StyleableObjectProperty<Shape> implements Runnable {
1354         @Override public Object getBean() { return Region.this; }
1355         @Override public String getName() { return "shape"; }
1356         @Override public CssMetaData<Region, Shape> getCssMetaData() {
1357             return StyleableProperties.SHAPE;
1358         }
1359         @Override protected void invalidated() {
1360             final Shape value = get();
1361             if (_shape != value) {
1362                 // The shape has changed. We need to add/remove listeners
1363                 if (_shape != null) ShapeHelper.setShapeChangeListener(_shape, null);
1364                 if (value != null) ShapeHelper.setShapeChangeListener(value, this);
1365                 // Invalidate the bounds and such
1366                 run();
1367                 if (_shape == null || value == null) {
1368                     // It either was null before, or is null now. In either case,
1369                     // the result of the insets computation will have changed, and
1370                     // we therefore need to fire that the insets value may have changed.
1371                     insets.fireValueChanged();
1372                 }
1373                 // Update our reference to the old shape
1374                 _shape = value;
1375             }
1376         }
1377 
1378         @Override public void run() {
1379             NodeHelper.geomChanged(Region.this);
1380             NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1381         }
1382     };
1383 
1384     /**
1385      * Specifies whether the shape, if defined, is scaled to match the size of the Region.
1386      * {@code true} means the shape is scaled to fit the size of the Region, {@code false}
1387      * means the shape is at its source size, its positioning depends on the value of
1388      * {@code centerShape}.
1389      *
1390      * @defaultValue true
1391      * @since JavaFX 8.0
1392      */
1393     private BooleanProperty scaleShape = null;
1394     public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); }
1395     public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); }
1396     public final BooleanProperty scaleShapeProperty() {
1397         if (scaleShape == null) {
1398             scaleShape = new StyleableBooleanProperty(true) {
1399                 @Override public Object getBean() { return Region.this; }
1400                 @Override public String getName() { return "scaleShape"; }
1401                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1402                     return StyleableProperties.SCALE_SHAPE;
1403                 }
1404                 @Override public void invalidated() {
1405                     NodeHelper.geomChanged(Region.this);
1406                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1407                 }
1408             };
1409         }
1410         return scaleShape;
1411     }
1412 
1413     /**
1414      * Defines whether the shape is centered within the Region's width or height.
1415      * {@code true} means the shape centered within the Region's width and height,
1416      * {@code false} means the shape is positioned at its source position.
1417      *
1418      * @defaultValue true
1419      * @since JavaFX 8.0
1420      */
1421     private BooleanProperty centerShape = null;
1422     public final void setCenterShape(boolean value) { centerShapeProperty().set(value); }
1423     public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); }
1424     public final BooleanProperty centerShapeProperty() {
1425         if (centerShape == null) {
1426             centerShape = new StyleableBooleanProperty(true) {
1427                 @Override public Object getBean() { return Region.this; }
1428                 @Override public String getName() { return "centerShape"; }
1429                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1430                     return StyleableProperties.POSITION_SHAPE;
1431                 }
1432                 @Override public void invalidated() {
1433                     NodeHelper.geomChanged(Region.this);
1434                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1435                 }
1436             };
1437         }
1438         return centerShape;
1439     }
1440 
1441     /**
1442      * Defines a hint to the system indicating that the Shape used to define the region's
1443      * background is stable and would benefit from caching.
1444      *
1445      * @defaultValue true
1446      * @since JavaFX 8.0
1447      */
1448     private BooleanProperty cacheShape = null;
1449     public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); }
1450     public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); }
1451     public final BooleanProperty cacheShapeProperty() {
1452         if (cacheShape == null) {
1453             cacheShape = new StyleableBooleanProperty(true) {
1454                 @Override public Object getBean() { return Region.this; }
1455                 @Override public String getName() { return "cacheShape"; }
1456                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1457                     return StyleableProperties.CACHE_SHAPE;
1458                 }
1459             };
1460         }
1461         return cacheShape;
1462     }
1463 
1464     /***************************************************************************
1465      *                                                                         *
1466      * Layout                                                                  *
1467      *                                                                         *
1468      **************************************************************************/
1469 
1470     /**
1471      * Returns <code>true</code> since all Regions are resizable.
1472      * @return whether this node can be resized by its parent during layout
1473      */
1474     @Override public boolean isResizable() {
1475         return true;
1476     }
1477 
1478     /**
1479      * Invoked by the region's parent during layout to set the region's
1480      * width and height.  <b>Applications should not invoke this method directly</b>.
1481      * If an application needs to directly set the size of the region, it should
1482      * override its size constraints by calling <code>setMinSize()</code>,
1483      *  <code>setPrefSize()</code>, or <code>setMaxSize()</code> and it's parent
1484      * will honor those overrides during layout.
1485      *
1486      * @param width the target layout bounds width
1487      * @param height the target layout bounds height
1488      */
1489     @Override public void resize(double width, double height) {
1490         setWidth(width);
1491         setHeight(height);
1492         PlatformLogger logger = Logging.getLayoutLogger();
1493         if (logger.isLoggable(Level.FINER)) {
1494             logger.finer(this.toString() + " resized to " + width + " x " + height);
1495         }
1496     }
1497 
1498     /**
1499      * Called during layout to determine the minimum width for this node.
1500      * Returns the value from <code>computeMinWidth(forHeight)</code> unless
1501      * the application overrode the minimum width by setting the minWidth property.
1502      *
1503      * @see #setMinWidth(double)
1504      * @return the minimum width that this node should be resized to during layout
1505      */
1506     @Override public final double minWidth(double height) {
1507         final double override = getMinWidth();
1508         if (override == USE_COMPUTED_SIZE) {
1509             return super.minWidth(height);
1510         } else if (override == USE_PREF_SIZE) {
1511             return prefWidth(height);
1512         }
1513         return Double.isNaN(override) || override < 0 ? 0 : override;
1514     }
1515 
1516     /**
1517      * Called during layout to determine the minimum height for this node.
1518      * Returns the value from <code>computeMinHeight(forWidth)</code> unless
1519      * the application overrode the minimum height by setting the minHeight property.
1520      *
1521      * @see #setMinHeight
1522      * @return the minimum height that this node should be resized to during layout
1523      */
1524     @Override public final double minHeight(double width) {
1525         final double override = getMinHeight();
1526         if (override == USE_COMPUTED_SIZE) {
1527             return super.minHeight(width);
1528         } else if (override == USE_PREF_SIZE) {
1529             return prefHeight(width);
1530         }
1531         return Double.isNaN(override) || override < 0 ? 0 : override;
1532     }
1533 
1534     /**
1535      * Called during layout to determine the preferred width for this node.
1536      * Returns the value from <code>computePrefWidth(forHeight)</code> unless
1537      * the application overrode the preferred width by setting the prefWidth property.
1538      *
1539      * @see #setPrefWidth
1540      * @return the preferred width that this node should be resized to during layout
1541      */
1542     @Override public final double prefWidth(double height) {
1543         final double override = getPrefWidth();
1544         if (override == USE_COMPUTED_SIZE) {
1545             return super.prefWidth(height);
1546         }
1547         return Double.isNaN(override) || override < 0 ? 0 : override;
1548     }
1549 
1550     /**
1551      * Called during layout to determine the preferred height for this node.
1552      * Returns the value from <code>computePrefHeight(forWidth)</code> unless
1553      * the application overrode the preferred height by setting the prefHeight property.
1554      *
1555      * @see #setPrefHeight
1556      * @return the preferred height that this node should be resized to during layout
1557      */
1558     @Override public final double prefHeight(double width) {
1559         final double override = getPrefHeight();
1560         if (override == USE_COMPUTED_SIZE) {
1561             return super.prefHeight(width);
1562         }
1563         return Double.isNaN(override) || override < 0 ? 0 : override;
1564     }
1565 
1566     /**
1567      * Called during layout to determine the maximum width for this node.
1568      * Returns the value from <code>computeMaxWidth(forHeight)</code> unless
1569      * the application overrode the maximum width by setting the maxWidth property.
1570      *
1571      * @see #setMaxWidth
1572      * @return the maximum width that this node should be resized to during layout
1573      */
1574     @Override public final double maxWidth(double height) {
1575         final double override = getMaxWidth();
1576         if (override == USE_COMPUTED_SIZE) {
1577             return computeMaxWidth(height);
1578         } else if (override == USE_PREF_SIZE) {
1579             return prefWidth(height);
1580         }
1581         return Double.isNaN(override) || override < 0 ? 0 : override;
1582     }
1583 
1584     /**
1585      * Called during layout to determine the maximum height for this node.
1586      * Returns the value from <code>computeMaxHeight(forWidth)</code> unless
1587      * the application overrode the maximum height by setting the maxHeight property.
1588      *
1589      * @see #setMaxHeight
1590      * @return the maximum height that this node should be resized to during layout
1591      */
1592     @Override public final double maxHeight(double width) {
1593         final double override = getMaxHeight();
1594         if (override == USE_COMPUTED_SIZE) {
1595             return computeMaxHeight(width);
1596         } else if (override == USE_PREF_SIZE) {
1597             return prefHeight(width);
1598         }
1599         return Double.isNaN(override) || override < 0 ? 0 : override;
1600     }
1601 
1602     /**
1603      * Computes the minimum width of this region.
1604      * Returns the sum of the left and right insets by default.
1605      * region subclasses should override this method to return an appropriate
1606      * value based on their content and layout strategy.  If the subclass
1607      * doesn't have a VERTICAL content bias, then the height parameter can be
1608      * ignored.
1609      *
1610      * @return the computed minimum width of this region
1611      */
1612     @Override protected double computeMinWidth(double height) {
1613         return getInsets().getLeft() + getInsets().getRight();
1614     }
1615 
1616     /**
1617      * Computes the minimum height of this region.
1618      * Returns the sum of the top and bottom insets by default.
1619      * Region subclasses should override this method to return an appropriate
1620      * value based on their content and layout strategy.  If the subclass
1621      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1622      * ignored.
1623      *
1624      * @return the computed minimum height for this region
1625      */
1626     @Override protected double computeMinHeight(double width) {
1627         return getInsets().getTop() + getInsets().getBottom();
1628     }
1629 
1630     /**
1631      * Computes the preferred width of this region for the given height.
1632      * Region subclasses should override this method to return an appropriate
1633      * value based on their content and layout strategy.  If the subclass
1634      * doesn't have a VERTICAL content bias, then the height parameter can be
1635      * ignored.
1636      *
1637      * @return the computed preferred width for this region
1638      */
1639     @Override protected double computePrefWidth(double height) {
1640         final double w = super.computePrefWidth(height);
1641         return getInsets().getLeft() + w + getInsets().getRight();
1642     }
1643 
1644     /**
1645      * Computes the preferred height of this region for the given width;
1646      * Region subclasses should override this method to return an appropriate
1647      * value based on their content and layout strategy.  If the subclass
1648      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1649      * ignored.
1650      *
1651      * @return the computed preferred height for this region
1652      */
1653     @Override protected double computePrefHeight(double width) {
1654         final double h = super.computePrefHeight(width);
1655         return getInsets().getTop() + h + getInsets().getBottom();
1656     }
1657 
1658     /**
1659      * Computes the maximum width for this region.
1660      * Returns Double.MAX_VALUE by default.
1661      * Region subclasses may override this method to return an different
1662      * value based on their content and layout strategy.  If the subclass
1663      * doesn't have a VERTICAL content bias, then the height parameter can be
1664      * ignored.
1665      *
1666      * @param height The height of the Region, in case this value might dictate
1667      * the maximum width
1668      * @return the computed maximum width for this region
1669      */
1670     protected double computeMaxWidth(double height) {
1671         return Double.MAX_VALUE;
1672     }
1673 
1674     /**
1675      * Computes the maximum height of this region.
1676      * Returns Double.MAX_VALUE by default.
1677      * Region subclasses may override this method to return a different
1678      * value based on their content and layout strategy.  If the subclass
1679      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1680      * ignored.
1681      *
1682      * @param width The width of the Region, in case this value might dictate
1683      * the maximum height
1684      * @return the computed maximum height for this region
1685      */
1686     protected double computeMaxHeight(double width) {
1687         return Double.MAX_VALUE;
1688     }
1689 
1690     /**
1691      * If this region's snapToPixel property is false, this method returns the
1692      * same value, else it tries to return a value rounded to the nearest
1693      * pixel, but since there is no indication if the value is a vertical
1694      * or horizontal measurement then it may be snapped to the wrong pixel
1695      * size metric on screens with different horizontal and vertical scales.
1696      * @param value the space value to be snapped
1697      * @return value rounded to nearest pixel
1698      * @deprecated replaced by {@code snapSpaceX()} and {@code snapSpaceY()}
1699      */
1700     @Deprecated(since="9")
1701     protected double snapSpace(double value) {
1702         return snapSpaceX(value, isSnapToPixel());
1703     }
1704 
1705     /**
1706      * If this region's snapToPixel property is true, returns a value rounded
1707      * to the nearest pixel in the horizontal direction, else returns the
1708      * same value.
1709      * @param value the space value to be snapped
1710      * @return value rounded to nearest pixel
1711      * @since 9
1712      */
1713     public double snapSpaceX(double value) {
1714         return snapSpaceX(value, isSnapToPixel());
1715     }
1716 
1717     /**
1718      * If this region's snapToPixel property is true, returns a value rounded
1719      * to the nearest pixel in the vertical direction, else returns the
1720      * same value.
1721      * @param value the space value to be snapped
1722      * @return value rounded to nearest pixel
1723      * @since 9
1724      */
1725     public double snapSpaceY(double value) {
1726         return snapSpaceY(value, isSnapToPixel());
1727     }
1728 
1729     /**
1730      * If this region's snapToPixel property is false, this method returns the
1731      * same value, else it tries to return a value ceiled to the nearest
1732      * pixel, but since there is no indication if the value is a vertical
1733      * or horizontal measurement then it may be snapped to the wrong pixel
1734      * size metric on screens with different horizontal and vertical scales.
1735      * @param value the size value to be snapped
1736      * @return value ceiled to nearest pixel
1737      * @deprecated replaced by {@code snapSizeX()} and {@code snapSizeY()}
1738      */
1739     @Deprecated(since="9")
1740     protected double snapSize(double value) {
1741         return snapSizeX(value, isSnapToPixel());
1742     }
1743 
1744     /**
1745      * If this region's snapToPixel property is true, returns a value ceiled
1746      * to the nearest pixel in the horizontal direction, else returns the
1747      * same value.
1748      * @param value the size value to be snapped
1749      * @return value ceiled to nearest pixel
1750      * @since 9
1751      */
1752     public double snapSizeX(double value) {
1753         return snapSizeX(value, isSnapToPixel());
1754     }
1755 
1756     /**
1757      * If this region's snapToPixel property is true, returns a value ceiled
1758      * to the nearest pixel in the vertical direction, else returns the
1759      * same value.
1760      * @param value the size value to be snapped
1761      * @return value ceiled to nearest pixel
1762      * @since 9
1763      */
1764     public double snapSizeY(double value) {
1765         return snapSizeY(value, isSnapToPixel());
1766     }
1767 
1768     /**
1769      * If this region's snapToPixel property is false, this method returns the
1770      * same value, else it tries to return a value rounded to the nearest
1771      * pixel, but since there is no indication if the value is a vertical
1772      * or horizontal measurement then it may be snapped to the wrong pixel
1773      * size metric on screens with different horizontal and vertical scales.
1774      * @param value the position value to be snapped
1775      * @return value rounded to nearest pixel
1776      * @deprecated replaced by {@code snapPositionX()} and {@code snapPositionY()}
1777      */
1778     @Deprecated(since="9")
1779     protected double snapPosition(double value) {
1780         return snapPositionX(value, isSnapToPixel());
1781     }
1782 
1783     /**
1784      * If this region's snapToPixel property is true, returns a value rounded
1785      * to the nearest pixel in the horizontal direction, else returns the
1786      * same value.
1787      * @param value the position value to be snapped
1788      * @return value rounded to nearest pixel
1789      * @since 9
1790      */
1791     public double snapPositionX(double value) {
1792         return snapPositionX(value, isSnapToPixel());
1793     }
1794 
1795     /**
1796      * If this region's snapToPixel property is true, returns a value rounded
1797      * to the nearest pixel in the vertical direction, else returns the
1798      * same value.
1799      * @param value the position value to be snapped
1800      * @return value rounded to nearest pixel
1801      * @since 9
1802      */
1803     public double snapPositionY(double value) {
1804         return snapPositionY(value, isSnapToPixel());
1805     }
1806 
1807     double snapPortionX(double value) {
1808         return snapPortionX(value, isSnapToPixel());
1809     }
1810     double snapPortionY(double value) {
1811         return snapPortionY(value, isSnapToPixel());
1812     }
1813 
1814 
1815     /**
1816      * Utility method to get the top inset which includes padding and border
1817      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1818      *
1819      * @since JavaFX 8.0
1820      * @return Rounded up insets top
1821      */
1822     public final double snappedTopInset() {
1823         return snappedTopInset;
1824     }
1825 
1826     /**
1827      * Utility method to get the bottom inset which includes padding and border
1828      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1829      *
1830      * @since JavaFX 8.0
1831      * @return Rounded up insets bottom
1832      */
1833     public final double snappedBottomInset() {
1834         return snappedBottomInset;
1835     }
1836 
1837     /**
1838      * Utility method to get the left inset which includes padding and border
1839      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1840      *
1841      * @since JavaFX 8.0
1842      * @return Rounded up insets left
1843      */
1844     public final double snappedLeftInset() {
1845         return snappedLeftInset;
1846     }
1847 
1848     /**
1849      * Utility method to get the right inset which includes padding and border
1850      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1851      *
1852      * @since JavaFX 8.0
1853      * @return Rounded up insets right
1854      */
1855     public final double snappedRightInset() {
1856         return snappedRightInset;
1857     }
1858 
1859 
1860     double computeChildMinAreaWidth(Node child, Insets margin) {
1861         return computeChildMinAreaWidth(child, -1, margin, -1, false);
1862     }
1863 
1864     double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1865         final boolean snap = isSnapToPixel();
1866         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1867         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1868         double alt = -1;
1869         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1870             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1871             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1872             double bo = child.getBaselineOffset();
1873             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1874                     height - top - bottom - baselineComplement :
1875                      height - top - bottom;
1876             if (fillHeight) {
1877                 alt = snapSizeY(boundedSize(
1878                         child.minHeight(-1), contentHeight,
1879                         child.maxHeight(-1)));
1880             } else {
1881                 alt = snapSizeY(boundedSize(
1882                         child.minHeight(-1),
1883                         child.prefHeight(-1),
1884                         Math.min(child.maxHeight(-1), contentHeight)));
1885             }
1886         }
1887         return left + snapSizeX(child.minWidth(alt)) + right;
1888     }
1889 
1890     double computeChildMinAreaHeight(Node child, Insets margin) {
1891         return computeChildMinAreaHeight(child, -1, margin, -1);
1892     }
1893 
1894     double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
1895         final boolean snap = isSnapToPixel();
1896         double top =margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1897         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1898 
1899         double alt = -1;
1900         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1901             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1902             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1903             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1904                     child.maxWidth(-1));
1905         }
1906 
1907         // For explanation, see computeChildPrefAreaHeight
1908         if (minBaselineComplement != -1) {
1909             double baseline = child.getBaselineOffset();
1910             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1911                 return top + snapSizeY(child.minHeight(alt)) + bottom
1912                         + minBaselineComplement;
1913             } else {
1914                 return baseline + minBaselineComplement;
1915             }
1916         } else {
1917             return top + snapSizeY(child.minHeight(alt)) + bottom;
1918         }
1919     }
1920 
1921     double computeChildPrefAreaWidth(Node child, Insets margin) {
1922         return computeChildPrefAreaWidth(child, -1, margin, -1, false);
1923     }
1924 
1925     double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1926         final boolean snap = isSnapToPixel();
1927         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1928         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1929         double alt = -1;
1930         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1931             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1932             double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1933             double bo = child.getBaselineOffset();
1934             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1935                     height - top - bottom - baselineComplement :
1936                      height - top - bottom;
1937             if (fillHeight) {
1938                 alt = snapSizeY(boundedSize(
1939                         child.minHeight(-1), contentHeight,
1940                         child.maxHeight(-1)));
1941             } else {
1942                 alt = snapSizeY(boundedSize(
1943                         child.minHeight(-1),
1944                         child.prefHeight(-1),
1945                         Math.min(child.maxHeight(-1), contentHeight)));
1946             }
1947         }
1948         return left + snapSizeX(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
1949     }
1950 
1951     double computeChildPrefAreaHeight(Node child, Insets margin) {
1952         return computeChildPrefAreaHeight(child, -1, margin, -1);
1953     }
1954 
1955     double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
1956         final boolean snap = isSnapToPixel();
1957         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1958         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1959 
1960         double alt = -1;
1961         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1962             double left = margin != null ? snapSpaceX(margin.getLeft(), snap) : 0;
1963             double right = margin != null ? snapSpaceX(margin.getRight(), snap) : 0;
1964             alt = snapSizeX(boundedSize(
1965                     child.minWidth(-1), width != -1 ? width - left - right
1966                     : child.prefWidth(-1), child.maxWidth(-1)));
1967         }
1968 
1969         if (prefBaselineComplement != -1) {
1970             double baseline = child.getBaselineOffset();
1971             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1972                 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
1973                 // the preferred complement to it
1974                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
1975                         + prefBaselineComplement;
1976             } else {
1977                 // For all other Nodes, it's just their baseline and the complement.
1978                 // Note that the complement already contain the Node's preferred (or fixed) height
1979                 return top + baseline + prefBaselineComplement + bottom;
1980             }
1981         } else {
1982             return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
1983         }
1984     }
1985 
1986     double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1987         double max = child.maxWidth(-1);
1988         if (max == Double.MAX_VALUE) {
1989             return max;
1990         }
1991         final boolean snap = isSnapToPixel();
1992         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1993         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1994         double alt = -1;
1995         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1996             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1997             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1998             double bo = child.getBaselineOffset();
1999             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
2000                     height - top - bottom - baselineComplement :
2001                      height - top - bottom;
2002             if (fillHeight) {
2003                 alt = snapSizeY(boundedSize(
2004                         child.minHeight(-1), contentHeight,
2005                         child.maxHeight(-1)));
2006             } else {
2007                 alt = snapSizeY(boundedSize(
2008                         child.minHeight(-1),
2009                         child.prefHeight(-1),
2010                         Math.min(child.maxHeight(-1), contentHeight)));
2011             }
2012             max = child.maxWidth(alt);
2013         }
2014         // if min > max, min wins, so still need to call boundedSize()
2015         return left + snapSizeX(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
2016     }
2017 
2018     double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
2019         double max = child.maxHeight(-1);
2020         if (max == Double.MAX_VALUE) {
2021             return max;
2022         }
2023 
2024         final boolean snap = isSnapToPixel();
2025         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
2026         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
2027         double alt = -1;
2028         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
2029             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
2030             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
2031             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
2032                 child.minWidth(-1));
2033             max = child.maxHeight(alt);
2034         }
2035         // For explanation, see computeChildPrefAreaHeight
2036         if (maxBaselineComplement != -1) {
2037             double baseline = child.getBaselineOffset();
2038             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2039                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
2040                         + maxBaselineComplement;
2041             } else {
2042                 return top + baseline + maxBaselineComplement + bottom;
2043             }
2044         } else {
2045             // if min > max, min wins, so still need to call boundedSize()
2046             return top + snapSizeY(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
2047         }
2048     }
2049 
2050     /* Max of children's minimum area widths */
2051 
2052     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins) {
2053         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, true);
2054     }
2055 
2056     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins, double height, boolean fillHeight) {
2057         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, true);
2058     }
2059 
2060     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight) {
2061         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, true);
2062     }
2063 
2064     /* Max of children's minimum area heights */
2065 
2066     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {
2067         return getMaxAreaHeight(children, margins, null, valignment, true);
2068     }
2069 
2070     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment, double width) {
2071         return getMaxAreaHeight(children, margins, new double[] { width }, valignment, true);
2072     }
2073 
2074     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) {
2075         return getMaxAreaHeight(children, childMargins, childWidths, valignment, true);
2076     }
2077 
2078     /* Max of children's pref area widths */
2079 
2080     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins) {
2081         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, false);
2082     }
2083 
2084     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins, double height,
2085             boolean fillHeight) {
2086         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, false);
2087     }
2088 
2089     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> childMargins,
2090             double childHeights[], boolean fillHeight) {
2091         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, false);
2092     }
2093 
2094     /* Max of children's pref area heights */
2095 
2096     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {
2097         return getMaxAreaHeight(children, margins, null, valignment, false);
2098     }
2099 
2100     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, double width, VPos valignment) {
2101         return getMaxAreaHeight(children, margins, new double[] { width }, valignment, false);
2102     }
2103 
2104     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) {
2105         return getMaxAreaHeight(children, childMargins, childWidths, valignment, false);
2106     }
2107 
2108     /**
2109      * Returns the size of a Node that should be placed in an area of the specified size,
2110      * bounded in it's min/max size, respecting bias.
2111      *
2112      * @param node the node
2113      * @param areaWidth the width of the bounding area where the node is going to be placed
2114      * @param areaHeight the height of the bounding area where the node is going to be placed
2115      * @param fillWidth if Node should try to fill the area width
2116      * @param fillHeight if Node should try to fill the area height
2117      * @param result Vec2d object for the result or null if new one should be created
2118      * @return Vec2d object with width(x parameter) and height (y parameter)
2119      */
2120     static Vec2d boundedNodeSizeWithBias(Node node, double areaWidth, double areaHeight,
2121             boolean fillWidth, boolean fillHeight, Vec2d result) {
2122         if (result == null) {
2123             result = new Vec2d();
2124         }
2125 
2126         Orientation bias = node.getContentBias();
2127 
2128         double childWidth = 0;
2129         double childHeight = 0;
2130 
2131         if (bias == null) {
2132             childWidth = boundedSize(
2133                     node.minWidth(-1), fillWidth ? areaWidth
2134                     : Math.min(areaWidth, node.prefWidth(-1)),
2135                     node.maxWidth(-1));
2136             childHeight = boundedSize(
2137                     node.minHeight(-1), fillHeight ? areaHeight
2138                     : Math.min(areaHeight, node.prefHeight(-1)),
2139                     node.maxHeight(-1));
2140 
2141         } else if (bias == Orientation.HORIZONTAL) {
2142             childWidth = boundedSize(
2143                     node.minWidth(-1), fillWidth ? areaWidth
2144                     : Math.min(areaWidth, node.prefWidth(-1)),
2145                     node.maxWidth(-1));
2146             childHeight = boundedSize(
2147                     node.minHeight(childWidth), fillHeight ? areaHeight
2148                     : Math.min(areaHeight, node.prefHeight(childWidth)),
2149                     node.maxHeight(childWidth));
2150 
2151         } else { // bias == VERTICAL
2152             childHeight = boundedSize(
2153                     node.minHeight(-1), fillHeight ? areaHeight
2154                     : Math.min(areaHeight, node.prefHeight(-1)),
2155                     node.maxHeight(-1));
2156             childWidth = boundedSize(
2157                     node.minWidth(childHeight), fillWidth ? areaWidth
2158                     : Math.min(areaWidth, node.prefWidth(childHeight)),
2159                     node.maxWidth(childHeight));
2160         }
2161 
2162         result.set(childWidth, childHeight);
2163         return result;
2164     }
2165 
2166     /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */
2167     private double getMaxAreaHeight(List<Node> children, Callback<Node,Insets> childMargins,  double childWidths[], VPos valignment, boolean minimum) {
2168         final double singleChildWidth = childWidths == null ? -1 : childWidths.length == 1 ? childWidths[0] : Double.NaN;
2169         if (valignment == VPos.BASELINE) {
2170             double maxAbove = 0;
2171             double maxBelow = 0;
2172             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2173                 final Node child = children.get(i);
2174                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2175                 Insets margin = childMargins.call(child);
2176                 final double top = margin != null? snapSpaceY(margin.getTop()) : 0;
2177                 final double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
2178                 final double baseline = child.getBaselineOffset();
2179 
2180                 final double childHeight = minimum? snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth));
2181                 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2182                     maxAbove = Math.max(maxAbove, childHeight + top);
2183                 } else {
2184                     maxAbove = Math.max(maxAbove, baseline + top);
2185                     maxBelow = Math.max(maxBelow,
2186                             snapSpaceY(minimum?snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth))) -
2187                             baseline + bottom);
2188                 }
2189             }
2190             return maxAbove + maxBelow; //remind(aim): ceil this value?
2191         } else {
2192             double max = 0;
2193             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2194                 final Node child = children.get(i);
2195                 Insets margin = childMargins.call(child);
2196                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2197                 max = Math.max(max, minimum?
2198                     computeChildMinAreaHeight(child, -1, margin, childWidth) :
2199                         computeChildPrefAreaHeight(child, -1, margin, childWidth));
2200             }
2201             return max;
2202         }
2203     }
2204 
2205     /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */
2206     private double getMaxAreaWidth(List<javafx.scene.Node> children,
2207             Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight, boolean minimum) {
2208         final double singleChildHeight = childHeights == null ? -1 : childHeights.length == 1 ? childHeights[0] : Double.NaN;
2209 
2210         double max = 0;
2211         for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2212             final Node child = children.get(i);
2213             final Insets margin = childMargins.call(child);
2214             final double childHeight = Double.isNaN(singleChildHeight) ? childHeights[i] : singleChildHeight;
2215             max = Math.max(max, minimum?
2216                 computeChildMinAreaWidth(children.get(i), -1, margin, childHeight, fillHeight) :
2217                     computeChildPrefAreaWidth(child, -1, margin, childHeight, fillHeight));
2218         }
2219         return max;
2220     }
2221 
2222     /**
2223      * Utility method which positions the child within an area of this
2224      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2225      * with a baseline offset relative to that area.
2226      * <p>
2227      * This function does <i>not</i> resize the node and uses the node's layout bounds
2228      * width and height to determine how it should be positioned within the area.
2229      * <p>
2230      * If the vertical alignment is {@code VPos.BASELINE} then it
2231      * will position the node so that its own baseline aligns with the passed in
2232      * {@code baselineOffset},  otherwise the baseline parameter is ignored.
2233      * <p>
2234      * If {@code snapToPixel} is {@code true} for this region, then the x/y position
2235      * values will be rounded to their nearest pixel boundaries.
2236      *
2237      * @param child the child being positioned within this region
2238      * @param areaX the horizontal offset of the layout area relative to this region
2239      * @param areaY the vertical offset of the layout area relative to this region
2240      * @param areaWidth  the width of the layout area
2241      * @param areaHeight the height of the layout area
2242      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2243      * @param halignment the horizontal alignment for the child within the area
2244      * @param valignment the vertical alignment for the child within the area
2245      *
2246      */
2247     protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2248                                double areaBaselineOffset, HPos halignment, VPos valignment) {
2249         positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2250                 Insets.EMPTY, halignment, valignment, isSnapToPixel());
2251     }
2252 
2253     /**
2254      * Utility method which positions the child within an area of this
2255      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2256      * with a baseline offset relative to that area.
2257      * <p>
2258      * This function does <i>not</i> resize the node and uses the node's layout bounds
2259      * width and height to determine how it should be positioned within the area.
2260      * <p>
2261      * If the vertical alignment is {@code VPos.BASELINE} then it
2262      * will position the node so that its own baseline aligns with the passed in
2263      * {@code baselineOffset},  otherwise the baseline parameter is ignored.
2264      * <p>
2265      * If {@code snapToPixel} is {@code true} for this region, then the x/y position
2266      * values will be rounded to their nearest pixel boundaries.
2267      * <p>
2268      * If {@code margin} is non-null, then that space will be allocated around the
2269      * child within the layout area.  margin may be null.
2270      *
2271      * @param child the child being positioned within this region
2272      * @param areaX the horizontal offset of the layout area relative to this region
2273      * @param areaY the vertical offset of the layout area relative to this region
2274      * @param areaWidth  the width of the layout area
2275      * @param areaHeight the height of the layout area
2276      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2277      * @param margin the margin of space to be allocated around the child
2278      * @param halignment the horizontal alignment for the child within the area
2279      * @param valignment the vertical alignment for the child within the area
2280      * @param isSnapToPixel whether to snap size and position to pixels
2281      *
2282      * @since JavaFX 8.0
2283      */
2284     public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2285                                double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
2286         Insets childMargin = margin != null? margin : Insets.EMPTY;
2287         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2288         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2289 
2290         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2291                 snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY),
2292                 snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX),
2293                 snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY),
2294                 snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX),
2295                 halignment, valignment, isSnapToPixel);
2296     }
2297 
2298     /**
2299      * Utility method which lays out the child within an area of this
2300      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2301      * with a baseline offset relative to that area.
2302      * <p>
2303      * If the child is resizable, this method will resize it to fill the specified
2304      * area unless the node's maximum size prevents it.  If the node's maximum
2305      * size preference is less than the area size, the maximum size will be used.
2306      * If node's maximum is greater than the area size, then the node will be
2307      * resized to fit within the area, unless its minimum size prevents it.
2308      * <p>
2309      * If the child has a non-null contentBias, then this method will use it when
2310      * resizing the child.  If the contentBias is horizontal, it will set its width
2311      * first to the area's width (up to the child's max width limit) and then pass
2312      * that value to compute the child's height.  If child's contentBias is vertical,
2313      * then it will set its height to the area height (up to child's max height limit)
2314      * and pass that height to compute the child's width.  If the child's contentBias
2315      * is null, then it's width and height have no dependencies on each other.
2316      * <p>
2317      * If the child is not resizable (Shape, Group, etc) then it will only be
2318      * positioned and not resized.
2319      * <p>
2320      * If the child's resulting size differs from the area's size (either
2321      * because it was not resizable or it's sizing preferences prevented it), then
2322      * this function will align the node relative to the area using horizontal and
2323      * vertical alignment values.
2324      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2325      * with the area baseline offset parameter, otherwise the baseline parameter
2326      * is ignored.
2327      * <p>
2328      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2329      * values will be rounded to their nearest pixel boundaries and the
2330      * width/height values will be ceiled to the next pixel boundary.
2331      *
2332      * @param child the child being positioned within this region
2333      * @param areaX the horizontal offset of the layout area relative to this region
2334      * @param areaY the vertical offset of the layout area relative to this region
2335      * @param areaWidth  the width of the layout area
2336      * @param areaHeight the height of the layout area
2337      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2338      * @param halignment the horizontal alignment for the child within the area
2339      * @param valignment the vertical alignment for the child within the area
2340      *
2341      */
2342     protected void layoutInArea(Node child, double areaX, double areaY,
2343                                double areaWidth, double areaHeight,
2344                                double areaBaselineOffset,
2345                                HPos halignment, VPos valignment) {
2346         layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2347                 Insets.EMPTY, halignment, valignment);
2348     }
2349 
2350     /**
2351      * Utility method which lays out the child within an area of this
2352      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2353      * with a baseline offset relative to that area.
2354      * <p>
2355      * If the child is resizable, this method will resize it to fill the specified
2356      * area unless the node's maximum size prevents it.  If the node's maximum
2357      * size preference is less than the area size, the maximum size will be used.
2358      * If node's maximum is greater than the area size, then the node will be
2359      * resized to fit within the area, unless its minimum size prevents it.
2360      * <p>
2361      * If the child has a non-null contentBias, then this method will use it when
2362      * resizing the child.  If the contentBias is horizontal, it will set its width
2363      * first to the area's width (up to the child's max width limit) and then pass
2364      * that value to compute the child's height.  If child's contentBias is vertical,
2365      * then it will set its height to the area height (up to child's max height limit)
2366      * and pass that height to compute the child's width.  If the child's contentBias
2367      * is null, then it's width and height have no dependencies on each other.
2368      * <p>
2369      * If the child is not resizable (Shape, Group, etc) then it will only be
2370      * positioned and not resized.
2371      * <p>
2372      * If the child's resulting size differs from the area's size (either
2373      * because it was not resizable or it's sizing preferences prevented it), then
2374      * this function will align the node relative to the area using horizontal and
2375      * vertical alignment values.
2376      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2377      * with the area baseline offset parameter, otherwise the baseline parameter
2378      * is ignored.
2379      * <p>
2380      * If {@code margin} is non-null, then that space will be allocated around the
2381      * child within the layout area.  margin may be null.
2382      * <p>
2383      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2384      * values will be rounded to their nearest pixel boundaries and the
2385      * width/height values will be ceiled to the next pixel boundary.
2386      *
2387      * @param child the child being positioned within this region
2388      * @param areaX the horizontal offset of the layout area relative to this region
2389      * @param areaY the vertical offset of the layout area relative to this region
2390      * @param areaWidth  the width of the layout area
2391      * @param areaHeight the height of the layout area
2392      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2393      * @param margin the margin of space to be allocated around the child
2394      * @param halignment the horizontal alignment for the child within the area
2395      * @param valignment the vertical alignment for the child within the area
2396      */
2397     protected void layoutInArea(Node child, double areaX, double areaY,
2398                                double areaWidth, double areaHeight,
2399                                double areaBaselineOffset,
2400                                Insets margin,
2401                                HPos halignment, VPos valignment) {
2402         layoutInArea(child, areaX, areaY, areaWidth, areaHeight,
2403                 areaBaselineOffset, margin, true, true, halignment, valignment);
2404     }
2405 
2406     /**
2407      * Utility method which lays out the child within an area of this
2408      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2409      * with a baseline offset relative to that area.
2410      * <p>
2411      * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
2412      * to determine whether to resize it to fill the area or keep the child at its
2413      * preferred dimension.  If fillWidth/fillHeight are true, then this method
2414      * will only resize the child up to its max size limits.  If the node's maximum
2415      * size preference is less than the area size, the maximum size will be used.
2416      * If node's maximum is greater than the area size, then the node will be
2417      * resized to fit within the area, unless its minimum size prevents it.
2418      * <p>
2419      * If the child has a non-null contentBias, then this method will use it when
2420      * resizing the child.  If the contentBias is horizontal, it will set its width
2421      * first and then pass that value to compute the child's height.  If child's
2422      * contentBias is vertical, then it will set its height first
2423      * and pass that value to compute the child's width.  If the child's contentBias
2424      * is null, then it's width and height have no dependencies on each other.
2425      * <p>
2426      * If the child is not resizable (Shape, Group, etc) then it will only be
2427      * positioned and not resized.
2428      * <p>
2429      * If the child's resulting size differs from the area's size (either
2430      * because it was not resizable or it's sizing preferences prevented it), then
2431      * this function will align the node relative to the area using horizontal and
2432      * vertical alignment values.
2433      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2434      * with the area baseline offset parameter, otherwise the baseline parameter
2435      * is ignored.
2436      * <p>
2437      * If {@code margin} is non-null, then that space will be allocated around the
2438      * child within the layout area.  margin may be null.
2439      * <p>
2440      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2441      * values will be rounded to their nearest pixel boundaries and the
2442      * width/height values will be ceiled to the next pixel boundary.
2443      *
2444      * @param child the child being positioned within this region
2445      * @param areaX the horizontal offset of the layout area relative to this region
2446      * @param areaY the vertical offset of the layout area relative to this region
2447      * @param areaWidth  the width of the layout area
2448      * @param areaHeight the height of the layout area
2449      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2450      * @param margin the margin of space to be allocated around the child
2451      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2452      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2453      * @param halignment the horizontal alignment for the child within the area
2454      * @param valignment the vertical alignment for the child within the area
2455      */
2456     protected void layoutInArea(Node child, double areaX, double areaY,
2457                                double areaWidth, double areaHeight,
2458                                double areaBaselineOffset,
2459                                Insets margin, boolean fillWidth, boolean fillHeight,
2460                                HPos halignment, VPos valignment) {
2461         layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, isSnapToPixel());
2462     }
2463 
2464     /**
2465      * Utility method which lays out the child within an area of it's
2466      * parent defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2467      * with a baseline offset relative to that area.
2468      * <p>
2469      * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
2470      * to determine whether to resize it to fill the area or keep the child at its
2471      * preferred dimension.  If fillWidth/fillHeight are true, then this method
2472      * will only resize the child up to its max size limits.  If the node's maximum
2473      * size preference is less than the area size, the maximum size will be used.
2474      * If node's maximum is greater than the area size, then the node will be
2475      * resized to fit within the area, unless its minimum size prevents it.
2476      * <p>
2477      * If the child has a non-null contentBias, then this method will use it when
2478      * resizing the child.  If the contentBias is horizontal, it will set its width
2479      * first and then pass that value to compute the child's height.  If child's
2480      * contentBias is vertical, then it will set its height first
2481      * and pass that value to compute the child's width.  If the child's contentBias
2482      * is null, then it's width and height have no dependencies on each other.
2483      * <p>
2484      * If the child is not resizable (Shape, Group, etc) then it will only be
2485      * positioned and not resized.
2486      * <p>
2487      * If the child's resulting size differs from the area's size (either
2488      * because it was not resizable or it's sizing preferences prevented it), then
2489      * this function will align the node relative to the area using horizontal and
2490      * vertical alignment values.
2491      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2492      * with the area baseline offset parameter, otherwise the baseline parameter
2493      * is ignored.
2494      * <p>
2495      * If {@code margin} is non-null, then that space will be allocated around the
2496      * child within the layout area.  margin may be null.
2497      * <p>
2498      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2499      * values will be rounded to their nearest pixel boundaries and the
2500      * width/height values will be ceiled to the next pixel boundary.
2501      *
2502      * @param child the child being positioned within this region
2503      * @param areaX the horizontal offset of the layout area relative to this region
2504      * @param areaY the vertical offset of the layout area relative to this region
2505      * @param areaWidth  the width of the layout area
2506      * @param areaHeight the height of the layout area
2507      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2508      * @param margin the margin of space to be allocated around the child
2509      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2510      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2511      * @param halignment the horizontal alignment for the child within the area
2512      * @param valignment the vertical alignment for the child within the area
2513      * @param isSnapToPixel whether to snap size and position to pixels
2514      * @since JavaFX 8.0
2515      */
2516     public static void layoutInArea(Node child, double areaX, double areaY,
2517                                double areaWidth, double areaHeight,
2518                                double areaBaselineOffset,
2519                                Insets margin, boolean fillWidth, boolean fillHeight,
2520                                HPos halignment, VPos valignment, boolean isSnapToPixel) {
2521 
2522         Insets childMargin = margin != null ? margin : Insets.EMPTY;
2523         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2524         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2525 
2526         double top = snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY);
2527         double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY);
2528         double left = snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX);
2529         double right = snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX);
2530 
2531         if (valignment == VPos.BASELINE) {
2532             double bo = child.getBaselineOffset();
2533             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2534                 if (child.isResizable()) {
2535                     // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
2536                     // be resized to this area
2537                     bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel, snapScaleY);
2538                 } else {
2539                     top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel, snapScaleY);
2540                 }
2541             } else {
2542                 top = snapSpace(areaBaselineOffset - bo, isSnapToPixel, snapScaleY);
2543             }
2544         }
2545 
2546 
2547         if (child.isResizable()) {
2548             Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
2549                     fillWidth, fillHeight, TEMP_VEC2D);
2550             child.resize(snapSize(size.x, isSnapToPixel, snapScaleX),
2551                          snapSize(size.y, isSnapToPixel, snapScaleX));
2552         }
2553         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2554                 top, right, bottom, left, halignment, valignment, isSnapToPixel);
2555     }
2556 
2557     private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2558                           double areaBaselineOffset,
2559                           double topMargin, double rightMargin, double bottomMargin, double leftMargin,
2560                           HPos hpos, VPos vpos, boolean isSnapToPixel) {
2561         final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
2562                                                      child.getLayoutBounds().getWidth(), hpos);
2563         final double yoffset;
2564         if (vpos == VPos.BASELINE) {
2565             double bo = child.getBaselineOffset();
2566             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2567                 // We already know the layout bounds at this stage, so we can use them
2568                 yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
2569             } else {
2570                 yoffset = areaBaselineOffset - bo;
2571             }
2572         } else {
2573             yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
2574                                          child.getLayoutBounds().getHeight(), vpos);
2575         }
2576         double x = areaX + xoffset;
2577         double y = areaY + yoffset;
2578         if (isSnapToPixel) {
2579             x = snapPosition(x, true, getSnapScaleX(child));
2580             y = snapPosition(y, true, getSnapScaleY(child));
2581         }
2582 
2583         child.relocate(x,y);
2584     }
2585 
2586      /**************************************************************************
2587      *                                                                         *
2588      * PG Implementation                                                       *
2589      *                                                                         *
2590      **************************************************************************/
2591 
2592     /*
2593      * Note: This method MUST only be called via its accessor method.
2594      */
2595     private void doUpdatePeer() {
2596         // TODO I think we have a bug, where if you create a Region with an Image that hasn't
2597         // been loaded, we have no listeners on that image so as to cause a pulse & repaint
2598         // to happen once the image is loaded. We just assume the image has been loaded
2599         // (since when the image is created using new Image(url) or CSS it happens eagerly).
2600         if (_shape != null) NodeHelper.syncPeer(_shape);
2601         NGRegion pg = NodeHelper.getPeer(this);
2602 
2603         if (!cornersValid) {
2604             validateCorners();
2605         }
2606 
2607         final boolean sizeChanged = NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY);
2608         if (sizeChanged) {
2609             pg.setSize((float)getWidth(), (float)getHeight());
2610         }
2611 
2612         // NOTE: The order here is very important. There is logic in NGRegion which determines
2613         // whether we can cache an image representing this region, and for this to work correctly,
2614         // the shape must be specified before the background which is before the border.
2615         final boolean shapeChanged = NodeHelper.isDirty(this, DirtyBits.REGION_SHAPE);
2616         if (shapeChanged) {
2617             pg.updateShape(_shape, isScaleShape(), isCenterShape(), isCacheShape());
2618         }
2619 
2620         // The normalized corners can always be updated since they require no
2621         // processing at the NG layer.
2622         pg.updateFillCorners(normalizedFillCorners);
2623         final boolean backgroundChanged = NodeHelper.isDirty(this, DirtyBits.SHAPE_FILL);
2624         final Background bg = getBackground();
2625         if (backgroundChanged) {
2626             pg.updateBackground(bg);
2627         }
2628 
2629         // This will be true if an image that makes up the background or border of this
2630         // region has changed, such that we need to redraw the region.
2631         if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) {
2632             pg.imagesUpdated();
2633         }
2634 
2635         // The normalized corners can always be updated since they require no
2636         // processing at the NG layer.
2637         pg.updateStrokeCorners(normalizedStrokeCorners);
2638         if (NodeHelper.isDirty(this, DirtyBits.SHAPE_STROKE)) {
2639             pg.updateBorder(getBorder());
2640         }
2641 
2642         // TODO given the note above, this *must* be called when an image which makes up the
2643         // background images and border images changes (is loaded) if it was being loaded asynchronously
2644         // Also note, one day we can add support for automatic opaque insets determination for border images.
2645         // However right now it is impractical because the image pixel format is almost undoubtedly going
2646         // to have alpha, and so without inspecting the source image's actual pixels for the filled center
2647         // we can't automatically determine whether the interior is filled.
2648         if (sizeChanged || backgroundChanged || shapeChanged) {
2649             // These are the opaque insets, as specified by the developer in code or CSS. If null,
2650             // then we must compute the opaque insets. If not null, then we will still compute the
2651             // opaque insets and combine them with these insets, as appropriate. We do ignore these
2652             // developer specified insets in cases where we know without a doubt that the developer
2653             // gave us bad data.
2654             final Insets i = getOpaqueInsets();
2655 
2656             // If the background is determined by a shape, then we don't attempt to calculate the
2657             // opaque insets. If the developer specified opaque insets, we will use them, otherwise
2658             // we will make sure the opaque insets are cleared
2659             if (_shape != null) {
2660                 if (i != null) {
2661                     pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(),
2662                                        (float) i.getBottom(), (float) i.getLeft());
2663                 } else {
2664                     pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2665                 }
2666             } else {
2667                 // This is a rectangle (not shape) region. The opaque insets must be calculated,
2668                 // even if the developer has supplied their own opaque insets. The first (and cheapest)
2669                 // check is whether the region has any backgrounds at all. If not, then
2670                 // we will ignore the developer supplied insets because they are clearly wrong.
2671                 if (bg == null || bg.isEmpty()) {
2672                     pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2673                 } else {
2674                     // There is a background, so it is conceivable that there are
2675                     // opaque insets. From this point on, we have to honor the developer's supplied
2676                     // insets, only expanding them if we know for certain the opaque insets are
2677                     // bigger than what was supplied by the developer. Start by defining our initial
2678                     // values for top, right, bottom, and left. If the developer supplied us
2679                     // insets, use those. Otherwise initialize to NaN. Note that the developer may
2680                     // also have given us NaN values (so we'd have to check for these anyway). We use
2681                     // NaN to mean "not defined".
2682                     final double[] trbl = new double[4];
2683                     bg.computeOpaqueInsets(getWidth(), getHeight(), trbl);
2684 
2685                     if (i != null) {
2686                         trbl[0] = Double.isNaN(trbl[0]) ? i.getTop() : Double.isNaN(i.getTop()) ? trbl[0] : Math.min(trbl[0], i.getTop());
2687                         trbl[1] = Double.isNaN(trbl[1]) ? i.getRight() : Double.isNaN(i.getRight()) ? trbl[1] : Math.min(trbl[1], i.getRight());
2688                         trbl[2] = Double.isNaN(trbl[2]) ? i.getBottom() : Double.isNaN(i.getBottom()) ? trbl[2] : Math.min(trbl[2], i.getBottom());
2689                         trbl[3] = Double.isNaN(trbl[3]) ? i.getLeft() : Double.isNaN(i.getLeft()) ? trbl[3] : Math.min(trbl[3], i.getLeft());
2690                     }
2691 
2692                     // Now set the insets onto the peer. Passing NaN here is perfectly
2693                     // acceptable (even encouraged, to mean "unknown" or "disabled").
2694                     pg.setOpaqueInsets((float) trbl[0], (float) trbl[1], (float) trbl[2], (float) trbl[3]);
2695                 }
2696             }
2697         }
2698     }
2699 
2700     /*
2701      * Note: This method MUST only be called via its accessor method.
2702      */
2703     private NGNode doCreatePeer() {
2704         return new NGRegion();
2705     }
2706 
2707     /**
2708      * Transform x, y in local Region coordinates to local coordinates of scaled/centered shape and
2709      * check if the shape contains the coordinates.
2710      * The transformations here are basically an inversion of transformations being done in NGShape#resizeShape.
2711      */
2712     private boolean shapeContains(com.sun.javafx.geom.Shape shape,
2713             final double x, final double y,
2714             double topOffset, double rightOffset, double bottomOffset, double leftOffset) {
2715         double resX = x;
2716         double resY = y;
2717         // The bounds of the shape, before any centering / scaling takes place
2718         final RectBounds bounds = shape.getBounds();
2719         if (isScaleShape()) {
2720             // Modify the transform to scale the shape so that it will fit
2721             // within the insets.
2722             resX -= leftOffset;
2723             resY -= topOffset;
2724 
2725             //denominator represents the width and height of the box within which the new shape must fit.
2726             resX *= bounds.getWidth() / (getWidth() - leftOffset - rightOffset);
2727             resY *= bounds.getHeight() / (getHeight() - topOffset - bottomOffset);
2728 
2729             // If we also need to center it, we need to adjust the transform so as to place
2730             // the shape in the center of the bounds
2731             if (isCenterShape()) {
2732                 resX += bounds.getMinX();
2733                 resY += bounds.getMinY();
2734             }
2735         } else if (isCenterShape()) {
2736             // We are only centering. In this case, what we want is for the
2737             // original shape to be centered. If there are offsets (insets)
2738             // then we must pre-scale about the center to account for it.
2739 
2740             double boundsWidth = bounds.getWidth();
2741             double boundsHeight = bounds.getHeight();
2742 
2743             double scaleFactorX = boundsWidth / (boundsWidth - leftOffset - rightOffset);
2744             double scaleFactorY = boundsHeight / (boundsHeight - topOffset - bottomOffset);
2745 
2746             //This is equivalent to:
2747             // translate(bounds.getMinX(), bounds.getMinY())
2748             // scale(scaleFactorX, scaleFactorY)
2749             // translate(-bounds.getMinX(), -bounds.getMinY())
2750             // translate(-leftOffset - (getWidth() - boundsWidth)/2 + bounds.getMinX(),
2751             //                            -topOffset - (getHeight() - boundsHeight)/2 + bounds.getMinY());
2752             // which is an inversion of an transformation done to the shape
2753             // This gives us
2754             //
2755             //resX = resX * scaleFactorX - scaleFactorX * bounds.getMinX() - scaleFactorX * (leftOffset + (getWidth() - boundsWidth) / 2 - bounds.getMinX()) + bounds.getMinX();
2756             //resY = resY * scaleFactorY - scaleFactorY * bounds.getMinY() - scaleFactorY * (topOffset + (getHeight() - boundsHeight) / 2 - bounds.getMinY()) + bounds.getMinY();
2757             //
2758             // which can further reduced to
2759 
2760             resX = scaleFactorX * (resX -(leftOffset + (getWidth() - boundsWidth) / 2)) + bounds.getMinX();
2761             resY = scaleFactorY * (resY -(topOffset + (getHeight() - boundsHeight) / 2)) + bounds.getMinY();
2762 
2763         } else if (topOffset != 0 || rightOffset != 0 || bottomOffset != 0 || leftOffset != 0) {
2764             // We are neither centering nor scaling, but we still have to resize the
2765             // shape because we have to fit within the bounds defined by the offsets
2766             double scaleFactorX = bounds.getWidth() / (bounds.getWidth() - leftOffset - rightOffset);
2767             double scaleFactorY = bounds.getHeight() / (bounds.getHeight() - topOffset - bottomOffset);
2768 
2769             // This is equivalent to:
2770             // translate(bounds.getMinX(), bounds.getMinY())
2771             // scale(scaleFactorX, scaleFactorY)
2772             // translate(-bounds.getMinX(), -bounds.getMinY())
2773             // translate(-leftOffset, -topOffset)
2774             //
2775             // which is an inversion of an transformation done to the shape
2776             // This gives us
2777             //
2778             //resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX();
2779             //resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY();
2780             //
2781             // which can be further reduceD to
2782             resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX();
2783             resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY();
2784 
2785         }
2786         return shape.contains((float)resX, (float)resY);
2787     }
2788 
2789     /*
2790      * Note: This method MUST only be called via its accessor method.
2791      */
2792     private boolean doComputeContains(double localX, double localY) {
2793         // NOTE: This method only gets called if a quick check of bounds has already
2794         // occurred, so there is no need to test against bound again. We know that the
2795         // point (localX, localY) falls within the bounds of this node, now we need
2796         // to determine if it falls within the geometry of this node.
2797         // Also note that because Region defaults pickOnBounds to true, this code is
2798         // not usually executed. It will only be executed if pickOnBounds is set to false.
2799 
2800         final double x2 = getWidth();
2801         final double y2 = getHeight();
2802 
2803         final Background background = getBackground();
2804         // First check the shape. Shape could be impacted by scaleShape & positionShape properties.
2805         if (_shape != null) {
2806             if (background != null && !background.getFills().isEmpty()) {
2807                 final List<BackgroundFill> fills = background.getFills();
2808                 double topO = Double.MAX_VALUE;
2809                 double leftO = Double.MAX_VALUE;
2810                 double bottomO = Double.MAX_VALUE;
2811                 double rightO = Double.MAX_VALUE;
2812                 for (int i = 0, max = fills.size(); i < max; i++) {
2813                     BackgroundFill bf = fills.get(0);
2814                     topO = Math.min(topO, bf.getInsets().getTop());
2815                     leftO = Math.min(leftO, bf.getInsets().getLeft());
2816                     bottomO = Math.min(bottomO, bf.getInsets().getBottom());
2817                     rightO = Math.min(rightO, bf.getInsets().getRight());
2818                 }
2819                 return shapeContains(ShapeHelper.configShape(_shape), localX, localY, topO, leftO, bottomO, rightO);
2820             }
2821             return false;
2822         }
2823 
2824         // OK, there was no background shape, so I'm going to work on the principle of
2825         // nested rounded rectangles. We'll start by checking the backgrounds. The
2826         // first background which passes the test is good enough for us!
2827         if (background != null) {
2828             final List<BackgroundFill> fills = background.getFills();
2829             for (int i = 0, max = fills.size(); i < max; i++) {
2830                 final BackgroundFill bgFill = fills.get(i);
2831                 if (contains(localX, localY, 0, 0, x2, y2, bgFill.getInsets(), getNormalizedFillCorner(i))) {
2832                     return true;
2833                 }
2834             }
2835         }
2836 
2837         // If we are here then either there were no background fills or there were no background
2838         // fills which contained the point, and the region is not defined by a shape.
2839         final Border border = getBorder();
2840         if (border != null) {
2841             // Check all the stroke borders first. If the pick occurs on any stroke border
2842             // then we consider the contains test to have passed. Semantically we will treat a Region
2843             // with a border as if it were a rectangle with a stroke but no fill.
2844             final List<BorderStroke> strokes = border.getStrokes();
2845             for (int i=0, max=strokes.size(); i<max; i++) {
2846                 final BorderStroke strokeBorder = strokes.get(i);
2847                 if (contains(localX, localY, 0, 0, x2, y2, strokeBorder.getWidths(), false, strokeBorder.getInsets(),
2848                              getNormalizedStrokeCorner(i))) {
2849                     return true;
2850                 }
2851             }
2852 
2853             // Check the image borders. We treat the image border as though it is opaque.
2854             final List<BorderImage> images = border.getImages();
2855             for (int i = 0, max = images.size(); i < max; i++) {
2856                 final BorderImage borderImage = images.get(i);
2857                 if (contains(localX, localY, 0, 0, x2, y2, borderImage.getWidths(), borderImage.isFilled(),
2858                              borderImage.getInsets(), CornerRadii.EMPTY)) {
2859                     return true;
2860                 }
2861             }
2862         }
2863         return false;
2864     }
2865 
2866     /**
2867      * Basically we will perform two contains tests. For a point to be on the stroke, it must
2868      * be within the outermost edge of the stroke, but outside the innermost edge of the stroke.
2869      * Unless it is filled, in which case it is really just a normal contains test.
2870      *
2871      * @param px        The x position of the point to test
2872      * @param py        The y position of the point to test
2873      * @param x1        The x1 position of the bounds to test
2874      * @param y1        The y1 position of the bounds to test
2875      * @param x2        The x2 position of the bounds to test
2876      * @param y2        The y2 position of the bounds to test
2877      * @param widths    The widths of the stroke on each side
2878      * @param filled    Whether the area is filled or is just stroked
2879      * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2880      * @param rad       The corner radii to test with. Must not be null.
2881      * @param maxRadius The maximum possible radius value
2882      * @return True if (px, py) is within the stroke, taking into account insets and corner radii.
2883      */
2884     private boolean contains(final double px, final double py,
2885                              final double x1, final double y1, final double x2, final double y2,
2886                              BorderWidths widths, boolean filled,
2887                              final Insets insets, final CornerRadii rad) {
2888         if (filled) {
2889             if (contains(px, py, x1, y1, x2, y2, insets, rad)) {
2890                 return true;
2891             }
2892         } else {
2893             boolean insideOuterEdge = contains(px, py, x1, y1, x2, y2, insets, rad);
2894             if (insideOuterEdge) {
2895                 boolean outsideInnerEdge = !contains(px, py,
2896                     x1 + (widths.isLeftAsPercentage() ? getWidth() * widths.getLeft() : widths.getLeft()),
2897                     y1 + (widths.isTopAsPercentage() ? getHeight() * widths.getTop() : widths.getTop()),
2898                     x2 - (widths.isRightAsPercentage() ? getWidth() * widths.getRight() : widths.getRight()),
2899                     y2 - (widths.isBottomAsPercentage() ? getHeight() * widths.getBottom() : widths.getBottom()),
2900                     insets, rad);
2901                 if (outsideInnerEdge) return true;
2902             }
2903         }
2904         return false;
2905     }
2906 
2907     /**
2908      * Determines whether the point (px, py) is contained within the the bounds (x1, y1)-(x2, y2),
2909      * after taking into account the insets and the corner radii.
2910      *
2911      * @param px        The x position of the point to test
2912      * @param py        The y position of the point to test
2913      * @param x1        The x1 position of the bounds to test
2914      * @param y1        The y1 position of the bounds to test
2915      * @param x2        The x2 position of the bounds to test
2916      * @param y2        The y2 position of the bounds to test
2917      * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2918      * @param rad       The corner radii to test with. Must not be null.
2919      * @param maxRadius The maximum possible radius value
2920      * @return True if (px, py) is within the bounds, taking into account insets and corner radii.
2921      */
2922     private boolean contains(final double px, final double py,
2923                              final double x1, final double y1, final double x2, final double y2,
2924                              final Insets insets, CornerRadii rad) {
2925         // These four values are the x0, y0, x1, y1 bounding box after
2926         // having taken into account the insets of this particular
2927         // background fill.
2928         final double rrx0 = x1 + insets.getLeft();
2929         final double rry0 = y1 + insets.getTop();
2930         final double rrx1 = x2 - insets.getRight();
2931         final double rry1 = y2 - insets.getBottom();
2932 
2933 //        assert rad.hasPercentBasedRadii == false;
2934 
2935         // Check for trivial rejection - point is inside bounding rectangle
2936         if (px >= rrx0 && py >= rry0 && px <= rrx1 && py <= rry1) {
2937             // The point was within the index bounding box. Now we need to analyze the
2938             // corner radii to see if the point lies within the corners or not. If the
2939             // point is within a corner then we reject this one.
2940             final double tlhr = rad.getTopLeftHorizontalRadius();
2941             if (rad.isUniform() && tlhr == 0) {
2942                 // This is a simple square! Since we know the point is already within
2943                 // the insets of this fill, we can simply return true.
2944                 return true;
2945             } else {
2946                 final double tlvr = rad.getTopLeftVerticalRadius();
2947                 final double trhr = rad.getTopRightHorizontalRadius();
2948                 final double trvr = rad.getTopRightVerticalRadius();
2949                 final double blhr = rad.getBottomLeftHorizontalRadius();
2950                 final double blvr = rad.getBottomLeftVerticalRadius();
2951                 final double brhr = rad.getBottomRightHorizontalRadius();
2952                 final double brvr = rad.getBottomRightVerticalRadius();
2953 
2954                 // The four corners can each be described as a quarter of an ellipse
2955                 double centerX, centerY, a, b;
2956 
2957                 if (px <= rrx0 + tlhr && py <= rry0 + tlvr) {
2958                     // Point is in the top left corner
2959                     centerX = rrx0 + tlhr;
2960                     centerY = rry0 + tlvr;
2961                     a = tlhr;
2962                     b = tlvr;
2963                 } else if (px >= rrx1 - trhr && py <= rry0 + trvr) {
2964                     // Point is in the top right corner
2965                     centerX = rrx1 - trhr;
2966                     centerY = rry0 + trvr;
2967                     a = trhr;
2968                     b = trvr;
2969                 } else if (px >= rrx1 - brhr && py >= rry1 - brvr) {
2970                     // Point is in the bottom right corner
2971                     centerX = rrx1 - brhr;
2972                     centerY = rry1 - brvr;
2973                     a = brhr;
2974                     b = brvr;
2975                 } else if (px <= rrx0 + blhr && py >= rry1 - blvr) {
2976                     // Point is in the bottom left corner
2977                     centerX = rrx0 + blhr;
2978                     centerY = rry1 - blvr;
2979                     a = blhr;
2980                     b = blvr;
2981                 } else {
2982                     // The point must have been in the solid body someplace
2983                     return true;
2984                 }
2985 
2986                 double x = px - centerX;
2987                 double y = py - centerY;
2988                 double result = ((x*x)/(a*a) + (y*y)/(b*b));
2989                 // The .0000001 is fudge to help in cases where double arithmetic isn't quite right
2990                 if (result - .0000001 <= 1) return true;
2991             }
2992         }
2993         return false;
2994     }
2995 
2996     /*
2997      * The normalized corner radii are unmodifiable List objects shared between
2998      * the NG layer and the FX layer.  As cached shadow copies of the objects
2999      * in the BackgroundFill and BorderStroke objects they should be considered
3000      * read-only and will only be updated by replacing the original objects
3001      * when validation is needed.
3002      */
3003     private boolean cornersValid; // = false
3004     private List<CornerRadii> normalizedFillCorners; // = null
3005     private List<CornerRadii> normalizedStrokeCorners; // = null
3006 
3007     /**
3008      * Returns the normalized absolute radii for the indicated BackgroundFill,
3009      * taking the current size of the region into account to eliminate any
3010      * percentage-based measurements and to scale the radii to prevent
3011      * overflowing the width or height.
3012      *
3013      * @param i the index of the BackgroundFill whose radii will be normalized.
3014      * @return the normalized (non-percentage, non-overflowing) radii
3015      */
3016     private CornerRadii getNormalizedFillCorner(int i) {
3017         if (!cornersValid) {
3018             validateCorners();
3019         }
3020         return (normalizedFillCorners == null
3021                 ? getBackground().getFills().get(i).getRadii()
3022                 : normalizedFillCorners.get(i));
3023     }
3024 
3025     /**
3026      * Returns the normalized absolute radii for the indicated BorderStroke,
3027      * taking the current size of the region into account to eliminate any
3028      * percentage-based measurements and to scale the radii to prevent
3029      * overflowing the width or height.
3030      *
3031      * @param i the index of the BorderStroke whose radii will be normalized.
3032      * @return the normalized (non-percentage, non-overflowing) radii
3033      */
3034     private CornerRadii getNormalizedStrokeCorner(int i) {
3035         if (!cornersValid) {
3036             validateCorners();
3037         }
3038         return (normalizedStrokeCorners == null
3039                 ? getBorder().getStrokes().get(i).getRadii()
3040                 : normalizedStrokeCorners.get(i));
3041     }
3042 
3043     /**
3044      * This method validates all CornerRadii objects in both the set of
3045      * BackgroundFills and BorderStrokes and saves the normalized values
3046      * into the private fields above.
3047      */
3048     private void validateCorners() {
3049         final double width = getWidth();
3050         final double height = getHeight();
3051         List<CornerRadii> newFillCorners = null;
3052         List<CornerRadii> newStrokeCorners = null;
3053         final Background background = getBackground();
3054         final List<BackgroundFill> fills = background == null ? Collections.EMPTY_LIST : background.getFills();
3055         for (int i = 0; i < fills.size(); i++) {
3056             final BackgroundFill fill = fills.get(i);
3057             final CornerRadii origRadii = fill.getRadii();
3058             final Insets origInsets = fill.getInsets();
3059             final CornerRadii newRadii = normalize(origRadii, origInsets, width, height);
3060             if (origRadii != newRadii) {
3061                 if (newFillCorners == null) {
3062                     newFillCorners = Arrays.asList(new CornerRadii[fills.size()]);
3063                 }
3064                 newFillCorners.set(i, newRadii);
3065             }
3066         }
3067         final Border border = getBorder();
3068         final List<BorderStroke> strokes = (border == null ? Collections.EMPTY_LIST : border.getStrokes());
3069         for (int i = 0; i < strokes.size(); i++) {
3070             final BorderStroke stroke = strokes.get(i);
3071             final CornerRadii origRadii = stroke.getRadii();
3072             final Insets origInsets = stroke.getInsets();
3073             final CornerRadii newRadii = normalize(origRadii, origInsets, width, height);
3074             if (origRadii != newRadii) {
3075                 if (newStrokeCorners == null) {
3076                     newStrokeCorners = Arrays.asList(new CornerRadii[strokes.size()]);
3077                 }
3078                 newStrokeCorners.set(i, newRadii);
3079             }
3080         }
3081         if (newFillCorners != null) {
3082             for (int i = 0; i < fills.size(); i++) {
3083                 if (newFillCorners.get(i) == null) {
3084                     newFillCorners.set(i, fills.get(i).getRadii());
3085                 }
3086             }
3087             newFillCorners = Collections.unmodifiableList(newFillCorners);
3088         }
3089         if (newStrokeCorners != null) {
3090             for (int i = 0; i < strokes.size(); i++) {
3091                 if (newStrokeCorners.get(i) == null) {
3092                     newStrokeCorners.set(i, strokes.get(i).getRadii());
3093                 }
3094             }
3095             newStrokeCorners = Collections.unmodifiableList(newStrokeCorners);
3096         }
3097         normalizedFillCorners = newFillCorners;
3098         normalizedStrokeCorners = newStrokeCorners;
3099         cornersValid = true;
3100     }
3101 
3102     /**
3103      * Return a version of the radii that is not percentage based and is scaled to
3104      * fit the indicated inset rectangle without overflow.
3105      * This method may return the original CornerRadii if none of the radii
3106      * values in the given object are percentages or require scaling.
3107      *
3108      * @param radii    The radii.
3109      * @param insets   The insets for the associated background or stroke.
3110      * @param width    The width of the region before insets are applied.
3111      * @param height   The height of the region before insets are applied.
3112      * @return Normalized radii.
3113      */
3114     private static CornerRadii normalize(CornerRadii radii, Insets insets, double width, double height) {
3115         width  -= insets.getLeft() + insets.getRight();
3116         height -= insets.getTop() + insets.getBottom();
3117         if (width <= 0 || height <= 0) return CornerRadii.EMPTY;
3118         double tlvr = radii.getTopLeftVerticalRadius();
3119         double tlhr = radii.getTopLeftHorizontalRadius();
3120         double trvr = radii.getTopRightVerticalRadius();
3121         double trhr = radii.getTopRightHorizontalRadius();
3122         double brvr = radii.getBottomRightVerticalRadius();
3123         double brhr = radii.getBottomRightHorizontalRadius();
3124         double blvr = radii.getBottomLeftVerticalRadius();
3125         double blhr = radii.getBottomLeftHorizontalRadius();
3126         if (radii.hasPercentBasedRadii) {
3127             if (radii.isTopLeftVerticalRadiusAsPercentage())       tlvr *= height;
3128             if (radii.isTopLeftHorizontalRadiusAsPercentage())     tlhr *= width;
3129             if (radii.isTopRightVerticalRadiusAsPercentage())      trvr *= height;
3130             if (radii.isTopRightHorizontalRadiusAsPercentage())    trhr *= width;
3131             if (radii.isBottomRightVerticalRadiusAsPercentage())   brvr *= height;
3132             if (radii.isBottomRightHorizontalRadiusAsPercentage()) brhr *= width;
3133             if (radii.isBottomLeftVerticalRadiusAsPercentage())    blvr *= height;
3134             if (radii.isBottomLeftHorizontalRadiusAsPercentage())  blhr *= width;
3135         }
3136         double scale = 1.0;
3137         if (tlhr + trhr > width)  { scale = Math.min(scale, width  / (tlhr + trhr)); }
3138         if (blhr + brhr > width)  { scale = Math.min(scale, width  / (blhr + brhr)); }
3139         if (tlvr + blvr > height) { scale = Math.min(scale, height / (tlvr + blvr)); }
3140         if (trvr + brvr > height) { scale = Math.min(scale, height / (trvr + brvr)); }
3141         if (scale < 1.0) {
3142             tlvr *= scale;  tlhr *= scale;
3143             trvr *= scale;  trhr *= scale;
3144             brvr *= scale;  brhr *= scale;
3145             blvr *= scale;  blhr *= scale;
3146         }
3147         if (radii.hasPercentBasedRadii || scale < 1.0) {
3148             return new CornerRadii(tlhr,  tlvr,  trvr,  trhr,  brhr,  brvr,  blvr,  blhr,
3149                                    false, false, false, false, false, false, false, false);
3150         }
3151         return radii;
3152     }
3153 
3154     /**
3155      * Some skins relying on this
3156      *
3157      * Note: This method MUST only be called via its accessor method.
3158      */
3159     private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) {
3160          double boundsDistance = NodeHelper.intersectsBounds(this, pickRay);
3161 
3162         if (!Double.isNaN(boundsDistance) && ParentHelper.pickChildrenNode(this, pickRay, result)) {
3163             NodeHelper.intersects(this, pickRay, result);
3164         }
3165     }
3166 
3167     private Bounds boundingBox;
3168 
3169     /**
3170      * The layout bounds of this region: {@code 0, 0  width x height}
3171      */
3172     private Bounds doComputeLayoutBounds() {
3173         if (boundingBox == null) {
3174             // we reuse the bounding box if the width and height haven't changed.
3175             boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0);
3176         }
3177         return boundingBox;
3178     }
3179 
3180     /*
3181      * Note: This method MUST only be called via its accessor method.
3182      */
3183     private void doNotifyLayoutBoundsChanged() {
3184         // override Node's default behavior of having a geometric bounds change
3185         // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
3186         // is unrelated to geometric bounds.
3187     }
3188 
3189     private BaseBounds computeShapeBounds(BaseBounds bounds)
3190     {
3191         com.sun.javafx.geom.Shape s = ShapeHelper.configShape(_shape);
3192 
3193         float[] bbox = {
3194                 Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
3195                 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
3196         };
3197 
3198         Background bg = getBackground();
3199         if (bg != null) {
3200             final RectBounds sBounds = s.getBounds();
3201             final Insets bgOutsets = bg.getOutsets();
3202             bbox[0] = sBounds.getMinX() - (float) bgOutsets.getLeft();
3203             bbox[1] = sBounds.getMinY() - (float) bgOutsets.getTop();
3204             bbox[2] = sBounds.getMaxX() + (float) bgOutsets.getBottom();
3205             bbox[3] = sBounds.getMaxY() + (float) bgOutsets.getRight();
3206         }
3207 
3208         final Border b = getBorder();
3209         if (b != null && b.getStrokes().size() > 0) {
3210             for (BorderStroke bs : b.getStrokes()) {
3211                 // This order of border strokes is used in NGRegion.renderAsShape/setBorderStyle
3212                 BorderStrokeStyle bss = bs.getTopStyle() != null ? bs.getTopStyle() :
3213                         bs.getLeftStyle() != null ? bs.getLeftStyle() :
3214                                 bs.getBottomStyle() != null ? bs.getBottomStyle() :
3215                                         bs.getRightStyle() != null ? bs.getRightStyle() : null;
3216 
3217                 if (bss == null || bss == BorderStrokeStyle.NONE) {
3218                     continue;
3219                 }
3220 
3221                 final StrokeType type = bss.getType();
3222                 double sw = Math.max(bs.getWidths().top, 0d);
3223                 StrokeLineCap cap = bss.getLineCap();
3224                 StrokeLineJoin join = bss.getLineJoin();
3225                 float miterlimit = (float) Math.max(bss.getMiterLimit(), 1d);
3226                 Toolkit.getToolkit().accumulateStrokeBounds(
3227                         s,
3228                         bbox, type, sw,
3229                         cap, join, miterlimit, BaseTransform.IDENTITY_TRANSFORM);
3230 
3231             }
3232         }
3233 
3234         if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) {
3235             return bounds.makeEmpty();
3236         }
3237 
3238         return bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f,
3239                 bbox[2], bbox[3], 0.0f);
3240     }
3241 
3242     /*
3243      * Note: This method MUST only be called via its accessor method.
3244      */
3245     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
3246         // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children.
3247         // The bounds of the Region must take into account any backgrounds and borders and how
3248         // they are used to draw the Region. The geom bounds must always take into account
3249         // all pixels drawn (because the geom bounds forms the basis of the dirty regions).
3250         // Note that the layout bounds of a Region is not based on the geom bounds.
3251 
3252         // Define some variables to hold the top-left and bottom-right corners of the bounds
3253         double bx1 = 0;
3254         double by1 = 0;
3255         double bx2 = getWidth();
3256         double by2 = getHeight();
3257 
3258         // If the shape is defined, then the top-left and bottom-right corner positions
3259         // need to be redefined
3260         if (_shape != null && isScaleShape() == false) {
3261             // We will hijack the bounds here temporarily just to compute the shape bounds
3262             final BaseBounds shapeBounds = computeShapeBounds(bounds);
3263             final double shapeWidth = shapeBounds.getWidth();
3264             final double shapeHeight = shapeBounds.getHeight();
3265             if (isCenterShape()) {
3266                 bx1 = (bx2 - shapeWidth) / 2;
3267                 by1 = (by2 - shapeHeight) / 2;
3268                 bx2 = bx1 + shapeWidth;
3269                 by2 = by1 + shapeHeight;
3270             } else {
3271                 bx1 = shapeBounds.getMinX();
3272                 by1 = shapeBounds.getMinY();
3273                 bx2 = shapeBounds.getMaxX();
3274                 by2 = shapeBounds.getMaxY();
3275             }
3276         } else {
3277             // Expand the bounds to include the outsets from the background and border.
3278             // The outsets are the opposite of insets -- a measure of distance from the
3279             // edge of the Region outward. The outsets cannot, however, be negative.
3280             final Background background = getBackground();
3281             final Border border = getBorder();
3282             final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets();
3283             final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets();
3284             bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft());
3285             by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop());
3286             bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight());
3287             by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom());
3288         }
3289         // NOTE: Okay to call RegionHelper.superComputeGeomBounds with tx even in the 3D case
3290         // since Parent's computeGeomBounds does handle 3D correctly.
3291         BaseBounds cb = RegionHelper.superComputeGeomBounds(this, bounds, tx);
3292         /*
3293          * This is a work around for RT-7680. Parent returns invalid bounds from
3294          * computeGeomBoundsImpl when it has no children or if all its children
3295          * have invalid bounds. If RT-7680 were fixed, then we could omit this
3296          * first branch of the if and only use the else since the correct value
3297          * would be computed.
3298          */
3299         if (cb.isEmpty()) {
3300             // There are no children bounds, so
3301             bounds = bounds.deriveWithNewBounds(
3302                     (float)bx1, (float)by1, 0.0f,
3303                     (float)bx2, (float)by2, 0.0f);
3304             bounds = tx.transform(bounds, bounds);
3305             return bounds;
3306         } else {
3307             // Union with children's bounds
3308             BaseBounds tempBounds = TempState.getInstance().bounds;
3309             tempBounds = tempBounds.deriveWithNewBounds(
3310                     (float)bx1, (float)by1, 0.0f,
3311                     (float)bx2, (float)by2, 0.0f);
3312             BaseBounds bb = tx.transform(tempBounds, tempBounds);
3313             cb = cb.deriveWithUnion(bb);
3314             return cb;
3315         }
3316     }
3317 
3318     /***************************************************************************
3319      *                                                                         *
3320      * CSS                                                                     *
3321      *                                                                         *
3322      **************************************************************************/
3323 
3324     /**
3325      * An implementation may specify its own user-agent styles for this Region, and its children,
3326      * by overriding this method. These styles are used in addition to whatever user-agent stylesheets
3327      * are in use. This provides a mechanism for third parties to introduce styles for custom controls.
3328      * <p>
3329      * The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL
3330      * does not have a [scheme:] component, the URL is considered to be the [path] component only.
3331      * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to
3332      * the root of the application's classpath.
3333      * </p>
3334      * <p>
3335      * Subclasses overriding this method should not assume any particular implementation approach as to
3336      * the number and frequency with which it is called. For this reason, attempting any kind of
3337      * dynamic implementation (i.e. returning different user agent stylesheet values) based on some
3338      * state change is highly discouraged, as there is no guarantee when, or even if, this method will
3339      * be called. Some JavaFX CSS implementations may choose to cache this response for an indefinite
3340      * period of time, and therefore there should be no expectation around when this method is called.
3341      * </p>
3342      *
3343      * <pre><code>
3344      *
3345      * package com.example.javafx.app;
3346      *
3347      * import javafx.application.Application;
3348      * import javafx.scene.Group;
3349      * import javafx.scene.Scene;
3350      * import javafx.stage.Stage;
3351      *
3352      * public class MyApp extends Application {
3353      *
3354      *     {@literal @}Override public void start(Stage stage) {
3355      *         Scene scene = new Scene(new Group());
3356      *         scene.getStylesheets().add("/com/example/javafx/app/mystyles.css");
3357      *         stage.setScene(scene);
3358      *         stage.show();
3359      *     }
3360      *
3361      *     public static void main(String[] args) {
3362      *         launch(args);
3363      *     }
3364      * }
3365      * </code></pre>
3366      * For additional information about using CSS with the scene graph,
3367      * see the <a href="../doc-files/cssref.html">CSS Reference Guide</a>.
3368      *
3369      * @return A string URL
3370      * @since JavaFX 8u40
3371      */
3372     public String getUserAgentStylesheet() {
3373         return null;
3374     }
3375 
3376     /*
3377      * Super-lazy instantiation pattern from Bill Pugh.
3378      */
3379      private static class StyleableProperties {
3380          private static final CssMetaData<Region,Insets> PADDING =
3381              new CssMetaData<Region,Insets>("-fx-padding",
3382                  InsetsConverter.getInstance(), Insets.EMPTY) {
3383 
3384             @Override public boolean isSettable(Region node) {
3385                 return node.padding == null || !node.padding.isBound();
3386             }
3387 
3388             @Override public StyleableProperty<Insets> getStyleableProperty(Region node) {
3389                 return (StyleableProperty<Insets>)node.paddingProperty();
3390             }
3391          };
3392 
3393          private static final CssMetaData<Region,Insets> OPAQUE_INSETS =
3394                  new CssMetaData<Region,Insets>("-fx-opaque-insets",
3395                          InsetsConverter.getInstance(), null) {
3396 
3397                      @Override
3398                      public boolean isSettable(Region node) {
3399                          return node.opaqueInsets == null || !node.opaqueInsets.isBound();
3400                      }
3401 
3402                      @Override
3403                      public StyleableProperty<Insets> getStyleableProperty(Region node) {
3404                          return (StyleableProperty<Insets>)node.opaqueInsetsProperty();
3405                      }
3406 
3407                  };
3408 
3409          private static final CssMetaData<Region,Background> BACKGROUND =
3410              new CssMetaData<Region,Background>("-fx-region-background",
3411                  BackgroundConverter.INSTANCE,
3412                  null,
3413                  false,
3414                  Background.getClassCssMetaData()) {
3415 
3416             @Override public boolean isSettable(Region node) {
3417                 return !node.background.isBound();
3418             }
3419 
3420             @Override public StyleableProperty<Background> getStyleableProperty(Region node) {
3421                 return (StyleableProperty<Background>)node.background;
3422             }
3423          };
3424 
3425          private static final CssMetaData<Region,Border> BORDER =
3426              new CssMetaData<Region,Border>("-fx-region-border",
3427                      BorderConverter.getInstance(),
3428                      null,
3429                      false,
3430                      Border.getClassCssMetaData()) {
3431 
3432                  @Override public boolean isSettable(Region node) {
3433                      return !node.border.isBound();
3434                  }
3435 
3436                  @Override public StyleableProperty<Border> getStyleableProperty(Region node) {
3437                      return (StyleableProperty<Border>)node.border;
3438                  }
3439              };
3440 
3441          private static final CssMetaData<Region,Shape> SHAPE =
3442              new CssMetaData<Region,Shape>("-fx-shape",
3443                  ShapeConverter.getInstance()) {
3444 
3445             @Override public boolean isSettable(Region node) {
3446                 // isSettable depends on node.shape, not node.shapeContent
3447                 return node.shape == null || !node.shape.isBound();
3448             }
3449 
3450             @Override public StyleableProperty<Shape> getStyleableProperty(Region node) {
3451                 return (StyleableProperty<Shape>)node.shapeProperty();
3452             }
3453          };
3454 
3455          private static final CssMetaData<Region, Boolean> SCALE_SHAPE =
3456              new CssMetaData<Region,Boolean>("-fx-scale-shape",
3457                  BooleanConverter.getInstance(), Boolean.TRUE){
3458 
3459             @Override public boolean isSettable(Region node) {
3460                 return node.scaleShape == null || !node.scaleShape.isBound();
3461             }
3462 
3463             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3464                 return (StyleableProperty<Boolean>)node.scaleShapeProperty();
3465             }
3466         };
3467 
3468          private static final CssMetaData<Region,Boolean> POSITION_SHAPE =
3469              new CssMetaData<Region,Boolean>("-fx-position-shape",
3470                  BooleanConverter.getInstance(), Boolean.TRUE){
3471 
3472             @Override public boolean isSettable(Region node) {
3473                 return node.centerShape == null || !node.centerShape.isBound();
3474             }
3475 
3476             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3477                 return (StyleableProperty<Boolean>)node.centerShapeProperty();
3478             }
3479         };
3480 
3481          private static final CssMetaData<Region,Boolean> CACHE_SHAPE =
3482              new CssMetaData<Region,Boolean>("-fx-cache-shape",
3483                  BooleanConverter.getInstance(), Boolean.TRUE){
3484 
3485             @Override public boolean isSettable(Region node) {
3486                 return node.cacheShape == null || !node.cacheShape.isBound();
3487             }
3488 
3489             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3490                 return (StyleableProperty<Boolean>)node.cacheShapeProperty();
3491             }
3492         };
3493 
3494          private static final CssMetaData<Region, Boolean> SNAP_TO_PIXEL =
3495              new CssMetaData<Region,Boolean>("-fx-snap-to-pixel",
3496                  BooleanConverter.getInstance(), Boolean.TRUE){
3497 
3498             @Override public boolean isSettable(Region node) {
3499                 return node.snapToPixel == null ||
3500                         !node.snapToPixel.isBound();
3501             }
3502 
3503             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3504                 return (StyleableProperty<Boolean>)node.snapToPixelProperty();
3505             }
3506         };
3507 
3508          private static final CssMetaData<Region, Number> MIN_HEIGHT =
3509              new CssMetaData<Region,Number>("-fx-min-height",
3510                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3511 
3512             @Override public boolean isSettable(Region node) {
3513                 return node.minHeight == null ||
3514                         !node.minHeight.isBound();
3515             }
3516 
3517             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3518                 return (StyleableProperty<Number>)node.minHeightProperty();
3519             }
3520         };
3521 
3522          private static final CssMetaData<Region, Number> PREF_HEIGHT =
3523              new CssMetaData<Region,Number>("-fx-pref-height",
3524                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3525 
3526             @Override public boolean isSettable(Region node) {
3527                 return node.prefHeight == null ||
3528                         !node.prefHeight.isBound();
3529             }
3530 
3531             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3532                 return (StyleableProperty<Number>)node.prefHeightProperty();
3533             }
3534         };
3535 
3536          private static final CssMetaData<Region, Number> MAX_HEIGHT =
3537              new CssMetaData<Region,Number>("-fx-max-height",
3538                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3539 
3540             @Override public boolean isSettable(Region node) {
3541                 return node.maxHeight == null ||
3542                         !node.maxHeight.isBound();
3543             }
3544 
3545             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3546                 return (StyleableProperty<Number>)node.maxHeightProperty();
3547             }
3548         };
3549 
3550          private static final CssMetaData<Region, Number> MIN_WIDTH =
3551              new CssMetaData<Region,Number>("-fx-min-width",
3552                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3553 
3554             @Override public boolean isSettable(Region node) {
3555                 return node.minWidth == null ||
3556                         !node.minWidth.isBound();
3557             }
3558 
3559             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3560                 return (StyleableProperty<Number>)node.minWidthProperty();
3561             }
3562         };
3563 
3564          private static final CssMetaData<Region, Number> PREF_WIDTH =
3565              new CssMetaData<Region,Number>("-fx-pref-width",
3566                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3567 
3568             @Override public boolean isSettable(Region node) {
3569                 return node.prefWidth == null ||
3570                         !node.prefWidth.isBound();
3571             }
3572 
3573             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3574                 return (StyleableProperty<Number>)node.prefWidthProperty();
3575             }
3576         };
3577 
3578          private static final CssMetaData<Region, Number> MAX_WIDTH =
3579              new CssMetaData<Region,Number>("-fx-max-width",
3580                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3581 
3582             @Override public boolean isSettable(Region node) {
3583                 return node.maxWidth == null ||
3584                         !node.maxWidth.isBound();
3585             }
3586 
3587             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3588                 return (StyleableProperty<Number>)node.maxWidthProperty();
3589             }
3590         };
3591 
3592          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
3593          static {
3594 
3595             final List<CssMetaData<? extends Styleable, ?>> styleables =
3596                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData());
3597             styleables.add(PADDING);
3598             styleables.add(BACKGROUND);
3599             styleables.add(BORDER);
3600             styleables.add(OPAQUE_INSETS);
3601             styleables.add(SHAPE);
3602             styleables.add(SCALE_SHAPE);
3603             styleables.add(POSITION_SHAPE);
3604             styleables.add(SNAP_TO_PIXEL);
3605             styleables.add(MIN_WIDTH);
3606             styleables.add(PREF_WIDTH);
3607             styleables.add(MAX_WIDTH);
3608             styleables.add(MIN_HEIGHT);
3609             styleables.add(PREF_HEIGHT);
3610             styleables.add(MAX_HEIGHT);
3611             STYLEABLES = Collections.unmodifiableList(styleables);
3612          }
3613     }
3614 
3615     /**
3616      * @return The CssMetaData associated with this class, which may include the
3617      * CssMetaData of its superclasses.
3618      * @since JavaFX 8.0
3619      */
3620     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
3621         return StyleableProperties.STYLEABLES;
3622     }
3623 
3624     /**
3625      * {@inheritDoc}
3626      *
3627      * @since JavaFX 8.0
3628      */
3629 
3630 
3631     @Override
3632     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
3633         return getClassCssMetaData();
3634     }
3635 
3636 }