Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2012, 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.Arrays;
029import java.util.Collections;
030import java.util.List;
031import javafx.geometry.Insets;
032import javafx.scene.Node;
033import javafx.scene.image.Image;
034import javafx.scene.paint.Color;
035import javafx.scene.paint.Paint;
036import com.sun.javafx.UnmodifiableArrayList;
037import javafx.css.CssMetaData;
038import com.sun.javafx.css.SubCssMetaData;
039import com.sun.javafx.css.converters.InsetsConverter;
040import com.sun.javafx.css.converters.PaintConverter;
041import com.sun.javafx.css.converters.URLConverter;
042import com.sun.javafx.scene.layout.region.LayeredBackgroundPositionConverter;
043import com.sun.javafx.scene.layout.region.LayeredBackgroundSizeConverter;
044import com.sun.javafx.scene.layout.region.RepeatStruct;
045import com.sun.javafx.scene.layout.region.RepeatStructConverter;
046import javafx.css.Styleable;
047
048/**
049 * The Background of a {@link Region}. A Background is an immutable object which
050 * encapsulates the entire set of data required to render the background
051 * of a Region. Because this class is immutable, you can freely reuse the same
052 * Background on many different Regions. Please refer to
053 * {@link ../doc-files/cssref.html JavaFX CSS Reference} for a complete description
054 * of the CSS rules for styling the background of a Region.
055 * <p/>
056 * Every Background is comprised of {@link #getFills() fills} and / or
057 * {@link #getImages() images}. Neither list will ever be null, but either or
058 * both may be empty. Each defined {@link BackgroundFill} is rendered in order,
059 * followed by each defined {@link BackgroundImage}.
060 * <p/>
061 * The Background's {@link #getOutsets() outsets} define any extension of the drawing area of a Region
062 * which is necessary to account for all background drawing. These outsets are strictly
063 * defined by the BackgroundFills that are specified on this Background, if any, because
064 * all BackgroundImages are clipped to the drawing area, and do not define it. The
065 * outsets values are strictly non-negative.
066 *
067 * @since JavaFX 8
068 */
069@SuppressWarnings("unchecked") 
070public final class Background {
071    static final CssMetaData<Node,Paint[]> BACKGROUND_COLOR =
072            new SubCssMetaData<Paint[]>("-fx-background-color",
073                    PaintConverter.SequenceConverter.getInstance(),
074                    new Paint[] {Color.TRANSPARENT});
075
076    static final CssMetaData<Node,Insets[]> BACKGROUND_RADIUS =
077            new SubCssMetaData<Insets[]>("-fx-background-radius",
078                    InsetsConverter.SequenceConverter.getInstance(),
079                    new Insets[] {Insets.EMPTY});
080
081    static final CssMetaData<Node,Insets[]> BACKGROUND_INSETS =
082            new SubCssMetaData<Insets[]>("-fx-background-insets",
083                    InsetsConverter.SequenceConverter.getInstance(),
084                    new Insets[] {Insets.EMPTY});
085
086    static final CssMetaData<Node,Image[]> BACKGROUND_IMAGE =
087            new SubCssMetaData<Image[]>("-fx-background-image",
088                    URLConverter.SequenceConverter.getInstance());
089
090    static final CssMetaData<Node,RepeatStruct[]> BACKGROUND_REPEAT =
091            new SubCssMetaData<RepeatStruct[]>("-fx-background-repeat",
092                    RepeatStructConverter.getInstance(),
093                    new RepeatStruct[] {new RepeatStruct(BackgroundRepeat.REPEAT,
094                                                         BackgroundRepeat.REPEAT) });
095
096    static final CssMetaData<Node,BackgroundPosition[]> BACKGROUND_POSITION =
097            new SubCssMetaData<BackgroundPosition[]>("-fx-background-position",
098                    LayeredBackgroundPositionConverter.getInstance(),
099                    new BackgroundPosition[] { BackgroundPosition.DEFAULT });
100
101    static final CssMetaData<Node,BackgroundSize[]> BACKGROUND_SIZE =
102            new SubCssMetaData<BackgroundSize[]>("-fx-background-size",
103                    LayeredBackgroundSizeConverter.getInstance(),
104                    new BackgroundSize[] { BackgroundSize.DEFAULT } );
105
106    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES =
107            (List<CssMetaData<? extends Styleable, ?>>) (List) Collections.unmodifiableList(
108                    // Unchecked!
109                    Arrays.asList(BACKGROUND_COLOR,
110                            BACKGROUND_INSETS,
111                            BACKGROUND_RADIUS,
112                            BACKGROUND_IMAGE,
113                            BACKGROUND_REPEAT,
114                            BACKGROUND_POSITION,
115                            BACKGROUND_SIZE));
116
117    /**
118     * @return The CssMetaData associated with this class, which may include the
119     * CssMetaData of its super classes.
120     */
121    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
122        return STYLEABLES;
123    }
124
125    /**
126     * An empty Background, useful to use instead of null.
127     */
128    public static final Background EMPTY = new Background((BackgroundFill[])null, null);
129
130    /**
131     * The list of BackgroundFills which together define the filled portion
132     * of this Background. This List is unmodifiable and immutable. It
133     * will never be null. The elements of this list will also never be null.
134     */
135    final List<BackgroundFill> fills;
136    public final List<BackgroundFill> getFills() { return fills; }
137
138    /**
139     * The list of BackgroundImages which together define the image portion
140     * of this Background. This List is unmodifiable and immutable. It
141     * will never be null. The elements of this list will also never be null.
142     */
143    final List<BackgroundImage> images;
144    public final List<BackgroundImage> getImages() { return images; }
145
146    /**
147     * The outsets of this Background. This represents the largest
148     * bounding rectangle within which all drawing for the Background
149     * will take place. The outsets will never be negative, and represent
150     * the distance from the edge of the Region outward. Any BackgroundImages
151     * which would extend beyond the outsets will be clipped. Only the
152     * BackgroundFills contribute to the outsets.
153     */
154    final Insets outsets;
155    public final Insets getOutsets() { return outsets; }
156
157    /**
158     * Gets whether the background is empty. It is empty if there are no fills or images.
159     * @return true if the Background is empty, false otherwise.
160     */
161    public final boolean isEmpty() {
162        return fills.isEmpty() && images.isEmpty();
163    }
164
165    /**
166     * Specifies whether the Background has at least one opaque fill.
167     */
168    final boolean hasOpaqueFill;
169
170    /**
171     * Package-private immutable fields referring to the opaque insets
172     * of this Background.
173     */
174    private final double opaqueTop, opaqueRight, opaqueBottom, opaqueLeft;
175    final boolean hasPercentageBasedOpaqueInsets;
176
177    /**
178     * The cached hash code computation for the Background. One very big
179     * reason for making Background immutable was to make it possible to
180     * cache and reuse the same Background instance for multiple
181     * Regions (for example, every un-hovered Button should have the same
182     * Background instance). To enable efficient caching, we cache the hash.
183     */
184    private final int hash;
185
186    /**
187     * Create a new Background by supplying an array of BackgroundFills.
188     * This array may be null, or may contain null values. Any null values
189     * will be ignored and will not contribute to the {@link #getFills() fills}
190     * or {@link #getOutsets() outsets}.
191     *
192     * @param fills     The fills. This may be null, and may contain nulls. Any
193     *                  contained nulls are filtered out and not included in the
194     *                  final List of fills. A null array becomes an empty List.
195     */
196    public Background(final BackgroundFill... fills) {
197        this(fills, null);
198    }
199
200    /**
201     * Create a new Background by supplying an array of BackgroundImages.
202     * This array may be null, or may contain null values. Any null values will
203     * be ignored and will not contribute to the {@link #getImages() images}.
204     *
205     * @param images    The images. This may be null, and may contain nulls. Any
206     *                  contained nulls are filtered out and not included in the
207     *                  final List of images. A null array becomes an empty List.
208     */
209    public Background(final BackgroundImage... images) {
210        this(null, images);
211    }
212
213    /**
214     * Create a new Background supply two Lists, one for background fills and
215     * one for background images. Either list may be null, and may contain nulls.
216     * Any null values in these lists will be ignored and will not
217     * contribute to the {@link #getFills() fills}, {@link #getImages() images}, or
218     * {@link #getOutsets() outsets}.
219     *
220     * @param fills     The fills. This may be null, and may contain nulls. Any
221     *                  contained nulls are filtered out and not included in the
222     *                  final List of fills. A null List becomes an empty List.
223     * @param images    The images. This may be null, and may contain nulls. Any
224     *                  contained nulls are filtered out and not included in the
225     *                  final List of images. A null List becomes an empty List.
226     */
227    public Background(final List<BackgroundFill> fills, final List<BackgroundImage> images) {
228        // NOTE: This constructor had to be supplied in order to cause a Builder
229        // to be auto-generated, because otherwise the types of the fills and images
230        // properties didn't match the types of the array based constructor parameters.
231        // So a Builder will use this constructor, while the CSS engine uses the
232        // array based constructor (for speed).
233        this(fills == null ? null : fills.toArray(new BackgroundFill[fills.size()]),
234             images == null ? null : images.toArray(new BackgroundImage[images.size()]));
235    }
236
237    /**
238     * Create a new Background by supplying two arrays, one for background fills,
239     * and one for background images. Either array may be null, and may contain null
240     * values. Any null values in these arrays will be ignored and will not
241     * contribute to the {@link #getFills() fills}, {@link #getImages() images}, or
242     * {@link #getOutsets() outsets}.
243     *
244     * @param fills     The fills. This may be null, and may contain nulls. Any
245     *                  contained nulls are filtered out and not included in the
246     *                  final List of fills. A null array becomes an empty List.
247     * @param images    The images. This may be null, and may contain nulls. Any
248     *                  contained nulls are filtered out and not included in the
249     *                  final List of images. A null array becomes an empty List.
250     */
251    public Background(final BackgroundFill[] fills, final BackgroundImage[] images) {
252        // The cumulative insets
253        double outerTop = 0, outerRight = 0, outerBottom = 0, outerLeft = 0;
254        boolean hasPercentOpaqueInsets = false;
255        boolean opaqueFill = false;
256
257        // If the fills is empty or null then we know we can just use the shared
258        // immutable empty list from Collections.
259        if (fills == null || fills.length == 0) {
260            this.fills = Collections.emptyList();
261        } else {
262            // We need to iterate over all of the supplied elements in the fills array.
263            // Each null element is ignored. Each non-null element is inspected to
264            // see if it contributes to the outsets.
265            final BackgroundFill[] noNulls = new BackgroundFill[fills.length];
266            int size = 0;
267            for (int i=0; i<fills.length; i++) {
268                final BackgroundFill fill = fills[i];
269                if (fill != null) {
270                    noNulls[size++] = fill;
271                    final Insets fillInsets = fill.getInsets();
272                    final double fillTop = fillInsets.getTop();
273                    final double fillRight = fillInsets.getRight();
274                    final double fillBottom = fillInsets.getBottom();
275                    final double fillLeft = fillInsets.getLeft();
276                    outerTop = outerTop <= fillTop ? outerTop : fillTop; // min
277                    outerRight = outerRight <= fillRight ? outerRight : fillRight; // min
278                    outerBottom = outerBottom <= fillBottom ? outerBottom : fillBottom; // min
279                    outerLeft = outerLeft <= fillLeft ? outerLeft : fillLeft; // min
280
281                    if (fill.fill.isOpaque()) {
282                        opaqueFill = true;
283                        if (fill.getRadii().hasPercentBasedRadii) {
284                            hasPercentOpaqueInsets = true;
285                        }
286                    }
287                }
288            }
289            this.fills = new UnmodifiableArrayList<BackgroundFill>(noNulls, size);
290        }
291
292        hasOpaqueFill = opaqueFill;
293        if (hasPercentOpaqueInsets) {
294            opaqueTop = Double.NaN;
295            opaqueRight = Double.NaN;
296            opaqueBottom = Double.NaN;
297            opaqueLeft = Double.NaN;
298        } else {
299            double[] trbl = new double[4];
300            computeOpaqueInsets(1, 1, true, trbl);
301            opaqueTop = trbl[0];
302            opaqueRight = trbl[1];
303            opaqueBottom = trbl[2];
304            opaqueLeft = trbl[3];
305        }
306        hasPercentageBasedOpaqueInsets = hasPercentOpaqueInsets;
307
308        // This ensures that we either have outsets of 0, if all the insets were positive,
309        // or a value greater than zero if they were negative.
310        outsets = new Insets(
311                Math.max(0, -outerTop),
312                Math.max(0, -outerRight),
313                Math.max(0, -outerBottom),
314                Math.max(0, -outerLeft));
315
316        // An null or empty images array results in an empty list
317        if (images == null || images.length == 0) {
318            this.images = Collections.emptyList();
319        } else {
320            // Filter out any  null values and create an immutable array list
321            final BackgroundImage[] noNulls = new BackgroundImage[images.length];
322            int size = 0;
323            for (int i=0; i<images.length; i++) {
324                final BackgroundImage image = images[i];
325                if (image != null) noNulls[size++] = image;
326            }
327            this.images = new UnmodifiableArrayList<BackgroundImage>(noNulls, size);
328        }
329
330        // Pre-compute the hash code. NOTE: all variables are prefixed with "this" so that we
331        // do not accidentally compute the hash based on the constructor arguments rather than
332        // based on the fields themselves!
333        int result = this.fills.hashCode();
334        result = 31 * result + this.images.hashCode();
335        hash = result;
336    }
337
338    /**
339     * Computes the opaque insets for a region with the specified width and height. This call
340     * must be made whenever the width or height of the region change, because the opaque insets
341     * are based on background fills, and the corner radii of a background fill can be percentage
342     * based. Thus, we need to potentially recompute the opaque insets whenever the width or
343     * height of the region change. On the other hand, if there are no percentage based corner
344     * radii, then we can simply return the pre-computed and cached answers.
345     *
346     * @param width     The width of the region
347     * @param height    The height of the region
348     * @param trbl      A four-element array of doubles in order: top, right, bottom, left.
349     */
350    void computeOpaqueInsets(double width, double height, double[] trbl) {
351        computeOpaqueInsets(width, height, false, trbl);
352    }
353
354    /**
355     * Computes the opaque insets. The first time this is called from the constructor
356     * we want to take the long route through and compute everything, whether there are
357     * percentage based insets or not (the constructor ensures not to call it in the case
358     * that it has percentage based insets!). All other times, this is called by the other
359     * computeOpaqueInsets method with "firstTime" set to false, such that if we have
360     * percentage based insets, then we will bail early.
361     *
362     * @param width        The width of the region
363     * @param height       The height of the region
364     * @param firstTime    Whether this is being called from the constructor
365     * @param trbl         A four-element array of doubles in order: top, right, bottom, left.
366     */
367    private void computeOpaqueInsets(double width, double height, boolean firstTime, double[] trbl) {
368        // If during object construction we determined that there are no opaque
369        // fills, then we will simple return the "I don't know" answer for
370        // the opaque insets.
371        if (!hasOpaqueFill) {
372            trbl[0] = Double.NaN;
373            trbl[1] = Double.NaN;
374            trbl[2] = Double.NaN;
375            trbl[3] = Double.NaN;
376            return;
377        }
378
379        // If during construction time we determined that none of the fills had a percentage based
380        // opaque inset, then we can return the pre-computed values. This is worth doing since
381        // at this time all CSS based radii for BackgroundFills are literal values!
382        if (!firstTime && !hasPercentageBasedOpaqueInsets) {
383            trbl[0] = opaqueTop;
384            trbl[1] = opaqueRight;
385            trbl[2] = opaqueBottom;
386            trbl[3] = opaqueLeft;
387            return;
388        }
389
390        // NOTE: We know at this point that there is an opaque fill, and that at least one
391        // of them uses a percentage for at least one corner radius. Iterate over each
392        // BackgroundFill. If the fill is opaque, then we will compute the largest rectangle
393        // which will fit within its opaque area, taking the corner radii into account.
394        // Initialize them to the "I Don't Know" answer.
395        double opaqueRegionLeft = Double.NaN,
396                opaqueRegionTop = Double.NaN,
397                opaqueRegionRight = Double.NaN,
398                opaqueRegionBottom = Double.NaN;
399
400        for (int i=0, max=fills.size(); i<max; i++) {
401            final BackgroundFill fill = fills.get(i);
402            final Insets fillInsets = fill.getInsets();
403            final double fillTop = fillInsets.getTop();
404            final double fillRight = fillInsets.getRight();
405            final double fillBottom = fillInsets.getBottom();
406            final double fillLeft = fillInsets.getLeft();
407
408            if (fill.fill.isOpaque()) {
409                // Some possible configurations:
410                //     (a) rect1 is completely contained by rect2
411                //     (b) rect2 is completely contained by rect1
412                //     (c) rect1 is the same height as rect 2 and they overlap on the left or right
413                //     (d) rect1 is the same width as rect 2 and they overlap on the top or bottom
414                //     (e) they are disjoint or overlap in an unsupported manner.
415                final CornerRadii radii = fill.getRadii();
416                final double topLeftHorizontalRadius = radii.isTopLeftHorizontalRadiusAsPercentage() ?
417                        width * radii.getTopLeftHorizontalRadius() : radii.getTopLeftHorizontalRadius();
418                final double topLeftVerticalRadius = radii.isTopLeftVerticalRadiusAsPercentage() ?
419                        height * radii.getTopLeftVerticalRadius() : radii.getTopLeftVerticalRadius();
420                final double topRightVerticalRadius = radii.isTopRightVerticalRadiusAsPercentage() ?
421                        height * radii.getTopRightVerticalRadius() : radii.getTopRightVerticalRadius();
422                final double topRightHorizontalRadius = radii.isTopRightHorizontalRadiusAsPercentage() ?
423                        width * radii.getTopRightHorizontalRadius() : radii.getTopRightHorizontalRadius();
424                final double bottomRightHorizontalRadius = radii.isBottomRightHorizontalRadiusAsPercentage() ?
425                        width * radii.getBottomRightHorizontalRadius() : radii.getBottomRightHorizontalRadius();
426                final double bottomRightVerticalRadius = radii.isBottomRightVerticalRadiusAsPercentage() ?
427                        height * radii.getBottomRightVerticalRadius() : radii.getBottomRightVerticalRadius();
428                final double bottomLeftVerticalRadius = radii.isBottomLeftVerticalRadiusAsPercentage() ?
429                        height * radii.getBottomLeftVerticalRadius() : radii.getBottomLeftVerticalRadius();
430                final double bottomLeftHorizontalRadius = radii.isBottomLeftHorizontalRadiusAsPercentage() ?
431                        width * radii.getBottomLeftHorizontalRadius() : radii.getBottomLeftHorizontalRadius();
432
433                final double t = fillTop + (Math.max(topLeftVerticalRadius, topRightVerticalRadius) / 2);
434                final double r = fillRight + (Math.max(topRightHorizontalRadius, bottomRightHorizontalRadius) / 2);
435                final double b = fillBottom + (Math.max(bottomLeftVerticalRadius, bottomRightVerticalRadius) / 2);
436                final double l = fillLeft + (Math.max(topLeftHorizontalRadius, bottomLeftHorizontalRadius) / 2);
437                if (Double.isNaN(opaqueRegionTop)) {
438                    // This only happens for the first opaque fill we encounter
439                    opaqueRegionTop = t;
440                    opaqueRegionRight = r;
441                    opaqueRegionBottom = b;
442                    opaqueRegionLeft = l;
443                } else {
444                    final boolean largerTop = t >= opaqueRegionTop;
445                    final boolean largerRight = r >= opaqueRegionRight;
446                    final boolean largerBottom = b >= opaqueRegionBottom;
447                    final boolean largerLeft = l >= opaqueRegionLeft;
448                    if (largerTop && largerRight && largerBottom && largerLeft) {
449                        // The new fill is completely contained within the existing rect, so no change
450                        continue;
451                    } else if (!largerTop && !largerRight && !largerBottom && !largerLeft) {
452                        // The new fill completely contains the existing rect, so use these
453                        // new values for our opaque region
454                        opaqueRegionTop = fillTop;
455                        opaqueRegionRight = fillRight;
456                        opaqueRegionBottom = fillBottom;
457                        opaqueRegionLeft = fillLeft;
458                    } else if (l == opaqueRegionLeft && r == opaqueRegionRight) {
459                        // The left and right insets are the same between the two rects, so just pick
460                        // the smallest top and bottom
461                        opaqueRegionTop = Math.min(t, opaqueRegionTop);
462                        opaqueRegionBottom = Math.min(b, opaqueRegionBottom);
463                    } else if (t == opaqueRegionTop && b == opaqueRegionBottom) {
464                        // The top and bottom are the same between the two rects so just pick
465                        // the smallest left and right
466                        opaqueRegionLeft = Math.min(l, opaqueRegionLeft);
467                        opaqueRegionRight = Math.min(r, opaqueRegionRight);
468                    } else {
469                        // They are disjoint or overlap in some other manner. So we will just
470                        // ignore this region.
471                        continue;
472                    }
473                }
474            }
475        }
476
477        trbl[0] = opaqueRegionTop;
478        trbl[1] = opaqueRegionRight;
479        trbl[2] = opaqueRegionBottom;
480        trbl[3] = opaqueRegionLeft;
481    }
482
483    /**
484     * @inheritDoc
485     */
486    @Override public boolean equals(Object o) {
487        if (this == o) return true;
488        if (o == null || getClass() != o.getClass()) return false;
489        Background that = (Background) o;
490        // Because the hash is cached, this can be a very fast check
491        if (hash != that.hash) return false;
492        if (!fills.equals(that.fills)) return false;
493        if (!images.equals(that.images)) return false;
494
495        return true;
496    }
497
498    /**
499     * @inheritDoc
500     */
501    @Override public int hashCode() {
502        return hash;
503    }
504}