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