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