1 /*
   2  * Copyright (c) 2010, 2016, 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 sun.util.logging.PlatformLogger;
  87 import sun.util.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      * @since JavaFX 8.0
 841      */
 842     public final ObjectProperty<Insets> opaqueInsetsProperty() {
 843         if (opaqueInsets == null) {
 844             opaqueInsets = new StyleableObjectProperty<Insets>() {
 845                 @Override public Object getBean() { return Region.this; }
 846                 @Override public String getName() { return "opaqueInsets"; }
 847                 @Override public CssMetaData<Region, Insets> getCssMetaData() {
 848                     return StyleableProperties.OPAQUE_INSETS;
 849                 }
 850                 @Override protected void invalidated() {
 851                     // This causes the background to be updated, which
 852                     // is the code block where we also compute the opaque insets
 853                     // since updating the background is super fast even when
 854                     // nothing has changed.
 855                     NodeHelper.markDirty(Region.this, DirtyBits.SHAPE_FILL);
 856                 }
 857             };
 858         }
 859         return opaqueInsets;
 860     }
 861     private ObjectProperty<Insets> opaqueInsets;
 862     public final void setOpaqueInsets(Insets value) { opaqueInsetsProperty().set(value); }
 863     public final Insets getOpaqueInsets() { return opaqueInsets == null ? null : opaqueInsets.get(); }
 864 
 865     /**
 866      * The insets of the Region define the distance from the edge of the region (its layout bounds,
 867      * or (0, 0, width, height)) to the edge of the content area. All child nodes should be laid out
 868      * within the content area. The insets are computed based on the Border which has been specified,
 869      * if any, and also the padding.
 870      * @since JavaFX 8.0
 871      */
 872     private final InsetsProperty insets = new InsetsProperty();
 873     public final Insets getInsets() { return insets.get(); }
 874     public final ReadOnlyObjectProperty<Insets> insetsProperty() { return insets; }
 875     private final class InsetsProperty extends ReadOnlyObjectProperty<Insets> {
 876         private Insets cache = null;
 877         private ExpressionHelper<Insets> helper = null;
 878 
 879         @Override public Object getBean() { return Region.this; }
 880         @Override public String getName() { return "insets"; }
 881 
 882         @Override public void addListener(InvalidationListener listener) {
 883             helper = ExpressionHelper.addListener(helper, this, listener);
 884         }
 885 
 886         @Override public void removeListener(InvalidationListener listener) {
 887             helper = ExpressionHelper.removeListener(helper, listener);
 888         }
 889 
 890         @Override public void addListener(ChangeListener<? super Insets> listener) {
 891             helper = ExpressionHelper.addListener(helper, this, listener);
 892         }
 893 
 894         @Override public void removeListener(ChangeListener<? super Insets> listener) {
 895             helper = ExpressionHelper.removeListener(helper, listener);
 896         }
 897 
 898         void fireValueChanged() {
 899             cache = null;
 900             updateSnappedInsets();
 901             requestLayout();
 902             ExpressionHelper.fireValueChangedEvent(helper);
 903         }
 904 
 905         @Override public Insets get() {
 906             // If a shape is specified, then we don't really care whether there are any borders
 907             // specified, since borders of shapes do not contribute to the insets.
 908             if (_shape != null) return getPadding();
 909 
 910             // If there is no border or the border has no insets itself, then the only thing
 911             // affecting the insets is the padding, so we can just return it directly.
 912             final Border b = getBorder();
 913             if (b == null || Insets.EMPTY.equals(b.getInsets())) {
 914                 return getPadding();
 915             }
 916 
 917             // There is a border with some non-zero insets and we do not have a _shape, so we need
 918             // to take the border's insets into account
 919             if (cache == null) {
 920                 // Combine the padding and the border insets.
 921                 // TODO note that negative border insets were being ignored, but
 922                 // I'm not sure that that made sense or was reasonable, so I have
 923                 // changed it so that we just do simple math.
 924                 // TODO Stroke borders should NOT contribute to the insets. Ensure via tests.
 925                 final Insets borderInsets = b.getInsets();
 926                 final Insets paddingInsets = getPadding();
 927                 cache = new Insets(
 928                         borderInsets.getTop() + paddingInsets.getTop(),
 929                         borderInsets.getRight() + paddingInsets.getRight(),
 930                         borderInsets.getBottom() + paddingInsets.getBottom(),
 931                         borderInsets.getLeft() + paddingInsets.getLeft()
 932                 );
 933             }
 934             return cache;
 935         }
 936     };
 937 
 938     /**
 939      * cached results of snapped insets, this are used a lot during layout so makes sense
 940      * to keep fast access cached copies here.
 941      */
 942     private double snappedTopInset = 0;
 943     private double snappedRightInset = 0;
 944     private double snappedBottomInset = 0;
 945     private double snappedLeftInset = 0;
 946 
 947     /** Called to update the cached snapped insets */
 948     private void updateSnappedInsets() {
 949         final Insets insets = getInsets();
 950         if (_snapToPixel) {
 951             snappedTopInset = Math.ceil(insets.getTop());
 952             snappedRightInset = Math.ceil(insets.getRight());
 953             snappedBottomInset = Math.ceil(insets.getBottom());
 954             snappedLeftInset = Math.ceil(insets.getLeft());
 955         } else {
 956             snappedTopInset = insets.getTop();
 957             snappedRightInset = insets.getRight();
 958             snappedBottomInset = insets.getBottom();
 959             snappedLeftInset = insets.getLeft();
 960         }
 961     }
 962 
 963     /**
 964     * The width of this resizable node.  This property is set by the region's parent
 965     * during layout and may not be set by the application.  If an application
 966     * needs to explicitly control the size of a region, it should override its
 967     * preferred size range by setting the <code>minWidth</code>, <code>prefWidth</code>,
 968     * and <code>maxWidth</code> properties.
 969     */
 970     private ReadOnlyDoubleWrapper width;
 971 
 972     /**
 973      * Because the width is very often set and very often read but only sometimes
 974      * listened to, it is beneficial to use the super-lazy pattern property, where we
 975      * only inflate the property object when widthProperty() is explicitly invoked.
 976      */
 977     private double _width;
 978 
 979     // Note that it is OK for this method to be protected so long as the width
 980     // property is never bound. Only Region could do so because only Region has
 981     // access to a writable property for "width", but since there is now a protected
 982     // set method, it is impossible for Region to ever bind this property.
 983     protected void setWidth(double value) {
 984         if(width == null) {
 985             widthChanged(value);
 986         } else {
 987             width.set(value);
 988         }
 989     }
 990 
 991     private void widthChanged(double value) {
 992         // It is possible that somebody sets the width of the region to a value which
 993         // it previously held. If this is the case, we want to avoid excessive layouts.
 994         // Note that I have biased this for layout over binding, because the widthProperty
 995         // is now going to recompute the width eagerly. The cost of excessive and
 996         // unnecessary bounds changes, however, is relatively high.
 997         if (value != _width) {
 998             _width = value;
 999             cornersValid = false;
1000             boundingBox = null;
1001             NodeHelper.layoutBoundsChanged(this);
1002             NodeHelper.geomChanged(this);
1003             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1004             setNeedsLayout(true);
1005             requestParentLayout();
1006         }
1007     }
1008 
1009     public final double getWidth() { return width == null ? _width : width.get(); }
1010 
1011     public final ReadOnlyDoubleProperty widthProperty() {
1012         if (width == null) {
1013             width = new ReadOnlyDoubleWrapper(_width) {
1014                 @Override protected void invalidated() { widthChanged(get()); }
1015                 @Override public Object getBean() { return Region.this; }
1016                 @Override public String getName() { return "width"; }
1017             };
1018         }
1019         return width.getReadOnlyProperty();
1020     }
1021 
1022     /**
1023      * The height of this resizable node.  This property is set by the region's parent
1024      * during layout and may not be set by the application.  If an application
1025      * needs to explicitly control the size of a region, it should override its
1026      * preferred size range by setting the <code>minHeight</code>, <code>prefHeight</code>,
1027      * and <code>maxHeight</code> properties.
1028      */
1029     private ReadOnlyDoubleWrapper height;
1030 
1031     /**
1032      * Because the height is very often set and very often read but only sometimes
1033      * listened to, it is beneficial to use the super-lazy pattern property, where we
1034      * only inflate the property object when heightProperty() is explicitly invoked.
1035      */
1036     private double _height;
1037 
1038     // Note that it is OK for this method to be protected so long as the height
1039     // property is never bound. Only Region could do so because only Region has
1040     // access to a writable property for "height", but since there is now a protected
1041     // set method, it is impossible for Region to ever bind this property.
1042     protected void setHeight(double value) {
1043         if (height == null) {
1044             heightChanged(value);
1045         } else {
1046             height.set(value);
1047         }
1048     }
1049 
1050     private void heightChanged(double value) {
1051         if (_height != value) {
1052             _height = value;
1053             cornersValid = false;
1054             // It is possible that somebody sets the height of the region to a value which
1055             // it previously held. If this is the case, we want to avoid excessive layouts.
1056             // Note that I have biased this for layout over binding, because the heightProperty
1057             // is now going to recompute the height eagerly. The cost of excessive and
1058             // unnecessary bounds changes, however, is relatively high.
1059             boundingBox = null;
1060             // Note: although NodeHelper.geomChanged will usually also invalidate the
1061             // layout bounds, that is not the case for Regions, and both must
1062             // be called separately.
1063             NodeHelper.geomChanged(this);
1064             NodeHelper.layoutBoundsChanged(this);
1065             // We use "NODE_GEOMETRY" to mean that the bounds have changed and
1066             // need to be sync'd with the render tree
1067             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1068             // Change of the height (or width) won't change the preferred size.
1069             // So we don't need to flush the cache. We should however mark this node
1070             // as needs layout to be internally layouted.
1071             setNeedsLayout(true);
1072             // This call is only needed when this was not called from the parent during the layout.
1073             // Otherwise it would flush the cache of the parent, which is not necessary
1074             requestParentLayout();
1075         }
1076     }
1077 
1078     public final double getHeight() { return height == null ? _height : height.get(); }
1079 
1080     public final ReadOnlyDoubleProperty heightProperty() {
1081         if (height == null) {
1082             height = new ReadOnlyDoubleWrapper(_height) {
1083                 @Override protected void invalidated() { heightChanged(get()); }
1084                 @Override public Object getBean() { return Region.this; }
1085                 @Override public String getName() { return "height"; }
1086             };
1087         }
1088         return height.getReadOnlyProperty();
1089     }
1090 
1091     /**
1092      * This class is reused for the min, pref, and max properties since
1093      * they all performed the same function (to call requestParentLayout).
1094      */
1095     private final class MinPrefMaxProperty extends StyleableDoubleProperty {
1096         private final String name;
1097         private final CssMetaData<? extends Styleable, Number> cssMetaData;
1098 
1099         MinPrefMaxProperty(String name, double initialValue, CssMetaData<? extends Styleable, Number> cssMetaData) {
1100             super(initialValue);
1101             this.name = name;
1102             this.cssMetaData = cssMetaData;
1103         }
1104 
1105         @Override public void invalidated() { requestParentLayout(); }
1106         @Override public Object getBean() { return Region.this; }
1107         @Override public String getName() { return name; }
1108 
1109         @Override
1110         public CssMetaData<? extends Styleable, Number> getCssMetaData() {
1111             return cssMetaData;
1112         }
1113     }
1114 
1115     /**
1116      * Property for overriding the region's computed minimum width.
1117      * This should only be set if the region's internally computed minimum width
1118      * doesn't meet the application's layout needs.
1119      * <p>
1120      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1121      * <code>minWidth(forHeight)</code> will return the region's internally
1122      * computed minimum width.
1123      * <p>
1124      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1125      * <code>minWidth(forHeight)</code> to return the region's preferred width,
1126      * enabling applications to easily restrict the resizability of the region.
1127      */
1128     private DoubleProperty minWidth;
1129     private double _minWidth = USE_COMPUTED_SIZE;
1130     public final void setMinWidth(double value) {
1131         if (minWidth == null) {
1132             _minWidth = value;
1133             requestParentLayout();
1134         } else {
1135             minWidth.set(value);
1136         }
1137     }
1138     public final double getMinWidth() { return minWidth == null ? _minWidth : minWidth.get(); }
1139     public final DoubleProperty minWidthProperty() {
1140         if (minWidth == null) minWidth = new MinPrefMaxProperty("minWidth", _minWidth, StyleableProperties.MIN_WIDTH);
1141         return minWidth;
1142     }
1143 
1144     /**
1145      * Property for overriding the region's computed minimum height.
1146      * This should only be set if the region's internally computed minimum height
1147      * doesn't meet the application's layout needs.
1148      * <p>
1149      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1150      * <code>minHeight(forWidth)</code> will return the region's internally
1151      * computed minimum height.
1152      * <p>
1153      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1154      * <code>minHeight(forWidth)</code> to return the region's preferred height,
1155      * enabling applications to easily restrict the resizability of the region.
1156      *
1157      */
1158     private DoubleProperty minHeight;
1159     private double _minHeight = USE_COMPUTED_SIZE;
1160     public final void setMinHeight(double value) {
1161         if (minHeight == null) {
1162             _minHeight = value;
1163             requestParentLayout();
1164         } else {
1165             minHeight.set(value);
1166         }
1167     }
1168     public final double getMinHeight() { return minHeight == null ? _minHeight : minHeight.get(); }
1169     public final DoubleProperty minHeightProperty() {
1170         if (minHeight == null) minHeight = new MinPrefMaxProperty("minHeight", _minHeight, StyleableProperties.MIN_HEIGHT);
1171         return minHeight;
1172     }
1173 
1174     /**
1175      * Convenience method for overriding the region's computed minimum width and height.
1176      * This should only be called if the region's internally computed minimum size
1177      * doesn't meet the application's layout needs.
1178      *
1179      * @see #setMinWidth
1180      * @see #setMinHeight
1181      * @param minWidth  the override value for minimum width
1182      * @param minHeight the override value for minimum height
1183      */
1184     public void setMinSize(double minWidth, double minHeight) {
1185         setMinWidth(minWidth);
1186         setMinHeight(minHeight);
1187     }
1188 
1189     /**
1190      * Property for overriding the region's computed preferred width.
1191      * This should only be set if the region's internally computed preferred width
1192      * doesn't meet the application's layout needs.
1193      * <p>
1194      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1195      * <code>getPrefWidth(forHeight)</code> will return the region's internally
1196      * computed preferred width.
1197      */
1198     private DoubleProperty prefWidth;
1199     private double _prefWidth = USE_COMPUTED_SIZE;
1200     public final void setPrefWidth(double value) {
1201         if (prefWidth == null) {
1202             _prefWidth = value;
1203             requestParentLayout();
1204         } else {
1205             prefWidth.set(value);
1206         }
1207     }
1208     public final double getPrefWidth() { return prefWidth == null ? _prefWidth : prefWidth.get(); }
1209     public final DoubleProperty prefWidthProperty() {
1210         if (prefWidth == null) prefWidth = new MinPrefMaxProperty("prefWidth", _prefWidth, StyleableProperties.PREF_WIDTH);
1211         return prefWidth;
1212     }
1213 
1214     /**
1215      * Property for overriding the region's computed preferred height.
1216      * This should only be set if the region's internally computed preferred height
1217      * doesn't meet the application's layout needs.
1218      * <p>
1219      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1220      * <code>getPrefHeight(forWidth)</code> will return the region's internally
1221      * computed preferred width.
1222      */
1223     private DoubleProperty prefHeight;
1224     private double _prefHeight = USE_COMPUTED_SIZE;
1225     public final void setPrefHeight(double value) {
1226         if (prefHeight == null) {
1227             _prefHeight = value;
1228             requestParentLayout();
1229         } else {
1230             prefHeight.set(value);
1231         }
1232     }
1233     public final double getPrefHeight() { return prefHeight == null ? _prefHeight : prefHeight.get(); }
1234     public final DoubleProperty prefHeightProperty() {
1235         if (prefHeight == null) prefHeight = new MinPrefMaxProperty("prefHeight", _prefHeight, StyleableProperties.PREF_HEIGHT);
1236         return prefHeight;
1237     }
1238 
1239     /**
1240      * Convenience method for overriding the region's computed preferred width and height.
1241      * This should only be called if the region's internally computed preferred size
1242      * doesn't meet the application's layout needs.
1243      *
1244      * @see #setPrefWidth
1245      * @see #setPrefHeight
1246      * @param prefWidth the override value for preferred width
1247      * @param prefHeight the override value for preferred height
1248      */
1249     public void setPrefSize(double prefWidth, double prefHeight) {
1250         setPrefWidth(prefWidth);
1251         setPrefHeight(prefHeight);
1252     }
1253 
1254     /**
1255      * Property for overriding the region's computed maximum width.
1256      * This should only be set if the region's internally computed maximum width
1257      * doesn't meet the application's layout needs.
1258      * <p>
1259      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1260      * <code>getMaxWidth(forHeight)</code> will return the region's internally
1261      * computed maximum width.
1262      * <p>
1263      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1264      * <code>getMaxWidth(forHeight)</code> to return the region's preferred width,
1265      * enabling applications to easily restrict the resizability of the region.
1266      */
1267     private DoubleProperty maxWidth;
1268     private double _maxWidth = USE_COMPUTED_SIZE;
1269     public final void setMaxWidth(double value) {
1270         if (maxWidth == null) {
1271             _maxWidth = value;
1272             requestParentLayout();
1273         } else {
1274             maxWidth.set(value);
1275         }
1276     }
1277     public final double getMaxWidth() { return maxWidth == null ? _maxWidth : maxWidth.get(); }
1278     public final DoubleProperty maxWidthProperty() {
1279         if (maxWidth == null) maxWidth = new MinPrefMaxProperty("maxWidth", _maxWidth, StyleableProperties.MAX_WIDTH);
1280         return maxWidth;
1281     }
1282 
1283     /**
1284      * Property for overriding the region's computed maximum height.
1285      * This should only be set if the region's internally computed maximum height
1286      * doesn't meet the application's layout needs.
1287      * <p>
1288      * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
1289      * <code>getMaxHeight(forWidth)</code> will return the region's internally
1290      * computed maximum height.
1291      * <p>
1292      * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
1293      * <code>getMaxHeight(forWidth)</code> to return the region's preferred height,
1294      * enabling applications to easily restrict the resizability of the region.
1295      */
1296     private DoubleProperty maxHeight;
1297     private double _maxHeight = USE_COMPUTED_SIZE;
1298     public final void setMaxHeight(double value) {
1299         if (maxHeight == null) {
1300             _maxHeight = value;
1301             requestParentLayout();
1302         } else {
1303             maxHeight.set(value);
1304         }
1305     }
1306     public final double getMaxHeight() { return maxHeight == null ? _maxHeight : maxHeight.get(); }
1307     public final DoubleProperty maxHeightProperty() {
1308         if (maxHeight == null) maxHeight = new MinPrefMaxProperty("maxHeight", _maxHeight, StyleableProperties.MAX_HEIGHT);
1309         return maxHeight;
1310     }
1311 
1312     /**
1313      * Convenience method for overriding the region's computed maximum width and height.
1314      * This should only be called if the region's internally computed maximum size
1315      * doesn't meet the application's layout needs.
1316      *
1317      * @see #setMaxWidth
1318      * @see #setMaxHeight
1319      * @param maxWidth  the override value for maximum width
1320      * @param maxHeight the override value for maximum height
1321      */
1322     public void setMaxSize(double maxWidth, double maxHeight) {
1323         setMaxWidth(maxWidth);
1324         setMaxHeight(maxHeight);
1325     }
1326 
1327     /**
1328      * When specified, the {@code shape} will cause the region to be
1329      * rendered as the specified shape rather than as a rounded rectangle.
1330      * When null, the Region is rendered as a rounded rectangle. When rendered
1331      * as a Shape, any Background is used to fill the shape, although any
1332      * background insets are ignored as are background radii. Any BorderStrokes
1333      * defined are used for stroking the shape. Any BorderImages are ignored.
1334      *
1335      * @default null
1336      * @css shape SVG shape string
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      * @default true
1391      * @css shape-size      true | false
1392      * @since JavaFX 8.0
1393      */
1394     private BooleanProperty scaleShape = null;
1395     public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); }
1396     public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); }
1397     public final BooleanProperty scaleShapeProperty() {
1398         if (scaleShape == null) {
1399             scaleShape = new StyleableBooleanProperty(true) {
1400                 @Override public Object getBean() { return Region.this; }
1401                 @Override public String getName() { return "scaleShape"; }
1402                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1403                     return StyleableProperties.SCALE_SHAPE;
1404                 }
1405                 @Override public void invalidated() {
1406                     NodeHelper.geomChanged(Region.this);
1407                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1408                 }
1409             };
1410         }
1411         return scaleShape;
1412     }
1413 
1414     /**
1415      * Defines whether the shape is centered within the Region's width or height.
1416      * {@code true} means the shape centered within the Region's width and height,
1417      * {@code false} means the shape is positioned at its source position.
1418      *
1419      * @default true
1420      * @css position-shape      true | false
1421      * @since JavaFX 8.0
1422      */
1423     private BooleanProperty centerShape = null;
1424     public final void setCenterShape(boolean value) { centerShapeProperty().set(value); }
1425     public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); }
1426     public final BooleanProperty centerShapeProperty() {
1427         if (centerShape == null) {
1428             centerShape = new StyleableBooleanProperty(true) {
1429                 @Override public Object getBean() { return Region.this; }
1430                 @Override public String getName() { return "centerShape"; }
1431                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1432                     return StyleableProperties.POSITION_SHAPE;
1433                 }
1434                 @Override public void invalidated() {
1435                     NodeHelper.geomChanged(Region.this);
1436                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1437                 }
1438             };
1439         }
1440         return centerShape;
1441     }
1442 
1443     /**
1444      * Defines a hint to the system indicating that the Shape used to define the region's
1445      * background is stable and would benefit from caching.
1446      *
1447      * @default true
1448      * @css -fx-cache-shape      true | false
1449      * @since JavaFX 8.0
1450      */
1451     private BooleanProperty cacheShape = null;
1452     public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); }
1453     public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); }
1454     public final BooleanProperty cacheShapeProperty() {
1455         if (cacheShape == null) {
1456             cacheShape = new StyleableBooleanProperty(true) {
1457                 @Override public Object getBean() { return Region.this; }
1458                 @Override public String getName() { return "cacheShape"; }
1459                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1460                     return StyleableProperties.CACHE_SHAPE;
1461                 }
1462             };
1463         }
1464         return cacheShape;
1465     }
1466 
1467     /***************************************************************************
1468      *                                                                         *
1469      * Layout                                                                  *
1470      *                                                                         *
1471      **************************************************************************/
1472 
1473     /**
1474      * Returns <code>true</code> since all Regions are resizable.
1475      * @return whether this node can be resized by its parent during layout
1476      */
1477     @Override public boolean isResizable() {
1478         return true;
1479     }
1480 
1481     /**
1482      * Invoked by the region's parent during layout to set the region's
1483      * width and height.  <b>Applications should not invoke this method directly</b>.
1484      * If an application needs to directly set the size of the region, it should
1485      * override its size constraints by calling <code>setMinSize()</code>,
1486      *  <code>setPrefSize()</code>, or <code>setMaxSize()</code> and it's parent
1487      * will honor those overrides during layout.
1488      *
1489      * @param width the target layout bounds width
1490      * @param height the target layout bounds height
1491      */
1492     @Override public void resize(double width, double height) {
1493         setWidth(width);
1494         setHeight(height);
1495         PlatformLogger logger = Logging.getLayoutLogger();
1496         if (logger.isLoggable(Level.FINER)) {
1497             logger.finer(this.toString() + " resized to " + width + " x " + height);
1498         }
1499     }
1500 
1501     /**
1502      * Called during layout to determine the minimum width for this node.
1503      * Returns the value from <code>computeMinWidth(forHeight)</code> unless
1504      * the application overrode the minimum width by setting the minWidth property.
1505      *
1506      * @see #setMinWidth(double)
1507      * @return the minimum width that this node should be resized to during layout
1508      */
1509     @Override public final double minWidth(double height) {
1510         final double override = getMinWidth();
1511         if (override == USE_COMPUTED_SIZE) {
1512             return super.minWidth(height);
1513         } else if (override == USE_PREF_SIZE) {
1514             return prefWidth(height);
1515         }
1516         return Double.isNaN(override) || override < 0 ? 0 : override;
1517     }
1518 
1519     /**
1520      * Called during layout to determine the minimum height for this node.
1521      * Returns the value from <code>computeMinHeight(forWidth)</code> unless
1522      * the application overrode the minimum height by setting the minHeight property.
1523      *
1524      * @see #setMinHeight
1525      * @return the minimum height that this node should be resized to during layout
1526      */
1527     @Override public final double minHeight(double width) {
1528         final double override = getMinHeight();
1529         if (override == USE_COMPUTED_SIZE) {
1530             return super.minHeight(width);
1531         } else if (override == USE_PREF_SIZE) {
1532             return prefHeight(width);
1533         }
1534         return Double.isNaN(override) || override < 0 ? 0 : override;
1535     }
1536 
1537     /**
1538      * Called during layout to determine the preferred width for this node.
1539      * Returns the value from <code>computePrefWidth(forHeight)</code> unless
1540      * the application overrode the preferred width by setting the prefWidth property.
1541      *
1542      * @see #setPrefWidth
1543      * @return the preferred width that this node should be resized to during layout
1544      */
1545     @Override public final double prefWidth(double height) {
1546         final double override = getPrefWidth();
1547         if (override == USE_COMPUTED_SIZE) {
1548             return super.prefWidth(height);
1549         }
1550         return Double.isNaN(override) || override < 0 ? 0 : override;
1551     }
1552 
1553     /**
1554      * Called during layout to determine the preferred height for this node.
1555      * Returns the value from <code>computePrefHeight(forWidth)</code> unless
1556      * the application overrode the preferred height by setting the prefHeight property.
1557      *
1558      * @see #setPrefHeight
1559      * @return the preferred height that this node should be resized to during layout
1560      */
1561     @Override public final double prefHeight(double width) {
1562         final double override = getPrefHeight();
1563         if (override == USE_COMPUTED_SIZE) {
1564             return super.prefHeight(width);
1565         }
1566         return Double.isNaN(override) || override < 0 ? 0 : override;
1567     }
1568 
1569     /**
1570      * Called during layout to determine the maximum width for this node.
1571      * Returns the value from <code>computeMaxWidth(forHeight)</code> unless
1572      * the application overrode the maximum width by setting the maxWidth property.
1573      *
1574      * @see #setMaxWidth
1575      * @return the maximum width that this node should be resized to during layout
1576      */
1577     @Override public final double maxWidth(double height) {
1578         final double override = getMaxWidth();
1579         if (override == USE_COMPUTED_SIZE) {
1580             return computeMaxWidth(height);
1581         } else if (override == USE_PREF_SIZE) {
1582             return prefWidth(height);
1583         }
1584         return Double.isNaN(override) || override < 0 ? 0 : override;
1585     }
1586 
1587     /**
1588      * Called during layout to determine the maximum height for this node.
1589      * Returns the value from <code>computeMaxHeight(forWidth)</code> unless
1590      * the application overrode the maximum height by setting the maxHeight property.
1591      *
1592      * @see #setMaxHeight
1593      * @return the maximum height that this node should be resized to during layout
1594      */
1595     @Override public final double maxHeight(double width) {
1596         final double override = getMaxHeight();
1597         if (override == USE_COMPUTED_SIZE) {
1598             return computeMaxHeight(width);
1599         } else if (override == USE_PREF_SIZE) {
1600             return prefHeight(width);
1601         }
1602         return Double.isNaN(override) || override < 0 ? 0 : override;
1603     }
1604 
1605     /**
1606      * Computes the minimum width of this region.
1607      * Returns the sum of the left and right insets by default.
1608      * region subclasses should override this method to return an appropriate
1609      * value based on their content and layout strategy.  If the subclass
1610      * doesn't have a VERTICAL content bias, then the height parameter can be
1611      * ignored.
1612      *
1613      * @return the computed minimum width of this region
1614      */
1615     @Override protected double computeMinWidth(double height) {
1616         return getInsets().getLeft() + getInsets().getRight();
1617     }
1618 
1619     /**
1620      * Computes the minimum height of this region.
1621      * Returns the sum of the top and bottom insets by default.
1622      * Region subclasses should override this method to return an appropriate
1623      * value based on their content and layout strategy.  If the subclass
1624      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1625      * ignored.
1626      *
1627      * @return the computed minimum height for this region
1628      */
1629     @Override protected double computeMinHeight(double width) {
1630         return getInsets().getTop() + getInsets().getBottom();
1631     }
1632 
1633     /**
1634      * Computes the preferred width of this region for the given height.
1635      * Region subclasses should override this method to return an appropriate
1636      * value based on their content and layout strategy.  If the subclass
1637      * doesn't have a VERTICAL content bias, then the height parameter can be
1638      * ignored.
1639      *
1640      * @return the computed preferred width for this region
1641      */
1642     @Override protected double computePrefWidth(double height) {
1643         final double w = super.computePrefWidth(height);
1644         return getInsets().getLeft() + w + getInsets().getRight();
1645     }
1646 
1647     /**
1648      * Computes the preferred height of this region for the given width;
1649      * Region subclasses should override this method to return an appropriate
1650      * value based on their content and layout strategy.  If the subclass
1651      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1652      * ignored.
1653      *
1654      * @return the computed preferred height for this region
1655      */
1656     @Override protected double computePrefHeight(double width) {
1657         final double h = super.computePrefHeight(width);
1658         return getInsets().getTop() + h + getInsets().getBottom();
1659     }
1660 
1661     /**
1662      * Computes the maximum width for this region.
1663      * Returns Double.MAX_VALUE by default.
1664      * Region subclasses may override this method to return an different
1665      * value based on their content and layout strategy.  If the subclass
1666      * doesn't have a VERTICAL content bias, then the height parameter can be
1667      * ignored.
1668      *
1669      * @return the computed maximum width for this region
1670      */
1671     protected double computeMaxWidth(double height) {
1672         return Double.MAX_VALUE;
1673     }
1674 
1675     /**
1676      * Computes the maximum height of this region.
1677      * Returns Double.MAX_VALUE by default.
1678      * Region subclasses may override this method to return a different
1679      * value based on their content and layout strategy.  If the subclass
1680      * doesn't have a HORIZONTAL content bias, then the width parameter can be
1681      * ignored.
1682      *
1683      * @return the computed maximum height for this region
1684      */
1685     protected double computeMaxHeight(double width) {
1686         return Double.MAX_VALUE;
1687     }
1688 
1689     /**
1690      * If this region's snapToPixel property is false, this method returns the
1691      * same value, else it tries to return a value rounded to the nearest
1692      * pixel, but since there is no indication if the value is a vertical
1693      * or horizontal measurement then it may be snapped to the wrong pixel
1694      * size metric on screens with different horizontal and vertical scales.
1695      * @param value the space value to be snapped
1696      * @return value rounded to nearest pixel
1697      * @deprecated replaced by {@code snapSpaceX()} and {@code snapSpaceY()}
1698      */
1699     @Deprecated
1700     protected double snapSpace(double value) {
1701         return snapSpaceX(value, isSnapToPixel());
1702     }
1703 
1704     /**
1705      * If this region's snapToPixel property is true, returns a value rounded
1706      * to the nearest pixel in the horizontal direction, else returns the
1707      * same value.
1708      * @param value the space value to be snapped
1709      * @return value rounded to nearest pixel
1710      * @since 9
1711      */
1712     protected double snapSpaceX(double value) {
1713         return snapSpaceX(value, isSnapToPixel());
1714     }
1715 
1716     /**
1717      * If this region's snapToPixel property is true, returns a value rounded
1718      * to the nearest pixel in the vertical direction, else returns the
1719      * same value.
1720      * @param value the space value to be snapped
1721      * @return value rounded to nearest pixel
1722      * @since 9
1723      */
1724     protected double snapSpaceY(double value) {
1725         return snapSpaceY(value, isSnapToPixel());
1726     }
1727 
1728     /**
1729      * If this region's snapToPixel property is false, this method returns the
1730      * same value, else it tries to return a value ceiled to the nearest
1731      * pixel, but since there is no indication if the value is a vertical
1732      * or horizontal measurement then it may be snapped to the wrong pixel
1733      * size metric on screens with different horizontal and vertical scales.
1734      * @param value the size value to be snapped
1735      * @return value ceiled to nearest pixel
1736      * @deprecated replaced by {@code snapSizeX()} and {@code snapSizeY()}
1737      */
1738     @Deprecated
1739     protected double snapSize(double value) {
1740         return snapSizeX(value, isSnapToPixel());
1741     }
1742 
1743     /**
1744      * If this region's snapToPixel property is true, returns a value ceiled
1745      * to the nearest pixel in the horizontal direction, else returns the
1746      * same value.
1747      * @param value the size value to be snapped
1748      * @return value ceiled to nearest pixel
1749      * @since 9
1750      */
1751     protected double snapSizeX(double value) {
1752         return snapSizeX(value, isSnapToPixel());
1753     }
1754 
1755     /**
1756      * If this region's snapToPixel property is true, returns a value ceiled
1757      * to the nearest pixel in the vertical direction, else returns the
1758      * same value.
1759      * @param value the size value to be snapped
1760      * @return value ceiled to nearest pixel
1761      * @since 9
1762      */
1763     protected double snapSizeY(double value) {
1764         return snapSizeY(value, isSnapToPixel());
1765     }
1766 
1767     /**
1768      * If this region's snapToPixel property is false, this method returns the
1769      * same value, else it tries to return a value rounded to the nearest
1770      * pixel, but since there is no indication if the value is a vertical
1771      * or horizontal measurement then it may be snapped to the wrong pixel
1772      * size metric on screens with different horizontal and vertical scales.
1773      * @param value the position value to be snapped
1774      * @return value rounded to nearest pixel
1775      * @deprecated replaced by {@code snapPositionX()} and {@code snapPositionY()}
1776      */
1777     @Deprecated
1778     protected double snapPosition(double value) {
1779         return snapPositionX(value, isSnapToPixel());
1780     }
1781 
1782     /**
1783      * If this region's snapToPixel property is true, returns a value rounded
1784      * to the nearest pixel in the horizontal direction, else returns the
1785      * same value.
1786      * @param value the position value to be snapped
1787      * @return value rounded to nearest pixel
1788      * @since 9
1789      */
1790     protected double snapPositionX(double value) {
1791         return snapPositionX(value, isSnapToPixel());
1792     }
1793 
1794     /**
1795      * If this region's snapToPixel property is true, returns a value rounded
1796      * to the nearest pixel in the vertical direction, else returns the
1797      * same value.
1798      * @param value the position value to be snapped
1799      * @return value rounded to nearest pixel
1800      * @since 9
1801      */
1802     protected double snapPositionY(double value) {
1803         return snapPositionY(value, isSnapToPixel());
1804     }
1805 
1806     double snapPortionX(double value) {
1807         return snapPortionX(value, isSnapToPixel());
1808     }
1809     double snapPortionY(double value) {
1810         return snapPortionY(value, isSnapToPixel());
1811     }
1812 
1813 
1814     /**
1815      * Utility method to get the top inset which includes padding and border
1816      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1817      *
1818      * @since JavaFX 8.0
1819      * @return Rounded up insets top
1820      */
1821     public final double snappedTopInset() {
1822         return snappedTopInset;
1823     }
1824 
1825     /**
1826      * Utility method to get the bottom inset which includes padding and border
1827      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1828      *
1829      * @since JavaFX 8.0
1830      * @return Rounded up insets bottom
1831      */
1832     public final double snappedBottomInset() {
1833         return snappedBottomInset;
1834     }
1835 
1836     /**
1837      * Utility method to get the left inset which includes padding and border
1838      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1839      *
1840      * @since JavaFX 8.0
1841      * @return Rounded up insets left
1842      */
1843     public final double snappedLeftInset() {
1844         return snappedLeftInset;
1845     }
1846 
1847     /**
1848      * Utility method to get the right inset which includes padding and border
1849      * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1850      *
1851      * @since JavaFX 8.0
1852      * @return Rounded up insets right
1853      */
1854     public final double snappedRightInset() {
1855         return snappedRightInset;
1856     }
1857 
1858 
1859     double computeChildMinAreaWidth(Node child, Insets margin) {
1860         return computeChildMinAreaWidth(child, -1, margin, -1, false);
1861     }
1862 
1863     double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1864         final boolean snap = isSnapToPixel();
1865         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1866         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1867         double alt = -1;
1868         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1869             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1870             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1871             double bo = child.getBaselineOffset();
1872             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1873                     height - top - bottom - baselineComplement :
1874                      height - top - bottom;
1875             if (fillHeight) {
1876                 alt = snapSizeY(boundedSize(
1877                         child.minHeight(-1), contentHeight,
1878                         child.maxHeight(-1)));
1879             } else {
1880                 alt = snapSizeY(boundedSize(
1881                         child.minHeight(-1),
1882                         child.prefHeight(-1),
1883                         Math.min(child.maxHeight(-1), contentHeight)));
1884             }
1885         }
1886         return left + snapSizeX(child.minWidth(alt)) + right;
1887     }
1888 
1889     double computeChildMinAreaHeight(Node child, Insets margin) {
1890         return computeChildMinAreaHeight(child, -1, margin, -1);
1891     }
1892 
1893     double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
1894         final boolean snap = isSnapToPixel();
1895         double top =margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1896         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1897 
1898         double alt = -1;
1899         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1900             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1901             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1902             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
1903                     child.maxWidth(-1));
1904         }
1905 
1906         // For explanation, see computeChildPrefAreaHeight
1907         if (minBaselineComplement != -1) {
1908             double baseline = child.getBaselineOffset();
1909             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1910                 return top + snapSizeY(child.minHeight(alt)) + bottom
1911                         + minBaselineComplement;
1912             } else {
1913                 return baseline + minBaselineComplement;
1914             }
1915         } else {
1916             return top + snapSizeY(child.minHeight(alt)) + bottom;
1917         }
1918     }
1919 
1920     double computeChildPrefAreaWidth(Node child, Insets margin) {
1921         return computeChildPrefAreaWidth(child, -1, margin, -1, false);
1922     }
1923 
1924     double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1925         final boolean snap = isSnapToPixel();
1926         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1927         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1928         double alt = -1;
1929         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1930             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1931             double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1932             double bo = child.getBaselineOffset();
1933             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1934                     height - top - bottom - baselineComplement :
1935                      height - top - bottom;
1936             if (fillHeight) {
1937                 alt = snapSizeY(boundedSize(
1938                         child.minHeight(-1), contentHeight,
1939                         child.maxHeight(-1)));
1940             } else {
1941                 alt = snapSizeY(boundedSize(
1942                         child.minHeight(-1),
1943                         child.prefHeight(-1),
1944                         Math.min(child.maxHeight(-1), contentHeight)));
1945             }
1946         }
1947         return left + snapSizeX(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
1948     }
1949 
1950     double computeChildPrefAreaHeight(Node child, Insets margin) {
1951         return computeChildPrefAreaHeight(child, -1, margin, -1);
1952     }
1953 
1954     double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
1955         final boolean snap = isSnapToPixel();
1956         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1957         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
1958 
1959         double alt = -1;
1960         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1961             double left = margin != null ? snapSpaceX(margin.getLeft(), snap) : 0;
1962             double right = margin != null ? snapSpaceX(margin.getRight(), snap) : 0;
1963             alt = snapSizeX(boundedSize(
1964                     child.minWidth(-1), width != -1 ? width - left - right
1965                     : child.prefWidth(-1), child.maxWidth(-1)));
1966         }
1967 
1968         if (prefBaselineComplement != -1) {
1969             double baseline = child.getBaselineOffset();
1970             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1971                 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
1972                 // the preferred complement to it
1973                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
1974                         + prefBaselineComplement;
1975             } else {
1976                 // For all other Nodes, it's just their baseline and the complement.
1977                 // Note that the complement already contain the Node's preferred (or fixed) height
1978                 return top + baseline + prefBaselineComplement + bottom;
1979             }
1980         } else {
1981             return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
1982         }
1983     }
1984 
1985     double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
1986         double max = child.maxWidth(-1);
1987         if (max == Double.MAX_VALUE) {
1988             return max;
1989         }
1990         final boolean snap = isSnapToPixel();
1991         double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
1992         double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
1993         double alt = -1;
1994         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1995             double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
1996             double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
1997             double bo = child.getBaselineOffset();
1998             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
1999                     height - top - bottom - baselineComplement :
2000                      height - top - bottom;
2001             if (fillHeight) {
2002                 alt = snapSizeY(boundedSize(
2003                         child.minHeight(-1), contentHeight,
2004                         child.maxHeight(-1)));
2005             } else {
2006                 alt = snapSizeY(boundedSize(
2007                         child.minHeight(-1),
2008                         child.prefHeight(-1),
2009                         Math.min(child.maxHeight(-1), contentHeight)));
2010             }
2011             max = child.maxWidth(alt);
2012         }
2013         // if min > max, min wins, so still need to call boundedSize()
2014         return left + snapSizeX(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
2015     }
2016 
2017     double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
2018         double max = child.maxHeight(-1);
2019         if (max == Double.MAX_VALUE) {
2020             return max;
2021         }
2022 
2023         final boolean snap = isSnapToPixel();
2024         double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
2025         double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
2026         double alt = -1;
2027         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
2028             double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
2029             double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
2030             alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
2031                 child.minWidth(-1));
2032             max = child.maxHeight(alt);
2033         }
2034         // For explanation, see computeChildPrefAreaHeight
2035         if (maxBaselineComplement != -1) {
2036             double baseline = child.getBaselineOffset();
2037             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2038                 return top + snapSizeY(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
2039                         + maxBaselineComplement;
2040             } else {
2041                 return top + baseline + maxBaselineComplement + bottom;
2042             }
2043         } else {
2044             // if min > max, min wins, so still need to call boundedSize()
2045             return top + snapSizeY(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
2046         }
2047     }
2048 
2049     /* Max of children's minimum area widths */
2050 
2051     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins) {
2052         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, true);
2053     }
2054 
2055     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins, double height, boolean fillHeight) {
2056         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, true);
2057     }
2058 
2059     double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight) {
2060         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, true);
2061     }
2062 
2063     /* Max of children's minimum area heights */
2064 
2065     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {
2066         return getMaxAreaHeight(children, margins, null, valignment, true);
2067     }
2068 
2069     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment, double width) {
2070         return getMaxAreaHeight(children, margins, new double[] { width }, valignment, true);
2071     }
2072 
2073     double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) {
2074         return getMaxAreaHeight(children, childMargins, childWidths, valignment, true);
2075     }
2076 
2077     /* Max of children's pref area widths */
2078 
2079     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins) {
2080         return getMaxAreaWidth(children, margins, new double[] { -1 }, false, false);
2081     }
2082 
2083     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins, double height,
2084             boolean fillHeight) {
2085         return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, false);
2086     }
2087 
2088     double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> childMargins,
2089             double childHeights[], boolean fillHeight) {
2090         return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, false);
2091     }
2092 
2093     /* Max of children's pref area heights */
2094 
2095     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) {
2096         return getMaxAreaHeight(children, margins, null, valignment, false);
2097     }
2098 
2099     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, double width, VPos valignment) {
2100         return getMaxAreaHeight(children, margins, new double[] { width }, valignment, false);
2101     }
2102 
2103     double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) {
2104         return getMaxAreaHeight(children, childMargins, childWidths, valignment, false);
2105     }
2106 
2107     /**
2108      * Returns the size of a Node that should be placed in an area of the specified size,
2109      * bounded in it's min/max size, respecting bias.
2110      *
2111      * @param node the node
2112      * @param areaWidth the width of the bounding area where the node is going to be placed
2113      * @param areaHeight the height of the bounding area where the node is going to be placed
2114      * @param fillWidth if Node should try to fill the area width
2115      * @param fillHeight if Node should try to fill the area height
2116      * @param result Vec2d object for the result or null if new one should be created
2117      * @return Vec2d object with width(x parameter) and height (y parameter)
2118      */
2119     static Vec2d boundedNodeSizeWithBias(Node node, double areaWidth, double areaHeight,
2120             boolean fillWidth, boolean fillHeight, Vec2d result) {
2121         if (result == null) {
2122             result = new Vec2d();
2123         }
2124 
2125         Orientation bias = node.getContentBias();
2126 
2127         double childWidth = 0;
2128         double childHeight = 0;
2129 
2130         if (bias == null) {
2131             childWidth = boundedSize(
2132                     node.minWidth(-1), fillWidth ? areaWidth
2133                     : Math.min(areaWidth, node.prefWidth(-1)),
2134                     node.maxWidth(-1));
2135             childHeight = boundedSize(
2136                     node.minHeight(-1), fillHeight ? areaHeight
2137                     : Math.min(areaHeight, node.prefHeight(-1)),
2138                     node.maxHeight(-1));
2139 
2140         } else if (bias == Orientation.HORIZONTAL) {
2141             childWidth = boundedSize(
2142                     node.minWidth(-1), fillWidth ? areaWidth
2143                     : Math.min(areaWidth, node.prefWidth(-1)),
2144                     node.maxWidth(-1));
2145             childHeight = boundedSize(
2146                     node.minHeight(childWidth), fillHeight ? areaHeight
2147                     : Math.min(areaHeight, node.prefHeight(childWidth)),
2148                     node.maxHeight(childWidth));
2149 
2150         } else { // bias == VERTICAL
2151             childHeight = boundedSize(
2152                     node.minHeight(-1), fillHeight ? areaHeight
2153                     : Math.min(areaHeight, node.prefHeight(-1)),
2154                     node.maxHeight(-1));
2155             childWidth = boundedSize(
2156                     node.minWidth(childHeight), fillWidth ? areaWidth
2157                     : Math.min(areaWidth, node.prefWidth(childHeight)),
2158                     node.maxWidth(childHeight));
2159         }
2160 
2161         result.set(childWidth, childHeight);
2162         return result;
2163     }
2164 
2165     /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */
2166     private double getMaxAreaHeight(List<Node> children, Callback<Node,Insets> childMargins,  double childWidths[], VPos valignment, boolean minimum) {
2167         final double singleChildWidth = childWidths == null ? -1 : childWidths.length == 1 ? childWidths[0] : Double.NaN;
2168         if (valignment == VPos.BASELINE) {
2169             double maxAbove = 0;
2170             double maxBelow = 0;
2171             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2172                 final Node child = children.get(i);
2173                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2174                 Insets margin = childMargins.call(child);
2175                 final double top = margin != null? snapSpaceY(margin.getTop()) : 0;
2176                 final double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
2177                 final double baseline = child.getBaselineOffset();
2178 
2179                 final double childHeight = minimum? snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth));
2180                 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2181                     maxAbove = Math.max(maxAbove, childHeight + top);
2182                 } else {
2183                     maxAbove = Math.max(maxAbove, baseline + top);
2184                     maxBelow = Math.max(maxBelow,
2185                             snapSpaceY(minimum?snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth))) -
2186                             baseline + bottom);
2187                 }
2188             }
2189             return maxAbove + maxBelow; //remind(aim): ceil this value?
2190         } else {
2191             double max = 0;
2192             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2193                 final Node child = children.get(i);
2194                 Insets margin = childMargins.call(child);
2195                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
2196                 max = Math.max(max, minimum?
2197                     computeChildMinAreaHeight(child, -1, margin, childWidth) :
2198                         computeChildPrefAreaHeight(child, -1, margin, childWidth));
2199             }
2200             return max;
2201         }
2202     }
2203 
2204     /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */
2205     private double getMaxAreaWidth(List<javafx.scene.Node> children,
2206             Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight, boolean minimum) {
2207         final double singleChildHeight = childHeights == null ? -1 : childHeights.length == 1 ? childHeights[0] : Double.NaN;
2208 
2209         double max = 0;
2210         for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
2211             final Node child = children.get(i);
2212             final Insets margin = childMargins.call(child);
2213             final double childHeight = Double.isNaN(singleChildHeight) ? childHeights[i] : singleChildHeight;
2214             max = Math.max(max, minimum?
2215                 computeChildMinAreaWidth(children.get(i), -1, margin, childHeight, fillHeight) :
2216                     computeChildPrefAreaWidth(child, -1, margin, childHeight, fillHeight));
2217         }
2218         return max;
2219     }
2220 
2221     /**
2222      * Utility method which positions the child within an area of this
2223      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2224      * with a baseline offset relative to that area.
2225      * <p>
2226      * This function does <i>not</i> resize the node and uses the node's layout bounds
2227      * width and height to determine how it should be positioned within the area.
2228      * <p>
2229      * If the vertical alignment is {@code VPos.BASELINE} then it
2230      * will position the node so that its own baseline aligns with the passed in
2231      * {@code baselineOffset},  otherwise the baseline parameter is ignored.
2232      * <p>
2233      * If {@code snapToPixel} is {@code true} for this region, then the x/y position
2234      * values will be rounded to their nearest pixel boundaries.
2235      *
2236      * @param child the child being positioned within this region
2237      * @param areaX the horizontal offset of the layout area relative to this region
2238      * @param areaY the vertical offset of the layout area relative to this region
2239      * @param areaWidth  the width of the layout area
2240      * @param areaHeight the height of the layout area
2241      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2242      * @param halignment the horizontal alignment for the child within the area
2243      * @param valignment the vertical alignment for the child within the area
2244      *
2245      */
2246     protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2247                                double areaBaselineOffset, HPos halignment, VPos valignment) {
2248         positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2249                 Insets.EMPTY, halignment, valignment, isSnapToPixel());
2250     }
2251 
2252     /**
2253      * Utility method which positions the child within an area of this
2254      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2255      * with a baseline offset relative to that area.
2256      * <p>
2257      * This function does <i>not</i> resize the node and uses the node's layout bounds
2258      * width and height to determine how it should be positioned within the area.
2259      * <p>
2260      * If the vertical alignment is {@code VPos.BASELINE} then it
2261      * will position the node so that its own baseline aligns with the passed in
2262      * {@code baselineOffset},  otherwise the baseline parameter is ignored.
2263      * <p>
2264      * If {@code snapToPixel} is {@code true} for this region, then the x/y position
2265      * values will be rounded to their nearest pixel boundaries.
2266      * <p>
2267      * If {@code margin} is non-null, then that space will be allocated around the
2268      * child within the layout area.  margin may be null.
2269      *
2270      * @param child the child being positioned within this region
2271      * @param areaX the horizontal offset of the layout area relative to this region
2272      * @param areaY the vertical offset of the layout area relative to this region
2273      * @param areaWidth  the width of the layout area
2274      * @param areaHeight the height of the layout area
2275      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2276      * @param margin the margin of space to be allocated around the child
2277      * @param halignment the horizontal alignment for the child within the area
2278      * @param valignment the vertical alignment for the child within the area
2279      *
2280      * @since JavaFX 8.0
2281      */
2282     public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2283                                double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
2284         Insets childMargin = margin != null? margin : Insets.EMPTY;
2285         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2286         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2287 
2288         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2289                 snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY),
2290                 snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX),
2291                 snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY),
2292                 snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX),
2293                 halignment, valignment, isSnapToPixel);
2294     }
2295 
2296     /**
2297      * Utility method which lays out the child within an area of this
2298      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2299      * with a baseline offset relative to that area.
2300      * <p>
2301      * If the child is resizable, this method will resize it to fill the specified
2302      * area unless the node's maximum size prevents it.  If the node's maximum
2303      * size preference is less than the area size, the maximum size will be used.
2304      * If node's maximum is greater than the area size, then the node will be
2305      * resized to fit within the area, unless its minimum size prevents it.
2306      * <p>
2307      * If the child has a non-null contentBias, then this method will use it when
2308      * resizing the child.  If the contentBias is horizontal, it will set its width
2309      * first to the area's width (up to the child's max width limit) and then pass
2310      * that value to compute the child's height.  If child's contentBias is vertical,
2311      * then it will set its height to the area height (up to child's max height limit)
2312      * and pass that height to compute the child's width.  If the child's contentBias
2313      * is null, then it's width and height have no dependencies on each other.
2314      * <p>
2315      * If the child is not resizable (Shape, Group, etc) then it will only be
2316      * positioned and not resized.
2317      * <p>
2318      * If the child's resulting size differs from the area's size (either
2319      * because it was not resizable or it's sizing preferences prevented it), then
2320      * this function will align the node relative to the area using horizontal and
2321      * vertical alignment values.
2322      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2323      * with the area baseline offset parameter, otherwise the baseline parameter
2324      * is ignored.
2325      * <p>
2326      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2327      * values will be rounded to their nearest pixel boundaries and the
2328      * width/height values will be ceiled to the next pixel boundary.
2329      *
2330      * @param child the child being positioned within this region
2331      * @param areaX the horizontal offset of the layout area relative to this region
2332      * @param areaY the vertical offset of the layout area relative to this region
2333      * @param areaWidth  the width of the layout area
2334      * @param areaHeight the height of the layout area
2335      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2336      * @param halignment the horizontal alignment for the child within the area
2337      * @param valignment the vertical alignment for the child within the area
2338      *
2339      */
2340     protected void layoutInArea(Node child, double areaX, double areaY,
2341                                double areaWidth, double areaHeight,
2342                                double areaBaselineOffset,
2343                                HPos halignment, VPos valignment) {
2344         layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2345                 Insets.EMPTY, halignment, valignment);
2346     }
2347 
2348     /**
2349      * Utility method which lays out the child within an area of this
2350      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2351      * with a baseline offset relative to that area.
2352      * <p>
2353      * If the child is resizable, this method will resize it to fill the specified
2354      * area unless the node's maximum size prevents it.  If the node's maximum
2355      * size preference is less than the area size, the maximum size will be used.
2356      * If node's maximum is greater than the area size, then the node will be
2357      * resized to fit within the area, unless its minimum size prevents it.
2358      * <p>
2359      * If the child has a non-null contentBias, then this method will use it when
2360      * resizing the child.  If the contentBias is horizontal, it will set its width
2361      * first to the area's width (up to the child's max width limit) and then pass
2362      * that value to compute the child's height.  If child's contentBias is vertical,
2363      * then it will set its height to the area height (up to child's max height limit)
2364      * and pass that height to compute the child's width.  If the child's contentBias
2365      * is null, then it's width and height have no dependencies on each other.
2366      * <p>
2367      * If the child is not resizable (Shape, Group, etc) then it will only be
2368      * positioned and not resized.
2369      * <p>
2370      * If the child's resulting size differs from the area's size (either
2371      * because it was not resizable or it's sizing preferences prevented it), then
2372      * this function will align the node relative to the area using horizontal and
2373      * vertical alignment values.
2374      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2375      * with the area baseline offset parameter, otherwise the baseline parameter
2376      * is ignored.
2377      * <p>
2378      * If {@code margin} is non-null, then that space will be allocated around the
2379      * child within the layout area.  margin may be null.
2380      * <p>
2381      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2382      * values will be rounded to their nearest pixel boundaries and the
2383      * width/height values will be ceiled to the next pixel boundary.
2384      *
2385      * @param child the child being positioned within this region
2386      * @param areaX the horizontal offset of the layout area relative to this region
2387      * @param areaY the vertical offset of the layout area relative to this region
2388      * @param areaWidth  the width of the layout area
2389      * @param areaHeight the height of the layout area
2390      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2391      * @param margin the margin of space to be allocated around the child
2392      * @param halignment the horizontal alignment for the child within the area
2393      * @param valignment the vertical alignment for the child within the area
2394      */
2395     protected void layoutInArea(Node child, double areaX, double areaY,
2396                                double areaWidth, double areaHeight,
2397                                double areaBaselineOffset,
2398                                Insets margin,
2399                                HPos halignment, VPos valignment) {
2400         layoutInArea(child, areaX, areaY, areaWidth, areaHeight,
2401                 areaBaselineOffset, margin, true, true, halignment, valignment);
2402     }
2403 
2404     /**
2405      * Utility method which lays out the child within an area of this
2406      * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2407      * with a baseline offset relative to that area.
2408      * <p>
2409      * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
2410      * to determine whether to resize it to fill the area or keep the child at its
2411      * preferred dimension.  If fillWidth/fillHeight are true, then this method
2412      * will only resize the child up to its max size limits.  If the node's maximum
2413      * size preference is less than the area size, the maximum size will be used.
2414      * If node's maximum is greater than the area size, then the node will be
2415      * resized to fit within the area, unless its minimum size prevents it.
2416      * <p>
2417      * If the child has a non-null contentBias, then this method will use it when
2418      * resizing the child.  If the contentBias is horizontal, it will set its width
2419      * first and then pass that value to compute the child's height.  If child's
2420      * contentBias is vertical, then it will set its height first
2421      * and pass that value to compute the child's width.  If the child's contentBias
2422      * is null, then it's width and height have no dependencies on each other.
2423      * <p>
2424      * If the child is not resizable (Shape, Group, etc) then it will only be
2425      * positioned and not resized.
2426      * <p>
2427      * If the child's resulting size differs from the area's size (either
2428      * because it was not resizable or it's sizing preferences prevented it), then
2429      * this function will align the node relative to the area using horizontal and
2430      * vertical alignment values.
2431      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2432      * with the area baseline offset parameter, otherwise the baseline parameter
2433      * is ignored.
2434      * <p>
2435      * If {@code margin} is non-null, then that space will be allocated around the
2436      * child within the layout area.  margin may be null.
2437      * <p>
2438      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2439      * values will be rounded to their nearest pixel boundaries and the
2440      * width/height values will be ceiled to the next pixel boundary.
2441      *
2442      * @param child the child being positioned within this region
2443      * @param areaX the horizontal offset of the layout area relative to this region
2444      * @param areaY the vertical offset of the layout area relative to this region
2445      * @param areaWidth  the width of the layout area
2446      * @param areaHeight the height of the layout area
2447      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2448      * @param margin the margin of space to be allocated around the child
2449      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2450      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2451      * @param halignment the horizontal alignment for the child within the area
2452      * @param valignment the vertical alignment for the child within the area
2453      */
2454     protected void layoutInArea(Node child, double areaX, double areaY,
2455                                double areaWidth, double areaHeight,
2456                                double areaBaselineOffset,
2457                                Insets margin, boolean fillWidth, boolean fillHeight,
2458                                HPos halignment, VPos valignment) {
2459         layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, isSnapToPixel());
2460     }
2461 
2462     /**
2463      * Utility method which lays out the child within an area of it's
2464      * parent defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
2465      * with a baseline offset relative to that area.
2466      * <p>
2467      * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
2468      * to determine whether to resize it to fill the area or keep the child at its
2469      * preferred dimension.  If fillWidth/fillHeight are true, then this method
2470      * will only resize the child up to its max size limits.  If the node's maximum
2471      * size preference is less than the area size, the maximum size will be used.
2472      * If node's maximum is greater than the area size, then the node will be
2473      * resized to fit within the area, unless its minimum size prevents it.
2474      * <p>
2475      * If the child has a non-null contentBias, then this method will use it when
2476      * resizing the child.  If the contentBias is horizontal, it will set its width
2477      * first and then pass that value to compute the child's height.  If child's
2478      * contentBias is vertical, then it will set its height first
2479      * and pass that value to compute the child's width.  If the child's contentBias
2480      * is null, then it's width and height have no dependencies on each other.
2481      * <p>
2482      * If the child is not resizable (Shape, Group, etc) then it will only be
2483      * positioned and not resized.
2484      * <p>
2485      * If the child's resulting size differs from the area's size (either
2486      * because it was not resizable or it's sizing preferences prevented it), then
2487      * this function will align the node relative to the area using horizontal and
2488      * vertical alignment values.
2489      * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
2490      * with the area baseline offset parameter, otherwise the baseline parameter
2491      * is ignored.
2492      * <p>
2493      * If {@code margin} is non-null, then that space will be allocated around the
2494      * child within the layout area.  margin may be null.
2495      * <p>
2496      * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
2497      * values will be rounded to their nearest pixel boundaries and the
2498      * width/height values will be ceiled to the next pixel boundary.
2499      *
2500      * @param child the child being positioned within this region
2501      * @param areaX the horizontal offset of the layout area relative to this region
2502      * @param areaY the vertical offset of the layout area relative to this region
2503      * @param areaWidth  the width of the layout area
2504      * @param areaHeight the height of the layout area
2505      * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
2506      * @param margin the margin of space to be allocated around the child
2507      * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
2508      * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
2509      * @param halignment the horizontal alignment for the child within the area
2510      * @param valignment the vertical alignment for the child within the area
2511      * @param isSnapToPixel whether to snap size and position to pixels
2512      * @since JavaFX 8.0
2513      */
2514     public static void layoutInArea(Node child, double areaX, double areaY,
2515                                double areaWidth, double areaHeight,
2516                                double areaBaselineOffset,
2517                                Insets margin, boolean fillWidth, boolean fillHeight,
2518                                HPos halignment, VPos valignment, boolean isSnapToPixel) {
2519 
2520         Insets childMargin = margin != null ? margin : Insets.EMPTY;
2521         double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
2522         double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
2523 
2524         double top = snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY);
2525         double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY);
2526         double left = snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX);
2527         double right = snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX);
2528 
2529         if (valignment == VPos.BASELINE) {
2530             double bo = child.getBaselineOffset();
2531             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2532                 if (child.isResizable()) {
2533                     // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
2534                     // be resized to this area
2535                     bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel, snapScaleY);
2536                 } else {
2537                     top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel, snapScaleY);
2538                 }
2539             } else {
2540                 top = snapSpace(areaBaselineOffset - bo, isSnapToPixel, snapScaleY);
2541             }
2542         }
2543 
2544 
2545         if (child.isResizable()) {
2546             Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
2547                     fillWidth, fillHeight, TEMP_VEC2D);
2548             child.resize(snapSize(size.x, isSnapToPixel, snapScaleX),
2549                          snapSize(size.y, isSnapToPixel, snapScaleX));
2550         }
2551         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
2552                 top, right, bottom, left, halignment, valignment, isSnapToPixel);
2553     }
2554 
2555     private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
2556                           double areaBaselineOffset,
2557                           double topMargin, double rightMargin, double bottomMargin, double leftMargin,
2558                           HPos hpos, VPos vpos, boolean isSnapToPixel) {
2559         final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
2560                                                      child.getLayoutBounds().getWidth(), hpos);
2561         final double yoffset;
2562         if (vpos == VPos.BASELINE) {
2563             double bo = child.getBaselineOffset();
2564             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
2565                 // We already know the layout bounds at this stage, so we can use them
2566                 yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight();
2567             } else {
2568                 yoffset = areaBaselineOffset - bo;
2569             }
2570         } else {
2571             yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
2572                                          child.getLayoutBounds().getHeight(), vpos);
2573         }
2574         double x = areaX + xoffset;
2575         double y = areaY + yoffset;
2576         if (isSnapToPixel) {
2577             x = snapPosition(x, true, getSnapScaleX(child));
2578             y = snapPosition(y, true, getSnapScaleY(child));
2579         }
2580 
2581         child.relocate(x,y);
2582     }
2583 
2584      /**************************************************************************
2585      *                                                                         *
2586      * PG Implementation                                                       *
2587      *                                                                         *
2588      **************************************************************************/
2589 
2590     /*
2591      * Note: This method MUST only be called via its accessor method.
2592      */
2593     private void doUpdatePeer() {
2594         // TODO I think we have a bug, where if you create a Region with an Image that hasn't
2595         // been loaded, we have no listeners on that image so as to cause a pulse & repaint
2596         // to happen once the image is loaded. We just assume the image has been loaded
2597         // (since when the image is created using new Image(url) or CSS it happens eagerly).
2598         if (_shape != null) NodeHelper.syncPeer(_shape);
2599         NGRegion pg = NodeHelper.getPeer(this);
2600 
2601         if (!cornersValid) {
2602             validateCorners();
2603         }
2604 
2605         final boolean sizeChanged = NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY);
2606         if (sizeChanged) {
2607             pg.setSize((float)getWidth(), (float)getHeight());
2608         }
2609 
2610         // NOTE: The order here is very important. There is logic in NGRegion which determines
2611         // whether we can cache an image representing this region, and for this to work correctly,
2612         // the shape must be specified before the background which is before the border.
2613         final boolean shapeChanged = NodeHelper.isDirty(this, DirtyBits.REGION_SHAPE);
2614         if (shapeChanged) {
2615             pg.updateShape(_shape, isScaleShape(), isCenterShape(), isCacheShape());
2616         }
2617 
2618         // The normalized corners can always be updated since they require no
2619         // processing at the NG layer.
2620         pg.updateFillCorners(normalizedFillCorners);
2621         final boolean backgroundChanged = NodeHelper.isDirty(this, DirtyBits.SHAPE_FILL);
2622         final Background bg = getBackground();
2623         if (backgroundChanged) {
2624             pg.updateBackground(bg);
2625         }
2626 
2627         // This will be true if an image that makes up the background or border of this
2628         // region has changed, such that we need to redraw the region.
2629         if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) {
2630             pg.imagesUpdated();
2631         }
2632 
2633         // The normalized corners can always be updated since they require no
2634         // processing at the NG layer.
2635         pg.updateStrokeCorners(normalizedStrokeCorners);
2636         if (NodeHelper.isDirty(this, DirtyBits.SHAPE_STROKE)) {
2637             pg.updateBorder(getBorder());
2638         }
2639 
2640         // TODO given the note above, this *must* be called when an image which makes up the
2641         // background images and border images changes (is loaded) if it was being loaded asynchronously
2642         // Also note, one day we can add support for automatic opaque insets determination for border images.
2643         // However right now it is impractical because the image pixel format is almost undoubtedly going
2644         // to have alpha, and so without inspecting the source image's actual pixels for the filled center
2645         // we can't automatically determine whether the interior is filled.
2646         if (sizeChanged || backgroundChanged || shapeChanged) {
2647             // These are the opaque insets, as specified by the developer in code or CSS. If null,
2648             // then we must compute the opaque insets. If not null, then we will still compute the
2649             // opaque insets and combine them with these insets, as appropriate. We do ignore these
2650             // developer specified insets in cases where we know without a doubt that the developer
2651             // gave us bad data.
2652             final Insets i = getOpaqueInsets();
2653 
2654             // If the background is determined by a shape, then we don't attempt to calculate the
2655             // opaque insets. If the developer specified opaque insets, we will use them, otherwise
2656             // we will make sure the opaque insets are cleared
2657             if (_shape != null) {
2658                 if (i != null) {
2659                     pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(),
2660                                        (float) i.getBottom(), (float) i.getLeft());
2661                 } else {
2662                     pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2663                 }
2664             } else {
2665                 // This is a rectangle (not shape) region. The opaque insets must be calculated,
2666                 // even if the developer has supplied their own opaque insets. The first (and cheapest)
2667                 // check is whether the region has any backgrounds at all. If not, then
2668                 // we will ignore the developer supplied insets because they are clearly wrong.
2669                 if (bg == null || bg.isEmpty()) {
2670                     pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2671                 } else {
2672                     // There is a background, so it is conceivable that there are
2673                     // opaque insets. From this point on, we have to honor the developer's supplied
2674                     // insets, only expanding them if we know for certain the opaque insets are
2675                     // bigger than what was supplied by the developer. Start by defining our initial
2676                     // values for top, right, bottom, and left. If the developer supplied us
2677                     // insets, use those. Otherwise initialize to NaN. Note that the developer may
2678                     // also have given us NaN values (so we'd have to check for these anyway). We use
2679                     // NaN to mean "not defined".
2680                     final double[] trbl = new double[4];
2681                     bg.computeOpaqueInsets(getWidth(), getHeight(), trbl);
2682 
2683                     if (i != null) {
2684                         trbl[0] = Double.isNaN(trbl[0]) ? i.getTop() : Double.isNaN(i.getTop()) ? trbl[0] : Math.min(trbl[0], i.getTop());
2685                         trbl[1] = Double.isNaN(trbl[1]) ? i.getRight() : Double.isNaN(i.getRight()) ? trbl[1] : Math.min(trbl[1], i.getRight());
2686                         trbl[2] = Double.isNaN(trbl[2]) ? i.getBottom() : Double.isNaN(i.getBottom()) ? trbl[2] : Math.min(trbl[2], i.getBottom());
2687                         trbl[3] = Double.isNaN(trbl[3]) ? i.getLeft() : Double.isNaN(i.getLeft()) ? trbl[3] : Math.min(trbl[3], i.getLeft());
2688                     }
2689 
2690                     // Now set the insets onto the peer. Passing NaN here is perfectly
2691                     // acceptable (even encouraged, to mean "unknown" or "disabled").
2692                     pg.setOpaqueInsets((float) trbl[0], (float) trbl[1], (float) trbl[2], (float) trbl[3]);
2693                 }
2694             }
2695         }
2696     }
2697 
2698     /*
2699      * Note: This method MUST only be called via its accessor method.
2700      */
2701     private NGNode doCreatePeer() {
2702         return new NGRegion();
2703     }
2704 
2705     /**
2706      * Transform x, y in local Region coordinates to local coordinates of scaled/centered shape and
2707      * check if the shape contains the coordinates.
2708      * The transformations here are basically an inversion of transformations being done in NGShape#resizeShape.
2709      */
2710     private boolean shapeContains(com.sun.javafx.geom.Shape shape,
2711             final double x, final double y,
2712             double topOffset, double rightOffset, double bottomOffset, double leftOffset) {
2713         double resX = x;
2714         double resY = y;
2715         // The bounds of the shape, before any centering / scaling takes place
2716         final RectBounds bounds = shape.getBounds();
2717         if (isScaleShape()) {
2718             // Modify the transform to scale the shape so that it will fit
2719             // within the insets.
2720             resX -= leftOffset;
2721             resY -= topOffset;
2722 
2723             //denominator represents the width and height of the box within which the new shape must fit.
2724             resX *= bounds.getWidth() / (getWidth() - leftOffset - rightOffset);
2725             resY *= bounds.getHeight() / (getHeight() - topOffset - bottomOffset);
2726 
2727             // If we also need to center it, we need to adjust the transform so as to place
2728             // the shape in the center of the bounds
2729             if (isCenterShape()) {
2730                 resX += bounds.getMinX();
2731                 resY += bounds.getMinY();
2732             }
2733         } else if (isCenterShape()) {
2734             // We are only centering. In this case, what we want is for the
2735             // original shape to be centered. If there are offsets (insets)
2736             // then we must pre-scale about the center to account for it.
2737 
2738             double boundsWidth = bounds.getWidth();
2739             double boundsHeight = bounds.getHeight();
2740 
2741             double scaleFactorX = boundsWidth / (boundsWidth - leftOffset - rightOffset);
2742             double scaleFactorY = boundsHeight / (boundsHeight - topOffset - bottomOffset);
2743 
2744             //This is equivalent to:
2745             // translate(bounds.getMinX(), bounds.getMinY())
2746             // scale(scaleFactorX, scaleFactorY)
2747             // translate(-bounds.getMinX(), -bounds.getMinY())
2748             // translate(-leftOffset - (getWidth() - boundsWidth)/2 + bounds.getMinX(),
2749             //                            -topOffset - (getHeight() - boundsHeight)/2 + bounds.getMinY());
2750             // which is an inversion of an transformation done to the shape
2751             // This gives us
2752             //
2753             //resX = resX * scaleFactorX - scaleFactorX * bounds.getMinX() - scaleFactorX * (leftOffset + (getWidth() - boundsWidth) / 2 - bounds.getMinX()) + bounds.getMinX();
2754             //resY = resY * scaleFactorY - scaleFactorY * bounds.getMinY() - scaleFactorY * (topOffset + (getHeight() - boundsHeight) / 2 - bounds.getMinY()) + bounds.getMinY();
2755             //
2756             // which can further reduced to
2757 
2758             resX = scaleFactorX * (resX -(leftOffset + (getWidth() - boundsWidth) / 2)) + bounds.getMinX();
2759             resY = scaleFactorY * (resY -(topOffset + (getHeight() - boundsHeight) / 2)) + bounds.getMinY();
2760 
2761         } else if (topOffset != 0 || rightOffset != 0 || bottomOffset != 0 || leftOffset != 0) {
2762             // We are neither centering nor scaling, but we still have to resize the
2763             // shape because we have to fit within the bounds defined by the offsets
2764             double scaleFactorX = bounds.getWidth() / (bounds.getWidth() - leftOffset - rightOffset);
2765             double scaleFactorY = bounds.getHeight() / (bounds.getHeight() - topOffset - bottomOffset);
2766 
2767             // This is equivalent to:
2768             // translate(bounds.getMinX(), bounds.getMinY())
2769             // scale(scaleFactorX, scaleFactorY)
2770             // translate(-bounds.getMinX(), -bounds.getMinY())
2771             // translate(-leftOffset, -topOffset)
2772             //
2773             // which is an inversion of an transformation done to the shape
2774             // This gives us
2775             //
2776             //resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX();
2777             //resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY();
2778             //
2779             // which can be further reduceD to
2780             resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX();
2781             resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY();
2782 
2783         }
2784         return shape.contains((float)resX, (float)resY);
2785     }
2786 
2787     /*
2788      * Note: This method MUST only be called via its accessor method.
2789      */
2790     private boolean doComputeContains(double localX, double localY) {
2791         // NOTE: This method only gets called if a quick check of bounds has already
2792         // occurred, so there is no need to test against bound again. We know that the
2793         // point (localX, localY) falls within the bounds of this node, now we need
2794         // to determine if it falls within the geometry of this node.
2795         // Also note that because Region defaults pickOnBounds to true, this code is
2796         // not usually executed. It will only be executed if pickOnBounds is set to false.
2797 
2798         final double x2 = getWidth();
2799         final double y2 = getHeight();
2800 
2801         final Background background = getBackground();
2802         // First check the shape. Shape could be impacted by scaleShape & positionShape properties.
2803         if (_shape != null) {
2804             if (background != null && !background.getFills().isEmpty()) {
2805                 final List<BackgroundFill> fills = background.getFills();
2806                 double topO = Double.MAX_VALUE;
2807                 double leftO = Double.MAX_VALUE;
2808                 double bottomO = Double.MAX_VALUE;
2809                 double rightO = Double.MAX_VALUE;
2810                 for (int i = 0, max = fills.size(); i < max; i++) {
2811                     BackgroundFill bf = fills.get(0);
2812                     topO = Math.min(topO, bf.getInsets().getTop());
2813                     leftO = Math.min(leftO, bf.getInsets().getLeft());
2814                     bottomO = Math.min(bottomO, bf.getInsets().getBottom());
2815                     rightO = Math.min(rightO, bf.getInsets().getRight());
2816                 }
2817                 return shapeContains(ShapeHelper.configShape(_shape), localX, localY, topO, leftO, bottomO, rightO);
2818             }
2819             return false;
2820         }
2821 
2822         // OK, there was no background shape, so I'm going to work on the principle of
2823         // nested rounded rectangles. We'll start by checking the backgrounds. The
2824         // first background which passes the test is good enough for us!
2825         if (background != null) {
2826             final List<BackgroundFill> fills = background.getFills();
2827             for (int i = 0, max = fills.size(); i < max; i++) {
2828                 final BackgroundFill bgFill = fills.get(i);
2829                 if (contains(localX, localY, 0, 0, x2, y2, bgFill.getInsets(), getNormalizedFillCorner(i))) {
2830                     return true;
2831                 }
2832             }
2833         }
2834 
2835         // If we are here then either there were no background fills or there were no background
2836         // fills which contained the point, and the region is not defined by a shape.
2837         final Border border = getBorder();
2838         if (border != null) {
2839             // Check all the stroke borders first. If the pick occurs on any stroke border
2840             // then we consider the contains test to have passed. Semantically we will treat a Region
2841             // with a border as if it were a rectangle with a stroke but no fill.
2842             final List<BorderStroke> strokes = border.getStrokes();
2843             for (int i=0, max=strokes.size(); i<max; i++) {
2844                 final BorderStroke strokeBorder = strokes.get(i);
2845                 if (contains(localX, localY, 0, 0, x2, y2, strokeBorder.getWidths(), false, strokeBorder.getInsets(),
2846                              getNormalizedStrokeCorner(i))) {
2847                     return true;
2848                 }
2849             }
2850 
2851             // Check the image borders. We treat the image border as though it is opaque.
2852             final List<BorderImage> images = border.getImages();
2853             for (int i = 0, max = images.size(); i < max; i++) {
2854                 final BorderImage borderImage = images.get(i);
2855                 if (contains(localX, localY, 0, 0, x2, y2, borderImage.getWidths(), borderImage.isFilled(),
2856                              borderImage.getInsets(), CornerRadii.EMPTY)) {
2857                     return true;
2858                 }
2859             }
2860         }
2861         return false;
2862     }
2863 
2864     /**
2865      * Basically we will perform two contains tests. For a point to be on the stroke, it must
2866      * be within the outermost edge of the stroke, but outside the innermost edge of the stroke.
2867      * Unless it is filled, in which case it is really just a normal contains test.
2868      *
2869      * @param px        The x position of the point to test
2870      * @param py        The y position of the point to test
2871      * @param x1        The x1 position of the bounds to test
2872      * @param y1        The y1 position of the bounds to test
2873      * @param x2        The x2 position of the bounds to test
2874      * @param y2        The y2 position of the bounds to test
2875      * @param widths    The widths of the stroke on each side
2876      * @param filled    Whether the area is filled or is just stroked
2877      * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2878      * @param rad       The corner radii to test with. Must not be null.
2879      * @param maxRadius The maximum possible radius value
2880      * @return True if (px, py) is within the stroke, taking into account insets and corner radii.
2881      */
2882     private boolean contains(final double px, final double py,
2883                              final double x1, final double y1, final double x2, final double y2,
2884                              BorderWidths widths, boolean filled,
2885                              final Insets insets, final CornerRadii rad) {
2886         if (filled) {
2887             if (contains(px, py, x1, y1, x2, y2, insets, rad)) {
2888                 return true;
2889             }
2890         } else {
2891             boolean insideOuterEdge = contains(px, py, x1, y1, x2, y2, insets, rad);
2892             if (insideOuterEdge) {
2893                 boolean outsideInnerEdge = !contains(px, py,
2894                     x1 + (widths.isLeftAsPercentage() ? getWidth() * widths.getLeft() : widths.getLeft()),
2895                     y1 + (widths.isTopAsPercentage() ? getHeight() * widths.getTop() : widths.getTop()),
2896                     x2 - (widths.isRightAsPercentage() ? getWidth() * widths.getRight() : widths.getRight()),
2897                     y2 - (widths.isBottomAsPercentage() ? getHeight() * widths.getBottom() : widths.getBottom()),
2898                     insets, rad);
2899                 if (outsideInnerEdge) return true;
2900             }
2901         }
2902         return false;
2903     }
2904 
2905     /**
2906      * Determines whether the point (px, py) is contained within the the bounds (x1, y1)-(x2, y2),
2907      * after taking into account the insets and the corner radii.
2908      *
2909      * @param px        The x position of the point to test
2910      * @param py        The y position of the point to test
2911      * @param x1        The x1 position of the bounds to test
2912      * @param y1        The y1 position of the bounds to test
2913      * @param x2        The x2 position of the bounds to test
2914      * @param y2        The y2 position of the bounds to test
2915      * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2916      * @param rad       The corner radii to test with. Must not be null.
2917      * @param maxRadius The maximum possible radius value
2918      * @return True if (px, py) is within the bounds, taking into account insets and corner radii.
2919      */
2920     private boolean contains(final double px, final double py,
2921                              final double x1, final double y1, final double x2, final double y2,
2922                              final Insets insets, CornerRadii rad) {
2923         // These four values are the x0, y0, x1, y1 bounding box after
2924         // having taken into account the insets of this particular
2925         // background fill.
2926         final double rrx0 = x1 + insets.getLeft();
2927         final double rry0 = y1 + insets.getTop();
2928         final double rrx1 = x2 - insets.getRight();
2929         final double rry1 = y2 - insets.getBottom();
2930 
2931 //        assert rad.hasPercentBasedRadii == false;
2932 
2933         // Check for trivial rejection - point is inside bounding rectangle
2934         if (px >= rrx0 && py >= rry0 && px <= rrx1 && py <= rry1) {
2935             // The point was within the index bounding box. Now we need to analyze the
2936             // corner radii to see if the point lies within the corners or not. If the
2937             // point is within a corner then we reject this one.
2938             final double tlhr = rad.getTopLeftHorizontalRadius();
2939             if (rad.isUniform() && tlhr == 0) {
2940                 // This is a simple square! Since we know the point is already within
2941                 // the insets of this fill, we can simply return true.
2942                 return true;
2943             } else {
2944                 final double tlvr = rad.getTopLeftVerticalRadius();
2945                 final double trhr = rad.getTopRightHorizontalRadius();
2946                 final double trvr = rad.getTopRightVerticalRadius();
2947                 final double blhr = rad.getBottomLeftHorizontalRadius();
2948                 final double blvr = rad.getBottomLeftVerticalRadius();
2949                 final double brhr = rad.getBottomRightHorizontalRadius();
2950                 final double brvr = rad.getBottomRightVerticalRadius();
2951 
2952                 // The four corners can each be described as a quarter of an ellipse
2953                 double centerX, centerY, a, b;
2954 
2955                 if (px <= rrx0 + tlhr && py <= rry0 + tlvr) {
2956                     // Point is in the top left corner
2957                     centerX = rrx0 + tlhr;
2958                     centerY = rry0 + tlvr;
2959                     a = tlhr;
2960                     b = tlvr;
2961                 } else if (px >= rrx1 - trhr && py <= rry0 + trvr) {
2962                     // Point is in the top right corner
2963                     centerX = rrx1 - trhr;
2964                     centerY = rry0 + trvr;
2965                     a = trhr;
2966                     b = trvr;
2967                 } else if (px >= rrx1 - brhr && py >= rry1 - brvr) {
2968                     // Point is in the bottom right corner
2969                     centerX = rrx1 - brhr;
2970                     centerY = rry1 - brvr;
2971                     a = brhr;
2972                     b = brvr;
2973                 } else if (px <= rrx0 + blhr && py >= rry1 - blvr) {
2974                     // Point is in the bottom left corner
2975                     centerX = rrx0 + blhr;
2976                     centerY = rry1 - blvr;
2977                     a = blhr;
2978                     b = blvr;
2979                 } else {
2980                     // The point must have been in the solid body someplace
2981                     return true;
2982                 }
2983 
2984                 double x = px - centerX;
2985                 double y = py - centerY;
2986                 double result = ((x*x)/(a*a) + (y*y)/(b*b));
2987                 // The .0000001 is fudge to help in cases where double arithmetic isn't quite right
2988                 if (result - .0000001 <= 1) return true;
2989             }
2990         }
2991         return false;
2992     }
2993 
2994     /*
2995      * The normalized corner radii are unmodifiable List objects shared between
2996      * the NG layer and the FX layer.  As cached shadow copies of the objects
2997      * in the BackgroundFill and BorderStroke objects they should be considered
2998      * read-only and will only be updated by replacing the original objects
2999      * when validation is needed.
3000      */
3001     private boolean cornersValid; // = false
3002     private List<CornerRadii> normalizedFillCorners; // = null
3003     private List<CornerRadii> normalizedStrokeCorners; // = null
3004 
3005     /**
3006      * Returns the normalized absolute radii for the indicated BackgroundFill,
3007      * taking the current size of the region into account to eliminate any
3008      * percentage-based measurements and to scale the radii to prevent
3009      * overflowing the width or height.
3010      *
3011      * @param i the index of the BackgroundFill whose radii will be normalized.
3012      * @return the normalized (non-percentage, non-overflowing) radii
3013      */
3014     private CornerRadii getNormalizedFillCorner(int i) {
3015         if (!cornersValid) {
3016             validateCorners();
3017         }
3018         return (normalizedFillCorners == null
3019                 ? getBackground().getFills().get(i).getRadii()
3020                 : normalizedFillCorners.get(i));
3021     }
3022 
3023     /**
3024      * Returns the normalized absolute radii for the indicated BorderStroke,
3025      * taking the current size of the region into account to eliminate any
3026      * percentage-based measurements and to scale the radii to prevent
3027      * overflowing the width or height.
3028      *
3029      * @param i the index of the BorderStroke whose radii will be normalized.
3030      * @return the normalized (non-percentage, non-overflowing) radii
3031      */
3032     private CornerRadii getNormalizedStrokeCorner(int i) {
3033         if (!cornersValid) {
3034             validateCorners();
3035         }
3036         return (normalizedStrokeCorners == null
3037                 ? getBorder().getStrokes().get(i).getRadii()
3038                 : normalizedStrokeCorners.get(i));
3039     }
3040 
3041     /**
3042      * This method validates all CornerRadii objects in both the set of
3043      * BackgroundFills and BorderStrokes and saves the normalized values
3044      * into the private fields above.
3045      */
3046     private void validateCorners() {
3047         final double width = getWidth();
3048         final double height = getHeight();
3049         List<CornerRadii> newFillCorners = null;
3050         List<CornerRadii> newStrokeCorners = null;
3051         final Background background = getBackground();
3052         final List<BackgroundFill> fills = background == null ? Collections.EMPTY_LIST : background.getFills();
3053         for (int i = 0; i < fills.size(); i++) {
3054             final BackgroundFill fill = fills.get(i);
3055             final CornerRadii origRadii = fill.getRadii();
3056             final Insets origInsets = fill.getInsets();
3057             final CornerRadii newRadii = normalize(origRadii, origInsets, width, height);
3058             if (origRadii != newRadii) {
3059                 if (newFillCorners == null) {
3060                     newFillCorners = Arrays.asList(new CornerRadii[fills.size()]);
3061                 }
3062                 newFillCorners.set(i, newRadii);
3063             }
3064         }
3065         final Border border = getBorder();
3066         final List<BorderStroke> strokes = (border == null ? Collections.EMPTY_LIST : border.getStrokes());
3067         for (int i = 0; i < strokes.size(); i++) {
3068             final BorderStroke stroke = strokes.get(i);
3069             final CornerRadii origRadii = stroke.getRadii();
3070             final Insets origInsets = stroke.getInsets();
3071             final CornerRadii newRadii = normalize(origRadii, origInsets, width, height);
3072             if (origRadii != newRadii) {
3073                 if (newStrokeCorners == null) {
3074                     newStrokeCorners = Arrays.asList(new CornerRadii[strokes.size()]);
3075                 }
3076                 newStrokeCorners.set(i, newRadii);
3077             }
3078         }
3079         if (newFillCorners != null) {
3080             for (int i = 0; i < fills.size(); i++) {
3081                 if (newFillCorners.get(i) == null) {
3082                     newFillCorners.set(i, fills.get(i).getRadii());
3083                 }
3084             }
3085             newFillCorners = Collections.unmodifiableList(newFillCorners);
3086         }
3087         if (newStrokeCorners != null) {
3088             for (int i = 0; i < strokes.size(); i++) {
3089                 if (newStrokeCorners.get(i) == null) {
3090                     newStrokeCorners.set(i, strokes.get(i).getRadii());
3091                 }
3092             }
3093             newStrokeCorners = Collections.unmodifiableList(newStrokeCorners);
3094         }
3095         normalizedFillCorners = newFillCorners;
3096         normalizedStrokeCorners = newStrokeCorners;
3097         cornersValid = true;
3098     }
3099 
3100     /**
3101      * Return a version of the radii that is not percentage based and is scaled to
3102      * fit the indicated inset rectangle without overflow.
3103      * This method may return the original CornerRadii if none of the radii
3104      * values in the given object are percentages or require scaling.
3105      *
3106      * @param radii    The radii.
3107      * @param insets   The insets for the associated background or stroke.
3108      * @param width    The width of the region before insets are applied.
3109      * @param height   The height of the region before insets are applied.
3110      * @return Normalized radii.
3111      */
3112     private static CornerRadii normalize(CornerRadii radii, Insets insets, double width, double height) {
3113         width  -= insets.getLeft() + insets.getRight();
3114         height -= insets.getTop() + insets.getBottom();
3115         if (width <= 0 || height <= 0) return CornerRadii.EMPTY;
3116         double tlvr = radii.getTopLeftVerticalRadius();
3117         double tlhr = radii.getTopLeftHorizontalRadius();
3118         double trvr = radii.getTopRightVerticalRadius();
3119         double trhr = radii.getTopRightHorizontalRadius();
3120         double brvr = radii.getBottomRightVerticalRadius();
3121         double brhr = radii.getBottomRightHorizontalRadius();
3122         double blvr = radii.getBottomLeftVerticalRadius();
3123         double blhr = radii.getBottomLeftHorizontalRadius();
3124         if (radii.hasPercentBasedRadii) {
3125             if (radii.isTopLeftVerticalRadiusAsPercentage())       tlvr *= height;
3126             if (radii.isTopLeftHorizontalRadiusAsPercentage())     tlhr *= width;
3127             if (radii.isTopRightVerticalRadiusAsPercentage())      trvr *= height;
3128             if (radii.isTopRightHorizontalRadiusAsPercentage())    trhr *= width;
3129             if (radii.isBottomRightVerticalRadiusAsPercentage())   brvr *= height;
3130             if (radii.isBottomRightHorizontalRadiusAsPercentage()) brhr *= width;
3131             if (radii.isBottomLeftVerticalRadiusAsPercentage())    blvr *= height;
3132             if (radii.isBottomLeftHorizontalRadiusAsPercentage())  blhr *= width;
3133         }
3134         double scale = 1.0;
3135         if (tlhr + trhr > width)  { scale = Math.min(scale, width  / (tlhr + trhr)); }
3136         if (blhr + brhr > width)  { scale = Math.min(scale, width  / (blhr + brhr)); }
3137         if (tlvr + blvr > height) { scale = Math.min(scale, height / (tlvr + blvr)); }
3138         if (trvr + brvr > height) { scale = Math.min(scale, height / (trvr + brvr)); }
3139         if (scale < 1.0) {
3140             tlvr *= scale;  tlhr *= scale;
3141             trvr *= scale;  trhr *= scale;
3142             brvr *= scale;  brhr *= scale;
3143             blvr *= scale;  blhr *= scale;
3144         }
3145         if (radii.hasPercentBasedRadii || scale < 1.0) {
3146             return new CornerRadii(tlhr,  tlvr,  trvr,  trhr,  brhr,  brvr,  blvr,  blhr,
3147                                    false, false, false, false, false, false, false, false);
3148         }
3149         return radii;
3150     }
3151 
3152     /**
3153      * Some skins relying on this
3154      *
3155      * Note: This method MUST only be called via its accessor method.
3156      */
3157     private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) {
3158          double boundsDistance = NodeHelper.intersectsBounds(this, pickRay);
3159 
3160         if (!Double.isNaN(boundsDistance) && ParentHelper.pickChildrenNode(this, pickRay, result)) {
3161             NodeHelper.intersects(this, pickRay, result);
3162         }
3163     }
3164 
3165     private Bounds boundingBox;
3166 
3167     /**
3168      * The layout bounds of this region: {@code 0, 0  width x height}
3169      */
3170     private Bounds doComputeLayoutBounds() {
3171         if (boundingBox == null) {
3172             // we reuse the bounding box if the width and height haven't changed.
3173             boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0);
3174         }
3175         return boundingBox;
3176     }
3177 
3178     /*
3179      * Note: This method MUST only be called via its accessor method.
3180      */
3181     private void doNotifyLayoutBoundsChanged() {
3182         // override Node's default behavior of having a geometric bounds change
3183         // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
3184         // is unrelated to geometric bounds.
3185     }
3186 
3187     private BaseBounds computeShapeBounds(BaseBounds bounds)
3188     {
3189         com.sun.javafx.geom.Shape s = ShapeHelper.configShape(_shape);
3190 
3191         float[] bbox = {
3192                 Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
3193                 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
3194         };
3195 
3196         Background bg = getBackground();
3197         if (bg != null) {
3198             final RectBounds sBounds = s.getBounds();
3199             final Insets bgOutsets = bg.getOutsets();
3200             bbox[0] = sBounds.getMinX() - (float) bgOutsets.getLeft();
3201             bbox[1] = sBounds.getMinY() - (float) bgOutsets.getTop();
3202             bbox[2] = sBounds.getMaxX() + (float) bgOutsets.getBottom();
3203             bbox[3] = sBounds.getMaxY() + (float) bgOutsets.getRight();
3204         }
3205 
3206         final Border b = getBorder();
3207         if (b != null && b.getStrokes().size() > 0) {
3208             for (BorderStroke bs : b.getStrokes()) {
3209                 // This order of border strokes is used in NGRegion.renderAsShape/setBorderStyle
3210                 BorderStrokeStyle bss = bs.getTopStyle() != null ? bs.getTopStyle() :
3211                         bs.getLeftStyle() != null ? bs.getLeftStyle() :
3212                                 bs.getBottomStyle() != null ? bs.getBottomStyle() :
3213                                         bs.getRightStyle() != null ? bs.getRightStyle() : null;
3214 
3215                 if (bss == null || bss == BorderStrokeStyle.NONE) {
3216                     continue;
3217                 }
3218 
3219                 final StrokeType type = bss.getType();
3220                 double sw = Math.max(bs.getWidths().top, 0d);
3221                 StrokeLineCap cap = bss.getLineCap();
3222                 StrokeLineJoin join = bss.getLineJoin();
3223                 float miterlimit = (float) Math.max(bss.getMiterLimit(), 1d);
3224                 Toolkit.getToolkit().accumulateStrokeBounds(
3225                         s,
3226                         bbox, type, sw,
3227                         cap, join, miterlimit, BaseTransform.IDENTITY_TRANSFORM);
3228 
3229             }
3230         }
3231 
3232         if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) {
3233             return bounds.makeEmpty();
3234         }
3235 
3236         return bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f,
3237                 bbox[2], bbox[3], 0.0f);
3238     }
3239 
3240     /*
3241      * Note: This method MUST only be called via its accessor method.
3242      */
3243     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
3244         // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children.
3245         // The bounds of the Region must take into account any backgrounds and borders and how
3246         // they are used to draw the Region. The geom bounds must always take into account
3247         // all pixels drawn (because the geom bounds forms the basis of the dirty regions).
3248         // Note that the layout bounds of a Region is not based on the geom bounds.
3249 
3250         // Define some variables to hold the top-left and bottom-right corners of the bounds
3251         double bx1 = 0;
3252         double by1 = 0;
3253         double bx2 = getWidth();
3254         double by2 = getHeight();
3255 
3256         // If the shape is defined, then the top-left and bottom-right corner positions
3257         // need to be redefined
3258         if (_shape != null && isScaleShape() == false) {
3259             // We will hijack the bounds here temporarily just to compute the shape bounds
3260             final BaseBounds shapeBounds = computeShapeBounds(bounds);
3261             final double shapeWidth = shapeBounds.getWidth();
3262             final double shapeHeight = shapeBounds.getHeight();
3263             if (isCenterShape()) {
3264                 bx1 = (bx2 - shapeWidth) / 2;
3265                 by1 = (by2 - shapeHeight) / 2;
3266                 bx2 = bx1 + shapeWidth;
3267                 by2 = by1 + shapeHeight;
3268             } else {
3269                 bx1 = shapeBounds.getMinX();
3270                 by1 = shapeBounds.getMinY();
3271                 bx2 = shapeBounds.getMaxX();
3272                 by2 = shapeBounds.getMaxY();
3273             }
3274         } else {
3275             // Expand the bounds to include the outsets from the background and border.
3276             // The outsets are the opposite of insets -- a measure of distance from the
3277             // edge of the Region outward. The outsets cannot, however, be negative.
3278             final Background background = getBackground();
3279             final Border border = getBorder();
3280             final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets();
3281             final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets();
3282             bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft());
3283             by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop());
3284             bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight());
3285             by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom());
3286         }
3287         // NOTE: Okay to call RegionHelper.superComputeGeomBounds with tx even in the 3D case
3288         // since Parent's computeGeomBounds does handle 3D correctly.
3289         BaseBounds cb = RegionHelper.superComputeGeomBounds(this, bounds, tx);
3290         /*
3291          * This is a work around for RT-7680. Parent returns invalid bounds from
3292          * computeGeomBoundsImpl when it has no children or if all its children
3293          * have invalid bounds. If RT-7680 were fixed, then we could omit this
3294          * first branch of the if and only use the else since the correct value
3295          * would be computed.
3296          */
3297         if (cb.isEmpty()) {
3298             // There are no children bounds, so
3299             bounds = bounds.deriveWithNewBounds(
3300                     (float)bx1, (float)by1, 0.0f,
3301                     (float)bx2, (float)by2, 0.0f);
3302             bounds = tx.transform(bounds, bounds);
3303             return bounds;
3304         } else {
3305             // Union with children's bounds
3306             BaseBounds tempBounds = TempState.getInstance().bounds;
3307             tempBounds = tempBounds.deriveWithNewBounds(
3308                     (float)bx1, (float)by1, 0.0f,
3309                     (float)bx2, (float)by2, 0.0f);
3310             BaseBounds bb = tx.transform(tempBounds, tempBounds);
3311             cb = cb.deriveWithUnion(bb);
3312             return cb;
3313         }
3314     }
3315 
3316     /***************************************************************************
3317      *                                                                         *
3318      * CSS                                                                     *
3319      *                                                                         *
3320      **************************************************************************/
3321 
3322     /**
3323      * An implementation may specify its own user-agent styles for this Region, and its children,
3324      * by overriding this method. These styles are used in addition to whatever user-agent stylesheets
3325      * are in use. This provides a mechanism for third parties to introduce styles for custom controls.
3326      * <p>
3327      * The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL
3328      * does not have a [scheme:] component, the URL is considered to be the [path] component only.
3329      * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to
3330      * the root of the application's classpath.
3331      * </p>
3332      * <p>
3333      * Subclasses overriding this method should not assume any particular implementation approach as to
3334      * the number and frequency with which it is called. For this reason, attempting any kind of
3335      * dynamic implementation (i.e. returning different user agent stylesheet values) based on some
3336      * state change is highly discouraged, as there is no guarantee when, or even if, this method will
3337      * be called. Some JavaFX CSS implementations may choose to cache this response for an indefinite
3338      * period of time, and therefore there should be no expectation around when this method is called.
3339      * </p>
3340      *
3341      * <code><pre>
3342      *
3343      * package com.example.javafx.app;
3344      *
3345      * import javafx.application.Application;
3346      * import javafx.scene.Group;
3347      * import javafx.scene.Scene;
3348      * import javafx.stage.Stage;
3349      *
3350      * public class MyApp extends Application {
3351      *
3352      *     {@literal @}Override public void start(Stage stage) {
3353      *         Scene scene = new Scene(new Group());
3354      *         scene.getStylesheets().add("/com/example/javafx/app/mystyles.css");
3355      *         stage.setScene(scene);
3356      *         stage.show();
3357      *     }
3358      *
3359      *     public static void main(String[] args) {
3360      *         launch(args);
3361      *     }
3362      * }
3363      * </pre></code>
3364      * For additional information about using CSS with the scene graph,
3365      * see the <a href="../doc-files/cssref.html">CSS Reference Guide</a>.
3366      *
3367      * @return A string URL
3368      * @since JavaFX 8u40
3369      */
3370     public String getUserAgentStylesheet() {
3371         return null;
3372     }
3373 
3374     /*
3375      * Super-lazy instantiation pattern from Bill Pugh.
3376      */
3377      private static class StyleableProperties {
3378          private static final CssMetaData<Region,Insets> PADDING =
3379              new CssMetaData<Region,Insets>("-fx-padding",
3380                  InsetsConverter.getInstance(), Insets.EMPTY) {
3381 
3382             @Override public boolean isSettable(Region node) {
3383                 return node.padding == null || !node.padding.isBound();
3384             }
3385 
3386             @Override public StyleableProperty<Insets> getStyleableProperty(Region node) {
3387                 return (StyleableProperty<Insets>)node.paddingProperty();
3388             }
3389          };
3390 
3391          private static final CssMetaData<Region,Insets> OPAQUE_INSETS =
3392                  new CssMetaData<Region,Insets>("-fx-opaque-insets",
3393                          InsetsConverter.getInstance(), null) {
3394 
3395                      @Override
3396                      public boolean isSettable(Region node) {
3397                          return node.opaqueInsets == null || !node.opaqueInsets.isBound();
3398                      }
3399 
3400                      @Override
3401                      public StyleableProperty<Insets> getStyleableProperty(Region node) {
3402                          return (StyleableProperty<Insets>)node.opaqueInsetsProperty();
3403                      }
3404 
3405                  };
3406 
3407          private static final CssMetaData<Region,Background> BACKGROUND =
3408              new CssMetaData<Region,Background>("-fx-region-background",
3409                  BackgroundConverter.INSTANCE,
3410                  null,
3411                  false,
3412                  Background.getClassCssMetaData()) {
3413 
3414             @Override public boolean isSettable(Region node) {
3415                 return !node.background.isBound();
3416             }
3417 
3418             @Override public StyleableProperty<Background> getStyleableProperty(Region node) {
3419                 return (StyleableProperty<Background>)node.background;
3420             }
3421          };
3422 
3423          private static final CssMetaData<Region,Border> BORDER =
3424              new CssMetaData<Region,Border>("-fx-region-border",
3425                      BorderConverter.getInstance(),
3426                      null,
3427                      false,
3428                      Border.getClassCssMetaData()) {
3429 
3430                  @Override public boolean isSettable(Region node) {
3431                      return !node.border.isBound();
3432                  }
3433 
3434                  @Override public StyleableProperty<Border> getStyleableProperty(Region node) {
3435                      return (StyleableProperty<Border>)node.border;
3436                  }
3437              };
3438 
3439          private static final CssMetaData<Region,Shape> SHAPE =
3440              new CssMetaData<Region,Shape>("-fx-shape",
3441                  ShapeConverter.getInstance()) {
3442 
3443             @Override public boolean isSettable(Region node) {
3444                 // isSettable depends on node.shape, not node.shapeContent
3445                 return node.shape == null || !node.shape.isBound();
3446             }
3447 
3448             @Override public StyleableProperty<Shape> getStyleableProperty(Region node) {
3449                 return (StyleableProperty<Shape>)node.shapeProperty();
3450             }
3451          };
3452 
3453          private static final CssMetaData<Region, Boolean> SCALE_SHAPE =
3454              new CssMetaData<Region,Boolean>("-fx-scale-shape",
3455                  BooleanConverter.getInstance(), Boolean.TRUE){
3456 
3457             @Override public boolean isSettable(Region node) {
3458                 return node.scaleShape == null || !node.scaleShape.isBound();
3459             }
3460 
3461             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3462                 return (StyleableProperty<Boolean>)node.scaleShapeProperty();
3463             }
3464         };
3465 
3466          private static final CssMetaData<Region,Boolean> POSITION_SHAPE =
3467              new CssMetaData<Region,Boolean>("-fx-position-shape",
3468                  BooleanConverter.getInstance(), Boolean.TRUE){
3469 
3470             @Override public boolean isSettable(Region node) {
3471                 return node.centerShape == null || !node.centerShape.isBound();
3472             }
3473 
3474             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3475                 return (StyleableProperty<Boolean>)node.centerShapeProperty();
3476             }
3477         };
3478 
3479          private static final CssMetaData<Region,Boolean> CACHE_SHAPE =
3480              new CssMetaData<Region,Boolean>("-fx-cache-shape",
3481                  BooleanConverter.getInstance(), Boolean.TRUE){
3482 
3483             @Override public boolean isSettable(Region node) {
3484                 return node.cacheShape == null || !node.cacheShape.isBound();
3485             }
3486 
3487             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3488                 return (StyleableProperty<Boolean>)node.cacheShapeProperty();
3489             }
3490         };
3491 
3492          private static final CssMetaData<Region, Boolean> SNAP_TO_PIXEL =
3493              new CssMetaData<Region,Boolean>("-fx-snap-to-pixel",
3494                  BooleanConverter.getInstance(), Boolean.TRUE){
3495 
3496             @Override public boolean isSettable(Region node) {
3497                 return node.snapToPixel == null ||
3498                         !node.snapToPixel.isBound();
3499             }
3500 
3501             @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
3502                 return (StyleableProperty<Boolean>)node.snapToPixelProperty();
3503             }
3504         };
3505 
3506          private static final CssMetaData<Region, Number> MIN_HEIGHT =
3507              new CssMetaData<Region,Number>("-fx-min-height",
3508                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3509 
3510             @Override public boolean isSettable(Region node) {
3511                 return node.minHeight == null ||
3512                         !node.minHeight.isBound();
3513             }
3514 
3515             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3516                 return (StyleableProperty<Number>)node.minHeightProperty();
3517             }
3518         };
3519 
3520          private static final CssMetaData<Region, Number> PREF_HEIGHT =
3521              new CssMetaData<Region,Number>("-fx-pref-height",
3522                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3523 
3524             @Override public boolean isSettable(Region node) {
3525                 return node.prefHeight == null ||
3526                         !node.prefHeight.isBound();
3527             }
3528 
3529             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3530                 return (StyleableProperty<Number>)node.prefHeightProperty();
3531             }
3532         };
3533 
3534          private static final CssMetaData<Region, Number> MAX_HEIGHT =
3535              new CssMetaData<Region,Number>("-fx-max-height",
3536                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3537 
3538             @Override public boolean isSettable(Region node) {
3539                 return node.maxHeight == null ||
3540                         !node.maxHeight.isBound();
3541             }
3542 
3543             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3544                 return (StyleableProperty<Number>)node.maxHeightProperty();
3545             }
3546         };
3547 
3548          private static final CssMetaData<Region, Number> MIN_WIDTH =
3549              new CssMetaData<Region,Number>("-fx-min-width",
3550                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3551 
3552             @Override public boolean isSettable(Region node) {
3553                 return node.minWidth == null ||
3554                         !node.minWidth.isBound();
3555             }
3556 
3557             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3558                 return (StyleableProperty<Number>)node.minWidthProperty();
3559             }
3560         };
3561 
3562          private static final CssMetaData<Region, Number> PREF_WIDTH =
3563              new CssMetaData<Region,Number>("-fx-pref-width",
3564                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3565 
3566             @Override public boolean isSettable(Region node) {
3567                 return node.prefWidth == null ||
3568                         !node.prefWidth.isBound();
3569             }
3570 
3571             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3572                 return (StyleableProperty<Number>)node.prefWidthProperty();
3573             }
3574         };
3575 
3576          private static final CssMetaData<Region, Number> MAX_WIDTH =
3577              new CssMetaData<Region,Number>("-fx-max-width",
3578                  SizeConverter.getInstance(), USE_COMPUTED_SIZE){
3579 
3580             @Override public boolean isSettable(Region node) {
3581                 return node.maxWidth == null ||
3582                         !node.maxWidth.isBound();
3583             }
3584 
3585             @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
3586                 return (StyleableProperty<Number>)node.maxWidthProperty();
3587             }
3588         };
3589 
3590          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
3591          static {
3592 
3593             final List<CssMetaData<? extends Styleable, ?>> styleables =
3594                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData());
3595             styleables.add(PADDING);
3596             styleables.add(BACKGROUND);
3597             styleables.add(BORDER);
3598             styleables.add(OPAQUE_INSETS);
3599             styleables.add(SHAPE);
3600             styleables.add(SCALE_SHAPE);
3601             styleables.add(POSITION_SHAPE);
3602             styleables.add(SNAP_TO_PIXEL);
3603             styleables.add(MIN_WIDTH);
3604             styleables.add(PREF_WIDTH);
3605             styleables.add(MAX_WIDTH);
3606             styleables.add(MIN_HEIGHT);
3607             styleables.add(PREF_HEIGHT);
3608             styleables.add(MAX_HEIGHT);
3609             STYLEABLES = Collections.unmodifiableList(styleables);
3610          }
3611     }
3612 
3613     /**
3614      * @return The CssMetaData associated with this class, which may include the
3615      * CssMetaData of its super classes.
3616      * @since JavaFX 8.0
3617      */
3618     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
3619         return StyleableProperties.STYLEABLES;
3620     }
3621 
3622     /**
3623      * {@inheritDoc}
3624      *
3625      * @since JavaFX 8.0
3626      */
3627 
3628 
3629     @Override
3630     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
3631         return getClassCssMetaData();
3632     }
3633 
3634 }