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 javafx.geometry.Insets;
029import javafx.scene.paint.Color;
030import javafx.scene.paint.Paint;
031import javafx.scene.shape.StrokeType;
032
033/**
034 * Defines the stroke to use on a Border for styling a Region. The
035 * stroke is a vector-based rendering that outlines the border area.
036 * It can be inset (or outset) from the Region's edge, and the values
037 * of the stroke are taken into account when computing the Region's
038 * insets (for defining the content area). The stroke visuals are
039 * not used when any BorderImages are in use.
040 * <p/>
041 * When applied to a Region with a defined shape, the border width
042 * and stroking information for the {@code top} is used, while the
043 * other attributes are ignored.
044 */
045public class BorderStroke {
046    /**
047     * The default insets when "thin" is specified.
048     */
049    public static final BorderWidths THIN = new BorderWidths(1);
050
051    /**
052     * The default insets when "medium" is specified
053     */
054    public static final BorderWidths MEDIUM = new BorderWidths(3);
055
056    /**
057     * The default insets when "thick" is specified
058     */
059    public static final BorderWidths THICK = new BorderWidths(5);
060
061    /**
062     * The default Insets to be used with a BorderStroke that does not
063     * otherwise define any.
064     */
065    public static final BorderWidths DEFAULT_WIDTHS = THIN;
066
067    /**
068     * Defines the fill of top side of this border.
069     *
070     * @defaultValue black
071     */
072    final Paint topStroke;
073    public final Paint getTopStroke() { return topStroke; }
074    // TODO: The spec says the default color is "currentColor", which appears to mean
075    // by default the color is "inherit". So we should file a JIRA on this so that
076    // we use inherit. But first I'd like a performance analysis.
077
078    /**
079     * Defines the fill of right side of this border. If {@code null} then the
080     * topFill is used.
081     *
082     * @defaultValue null = same as topFill
083     */
084    final Paint rightStroke;
085    public final Paint getRightStroke() { return rightStroke; }
086
087    /**
088     * Defines the fill of bottom side of this border. If {@code null} then the
089     * topFill is used.
090     *
091     * @defaultValue null = same as topFill
092     */
093    final Paint bottomStroke;
094    public final Paint getBottomStroke() { return bottomStroke; }
095
096    /**
097     * Defines the fill of left side of this border. If {@code null} then the
098     * rightFill is used.
099     *
100     * @defaultValue null = same sa rightFill
101     */
102    final Paint leftStroke;
103    public final Paint getLeftStroke() { return leftStroke; }
104
105    /**
106     * Defines the style of top side of this border.
107     *
108     * @defaultValue none
109     */
110    final BorderStrokeStyle topStyle;
111    public final BorderStrokeStyle getTopStyle() { return topStyle; }
112
113    /**
114     * Defines the style of right side of this border. If {@code null} then
115     * topStyle is used;
116     *
117     * @defaultValue null = same as topStyle
118     */
119    final BorderStrokeStyle rightStyle;
120    public final BorderStrokeStyle getRightStyle() { return rightStyle; }
121
122    /**
123     * Defines the style of bottom side of this border. If {@code null} then
124     * topStyle is used;  Use BorderStyle.NONE to set the border to
125     * have no border style.
126     *
127     * @defaultValue null = same as topStyle
128     */
129    final BorderStrokeStyle bottomStyle;
130    public final BorderStrokeStyle getBottomStyle() { return bottomStyle; }
131
132    /**
133     * Defines the style of left side of this border. If {@code null} then
134     * rightStyle is used. Use BorderStyle.NONE to set the border to
135     * have no border style.
136     *
137     * @defaultValue null = same as rightStyle
138     */
139    final BorderStrokeStyle leftStyle;
140    public final BorderStrokeStyle getLeftStyle() { return leftStyle; }
141
142    /**
143     * Defines the thickness of each side of the BorderStroke. This will never
144     * be null, and defaults to DEFAULT_WIDTHS.
145     */
146    final BorderWidths widths;
147    public final BorderWidths getWidths() { return widths; }
148
149    /**
150     * Defines the insets of each side of the BorderStroke. This will never
151     * be null, and defaults to EMPTY.
152     */
153    final Insets insets;
154    public final Insets getInsets() { return insets; }
155
156    // These two are used by Border to compute the insets and outsets of the border
157    final Insets innerEdge;
158    final Insets outerEdge;
159    public void solid(Region region) {
160        region.setStyle("-fx-background-color: lightgrey;" +
161                        "-fx-border-style: solid;" +
162                        "-fx-border-width: 5;" +
163                        "-fx-border-color: red;");
164    }
165
166    /**
167     * Defines the radii for each corner of this BorderStroke. This will never
168     * be null, and defaults to CornerRadii.EMPTY.
169     * TODO I should change CornerRadii to be 4 properties, one for each corner,
170     * where each corner is a horizontal / vertical radius! I think that would
171     * be cleaner.
172     */
173    private final CornerRadii radii;
174    public final CornerRadii getRadii() { return radii; }
175
176    private final boolean strokeUniform;
177    public final boolean isStrokeUniform() { return strokeUniform; }
178
179    /**
180     * A cached hash code
181     */
182    private final int hash;
183
184    /**
185     * Creates a new BorderStroke.
186     *
187     * @param stroke    The stroke to use for all sides. If null, we default to Color.BLACK.
188     * @param style     The style to use for all sides. If null, we default to BorderStrokeStyle.NONE
189     * @param radii     The radii to use. If null, we default to CornerRadii.EMPTY
190     * @param widths    The widths to use. If null, we default to DEFAULT_WIDTHS
191     */
192    public BorderStroke(Paint stroke, BorderStrokeStyle style, CornerRadii radii, BorderWidths widths) {
193        // TODO: Note that we default to THIN, not to MEDIUM as the CSS spec says. So it will be
194        // up to our CSS converter code to make sure the default is MEDIUM in that case.
195        this.leftStroke = this.topStroke = this.rightStroke = this.bottomStroke = stroke == null ? Color.BLACK : stroke;
196        this.topStyle = this.rightStyle = this.bottomStyle = this.leftStyle = style == null ? BorderStrokeStyle.NONE : style;
197        this.radii = radii == null ? CornerRadii.EMPTY : radii;
198        this.widths = widths == null ? DEFAULT_WIDTHS : widths;
199        this.insets = Insets.EMPTY;
200
201        // TODO: Our inside / outside should be 0 when stroke type is NONE in that dimension!
202        // In fact, we could adjust the widths in such a case so that when you ask for the
203        // widths, you get 0 instead of whatever was specified. See 4.3 of the CSS Spec.
204        StrokeType type = this.topStyle.getType();
205        double inside, outside;
206        if (type == StrokeType.OUTSIDE) {
207            outside = this.widths.getTop();
208            inside = 0;
209        } else if (type == StrokeType.CENTERED) {
210            final double width = this.getWidths().getTop();
211            outside = inside = width / 2.0;
212        } else if (type == StrokeType.INSIDE) {
213            outside = 0;
214            inside = this.widths.getTop();
215        } else {
216            throw new AssertionError("Unexpected Stroke Type");
217        }
218
219        // This constructor enforces that they are all uniform
220        strokeUniform = true;
221
222        // Since insets are empty, don't have to worry about it
223        innerEdge = new Insets(inside);
224        outerEdge = new Insets(outside);
225        this.hash = preComputeHash();
226    }
227
228    /**
229     * Creates a new BorderStroke.
230     *
231     * @param stroke    The stroke to use for all sides. If null, we default to Color.BLACK.
232     * @param style     The style to use for all sides. If null, we default to BorderStrokeStyle.NONE
233     * @param radii     The radii to use. If null, we default to CornerRadii.EMPTY
234     * @param widths    The widths to use. If null, we default to DEFAULT_WIDTHS
235     * @param insets    The insets indicating where to draw the border relative to the region edges.
236     */
237    public BorderStroke(Paint stroke, BorderStrokeStyle style, CornerRadii radii, BorderWidths widths, Insets insets) {
238        this(stroke, stroke, stroke, stroke, style, style, style, style, radii, widths, insets);
239    }
240
241    /**
242     * Create a new BorderStroke, specifying all construction parameters.
243     *
244     * @param topStroke       The fill to use on the top. If null, defaults to BLACK.
245     * @param rightStroke     The fill to use on the right. If null, defaults to the same value as topStroke
246     * @param bottomStroke    The fill to use on the bottom. If null, defaults to the same value as bottomStroke
247     * @param leftStroke      The fill to use on the left. If null, defaults to the same value as rightStroke
248     * @param topStyle        The style to use on the top. If null, defaults to BorderStrokeStyle.NONE
249     * @param rightStyle      The style to use on the right. If null, defaults to the same value as topStyle
250     * @param bottomStyle     The style to use on the bottom. If null, defaults to the same value as topStyle
251     * @param leftStyle       The style to use on the left. If null, defaults to the same value as rightStyle
252     * @param radii           The radii. If null, we default to square corners by using CornerRadii.EMPTY
253     * @param widths          The thickness of each side. If null, we default to DEFAULT_WIDTHS.
254     * @param insets    The insets indicating where to draw the border relative to the region edges.
255     */
256    public BorderStroke(
257            Paint topStroke, Paint rightStroke, Paint bottomStroke, Paint leftStroke,
258            BorderStrokeStyle topStyle, BorderStrokeStyle rightStyle,
259            BorderStrokeStyle bottomStyle, BorderStrokeStyle leftStyle,
260            CornerRadii radii, BorderWidths widths, Insets insets)
261    {
262        this.topStroke = topStroke == null ? Color.BLACK : topStroke;
263        this.rightStroke = rightStroke == null ? this.topStroke : rightStroke;
264        this.bottomStroke = bottomStroke == null ? this.topStroke : bottomStroke;
265        this.leftStroke = leftStroke == null ? this.rightStroke : leftStroke;
266        this.topStyle = topStyle == null ? BorderStrokeStyle.NONE : topStyle;
267        this.rightStyle = rightStyle == null ? this.topStyle : rightStyle;
268        this.bottomStyle = bottomStyle == null ? this.topStyle : bottomStyle;
269        this.leftStyle = leftStyle == null ? this.rightStyle : leftStyle;
270        this.radii = radii == null ? CornerRadii.EMPTY : radii;
271        this.widths = widths == null ? DEFAULT_WIDTHS : widths;
272        this.insets = insets == null ? Insets.EMPTY : insets;
273
274
275        final boolean colorsSame =
276                this.leftStroke.equals(this.topStroke) &&
277                this.leftStroke.equals(this.rightStroke) &&
278                this.leftStroke.equals(this.bottomStroke);
279        final boolean widthsSame =
280                this.widths.left == this.widths.top &&
281                this.widths.left == this.widths.right &&
282                this.widths.left == this.widths.bottom;
283        final boolean stylesSame =
284                this.leftStyle.equals(this.topStyle) &&
285                this.leftStyle.equals(this.rightStyle) &&
286                this.leftStyle.equals(this.bottomStyle);
287
288        strokeUniform = colorsSame && widthsSame && stylesSame;
289
290        // TODO these calculations are not accurate if we are stroking a shape. In such cases, we
291        // need to account for the mitre limit
292
293        innerEdge = new Insets(
294                this.insets.getTop() + computeInside(this.topStyle.getType(), this.widths.getTop()),
295                this.insets.getRight() + computeInside(this.rightStyle.getType(), this.widths.getRight()),
296                this.insets.getBottom() + computeInside(this.bottomStyle.getType(), this.widths.getBottom()),
297                this.insets.getLeft() + computeInside(this.leftStyle.getType(), this.widths.getLeft())
298        );
299        outerEdge = new Insets(
300                Math.max(0, computeOutside(this.topStyle.getType(), this.widths.getTop()) - this.insets.getTop()),
301                Math.max(0, computeOutside(this.rightStyle.getType(), this.widths.getRight()) - this.insets.getRight()),
302                Math.max(0, computeOutside(this.bottomStyle.getType(), this.widths.getBottom())- this.insets.getBottom()),
303                Math.max(0, computeOutside(this.leftStyle.getType(), this.widths.getLeft()) - this.insets.getLeft())
304        );
305        this.hash = preComputeHash();
306    }
307
308    private int preComputeHash() {
309        int result;
310        result = topStroke.hashCode();
311        result = 31 * result + rightStroke.hashCode();
312        result = 31 * result + bottomStroke.hashCode();
313        result = 31 * result + leftStroke.hashCode();
314        result = 31 * result + topStyle.hashCode();
315        result = 31 * result + rightStyle.hashCode();
316        result = 31 * result + bottomStyle.hashCode();
317        result = 31 * result + leftStyle.hashCode();
318        result = 31 * result + widths.hashCode();
319        result = 31 * result + radii.hashCode();
320        result = 31 * result + insets.hashCode();
321        return result;
322    }
323
324    private double computeInside(StrokeType type, double width) {
325        if (type == StrokeType.OUTSIDE) {
326            return 0;
327        } else if (type == StrokeType.CENTERED) {
328            return width / 2.0;
329        } else if (type == StrokeType.INSIDE) {
330            return width;
331        } else {
332            throw new AssertionError("Unexpected Stroke Type");
333        }
334    }
335
336    private double computeOutside(StrokeType type, double width) {
337        if (type == StrokeType.OUTSIDE) {
338            return width;
339        } else if (type == StrokeType.CENTERED) {
340            return width / 2.0;
341        } else if (type == StrokeType.INSIDE) {
342            return 0;
343        } else {
344            throw new AssertionError("Unexpected Stroke Type");
345        }
346    }
347
348    /**
349     * @inheritDoc
350     */
351    @Override public boolean equals(Object o) {
352        if (this == o) return true;
353        if (o == null || getClass() != o.getClass()) return false;
354        BorderStroke that = (BorderStroke) o;
355        if (this.hash != that.hash) return false;
356        if (!bottomStroke.equals(that.bottomStroke)) return false;
357        if (!bottomStyle.equals(that.bottomStyle)) return false;
358        if (!leftStroke.equals(that.leftStroke)) return false;
359        if (!leftStyle.equals(that.leftStyle)) return false;
360        if (!radii.equals(that.radii)) return false;
361        if (!rightStroke.equals(that.rightStroke)) return false;
362        if (!rightStyle.equals(that.rightStyle)) return false;
363        if (!topStroke.equals(that.topStroke)) return false;
364        if (!topStyle.equals(that.topStyle)) return false;
365        if (!widths.equals(that.widths)) return false;
366        if (!insets.equals(that.insets)) return false;
367
368        return true;
369    }
370
371    /**
372     * @inheritDoc
373     */
374    @Override public int hashCode() {
375        return hash;
376    }
377}