Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.layout;
027
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031import javafx.beans.InvalidationListener;
032import javafx.beans.property.BooleanProperty;
033import javafx.beans.property.DoubleProperty;
034import javafx.beans.property.DoublePropertyBase;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.ReadOnlyDoubleProperty;
037import javafx.beans.property.ReadOnlyDoubleWrapper;
038import javafx.beans.property.ReadOnlyObjectProperty;
039import javafx.beans.value.ChangeListener;
040import javafx.collections.ObservableList;
041import javafx.css.CssMetaData;
042import javafx.css.StyleableBooleanProperty;
043import javafx.css.StyleableObjectProperty;
044import javafx.css.StyleableProperty;
045import javafx.geometry.BoundingBox;
046import javafx.geometry.Bounds;
047import javafx.geometry.HPos;
048import javafx.geometry.Insets;
049import javafx.geometry.Orientation;
050import javafx.geometry.Point3D;
051import javafx.geometry.VPos;
052import javafx.scene.Node;
053import javafx.scene.Parent;
054import javafx.scene.shape.Shape;
055import com.sun.javafx.Logging;
056import com.sun.javafx.TempState;
057import com.sun.javafx.binding.ExpressionHelper;
058import com.sun.javafx.css.converters.BooleanConverter;
059import com.sun.javafx.css.converters.InsetsConverter;
060import com.sun.javafx.css.converters.ShapeConverter;
061import com.sun.javafx.css.converters.SizeConverter;
062import com.sun.javafx.geom.BaseBounds;
063import com.sun.javafx.geom.PickRay;
064import com.sun.javafx.geom.Vec2d;
065import com.sun.javafx.geom.transform.BaseTransform;
066import com.sun.javafx.scene.DirtyBits;
067import com.sun.javafx.scene.input.PickResultChooser;
068import com.sun.javafx.sg.PGNode;
069import com.sun.javafx.sg.PGRegion;
070import com.sun.javafx.tk.Toolkit;
071import javafx.beans.value.ObservableValue;
072import javafx.css.StyleOrigin;
073import javafx.css.Styleable;
074import javafx.css.StyleableDoubleProperty;
075import sun.util.logging.PlatformLogger;
076
077/**
078 * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers.
079 * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds
080 * and borders. It is designed to support as much of the CSS3 specification for backgrounds
081 * and borders as is relevant to JavaFX.
082 * The full specification is available at <a href="http://www.w3.org/TR/2012/CR-css3-background-20120724/">the W3C</a>.
083 * <p/>
084 * Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside
085 * these bounds. The content area of a Region is the area which is occupied for the layout of its children.
086 * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the
087 * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can
088 * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region,
089 * but does not affect the layout bounds.
090 * <p/>
091 * A Region has a Background, and a Border, although either or both of these might be empty. The Background
092 * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the
093 * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and
094 * zero or more BorderImages. All BackgroundFills are drawn first, followed by BackgroundImages, BorderStrokes,
095 * and finally BorderImages. The content is drawn above all backgrounds and borders. If a BorderImage is
096 * present (and loaded all images properly), then no BorderStrokes are actually drawn, although they are
097 * considered for computing the position of the content area (see the stroke width property of a BorderStroke).
098 * These semantics are in line with the CSS 3 specification. The purpose of these semantics are to allow an
099 * application to specify a fallback BorderStroke to be displayed in the case that an ImageStroke fails to
100 * download or load.
101 * <p/>
102 * By default a Region appears as a Rectangle. A BackgroundFill radii might cause the Rectangle to appear rounded.
103 * This affects not only making the visuals look like a rounded rectangle, but it also causes the picking behavior
104 * of the Region to act like a rounded rectangle, such that locations outside the corner radii are ignored. A
105 * Region can be made to use any shape, however, by specifing the {@code shape} property. If a shape is specified,
106 * then all BackgroundFills, BackgroundImages, and BorderStrokes will be applied to the shape. BorderImages are
107 * not used for Regions which have a shape specified.
108 * <p/>
109 * A Region with a shape
110 * <p/>
111 * Although the layout bounds of a Region are not influenced by any Border or Background, the content area
112 * insets and the picking area of the Region are. The {@code insets} of the Region define the distance
113 * between the edge of the layout bounds and the edge of the content area. For example, if the Region
114 * layout bounds are (x=0, y=0, width=200, height=100), and the insets are (top=10, right=20, bottom=30, left=40),
115 * then the content area bounds will be (x=40, y=10, width=140, height=60). A Region subclass which is laying
116 * out its children should compute and honor these content area bounds.
117 * <p/>
118 * By default a Region inherits the layout behavior of its superclass, {@link Parent},
119 * which means that it will resize any resizable child nodes to their preferred
120 * size, but will not reposition them.  If an application needs more specific
121 * layout behavior, then it should use one of the Region subclasses:
122 * {@link StackPane}, {@link HBox}, {@link VBox}, {@link TilePane}, {@link FlowPane},
123 * {@link BorderPane}, {@link GridPane}, or {@link AnchorPane}.
124 * <p/>
125 * To implement a more custom layout, a Region subclass must override
126 * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, and
127 * {@link #layoutChildren() layoutChildren}. Note that {@link #layoutChildren() layoutChildren} is called automatically
128 * by the scene graph while executing a top-down layout pass and it should not be invoked directly by the
129 * region subclass.
130 * <p/>
131 * Region subclasses which layout their children will position nodes by setting
132 * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter
133 * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for
134 * adjustments and animation.
135 */
136public class Region extends Parent {
137
138    /**
139     * Sentinel value which can be passed to a region's
140     * {@link #setMinWidth(double) setMinWidth},
141     * {@link #setMinHeight(double) setMinHeight},
142     * {@link #setMaxWidth(double) setMaxWidth} or
143     * {@link #setMaxHeight(double) setMaxHeight}
144     * methods to indicate that the preferred dimension should be used for that max and/or min constraint.
145     */
146    public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY;
147
148    /**
149     * Sentinel value which can be passed to a region's
150     * {@link #setMinWidth(double) setMinWidth},
151     * {@link #setMinHeight(double) setMinHeight},
152     * {@link #setPrefWidth(double) setPrefWidth},
153     * {@link #setPrefHeight(double) setPrefHeight},
154     * {@link #setMaxWidth(double) setMaxWidth},
155     * {@link #setMaxHeight(double) setMaxHeight} methods
156     * to reset the region's size constraint back to it's intrinsic size returned
157     * by {@link #computeMinWidth(double) computeMinWidth}, {@link #computeMinHeight(double) computeMinHeight},
158     * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight},
159     * {@link #computeMaxWidth(double) computeMaxWidth}, or {@link #computeMaxHeight(double) computeMaxHeight}.
160     */
161    public static final double USE_COMPUTED_SIZE = -1;
162
163    static Vec2d TEMP_VEC2D = new Vec2d();
164
165    /***************************************************************************
166     *                                                                         *
167     * Static convenience methods for layout                                   *
168     *                                                                         *
169     **************************************************************************/
170
171    /**
172     * Computes the value based on the given min and max values. We encode in this
173     * method the logic surrounding various edge cases, such as when the min is
174     * specified as greater than the max, or the max less than the min, or a pref
175     * value that exceeds either the max or min in their extremes.
176     * <p/>
177     * If the min is greater than the max, then we want to make sure the returned
178     * value is the min. In other words, in such a case, the min becomes the only
179     * acceptable return value.
180     * <p/>
181     * If the min and max values are well ordered, and the pref is less than the min
182     * then the min is returned. Likewise, if the values are well ordered and the
183     * pref is greater than the max, then the max is returned. If the pref lies
184     * between the min and the max, then the pref is returned.
185     *
186     *
187     * @param min The minimum bound
188     * @param pref The value to be clamped between the min and max
189     * @param max the maximum bound
190     * @return the size bounded by min, pref, and max.
191     */
192    static double boundedSize(double min, double pref, double max) {
193        double a = pref >= min ? pref : min;
194        double b = min >= max ? min : max;
195        return a <= b ? a : b;
196    }
197
198    double adjustWidthByMargin(double width, Insets margin) {
199        if (margin == null || margin == Insets.EMPTY) {
200            return width;
201        }
202        boolean isSnapToPixel = isSnapToPixel();
203        return width - snapSpace(margin.getLeft(), isSnapToPixel) - snapSpace(margin.getRight(), isSnapToPixel);
204    }
205
206    double adjustHeightByMargin(double height, Insets margin) {
207        if (margin == null || margin == Insets.EMPTY) {
208            return height;
209        }
210        boolean isSnapToPixel = isSnapToPixel();
211        return height - snapSpace(margin.getTop(), isSnapToPixel) - snapSpace(margin.getBottom(), isSnapToPixel);
212    }
213
214    /**
215     * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
216     * the value is simply returned. This method will surely be JIT'd under normal
217     * circumstances, however on an interpreter it would be better to inline this
218     * method. However the use of Math.round here, and Math.ceil in snapSize is
219     * not obvious, and so for code maintenance this logic is pulled out into
220     * a separate method.
221     *
222     * @param value The value that needs to be snapped
223     * @param snapToPixel Whether to snap to pixel
224     * @return value either as passed in or rounded based on snapToPixel
225     */
226    private static double snapSpace(double value, boolean snapToPixel) {
227        return snapToPixel ? Math.round(value) : value;
228    }
229
230    /**
231     * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise,
232     * the value is simply returned.
233     *
234     * @param value The value that needs to be snapped
235     * @param snapToPixel Whether to snap to pixel
236     * @return value either as passed in or ceil'd based on snapToPixel
237     */
238    private static double snapSize(double value, boolean snapToPixel) {
239        return snapToPixel ? Math.ceil(value) : value;
240    }
241
242    /**
243     * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
244     * the value is simply returned.
245     *
246     * @param value The value that needs to be snapped
247     * @param snapToPixel Whether to snap to pixel
248     * @return value either as passed in or rounded based on snapToPixel
249     */
250    private static double snapPosition(double value, boolean snapToPixel) {
251        return snapToPixel ? Math.round(value) : value;
252    }
253
254    static double getMaxAreaBaselineOffset(List<Node> content, Insets margins[]) {
255        double max = 0;
256        for (int i = 0, maxPos = content.size(); i < maxPos; i++) {
257            final Node node = content.get(i);
258            final double topMargin = margins[i] != null ? margins[i].getTop() : 0;
259            final double position = topMargin + node.getBaselineOffset();
260            max = max >= position ? max : position; // Math.max
261        }
262        return max;
263    }
264
265    static double getMaxBaselineOffset(List<Node> content) {
266        double max = 0;
267        for (int i = 0, maxPos = content.size(); i < maxPos; i++) {
268            final Node node = content.get(i);
269            final double baselineOffset = node.getBaselineOffset();
270            max = max >= baselineOffset ? max : baselineOffset; // Math.max
271        }
272        return max;
273    }
274
275    static double computeXOffset(double width, double contentWidth, HPos hpos) {
276        switch(hpos) {
277            case LEFT:
278                return 0;
279            case CENTER:
280                return (width - contentWidth) / 2;
281            case RIGHT:
282                return width - contentWidth;
283            default:
284                throw new AssertionError("Unhandled hPos");
285        }
286    }
287
288    static double computeYOffset(double height, double contentHeight, VPos vpos) {
289        switch(vpos) {
290            case BASELINE:
291            case TOP:
292                return 0;
293            case CENTER:
294                return (height - contentHeight) / 2;
295            case BOTTOM:
296                return height - contentHeight;
297            default:
298                throw new AssertionError("Unhandled vPos");
299        }
300    }
301
302    static double[] createDoubleArray(int length, double value) {
303        double[] array = new double[length];
304        for (int i = 0; i < length; i++) {
305            array[i] = value;
306        }
307        return array;
308    }
309
310
311    /***************************************************************************
312     *                                                                         *
313     * Constructors                                                            *
314     *                                                                         *
315     **************************************************************************/
316
317    /**
318     * Creates a new Region with an empty Background and and empty Border. The
319     * Region defaults to having pickOnBounds set to true, meaning that any pick
320     * (mouse picking or touch picking etc) that occurs within the bounds in local
321     * of the Region will return true, regardless of whether the Region is filled
322     * or transparent.
323     */
324    public Region() {
325        super();
326        setPickOnBounds(true);
327    }
328
329    /***************************************************************************
330     *                                                                         *
331     * Region properties                                                       *
332     *                                                                         *
333     **************************************************************************/
334
335    /**
336     * Defines whether this region adjusts position, spacing, and size values of
337     * its children to pixel boundaries. This defaults to true, which is generally
338     * the expected behavior in order to have crisp user interfaces. A value of
339     * false will allow for fractional alignment, which may lead to "fuzzy"
340     * looking borders.
341     */
342    private BooleanProperty snapToPixel;
343    /**
344     * I'm using a super-lazy property pattern here, so as to only create the
345     * property object when needed for listeners or when being set from CSS,
346     * but also making sure that we only call requestParentLayout in the case
347     * that the snapToPixel value has actually changed, whether set via the setter
348     * or set via the property object.
349     */
350    private boolean _snapToPixel = true;
351    public final boolean isSnapToPixel() { return _snapToPixel; }
352    public final void setSnapToPixel(boolean value) {
353        if (snapToPixel == null) {
354            if (_snapToPixel != value) {
355                _snapToPixel = value;
356                updateSnappedInsets();
357                requestParentLayout();
358            }
359        } else {
360            snapToPixel.set(value);
361        }
362    }
363    public final BooleanProperty snapToPixelProperty() {
364        // Note: snapToPixel is virtually never set, and never listened to.
365        // Because of this, it works reasonably well as a lazy property,
366        // since this logic is just about never going to be called.
367        if (snapToPixel == null) {
368            snapToPixel = new StyleableBooleanProperty(_snapToPixel) {
369                @Override public Object getBean() { return Region.this; }
370                @Override public String getName() { return "snapToPixel"; }
371                @Override public CssMetaData<Region, Boolean> getCssMetaData() {
372                    return StyleableProperties.SNAP_TO_PIXEL;
373                }
374                @Override public void invalidated() {
375                    boolean value = get();
376                    if (_snapToPixel != value) {
377                        _snapToPixel = value;
378                        updateSnappedInsets();
379                        requestParentLayout();
380                    }
381                }
382            };
383        }
384        return snapToPixel;
385    }
386
387    /**
388     * The top, right, bottom, and left padding around the region's content.
389     * This space will be included in the calculation of the region's
390     * minimum and preferred sizes. By default padding is Insets.EMPTY. Setting the
391     * value to null should be avoided.
392     */
393    private ObjectProperty<Insets> padding = new StyleableObjectProperty<Insets>(Insets.EMPTY) {
394        // Keep track of the last valid value for the sake of
395        // rollback in case padding is set to null. Note that
396        // Richard really does not like this pattern because
397        // it essentially means that binding the padding property
398        // is not possible since a binding expression could very
399        // easily produce an intermediate null value.
400
401        // Also note that because padding is set virtually everywhere via CSS, and CSS
402        // requires a property object in order to set it, there is no benefit to having
403        // lazy initialization here.
404
405        private Insets lastValidValue = Insets.EMPTY;
406
407        @Override public Object getBean() { return Region.this; }
408        @Override public String getName() { return "padding"; }
409        @Override public CssMetaData<Region, Insets> getCssMetaData() {
410            return StyleableProperties.PADDING;
411        }
412        @Override public void invalidated() {
413            final Insets newValue = get();
414            if (newValue == null) {
415                // rollback
416                if (isBound()) {
417                    unbind();
418                }
419                set(lastValidValue);
420                throw new NullPointerException("cannot set padding to null");
421            }
422            lastValidValue = newValue;
423            insets.fireValueChanged();
424        }
425    };
426    public final void setPadding(Insets value) { padding.set(value); }
427    public final Insets getPadding() { return padding.get(); }
428    public final ObjectProperty<Insets> paddingProperty() { return padding; }
429
430    /**
431     * The background of the Region, which is made up of zero or more BackgroundFills, and
432     * zero or more BackgroundImages. It is possible for a Background to be empty, where it
433     * has neither fills nor images, and is semantically equivalent to null.
434     */
435    private final ObjectProperty<Background> background = new StyleableObjectProperty<Background>(null) {
436        private Background old = null;
437        @Override public Object getBean() { return Region.this; }
438        @Override public String getName() { return "background"; }
439        @Override public CssMetaData<Region, Background> getCssMetaData() {
440            return StyleableProperties.BACKGROUND;
441        }
442
443        @Override protected void invalidated() {
444            final Background b = get();
445            if(old != null ? !old.equals(b) : b != null) {
446                // They are different! Both cannot be null
447                if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
448                    // We have determined that the outsets of these two different background
449                    // objects is different, and therefore the bounds have changed.
450                    impl_geomChanged();
451                    insets.fireValueChanged();
452                }
453                // No matter what, the fill has changed, so we have to update it
454                impl_markDirty(DirtyBits.SHAPE_FILL);
455                old = b;
456            }
457        }
458    };
459    public final void setBackground(Background value) { background.set(value); }
460    public final Background getBackground() { return background.get(); }
461    public final ObjectProperty<Background> backgroundProperty() { return background; }
462
463    /**
464     * The border of the Region, which is made up of zero or more BorderStrokes, and
465     * zero or more BorderImages. It is possible for a Border to be empty, where it
466     * has neither strokes nor images, and is semantically equivalent to null.
467     */
468    private final ObjectProperty<Border> border = new StyleableObjectProperty<Border>(null) {
469        private Border old = null;
470        @Override public Object getBean() { return Region.this; }
471        @Override public String getName() { return "border"; }
472        @Override public CssMetaData<Region, Border> getCssMetaData() {
473            return StyleableProperties.BORDER;
474        }
475        @Override protected void invalidated() {
476            final Border b = get();
477            if(old != null ? !old.equals(b) : b != null) {
478                // They are different! Both cannot be null
479                if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
480                    // We have determined that the outsets of these two different border
481                    // objects is different, and therefore the bounds have changed.
482                    impl_geomChanged();
483                    insets.fireValueChanged();
484                }
485                // No matter what, the fill has changed, so we have to update it
486                impl_markDirty(DirtyBits.SHAPE_STROKE);
487                old = b;
488            }
489        }
490    };
491    public final void setBorder(Border value) { border.set(value); }
492    public final Border getBorder() { return border.get(); }
493    public final ObjectProperty<Border> borderProperty() { return border; }
494
495    /**
496     * Defines the area of the region within which completely opaque pixels
497     * are drawn. This is used for various performance optimizations.
498     * The pixels within this area MUST BE fully opaque, or rendering
499     * artifacts will result. It is the responsibility of the application, either
500     * via code or via CSS, to ensure that the opaqueInsets is correct for
501     * a Region based on the backgrounds and borders of that region. The values
502     * for each of the insets must be real numbers, not NaN or Infinity. If
503     * no known insets exist, then the opaqueInsets should be set to null.
504     */
505    public final ObjectProperty<Insets> opaqueInsetsProperty() {
506        if (opaqueInsets == null) {
507            opaqueInsets = new StyleableObjectProperty<Insets>() {
508                @Override public Object getBean() { return Region.this; }
509                @Override public String getName() { return "opaqueInsets"; }
510                @Override public CssMetaData<Region, Insets> getCssMetaData() {
511                    return StyleableProperties.OPAQUE_INSETS;
512                }
513                @Override protected void invalidated() {
514                    // This causes the background to be updated, which
515                    // is the code block where we also compute the opaque insets
516                    // since updating the background is super fast even when
517                    // nothing has changed.
518                    impl_markDirty(DirtyBits.SHAPE_FILL);
519                }
520            };
521        }
522        return opaqueInsets;
523    }
524    private ObjectProperty<Insets> opaqueInsets;
525    public final void setOpaqueInsets(Insets value) { opaqueInsetsProperty().set(value); }
526    public final Insets getOpaqueInsets() { return opaqueInsets == null ? null : opaqueInsets.get(); }
527
528    /**
529     * The insets of the Region define the distance from the edge of the region (its layout bounds,
530     * or (0, 0, width, height)) to the edge of the content area. All child nodes should be laid out
531     * within the content area. The insets are computed based on the Border which has been specified,
532     * if any, and also the padding.
533     */
534    private InsetsProperty insets = new InsetsProperty();
535    public final Insets getInsets() { return insets.get(); }
536    public final ReadOnlyObjectProperty<Insets> insetsProperty() { return insets; }
537    private final class InsetsProperty extends ReadOnlyObjectProperty<Insets> {
538        private Insets cache = null;
539        private ExpressionHelper<Insets> helper = null;
540
541        @Override public Object getBean() { return Region.this; }
542        @Override public String getName() { return "insets"; }
543
544        @Override public void addListener(InvalidationListener listener) {
545            helper = ExpressionHelper.addListener(helper, this, listener);
546        }
547
548        @Override public void removeListener(InvalidationListener listener) {
549            helper = ExpressionHelper.removeListener(helper, listener);
550        }
551
552        @Override public void addListener(ChangeListener<? super Insets> listener) {
553            helper = ExpressionHelper.addListener(helper, this, listener);
554        }
555
556        @Override public void removeListener(ChangeListener<? super Insets> listener) {
557            helper = ExpressionHelper.removeListener(helper, listener);
558        }
559
560        void fireValueChanged() {
561            cache = null;
562            updateSnappedInsets();
563            requestLayout();
564            ExpressionHelper.fireValueChangedEvent(helper);
565        }
566
567        @Override public Insets get() {
568            // If a shape is specified, then we don't really care whether there are any borders
569            // specified, since borders of shapes do not contribute to the insets.
570            if (_shape != null) return getPadding();
571
572            // If there is no border or the border has no insets itself, then the only thing
573            // affecting the insets is the padding, so we can just return it directly.
574            final Border b = getBorder();
575            if (b == null || Insets.EMPTY.equals(b.getInsets())) {
576                return getPadding();
577            }
578
579            // There is a border with some non-zero insets and we do not have a _shape, so we need
580            // to take the border's insets into account
581            if (cache == null) {
582                // Combine the padding and the border insets.
583                // TODO note that negative border insets were being ignored, but
584                // I'm not sure that that made sense or was reasonable, so I have
585                // changed it so that we just do simple math.
586                // TODO Stroke borders should NOT contribute to the insets. Ensure via tests.
587                final Insets borderInsets = b.getInsets();
588                final Insets paddingInsets = getPadding();
589                cache = new Insets(
590                        borderInsets.getTop() + paddingInsets.getTop(),
591                        borderInsets.getRight() + paddingInsets.getRight(),
592                        borderInsets.getBottom() + paddingInsets.getBottom(),
593                        borderInsets.getLeft() + paddingInsets.getLeft()
594                );
595            }
596            return cache;
597        }
598    };
599
600    /**
601     * cached results of snapped insets, this are used a lot during layout so makes sense
602     * to keep fast access cached copies here.
603     */
604    private double snappedTopInset = 0;
605    private double snappedRightInset = 0;
606    private double snappedBottomInset = 0;
607    private double snappedLeftInset = 0;
608
609    /** Called to update the cached snapped insets */
610    private void updateSnappedInsets() {
611        final Insets insets = getInsets();
612        if (_snapToPixel) {
613            snappedTopInset = Math.ceil(insets.getTop());
614            snappedRightInset = Math.ceil(insets.getRight());
615            snappedBottomInset = Math.ceil(insets.getBottom());
616            snappedLeftInset = Math.ceil(insets.getLeft());
617        } else {
618            snappedTopInset = insets.getTop();
619            snappedRightInset = insets.getRight();
620            snappedBottomInset = insets.getBottom();
621            snappedLeftInset = insets.getLeft();
622        }
623    }
624
625    /**
626    * The width of this resizable node.  This property is set by the region's parent
627    * during layout and may not be set by the application.  If an application
628    * needs to explicitly control the size of a region, it should override its
629    * preferred size range by setting the <code>minWidth</code>, <code>prefWidth</code>,
630    * and <code>maxWidth</code> properties.
631    */
632    private ReadOnlyDoubleWrapper width;
633
634    /**
635     * Because the width is very often set and very often read but only sometimes
636     * listened to, it is beneficial to use the super-lazy pattern property, where we
637     * only inflate the property object when widthProperty() is explicitly invoked.
638     */
639    private double _width;
640
641    // Note that it is OK for this method to be protected so long as the width
642    // property is never bound. Only Region could do so because only Region has
643    // access to a writable property for "width", but since there is now a protected
644    // set method, it is impossible for Region to ever bind this property.
645    protected void setWidth(double value) {
646        if(width == null) {
647            widthChanged(value);
648        } else {
649            width.set(value);
650        }
651    }
652
653    private void widthChanged(double value) {
654        // It is possible that somebody sets the width of the region to a value which
655        // it previously held. If this is the case, we want to avoid excessive layouts.
656        // Note that I have biased this for layout over binding, because the widthProperty
657        // is now going to recompute the width eagerly. The cost of excessive and
658        // unnecessary bounds changes, however, is relatively high.
659        if (value != _width) {
660            _width = value;
661            boundingBox = null;
662            impl_layoutBoundsChanged();
663            impl_geomChanged();
664            impl_markDirty(DirtyBits.NODE_GEOMETRY);
665            requestLayout();
666        }
667    }
668
669    public final double getWidth() { return width == null ? _width : width.get(); }
670
671    public final ReadOnlyDoubleProperty widthProperty() {
672        if (width == null) {
673            width = new ReadOnlyDoubleWrapper(_width) {
674                @Override protected void invalidated() { widthChanged(get()); }
675                @Override public Object getBean() { return Region.this; }
676                @Override public String getName() { return "width"; }
677            };
678        }
679        return width.getReadOnlyProperty();
680    }
681
682    /**
683     * The height of this resizable node.  This property is set by the region's parent
684     * during layout and may not be set by the application.  If an application
685     * needs to explicitly control the size of a region, it should override its
686     * preferred size range by setting the <code>minHeight</code>, <code>prefHeight</code>,
687     * and <code>maxHeight</code> properties.
688     */
689    private ReadOnlyDoubleWrapper height;
690
691    /**
692     * Because the height is very often set and very often read but only sometimes
693     * listened to, it is beneficial to use the super-lazy pattern property, where we
694     * only inflate the property object when heightProperty() is explicitly invoked.
695     */
696    private double _height;
697
698    // Note that it is OK for this method to be protected so long as the height
699    // property is never bound. Only Region could do so because only Region has
700    // access to a writable property for "height", but since there is now a protected
701    // set method, it is impossible for Region to ever bind this property.
702    protected void setHeight(double value) {
703        if (height == null) {
704            heightChanged(value);
705        } else {
706            height.set(value);
707        }
708    }
709
710    private void heightChanged(double value) {
711        if (_height != value) {
712            _height = value;
713            // It is possible that somebody sets the height of the region to a value which
714            // it previously held. If this is the case, we want to avoid excessive layouts.
715            // Note that I have biased this for layout over binding, because the heightProperty
716            // is now going to recompute the height eagerly. The cost of excessive and
717            // unnecessary bounds changes, however, is relatively high.
718            boundingBox = null;
719            // Note: although impl_geomChanged will usually also invalidate the
720            // layout bounds, that is not the case for Regions, and both must
721            // be called separately.
722            impl_geomChanged();
723            impl_layoutBoundsChanged();
724            // We use "NODE_GEOMETRY" to mean that the bounds have changed and
725            // need to be sync'd with the render tree
726            impl_markDirty(DirtyBits.NODE_GEOMETRY);
727            // TODO why do we do this? If the height can only be changed during
728            // layout, and if calls to requestLayout are ignored during layout,
729            // then why do we call requestLayout? It does protect against the case
730            // that a developer called resize() or whatnot outside of layout, in
731            // which case on the next pulse we'll "correct" the size according
732            // to layout. But I am not sure this case, which produces a visual "bug"
733            // anyway, is worth the cost? The same would go for the widthChanged.
734            requestLayout();
735        }
736    }
737
738    public final double getHeight() { return height == null ? _height : height.get(); }
739
740    public final ReadOnlyDoubleProperty heightProperty() {
741        if (height == null) {
742            height = new ReadOnlyDoubleWrapper(_height) {
743                @Override protected void invalidated() { heightChanged(get()); }
744                @Override public Object getBean() { return Region.this; }
745                @Override public String getName() { return "height"; }
746            };
747        }
748        return height.getReadOnlyProperty();
749    }
750
751    private void requestParentLayout() {
752        Parent parent = getParent();
753        if (parent != null) {
754            parent.requestLayout();
755        }
756    }
757
758    /**
759     * This class is reused for the min, pref, and max properties since
760     * they all performed the same function (to call requestParentLayout).
761     */
762    private final class MinPrefMaxProperty extends StyleableDoubleProperty {
763        private final String name;
764        private final CssMetaData<? extends Styleable, Number> cssMetaData;
765
766        MinPrefMaxProperty(String name, double initialValue, CssMetaData<? extends Styleable, Number> cssMetaData) {
767            super(initialValue);
768            this.name = name;
769            this.cssMetaData = cssMetaData;
770        }
771
772        @Override public void invalidated() { requestParentLayout(); }
773        @Override public Object getBean() { return Region.this; }
774        @Override public String getName() { return name; }
775
776        @Override
777        public CssMetaData<? extends Styleable, Number> getCssMetaData() {
778            return cssMetaData;
779        }
780    }
781
782    /**
783     * Property for overriding the region's computed minimum width.
784     * This should only be set if the region's internally computed minimum width
785     * doesn't meet the application's layout needs.
786     * <p>
787     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
788     * <code>minWidth(forHeight)</code> will return the region's internally
789     * computed minimum width.
790     * <p>
791     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
792     * <code>minWidth(forHeight)</code> to return the region's preferred width,
793     * enabling applications to easily restrict the resizability of the region.
794     */
795    private DoubleProperty minWidth;
796    private double _minWidth = USE_COMPUTED_SIZE;
797    public final void setMinWidth(double value) {
798        if (minWidth == null) {
799            _minWidth = value;
800            requestParentLayout();
801        } else {
802            minWidth.set(value);
803        }
804    }
805    public final double getMinWidth() { return minWidth == null ? _minWidth : minWidth.get(); }
806    public final DoubleProperty minWidthProperty() {
807        if (minWidth == null) minWidth = new MinPrefMaxProperty("minWidth", _minWidth, StyleableProperties.MIN_WIDTH);
808        return minWidth;
809    }
810
811    /**
812     * Property for overriding the region's computed minimum height.
813     * This should only be set if the region's internally computed minimum height
814     * doesn't meet the application's layout needs.
815     * <p>
816     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
817     * <code>minHeight(forWidth)</code> will return the region's internally
818     * computed minimum height.
819     * <p>
820     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
821     * <code>minHeight(forWidth)</code> to return the region's preferred height,
822     * enabling applications to easily restrict the resizability of the region.
823     *
824     */
825    private DoubleProperty minHeight;
826    private double _minHeight = USE_COMPUTED_SIZE;
827    public final void setMinHeight(double value) {
828        if (minHeight == null) {
829            _minHeight = value;
830            requestParentLayout();
831        } else {
832            minHeight.set(value);
833        }
834    }
835    public final double getMinHeight() { return minHeight == null ? _minHeight : minHeight.get(); }
836    public final DoubleProperty minHeightProperty() {
837        if (minHeight == null) minHeight = new MinPrefMaxProperty("minHeight", _minHeight, StyleableProperties.MIN_HEIGHT);
838        return minHeight;
839    }
840
841    /**
842     * Convenience method for overriding the region's computed minimum width and height.
843     * This should only be called if the region's internally computed minimum size
844     * doesn't meet the application's layout needs.
845     *
846     * @see #setMinWidth
847     * @see #setMinHeight
848     * @param minWidth  the override value for minimum width
849     * @param minHeight the override value for minimum height
850     */
851    public void setMinSize(double minWidth, double minHeight) {
852        setMinWidth(minWidth);
853        setMinHeight(minHeight);
854    }
855
856    /**
857     * Property for overriding the region's computed preferred width.
858     * This should only be set if the region's internally computed preferred width
859     * doesn't meet the application's layout needs.
860     * <p>
861     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
862     * <code>getPrefWidth(forHeight)</code> will return the region's internally
863     * computed preferred width.
864     */
865    private DoubleProperty prefWidth;
866    private double _prefWidth = USE_COMPUTED_SIZE;
867    public final void setPrefWidth(double value) {
868        if (prefWidth == null) {
869            _prefWidth = value;
870            requestParentLayout();
871        } else {
872            prefWidth.set(value);
873        }
874    }
875    public final double getPrefWidth() { return prefWidth == null ? _prefWidth : prefWidth.get(); }
876    public final DoubleProperty prefWidthProperty() {
877        if (prefWidth == null) prefWidth = new MinPrefMaxProperty("prefWidth", _prefWidth, StyleableProperties.PREF_WIDTH);
878        return prefWidth;
879    }
880
881    /**
882     * Property for overriding the region's computed preferred height.
883     * This should only be set if the region's internally computed preferred height
884     * doesn't meet the application's layout needs.
885     * <p>
886     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
887     * <code>getPrefHeight(forWidth)</code> will return the region's internally
888     * computed preferred width.
889     */
890    private DoubleProperty prefHeight;
891    private double _prefHeight = USE_COMPUTED_SIZE;
892    public final void setPrefHeight(double value) {
893        if (prefHeight == null) {
894            _prefHeight = value;
895            requestParentLayout();
896        } else {
897            prefHeight.set(value);
898        }
899    }
900    public final double getPrefHeight() { return prefHeight == null ? _prefHeight : prefHeight.get(); }
901    public final DoubleProperty prefHeightProperty() {
902        if (prefHeight == null) prefHeight = new MinPrefMaxProperty("prefHeight", _prefHeight, StyleableProperties.PREF_HEIGHT);
903        return prefHeight;
904    }
905
906    /**
907     * Convenience method for overriding the region's computed preferred width and height.
908     * This should only be called if the region's internally computed preferred size
909     * doesn't meet the application's layout needs.
910     *
911     * @see #setPrefWidth
912     * @see #setPrefHeight
913     * @param prefWidth the override value for preferred width
914     * @param prefHeight the override value for preferred height
915     */
916    public void setPrefSize(double prefWidth, double prefHeight) {
917        setPrefWidth(prefWidth);
918        setPrefHeight(prefHeight);
919    }
920
921    /**
922     * Property for overriding the region's computed maximum width.
923     * This should only be set if the region's internally computed maximum width
924     * doesn't meet the application's layout needs.
925     * <p>
926     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
927     * <code>getMaxWidth(forHeight)</code> will return the region's internally
928     * computed maximum width.
929     * <p>
930     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
931     * <code>getMaxWidth(forHeight)</code> to return the region's preferred width,
932     * enabling applications to easily restrict the resizability of the region.
933     */
934    private DoubleProperty maxWidth;
935    private double _maxWidth = USE_COMPUTED_SIZE;
936    public final void setMaxWidth(double value) {
937        if (maxWidth == null) {
938            _maxWidth = value;
939            requestParentLayout();
940        } else {
941            maxWidth.set(value);
942        }
943    }
944    public final double getMaxWidth() { return maxWidth == null ? _maxWidth : maxWidth.get(); }
945    public final DoubleProperty maxWidthProperty() {
946        if (maxWidth == null) maxWidth = new MinPrefMaxProperty("maxWidth", _maxWidth, StyleableProperties.MAX_WIDTH);
947        return maxWidth;
948    }
949
950    /**
951     * Property for overriding the region's computed maximum height.
952     * This should only be set if the region's internally computed maximum height
953     * doesn't meet the application's layout needs.
954     * <p>
955     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
956     * <code>getMaxHeight(forWidth)</code> will return the region's internally
957     * computed maximum height.
958     * <p>
959     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
960     * <code>getMaxHeight(forWidth)</code> to return the region's preferred height,
961     * enabling applications to easily restrict the resizability of the region.
962     */
963    private DoubleProperty maxHeight;
964    private double _maxHeight = USE_COMPUTED_SIZE;
965    public final void setMaxHeight(double value) {
966        if (maxHeight == null) {
967            _maxHeight = value;
968            requestParentLayout();
969        } else {
970            maxHeight.set(value);
971        }
972    }
973    public final double getMaxHeight() { return maxHeight == null ? _maxHeight : maxHeight.get(); }
974    public final DoubleProperty maxHeightProperty() {
975        if (maxHeight == null) maxHeight = new MinPrefMaxProperty("maxHeight", _maxHeight, StyleableProperties.MAX_HEIGHT);
976        return maxHeight;
977    }
978
979    /**
980     * Convenience method for overriding the region's computed maximum width and height.
981     * This should only be called if the region's internally computed maximum size
982     * doesn't meet the application's layout needs.
983     *
984     * @see #setMaxWidth
985     * @see #setMaxHeight
986     * @param maxWidth  the override value for maximum width
987     * @param maxHeight the override value for maximum height
988     */
989    public void setMaxSize(double maxWidth, double maxHeight) {
990        setMaxWidth(maxWidth);
991        setMaxHeight(maxHeight);
992    }
993
994    /**
995     * When specified, the {@code shape} will cause the region to be
996     * rendered as the specified shape rather than as a rounded rectangle.
997     * When null, the Region is rendered as a rounded rectangle. When rendered
998     * as a Shape, any Background is used to fill the shape, although any
999     * background insets are ignored as are background radii. Any BorderStrokes
1000     * defined are used for stroking the shape. Any BorderImages are ignored.
1001     *
1002     * @default null
1003     * @css shape SVG shape string
1004     */
1005    private ObjectProperty<Shape> shape = null;
1006    private Shape _shape;
1007    public final Shape getShape() { return shape == null ? _shape : shape.get(); }
1008    public final void setShape(Shape value) { shapeProperty().set(value); }
1009    public final ObjectProperty<Shape> shapeProperty() {
1010        if (shape == null) {
1011            shape = new ShapeProperty();
1012        }
1013        return shape;
1014    }
1015
1016    /**
1017     * An implementation for the ShapeProperty. This is also a ShapeChangeListener.
1018     */
1019    private final class ShapeProperty extends StyleableObjectProperty<Shape> implements Runnable {
1020        @Override public Object getBean() { return Region.this; }
1021        @Override public String getName() { return "shape"; }
1022        @Override public CssMetaData<Region, Shape> getCssMetaData() {
1023            return StyleableProperties.SHAPE;
1024        }
1025        @Override protected void invalidated() {
1026            final Shape value = get();
1027            if (_shape != value) {
1028                // The shape has changed. We need to add/remove listeners
1029                if (_shape != null) _shape.impl_setShapeChangeListener(null);
1030                if (value != null) value.impl_setShapeChangeListener(this);
1031                // Invalidate the bounds and such
1032                run();
1033                if (_shape == null || value == null) {
1034                    // It either was null before, or is null now. In either case,
1035                    // the result of the insets computation will have changed, and
1036                    // we therefore need to fire that the insets value may have changed.
1037                    insets.fireValueChanged();
1038                }
1039                // Update our reference to the old shape
1040                _shape = value;
1041            }
1042        }
1043
1044        @Override public void run() {
1045            impl_geomChanged();
1046            requestLayout();
1047            impl_markDirty(DirtyBits.REGION_SHAPE);
1048        }
1049    };
1050
1051    /**
1052     * Specifies whether the shape, if defined, is scaled to match the size of the Region.
1053     * {@code true} means the shape is scaled to fit the size of the Region, {@code false}
1054     * means the shape is at its source size, its positioning depends on the value of
1055     * {@code centerShape}.
1056     *
1057     * @default true
1058     * @css shape-size      true | false
1059     */
1060    private BooleanProperty scaleShape = null;
1061    public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); }
1062    public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); }
1063    public final BooleanProperty scaleShapeProperty() {
1064        if (scaleShape == null) {
1065            scaleShape = new StyleableBooleanProperty(true) {
1066                @Override public Object getBean() { return Region.this; }
1067                @Override public String getName() { return "scaleShape"; }
1068                @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1069                    return StyleableProperties.SCALE_SHAPE;
1070                }
1071                @Override public void invalidated() {
1072                    // TODO should be requestParentLayout?
1073                    requestLayout();
1074                    impl_markDirty(DirtyBits.REGION_SHAPE);
1075                }
1076            };
1077        }
1078        return scaleShape;
1079    }
1080
1081    /**
1082     * Defines whether the shape is centered within the Region's width or height.
1083     * {@code true} means the shape centered within the Region's width and height,
1084     * {@code false} means the shape is positioned at its source position.
1085     *
1086     * @default true
1087     * @css position-shape      true | false
1088     */
1089    private BooleanProperty centerShape = null;
1090    public final void setCenterShape(boolean value) { centerShapeProperty().set(value); }
1091    public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); }
1092    public final BooleanProperty centerShapeProperty() {
1093        if (centerShape == null) {
1094            centerShape = new StyleableBooleanProperty(true) {
1095                @Override public Object getBean() { return Region.this; }
1096                @Override public String getName() { return "centerShape"; }
1097                @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1098                    return StyleableProperties.POSITION_SHAPE;
1099                }
1100                @Override public void invalidated() {
1101                    // TODO should be requestParentLayout?
1102                    requestLayout();
1103                    impl_markDirty(DirtyBits.REGION_SHAPE);
1104                }
1105            };
1106        }
1107        return centerShape;
1108    }
1109
1110    /**
1111     * Defines a hint to the system indicating that the Shape used to define the region's
1112     * background is stable and would benefit from caching.
1113     *
1114     * @default true
1115     * @css -fx-cache-shape      true | false
1116     */
1117    private BooleanProperty cacheShape = null;
1118    public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); }
1119    public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); }
1120    public final BooleanProperty cacheShapeProperty() {
1121        if (cacheShape == null) {
1122            cacheShape = new StyleableBooleanProperty(true) {
1123                @Override public Object getBean() { return Region.this; }
1124                @Override public String getName() { return "cacheShape"; }
1125                @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1126                    return StyleableProperties.CACHE_SHAPE;
1127                }
1128            };
1129        }
1130        return cacheShape;
1131    }
1132
1133    /***************************************************************************
1134     *                                                                         *
1135     * Layout                                                                  *
1136     *                                                                         *
1137     **************************************************************************/
1138
1139    /**
1140     * Returns <code>true</code> since all Regions are resizable.
1141     * @return whether this node can be resized by its parent during layout
1142     */
1143    @Override public boolean isResizable() {
1144        return true;
1145    }
1146
1147    /**
1148     * Invoked by the region's parent during layout to set the region's
1149     * width and height.  <b>Applications should not invoke this method directly</b>.
1150     * If an application needs to directly set the size of the region, it should
1151     * override its size constraints by calling <code>setMinSize()</code>,
1152     *  <code>setPrefSize()</code>, or <code>setMaxSize()</code> and it's parent
1153     * will honor those overrides during layout.
1154     *
1155     * @param width the target layout bounds width
1156     * @param height the target layout bounds height
1157     */
1158    @Override public void resize(double width, double height) {
1159        setWidth(width);
1160        setHeight(height);
1161        PlatformLogger logger = Logging.getLayoutLogger();
1162        if (logger.isLoggable(PlatformLogger.FINER)) {
1163            logger.finer(this.toString() + " resized to " + width + " x " + height);
1164        }
1165    }
1166
1167    /**
1168     * Called during layout to determine the minimum width for this node.
1169     * Returns the value from <code>computeMinWidth(forHeight)</code> unless
1170     * the application overrode the minimum width by setting the minWidth property.
1171     *
1172     * @see #setMinWidth(double)
1173     * @return the minimum width that this node should be resized to during layout
1174     */
1175    @Override public final double minWidth(double height) {
1176        double override = getMinWidth();
1177        if (override == USE_COMPUTED_SIZE) {
1178            return super.minWidth(height);
1179        } else if (override == USE_PREF_SIZE) {
1180            return prefWidth(height);
1181        }
1182        return override;
1183    }
1184
1185    /**
1186     * Called during layout to determine the minimum height for this node.
1187     * Returns the value from <code>computeMinHeight(forWidth)</code> unless
1188     * the application overrode the minimum height by setting the minHeight property.
1189     *
1190     * @see #setMinHeight
1191     * @return the minimum height that this node should be resized to during layout
1192     */
1193    @Override public final double minHeight(double width) {
1194        double override = getMinHeight();
1195        if (override == USE_COMPUTED_SIZE) {
1196            return super.minHeight(width);
1197        } else if (override == USE_PREF_SIZE) {
1198            return prefHeight(width);
1199        }
1200        return override;
1201    }
1202
1203    /**
1204     * Called during layout to determine the preferred width for this node.
1205     * Returns the value from <code>computePrefWidth(forHeight)</code> unless
1206     * the application overrode the preferred width by setting the prefWidth property.
1207     *
1208     * @see #setPrefWidth
1209     * @return the preferred width that this node should be resized to during layout
1210     */
1211    @Override public final double prefWidth(double height) {
1212        double override = getPrefWidth();
1213        if (override == USE_COMPUTED_SIZE) {
1214            return super.prefWidth(height);
1215        }
1216        return override;
1217    }
1218
1219    /**
1220     * Called during layout to determine the preferred height for this node.
1221     * Returns the value from <code>computePrefHeight(forWidth)</code> unless
1222     * the application overrode the preferred height by setting the prefHeight property.
1223     *
1224     * @see #setPrefHeight
1225     * @return the preferred height that this node should be resized to during layout
1226     */
1227    @Override public final double prefHeight(double width) {
1228        double override = getPrefHeight();
1229        if (override == USE_COMPUTED_SIZE) {
1230            return super.prefHeight(width);
1231        }
1232        return override;
1233    }
1234
1235    /**
1236     * Called during layout to determine the maximum width for this node.
1237     * Returns the value from <code>computeMaxWidth(forHeight)</code> unless
1238     * the application overrode the maximum width by setting the maxWidth property.
1239     *
1240     * @see #setMaxWidth
1241     * @return the maximum width that this node should be resized to during layout
1242     */
1243    @Override public final double maxWidth(double height) {
1244        double override = getMaxWidth();
1245        if (override == USE_COMPUTED_SIZE) {
1246            return computeMaxWidth(height);
1247        } else if (override == USE_PREF_SIZE) {
1248            return prefWidth(height);
1249        }
1250        return override;
1251    }
1252
1253    /**
1254     * Called during layout to determine the maximum height for this node.
1255     * Returns the value from <code>computeMaxHeight(forWidth)</code> unless
1256     * the application overrode the maximum height by setting the maxHeight property.
1257     *
1258     * @see #setMaxHeight
1259     * @return the maximum height that this node should be resized to during layout
1260     */
1261    @Override public final double maxHeight(double width) {
1262        double override = getMaxHeight();
1263        if (override == USE_COMPUTED_SIZE) {
1264            return computeMaxHeight(width);
1265        } else if (override == USE_PREF_SIZE) {
1266            return prefHeight(width);
1267        }
1268        return override;
1269    }
1270
1271    /**
1272     * Computes the minimum width of this region.
1273     * Returns the sum of the left and right insets by default.
1274     * region subclasses should override this method to return an appropriate
1275     * value based on their content and layout strategy.  If the subclass
1276     * doesn't have a VERTICAL content bias, then the height parameter can be
1277     * ignored.
1278     *
1279     * @return the computed minimum width of this region
1280     */
1281    @Override protected double computeMinWidth(double height) {
1282        return getInsets().getLeft() + getInsets().getRight();
1283    }
1284
1285    /**
1286     * Computes the minimum height of this region.
1287     * Returns the sum of the top and bottom insets by default.
1288     * Region subclasses should override this method to return an appropriate
1289     * value based on their content and layout strategy.  If the subclass
1290     * doesn't have a HORIZONTAL content bias, then the width parameter can be
1291     * ignored.
1292     *
1293     * @return the computed minimum height for this region
1294     */
1295    @Override protected double computeMinHeight(double width) {
1296        return getInsets().getTop() + getInsets().getBottom();
1297    }
1298
1299    /**
1300     * Computes the preferred width of this region for the given height.
1301     * Region subclasses should override this method to return an appropriate
1302     * value based on their content and layout strategy.  If the subclass
1303     * doesn't have a VERTICAL content bias, then the height parameter can be
1304     * ignored.
1305     *
1306     * @return the computed preferred width for this region
1307     */
1308    @Override protected double computePrefWidth(double height) {
1309        final double w = super.computePrefWidth(height);
1310        return getInsets().getLeft() + w + getInsets().getRight();
1311    }
1312
1313    /**
1314     * Computes the preferred height of this region for the given width;
1315     * Region subclasses should override this method to return an appropriate
1316     * value based on their content and layout strategy.  If the subclass
1317     * doesn't have a HORIZONTAL content bias, then the width parameter can be
1318     * ignored.
1319     *
1320     * @return the computed preferred height for this region
1321     */
1322    @Override protected double computePrefHeight(double width) {
1323        final double h = super.computePrefHeight(width);
1324        return getInsets().getTop() + h + getInsets().getBottom();
1325    }
1326
1327    /**
1328     * Computes the maximum width for this region.
1329     * Returns Double.MAX_VALUE by default.
1330     * Region subclasses may override this method to return an different
1331     * value based on their content and layout strategy.  If the subclass
1332     * doesn't have a VERTICAL content bias, then the height parameter can be
1333     * ignored.
1334     *
1335     * @return the computed maximum width for this region
1336     */
1337    protected double computeMaxWidth(double height) {
1338        return Double.MAX_VALUE;
1339    }
1340
1341    /**
1342     * Computes the maximum height of this region.
1343     * Returns Double.MAX_VALUE by default.
1344     * Region subclasses may override this method to return a different
1345     * value based on their content and layout strategy.  If the subclass
1346     * doesn't have a HORIZONTAL content bias, then the width parameter can be
1347     * ignored.
1348     *
1349     * @return the computed maximum height for this region
1350     */
1351    protected double computeMaxHeight(double width) {
1352        return Double.MAX_VALUE;
1353    }
1354
1355    /**
1356     * If this region's snapToPixel property is true, returns a value rounded
1357     * to the nearest pixel, else returns the same value.
1358     * @param value the space value to be snapped
1359     * @return value rounded to nearest pixel
1360     */
1361    protected double snapSpace(double value) {
1362        return snapSpace(value, isSnapToPixel());
1363    }
1364
1365    /**
1366     * If this region's snapToPixel property is true, returns a value ceiled
1367     * to the nearest pixel, else returns the same value.
1368     * @param value the size value to be snapped
1369     * @return value ceiled to nearest pixel
1370     */
1371    protected double snapSize(double value) {
1372        return snapSize(value, isSnapToPixel());
1373    }
1374
1375    /**
1376     * If this region's snapToPixel property is true, returns a value rounded
1377     * to the nearest pixel, else returns the same value.
1378     * @param value the position value to be snapped
1379     * @return value rounded to nearest pixel
1380     */
1381    protected double snapPosition(double value) {
1382        return snapPosition(value, isSnapToPixel());
1383    }
1384
1385
1386    /**
1387     * Utility method to get the top inset which includes padding and border
1388     * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1389     *
1390     * @since 8.0
1391     * @return Rounded up insets top
1392     */
1393    public final double snappedTopInset() {
1394        return snappedTopInset;
1395    }
1396
1397    /**
1398     * Utility method to get the bottom inset which includes padding and border
1399     * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1400     *
1401     * @since 8.0
1402     * @return Rounded up insets bottom
1403     */
1404    public final double snappedBottomInset() {
1405        return snappedBottomInset;
1406    }
1407
1408    /**
1409     * Utility method to get the left inset which includes padding and border
1410     * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1411     *
1412     * @since 8.0
1413     * @return Rounded up insets left
1414     */
1415    public final double snappedLeftInset() {
1416        return snappedLeftInset;
1417    }
1418
1419    /**
1420     * Utility method to get the right inset which includes padding and border
1421     * inset. Then snapped to whole pixels if isSnapToPixel() is true.
1422     *
1423     * @since 8.0
1424     * @return Rounded up insets right
1425     */
1426    public final double snappedRightInset() {
1427        return snappedRightInset;
1428    }
1429
1430
1431    double computeChildMinAreaWidth(Node child, Insets margin) {
1432        return computeChildMinAreaWidth(child, margin, -1);
1433    }
1434
1435    double computeChildMinAreaWidth(Node child, Insets margin, double height) {
1436        final boolean snap = isSnapToPixel();
1437        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1438        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1439        double alt = -1;
1440        if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1441            alt = snapSize(height != -1? boundedSize(child.minHeight(-1), height, child.maxHeight(-1)) :
1442                                         child.maxHeight(-1));
1443        }
1444        return left + snapSize(child.minWidth(alt)) + right;
1445    }
1446
1447    double computeChildMinAreaHeight(Node child, Insets margin) {
1448        return computeChildMinAreaHeight(child, margin, -1);
1449    }
1450
1451    double computeChildMinAreaHeight(Node child, Insets margin, double width) {
1452        final boolean snap = isSnapToPixel();
1453        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1454        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1455        double alt = -1;
1456        if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1457            alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width, child.maxWidth(-1)) :
1458                                        child.maxWidth(-1));
1459        }
1460        return top + snapSize(child.minHeight(alt)) + bottom;
1461    }
1462
1463    double computeChildPrefAreaWidth(Node child, Insets margin) {
1464        return computeChildPrefAreaWidth(child, margin, -1);
1465    }
1466
1467    double computeChildPrefAreaWidth(Node child, Insets margin, double height) {
1468        final boolean snap = isSnapToPixel();
1469        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1470        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1471        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1472        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1473        double alt = -1;
1474        if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1475            alt = snapSize(boundedSize(
1476                    child.minHeight(-1), height != -1? height - top - bottom :
1477                           child.prefHeight(-1), child.maxHeight(-1)));
1478        }
1479        return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
1480    }
1481
1482    double computeChildPrefAreaHeight(Node child, Insets margin) {
1483        return computeChildPrefAreaHeight(child, margin, -1);
1484    }
1485
1486    double computeChildPrefAreaHeight(Node child, Insets margin, double width) {
1487        final boolean snap = isSnapToPixel();
1488        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1489        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1490        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1491        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1492        double alt = -1;
1493        if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1494            alt = snapSize(boundedSize(
1495                    child.minWidth(-1), width != -1? width - left - right :
1496                           child.prefWidth(-1), child.maxWidth(-1)));
1497        }
1498        return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
1499    }
1500
1501    double computeChildMaxAreaWidth(Node child, Insets margin, double height) {
1502        double max = child.maxWidth(-1);
1503        if (max == Double.MAX_VALUE) {
1504            return max;
1505        }
1506        final boolean snap = isSnapToPixel();
1507        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
1508        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
1509        double alt = -1;
1510        if (child.getContentBias() == Orientation.VERTICAL) { // width depends on height
1511            alt = snapSize(height != -1? boundedSize(child.minHeight(-1), height, child.maxHeight(-1)) :
1512                child.minHeight(-1));
1513            max = child.maxWidth(alt);
1514        }
1515        // if min > max, min wins, so still need to call boundedSize()
1516        return left + snapSize(boundedSize(child.minWidth(alt), max, child.maxWidth(alt))) + right;
1517    }
1518
1519    double computeChildMaxAreaHeight(Node child, Insets margin, double width) {
1520        double max = child.maxHeight(-1);
1521        if (max == Double.MAX_VALUE) {
1522            return max;
1523        }
1524
1525        final boolean snap = isSnapToPixel();
1526        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
1527        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
1528        double alt = -1;
1529        if (child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
1530            alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width, child.maxWidth(-1)) :
1531                child.minWidth(-1));
1532            max = child.maxHeight(alt);
1533        }
1534        // if min > max, min wins, so still need to call boundedSize()
1535        return top + snapSize(boundedSize(child.minHeight(alt), max, child.maxHeight(alt))) + bottom;
1536    }
1537
1538    /* Max of children's minimum area widths */
1539
1540    double computeMaxMinAreaWidth(List<Node> children, Insets margins[], HPos halignment /* ignored for now */) {
1541        return getMaxAreaWidth(children, margins, new double[] { -1 }, true);
1542    }
1543
1544    double computeMaxMinAreaWidth(List<Node> children, Insets margins[], HPos halignment /* ignored for now */, double height) {
1545        return getMaxAreaWidth(children, margins, new double[] { height }, true);
1546    }
1547
1548    double computeMaxMinAreaWidth(List<Node> children, Insets childMargins[], double childHeights[], HPos halignment /* ignored for now */) {
1549        return getMaxAreaWidth(children, childMargins, childHeights, true);
1550    }
1551
1552    /* Max of children's minimum area heights */
1553
1554    double computeMaxMinAreaHeight(List<Node>children, Insets margins[], VPos valignment) {
1555        return getMaxAreaHeight(children, margins, new double[] { -1 }, valignment, true);
1556    }
1557
1558    double computeMaxMinAreaHeight(List<Node>children, Insets margins[], VPos valignment, double width) {
1559        return getMaxAreaHeight(children, margins, new double[] { width }, valignment, true);
1560    }
1561
1562    double computeMaxMinAreaHeight(List<Node>children, Insets childMargins[], double childWidths[], VPos valignment) {
1563        return getMaxAreaHeight(children, childMargins, childWidths, valignment, true);
1564    }
1565
1566    /* Max of children's pref area widths */
1567
1568    double computeMaxPrefAreaWidth(List<Node>children, Insets margins[], HPos halignment /* ignored for now */) {
1569        return getMaxAreaWidth(children, margins, new double[] { -1 }, false);
1570    }
1571
1572    double computeMaxPrefAreaWidth(List<Node>children, Insets margins[], double height, HPos halignment /* ignored for now */) {
1573        return getMaxAreaWidth(children, margins, new double[] { height }, false);
1574    }
1575
1576    double computeMaxPrefAreaWidth(List<Node>children, Insets childMargins[], double childHeights[], HPos halignment /* ignored for now */) {
1577        return getMaxAreaWidth(children, childMargins, childHeights, false);
1578    }
1579
1580    /* Max of children's pref area heights */
1581
1582    double computeMaxPrefAreaHeight(List<Node>children, Insets margins[], VPos valignment) {
1583        return getMaxAreaHeight(children, margins, createDoubleArray(children.size(), -1), valignment, false);
1584    }
1585
1586    double computeMaxPrefAreaHeight(List<Node>children, Insets margins[], double width, VPos valignment) {
1587        return getMaxAreaHeight(children, margins, createDoubleArray(children.size(), width), valignment, false);
1588    }
1589
1590    double computeMaxPrefAreaHeight(List<Node>children, Insets childMargins[], double childWidths[], VPos valignment) {
1591        return getMaxAreaHeight(children, childMargins, childWidths, valignment, false);
1592    }
1593
1594    /**
1595     * Returns the size of a Node that should be placed in an area of the specified size,
1596     * bounded in it's min/max size, respecting bias.
1597     *
1598     * @param node the node
1599     * @param areaWidth the width of the bounding area where the node is going to be placed
1600     * @param areaHeight the height of the bounding area where the node is going to be placed
1601     * @param fillWidth if Node should try to fill the area width
1602     * @param fillHeight if Node should try to fill the area height
1603     * @param result Vec2d object for the result or null if new one should be created
1604     * @return Vec2d object with width(x parameter) and height (y parameter)
1605     */
1606    static Vec2d boundedNodeSizeWithBias(Node node, double areaWidth, double areaHeight,
1607            boolean fillWidth, boolean fillHeight, Vec2d result) {
1608        if (result == null) {
1609            result = new Vec2d();
1610        }
1611
1612        Orientation bias = node.getContentBias();
1613
1614        double childWidth = 0;
1615        double childHeight = 0;
1616
1617        if (bias == null) {
1618            childWidth = boundedSize(
1619                    node.minWidth(-1), fillWidth ? areaWidth
1620                    : Math.min(areaWidth, node.prefWidth(-1)),
1621                    node.maxWidth(-1));
1622            childHeight = boundedSize(
1623                    node.minHeight(-1), fillHeight ? areaHeight
1624                    : Math.min(areaHeight, node.prefHeight(-1)),
1625                    node.maxHeight(-1));
1626
1627        } else if (bias == Orientation.HORIZONTAL) {
1628            childWidth = boundedSize(
1629                    node.minWidth(-1), fillWidth ? areaWidth
1630                    : Math.min(areaWidth, node.prefWidth(-1)),
1631                    node.maxWidth(-1));
1632            childHeight = boundedSize(
1633                    node.minHeight(childWidth), fillHeight ? areaHeight
1634                    : Math.min(areaHeight, node.prefHeight(childWidth)),
1635                    node.maxHeight(childWidth));
1636
1637        } else { // bias == VERTICAL
1638            childHeight = boundedSize(
1639                    node.minHeight(-1), fillHeight ? areaHeight
1640                    : Math.min(areaHeight, node.prefHeight(-1)),
1641                    node.maxHeight(-1));
1642            childWidth = boundedSize(
1643                    node.minWidth(childHeight), fillWidth ? areaWidth
1644                    : Math.min(areaWidth, node.prefWidth(childHeight)),
1645                    node.maxWidth(childHeight));
1646        }
1647
1648        result.set(childWidth, childHeight);
1649        return result;
1650    }
1651
1652    /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */
1653    private double getMaxAreaHeight(List<Node> children, Insets childMargins[],  double childWidths[], VPos valignment, boolean minimum) {
1654        final double lastChildWidth = childWidths.length > 0 ? childWidths[childWidths.length - 1] : 0;
1655        if (valignment == VPos.BASELINE) {
1656            double maxAbove = 0;
1657            double maxBelow = 0;
1658            for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
1659                final Node child = children.get(i);
1660                final double baseline = child.getBaselineOffset();
1661                final double top = childMargins[i] != null? snapSpace(childMargins[i].getTop()) : 0;
1662                final double bottom = childMargins[i] != null? snapSpace(childMargins[i].getBottom()) : 0;
1663                final double childWidth = i < childWidths.length ? childWidths[i] : lastChildWidth;
1664                maxAbove = Math.max(maxAbove, baseline + top);
1665                maxBelow = Math.max(maxBelow,
1666                        snapSpace(minimum?snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth))) -
1667                        baseline + bottom);
1668            }
1669            return maxAbove + maxBelow; //remind(aim): ceil this value?
1670        } else {
1671            double max = 0;
1672            for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
1673                final Node child = children.get(i);
1674                final double childWidth = i < childWidths.length ? childWidths[i] : lastChildWidth;
1675                max = Math.max(max, minimum?
1676                    computeChildMinAreaHeight(child, childMargins[i], childWidth) :
1677                        computeChildPrefAreaHeight(child, childMargins[i], childWidth));
1678            }
1679            return max;
1680        }
1681    }
1682
1683    /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */
1684    private double getMaxAreaWidth(List<javafx.scene.Node> children, Insets childMargins[], double childHeights[], boolean minimum) {
1685        final double lastChildHeight = childHeights.length > 0 ? childHeights[childHeights.length - 1] : 0;
1686        double max = 0;
1687        for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
1688            final Node child = children.get(i);
1689            final double childHeight = i < childHeights.length ? childHeights[i] : lastChildHeight;
1690            max = Math.max(max, minimum?
1691                computeChildMinAreaWidth(children.get(i), childMargins[i], childHeight) :
1692                    computeChildPrefAreaWidth(child, childMargins[i], childHeight));
1693        }
1694        return max;
1695    }
1696
1697    /**
1698     * Utility method which positions the child within an area of this
1699     * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
1700     * with a baseline offset relative to that area.
1701     * <p>
1702     * This function does <i>not</i> resize the node and uses the node's layout bounds
1703     * width and height to determine how it should be positioned within the area.
1704     * <p>
1705     * If the vertical alignment is {@code VPos.BASELINE} then it
1706     * will position the node so that its own baseline aligns with the passed in
1707     * {@code baselineOffset},  otherwise the baseline parameter is ignored.
1708     * <p>
1709     * If {@code snapToPixel} is {@code true} for this region, then the x/y position
1710     * values will be rounded to their nearest pixel boundaries.
1711     *
1712     * @param child the child being positioned within this region
1713     * @param areaX the horizontal offset of the layout area relative to this region
1714     * @param areaY the vertical offset of the layout area relative to this region
1715     * @param areaWidth  the width of the layout area
1716     * @param areaHeight the height of the layout area
1717     * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
1718     * @param halignment the horizontal alignment for the child within the area
1719     * @param valignment the vertical alignment for the child within the area
1720     *
1721     */
1722    protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
1723                               double areaBaselineOffset, HPos halignment, VPos valignment) {
1724        positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
1725                Insets.EMPTY, halignment, valignment, isSnapToPixel());
1726    }
1727
1728    /**
1729     * Utility method which positions the child within an area of this
1730     * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
1731     * with a baseline offset relative to that area.
1732     * <p>
1733     * This function does <i>not</i> resize the node and uses the node's layout bounds
1734     * width and height to determine how it should be positioned within the area.
1735     * <p>
1736     * If the vertical alignment is {@code VPos.BASELINE} then it
1737     * will position the node so that its own baseline aligns with the passed in
1738     * {@code baselineOffset},  otherwise the baseline parameter is ignored.
1739     * <p>
1740     * If {@code snapToPixel} is {@code true} for this region, then the x/y position
1741     * values will be rounded to their nearest pixel boundaries.
1742     * <p>
1743     * If {@code margin} is non-null, then that space will be allocated around the
1744     * child within the layout area.  margin may be null.
1745     *
1746     * @param child the child being positioned within this region
1747     * @param areaX the horizontal offset of the layout area relative to this region
1748     * @param areaY the vertical offset of the layout area relative to this region
1749     * @param areaWidth  the width of the layout area
1750     * @param areaHeight the height of the layout area
1751     * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
1752     * @param margin the margin of space to be allocated around the child
1753     * @param halignment the horizontal alignment for the child within the area
1754     * @param valignment the vertical alignment for the child within the area
1755     *
1756     */
1757    public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
1758                               double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
1759        Insets childMargin = margin != null? margin : Insets.EMPTY;
1760
1761        position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
1762                snapSpace(childMargin.getTop(), isSnapToPixel),
1763                snapSpace(childMargin.getRight(), isSnapToPixel),
1764                snapSpace(childMargin.getBottom(), isSnapToPixel),
1765                snapSpace(childMargin.getLeft(), isSnapToPixel),
1766                halignment, valignment, isSnapToPixel);
1767    }
1768
1769    /**
1770     * Utility method which lays out the child within an area of this
1771     * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
1772     * with a baseline offset relative to that area.
1773     * <p>
1774     * If the child is resizable, this method will resize it to fill the specified
1775     * area unless the node's maximum size prevents it.  If the node's maximum
1776     * size preference is less than the area size, the maximum size will be used.
1777     * If node's maximum is greater than the area size, then the node will be
1778     * resized to fit within the area, unless its minimum size prevents it.
1779     * <p>
1780     * If the child has a non-null contentBias, then this method will use it when
1781     * resizing the child.  If the contentBias is horizontal, it will set its width
1782     * first to the area's width (up to the child's max width limit) and then pass
1783     * that value to compute the child's height.  If child's contentBias is vertical,
1784     * then it will set its height to the area height (up to child's max height limit)
1785     * and pass that height to compute the child's width.  If the child's contentBias
1786     * is null, then it's width and height have no dependencies on each other.
1787     * <p>
1788     * If the child is not resizable (Shape, Group, etc) then it will only be
1789     * positioned and not resized.
1790     * <p>
1791     * If the child's resulting size differs from the area's size (either
1792     * because it was not resizable or it's sizing preferences prevented it), then
1793     * this function will align the node relative to the area using horizontal and
1794     * vertical alignment values.
1795     * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
1796     * with the area baseline offset parameter, otherwise the baseline parameter
1797     * is ignored.
1798     * <p>
1799     * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
1800     * values will be rounded to their nearest pixel boundaries and the
1801     * width/height values will be ceiled to the next pixel boundary.
1802     *
1803     * @param child the child being positioned within this region
1804     * @param areaX the horizontal offset of the layout area relative to this region
1805     * @param areaY the vertical offset of the layout area relative to this region
1806     * @param areaWidth  the width of the layout area
1807     * @param areaHeight the height of the layout area
1808     * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
1809     * @param halignment the horizontal alignment for the child within the area
1810     * @param valignment the vertical alignment for the child within the area
1811     *
1812     */
1813    protected void layoutInArea(Node child, double areaX, double areaY,
1814                               double areaWidth, double areaHeight,
1815                               double areaBaselineOffset,
1816                               HPos halignment, VPos valignment) {
1817        layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
1818                Insets.EMPTY, halignment, valignment);
1819    }
1820
1821    /**
1822     * Utility method which lays out the child within an area of this
1823     * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
1824     * with a baseline offset relative to that area.
1825     * <p>
1826     * If the child is resizable, this method will resize it to fill the specified
1827     * area unless the node's maximum size prevents it.  If the node's maximum
1828     * size preference is less than the area size, the maximum size will be used.
1829     * If node's maximum is greater than the area size, then the node will be
1830     * resized to fit within the area, unless its minimum size prevents it.
1831     * <p>
1832     * If the child has a non-null contentBias, then this method will use it when
1833     * resizing the child.  If the contentBias is horizontal, it will set its width
1834     * first to the area's width (up to the child's max width limit) and then pass
1835     * that value to compute the child's height.  If child's contentBias is vertical,
1836     * then it will set its height to the area height (up to child's max height limit)
1837     * and pass that height to compute the child's width.  If the child's contentBias
1838     * is null, then it's width and height have no dependencies on each other.
1839     * <p>
1840     * If the child is not resizable (Shape, Group, etc) then it will only be
1841     * positioned and not resized.
1842     * <p>
1843     * If the child's resulting size differs from the area's size (either
1844     * because it was not resizable or it's sizing preferences prevented it), then
1845     * this function will align the node relative to the area using horizontal and
1846     * vertical alignment values.
1847     * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
1848     * with the area baseline offset parameter, otherwise the baseline parameter
1849     * is ignored.
1850     * <p>
1851     * If {@code margin} is non-null, then that space will be allocated around the
1852     * child within the layout area.  margin may be null.
1853     * <p>
1854     * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
1855     * values will be rounded to their nearest pixel boundaries and the
1856     * width/height values will be ceiled to the next pixel boundary.
1857     *
1858     * @param child the child being positioned within this region
1859     * @param areaX the horizontal offset of the layout area relative to this region
1860     * @param areaY the vertical offset of the layout area relative to this region
1861     * @param areaWidth  the width of the layout area
1862     * @param areaHeight the height of the layout area
1863     * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
1864     * @param margin the margin of space to be allocated around the child
1865     * @param halignment the horizontal alignment for the child within the area
1866     * @param valignment the vertical alignment for the child within the area
1867     */
1868    protected void layoutInArea(Node child, double areaX, double areaY,
1869                               double areaWidth, double areaHeight,
1870                               double areaBaselineOffset,
1871                               Insets margin,
1872                               HPos halignment, VPos valignment) {
1873        layoutInArea(child, areaX, areaY, areaWidth, areaHeight,
1874                areaBaselineOffset, margin, true, true, halignment, valignment);
1875    }
1876
1877    /**
1878     * Utility method which lays out the child within an area of this
1879     * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
1880     * with a baseline offset relative to that area.
1881     * <p>
1882     * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
1883     * to determine whether to resize it to fill the area or keep the child at its
1884     * preferred dimension.  If fillWidth/fillHeight are true, then this method
1885     * will only resize the child up to its max size limits.  If the node's maximum
1886     * size preference is less than the area size, the maximum size will be used.
1887     * If node's maximum is greater than the area size, then the node will be
1888     * resized to fit within the area, unless its minimum size prevents it.
1889     * <p>
1890     * If the child has a non-null contentBias, then this method will use it when
1891     * resizing the child.  If the contentBias is horizontal, it will set its width
1892     * first and then pass that value to compute the child's height.  If child's
1893     * contentBias is vertical, then it will set its height first
1894     * and pass that value to compute the child's width.  If the child's contentBias
1895     * is null, then it's width and height have no dependencies on each other.
1896     * <p>
1897     * If the child is not resizable (Shape, Group, etc) then it will only be
1898     * positioned and not resized.
1899     * <p>
1900     * If the child's resulting size differs from the area's size (either
1901     * because it was not resizable or it's sizing preferences prevented it), then
1902     * this function will align the node relative to the area using horizontal and
1903     * vertical alignment values.
1904     * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
1905     * with the area baseline offset parameter, otherwise the baseline parameter
1906     * is ignored.
1907     * <p>
1908     * If {@code margin} is non-null, then that space will be allocated around the
1909     * child within the layout area.  margin may be null.
1910     * <p>
1911     * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y
1912     * values will be rounded to their nearest pixel boundaries and the
1913     * width/height values will be ceiled to the next pixel boundary.
1914     *
1915     * @param child the child being positioned within this region
1916     * @param areaX the horizontal offset of the layout area relative to this region
1917     * @param areaY the vertical offset of the layout area relative to this region
1918     * @param areaWidth  the width of the layout area
1919     * @param areaHeight the height of the layout area
1920     * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
1921     * @param margin the margin of space to be allocated around the child
1922     * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
1923     * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
1924     * @param halignment the horizontal alignment for the child within the area
1925     * @param valignment the vertical alignment for the child within the area
1926     */
1927    protected void layoutInArea(Node child, double areaX, double areaY,
1928                               double areaWidth, double areaHeight,
1929                               double areaBaselineOffset,
1930                               Insets margin, boolean fillWidth, boolean fillHeight,
1931                               HPos halignment, VPos valignment) {
1932        layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, isSnapToPixel());
1933    }
1934
1935    public static void layoutInArea(Node child, double areaX, double areaY,
1936                               double areaWidth, double areaHeight,
1937                               double areaBaselineOffset,
1938                               Insets margin, boolean fillWidth, boolean fillHeight,
1939                               HPos halignment, VPos valignment, boolean isSnapToPixel) {
1940
1941        Insets childMargin = margin != null ? margin : Insets.EMPTY;
1942        double top = snapSpace(childMargin.getTop(), isSnapToPixel);
1943        double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel);
1944        double left = snapSpace(childMargin.getLeft(), isSnapToPixel);
1945        double right = snapSpace(childMargin.getRight(), isSnapToPixel);
1946
1947        if (child.isResizable()) {
1948            Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
1949                    fillWidth, fillHeight, TEMP_VEC2D);
1950            child.resize(snapSize(size.x, isSnapToPixel),snapSize(size.y, isSnapToPixel));
1951        }
1952        position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
1953                top, right, bottom, left, halignment, valignment, isSnapToPixel);
1954    }
1955
1956    private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
1957                          double areaBaselineOffset,
1958                          double topMargin, double rightMargin, double bottomMargin, double leftMargin,
1959                          HPos hpos, VPos vpos, boolean isSnapToPixel) {
1960        final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin,
1961                                                     child.getLayoutBounds().getWidth(), hpos);
1962        final double yoffset = topMargin +
1963                      (vpos == VPos.BASELINE?
1964                          areaBaselineOffset - child.getBaselineOffset() :
1965                          computeYOffset(areaHeight - topMargin - bottomMargin,
1966                                         child.getLayoutBounds().getHeight(), vpos));
1967        final double x = snapPosition(areaX + xoffset, isSnapToPixel);
1968        final double y = snapPosition(areaY + yoffset, isSnapToPixel);
1969
1970        child.relocate(x,y);
1971    }
1972
1973     /**************************************************************************
1974     *                                                                         *
1975     * PG Implementation                                                       *
1976     *                                                                         *
1977     **************************************************************************/
1978
1979    /** @treatAsPrivate */
1980    @Override public void impl_updatePG() {
1981        super.impl_updatePG();
1982        if (_shape != null) _shape.impl_syncPGNode();
1983        PGRegion pg = (PGRegion) impl_getPGNode();
1984
1985        final boolean sizeChanged = impl_isDirty(DirtyBits.NODE_GEOMETRY);
1986        if (sizeChanged) {
1987            pg.setSize((float)getWidth(), (float)getHeight());
1988        }
1989
1990        // NOTE: The order here is very important. There is logic in NGRegion which determines
1991        // whether we can cache an image representing this region, and for this to work correctly,
1992        // the shape must be specified before the background which is before the border.
1993        final boolean shapeChanged = impl_isDirty(DirtyBits.REGION_SHAPE);
1994        if (shapeChanged) {
1995            pg.updateShape(_shape, isScaleShape(), isCenterShape(), isCacheShape());
1996        }
1997
1998        final boolean backgroundChanged = impl_isDirty(DirtyBits.SHAPE_FILL);
1999        final Background bg = getBackground();
2000        if (backgroundChanged) {
2001            pg.updateBackground(bg);
2002        }
2003
2004        if (impl_isDirty(DirtyBits.SHAPE_STROKE)) {
2005            pg.updateBorder(getBorder());
2006        }
2007
2008        if (sizeChanged || backgroundChanged || shapeChanged) {
2009            // TODO Make sure there is a test ensuring that stroke borders do not contribute to insets or opaque insets
2010            // If the background is determined by a shape, then we don't care (for now) what the opaque insets
2011            // of the Background are. If the developer specified opaque insets, we will use them, otherwise
2012            // we will make sure the opaque insets are cleared
2013            final Insets i = getOpaqueInsets();
2014            if (_shape != null) {
2015                if (i != null) {
2016                    pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(),
2017                                       (float) i.getBottom(), (float) i.getLeft());
2018                } else {
2019                    pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2020                }
2021            } else if (bg == null || bg.isEmpty() || !bg.hasOpaqueFill) {
2022                // If the background is null or empty or has no opaque fills, then we will forbear
2023                // computing the opaque insets, and just send null down.
2024                pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
2025            } else if (i == null) {
2026                // There are no opaqueInsets specified by the developer (the most common case), so
2027                // I will have to compute them.
2028                // TODO If I could determine whether an Image were opaque, I could also compute
2029                // the opaqueInsets for a BackgroundImage and BorderImage.
2030                final double[] trbl = new double[4];
2031                bg.computeOpaqueInsets(getWidth(), getHeight(), trbl);
2032                pg.setOpaqueInsets((float) trbl[0], (float) trbl[1], (float) trbl[2], (float) trbl[3]);
2033            } else {
2034                // TODO For now, I'm just going to honor the opaqueInsets. Really I would want
2035                // to check to see if the computed opaqueTop, right, etc on the Background is
2036                // broader than the supplied opaque insets and adjust accordingly, but for now
2037                // I won't bother.
2038                pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(),
2039                                   (float) i.getBottom(), (float) i.getLeft());
2040            }
2041        }
2042    }
2043
2044    /** @treatAsPrivate */
2045    @Override public PGNode impl_createPGNode() {
2046        return Toolkit.getToolkit().createPGRegion();
2047    }
2048
2049    /**
2050     * @treatAsPrivate implementation detail
2051     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2052     */
2053    @Deprecated
2054    @Override protected boolean impl_computeContains(double localX, double localY) {
2055        // NOTE: This method only gets called if a quick check of bounds has already
2056        // occurred, so there is no need to test against bound again. We know that the
2057        // point (localX, localY) falls within the bounds of this node, now we need
2058        // to determine if it falls within the geometry of this node.
2059        // Also note that because Region defaults pickOnBounds to true, this code is
2060        // not usually executed. It will only be executed if pickOnBounds is set to false.
2061
2062        final double x2 = getWidth();
2063        final double y2 = getHeight();
2064        // Figure out what the maximum possible radius value is.
2065        final double maxRadius = Math.min(x2 / 2.0, y2 / 2.0);
2066
2067        // First check the shape. Shape could be impacted by scaleShape & positionShape properties.
2068        // This is going to be ugly! The problem is that basically all the scale / position operations
2069        // have to be implemented here in Region, whereas right now they are all implemented in
2070        // NGRegion. Drat. Basically I can't implement this properly until I have a way to get the
2071        // geometry backing an arbitrary FX shape. For example, in this case I need an NGShape peer
2072        // of this shape so that I can resize it as appropriate for these picking tests.
2073        // Lacking that, for now, I will simply check the shape (so that picking works for pie charts)
2074        // Bug is filed as RT-27775.
2075        if (_shape != null) {
2076            return _shape.contains(localX, localY);
2077        }
2078
2079        // OK, there was no background shape, so I'm going to work on the principle of
2080        // nested rounded rectangles. We'll start by checking the backgrounds. The
2081        // first background which passes the test is good enough for us!
2082        final Background background = getBackground();
2083        if (background != null) {
2084            final List<BackgroundFill> fills = background.getFills();
2085            for (int i = 0, max = fills.size(); i < max; i++) {
2086                final BackgroundFill bgFill = fills.get(i);
2087                if (contains(localX, localY, 0, 0, x2, y2, bgFill.getInsets(), bgFill.getRadii(), maxRadius)) {
2088                    return true;
2089                }
2090            }
2091        }
2092
2093        // If we are here then either there were no background fills or there were no background
2094        // fills which contained the point, and the region is not defined by a shape.
2095        final Border border = getBorder();
2096        if (border != null) {
2097            // Check all the stroke borders first. If the pick occurs on any stroke border
2098            // then we consider the contains test to have passed. Semantically we will treat a Region
2099            // with a border as if it were a rectangle with a stroke but no fill.
2100            final List<BorderStroke> strokes = border.getStrokes();
2101            for (int i=0, max=strokes.size(); i<max; i++) {
2102                final BorderStroke strokeBorder = strokes.get(i);
2103                if (contains(localX, localY, 0, 0, x2, y2, strokeBorder.getWidths(), false, strokeBorder.getInsets(),
2104                             strokeBorder.getRadii(), maxRadius)) {
2105                    return true;
2106                }
2107            }
2108
2109            // Check the image borders. We treat the image border as though it is opaque.
2110            final List<BorderImage> images = border.getImages();
2111            for (int i = 0, max = images.size(); i < max; i++) {
2112                final BorderImage borderImage = images.get(i);
2113                if (contains(localX, localY, 0, 0, x2, y2, borderImage.getWidths(), borderImage.isFilled(),
2114                             borderImage.getInsets(), CornerRadii.EMPTY, maxRadius)) {
2115                    return true;
2116                }
2117            }
2118        }
2119        return false;
2120    }
2121
2122    /**
2123     * Basically we will perform two contains tests. For a point to be on the stroke, it must
2124     * be within the outermost edge of the stroke, but outside the innermost edge of the stroke.
2125     * Unless it is filled, in which case it is really just a normal contains test.
2126     *
2127     * @param px        The x position of the point to test
2128     * @param py        The y position of the point to test
2129     * @param x1        The x1 position of the bounds to test
2130     * @param y1        The y1 position of the bounds to test
2131     * @param x2        The x2 position of the bounds to test
2132     * @param y2        The y2 position of the bounds to test
2133     * @param widths    The widths of the stroke on each side
2134     * @param filled    Whether the area is filled or is just stroked
2135     * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2136     * @param rad       The corner radii to test with. Must not be null.
2137     * @param maxRadius The maximum possible radius value
2138     * @return True if (px, py) is within the stroke, taking into account insets and corner radii.
2139     */
2140    private boolean contains(final double px, final double py,
2141                             final double x1, final double y1, final double x2, final double y2,
2142                             BorderWidths widths, boolean filled,
2143                             final Insets insets, final CornerRadii rad, final double maxRadius) {
2144        if (filled) {
2145            if (contains(px, py, x1, y1, x2, y2, insets, rad, maxRadius)) {
2146                return true;
2147            }
2148        } else {
2149            boolean insideOuterEdge = contains(px, py, x1, y1, x2, y2, insets, rad, maxRadius);
2150            if (insideOuterEdge) {
2151                boolean outsideInnerEdge = !contains(px, py,
2152                    x1 + (widths.isLeftAsPercentage() ? getWidth() * widths.getLeft() : widths.getLeft()),
2153                    y1 + (widths.isTopAsPercentage() ? getHeight() * widths.getTop() : widths.getTop()),
2154                    x2 - (widths.isRightAsPercentage() ? getWidth() * widths.getRight() : widths.getRight()),
2155                    y2 - (widths.isBottomAsPercentage() ? getHeight() * widths.getBottom() : widths.getBottom()),
2156                    insets, rad, maxRadius);
2157                if (outsideInnerEdge) return true;
2158            }
2159        }
2160        return false;
2161    }
2162
2163    /**
2164     * Determines whether the point (px, py) is contained within the the bounds (x1, y1)-(x2, y2),
2165     * after taking into account the insets and the corner radii.
2166     *
2167     * @param px        The x position of the point to test
2168     * @param py        The y position of the point to test
2169     * @param x1        The x1 position of the bounds to test
2170     * @param y1        The y1 position of the bounds to test
2171     * @param x2        The x2 position of the bounds to test
2172     * @param y2        The y2 position of the bounds to test
2173     * @param insets    The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test
2174     * @param rad       The corner radii to test with. Must not be null.
2175     * @param maxRadius The maximum possible radius value
2176     * @return True if (px, py) is within the bounds, taking into account insets and corner radii.
2177     */
2178    private boolean contains(final double px, final double py,
2179                             final double x1, final double y1, final double x2, final double y2,
2180                             final Insets insets, CornerRadii rad, final double maxRadius) {
2181        // These four values are the x0, y0, x1, y1 bounding box after
2182        // having taken into account the insets of this particular
2183        // background fill.
2184        final double rrx0 = x1 + insets.getLeft();
2185        final double rry0 = y1 + insets.getTop();
2186        final double rrx1 = x2 - insets.getRight();
2187        final double rry1 = y2 - insets.getBottom();
2188
2189        // Adjust based on whether it is % based radii
2190        rad = normalize(rad);
2191
2192        // Check for trivial rejection - point is inside bounding rectangle
2193        if (px >= rrx0 && py >= rry0 && px <= rrx1 && py <= rry1) {
2194            // The point was within the index bounding box. Now we need to analyze the
2195            // corner radii to see if the point lies within the corners or not. If the
2196            // point is within a corner then we reject this one.
2197            final double tlhr = Math.min(rad.getTopLeftHorizontalRadius(), maxRadius);
2198            if (rad.isUniform() && tlhr == 0) {
2199                // This is a simple square! Since we know the point is already within
2200                // the insets of this fill, we can simply return true.
2201                return true;
2202            } else {
2203                final double tlvr = Math.min(rad.getTopLeftVerticalRadius(), maxRadius);
2204                final double trhr = Math.min(rad.getTopRightHorizontalRadius(), maxRadius);
2205                final double trvr = Math.min(rad.getTopRightVerticalRadius(), maxRadius);
2206                final double blhr = Math.min(rad.getBottomLeftHorizontalRadius(), maxRadius);
2207                final double blvr = Math.min(rad.getBottomLeftVerticalRadius(), maxRadius);
2208                final double brhr = Math.min(rad.getBottomRightHorizontalRadius(), maxRadius);
2209                final double brvr = Math.min(rad.getBottomRightVerticalRadius(), maxRadius);
2210
2211                // The four corners can each be described as a quarter of an ellipse
2212                double centerX, centerY, a, b;
2213
2214                if (px <= rrx0 + tlhr && py <= rry0 + tlvr) {
2215                    // Point is in the top left corner
2216                    centerX = rrx0 + tlhr;
2217                    centerY = rry0 + tlvr;
2218                    a = tlhr;
2219                    b = tlvr;
2220                } else if (px >= rrx1 - trhr && py <= rry0 + trvr) {
2221                    // Point is in the top right corner
2222                    centerX = rrx1 - trhr;
2223                    centerY = rry0 + trvr;
2224                    a = trhr;
2225                    b = trvr;
2226                } else if (px >= rrx1 - brhr && py >= rry1 - brvr) {
2227                    // Point is in the bottom right corner
2228                    centerX = rrx1 - brhr;
2229                    centerY = rry1 - brvr;
2230                    a = brhr;
2231                    b = brvr;
2232                } else if (px <= rrx0 + blhr && py >= rry1 - blvr) {
2233                    // Point is in the bottom left corner
2234                    centerX = rrx0 + blhr;
2235                    centerY = rry1 - blvr;
2236                    a = blhr;
2237                    b = blvr;
2238                } else {
2239                    // The point must have been in the solid body someplace
2240                    return true;
2241                }
2242
2243                double x = px - centerX;
2244                double y = py - centerY;
2245                double result = ((x*x)/(a*a) + (y*y)/(b*b));
2246                // The .0000001 is fudge to help in cases where double arithmetic isn't quite right
2247                if (result - .0000001 <= 1) return true;
2248            }
2249        }
2250        return false;
2251    }
2252
2253    /**
2254     * Direct copy of a method in NGRegion. If NGRegion were part of the core graphics module (coming!)
2255     * then this method could be removed.
2256     *
2257     * @param radii    The radii.
2258     * @return Normalized radii.
2259     */
2260    private CornerRadii normalize(CornerRadii radii) {
2261        final double width = getWidth();
2262        final double height = getHeight();
2263        final double tlvr = radii.isTopLeftVerticalRadiusAsPercentage() ? height * radii.getTopLeftVerticalRadius() : radii.getTopLeftVerticalRadius();
2264        final double tlhr = radii.isTopLeftHorizontalRadiusAsPercentage() ? width * radii.getTopLeftHorizontalRadius() : radii.getTopLeftHorizontalRadius();
2265        final double trvr = radii.isTopRightVerticalRadiusAsPercentage() ? height * radii.getTopRightVerticalRadius() : radii.getTopRightVerticalRadius();
2266        final double trhr = radii.isTopRightHorizontalRadiusAsPercentage() ? width * radii.getTopRightHorizontalRadius() : radii.getTopRightHorizontalRadius();
2267        final double brvr = radii.isBottomRightVerticalRadiusAsPercentage() ? height * radii.getBottomRightVerticalRadius() : radii.getBottomRightVerticalRadius();
2268        final double brhr = radii.isBottomRightHorizontalRadiusAsPercentage() ? width * radii.getBottomRightHorizontalRadius() : radii.getBottomRightHorizontalRadius();
2269        final double blvr = radii.isBottomLeftVerticalRadiusAsPercentage() ? height * radii.getBottomLeftVerticalRadius() : radii.getBottomLeftVerticalRadius();
2270        final double blhr = radii.isBottomLeftHorizontalRadiusAsPercentage() ? width * radii.getBottomLeftHorizontalRadius() : radii.getBottomLeftHorizontalRadius();
2271        return new CornerRadii(tlhr, tlvr, trvr, trhr, brhr, brvr, blvr, blhr, false, false, false, false, false, false, false, false);
2272    }
2273
2274    /**
2275     * Some skins relying on this
2276     * @treatAsPrivate implementation detail
2277     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2278     */
2279    @Deprecated
2280    @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
2281
2282        double boundsDistance = impl_intersectsBounds(pickRay);
2283
2284        if (!Double.isNaN(boundsDistance)) {
2285            ObservableList<Node> children = getChildren();
2286            for (int i = children.size()-1; i >= 0; i--) {
2287                children.get(i).impl_pickNode(pickRay, result);
2288                if (result.isClosed()) {
2289                    return;
2290                }
2291            }
2292
2293            impl_intersects(pickRay, result);
2294        }
2295    }
2296
2297    private Bounds boundingBox;
2298
2299    /**
2300     * The layout bounds of this region: {@code 0, 0  width x height}
2301     *
2302     * @treatAsPrivate implementation detail
2303     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2304     */
2305    @Deprecated
2306    @Override protected final Bounds impl_computeLayoutBounds() {
2307        if (boundingBox == null) {
2308            // we reuse the bounding box if the width and height haven't changed.
2309            boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0);
2310        }
2311        return boundingBox;
2312    }
2313
2314    /**
2315     * @treatAsPrivate implementation detail
2316     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2317     */
2318    @Deprecated
2319    @Override final protected void impl_notifyLayoutBoundsChanged() {
2320        // override Node's default behavior of having a geometric bounds change
2321        // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
2322        // is unrelated to geometric bounds.
2323    }
2324
2325    /**
2326     * @treatAsPrivate implementation detail
2327     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2328     */
2329    @Deprecated
2330    @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
2331        // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children.
2332        // The bounds of the Region must take into account any backgrounds and borders and how
2333        // they are used to draw the Region. The geom bounds must always take into account
2334        // all pixels drawn (because the geom bounds forms the basis of the dirty regions).
2335        // Note that the layout bounds of a Region is not based on the geom bounds.
2336
2337        // Define some variables to hold the top-left and bottom-right corners of the bounds
2338        double bx1 = 0;
2339        double by1 = 0;
2340        double bx2 = getWidth();
2341        double by2 = getHeight();
2342
2343        // If the shape is defined, then the top-left and bottom-right corner positions
2344        // need to be redefined
2345        if (_shape != null && isScaleShape() == false) {
2346            final Bounds layoutBounds = _shape.getLayoutBounds();
2347            final double shapeWidth = layoutBounds.getWidth();
2348            final double shapeHeight = layoutBounds.getHeight();
2349            if (isCenterShape()) {
2350                bx1 = (bx2 - shapeWidth) / 2;
2351                by1 = (by2 - shapeHeight) / 2;
2352                bx2 = bx1 + shapeWidth;
2353                by2 = by1 + shapeHeight;
2354            } else {
2355                bx1 = layoutBounds.getMinX();
2356                by1 = layoutBounds.getMinY();
2357                bx2 = layoutBounds.getMaxX();
2358                by2 = layoutBounds.getMaxY();
2359            }
2360        }
2361
2362        // Expand the bounds to include the outsets from the background and border.
2363        // The outsets are the opposite of insets -- a measure of distance from the
2364        // edge of the Region outward. The outsets cannot, however, be negative.
2365        final Background background = getBackground();
2366        final Border border = getBorder();
2367        final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets();
2368        final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets();
2369        bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft());
2370        by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop());
2371        bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight());
2372        by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom());
2373
2374        // NOTE: Okay to call impl_computeGeomBounds with tx even in the 3D case
2375        // since Parent.impl_computeGeomBounds does handle 3D correctly.
2376        BaseBounds cb = super.impl_computeGeomBounds(bounds, tx);
2377        /*
2378         * This is a work around for RT-7680. Parent returns invalid bounds from
2379         * impl_computeGeomBounds when it has no children or if all its children
2380         * have invalid bounds. If RT-7680 were fixed, then we could omit this
2381         * first branch of the if and only use the else since the correct value
2382         * would be computed.
2383         */
2384        if (cb.isEmpty()) {
2385            // There are no children bounds, so
2386            bounds = bounds.deriveWithNewBounds(
2387                    (float)bx1, (float)by1, 0.0f,
2388                    (float)bx2, (float)by2, 0.0f);
2389            bounds = tx.transform(bounds, bounds);
2390            return bounds;
2391        } else {
2392            // Union with children's bounds
2393            BaseBounds tempBounds = TempState.getInstance().bounds;
2394            tempBounds = tempBounds.deriveWithNewBounds(
2395                    (float)bx1, (float)by1, 0.0f,
2396                    (float)bx2, (float)by2, 0.0f);
2397            BaseBounds bb = tx.transform(tempBounds, tempBounds);
2398            cb = cb.deriveWithUnion(bb);
2399            return cb;
2400        }
2401    }
2402
2403    /***************************************************************************
2404     *                                                                         *
2405     * CSS                                                                     *
2406     *                                                                         *
2407     **************************************************************************/
2408
2409     /**
2410      * Super-lazy instantiation pattern from Bill Pugh.
2411      * @treatAsPrivate implementation detail
2412      */
2413     private static class StyleableProperties {
2414         private static final CssMetaData<Region,Insets> PADDING =
2415             new CssMetaData<Region,Insets>("-fx-padding",
2416                 InsetsConverter.getInstance(), Insets.EMPTY) {
2417
2418            @Override public boolean isSettable(Region node) {
2419                return node.padding == null || !node.padding.isBound();
2420            }
2421
2422            @Override public StyleableProperty<Insets> getStyleableProperty(Region node) {
2423                return (StyleableProperty<Insets>)node.paddingProperty();
2424            }
2425         };
2426
2427         private static final CssMetaData<Region,Insets> OPAQUE_INSETS =
2428                 new CssMetaData<Region,Insets>("-fx-opaque-insets",
2429                         InsetsConverter.getInstance(), null) {
2430
2431                     @Override
2432                     public boolean isSettable(Region node) {
2433                         return node.opaqueInsets == null || !node.opaqueInsets.isBound();
2434                     }
2435
2436                     @Override
2437                     public StyleableProperty<Insets> getStyleableProperty(Region node) {
2438                         return (StyleableProperty<Insets>)node.opaqueInsetsProperty();
2439                     }
2440
2441                 };
2442
2443         private static final CssMetaData<Region,Background> BACKGROUND =
2444             new CssMetaData<Region,Background>("-fx-region-background",
2445                 BackgroundConverter.INSTANCE,
2446                 null,
2447                 false,
2448                 Background.getClassCssMetaData()) {
2449
2450            @Override public boolean isSettable(Region node) {
2451                return !node.background.isBound();
2452            }
2453
2454            @Override public StyleableProperty<Background> getStyleableProperty(Region node) {
2455                return (StyleableProperty<Background>)node.background;
2456            }
2457         };
2458
2459         private static final CssMetaData<Region,Border> BORDER =
2460             new CssMetaData<Region,Border>("-fx-region-border",
2461                     BorderConverter.getInstance(),
2462                     null,
2463                     false,
2464                     Border.getClassCssMetaData()) {
2465
2466                 @Override public boolean isSettable(Region node) {
2467                     return !node.background.isBound();
2468                 }
2469
2470                 @Override public StyleableProperty<Border> getStyleableProperty(Region node) {
2471                     return (StyleableProperty<Border>)node.border;
2472                 }
2473             };
2474
2475         private static final CssMetaData<Region,Shape> SHAPE =
2476             new CssMetaData<Region,Shape>("-fx-shape",
2477                 ShapeConverter.getInstance()) {
2478
2479            @Override public boolean isSettable(Region node) {
2480                // isSettable depends on node.shape, not node.shapeContent
2481                return node.shape == null || !node.shape.isBound();
2482            }
2483
2484            @Override public StyleableProperty<Shape> getStyleableProperty(Region node) {
2485                return (StyleableProperty<Shape>)node.shapeProperty();
2486            }
2487         };
2488
2489         private static final CssMetaData<Region, Boolean> SCALE_SHAPE =
2490             new CssMetaData<Region,Boolean>("-fx-scale-shape",
2491                 BooleanConverter.getInstance(), Boolean.TRUE){
2492
2493            @Override public boolean isSettable(Region node) {
2494                return node.scaleShape == null || !node.scaleShape.isBound();
2495            }
2496
2497            @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
2498                return (StyleableProperty<Boolean>)node.scaleShapeProperty();
2499            }
2500        };
2501
2502         private static final CssMetaData<Region,Boolean> POSITION_SHAPE =
2503             new CssMetaData<Region,Boolean>("-fx-position-shape",
2504                 BooleanConverter.getInstance(), Boolean.TRUE){
2505
2506            @Override public boolean isSettable(Region node) {
2507                return node.centerShape == null || !node.centerShape.isBound();
2508            }
2509
2510            @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
2511                return (StyleableProperty<Boolean>)node.centerShapeProperty();
2512            }
2513        };
2514
2515         private static final CssMetaData<Region,Boolean> CACHE_SHAPE =
2516             new CssMetaData<Region,Boolean>("-fx-cache-shape",
2517                 BooleanConverter.getInstance(), Boolean.TRUE){
2518
2519            @Override public boolean isSettable(Region node) {
2520                return node.cacheShape == null || !node.cacheShape.isBound();
2521            }
2522
2523            @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
2524                return (StyleableProperty<Boolean>)node.cacheShapeProperty();
2525            }
2526        };
2527
2528         private static final CssMetaData<Region, Boolean> SNAP_TO_PIXEL =
2529             new CssMetaData<Region,Boolean>("-fx-snap-to-pixel",
2530                 BooleanConverter.getInstance(), Boolean.TRUE){
2531
2532            @Override public boolean isSettable(Region node) {
2533                return node.snapToPixel == null ||
2534                        !node.snapToPixel.isBound();
2535            }
2536
2537            @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) {
2538                return (StyleableProperty<Boolean>)node.snapToPixelProperty();
2539            }
2540        };
2541
2542         private static final CssMetaData<Region, Number> MIN_HEIGHT =
2543             new CssMetaData<Region,Number>("-fx-min-height",
2544                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2545
2546            @Override public boolean isSettable(Region node) {
2547                return node.minHeight == null ||
2548                        !node.minHeight.isBound();
2549            }
2550
2551            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2552                return (StyleableProperty<Number>)node.minHeightProperty();
2553            }
2554        };
2555
2556         private static final CssMetaData<Region, Number> PREF_HEIGHT =
2557             new CssMetaData<Region,Number>("-fx-pref-height",
2558                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2559
2560            @Override public boolean isSettable(Region node) {
2561                return node.prefHeight == null ||
2562                        !node.prefHeight.isBound();
2563            }
2564
2565            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2566                return (StyleableProperty<Number>)node.prefHeightProperty();
2567            }
2568        };
2569
2570         private static final CssMetaData<Region, Number> MAX_HEIGHT =
2571             new CssMetaData<Region,Number>("-fx-max-height",
2572                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2573
2574            @Override public boolean isSettable(Region node) {
2575                return node.maxHeight == null ||
2576                        !node.maxHeight.isBound();
2577            }
2578
2579            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2580                return (StyleableProperty<Number>)node.maxHeightProperty();
2581            }
2582        };
2583
2584         private static final CssMetaData<Region, Number> MIN_WIDTH =
2585             new CssMetaData<Region,Number>("-fx-min-width",
2586                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2587
2588            @Override public boolean isSettable(Region node) {
2589                return node.minWidth == null ||
2590                        !node.minWidth.isBound();
2591            }
2592
2593            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2594                return (StyleableProperty<Number>)node.minWidthProperty();
2595            }
2596        };
2597
2598         private static final CssMetaData<Region, Number> PREF_WIDTH =
2599             new CssMetaData<Region,Number>("-fx-pref-width",
2600                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2601
2602            @Override public boolean isSettable(Region node) {
2603                return node.prefWidth == null ||
2604                        !node.prefWidth.isBound();
2605            }
2606
2607            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2608                return (StyleableProperty<Number>)node.prefWidthProperty();
2609            }
2610        };
2611
2612         private static final CssMetaData<Region, Number> MAX_WIDTH =
2613             new CssMetaData<Region,Number>("-fx-max-width",
2614                 SizeConverter.getInstance(), USE_COMPUTED_SIZE){
2615
2616            @Override public boolean isSettable(Region node) {
2617                return node.maxWidth == null ||
2618                        !node.maxWidth.isBound();
2619            }
2620
2621            @Override public StyleableProperty<Number> getStyleableProperty(Region node) {
2622                return (StyleableProperty<Number>)node.maxWidthProperty();
2623            }
2624        };
2625
2626         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
2627         static {
2628
2629            final List<CssMetaData<? extends Styleable, ?>> styleables =
2630                new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData());
2631            styleables.add(PADDING);
2632            styleables.add(BACKGROUND);
2633            styleables.add(BORDER);
2634            styleables.add(OPAQUE_INSETS);
2635            styleables.add(SHAPE);
2636            styleables.add(SCALE_SHAPE);
2637            styleables.add(POSITION_SHAPE);
2638            styleables.add(SNAP_TO_PIXEL);
2639            styleables.add(MIN_WIDTH);
2640            styleables.add(PREF_WIDTH);
2641            styleables.add(MAX_WIDTH);
2642            styleables.add(MIN_HEIGHT);
2643            styleables.add(PREF_HEIGHT);
2644            styleables.add(MAX_HEIGHT);
2645            STYLEABLES = Collections.unmodifiableList(styleables);
2646         }
2647    }
2648
2649    /**
2650     * @return The CssMetaData associated with this class, which may include the
2651     * CssMetaData of its super classes.
2652     */
2653    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
2654        return StyleableProperties.STYLEABLES;
2655    }
2656
2657    /**
2658     * {@inheritDoc}
2659     *
2660     */
2661
2662
2663    @Override
2664    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
2665        return getClassCssMetaData();
2666    }
2667
2668}