< prev index next >

modules/graphics/src/main/java/javafx/scene/layout/Region.java

Print this page




 141  * region subclass.
 142  * <p/>
 143  * Region subclasses which layout their children will position nodes by setting
 144  * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter
 145  * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for
 146  * adjustments and animation.
 147  * @since JavaFX 2.0
 148  */
 149 public class Region extends Parent {
 150     static {
 151         RegionHelper.setRegionAccessor(new RegionHelper.RegionAccessor() {
 152             @Override
 153             public NGNode doCreatePeer(Node node) {
 154                 return ((Region) node).doCreatePeer();
 155             }
 156 
 157             @Override
 158             public void doUpdatePeer(Node node) {
 159                 ((Region) node).doUpdatePeer();
 160             }
































 161         });
 162     }
 163 
 164     /**
 165      * Sentinel value which can be passed to a region's
 166      * {@link #setMinWidth(double) setMinWidth},
 167      * {@link #setMinHeight(double) setMinHeight},
 168      * {@link #setMaxWidth(double) setMaxWidth} or
 169      * {@link #setMaxHeight(double) setMaxHeight}
 170      * methods to indicate that the preferred dimension should be used for that max and/or min constraint.
 171      */
 172     public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY;
 173 
 174     /**
 175      * Sentinel value which can be passed to a region's
 176      * {@link #setMinWidth(double) setMinWidth},
 177      * {@link #setMinHeight(double) setMinHeight},
 178      * {@link #setPrefWidth(double) setPrefWidth},
 179      * {@link #setPrefHeight(double) setPrefHeight},
 180      * {@link #setMaxWidth(double) setMaxWidth},


 666      * The background of the Region, which is made up of zero or more BackgroundFills, and
 667      * zero or more BackgroundImages. It is possible for a Background to be empty, where it
 668      * has neither fills nor images, and is semantically equivalent to null.
 669      * @since JavaFX 8.0
 670      */
 671     private final ObjectProperty<Background> background = new StyleableObjectProperty<Background>(null) {
 672         private Background old = null;
 673         @Override public Object getBean() { return Region.this; }
 674         @Override public String getName() { return "background"; }
 675         @Override public CssMetaData<Region, Background> getCssMetaData() {
 676             return StyleableProperties.BACKGROUND;
 677         }
 678 
 679         @Override protected void invalidated() {
 680             final Background b = get();
 681             if(old != null ? !old.equals(b) : b != null) {
 682                 // They are different! Both cannot be null
 683                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 684                     // We have determined that the outsets of these two different background
 685                     // objects is different, and therefore the bounds have changed.
 686                     impl_geomChanged();
 687                     insets.fireValueChanged();
 688                 }
 689 
 690                 // If the Background is made up of any BackgroundImage objects, then we must
 691                 // inspect the images of those BackgroundImage objects to see if they are still
 692                 // being loaded in the background or if they are animated. If so, then we need
 693                 // to attach a listener, so that when the image finishes loading or changes,
 694                 // we can repaint the region.
 695                 if (b != null) {
 696                     for (BackgroundImage i : b.getImages()) {
 697                         final Image image = i.image;
 698                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 699                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 700                             addImageListener(image);
 701                         }
 702                     }
 703                 }
 704 
 705                 // And we must remove this listener from any old images
 706                 if (old != null) {


 723     /**
 724      * The border of the Region, which is made up of zero or more BorderStrokes, and
 725      * zero or more BorderImages. It is possible for a Border to be empty, where it
 726      * has neither strokes nor images, and is semantically equivalent to null.
 727      * @since JavaFX 8.0
 728      */
 729     private final ObjectProperty<Border> border = new StyleableObjectProperty<Border>(null) {
 730         private Border old = null;
 731         @Override public Object getBean() { return Region.this; }
 732         @Override public String getName() { return "border"; }
 733         @Override public CssMetaData<Region, Border> getCssMetaData() {
 734             return StyleableProperties.BORDER;
 735         }
 736         @Override protected void invalidated() {
 737             final Border b = get();
 738             if(old != null ? !old.equals(b) : b != null) {
 739                 // They are different! Both cannot be null
 740                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 741                     // We have determined that the outsets of these two different border
 742                     // objects is different, and therefore the bounds have changed.
 743                     impl_geomChanged();
 744                 }
 745                 if (old == null || b == null || !old.getInsets().equals(b.getInsets())) {
 746                     insets.fireValueChanged();
 747                 }
 748 
 749                 // If the Border is made up of any BorderImage objects, then we must
 750                 // inspect the images of those BorderImage objects to see if they are still
 751                 // being loaded in the background or if they are animated. If so, then we need
 752                 // to attach a listener, so that when the image finishes loading or changes,
 753                 // we can repaint the region.
 754                 if (b != null) {
 755                     for (BorderImage i : b.getImages()) {
 756                         final Image image = i.image;
 757                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 758                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 759                             addImageListener(image);
 760                         }
 761                     }
 762                 }
 763 


 954     // access to a writable property for "width", but since there is now a protected
 955     // set method, it is impossible for Region to ever bind this property.
 956     protected void setWidth(double value) {
 957         if(width == null) {
 958             widthChanged(value);
 959         } else {
 960             width.set(value);
 961         }
 962     }
 963 
 964     private void widthChanged(double value) {
 965         // It is possible that somebody sets the width of the region to a value which
 966         // it previously held. If this is the case, we want to avoid excessive layouts.
 967         // Note that I have biased this for layout over binding, because the widthProperty
 968         // is now going to recompute the width eagerly. The cost of excessive and
 969         // unnecessary bounds changes, however, is relatively high.
 970         if (value != _width) {
 971             _width = value;
 972             cornersValid = false;
 973             boundingBox = null;
 974             impl_layoutBoundsChanged();
 975             impl_geomChanged();
 976             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
 977             setNeedsLayout(true);
 978             requestParentLayout();
 979         }
 980     }
 981 
 982     public final double getWidth() { return width == null ? _width : width.get(); }
 983 
 984     public final ReadOnlyDoubleProperty widthProperty() {
 985         if (width == null) {
 986             width = new ReadOnlyDoubleWrapper(_width) {
 987                 @Override protected void invalidated() { widthChanged(get()); }
 988                 @Override public Object getBean() { return Region.this; }
 989                 @Override public String getName() { return "width"; }
 990             };
 991         }
 992         return width.getReadOnlyProperty();
 993     }
 994 
 995     /**


1013     // access to a writable property for "height", but since there is now a protected
1014     // set method, it is impossible for Region to ever bind this property.
1015     protected void setHeight(double value) {
1016         if (height == null) {
1017             heightChanged(value);
1018         } else {
1019             height.set(value);
1020         }
1021     }
1022 
1023     private void heightChanged(double value) {
1024         if (_height != value) {
1025             _height = value;
1026             cornersValid = false;
1027             // It is possible that somebody sets the height of the region to a value which
1028             // it previously held. If this is the case, we want to avoid excessive layouts.
1029             // Note that I have biased this for layout over binding, because the heightProperty
1030             // is now going to recompute the height eagerly. The cost of excessive and
1031             // unnecessary bounds changes, however, is relatively high.
1032             boundingBox = null;
1033             // Note: although impl_geomChanged will usually also invalidate the
1034             // layout bounds, that is not the case for Regions, and both must
1035             // be called separately.
1036             impl_geomChanged();
1037             impl_layoutBoundsChanged();
1038             // We use "NODE_GEOMETRY" to mean that the bounds have changed and
1039             // need to be sync'd with the render tree
1040             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1041             // Change of the height (or width) won't change the preferred size.
1042             // So we don't need to flush the cache. We should however mark this node
1043             // as needs layout to be internally layouted.
1044             setNeedsLayout(true);
1045             // This call is only needed when this was not called from the parent during the layout.
1046             // Otherwise it would flush the cache of the parent, which is not necessary
1047             requestParentLayout();
1048         }
1049     }
1050 
1051     public final double getHeight() { return height == null ? _height : height.get(); }
1052 
1053     public final ReadOnlyDoubleProperty heightProperty() {
1054         if (height == null) {
1055             height = new ReadOnlyDoubleWrapper(_height) {
1056                 @Override protected void invalidated() { heightChanged(get()); }
1057                 @Override public Object getBean() { return Region.this; }


1332         @Override protected void invalidated() {
1333             final Shape value = get();
1334             if (_shape != value) {
1335                 // The shape has changed. We need to add/remove listeners
1336                 if (_shape != null) ShapeHelper.setShapeChangeListener(_shape, null);
1337                 if (value != null) ShapeHelper.setShapeChangeListener(value, this);
1338                 // Invalidate the bounds and such
1339                 run();
1340                 if (_shape == null || value == null) {
1341                     // It either was null before, or is null now. In either case,
1342                     // the result of the insets computation will have changed, and
1343                     // we therefore need to fire that the insets value may have changed.
1344                     insets.fireValueChanged();
1345                 }
1346                 // Update our reference to the old shape
1347                 _shape = value;
1348             }
1349         }
1350 
1351         @Override public void run() {
1352             impl_geomChanged();
1353             NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1354         }
1355     };
1356 
1357     /**
1358      * Specifies whether the shape, if defined, is scaled to match the size of the Region.
1359      * {@code true} means the shape is scaled to fit the size of the Region, {@code false}
1360      * means the shape is at its source size, its positioning depends on the value of
1361      * {@code centerShape}.
1362      *
1363      * @default true
1364      * @css shape-size      true | false
1365      * @since JavaFX 8.0
1366      */
1367     private BooleanProperty scaleShape = null;
1368     public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); }
1369     public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); }
1370     public final BooleanProperty scaleShapeProperty() {
1371         if (scaleShape == null) {
1372             scaleShape = new StyleableBooleanProperty(true) {
1373                 @Override public Object getBean() { return Region.this; }
1374                 @Override public String getName() { return "scaleShape"; }
1375                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1376                     return StyleableProperties.SCALE_SHAPE;
1377                 }
1378                 @Override public void invalidated() {
1379                     impl_geomChanged();
1380                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1381                 }
1382             };
1383         }
1384         return scaleShape;
1385     }
1386 
1387     /**
1388      * Defines whether the shape is centered within the Region's width or height.
1389      * {@code true} means the shape centered within the Region's width and height,
1390      * {@code false} means the shape is positioned at its source position.
1391      *
1392      * @default true
1393      * @css position-shape      true | false
1394      * @since JavaFX 8.0
1395      */
1396     private BooleanProperty centerShape = null;
1397     public final void setCenterShape(boolean value) { centerShapeProperty().set(value); }
1398     public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); }
1399     public final BooleanProperty centerShapeProperty() {
1400         if (centerShape == null) {
1401             centerShape = new StyleableBooleanProperty(true) {
1402                 @Override public Object getBean() { return Region.this; }
1403                 @Override public String getName() { return "centerShape"; }
1404                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1405                     return StyleableProperties.POSITION_SHAPE;
1406                 }
1407                 @Override public void invalidated() {
1408                     impl_geomChanged();
1409                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1410                 }
1411             };
1412         }
1413         return centerShape;
1414     }
1415 
1416     /**
1417      * Defines a hint to the system indicating that the Shape used to define the region's
1418      * background is stable and would benefit from caching.
1419      *
1420      * @default true
1421      * @css -fx-cache-shape      true | false
1422      * @since JavaFX 8.0
1423      */
1424     private BooleanProperty cacheShape = null;
1425     public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); }
1426     public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); }
1427     public final BooleanProperty cacheShapeProperty() {
1428         if (cacheShape == null) {


2740             // This is equivalent to:
2741             // translate(bounds.getMinX(), bounds.getMinY())
2742             // scale(scaleFactorX, scaleFactorY)
2743             // translate(-bounds.getMinX(), -bounds.getMinY())
2744             // translate(-leftOffset, -topOffset)
2745             //
2746             // which is an inversion of an transformation done to the shape
2747             // This gives us
2748             //
2749             //resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX();
2750             //resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY();
2751             //
2752             // which can be further reduceD to
2753             resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX();
2754             resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY();
2755 
2756         }
2757         return shape.contains((float)resX, (float)resY);
2758     }
2759 
2760     /**
2761      * @treatAsPrivate implementation detail
2762      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2763      */
2764     @Deprecated
2765     @Override protected boolean impl_computeContains(double localX, double localY) {
2766         // NOTE: This method only gets called if a quick check of bounds has already
2767         // occurred, so there is no need to test against bound again. We know that the
2768         // point (localX, localY) falls within the bounds of this node, now we need
2769         // to determine if it falls within the geometry of this node.
2770         // Also note that because Region defaults pickOnBounds to true, this code is
2771         // not usually executed. It will only be executed if pickOnBounds is set to false.
2772 
2773         final double x2 = getWidth();
2774         final double y2 = getHeight();
2775 
2776         final Background background = getBackground();
2777         // First check the shape. Shape could be impacted by scaleShape & positionShape properties.
2778         if (_shape != null) {
2779             if (background != null && !background.getFills().isEmpty()) {
2780                 final List<BackgroundFill> fills = background.getFills();
2781                 double topO = Double.MAX_VALUE;
2782                 double leftO = Double.MAX_VALUE;
2783                 double bottomO = Double.MAX_VALUE;
2784                 double rightO = Double.MAX_VALUE;
2785                 for (int i = 0, max = fills.size(); i < max; i++) {


3109         double scale = 1.0;
3110         if (tlhr + trhr > width)  { scale = Math.min(scale, width  / (tlhr + trhr)); }
3111         if (blhr + brhr > width)  { scale = Math.min(scale, width  / (blhr + brhr)); }
3112         if (tlvr + blvr > height) { scale = Math.min(scale, height / (tlvr + blvr)); }
3113         if (trvr + brvr > height) { scale = Math.min(scale, height / (trvr + brvr)); }
3114         if (scale < 1.0) {
3115             tlvr *= scale;  tlhr *= scale;
3116             trvr *= scale;  trhr *= scale;
3117             brvr *= scale;  brhr *= scale;
3118             blvr *= scale;  blhr *= scale;
3119         }
3120         if (radii.hasPercentBasedRadii || scale < 1.0) {
3121             return new CornerRadii(tlhr,  tlvr,  trvr,  trhr,  brhr,  brvr,  blvr,  blhr,
3122                                    false, false, false, false, false, false, false, false);
3123         }
3124         return radii;
3125     }
3126 
3127     /**
3128      * Some skins relying on this
3129      * @treatAsPrivate implementation detail
3130      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3131      */
3132     @Deprecated
3133     @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
3134          double boundsDistance = impl_intersectsBounds(pickRay);
3135 
3136         if (!Double.isNaN(boundsDistance) && ParentHelper.pickChildrenNode(this, pickRay, result)) {
3137             impl_intersects(pickRay, result);
3138         }
3139     }
3140 
3141     private Bounds boundingBox;
3142 
3143     /**
3144      * The layout bounds of this region: {@code 0, 0  width x height}
3145      *
3146      * @treatAsPrivate implementation detail
3147      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3148      */
3149     @Deprecated
3150     @Override protected final Bounds impl_computeLayoutBounds() {
3151         if (boundingBox == null) {
3152             // we reuse the bounding box if the width and height haven't changed.
3153             boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0);
3154         }
3155         return boundingBox;
3156     }
3157 
3158     /**
3159      * @treatAsPrivate implementation detail
3160      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3161      */
3162     @Deprecated
3163     @Override final protected void impl_notifyLayoutBoundsChanged() {
3164         // override Node's default behavior of having a geometric bounds change
3165         // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
3166         // is unrelated to geometric bounds.
3167     }
3168 
3169     private BaseBounds computeShapeBounds(BaseBounds bounds)
3170     {
3171         com.sun.javafx.geom.Shape s = ShapeHelper.configShape(_shape);
3172 
3173         float[] bbox = {
3174                 Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
3175                 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
3176         };
3177 
3178         Background bg = getBackground();
3179         if (bg != null) {
3180             final RectBounds sBounds = s.getBounds();
3181             final Insets bgOutsets = bg.getOutsets();
3182             bbox[0] = sBounds.getMinX() - (float) bgOutsets.getLeft();
3183             bbox[1] = sBounds.getMinY() - (float) bgOutsets.getTop();


3202                 double sw = Math.max(bs.getWidths().top, 0d);
3203                 StrokeLineCap cap = bss.getLineCap();
3204                 StrokeLineJoin join = bss.getLineJoin();
3205                 float miterlimit = (float) Math.max(bss.getMiterLimit(), 1d);
3206                 Toolkit.getToolkit().accumulateStrokeBounds(
3207                         s,
3208                         bbox, type, sw,
3209                         cap, join, miterlimit, BaseTransform.IDENTITY_TRANSFORM);
3210 
3211             }
3212         }
3213 
3214         if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) {
3215             return bounds.makeEmpty();
3216         }
3217 
3218         return bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f,
3219                 bbox[2], bbox[3], 0.0f);
3220     }
3221 
3222     /**
3223      * @treatAsPrivate implementation detail
3224      * @deprecated This is an internal API that is not intended for use and will be removed in the next version


3225      */
3226     @Deprecated
3227     @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
3228         // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children.
3229         // The bounds of the Region must take into account any backgrounds and borders and how
3230         // they are used to draw the Region. The geom bounds must always take into account
3231         // all pixels drawn (because the geom bounds forms the basis of the dirty regions).
3232         // Note that the layout bounds of a Region is not based on the geom bounds.
3233 
3234         // Define some variables to hold the top-left and bottom-right corners of the bounds
3235         double bx1 = 0;
3236         double by1 = 0;
3237         double bx2 = getWidth();
3238         double by2 = getHeight();
3239 
3240         // If the shape is defined, then the top-left and bottom-right corner positions
3241         // need to be redefined
3242         if (_shape != null && isScaleShape() == false) {
3243             // We will hijack the bounds here temporarily just to compute the shape bounds
3244             final BaseBounds shapeBounds = computeShapeBounds(bounds);
3245             final double shapeWidth = shapeBounds.getWidth();
3246             final double shapeHeight = shapeBounds.getHeight();
3247             if (isCenterShape()) {
3248                 bx1 = (bx2 - shapeWidth) / 2;
3249                 by1 = (by2 - shapeHeight) / 2;
3250                 bx2 = bx1 + shapeWidth;
3251                 by2 = by1 + shapeHeight;
3252             } else {
3253                 bx1 = shapeBounds.getMinX();
3254                 by1 = shapeBounds.getMinY();
3255                 bx2 = shapeBounds.getMaxX();
3256                 by2 = shapeBounds.getMaxY();
3257             }
3258         } else {
3259             // Expand the bounds to include the outsets from the background and border.
3260             // The outsets are the opposite of insets -- a measure of distance from the
3261             // edge of the Region outward. The outsets cannot, however, be negative.
3262             final Background background = getBackground();
3263             final Border border = getBorder();
3264             final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets();
3265             final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets();
3266             bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft());
3267             by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop());
3268             bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight());
3269             by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom());
3270         }
3271         // NOTE: Okay to call impl_computeGeomBounds with tx even in the 3D case
3272         // since Parent.impl_computeGeomBounds does handle 3D correctly.
3273         BaseBounds cb = super.impl_computeGeomBounds(bounds, tx);



