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