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