3274         /*
3275          * This is a work around for RT-7680. Parent returns invalid bounds from
3276          * impl_computeGeomBounds when it has no children or if all its children
3277          * have invalid bounds. If RT-7680 were fixed, then we could omit this
3278          * first branch of the if and only use the else since the correct value
3279          * would be computed.
3280          */
3281         if (cb.isEmpty()) {
3282             // There are no children bounds, so
3283             bounds = bounds.deriveWithNewBounds(
3284                     (float)bx1, (float)by1, 0.0f,
3285                     (float)bx2, (float)by2, 0.0f);
3286             bounds = tx.transform(bounds, bounds);
3287             return bounds;
3288         } else {
3289             // Union with children's bounds
3290             BaseBounds tempBounds = TempState.getInstance().bounds;
3291             tempBounds = tempBounds.deriveWithNewBounds(
3292                     (float)bx1, (float)by1, 0.0f,
3293                     (float)bx2, (float)by2, 0.0f);
3294             BaseBounds bb = tx.transform(tempBounds, tempBounds);
3295             cb = cb.deriveWithUnion(bb);
3296             return cb;




 141  * region subclass.
 142  * <p/>
 143  * Region subclasses which layout their children will position nodes by setting
 144  * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter
 145  * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for
 146  * adjustments and animation.
 147  * @since JavaFX 2.0
 148  */
 149 public class Region extends Parent {
 150     static {
 151         RegionHelper.setRegionAccessor(new RegionHelper.RegionAccessor() {
 152             @Override
 153             public NGNode doCreatePeer(Node node) {
 154                 return ((Region) node).doCreatePeer();
 155             }
 156 
 157             @Override
 158             public void doUpdatePeer(Node node) {
 159                 ((Region) node).doUpdatePeer();
 160             }
 161 
 162             @Override
 163             public Bounds doComputeLayoutBounds(Node node) {
 164                 return ((Region) node).doComputeLayoutBounds();
 165             }
 166 
 167             @Override
 168             public BaseBounds doComputeGeomBounds(Node node,
 169             BaseBounds bounds, BaseTransform tx) {
 170                 return ((Region) node).doComputeGeomBounds(bounds, tx);
 171             }
 172 
 173             @Override
 174             public BaseBounds doComputeGeomBounds2(BaseBounds cb, Node node, BaseBounds bounds, BaseTransform tx) {
 175                 return ((Region) node).doComputeGeomBounds2(cb, bounds, tx);
 176             }
 177 
 178             @Override
 179             public boolean doComputeContains(Node node, double localX, double localY) {
 180                 return ((Region) node).doComputeContains(localX, localY);
 181             }
 182 
 183             @Override
 184             public void doNotifyLayoutBoundsChanged(Node node) {
 185                 ((Region) node).doNotifyLayoutBoundsChanged();
 186             }
 187 
 188             @Override
 189             public void doPickNodeLocal(Node node, PickRay localPickRay,
 190             PickResultChooser result) {
 191                 ((Region) node).doPickNodeLocal(localPickRay, result);
 192             }
 193         });
 194     }
 195 
 196     /**
 197      * Sentinel value which can be passed to a region's
 198      * {@link #setMinWidth(double) setMinWidth},
 199      * {@link #setMinHeight(double) setMinHeight},
 200      * {@link #setMaxWidth(double) setMaxWidth} or
 201      * {@link #setMaxHeight(double) setMaxHeight}
 202      * methods to indicate that the preferred dimension should be used for that max and/or min constraint.
 203      */
 204     public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY;
 205 
 206     /**
 207      * Sentinel value which can be passed to a region's
 208      * {@link #setMinWidth(double) setMinWidth},
 209      * {@link #setMinHeight(double) setMinHeight},
 210      * {@link #setPrefWidth(double) setPrefWidth},
 211      * {@link #setPrefHeight(double) setPrefHeight},
 212      * {@link #setMaxWidth(double) setMaxWidth},


 698      * The background of the Region, which is made up of zero or more BackgroundFills, and
 699      * zero or more BackgroundImages. It is possible for a Background to be empty, where it
 700      * has neither fills nor images, and is semantically equivalent to null.
 701      * @since JavaFX 8.0
 702      */
 703     private final ObjectProperty<Background> background = new StyleableObjectProperty<Background>(null) {
 704         private Background old = null;
 705         @Override public Object getBean() { return Region.this; }
 706         @Override public String getName() { return "background"; }
 707         @Override public CssMetaData<Region, Background> getCssMetaData() {
 708             return StyleableProperties.BACKGROUND;
 709         }
 710 
 711         @Override protected void invalidated() {
 712             final Background b = get();
 713             if(old != null ? !old.equals(b) : b != null) {
 714                 // They are different! Both cannot be null
 715                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 716                     // We have determined that the outsets of these two different background
 717                     // objects is different, and therefore the bounds have changed.
 718                     NodeHelper.geomChanged(Region.this);
 719                     insets.fireValueChanged();
 720                 }
 721 
 722                 // If the Background is made up of any BackgroundImage objects, then we must
 723                 // inspect the images of those BackgroundImage objects to see if they are still
 724                 // being loaded in the background or if they are animated. If so, then we need
 725                 // to attach a listener, so that when the image finishes loading or changes,
 726                 // we can repaint the region.
 727                 if (b != null) {
 728                     for (BackgroundImage i : b.getImages()) {
 729                         final Image image = i.image;
 730                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 731                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 732                             addImageListener(image);
 733                         }
 734                     }
 735                 }
 736 
 737                 // And we must remove this listener from any old images
 738                 if (old != null) {


 755     /**
 756      * The border of the Region, which is made up of zero or more BorderStrokes, and
 757      * zero or more BorderImages. It is possible for a Border to be empty, where it
 758      * has neither strokes nor images, and is semantically equivalent to null.
 759      * @since JavaFX 8.0
 760      */
 761     private final ObjectProperty<Border> border = new StyleableObjectProperty<Border>(null) {
 762         private Border old = null;
 763         @Override public Object getBean() { return Region.this; }
 764         @Override public String getName() { return "border"; }
 765         @Override public CssMetaData<Region, Border> getCssMetaData() {
 766             return StyleableProperties.BORDER;
 767         }
 768         @Override protected void invalidated() {
 769             final Border b = get();
 770             if(old != null ? !old.equals(b) : b != null) {
 771                 // They are different! Both cannot be null
 772                 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) {
 773                     // We have determined that the outsets of these two different border
 774                     // objects is different, and therefore the bounds have changed.
 775                     NodeHelper.geomChanged(Region.this);
 776                 }
 777                 if (old == null || b == null || !old.getInsets().equals(b.getInsets())) {
 778                     insets.fireValueChanged();
 779                 }
 780 
 781                 // If the Border is made up of any BorderImage objects, then we must
 782                 // inspect the images of those BorderImage objects to see if they are still
 783                 // being loaded in the background or if they are animated. If so, then we need
 784                 // to attach a listener, so that when the image finishes loading or changes,
 785                 // we can repaint the region.
 786                 if (b != null) {
 787                     for (BorderImage i : b.getImages()) {
 788                         final Image image = i.image;
 789                         final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor();
 790                         if (acc.isAnimation(image) || image.getProgress() < 1) {
 791                             addImageListener(image);
 792                         }
 793                     }
 794                 }
 795 


 986     // access to a writable property for "width", but since there is now a protected
 987     // set method, it is impossible for Region to ever bind this property.
 988     protected void setWidth(double value) {
 989         if(width == null) {
 990             widthChanged(value);
 991         } else {
 992             width.set(value);
 993         }
 994     }
 995 
 996     private void widthChanged(double value) {
 997         // It is possible that somebody sets the width of the region to a value which
 998         // it previously held. If this is the case, we want to avoid excessive layouts.
 999         // Note that I have biased this for layout over binding, because the widthProperty
1000         // is now going to recompute the width eagerly. The cost of excessive and
1001         // unnecessary bounds changes, however, is relatively high.
1002         if (value != _width) {
1003             _width = value;
1004             cornersValid = false;
1005             boundingBox = null;
1006             NodeHelper.layoutBoundsChanged(this);
1007             NodeHelper.geomChanged(this);
1008             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1009             setNeedsLayout(true);
1010             requestParentLayout();
1011         }
1012     }
1013 
1014     public final double getWidth() { return width == null ? _width : width.get(); }
1015 
1016     public final ReadOnlyDoubleProperty widthProperty() {
1017         if (width == null) {
1018             width = new ReadOnlyDoubleWrapper(_width) {
1019                 @Override protected void invalidated() { widthChanged(get()); }
1020                 @Override public Object getBean() { return Region.this; }
1021                 @Override public String getName() { return "width"; }
1022             };
1023         }
1024         return width.getReadOnlyProperty();
1025     }
1026 
1027     /**


1045     // access to a writable property for "height", but since there is now a protected
1046     // set method, it is impossible for Region to ever bind this property.
1047     protected void setHeight(double value) {
1048         if (height == null) {
1049             heightChanged(value);
1050         } else {
1051             height.set(value);
1052         }
1053     }
1054 
1055     private void heightChanged(double value) {
1056         if (_height != value) {
1057             _height = value;
1058             cornersValid = false;
1059             // It is possible that somebody sets the height of the region to a value which
1060             // it previously held. If this is the case, we want to avoid excessive layouts.
1061             // Note that I have biased this for layout over binding, because the heightProperty
1062             // is now going to recompute the height eagerly. The cost of excessive and
1063             // unnecessary bounds changes, however, is relatively high.
1064             boundingBox = null;
1065             // Note: although NodeHelper.geomChanged will usually also invalidate the
1066             // layout bounds, that is not the case for Regions, and both must
1067             // be called separately.
1068             NodeHelper.geomChanged(this);
1069             NodeHelper.layoutBoundsChanged(this);
1070             // We use "NODE_GEOMETRY" to mean that the bounds have changed and
1071             // need to be sync'd with the render tree
1072             NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
1073             // Change of the height (or width) won't change the preferred size.
1074             // So we don't need to flush the cache. We should however mark this node
1075             // as needs layout to be internally layouted.
1076             setNeedsLayout(true);
1077             // This call is only needed when this was not called from the parent during the layout.
1078             // Otherwise it would flush the cache of the parent, which is not necessary
1079             requestParentLayout();
1080         }
1081     }
1082 
1083     public final double getHeight() { return height == null ? _height : height.get(); }
1084 
1085     public final ReadOnlyDoubleProperty heightProperty() {
1086         if (height == null) {
1087             height = new ReadOnlyDoubleWrapper(_height) {
1088                 @Override protected void invalidated() { heightChanged(get()); }
1089                 @Override public Object getBean() { return Region.this; }


1364         @Override protected void invalidated() {
1365             final Shape value = get();
1366             if (_shape != value) {
1367                 // The shape has changed. We need to add/remove listeners
1368                 if (_shape != null) ShapeHelper.setShapeChangeListener(_shape, null);
1369                 if (value != null) ShapeHelper.setShapeChangeListener(value, this);
1370                 // Invalidate the bounds and such
1371                 run();
1372                 if (_shape == null || value == null) {
1373                     // It either was null before, or is null now. In either case,
1374                     // the result of the insets computation will have changed, and
1375                     // we therefore need to fire that the insets value may have changed.
1376                     insets.fireValueChanged();
1377                 }
1378                 // Update our reference to the old shape
1379                 _shape = value;
1380             }
1381         }
1382 
1383         @Override public void run() {
1384             NodeHelper.geomChanged(Region.this);
1385             NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1386         }
1387     };
1388 
1389     /**
1390      * Specifies whether the shape, if defined, is scaled to match the size of the Region.
1391      * {@code true} means the shape is scaled to fit the size of the Region, {@code false}
1392      * means the shape is at its source size, its positioning depends on the value of
1393      * {@code centerShape}.
1394      *
1395      * @default true
1396      * @css shape-size      true | false
1397      * @since JavaFX 8.0
1398      */
1399     private BooleanProperty scaleShape = null;
1400     public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); }
1401     public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); }
1402     public final BooleanProperty scaleShapeProperty() {
1403         if (scaleShape == null) {
1404             scaleShape = new StyleableBooleanProperty(true) {
1405                 @Override public Object getBean() { return Region.this; }
1406                 @Override public String getName() { return "scaleShape"; }
1407                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1408                     return StyleableProperties.SCALE_SHAPE;
1409                 }
1410                 @Override public void invalidated() {
1411                     NodeHelper.geomChanged(Region.this);
1412                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1413                 }
1414             };
1415         }
1416         return scaleShape;
1417     }
1418 
1419     /**
1420      * Defines whether the shape is centered within the Region's width or height.
1421      * {@code true} means the shape centered within the Region's width and height,
1422      * {@code false} means the shape is positioned at its source position.
1423      *
1424      * @default true
1425      * @css position-shape      true | false
1426      * @since JavaFX 8.0
1427      */
1428     private BooleanProperty centerShape = null;
1429     public final void setCenterShape(boolean value) { centerShapeProperty().set(value); }
1430     public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); }
1431     public final BooleanProperty centerShapeProperty() {
1432         if (centerShape == null) {
1433             centerShape = new StyleableBooleanProperty(true) {
1434                 @Override public Object getBean() { return Region.this; }
1435                 @Override public String getName() { return "centerShape"; }
1436                 @Override public CssMetaData<Region, Boolean> getCssMetaData() {
1437                     return StyleableProperties.POSITION_SHAPE;
1438                 }
1439                 @Override public void invalidated() {
1440                     NodeHelper.geomChanged(Region.this);
1441                     NodeHelper.markDirty(Region.this, DirtyBits.REGION_SHAPE);
1442                 }
1443             };
1444         }
1445         return centerShape;
1446     }
1447 
1448     /**
1449      * Defines a hint to the system indicating that the Shape used to define the region's
1450      * background is stable and would benefit from caching.
1451      *
1452      * @default true
1453      * @css -fx-cache-shape      true | false
1454      * @since JavaFX 8.0
1455      */
1456     private BooleanProperty cacheShape = null;
1457     public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); }
1458     public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); }
1459     public final BooleanProperty cacheShapeProperty() {
1460         if (cacheShape == null) {


2772             // This is equivalent to:
2773             // translate(bounds.getMinX(), bounds.getMinY())
2774             // scale(scaleFactorX, scaleFactorY)
2775             // translate(-bounds.getMinX(), -bounds.getMinY())
2776             // translate(-leftOffset, -topOffset)
2777             //
2778             // which is an inversion of an transformation done to the shape
2779             // This gives us
2780             //
2781             //resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX();
2782             //resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY();
2783             //
2784             // which can be further reduceD to
2785             resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX();
2786             resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY();
2787 
2788         }
2789         return shape.contains((float)resX, (float)resY);
2790     }
2791 
2792     /*
2793      * Note: This method MUST only be called via its accessor method.

2794      */
2795     private boolean doComputeContains(double localX, double localY) {

2796         // NOTE: This method only gets called if a quick check of bounds has already
2797         // occurred, so there is no need to test against bound again. We know that the
2798         // point (localX, localY) falls within the bounds of this node, now we need
2799         // to determine if it falls within the geometry of this node.
2800         // Also note that because Region defaults pickOnBounds to true, this code is
2801         // not usually executed. It will only be executed if pickOnBounds is set to false.
2802 
2803         final double x2 = getWidth();
2804         final double y2 = getHeight();
2805 
2806         final Background background = getBackground();
2807         // First check the shape. Shape could be impacted by scaleShape & positionShape properties.
2808         if (_shape != null) {
2809             if (background != null && !background.getFills().isEmpty()) {
2810                 final List<BackgroundFill> fills = background.getFills();
2811                 double topO = Double.MAX_VALUE;
2812                 double leftO = Double.MAX_VALUE;
2813                 double bottomO = Double.MAX_VALUE;
2814                 double rightO = Double.MAX_VALUE;
2815                 for (int i = 0, max = fills.size(); i < max; i++) {


3139         double scale = 1.0;
3140         if (tlhr + trhr > width)  { scale = Math.min(scale, width  / (tlhr + trhr)); }
3141         if (blhr + brhr > width)  { scale = Math.min(scale, width  / (blhr + brhr)); }
3142         if (tlvr + blvr > height) { scale = Math.min(scale, height / (tlvr + blvr)); }
3143         if (trvr + brvr > height) { scale = Math.min(scale, height / (trvr + brvr)); }
3144         if (scale < 1.0) {
3145             tlvr *= scale;  tlhr *= scale;
3146             trvr *= scale;  trhr *= scale;
3147             brvr *= scale;  brhr *= scale;
3148             blvr *= scale;  blhr *= scale;
3149         }
3150         if (radii.hasPercentBasedRadii || scale < 1.0) {
3151             return new CornerRadii(tlhr,  tlvr,  trvr,  trhr,  brhr,  brvr,  blvr,  blhr,
3152                                    false, false, false, false, false, false, false, false);
3153         }
3154         return radii;
3155     }
3156 
3157     /**
3158      * Some skins relying on this
3159      *
3160      * Note: This method MUST only be called via its accessor method.
3161      */
3162     private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) {
3163          double boundsDistance = NodeHelper.intersectsBounds(this, pickRay);

3164 
3165         if (!Double.isNaN(boundsDistance) && ParentHelper.pickChildrenNode(this, pickRay, result)) {
3166             NodeHelper.intersects(this, pickRay, result);
3167         }
3168     }
3169 
3170     private Bounds boundingBox;
3171 
3172     /**
3173      * The layout bounds of this region: {@code 0, 0  width x height}



3174      */
3175     private final Bounds doComputeLayoutBounds() {

3176         if (boundingBox == null) {
3177             // we reuse the bounding box if the width and height haven't changed.
3178             boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0);
3179         }
3180         return boundingBox;
3181     }
3182 
3183     /*
3184      * Note: This method MUST only be called via its accessor method.

3185      */
3186     private void doNotifyLayoutBoundsChanged() {

3187         // override Node's default behavior of having a geometric bounds change
3188         // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
3189         // is unrelated to geometric bounds.
3190     }
3191 
3192     private BaseBounds computeShapeBounds(BaseBounds bounds)
3193     {
3194         com.sun.javafx.geom.Shape s = ShapeHelper.configShape(_shape);
3195 
3196         float[] bbox = {
3197                 Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
3198                 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
3199         };
3200 
3201         Background bg = getBackground();
3202         if (bg != null) {
3203             final RectBounds sBounds = s.getBounds();
3204             final Insets bgOutsets = bg.getOutsets();
3205             bbox[0] = sBounds.getMinX() - (float) bgOutsets.getLeft();
3206             bbox[1] = sBounds.getMinY() - (float) bgOutsets.getTop();


3225                 double sw = Math.max(bs.getWidths().top, 0d);
3226                 StrokeLineCap cap = bss.getLineCap();
3227                 StrokeLineJoin join = bss.getLineJoin();
3228                 float miterlimit = (float) Math.max(bss.getMiterLimit(), 1d);
3229                 Toolkit.getToolkit().accumulateStrokeBounds(
3230                         s,
3231                         bbox, type, sw,
3232                         cap, join, miterlimit, BaseTransform.IDENTITY_TRANSFORM);
3233 
3234             }
3235         }
3236 
3237         if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) {
3238             return bounds.makeEmpty();
3239         }
3240 
3241         return bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f,
3242                 bbox[2], bbox[3], 0.0f);
3243     }
3244 
3245     // Define some variables to hold the top-left and bottom-right corners of the bounds
3246     private double bx1, by1, bx2, by2;
3247 
3248     /*
3249      * Note: This method MUST only be called via its accessor method.
3250      */
3251     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {

3252         // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children.
3253         // The bounds of the Region must take into account any backgrounds and borders and how
3254         // they are used to draw the Region. The geom bounds must always take into account
3255         // all pixels drawn (because the geom bounds forms the basis of the dirty regions).
3256         // Note that the layout bounds of a Region is not based on the geom bounds.
3257 
3258         // Define some variables to hold the top-left and bottom-right corners of the bounds
3259         bx1 = 0;
3260         by1 = 0;
3261         bx2 = getWidth();
3262         by2 = getHeight();
3263 
3264         // If the shape is defined, then the top-left and bottom-right corner positions
3265         // need to be redefined
3266         if (_shape != null && isScaleShape() == false) {
3267             // We will hijack the bounds here temporarily just to compute the shape bounds
3268             final BaseBounds shapeBounds = computeShapeBounds(bounds);
3269             final double shapeWidth = shapeBounds.getWidth();
3270             final double shapeHeight = shapeBounds.getHeight();
3271             if (isCenterShape()) {
3272                 bx1 = (bx2 - shapeWidth) / 2;
3273                 by1 = (by2 - shapeHeight) / 2;
3274                 bx2 = bx1 + shapeWidth;
3275                 by2 = by1 + shapeHeight;
3276             } else {
3277                 bx1 = shapeBounds.getMinX();
3278                 by1 = shapeBounds.getMinY();
3279                 bx2 = shapeBounds.getMaxX();
3280                 by2 = shapeBounds.getMaxY();
3281             }
3282         } else {
3283             // Expand the bounds to include the outsets from the background and border.
3284             // The outsets are the opposite of insets -- a measure of distance from the
3285             // edge of the Region outward. The outsets cannot, however, be negative.
3286             final Background background = getBackground();
3287             final Border border = getBorder();
3288             final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets();
3289             final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets();
3290             bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft());
3291             by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop());
3292             bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight());
3293             by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom());
3294         }
3295 
3296         return null;
3297     }
3298 
3299     private BaseBounds doComputeGeomBounds2(BaseBounds cb, BaseBounds bounds,
3300             BaseTransform tx) {
3301         /*
3302          * This is a work around for RT-7680. Parent returns invalid bounds from
3303          * computeGeomBoundsImpl when it has no children or if all its children
3304          * have invalid bounds. If RT-7680 were fixed, then we could omit this
3305          * first branch of the if and only use the else since the correct value
3306          * would be computed.
3307          */
3308         if (cb.isEmpty()) {
3309             // There are no children bounds, so
3310             bounds = bounds.deriveWithNewBounds(
3311                     (float)bx1, (float)by1, 0.0f,
3312                     (float)bx2, (float)by2, 0.0f);
3313             bounds = tx.transform(bounds, bounds);
3314             return bounds;
3315         } else {
3316             // Union with children's bounds
3317             BaseBounds tempBounds = TempState.getInstance().bounds;
3318             tempBounds = tempBounds.deriveWithNewBounds(
3319                     (float)bx1, (float)by1, 0.0f,
3320                     (float)bx2, (float)by2, 0.0f);
3321             BaseBounds bb = tx.transform(tempBounds, tempBounds);
3322             cb = cb.deriveWithUnion(bb);
3323             return cb;


< prev index next >