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.paint.Paint;
034import com.sun.javafx.UnmodifiableArrayList;
035import javafx.css.CssMetaData;
036import com.sun.javafx.css.SubCssMetaData;
037import com.sun.javafx.css.converters.InsetsConverter;
038import com.sun.javafx.css.converters.URLConverter;
039import com.sun.javafx.scene.layout.region.BorderImageSlices;
040import com.sun.javafx.scene.layout.region.BorderImageWidthConverter;
041import com.sun.javafx.scene.layout.region.LayeredBorderPaintConverter;
042import com.sun.javafx.scene.layout.region.LayeredBorderStyleConverter;
043import com.sun.javafx.scene.layout.region.Margins;
044import com.sun.javafx.scene.layout.region.RepeatStruct;
045import com.sun.javafx.scene.layout.region.RepeatStructConverter;
046import com.sun.javafx.scene.layout.region.SliceSequenceConverter;
047import javafx.css.Styleable;
048
049/**
050 * The Border of a {@link Region}. A Border is an immutable object which
051 * encapsulates the entire set of data required to render the border
052 * of a Region. Because this class is immutable, you can freely reuse the same
053 * Border on many different Regions. Please refer to
054 * {@link ../doc-files/cssref.html JavaFX CSS Reference} for a complete description
055 * of the CSS rules for styling the border of a Region.
056 * <p/>
057 * Every Border is comprised of {@link #getStrokes() strokes} and / or
058 * {@link #getImages() images}. Neither list will ever be null, but either or
059 * both may be empty. When rendering, if no images are specified or no
060 * image succeeds in loading, then all strokes will be rendered in order.
061 * If any image is specified and succeeds in loading, then no strokes will
062 * be drawn, although they will still contribute to the {@link #getInsets() insets}
063 * and {@link #getOutsets() outsets} of the Border.
064 * <p/>
065 * The Border's {@link #getOutsets() outsets} define any extension of the drawing area of a Region
066 * which is necessary to account for all border drawing and positioning. These outsets are defined
067 * by both the {@link BorderStroke}s and {@link BorderImage}s specified on this Border.
068 * Outsets are strictly non-negative.
069 * <p/>
070 * {@link #getInsets()} are used to define the inner-most edge of all of the borders. It also is
071 * always strictly non-negative. The Region uses the insets of the {@link Background} and Border
072 * and the {@link javafx.scene.layout.Region#getPadding() Region's padding} to determine the
073 * Region {@link javafx.scene.layout.Region#getInsets() insets}, which define the content area
074 * for any children of the Region. The outsets of a Border together with the outsets of a Background
075 * and the width and height of the Region define the geometric bounds of the Region (which in
076 * turn contribute to the {@code layoutBounds}, {@code boundsInLocal}, and {@code boundsInParent}).
077 * <p/>
078 * A Border is most often used in cases where you want to skin the Region with an image,
079 * often used in conjunction with 9-patch scaling techniques. In such cases, you may
080 * also specify a stroked border which is only used when the image fails to load for some
081 * reason.
082 *
083 * @since JavaFX 8
084 */
085@SuppressWarnings("unchecked")
086public final class Border {
087    static final CssMetaData<Node,Paint[]> BORDER_COLOR =
088            new SubCssMetaData<Paint[]>("-fx-border-color",
089                    LayeredBorderPaintConverter.getInstance());
090
091    static final CssMetaData<Node,BorderStrokeStyle[][]> BORDER_STYLE =
092            new SubCssMetaData<BorderStrokeStyle[][]>("-fx-border-style",
093                    LayeredBorderStyleConverter.getInstance());
094
095    static final CssMetaData<Node,Margins[]> BORDER_WIDTH =
096            new SubCssMetaData<Margins[]> ("-fx-border-width",
097                    Margins.SequenceConverter.getInstance());
098
099    static final CssMetaData<Node,Margins[]> BORDER_RADIUS =
100            new SubCssMetaData<Margins[]>("-fx-border-radius",
101                    Margins.SequenceConverter.getInstance());
102
103    static final CssMetaData<Node,Insets[]> BORDER_INSETS =
104            new SubCssMetaData<Insets[]>("-fx-border-insets",
105                    InsetsConverter.SequenceConverter.getInstance());
106
107    static final CssMetaData<Node,String[]> BORDER_IMAGE_SOURCE =
108            new SubCssMetaData<String[]>("-fx-border-image-source",
109                    URLConverter.SequenceConverter.getInstance());
110
111    static final CssMetaData<Node,RepeatStruct[]> BORDER_IMAGE_REPEAT =
112            new SubCssMetaData<RepeatStruct[]>("-fx-border-image-repeat",
113                    RepeatStructConverter.getInstance(),
114                    new RepeatStruct[] { new RepeatStruct(BackgroundRepeat.REPEAT, BackgroundRepeat.REPEAT) });
115
116    static final CssMetaData<Node,BorderImageSlices[]> BORDER_IMAGE_SLICE =
117            new SubCssMetaData<BorderImageSlices[]> ("-fx-border-image-slice",
118                    SliceSequenceConverter.getInstance(),
119                    new BorderImageSlices[] { BorderImageSlices.DEFAULT});
120
121    static final CssMetaData<Node,BorderWidths[]> BORDER_IMAGE_WIDTH =
122            new SubCssMetaData<BorderWidths[]>("-fx-border-image-width",
123                    BorderImageWidthConverter.getInstance(),
124                    new BorderWidths[] { BorderWidths.DEFAULT });
125
126    static final CssMetaData<Node,Insets[]> BORDER_IMAGE_INSETS =
127            new SubCssMetaData<Insets[]>("-fx-border-image-insets",
128                    InsetsConverter.SequenceConverter.getInstance(),
129                    new Insets[] {Insets.EMPTY});
130
131    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES =
132            (List<CssMetaData<? extends Styleable, ?>>) (List) Collections.unmodifiableList(
133                    // Unchecked!
134                    Arrays.asList(BORDER_COLOR,
135                            BORDER_STYLE,
136                            BORDER_WIDTH,
137                            BORDER_RADIUS,
138                            BORDER_INSETS,
139                            BORDER_IMAGE_SOURCE,
140                            BORDER_IMAGE_REPEAT,
141                            BORDER_IMAGE_SLICE,
142                            BORDER_IMAGE_WIDTH,
143                            BORDER_IMAGE_INSETS));
144
145    /**
146     * @return The CssMetaData associated with this class, which may include the
147     * CssMetaData of its super classes.
148     */
149    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
150        return STYLEABLES;
151    }
152
153    /**
154     * An empty Border, useful to use instead of null.
155     */
156    public static final Border EMPTY = new Border((BorderStroke[])null, null);
157
158    /**
159     * The list of BorderStrokes which together define the stroked portion
160     * of this Border. This List is unmodifiable and immutable. It
161     * will never be null. It will never contain any null elements.
162     */
163    final List<BorderStroke> strokes;
164    public final List<BorderStroke> getStrokes() { return strokes; }
165
166    /**
167     * The list of BorderImages which together define the images to use
168     * instead of stroke for this Border. If this list is specified and
169     * at least one image within it succeeds in loading, then any specified
170     * {@link #getStrokes strokes} are not drawn. If this list is null or no images
171     * succeeded in loading, then any specified {@code strokes} are drawn.
172     * <p>
173     * This List is unmodifiable and immutable. It will never be null.
174     * It will never contain any null elements.
175     */
176    final List<BorderImage> images;
177    public final List<BorderImage> getImages() { return images; }
178
179    /**
180     * The outsets of the border define the outer-most edge of the border to be drawn.
181     * The values in these outsets are strictly non-negative.
182     */
183    final Insets outsets;
184    public final Insets getOutsets() { return outsets; }
185
186    /**
187     * The insets define the distance from the edge of the Region to the inner-most edge
188     * of the border, if that distance is non-negative. The values in these outsets
189     * are strictly non-negative.
190     */
191    final Insets insets;
192    public final Insets getInsets() { return insets; }
193
194    /**
195     * Gets whether the Border is empty. It is empty if there are no strokes or images.
196     * @return true if the Border is empty, false otherwise.
197     */
198    public final boolean isEmpty() {
199        return strokes.isEmpty() && images.isEmpty();
200    }
201
202    /**
203     * The cached hash code computation for the Border. One very big
204     * reason for making Border immutable was to make it possible to
205     * cache and reuse the same Border instance for multiple
206     * Regions. To enable efficient caching, we cache the hash.
207     */
208    private final int hash;
209
210    /**
211     * Creates a new Border by supplying an array of BorderStrokes.
212     * This array may be null, or may contain null values. Any null values
213     * will be ignored and will not contribute to the {@link #getStrokes() strokes}
214     * or {@link #getOutsets() outsets} or {@link #getInsets() insets}.
215     *
216     * @param strokes   The strokes. This may be null, and may contain nulls. Any
217     *                  contained nulls are filtered out and not included in the
218     *                  final List of strokes. A null array becomes an empty List.
219     *                  If both strokes and images are specified, and if any one
220     *                  of the images specified succeeds in loading, then no
221     *                  strokes are shown. In this way, strokes can be defined as
222     *                  a fallback in the case that an image failed to load.
223     */
224    public Border(BorderStroke... strokes) {
225        this(strokes, null);
226    }
227
228    /**
229     * Creates a new Border by supplying an array of BorderImages.
230     * This array may be null, or may contain null values. Any null values
231     * will be ignored and will not contribute to the {@link #getImages() images}
232     * or {@link #getOutsets() outsets} or {@link #getInsets() insets}.
233     *
234     * @param images    The images. This may be null, and may contain nulls. Any
235     *                  contained nulls are filtered out and not included in the
236     *                  final List of images. A null array becomes an empty List.
237     */
238    public Border(BorderImage... images) {
239        this(null, images);
240    }
241
242    /**
243     * Creates a new Border by supplying a List of BorderStrokes and BorderImages.
244     * These Lists may be null, or may contain null values. Any null values
245     * will be ignored and will not contribute to the {@link #getStrokes() strokes}
246     * or {@link #getImages() images}, {@link #getOutsets() outsets}, or
247     * {@link #getInsets() insets}.
248     *
249     * @param strokes   The strokes. This may be null, and may contain nulls. Any
250     *                  contained nulls are filtered out and not included in the
251     *                  final List of strokes. A null array becomes an empty List.
252     *                  If both strokes and images are specified, and if any one
253     *                  of the images specified succeeds in loading, then no
254     *                  strokes are shown. In this way, strokes can be defined as
255     *                  a fallback in the case that an image failed to load.
256     * @param images    The images. This may be null, and may contain nulls. Any
257     *                  contained nulls are filtered out and not included in the
258     *                  final List of images. A null array becomes an empty List.
259     */
260    public Border(List<BorderStroke> strokes, List<BorderImage> images) {
261        // NOTE: This constructor had to be supplied in order to cause a Builder
262        // to be auto-generated, because otherwise the types of the strokes and images
263        // properties didn't match the types of the array based constructor parameters.
264        // So a Builder will use this constructor, while the CSS engine uses the
265        // array based constructor (for speed).
266        this(strokes == null ? null : strokes.toArray(new BorderStroke[strokes.size()]),
267             images == null ? null : images.toArray(new BorderImage[images.size()]));
268    }
269
270    /**
271     * Creates a new Border by supplying an array of BorderStrokes and BorderImages.
272     * These arrays may be null, or may contain null values. Any null values
273     * will be ignored and will not contribute to the {@link #getStrokes() strokes}
274     * or {@link #getImages() images}, {@link #getOutsets() outsets}, or
275     * {@link #getInsets() insets}.
276     *
277     * @param strokes   The strokes. This may be null, and may contain nulls. Any
278     *                  contained nulls are filtered out and not included in the
279     *                  final List of strokes. A null array becomes an empty List.
280     *                  If both strokes and images are specified, and if any one
281     *                  of the images specified succeeds in loading, then no
282     *                  strokes are shown. In this way, strokes can be defined as
283     *                  a fallback in the case that an image failed to load.
284     * @param images    The images. This may be null, and may contain nulls. Any
285     *                  contained nulls are filtered out and not included in the
286     *                  final List of images. A null array becomes an empty List.
287     */
288    public Border(BorderStroke[] strokes, BorderImage[] images) {
289        double innerTop = 0, innerRight = 0, innerBottom = 0, innerLeft = 0;
290        double outerTop = 0, outerRight = 0, outerBottom = 0, outerLeft = 0;
291
292        if (strokes == null || strokes.length == 0) {
293            this.strokes = Collections.emptyList();
294        } else {
295            final BorderStroke[] noNulls = new BorderStroke[strokes.length];
296            int size = 0;
297            for (int i=0; i<strokes.length; i++) {
298                final BorderStroke stroke = strokes[i];
299                if (stroke != null) {
300                    noNulls[size++] = stroke;
301
302                    // Calculate the insets and outsets. "insets" are the distance
303                    // from the edge of the region to the inmost edge of the inmost border.
304                    // Outsets are the distance from the edge of the region out towards the
305                    // outer-most edge of the outer-most border.
306                    final double strokeInnerTop = stroke.innerEdge.getTop();
307                    final double strokeInnerRight = stroke.innerEdge.getRight();
308                    final double strokeInnerBottom = stroke.innerEdge.getBottom();
309                    final double strokeInnerLeft = stroke.innerEdge.getLeft();
310
311                    innerTop = innerTop >= strokeInnerTop ? innerTop : strokeInnerTop;
312                    innerRight = innerRight >= strokeInnerRight? innerRight : strokeInnerRight;
313                    innerBottom = innerBottom >= strokeInnerBottom ? innerBottom : strokeInnerBottom;
314                    innerLeft = innerLeft >= strokeInnerLeft ? innerLeft : strokeInnerLeft;
315
316                    final double strokeOuterTop = stroke.outerEdge.getTop();
317                    final double strokeOuterRight = stroke.outerEdge.getRight();
318                    final double strokeOuterBottom = stroke.outerEdge.getBottom();
319                    final double strokeOuterLeft = stroke.outerEdge.getLeft();
320
321                    outerTop = outerTop >= strokeOuterTop ? outerTop : strokeOuterTop;
322                    outerRight = outerRight >= strokeOuterRight? outerRight : strokeOuterRight;
323                    outerBottom = outerBottom >= strokeOuterBottom ? outerBottom : strokeOuterBottom;
324                    outerLeft = outerLeft >= strokeOuterLeft ? outerLeft : strokeOuterLeft;
325                }
326            }
327            this.strokes = new UnmodifiableArrayList<BorderStroke>(noNulls, size);
328        }
329
330        if (images == null || images.length == 0) {
331            this.images = Collections.emptyList();
332        } else {
333            final BorderImage[] noNulls = new BorderImage[images.length];
334            int size = 0;
335            for (int i=0; i<images.length; i++) {
336                final BorderImage image = images[i];
337                if (image != null){
338                    noNulls[size++] = image;
339
340                    // The Image width + insets may contribute to the insets / outsets of
341                    // this border.
342                    final double imageInnerTop = image.innerEdge.getTop();
343                    final double imageInnerRight = image.innerEdge.getRight();
344                    final double imageInnerBottom = image.innerEdge.getBottom();
345                    final double imageInnerLeft = image.innerEdge.getLeft();
346
347                    innerTop = innerTop >= imageInnerTop ? innerTop : imageInnerTop;
348                    innerRight = innerRight >= imageInnerRight? innerRight : imageInnerRight;
349                    innerBottom = innerBottom >= imageInnerBottom ? innerBottom : imageInnerBottom;
350                    innerLeft = innerLeft >= imageInnerLeft ? innerLeft : imageInnerLeft;
351
352                    final double imageOuterTop = image.outerEdge.getTop();
353                    final double imageOuterRight = image.outerEdge.getRight();
354                    final double imageOuterBottom = image.outerEdge.getBottom();
355                    final double imageOuterLeft = image.outerEdge.getLeft();
356
357                    outerTop = outerTop >= imageOuterTop ? outerTop : imageOuterTop;
358                    outerRight = outerRight >= imageOuterRight? outerRight : imageOuterRight;
359                    outerBottom = outerBottom >= imageOuterBottom ? outerBottom : imageOuterBottom;
360                    outerLeft = outerLeft >= imageOuterLeft ? outerLeft : imageOuterLeft;
361                }
362            }
363            this.images = new UnmodifiableArrayList<BorderImage>(noNulls, size);
364        }
365
366        // Both the BorderStroke and BorderImage class make sure to return the outsets
367        // and insets in the right way, such that we don't have to worry about adjusting
368        // the sign, etc, unlike in the Background implementation.
369        outsets = new Insets(outerTop, outerRight, outerBottom, outerLeft);
370        insets = new Insets(innerTop, innerRight, innerBottom, innerLeft);
371
372        // Pre-compute the hash code. NOTE: all variables are prefixed with "this" so that we
373        // do not accidentally compute the hash based on the constructor arguments rather than
374        // based on the fields themselves!
375        int result = this.strokes.hashCode();
376        result = 31 * result + this.images.hashCode();
377        hash = result;
378    }
379
380    /**
381     * @inheritDoc
382     */
383    @Override public boolean equals(Object o) {
384        if (this == o) return true;
385        if (o == null || getClass() != o.getClass()) return false;
386        Border border = (Border) o;
387        if (this.hash != border.hash) return false;
388
389        if (!images.equals(border.images)) return false;
390        if (!strokes.equals(border.strokes)) return false;
391
392        return true;
393    }
394
395    /**
396     * @inheritDoc
397     */
398    @Override public int hashCode() {
399        return hash;
400    }
401}