/* * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.layout; import javafx.geometry.Insets; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.StrokeType; /** * Defines the stroke to use on a Border for styling a Region. The * stroke is a vector-based rendering that outlines the border area. * It can be inset (or outset) from the Region's edge, and the values * of the stroke are taken into account when computing the Region's * insets (for defining the content area). The stroke visuals are * not used when any BorderImages are in use. */ public class BorderStroke { /** * The default insets when "thin" is specified. */ public static final BorderWidths THIN = new BorderWidths(1); /** * The default insets when "medium" is specified */ public static final BorderWidths MEDIUM = new BorderWidths(3); /** * The default insets when "thick" is specified */ public static final BorderWidths THICK = new BorderWidths(3); /** * The default Insets to be used with a BorderStroke that does not * otherwise define any. */ public static final BorderWidths DEFAULT_WIDTHS = THIN; /** * Defines the fill of top side of this border. * * @defaultValue black */ final Paint topStroke; public final Paint getTopStroke() { return topStroke; } // TODO: The spec says the default color is "currentColor", which appears to mean // by default the color is "inherit". So we should file a JIRA on this so that // we use inherit. But first I'd like a performance analysis. /** * Defines the fill of right side of this border. If {@code null} then the * topFill is used. * * @defaultValue null = same as topFill */ final Paint rightStroke; public final Paint getRightStroke() { return rightStroke; } /** * Defines the fill of bottom side of this border. If {@code null} then the * topFill is used. * * @defaultValue null = same as topFill */ final Paint bottomStroke; public final Paint getBottomStroke() { return bottomStroke; } /** * Defines the fill of left side of this border. If {@code null} then the * rightFill is used. * * @defaultValue null = same sa rightFill */ final Paint leftStroke; public final Paint getLeftStroke() { return leftStroke; } /** * Defines the style of top side of this border. * * @defaultValue none */ final BorderStrokeStyle topStyle; public final BorderStrokeStyle getTopStyle() { return topStyle; } /** * Defines the style of right side of this border. If {@code null} then * topStyle is used; * * @defaultValue null = same as topStyle */ final BorderStrokeStyle rightStyle; public final BorderStrokeStyle getRightStyle() { return rightStyle; } /** * Defines the style of bottom side of this border. If {@code null} then * topStyle is used; Use BorderStyle.NONE to set the border to * have no border style. * * @defaultValue null = same as topStyle */ final BorderStrokeStyle bottomStyle; public final BorderStrokeStyle getBottomStyle() { return bottomStyle; } /** * Defines the style of left side of this border. If {@code null} then * rightStyle is used. Use BorderStyle.NONE to set the border to * have no border style. * * @defaultValue null = same as rightStyle */ final BorderStrokeStyle leftStyle; public final BorderStrokeStyle getLeftStyle() { return leftStyle; } /** * Defines the thickness of each side of the BorderStroke. This will never * be null, and defaults to DEFAULT_WIDTHS. */ final BorderWidths widths; public final BorderWidths getWidths() { return widths; } /** * Defines the insets of each side of the BorderStroke. This will never * be null, and defaults to EMPTY. */ final Insets insets; public final Insets getInsets() { return insets; } // These two are used by Border to compute the insets and outsets of the border final Insets innerEdge; final Insets outerEdge; /** * Defines the radii for each corner of this BorderStroke. This will never * be null, and defaults to BorderRadii.EMPTY. */ private final BorderRadii radii; public final BorderRadii getRadii() { return radii; } /** * A cached hash code */ private int hash = 0; /** * Creates a new BorderStroke. * * @param stroke The stroke to use for all sides. If null, we default to Color.BLACK. * @param style The style to use for all sides. If null, we default to BorderStrokeStyle.NONE * @param radii The radii to use. If null, we default to BorderRadii.EMPTY * @param widths The widths to use. If null, we default to DEFAULT_WIDTHS */ public BorderStroke(Paint stroke, BorderStrokeStyle style, BorderRadii radii, BorderWidths widths) { this.leftStroke = this.topStroke = this.rightStroke = this.bottomStroke = stroke == null ? Color.BLACK : stroke; this.topStyle = this.rightStyle = this.bottomStyle = this.leftStyle = style == null ? BorderStrokeStyle.NONE : style; this.radii = radii == null ? BorderRadii.EMPTY : radii; this.widths = widths == null ? DEFAULT_WIDTHS : widths; this.insets = Insets.EMPTY; StrokeType type = this.topStyle.getType(); double inside, outside; if (type == StrokeType.OUTSIDE) { outside = this.widths.getTop(); inside = 0; } else if (type == StrokeType.CENTERED) { outside = inside = this.getWidths().getTop() / 2.0; } else if (type == StrokeType.INSIDE) { outside = 0; inside = this.widths.getTop(); } else { throw new AssertionError("Unexpected Stroke Type"); } innerEdge = new Insets(inside); outerEdge = new Insets(outside); } /** * Create a new BorderStroke, specifying all construction parameters. * * @param topStroke The fill to use on the top. If null, defaults to BLACK. * @param rightStroke The fill to use on the right. If null, defaults to the same value as topStroke * @param bottomStroke The fill to use on the bottom. If null, defaults to the same value as bottomStroke * @param leftStroke The fill to use on the left. If null, defaults to the same value as rightStroke * @param topStyle The style to use on the top. If null, defaults to BorderStrokeStyle.NONE * @param rightStyle The style to use on the right. If null, defaults to the same value as topStyle * @param bottomStyle The style to use on the bottom. If null, defaults to the same value as topStyle * @param leftStyle The style to use on the left. If null, defaults to the same value as rightStyle * @param radii The radii. If null, we default to square corners by using BorderRadii.EMPTY * @param widths The thickness of each side. If null, we default to DEFAULT_WIDTHS. * @param insets The insets indicating where to draw the border relative to the region edges. */ public BorderStroke( Paint topStroke, Paint rightStroke, Paint bottomStroke, Paint leftStroke, BorderStrokeStyle topStyle, BorderStrokeStyle rightStyle, BorderStrokeStyle bottomStyle, BorderStrokeStyle leftStyle, BorderRadii radii, BorderWidths widths, Insets insets) { this.topStroke = topStroke == null ? Color.BLACK : topStroke; this.rightStroke = rightStroke == null ? this.topStroke : rightStroke; this.bottomStroke = bottomStroke == null ? this.topStroke : bottomStroke; this.leftStroke = leftStroke == null ? this.rightStroke : leftStroke; this.topStyle = topStyle == null ? BorderStrokeStyle.NONE : topStyle; this.rightStyle = rightStyle == null ? this.topStyle : rightStyle; this.bottomStyle = bottomStyle == null ? this.topStyle : bottomStyle; this.leftStyle = leftStyle == null ? this.rightStyle : leftStyle; this.radii = radii == null ? BorderRadii.EMPTY : radii; this.widths = widths == null ? DEFAULT_WIDTHS : widths; this.insets = insets == null ? Insets.EMPTY : insets; innerEdge = new Insets( computeInside(this.topStyle.getType(), this.widths.getTop()), computeInside(this.rightStyle.getType(), this.widths.getRight()), computeInside(this.bottomStyle.getType(), this.widths.getBottom()), computeInside(this.leftStyle.getType(), this.widths.getLeft()) ); outerEdge = new Insets( computeOutside(this.topStyle.getType(), this.widths.getTop()), computeOutside(this.rightStyle.getType(), this.widths.getRight()), computeOutside(this.bottomStyle.getType(), this.widths.getBottom()), computeOutside(this.leftStyle.getType(), this.widths.getLeft()) ); } private double computeInside(StrokeType type, double width) { if (type == StrokeType.OUTSIDE) { return 0; } else if (type == StrokeType.CENTERED) { return this.getWidths().getTop() / 2.0; } else if (type == StrokeType.INSIDE) { return this.widths.getTop(); } else { throw new AssertionError("Unexpected Stroke Type"); } } private double computeOutside(StrokeType type, double width) { double inside, outside; if (type == StrokeType.OUTSIDE) { return this.widths.getTop(); } else if (type == StrokeType.CENTERED) { return this.getWidths().getTop() / 2.0; } else if (type == StrokeType.INSIDE) { return 0; } else { throw new AssertionError("Unexpected Stroke Type"); } } /** * @inheritDoc */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (hashCode() != o.hashCode()) return false; BorderStroke that = (BorderStroke) o; if (!bottomStroke.equals(that.bottomStroke)) return false; if (!bottomStyle.equals(that.bottomStyle)) return false; if (!leftStroke.equals(that.leftStroke)) return false; if (!leftStyle.equals(that.leftStyle)) return false; if (!radii.equals(that.radii)) return false; if (!rightStroke.equals(that.rightStroke)) return false; if (!rightStyle.equals(that.rightStyle)) return false; if (!topStroke.equals(that.topStroke)) return false; if (!topStyle.equals(that.topStyle)) return false; if (!widths.equals(that.widths)) return false; if (!insets.equals(that.insets)) return false; return true; } /** * @inheritDoc */ @Override public int hashCode() { if (hash == 0) { // Please note: I have to compute into a temporary result variable // before making the final assignment, or the hash code could // come out wrong in a multi-threaded scenario. int result; result = topStroke.hashCode(); result = 31 * result + rightStroke.hashCode(); result = 31 * result + bottomStroke.hashCode(); result = 31 * result + leftStroke.hashCode(); result = 31 * result + topStyle.hashCode(); result = 31 * result + rightStyle.hashCode(); result = 31 * result + bottomStyle.hashCode(); result = 31 * result + leftStyle.hashCode(); result = 31 * result + widths.hashCode(); result = 31 * result + radii.hashCode(); result = 31 * result + insets.hashCode(); hash = result; } return hash; } }