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