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}