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}