1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.layout; 27 28 import com.sun.javafx.util.Utils; 29 import javafx.beans.InvalidationListener; 30 import javafx.beans.property.BooleanProperty; 31 import javafx.beans.property.DoubleProperty; 32 import javafx.beans.property.ObjectProperty; 33 import javafx.beans.property.ReadOnlyDoubleProperty; 34 import javafx.beans.property.ReadOnlyDoubleWrapper; 35 import javafx.beans.property.ReadOnlyObjectProperty; 36 import javafx.beans.property.ReadOnlyObjectPropertyBase; 37 import javafx.beans.value.ChangeListener; 38 import javafx.collections.ObservableList; 39 import javafx.css.CssMetaData; 40 import javafx.css.Styleable; 41 import javafx.css.StyleableBooleanProperty; 42 import javafx.css.StyleableDoubleProperty; 43 import javafx.css.StyleableObjectProperty; 44 import javafx.css.StyleableProperty; 45 import javafx.geometry.BoundingBox; 46 import javafx.geometry.Bounds; 47 import javafx.geometry.HPos; 48 import javafx.geometry.Insets; 49 import javafx.geometry.Orientation; 50 import javafx.geometry.VPos; 51 import javafx.scene.Node; 52 import javafx.scene.Parent; 53 import javafx.scene.image.Image; 54 import javafx.scene.shape.Shape; 55 import javafx.scene.shape.StrokeLineCap; 56 import javafx.scene.shape.StrokeLineJoin; 57 import javafx.scene.shape.StrokeType; 58 import javafx.util.Callback; 59 import java.util.ArrayList; 60 import java.util.Collections; 61 import java.util.Arrays; 62 import java.util.List; 63 import java.util.function.Function; 64 import com.sun.javafx.util.Logging; 65 import com.sun.javafx.util.TempState; 66 import com.sun.javafx.binding.ExpressionHelper; 67 import com.sun.javafx.css.converters.BooleanConverter; 68 import com.sun.javafx.css.converters.InsetsConverter; 69 import com.sun.javafx.css.converters.ShapeConverter; 70 import com.sun.javafx.css.converters.SizeConverter; 71 import com.sun.javafx.geom.BaseBounds; 72 import com.sun.javafx.geom.PickRay; 73 import com.sun.javafx.geom.RectBounds; 74 import com.sun.javafx.geom.Vec2d; 75 import com.sun.javafx.geom.transform.BaseTransform; 76 import com.sun.javafx.scene.DirtyBits; 77 import com.sun.javafx.scene.input.PickResultChooser; 78 import com.sun.javafx.sg.prism.NGNode; 79 import com.sun.javafx.sg.prism.NGRegion; 80 import com.sun.javafx.tk.Toolkit; 81 import sun.util.logging.PlatformLogger; 82 import sun.util.logging.PlatformLogger.Level; 83 84 /** 85 * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers. 86 * It is a resizable Parent node which can be styled from CSS. It can have multiple backgrounds 87 * and borders. It is designed to support as much of the CSS3 specification for backgrounds 88 * and borders as is relevant to JavaFX. 89 * The full specification is available at <a href="http://www.w3.org/TR/2012/CR-css3-background-20120724/">the W3C</a>. 90 * <p/> 91 * Every Region has its layout bounds, which are specified to be (0, 0, width, height). A Region might draw outside 92 * these bounds. The content area of a Region is the area which is occupied for the layout of its children. 93 * This area is, by default, the same as the layout bounds of the Region, but can be modified by either the 94 * properties of a border (either with BorderStrokes or BorderImages), and by padding. The padding can 95 * be negative, such that the content area of a Region might extend beyond the layout bounds of the Region, 96 * but does not affect the layout bounds. 97 * <p/> 98 * A Region has a Background, and a Border, although either or both of these might be empty. The Background 99 * of a Region is made up of zero or more BackgroundFills, and zero or more BackgroundImages. Likewise, the 100 * border of a Region is defined by its Border, which is made up of zero or more BorderStrokes and 101 * zero or more BorderImages. All BackgroundFills are drawn first, followed by BackgroundImages, BorderStrokes, 102 * and finally BorderImages. The content is drawn above all backgrounds and borders. If a BorderImage is 103 * present (and loaded all images properly), then no BorderStrokes are actually drawn, although they are 104 * considered for computing the position of the content area (see the stroke width property of a BorderStroke). 105 * These semantics are in line with the CSS 3 specification. The purpose of these semantics are to allow an 106 * application to specify a fallback BorderStroke to be displayed in the case that an ImageStroke fails to 107 * download or load. 108 * <p/> 109 * By default a Region appears as a Rectangle. A BackgroundFill radii might cause the Rectangle to appear rounded. 110 * This affects not only making the visuals look like a rounded rectangle, but it also causes the picking behavior 111 * of the Region to act like a rounded rectangle, such that locations outside the corner radii are ignored. A 112 * Region can be made to use any shape, however, by specifing the {@code shape} property. If a shape is specified, 113 * then all BackgroundFills, BackgroundImages, and BorderStrokes will be applied to the shape. BorderImages are 114 * not used for Regions which have a shape specified. 115 * <p/> 116 * A Region with a shape 117 * <p/> 118 * Although the layout bounds of a Region are not influenced by any Border or Background, the content area 119 * insets and the picking area of the Region are. The {@code insets} of the Region define the distance 120 * between the edge of the layout bounds and the edge of the content area. For example, if the Region 121 * layout bounds are (x=0, y=0, width=200, height=100), and the insets are (top=10, right=20, bottom=30, left=40), 122 * then the content area bounds will be (x=40, y=10, width=140, height=60). A Region subclass which is laying 123 * out its children should compute and honor these content area bounds. 124 * <p/> 125 * By default a Region inherits the layout behavior of its superclass, {@link Parent}, 126 * which means that it will resize any resizable child nodes to their preferred 127 * size, but will not reposition them. If an application needs more specific 128 * layout behavior, then it should use one of the Region subclasses: 129 * {@link StackPane}, {@link HBox}, {@link VBox}, {@link TilePane}, {@link FlowPane}, 130 * {@link BorderPane}, {@link GridPane}, or {@link AnchorPane}. 131 * <p/> 132 * To implement a more custom layout, a Region subclass must override 133 * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, and 134 * {@link #layoutChildren() layoutChildren}. Note that {@link #layoutChildren() layoutChildren} is called automatically 135 * by the scene graph while executing a top-down layout pass and it should not be invoked directly by the 136 * region subclass. 137 * <p/> 138 * Region subclasses which layout their children will position nodes by setting 139 * {@link #setLayoutX(double) layoutX}/{@link #setLayoutY(double) layoutY} and do not alter 140 * {@link #setTranslateX(double) translateX}/{@link #setTranslateY(double) translateY}, which are reserved for 141 * adjustments and animation. 142 * @since JavaFX 2.0 143 */ 144 public class 145 Region extends Parent { 146 147 /** 148 * Sentinel value which can be passed to a region's 149 * {@link #setMinWidth(double) setMinWidth}, 150 * {@link #setMinHeight(double) setMinHeight}, 151 * {@link #setMaxWidth(double) setMaxWidth} or 152 * {@link #setMaxHeight(double) setMaxHeight} 153 * methods to indicate that the preferred dimension should be used for that max and/or min constraint. 154 */ 155 public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY; 156 157 /** 158 * Sentinel value which can be passed to a region's 159 * {@link #setMinWidth(double) setMinWidth}, 160 * {@link #setMinHeight(double) setMinHeight}, 161 * {@link #setPrefWidth(double) setPrefWidth}, 162 * {@link #setPrefHeight(double) setPrefHeight}, 163 * {@link #setMaxWidth(double) setMaxWidth}, 164 * {@link #setMaxHeight(double) setMaxHeight} methods 165 * to reset the region's size constraint back to it's intrinsic size returned 166 * by {@link #computeMinWidth(double) computeMinWidth}, {@link #computeMinHeight(double) computeMinHeight}, 167 * {@link #computePrefWidth(double) computePrefWidth}, {@link #computePrefHeight(double) computePrefHeight}, 168 * {@link #computeMaxWidth(double) computeMaxWidth}, or {@link #computeMaxHeight(double) computeMaxHeight}. 169 */ 170 public static final double USE_COMPUTED_SIZE = -1; 171 172 static Vec2d TEMP_VEC2D = new Vec2d(); 173 174 /*************************************************************************** 175 * * 176 * Static convenience methods for layout * 177 * * 178 **************************************************************************/ 179 180 /** 181 * Computes the value based on the given min and max values. We encode in this 182 * method the logic surrounding various edge cases, such as when the min is 183 * specified as greater than the max, or the max less than the min, or a pref 184 * value that exceeds either the max or min in their extremes. 185 * <p/> 186 * If the min is greater than the max, then we want to make sure the returned 187 * value is the min. In other words, in such a case, the min becomes the only 188 * acceptable return value. 189 * <p/> 190 * If the min and max values are well ordered, and the pref is less than the min 191 * then the min is returned. Likewise, if the values are well ordered and the 192 * pref is greater than the max, then the max is returned. If the pref lies 193 * between the min and the max, then the pref is returned. 194 * 195 * 196 * @param min The minimum bound 197 * @param pref The value to be clamped between the min and max 198 * @param max the maximum bound 199 * @return the size bounded by min, pref, and max. 200 */ 201 static double boundedSize(double min, double pref, double max) { 202 double a = pref >= min ? pref : min; 203 double b = min >= max ? min : max; 204 return a <= b ? a : b; 205 } 206 207 double adjustWidthByMargin(double width, Insets margin) { 208 if (margin == null || margin == Insets.EMPTY) { 209 return width; 210 } 211 boolean isSnapToPixel = isSnapToPixel(); 212 return width - snapSpace(margin.getLeft(), isSnapToPixel) - snapSpace(margin.getRight(), isSnapToPixel); 213 } 214 215 double adjustHeightByMargin(double height, Insets margin) { 216 if (margin == null || margin == Insets.EMPTY) { 217 return height; 218 } 219 boolean isSnapToPixel = isSnapToPixel(); 220 return height - snapSpace(margin.getTop(), isSnapToPixel) - snapSpace(margin.getBottom(), isSnapToPixel); 221 } 222 223 /** 224 * If snapToPixel is true, then the value is rounded using Math.round. Otherwise, 225 * the value is simply returned. This method will surely be JIT'd under normal 226 * circumstances, however on an interpreter it would be better to inline this 227 * method. However the use of Math.round here, and Math.ceil in snapSize is 228 * not obvious, and so for code maintenance this logic is pulled out into 229 * a separate method. 230 * 231 * @param value The value that needs to be snapped 232 * @param snapToPixel Whether to snap to pixel 233 * @return value either as passed in or rounded based on snapToPixel 234 */ 235 private static double snapSpace(double value, boolean snapToPixel) { 236 return snapToPixel ? Math.round(value) : value; 237 } 238 239 /** 240 * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise, 241 * the value is simply returned. 242 * 243 * @param value The value that needs to be snapped 244 * @param snapToPixel Whether to snap to pixel 245 * @return value either as passed in or ceil'd based on snapToPixel 246 */ 247 private static double snapSize(double value, boolean snapToPixel) { 248 return snapToPixel ? Math.ceil(value) : value; 249 } 250 251 /** 252 * If snapToPixel is true, then the value is rounded using Math.round. Otherwise, 253 * the value is simply returned. 254 * 255 * @param value The value that needs to be snapped 256 * @param snapToPixel Whether to snap to pixel 257 * @return value either as passed in or rounded based on snapToPixel 258 */ 259 private static double snapPosition(double value, boolean snapToPixel) { 260 return snapToPixel ? Math.round(value) : value; 261 } 262 263 private static double snapPortion(double value, boolean snapToPixel) { 264 if (snapToPixel) { 265 return value == 0 ? 0 :(value > 0 ? Math.max(1, Math.floor(value)) : Math.min(-1, Math.ceil(value))); 266 } 267 return value; 268 } 269 270 double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 271 Function<Integer, Double> positionToWidth, 272 double areaHeight, boolean fillHeight) { 273 return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, isSnapToPixel()); 274 } 275 276 static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 277 Function<Integer, Double> positionToWidth, 278 double areaHeight, boolean fillHeight, boolean snapToPixel) { 279 return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, 280 getMinBaselineComplement(children), snapToPixel); 281 } 282 283 double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 284 Function<Integer, Double> positionToWidth, 285 double areaHeight, final boolean fillHeight, double minComplement) { 286 return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel()); 287 } 288 289 static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 290 Function<Integer, Double> positionToWidth, 291 double areaHeight, final boolean fillHeight, double minComplement, boolean snapToPixel) { 292 return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, t -> fillHeight, minComplement, snapToPixel); 293 } 294 295 double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 296 Function<Integer, Double> positionToWidth, 297 double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement) { 298 return getAreaBaselineOffset(children, margins, positionToWidth, areaHeight, fillHeight, minComplement, isSnapToPixel()); 299 } 300 301 /** 302 * Returns the baseline offset of provided children, with respect to the minimum complement, computed 303 * by {@link #getMinBaselineComplement(java.util.List)} from the same set of children. 304 * @param children the children with baseline alignment 305 * @param margins their margins (callback) 306 * @param positionToWidth callback for children widths (can return -1 if no bias is used) 307 * @param areaHeight height of the area to layout in 308 * @param fillHeight callback to specify children that has fillHeight constraint 309 * @param minComplement minimum complement 310 */ 311 static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins, 312 Function<Integer, Double> positionToWidth, 313 double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement, boolean snapToPixel) { 314 double b = 0; 315 for (int i = 0;i < children.size(); ++i) { 316 Node n = children.get(i); 317 Insets margin = margins.call(n); 318 double top = margin != null? snapSpace(margin.getTop(), snapToPixel) : 0; 319 double bottom = (margin != null? snapSpace(margin.getBottom(), snapToPixel) : 0); 320 final double bo = n.getBaselineOffset(); 321 if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) { 322 double alt = -1; 323 if (n.getContentBias() == Orientation.HORIZONTAL) { 324 alt = positionToWidth.apply(i); 325 } 326 if (fillHeight.apply(i)) { 327 // If the children fills it's height, than it's "preferred" height is the area without the complement and insets 328 b = Math.max(b, top + boundedSize(n.minHeight(alt), areaHeight - minComplement - top - bottom, 329 n.maxHeight(alt))); 330 } else { 331 // Otherwise, we must use the area without complement and insets as a maximum for the Node 332 b = Math.max(b, top + boundedSize(n.minHeight(alt), n.prefHeight(alt), 333 Math.min(n.maxHeight(alt), areaHeight - minComplement - top - bottom))); 334 } 335 } else { 336 b = Math.max(b, top + bo); 337 } 338 } 339 return b; 340 } 341 342 /** 343 * Return the minimum complement of baseline 344 * @param children 345 * @return 346 */ 347 static double getMinBaselineComplement(List<Node> children) { 348 return getBaselineComplement(children, true, false); 349 } 350 351 /** 352 * Return the preferred complement of baseline 353 * @param children 354 * @return 355 */ 356 static double getPrefBaselineComplement(List<Node> children) { 357 return getBaselineComplement(children, false, false); 358 } 359 360 /** 361 * Return the maximal complement of baseline 362 * @param children 363 * @return 364 */ 365 static double getMaxBaselineComplement(List<Node> children) { 366 return getBaselineComplement(children, false, true); 367 } 368 369 private static double getBaselineComplement(List<Node> children, boolean min, boolean max) { 370 double bc = 0; 371 for (Node n : children) { 372 final double bo = n.getBaselineOffset(); 373 if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) { 374 continue; 375 } 376 if (n.isResizable()) { 377 bc = Math.max(bc, (min ? n.minHeight(-1) : max ? n.maxHeight(-1) : n.prefHeight(-1)) - bo); 378 } else { 379 bc = Math.max(bc, n.getLayoutBounds().getHeight() - bo); 380 } 381 } 382 return bc; 383 } 384 385 386 static double computeXOffset(double width, double contentWidth, HPos hpos) { 387 switch(hpos) { 388 case LEFT: 389 return 0; 390 case CENTER: 391 return (width - contentWidth) / 2; 392 case RIGHT: 393 return width - contentWidth; 394 default: 395 throw new AssertionError("Unhandled hPos"); 396 } 397 } 398 399 static double computeYOffset(double height, double contentHeight, VPos vpos) { 400 switch(vpos) { 401 case BASELINE: 402 case TOP: 403 return 0; 404 case CENTER: 405 return (height - contentHeight) / 2; 406 case BOTTOM: 407 return height - contentHeight; 408 default: 409 throw new AssertionError("Unhandled vPos"); 410 } 411 } 412 413 static double[] createDoubleArray(int length, double value) { 414 double[] array = new double[length]; 415 for (int i = 0; i < length; i++) { 416 array[i] = value; 417 } 418 return array; 419 } 420 421 /*************************************************************************** 422 * * 423 * Constructors * 424 * * 425 **************************************************************************/ 426 427 /** 428 * At the time that a Background or Border is set on a Region, we inspect any 429 * BackgroundImage or BorderImage objects, to see if the Image backing them 430 * is background loading and not yet complete, or is animated. In such cases 431 * we attach the imageChangeListener to them, so that when the image finishes, 432 * the Region will be redrawn. If the particular image object is not animating 433 * (but was just background loading), then we also remove the listener. 434 * We also are sure to remove this listener from any old BackgroundImage or 435 * BorderImage images in the background and border property invalidation code. 436 */ 437 private InvalidationListener imageChangeListener = observable -> { 438 final ReadOnlyObjectPropertyBase imageProperty = (ReadOnlyObjectPropertyBase) observable; 439 final Image image = (Image) imageProperty.getBean(); 440 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 441 if (image.getProgress() == 1 && !acc.isAnimation(image)) { 442 // We can go ahead and remove the listener since loading is done. 443 removeImageListener(image); 444 } 445 // Cause the region to repaint 446 impl_markDirty(DirtyBits.NODE_CONTENTS); 447 }; 448 449 /** 450 * Creates a new Region with an empty Background and and empty Border. The 451 * Region defaults to having pickOnBounds set to true, meaning that any pick 452 * (mouse picking or touch picking etc) that occurs within the bounds in local 453 * of the Region will return true, regardless of whether the Region is filled 454 * or transparent. 455 */ 456 public Region() { 457 super(); 458 setPickOnBounds(true); 459 } 460 461 /*************************************************************************** 462 * * 463 * Region properties * 464 * * 465 **************************************************************************/ 466 467 /** 468 * Defines whether this region adjusts position, spacing, and size values of 469 * its children to pixel boundaries. This defaults to true, which is generally 470 * the expected behavior in order to have crisp user interfaces. A value of 471 * false will allow for fractional alignment, which may lead to "fuzzy" 472 * looking borders. 473 */ 474 private BooleanProperty snapToPixel; 475 /** 476 * I'm using a super-lazy property pattern here, so as to only create the 477 * property object when needed for listeners or when being set from CSS, 478 * but also making sure that we only call requestParentLayout in the case 479 * that the snapToPixel value has actually changed, whether set via the setter 480 * or set via the property object. 481 */ 482 private boolean _snapToPixel = true; 483 public final boolean isSnapToPixel() { return _snapToPixel; } 484 public final void setSnapToPixel(boolean value) { 485 if (snapToPixel == null) { 486 if (_snapToPixel != value) { 487 _snapToPixel = value; 488 updateSnappedInsets(); 489 requestParentLayout(); 490 } 491 } else { 492 snapToPixel.set(value); 493 } 494 } 495 public final BooleanProperty snapToPixelProperty() { 496 // Note: snapToPixel is virtually never set, and never listened to. 497 // Because of this, it works reasonably well as a lazy property, 498 // since this logic is just about never going to be called. 499 if (snapToPixel == null) { 500 snapToPixel = new StyleableBooleanProperty(_snapToPixel) { 501 @Override public Object getBean() { return Region.this; } 502 @Override public String getName() { return "snapToPixel"; } 503 @Override public CssMetaData<Region, Boolean> getCssMetaData() { 504 return StyleableProperties.SNAP_TO_PIXEL; 505 } 506 @Override public void invalidated() { 507 boolean value = get(); 508 if (_snapToPixel != value) { 509 _snapToPixel = value; 510 updateSnappedInsets(); 511 requestParentLayout(); 512 } 513 } 514 }; 515 } 516 return snapToPixel; 517 } 518 519 /** 520 * The top, right, bottom, and left padding around the region's content. 521 * This space will be included in the calculation of the region's 522 * minimum and preferred sizes. By default padding is Insets.EMPTY. Setting the 523 * value to null should be avoided. 524 */ 525 private ObjectProperty<Insets> padding = new StyleableObjectProperty<Insets>(Insets.EMPTY) { 526 // Keep track of the last valid value for the sake of 527 // rollback in case padding is set to null. Note that 528 // Richard really does not like this pattern because 529 // it essentially means that binding the padding property 530 // is not possible since a binding expression could very 531 // easily produce an intermediate null value. 532 533 // Also note that because padding is set virtually everywhere via CSS, and CSS 534 // requires a property object in order to set it, there is no benefit to having 535 // lazy initialization here. 536 537 private Insets lastValidValue = Insets.EMPTY; 538 539 @Override public Object getBean() { return Region.this; } 540 @Override public String getName() { return "padding"; } 541 @Override public CssMetaData<Region, Insets> getCssMetaData() { 542 return StyleableProperties.PADDING; 543 } 544 @Override public void invalidated() { 545 final Insets newValue = get(); 546 if (newValue == null) { 547 // rollback 548 if (isBound()) { 549 unbind(); 550 } 551 set(lastValidValue); 552 throw new NullPointerException("cannot set padding to null"); 553 } else if (!newValue.equals(lastValidValue)) { 554 lastValidValue = newValue; 555 insets.fireValueChanged(); 556 } 557 } 558 }; 559 public final void setPadding(Insets value) { padding.set(value); } 560 public final Insets getPadding() { return padding.get(); } 561 public final ObjectProperty<Insets> paddingProperty() { return padding; } 562 563 /** 564 * The background of the Region, which is made up of zero or more BackgroundFills, and 565 * zero or more BackgroundImages. It is possible for a Background to be empty, where it 566 * has neither fills nor images, and is semantically equivalent to null. 567 * @since JavaFX 8.0 568 */ 569 private final ObjectProperty<Background> background = new StyleableObjectProperty<Background>(null) { 570 private Background old = null; 571 @Override public Object getBean() { return Region.this; } 572 @Override public String getName() { return "background"; } 573 @Override public CssMetaData<Region, Background> getCssMetaData() { 574 return StyleableProperties.BACKGROUND; 575 } 576 577 @Override protected void invalidated() { 578 final Background b = get(); 579 if(old != null ? !old.equals(b) : b != null) { 580 // They are different! Both cannot be null 581 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) { 582 // We have determined that the outsets of these two different background 583 // objects is different, and therefore the bounds have changed. 584 impl_geomChanged(); 585 insets.fireValueChanged(); 586 } 587 588 // If the Background is made up of any BackgroundImage objects, then we must 589 // inspect the images of those BackgroundImage objects to see if they are still 590 // being loaded in the background or if they are animated. If so, then we need 591 // to attach a listener, so that when the image finishes loading or changes, 592 // we can repaint the region. 593 if (b != null) { 594 for (BackgroundImage i : b.getImages()) { 595 final Image image = i.image; 596 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 597 if (acc.isAnimation(image) || image.getProgress() < 1) { 598 addImageListener(image); 599 } 600 } 601 } 602 603 // And we must remove this listener from any old images 604 if (old != null) { 605 for (BackgroundImage i : old.getImages()) { 606 removeImageListener(i.image); 607 } 608 } 609 610 // No matter what, the fill has changed, so we have to update it 611 impl_markDirty(DirtyBits.SHAPE_FILL); 612 cornersValid = false; 613 old = b; 614 } 615 } 616 }; 617 public final void setBackground(Background value) { background.set(value); } 618 public final Background getBackground() { return background.get(); } 619 public final ObjectProperty<Background> backgroundProperty() { return background; } 620 621 /** 622 * The border of the Region, which is made up of zero or more BorderStrokes, and 623 * zero or more BorderImages. It is possible for a Border to be empty, where it 624 * has neither strokes nor images, and is semantically equivalent to null. 625 * @since JavaFX 8.0 626 */ 627 private final ObjectProperty<Border> border = new StyleableObjectProperty<Border>(null) { 628 private Border old = null; 629 @Override public Object getBean() { return Region.this; } 630 @Override public String getName() { return "border"; } 631 @Override public CssMetaData<Region, Border> getCssMetaData() { 632 return StyleableProperties.BORDER; 633 } 634 @Override protected void invalidated() { 635 final Border b = get(); 636 if(old != null ? !old.equals(b) : b != null) { 637 // They are different! Both cannot be null 638 if (old == null || b == null || !old.getOutsets().equals(b.getOutsets())) { 639 // We have determined that the outsets of these two different border 640 // objects is different, and therefore the bounds have changed. 641 impl_geomChanged(); 642 } 643 if (old == null || b == null || !old.getInsets().equals(b.getInsets())) { 644 insets.fireValueChanged(); 645 } 646 647 // If the Border is made up of any BorderImage objects, then we must 648 // inspect the images of those BorderImage objects to see if they are still 649 // being loaded in the background or if they are animated. If so, then we need 650 // to attach a listener, so that when the image finishes loading or changes, 651 // we can repaint the region. 652 if (b != null) { 653 for (BorderImage i : b.getImages()) { 654 final Image image = i.image; 655 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 656 if (acc.isAnimation(image) || image.getProgress() < 1) { 657 addImageListener(image); 658 } 659 } 660 } 661 662 // And we must remove this listener from any old images 663 if (old != null) { 664 for (BorderImage i : old.getImages()) { 665 removeImageListener(i.image); 666 } 667 } 668 669 // No matter what, the fill has changed, so we have to update it 670 impl_markDirty(DirtyBits.SHAPE_STROKE); 671 cornersValid = false; 672 old = b; 673 } 674 } 675 }; 676 public final void setBorder(Border value) { border.set(value); } 677 public final Border getBorder() { return border.get(); } 678 public final ObjectProperty<Border> borderProperty() { return border; } 679 680 /** 681 * Adds the imageChangeListener to this image. This method was broken out and made 682 * package private for testing purposes. 683 * 684 * @param image a non-null image 685 */ 686 void addImageListener(Image image) { 687 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 688 acc.getImageProperty(image).addListener(imageChangeListener); 689 } 690 691 /** 692 * Removes the imageChangeListener from this image. This method was broken out and made 693 * package private for testing purposes. 694 * 695 * @param image a non-null image 696 */ 697 void removeImageListener(Image image) { 698 final Toolkit.ImageAccessor acc = Toolkit.getImageAccessor(); 699 acc.getImageProperty(image).removeListener(imageChangeListener); 700 } 701 702 /** 703 * Defines the area of the region within which completely opaque pixels 704 * are drawn. This is used for various performance optimizations. 705 * The pixels within this area MUST BE fully opaque, or rendering 706 * artifacts will result. It is the responsibility of the application, either 707 * via code or via CSS, to ensure that the opaqueInsets is correct for 708 * a Region based on the backgrounds and borders of that region. The values 709 * for each of the insets must be real numbers, not NaN or Infinity. If 710 * no known insets exist, then the opaqueInsets should be set to null. 711 * @since JavaFX 8.0 712 */ 713 public final ObjectProperty<Insets> opaqueInsetsProperty() { 714 if (opaqueInsets == null) { 715 opaqueInsets = new StyleableObjectProperty<Insets>() { 716 @Override public Object getBean() { return Region.this; } 717 @Override public String getName() { return "opaqueInsets"; } 718 @Override public CssMetaData<Region, Insets> getCssMetaData() { 719 return StyleableProperties.OPAQUE_INSETS; 720 } 721 @Override protected void invalidated() { 722 // This causes the background to be updated, which 723 // is the code block where we also compute the opaque insets 724 // since updating the background is super fast even when 725 // nothing has changed. 726 impl_markDirty(DirtyBits.SHAPE_FILL); 727 } 728 }; 729 } 730 return opaqueInsets; 731 } 732 private ObjectProperty<Insets> opaqueInsets; 733 public final void setOpaqueInsets(Insets value) { opaqueInsetsProperty().set(value); } 734 public final Insets getOpaqueInsets() { return opaqueInsets == null ? null : opaqueInsets.get(); } 735 736 /** 737 * The insets of the Region define the distance from the edge of the region (its layout bounds, 738 * or (0, 0, width, height)) to the edge of the content area. All child nodes should be laid out 739 * within the content area. The insets are computed based on the Border which has been specified, 740 * if any, and also the padding. 741 * @since JavaFX 8.0 742 */ 743 private final InsetsProperty insets = new InsetsProperty(); 744 public final Insets getInsets() { return insets.get(); } 745 public final ReadOnlyObjectProperty<Insets> insetsProperty() { return insets; } 746 private final class InsetsProperty extends ReadOnlyObjectProperty<Insets> { 747 private Insets cache = null; 748 private ExpressionHelper<Insets> helper = null; 749 750 @Override public Object getBean() { return Region.this; } 751 @Override public String getName() { return "insets"; } 752 753 @Override public void addListener(InvalidationListener listener) { 754 helper = ExpressionHelper.addListener(helper, this, listener); 755 } 756 757 @Override public void removeListener(InvalidationListener listener) { 758 helper = ExpressionHelper.removeListener(helper, listener); 759 } 760 761 @Override public void addListener(ChangeListener<? super Insets> listener) { 762 helper = ExpressionHelper.addListener(helper, this, listener); 763 } 764 765 @Override public void removeListener(ChangeListener<? super Insets> listener) { 766 helper = ExpressionHelper.removeListener(helper, listener); 767 } 768 769 void fireValueChanged() { 770 cache = null; 771 updateSnappedInsets(); 772 requestLayout(); 773 ExpressionHelper.fireValueChangedEvent(helper); 774 } 775 776 @Override public Insets get() { 777 // If a shape is specified, then we don't really care whether there are any borders 778 // specified, since borders of shapes do not contribute to the insets. 779 if (_shape != null) return getPadding(); 780 781 // If there is no border or the border has no insets itself, then the only thing 782 // affecting the insets is the padding, so we can just return it directly. 783 final Border b = getBorder(); 784 if (b == null || Insets.EMPTY.equals(b.getInsets())) { 785 return getPadding(); 786 } 787 788 // There is a border with some non-zero insets and we do not have a _shape, so we need 789 // to take the border's insets into account 790 if (cache == null) { 791 // Combine the padding and the border insets. 792 // TODO note that negative border insets were being ignored, but 793 // I'm not sure that that made sense or was reasonable, so I have 794 // changed it so that we just do simple math. 795 // TODO Stroke borders should NOT contribute to the insets. Ensure via tests. 796 final Insets borderInsets = b.getInsets(); 797 final Insets paddingInsets = getPadding(); 798 cache = new Insets( 799 borderInsets.getTop() + paddingInsets.getTop(), 800 borderInsets.getRight() + paddingInsets.getRight(), 801 borderInsets.getBottom() + paddingInsets.getBottom(), 802 borderInsets.getLeft() + paddingInsets.getLeft() 803 ); 804 } 805 return cache; 806 } 807 }; 808 809 /** 810 * cached results of snapped insets, this are used a lot during layout so makes sense 811 * to keep fast access cached copies here. 812 */ 813 private double snappedTopInset = 0; 814 private double snappedRightInset = 0; 815 private double snappedBottomInset = 0; 816 private double snappedLeftInset = 0; 817 818 /** Called to update the cached snapped insets */ 819 private void updateSnappedInsets() { 820 final Insets insets = getInsets(); 821 if (_snapToPixel) { 822 snappedTopInset = Math.ceil(insets.getTop()); 823 snappedRightInset = Math.ceil(insets.getRight()); 824 snappedBottomInset = Math.ceil(insets.getBottom()); 825 snappedLeftInset = Math.ceil(insets.getLeft()); 826 } else { 827 snappedTopInset = insets.getTop(); 828 snappedRightInset = insets.getRight(); 829 snappedBottomInset = insets.getBottom(); 830 snappedLeftInset = insets.getLeft(); 831 } 832 } 833 834 /** 835 * The width of this resizable node. This property is set by the region's parent 836 * during layout and may not be set by the application. If an application 837 * needs to explicitly control the size of a region, it should override its 838 * preferred size range by setting the <code>minWidth</code>, <code>prefWidth</code>, 839 * and <code>maxWidth</code> properties. 840 */ 841 private ReadOnlyDoubleWrapper width; 842 843 /** 844 * Because the width is very often set and very often read but only sometimes 845 * listened to, it is beneficial to use the super-lazy pattern property, where we 846 * only inflate the property object when widthProperty() is explicitly invoked. 847 */ 848 private double _width; 849 850 // Note that it is OK for this method to be protected so long as the width 851 // property is never bound. Only Region could do so because only Region has 852 // access to a writable property for "width", but since there is now a protected 853 // set method, it is impossible for Region to ever bind this property. 854 protected void setWidth(double value) { 855 if(width == null) { 856 widthChanged(value); 857 } else { 858 width.set(value); 859 } 860 } 861 862 private void widthChanged(double value) { 863 // It is possible that somebody sets the width of the region to a value which 864 // it previously held. If this is the case, we want to avoid excessive layouts. 865 // Note that I have biased this for layout over binding, because the widthProperty 866 // is now going to recompute the width eagerly. The cost of excessive and 867 // unnecessary bounds changes, however, is relatively high. 868 if (value != _width) { 869 _width = value; 870 cornersValid = false; 871 boundingBox = null; 872 impl_layoutBoundsChanged(); 873 impl_geomChanged(); 874 impl_markDirty(DirtyBits.NODE_GEOMETRY); 875 setNeedsLayout(true); 876 requestParentLayout(); 877 } 878 } 879 880 public final double getWidth() { return width == null ? _width : width.get(); } 881 882 public final ReadOnlyDoubleProperty widthProperty() { 883 if (width == null) { 884 width = new ReadOnlyDoubleWrapper(_width) { 885 @Override protected void invalidated() { widthChanged(get()); } 886 @Override public Object getBean() { return Region.this; } 887 @Override public String getName() { return "width"; } 888 }; 889 } 890 return width.getReadOnlyProperty(); 891 } 892 893 /** 894 * The height of this resizable node. This property is set by the region's parent 895 * during layout and may not be set by the application. If an application 896 * needs to explicitly control the size of a region, it should override its 897 * preferred size range by setting the <code>minHeight</code>, <code>prefHeight</code>, 898 * and <code>maxHeight</code> properties. 899 */ 900 private ReadOnlyDoubleWrapper height; 901 902 /** 903 * Because the height is very often set and very often read but only sometimes 904 * listened to, it is beneficial to use the super-lazy pattern property, where we 905 * only inflate the property object when heightProperty() is explicitly invoked. 906 */ 907 private double _height; 908 909 // Note that it is OK for this method to be protected so long as the height 910 // property is never bound. Only Region could do so because only Region has 911 // access to a writable property for "height", but since there is now a protected 912 // set method, it is impossible for Region to ever bind this property. 913 protected void setHeight(double value) { 914 if (height == null) { 915 heightChanged(value); 916 } else { 917 height.set(value); 918 } 919 } 920 921 private void heightChanged(double value) { 922 if (_height != value) { 923 _height = value; 924 cornersValid = false; 925 // It is possible that somebody sets the height of the region to a value which 926 // it previously held. If this is the case, we want to avoid excessive layouts. 927 // Note that I have biased this for layout over binding, because the heightProperty 928 // is now going to recompute the height eagerly. The cost of excessive and 929 // unnecessary bounds changes, however, is relatively high. 930 boundingBox = null; 931 // Note: although impl_geomChanged will usually also invalidate the 932 // layout bounds, that is not the case for Regions, and both must 933 // be called separately. 934 impl_geomChanged(); 935 impl_layoutBoundsChanged(); 936 // We use "NODE_GEOMETRY" to mean that the bounds have changed and 937 // need to be sync'd with the render tree 938 impl_markDirty(DirtyBits.NODE_GEOMETRY); 939 // Change of the height (or width) won't change the preferred size. 940 // So we don't need to flush the cache. We should however mark this node 941 // as needs layout to be internally layouted. 942 setNeedsLayout(true); 943 // This call is only needed when this was not called from the parent during the layout. 944 // Otherwise it would flush the cache of the parent, which is not necessary 945 requestParentLayout(); 946 } 947 } 948 949 public final double getHeight() { return height == null ? _height : height.get(); } 950 951 public final ReadOnlyDoubleProperty heightProperty() { 952 if (height == null) { 953 height = new ReadOnlyDoubleWrapper(_height) { 954 @Override protected void invalidated() { heightChanged(get()); } 955 @Override public Object getBean() { return Region.this; } 956 @Override public String getName() { return "height"; } 957 }; 958 } 959 return height.getReadOnlyProperty(); 960 } 961 962 /** 963 * This class is reused for the min, pref, and max properties since 964 * they all performed the same function (to call requestParentLayout). 965 */ 966 private final class MinPrefMaxProperty extends StyleableDoubleProperty { 967 private final String name; 968 private final CssMetaData<? extends Styleable, Number> cssMetaData; 969 970 MinPrefMaxProperty(String name, double initialValue, CssMetaData<? extends Styleable, Number> cssMetaData) { 971 super(initialValue); 972 this.name = name; 973 this.cssMetaData = cssMetaData; 974 } 975 976 @Override public void invalidated() { requestParentLayout(); } 977 @Override public Object getBean() { return Region.this; } 978 @Override public String getName() { return name; } 979 980 @Override 981 public CssMetaData<? extends Styleable, Number> getCssMetaData() { 982 return cssMetaData; 983 } 984 } 985 986 /** 987 * Property for overriding the region's computed minimum width. 988 * This should only be set if the region's internally computed minimum width 989 * doesn't meet the application's layout needs. 990 * <p> 991 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 992 * <code>minWidth(forHeight)</code> will return the region's internally 993 * computed minimum width. 994 * <p> 995 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 996 * <code>minWidth(forHeight)</code> to return the region's preferred width, 997 * enabling applications to easily restrict the resizability of the region. 998 */ 999 private DoubleProperty minWidth; 1000 private double _minWidth = USE_COMPUTED_SIZE; 1001 public final void setMinWidth(double value) { 1002 if (minWidth == null) { 1003 _minWidth = value; 1004 requestParentLayout(); 1005 } else { 1006 minWidth.set(value); 1007 } 1008 } 1009 public final double getMinWidth() { return minWidth == null ? _minWidth : minWidth.get(); } 1010 public final DoubleProperty minWidthProperty() { 1011 if (minWidth == null) minWidth = new MinPrefMaxProperty("minWidth", _minWidth, StyleableProperties.MIN_WIDTH); 1012 return minWidth; 1013 } 1014 1015 /** 1016 * Property for overriding the region's computed minimum height. 1017 * This should only be set if the region's internally computed minimum height 1018 * doesn't meet the application's layout needs. 1019 * <p> 1020 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 1021 * <code>minHeight(forWidth)</code> will return the region's internally 1022 * computed minimum height. 1023 * <p> 1024 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 1025 * <code>minHeight(forWidth)</code> to return the region's preferred height, 1026 * enabling applications to easily restrict the resizability of the region. 1027 * 1028 */ 1029 private DoubleProperty minHeight; 1030 private double _minHeight = USE_COMPUTED_SIZE; 1031 public final void setMinHeight(double value) { 1032 if (minHeight == null) { 1033 _minHeight = value; 1034 requestParentLayout(); 1035 } else { 1036 minHeight.set(value); 1037 } 1038 } 1039 public final double getMinHeight() { return minHeight == null ? _minHeight : minHeight.get(); } 1040 public final DoubleProperty minHeightProperty() { 1041 if (minHeight == null) minHeight = new MinPrefMaxProperty("minHeight", _minHeight, StyleableProperties.MIN_HEIGHT); 1042 return minHeight; 1043 } 1044 1045 /** 1046 * Convenience method for overriding the region's computed minimum width and height. 1047 * This should only be called if the region's internally computed minimum size 1048 * doesn't meet the application's layout needs. 1049 * 1050 * @see #setMinWidth 1051 * @see #setMinHeight 1052 * @param minWidth the override value for minimum width 1053 * @param minHeight the override value for minimum height 1054 */ 1055 public void setMinSize(double minWidth, double minHeight) { 1056 setMinWidth(minWidth); 1057 setMinHeight(minHeight); 1058 } 1059 1060 /** 1061 * Property for overriding the region's computed preferred width. 1062 * This should only be set if the region's internally computed preferred width 1063 * doesn't meet the application's layout needs. 1064 * <p> 1065 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 1066 * <code>getPrefWidth(forHeight)</code> will return the region's internally 1067 * computed preferred width. 1068 */ 1069 private DoubleProperty prefWidth; 1070 private double _prefWidth = USE_COMPUTED_SIZE; 1071 public final void setPrefWidth(double value) { 1072 if (prefWidth == null) { 1073 _prefWidth = value; 1074 requestParentLayout(); 1075 } else { 1076 prefWidth.set(value); 1077 } 1078 } 1079 public final double getPrefWidth() { return prefWidth == null ? _prefWidth : prefWidth.get(); } 1080 public final DoubleProperty prefWidthProperty() { 1081 if (prefWidth == null) prefWidth = new MinPrefMaxProperty("prefWidth", _prefWidth, StyleableProperties.PREF_WIDTH); 1082 return prefWidth; 1083 } 1084 1085 /** 1086 * Property for overriding the region's computed preferred height. 1087 * This should only be set if the region's internally computed preferred height 1088 * doesn't meet the application's layout needs. 1089 * <p> 1090 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 1091 * <code>getPrefHeight(forWidth)</code> will return the region's internally 1092 * computed preferred width. 1093 */ 1094 private DoubleProperty prefHeight; 1095 private double _prefHeight = USE_COMPUTED_SIZE; 1096 public final void setPrefHeight(double value) { 1097 if (prefHeight == null) { 1098 _prefHeight = value; 1099 requestParentLayout(); 1100 } else { 1101 prefHeight.set(value); 1102 } 1103 } 1104 public final double getPrefHeight() { return prefHeight == null ? _prefHeight : prefHeight.get(); } 1105 public final DoubleProperty prefHeightProperty() { 1106 if (prefHeight == null) prefHeight = new MinPrefMaxProperty("prefHeight", _prefHeight, StyleableProperties.PREF_HEIGHT); 1107 return prefHeight; 1108 } 1109 1110 /** 1111 * Convenience method for overriding the region's computed preferred width and height. 1112 * This should only be called if the region's internally computed preferred size 1113 * doesn't meet the application's layout needs. 1114 * 1115 * @see #setPrefWidth 1116 * @see #setPrefHeight 1117 * @param prefWidth the override value for preferred width 1118 * @param prefHeight the override value for preferred height 1119 */ 1120 public void setPrefSize(double prefWidth, double prefHeight) { 1121 setPrefWidth(prefWidth); 1122 setPrefHeight(prefHeight); 1123 } 1124 1125 /** 1126 * Property for overriding the region's computed maximum width. 1127 * This should only be set if the region's internally computed maximum width 1128 * doesn't meet the application's layout needs. 1129 * <p> 1130 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 1131 * <code>getMaxWidth(forHeight)</code> will return the region's internally 1132 * computed maximum width. 1133 * <p> 1134 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 1135 * <code>getMaxWidth(forHeight)</code> to return the region's preferred width, 1136 * enabling applications to easily restrict the resizability of the region. 1137 */ 1138 private DoubleProperty maxWidth; 1139 private double _maxWidth = USE_COMPUTED_SIZE; 1140 public final void setMaxWidth(double value) { 1141 if (maxWidth == null) { 1142 _maxWidth = value; 1143 requestParentLayout(); 1144 } else { 1145 maxWidth.set(value); 1146 } 1147 } 1148 public final double getMaxWidth() { return maxWidth == null ? _maxWidth : maxWidth.get(); } 1149 public final DoubleProperty maxWidthProperty() { 1150 if (maxWidth == null) maxWidth = new MinPrefMaxProperty("maxWidth", _maxWidth, StyleableProperties.MAX_WIDTH); 1151 return maxWidth; 1152 } 1153 1154 /** 1155 * Property for overriding the region's computed maximum height. 1156 * This should only be set if the region's internally computed maximum height 1157 * doesn't meet the application's layout needs. 1158 * <p> 1159 * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that 1160 * <code>getMaxHeight(forWidth)</code> will return the region's internally 1161 * computed maximum height. 1162 * <p> 1163 * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause 1164 * <code>getMaxHeight(forWidth)</code> to return the region's preferred height, 1165 * enabling applications to easily restrict the resizability of the region. 1166 */ 1167 private DoubleProperty maxHeight; 1168 private double _maxHeight = USE_COMPUTED_SIZE; 1169 public final void setMaxHeight(double value) { 1170 if (maxHeight == null) { 1171 _maxHeight = value; 1172 requestParentLayout(); 1173 } else { 1174 maxHeight.set(value); 1175 } 1176 } 1177 public final double getMaxHeight() { return maxHeight == null ? _maxHeight : maxHeight.get(); } 1178 public final DoubleProperty maxHeightProperty() { 1179 if (maxHeight == null) maxHeight = new MinPrefMaxProperty("maxHeight", _maxHeight, StyleableProperties.MAX_HEIGHT); 1180 return maxHeight; 1181 } 1182 1183 /** 1184 * Convenience method for overriding the region's computed maximum width and height. 1185 * This should only be called if the region's internally computed maximum size 1186 * doesn't meet the application's layout needs. 1187 * 1188 * @see #setMaxWidth 1189 * @see #setMaxHeight 1190 * @param maxWidth the override value for maximum width 1191 * @param maxHeight the override value for maximum height 1192 */ 1193 public void setMaxSize(double maxWidth, double maxHeight) { 1194 setMaxWidth(maxWidth); 1195 setMaxHeight(maxHeight); 1196 } 1197 1198 /** 1199 * When specified, the {@code shape} will cause the region to be 1200 * rendered as the specified shape rather than as a rounded rectangle. 1201 * When null, the Region is rendered as a rounded rectangle. When rendered 1202 * as a Shape, any Background is used to fill the shape, although any 1203 * background insets are ignored as are background radii. Any BorderStrokes 1204 * defined are used for stroking the shape. Any BorderImages are ignored. 1205 * 1206 * @default null 1207 * @css shape SVG shape string 1208 * @since JavaFX 8.0 1209 */ 1210 private ObjectProperty<Shape> shape = null; 1211 private Shape _shape; 1212 public final Shape getShape() { return shape == null ? _shape : shape.get(); } 1213 public final void setShape(Shape value) { shapeProperty().set(value); } 1214 public final ObjectProperty<Shape> shapeProperty() { 1215 if (shape == null) { 1216 shape = new ShapeProperty(); 1217 } 1218 return shape; 1219 } 1220 1221 /** 1222 * An implementation for the ShapeProperty. This is also a ShapeChangeListener. 1223 */ 1224 private final class ShapeProperty extends StyleableObjectProperty<Shape> implements Runnable { 1225 @Override public Object getBean() { return Region.this; } 1226 @Override public String getName() { return "shape"; } 1227 @Override public CssMetaData<Region, Shape> getCssMetaData() { 1228 return StyleableProperties.SHAPE; 1229 } 1230 @Override protected void invalidated() { 1231 final Shape value = get(); 1232 if (_shape != value) { 1233 // The shape has changed. We need to add/remove listeners 1234 if (_shape != null) _shape.impl_setShapeChangeListener(null); 1235 if (value != null) value.impl_setShapeChangeListener(this); 1236 // Invalidate the bounds and such 1237 run(); 1238 if (_shape == null || value == null) { 1239 // It either was null before, or is null now. In either case, 1240 // the result of the insets computation will have changed, and 1241 // we therefore need to fire that the insets value may have changed. 1242 insets.fireValueChanged(); 1243 } 1244 // Update our reference to the old shape 1245 _shape = value; 1246 } 1247 } 1248 1249 @Override public void run() { 1250 impl_geomChanged(); 1251 impl_markDirty(DirtyBits.REGION_SHAPE); 1252 } 1253 }; 1254 1255 /** 1256 * Specifies whether the shape, if defined, is scaled to match the size of the Region. 1257 * {@code true} means the shape is scaled to fit the size of the Region, {@code false} 1258 * means the shape is at its source size, its positioning depends on the value of 1259 * {@code centerShape}. 1260 * 1261 * @default true 1262 * @css shape-size true | false 1263 * @since JavaFX 8.0 1264 */ 1265 private BooleanProperty scaleShape = null; 1266 public final void setScaleShape(boolean value) { scaleShapeProperty().set(value); } 1267 public final boolean isScaleShape() { return scaleShape == null ? true : scaleShape.get(); } 1268 public final BooleanProperty scaleShapeProperty() { 1269 if (scaleShape == null) { 1270 scaleShape = new StyleableBooleanProperty(true) { 1271 @Override public Object getBean() { return Region.this; } 1272 @Override public String getName() { return "scaleShape"; } 1273 @Override public CssMetaData<Region, Boolean> getCssMetaData() { 1274 return StyleableProperties.SCALE_SHAPE; 1275 } 1276 @Override public void invalidated() { 1277 impl_geomChanged(); 1278 impl_markDirty(DirtyBits.REGION_SHAPE); 1279 } 1280 }; 1281 } 1282 return scaleShape; 1283 } 1284 1285 /** 1286 * Defines whether the shape is centered within the Region's width or height. 1287 * {@code true} means the shape centered within the Region's width and height, 1288 * {@code false} means the shape is positioned at its source position. 1289 * 1290 * @default true 1291 * @css position-shape true | false 1292 * @since JavaFX 8.0 1293 */ 1294 private BooleanProperty centerShape = null; 1295 public final void setCenterShape(boolean value) { centerShapeProperty().set(value); } 1296 public final boolean isCenterShape() { return centerShape == null ? true : centerShape.get(); } 1297 public final BooleanProperty centerShapeProperty() { 1298 if (centerShape == null) { 1299 centerShape = new StyleableBooleanProperty(true) { 1300 @Override public Object getBean() { return Region.this; } 1301 @Override public String getName() { return "centerShape"; } 1302 @Override public CssMetaData<Region, Boolean> getCssMetaData() { 1303 return StyleableProperties.POSITION_SHAPE; 1304 } 1305 @Override public void invalidated() { 1306 impl_geomChanged(); 1307 impl_markDirty(DirtyBits.REGION_SHAPE); 1308 } 1309 }; 1310 } 1311 return centerShape; 1312 } 1313 1314 /** 1315 * Defines a hint to the system indicating that the Shape used to define the region's 1316 * background is stable and would benefit from caching. 1317 * 1318 * @default true 1319 * @css -fx-cache-shape true | false 1320 * @since JavaFX 8.0 1321 */ 1322 private BooleanProperty cacheShape = null; 1323 public final void setCacheShape(boolean value) { cacheShapeProperty().set(value); } 1324 public final boolean isCacheShape() { return cacheShape == null ? true : cacheShape.get(); } 1325 public final BooleanProperty cacheShapeProperty() { 1326 if (cacheShape == null) { 1327 cacheShape = new StyleableBooleanProperty(true) { 1328 @Override public Object getBean() { return Region.this; } 1329 @Override public String getName() { return "cacheShape"; } 1330 @Override public CssMetaData<Region, Boolean> getCssMetaData() { 1331 return StyleableProperties.CACHE_SHAPE; 1332 } 1333 }; 1334 } 1335 return cacheShape; 1336 } 1337 1338 /*************************************************************************** 1339 * * 1340 * Layout * 1341 * * 1342 **************************************************************************/ 1343 1344 /** 1345 * Returns <code>true</code> since all Regions are resizable. 1346 * @return whether this node can be resized by its parent during layout 1347 */ 1348 @Override public boolean isResizable() { 1349 return true; 1350 } 1351 1352 /** 1353 * Invoked by the region's parent during layout to set the region's 1354 * width and height. <b>Applications should not invoke this method directly</b>. 1355 * If an application needs to directly set the size of the region, it should 1356 * override its size constraints by calling <code>setMinSize()</code>, 1357 * <code>setPrefSize()</code>, or <code>setMaxSize()</code> and it's parent 1358 * will honor those overrides during layout. 1359 * 1360 * @param width the target layout bounds width 1361 * @param height the target layout bounds height 1362 */ 1363 @Override public void resize(double width, double height) { 1364 setWidth(width); 1365 setHeight(height); 1366 PlatformLogger logger = Logging.getLayoutLogger(); 1367 if (logger.isLoggable(Level.FINER)) { 1368 logger.finer(this.toString() + " resized to " + width + " x " + height); 1369 } 1370 } 1371 1372 /** 1373 * Called during layout to determine the minimum width for this node. 1374 * Returns the value from <code>computeMinWidth(forHeight)</code> unless 1375 * the application overrode the minimum width by setting the minWidth property. 1376 * 1377 * @see #setMinWidth(double) 1378 * @return the minimum width that this node should be resized to during layout 1379 */ 1380 @Override public final double minWidth(double height) { 1381 final double override = getMinWidth(); 1382 if (override == USE_COMPUTED_SIZE) { 1383 return super.minWidth(height); 1384 } else if (override == USE_PREF_SIZE) { 1385 return prefWidth(height); 1386 } 1387 return Double.isNaN(override) || override < 0 ? 0 : override; 1388 } 1389 1390 /** 1391 * Called during layout to determine the minimum height for this node. 1392 * Returns the value from <code>computeMinHeight(forWidth)</code> unless 1393 * the application overrode the minimum height by setting the minHeight property. 1394 * 1395 * @see #setMinHeight 1396 * @return the minimum height that this node should be resized to during layout 1397 */ 1398 @Override public final double minHeight(double width) { 1399 final double override = getMinHeight(); 1400 if (override == USE_COMPUTED_SIZE) { 1401 return super.minHeight(width); 1402 } else if (override == USE_PREF_SIZE) { 1403 return prefHeight(width); 1404 } 1405 return Double.isNaN(override) || override < 0 ? 0 : override; 1406 } 1407 1408 /** 1409 * Called during layout to determine the preferred width for this node. 1410 * Returns the value from <code>computePrefWidth(forHeight)</code> unless 1411 * the application overrode the preferred width by setting the prefWidth property. 1412 * 1413 * @see #setPrefWidth 1414 * @return the preferred width that this node should be resized to during layout 1415 */ 1416 @Override public final double prefWidth(double height) { 1417 final double override = getPrefWidth(); 1418 if (override == USE_COMPUTED_SIZE) { 1419 return super.prefWidth(height); 1420 } 1421 return Double.isNaN(override) || override < 0 ? 0 : override; 1422 } 1423 1424 /** 1425 * Called during layout to determine the preferred height for this node. 1426 * Returns the value from <code>computePrefHeight(forWidth)</code> unless 1427 * the application overrode the preferred height by setting the prefHeight property. 1428 * 1429 * @see #setPrefHeight 1430 * @return the preferred height that this node should be resized to during layout 1431 */ 1432 @Override public final double prefHeight(double width) { 1433 final double override = getPrefHeight(); 1434 if (override == USE_COMPUTED_SIZE) { 1435 return super.prefHeight(width); 1436 } 1437 return Double.isNaN(override) || override < 0 ? 0 : override; 1438 } 1439 1440 /** 1441 * Called during layout to determine the maximum width for this node. 1442 * Returns the value from <code>computeMaxWidth(forHeight)</code> unless 1443 * the application overrode the maximum width by setting the maxWidth property. 1444 * 1445 * @see #setMaxWidth 1446 * @return the maximum width that this node should be resized to during layout 1447 */ 1448 @Override public final double maxWidth(double height) { 1449 final double override = getMaxWidth(); 1450 if (override == USE_COMPUTED_SIZE) { 1451 return computeMaxWidth(height); 1452 } else if (override == USE_PREF_SIZE) { 1453 return prefWidth(height); 1454 } 1455 return Double.isNaN(override) || override < 0 ? 0 : override; 1456 } 1457 1458 /** 1459 * Called during layout to determine the maximum height for this node. 1460 * Returns the value from <code>computeMaxHeight(forWidth)</code> unless 1461 * the application overrode the maximum height by setting the maxHeight property. 1462 * 1463 * @see #setMaxHeight 1464 * @return the maximum height that this node should be resized to during layout 1465 */ 1466 @Override public final double maxHeight(double width) { 1467 final double override = getMaxHeight(); 1468 if (override == USE_COMPUTED_SIZE) { 1469 return computeMaxHeight(width); 1470 } else if (override == USE_PREF_SIZE) { 1471 return prefHeight(width); 1472 } 1473 return Double.isNaN(override) || override < 0 ? 0 : override; 1474 } 1475 1476 /** 1477 * Computes the minimum width of this region. 1478 * Returns the sum of the left and right insets by default. 1479 * region subclasses should override this method to return an appropriate 1480 * value based on their content and layout strategy. If the subclass 1481 * doesn't have a VERTICAL content bias, then the height parameter can be 1482 * ignored. 1483 * 1484 * @return the computed minimum width of this region 1485 */ 1486 @Override protected double computeMinWidth(double height) { 1487 return getInsets().getLeft() + getInsets().getRight(); 1488 } 1489 1490 /** 1491 * Computes the minimum height of this region. 1492 * Returns the sum of the top and bottom insets by default. 1493 * Region subclasses should override this method to return an appropriate 1494 * value based on their content and layout strategy. If the subclass 1495 * doesn't have a HORIZONTAL content bias, then the width parameter can be 1496 * ignored. 1497 * 1498 * @return the computed minimum height for this region 1499 */ 1500 @Override protected double computeMinHeight(double width) { 1501 return getInsets().getTop() + getInsets().getBottom(); 1502 } 1503 1504 /** 1505 * Computes the preferred width of this region for the given height. 1506 * Region subclasses should override this method to return an appropriate 1507 * value based on their content and layout strategy. If the subclass 1508 * doesn't have a VERTICAL content bias, then the height parameter can be 1509 * ignored. 1510 * 1511 * @return the computed preferred width for this region 1512 */ 1513 @Override protected double computePrefWidth(double height) { 1514 final double w = super.computePrefWidth(height); 1515 return getInsets().getLeft() + w + getInsets().getRight(); 1516 } 1517 1518 /** 1519 * Computes the preferred height of this region for the given width; 1520 * Region subclasses should override this method to return an appropriate 1521 * value based on their content and layout strategy. If the subclass 1522 * doesn't have a HORIZONTAL content bias, then the width parameter can be 1523 * ignored. 1524 * 1525 * @return the computed preferred height for this region 1526 */ 1527 @Override protected double computePrefHeight(double width) { 1528 final double h = super.computePrefHeight(width); 1529 return getInsets().getTop() + h + getInsets().getBottom(); 1530 } 1531 1532 /** 1533 * Computes the maximum width for this region. 1534 * Returns Double.MAX_VALUE by default. 1535 * Region subclasses may override this method to return an different 1536 * value based on their content and layout strategy. If the subclass 1537 * doesn't have a VERTICAL content bias, then the height parameter can be 1538 * ignored. 1539 * 1540 * @return the computed maximum width for this region 1541 */ 1542 protected double computeMaxWidth(double height) { 1543 return Double.MAX_VALUE; 1544 } 1545 1546 /** 1547 * Computes the maximum height of this region. 1548 * Returns Double.MAX_VALUE by default. 1549 * Region subclasses may override this method to return a different 1550 * value based on their content and layout strategy. If the subclass 1551 * doesn't have a HORIZONTAL content bias, then the width parameter can be 1552 * ignored. 1553 * 1554 * @return the computed maximum height for this region 1555 */ 1556 protected double computeMaxHeight(double width) { 1557 return Double.MAX_VALUE; 1558 } 1559 1560 /** 1561 * If this region's snapToPixel property is true, returns a value rounded 1562 * to the nearest pixel, else returns the same value. 1563 * @param value the space value to be snapped 1564 * @return value rounded to nearest pixel 1565 */ 1566 protected double snapSpace(double value) { 1567 return snapSpace(value, isSnapToPixel()); 1568 } 1569 1570 /** 1571 * If this region's snapToPixel property is true, returns a value ceiled 1572 * to the nearest pixel, else returns the same value. 1573 * @param value the size value to be snapped 1574 * @return value ceiled to nearest pixel 1575 */ 1576 protected double snapSize(double value) { 1577 return snapSize(value, isSnapToPixel()); 1578 } 1579 1580 /** 1581 * If this region's snapToPixel property is true, returns a value rounded 1582 * to the nearest pixel, else returns the same value. 1583 * @param value the position value to be snapped 1584 * @return value rounded to nearest pixel 1585 */ 1586 protected double snapPosition(double value) { 1587 return snapPosition(value, isSnapToPixel()); 1588 } 1589 1590 double snapPortion(double value) { 1591 return snapPortion(value, isSnapToPixel()); 1592 } 1593 1594 1595 /** 1596 * Utility method to get the top inset which includes padding and border 1597 * inset. Then snapped to whole pixels if isSnapToPixel() is true. 1598 * 1599 * @since JavaFX 8.0 1600 * @return Rounded up insets top 1601 */ 1602 public final double snappedTopInset() { 1603 return snappedTopInset; 1604 } 1605 1606 /** 1607 * Utility method to get the bottom inset which includes padding and border 1608 * inset. Then snapped to whole pixels if isSnapToPixel() is true. 1609 * 1610 * @since JavaFX 8.0 1611 * @return Rounded up insets bottom 1612 */ 1613 public final double snappedBottomInset() { 1614 return snappedBottomInset; 1615 } 1616 1617 /** 1618 * Utility method to get the left inset which includes padding and border 1619 * inset. Then snapped to whole pixels if isSnapToPixel() is true. 1620 * 1621 * @since JavaFX 8.0 1622 * @return Rounded up insets left 1623 */ 1624 public final double snappedLeftInset() { 1625 return snappedLeftInset; 1626 } 1627 1628 /** 1629 * Utility method to get the right inset which includes padding and border 1630 * inset. Then snapped to whole pixels if isSnapToPixel() is true. 1631 * 1632 * @since JavaFX 8.0 1633 * @return Rounded up insets right 1634 */ 1635 public final double snappedRightInset() { 1636 return snappedRightInset; 1637 } 1638 1639 1640 double computeChildMinAreaWidth(Node child, Insets margin) { 1641 return computeChildMinAreaWidth(child, -1, margin, -1, false); 1642 } 1643 1644 double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) { 1645 final boolean snap = isSnapToPixel(); 1646 double left = margin != null? snapSpace(margin.getLeft(), snap) : 0; 1647 double right = margin != null? snapSpace(margin.getRight(), snap) : 0; 1648 double alt = -1; 1649 if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height 1650 double top = margin != null? snapSpace(margin.getTop(), snap) : 0; 1651 double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0); 1652 double bo = child.getBaselineOffset(); 1653 final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ? 1654 height - top - bottom - baselineComplement : 1655 height - top - bottom; 1656 if (fillHeight) { 1657 alt = snapSize(boundedSize( 1658 child.minHeight(-1), contentHeight, 1659 child.maxHeight(-1))); 1660 } else { 1661 alt = snapSize(boundedSize( 1662 child.minHeight(-1), 1663 child.prefHeight(-1), 1664 Math.min(child.maxHeight(-1), contentHeight))); 1665 } 1666 } 1667 return left + snapSize(child.minWidth(alt)) + right; 1668 } 1669 1670 double computeChildMinAreaHeight(Node child, Insets margin) { 1671 return computeChildMinAreaHeight(child, -1, margin, -1); 1672 } 1673 1674 double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) { 1675 final boolean snap = isSnapToPixel(); 1676 double top =margin != null? snapSpace(margin.getTop(), snap) : 0; 1677 double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0; 1678 1679 double alt = -1; 1680 if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width 1681 double left = margin != null? snapSpace(margin.getLeft(), snap) : 0; 1682 double right = margin != null? snapSpace(margin.getRight(), snap) : 0; 1683 alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) : 1684 child.maxWidth(-1)); 1685 } 1686 1687 // For explanation, see computeChildPrefAreaHeight 1688 if (minBaselineComplement != -1) { 1689 double baseline = child.getBaselineOffset(); 1690 if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) { 1691 return top + snapSize(child.minHeight(alt)) + bottom 1692 + minBaselineComplement; 1693 } else { 1694 return baseline + minBaselineComplement; 1695 } 1696 } else { 1697 return top + snapSize(child.minHeight(alt)) + bottom; 1698 } 1699 } 1700 1701 double computeChildPrefAreaWidth(Node child, Insets margin) { 1702 return computeChildPrefAreaWidth(child, -1, margin, -1, false); 1703 } 1704 1705 double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) { 1706 final boolean snap = isSnapToPixel(); 1707 double left = margin != null? snapSpace(margin.getLeft(), snap) : 0; 1708 double right = margin != null? snapSpace(margin.getRight(), snap) : 0; 1709 double alt = -1; 1710 if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height 1711 double top = margin != null? snapSpace(margin.getTop(), snap) : 0; 1712 double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0; 1713 double bo = child.getBaselineOffset(); 1714 final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ? 1715 height - top - bottom - baselineComplement : 1716 height - top - bottom; 1717 if (fillHeight) { 1718 alt = snapSize(boundedSize( 1719 child.minHeight(-1), contentHeight, 1720 child.maxHeight(-1))); 1721 } else { 1722 alt = snapSize(boundedSize( 1723 child.minHeight(-1), 1724 child.prefHeight(-1), 1725 Math.min(child.maxHeight(-1), contentHeight))); 1726 } 1727 } 1728 return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right; 1729 } 1730 1731 double computeChildPrefAreaHeight(Node child, Insets margin) { 1732 return computeChildPrefAreaHeight(child, -1, margin, -1); 1733 } 1734 1735 double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) { 1736 final boolean snap = isSnapToPixel(); 1737 double top = margin != null? snapSpace(margin.getTop(), snap) : 0; 1738 double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0; 1739 1740 double alt = -1; 1741 if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width 1742 double left = margin != null ? snapSpace(margin.getLeft(), snap) : 0; 1743 double right = margin != null ? snapSpace(margin.getRight(), snap) : 0; 1744 alt = snapSize(boundedSize( 1745 child.minWidth(-1), width != -1 ? width - left - right 1746 : child.prefWidth(-1), child.maxWidth(-1))); 1747 } 1748 1749 if (prefBaselineComplement != -1) { 1750 double baseline = child.getBaselineOffset(); 1751 if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) { 1752 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add 1753 // the preferred complement to it 1754 return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom 1755 + prefBaselineComplement; 1756 } else { 1757 // For all other Nodes, it's just their baseline and the complement. 1758 // Note that the complement already contain the Node's preferred (or fixed) height 1759 return top + baseline + prefBaselineComplement + bottom; 1760 } 1761 } else { 1762 return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom; 1763 } 1764 } 1765 1766 double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) { 1767 double max = child.maxWidth(-1); 1768 if (max == Double.MAX_VALUE) { 1769 return max; 1770 } 1771 final boolean snap = isSnapToPixel(); 1772 double left = margin != null? snapSpace(margin.getLeft(), snap) : 0; 1773 double right = margin != null? snapSpace(margin.getRight(), snap) : 0; 1774 double alt = -1; 1775 if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height 1776 double top = margin != null? snapSpace(margin.getTop(), snap) : 0; 1777 double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0); 1778 double bo = child.getBaselineOffset(); 1779 final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ? 1780 height - top - bottom - baselineComplement : 1781 height - top - bottom; 1782 if (fillHeight) { 1783 alt = snapSize(boundedSize( 1784 child.minHeight(-1), contentHeight, 1785 child.maxHeight(-1))); 1786 } else { 1787 alt = snapSize(boundedSize( 1788 child.minHeight(-1), 1789 child.prefHeight(-1), 1790 Math.min(child.maxHeight(-1), contentHeight))); 1791 } 1792 max = child.maxWidth(alt); 1793 } 1794 // if min > max, min wins, so still need to call boundedSize() 1795 return left + snapSize(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right; 1796 } 1797 1798 double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) { 1799 double max = child.maxHeight(-1); 1800 if (max == Double.MAX_VALUE) { 1801 return max; 1802 } 1803 1804 final boolean snap = isSnapToPixel(); 1805 double top = margin != null? snapSpace(margin.getTop(), snap) : 0; 1806 double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0; 1807 double alt = -1; 1808 if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width 1809 double left = margin != null? snapSpace(margin.getLeft(), snap) : 0; 1810 double right = margin != null? snapSpace(margin.getRight(), snap) : 0; 1811 alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) : 1812 child.minWidth(-1)); 1813 max = child.maxHeight(alt); 1814 } 1815 // For explanation, see computeChildPrefAreaHeight 1816 if (maxBaselineComplement != -1) { 1817 double baseline = child.getBaselineOffset(); 1818 if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) { 1819 return top + snapSize(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom 1820 + maxBaselineComplement; 1821 } else { 1822 return top + baseline + maxBaselineComplement + bottom; 1823 } 1824 } else { 1825 // if min > max, min wins, so still need to call boundedSize() 1826 return top + snapSize(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom; 1827 } 1828 } 1829 1830 /* Max of children's minimum area widths */ 1831 1832 double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins) { 1833 return getMaxAreaWidth(children, margins, new double[] { -1 }, false, true); 1834 } 1835 1836 double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> margins, double height, boolean fillHeight) { 1837 return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, true); 1838 } 1839 1840 double computeMaxMinAreaWidth(List<Node> children, Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight) { 1841 return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, true); 1842 } 1843 1844 /* Max of children's minimum area heights */ 1845 1846 double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) { 1847 return getMaxAreaHeight(children, margins, null, valignment, true); 1848 } 1849 1850 double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment, double width) { 1851 return getMaxAreaHeight(children, margins, new double[] { width }, valignment, true); 1852 } 1853 1854 double computeMaxMinAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) { 1855 return getMaxAreaHeight(children, childMargins, childWidths, valignment, true); 1856 } 1857 1858 /* Max of children's pref area widths */ 1859 1860 double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins) { 1861 return getMaxAreaWidth(children, margins, new double[] { -1 }, false, false); 1862 } 1863 1864 double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> margins, double height, 1865 boolean fillHeight) { 1866 return getMaxAreaWidth(children, margins, new double[] { height }, fillHeight, false); 1867 } 1868 1869 double computeMaxPrefAreaWidth(List<Node>children, Callback<Node, Insets> childMargins, 1870 double childHeights[], boolean fillHeight) { 1871 return getMaxAreaWidth(children, childMargins, childHeights, fillHeight, false); 1872 } 1873 1874 /* Max of children's pref area heights */ 1875 1876 double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, VPos valignment) { 1877 return getMaxAreaHeight(children, margins, null, valignment, false); 1878 } 1879 1880 double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> margins, double width, VPos valignment) { 1881 return getMaxAreaHeight(children, margins, new double[] { width }, valignment, false); 1882 } 1883 1884 double computeMaxPrefAreaHeight(List<Node>children, Callback<Node, Insets> childMargins, double childWidths[], VPos valignment) { 1885 return getMaxAreaHeight(children, childMargins, childWidths, valignment, false); 1886 } 1887 1888 /** 1889 * Returns the size of a Node that should be placed in an area of the specified size, 1890 * bounded in it's min/max size, respecting bias. 1891 * 1892 * @param node the node 1893 * @param areaWidth the width of the bounding area where the node is going to be placed 1894 * @param areaHeight the height of the bounding area where the node is going to be placed 1895 * @param fillWidth if Node should try to fill the area width 1896 * @param fillHeight if Node should try to fill the area height 1897 * @param result Vec2d object for the result or null if new one should be created 1898 * @return Vec2d object with width(x parameter) and height (y parameter) 1899 */ 1900 static Vec2d boundedNodeSizeWithBias(Node node, double areaWidth, double areaHeight, 1901 boolean fillWidth, boolean fillHeight, Vec2d result) { 1902 if (result == null) { 1903 result = new Vec2d(); 1904 } 1905 1906 Orientation bias = node.getContentBias(); 1907 1908 double childWidth = 0; 1909 double childHeight = 0; 1910 1911 if (bias == null) { 1912 childWidth = boundedSize( 1913 node.minWidth(-1), fillWidth ? areaWidth 1914 : Math.min(areaWidth, node.prefWidth(-1)), 1915 node.maxWidth(-1)); 1916 childHeight = boundedSize( 1917 node.minHeight(-1), fillHeight ? areaHeight 1918 : Math.min(areaHeight, node.prefHeight(-1)), 1919 node.maxHeight(-1)); 1920 1921 } else if (bias == Orientation.HORIZONTAL) { 1922 childWidth = boundedSize( 1923 node.minWidth(-1), fillWidth ? areaWidth 1924 : Math.min(areaWidth, node.prefWidth(-1)), 1925 node.maxWidth(-1)); 1926 childHeight = boundedSize( 1927 node.minHeight(childWidth), fillHeight ? areaHeight 1928 : Math.min(areaHeight, node.prefHeight(childWidth)), 1929 node.maxHeight(childWidth)); 1930 1931 } else { // bias == VERTICAL 1932 childHeight = boundedSize( 1933 node.minHeight(-1), fillHeight ? areaHeight 1934 : Math.min(areaHeight, node.prefHeight(-1)), 1935 node.maxHeight(-1)); 1936 childWidth = boundedSize( 1937 node.minWidth(childHeight), fillWidth ? areaWidth 1938 : Math.min(areaWidth, node.prefWidth(childHeight)), 1939 node.maxWidth(childHeight)); 1940 } 1941 1942 result.set(childWidth, childHeight); 1943 return result; 1944 } 1945 1946 /* utility method for computing the max of children's min or pref heights, taking into account baseline alignment */ 1947 private double getMaxAreaHeight(List<Node> children, Callback<Node,Insets> childMargins, double childWidths[], VPos valignment, boolean minimum) { 1948 final double singleChildWidth = childWidths == null ? -1 : childWidths.length == 1 ? childWidths[0] : Double.NaN; 1949 if (valignment == VPos.BASELINE) { 1950 double maxAbove = 0; 1951 double maxBelow = 0; 1952 for (int i = 0, maxPos = children.size(); i < maxPos; i++) { 1953 final Node child = children.get(i); 1954 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth; 1955 Insets margin = childMargins.call(child); 1956 final double top = margin != null? snapSpace(margin.getTop()) : 0; 1957 final double bottom = margin != null? snapSpace(margin.getBottom()) : 0; 1958 final double baseline = child.getBaselineOffset(); 1959 1960 final double childHeight = minimum? snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth)); 1961 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) { 1962 maxAbove = Math.max(maxAbove, childHeight + top); 1963 } else { 1964 maxAbove = Math.max(maxAbove, baseline + top); 1965 maxBelow = Math.max(maxBelow, 1966 snapSpace(minimum?snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth))) - 1967 baseline + bottom); 1968 } 1969 } 1970 return maxAbove + maxBelow; //remind(aim): ceil this value? 1971 } else { 1972 double max = 0; 1973 for (int i = 0, maxPos = children.size(); i < maxPos; i++) { 1974 final Node child = children.get(i); 1975 Insets margin = childMargins.call(child); 1976 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth; 1977 max = Math.max(max, minimum? 1978 computeChildMinAreaHeight(child, -1, margin, childWidth) : 1979 computeChildPrefAreaHeight(child, -1, margin, childWidth)); 1980 } 1981 return max; 1982 } 1983 } 1984 1985 /* utility method for computing the max of children's min or pref width, horizontal alignment is ignored for now */ 1986 private double getMaxAreaWidth(List<javafx.scene.Node> children, 1987 Callback<Node, Insets> childMargins, double childHeights[], boolean fillHeight, boolean minimum) { 1988 final double singleChildHeight = childHeights == null ? -1 : childHeights.length == 1 ? childHeights[0] : Double.NaN; 1989 1990 double max = 0; 1991 for (int i = 0, maxPos = children.size(); i < maxPos; i++) { 1992 final Node child = children.get(i); 1993 final Insets margin = childMargins.call(child); 1994 final double childHeight = Double.isNaN(singleChildHeight) ? childHeights[i] : singleChildHeight; 1995 max = Math.max(max, minimum? 1996 computeChildMinAreaWidth(children.get(i), -1, margin, childHeight, fillHeight) : 1997 computeChildPrefAreaWidth(child, -1, margin, childHeight, fillHeight)); 1998 } 1999 return max; 2000 } 2001 2002 /** 2003 * Utility method which positions the child within an area of this 2004 * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2005 * with a baseline offset relative to that area. 2006 * <p> 2007 * This function does <i>not</i> resize the node and uses the node's layout bounds 2008 * width and height to determine how it should be positioned within the area. 2009 * <p> 2010 * If the vertical alignment is {@code VPos.BASELINE} then it 2011 * will position the node so that its own baseline aligns with the passed in 2012 * {@code baselineOffset}, otherwise the baseline parameter is ignored. 2013 * <p> 2014 * If {@code snapToPixel} is {@code true} for this region, then the x/y position 2015 * values will be rounded to their nearest pixel boundaries. 2016 * 2017 * @param child the child being positioned within this region 2018 * @param areaX the horizontal offset of the layout area relative to this region 2019 * @param areaY the vertical offset of the layout area relative to this region 2020 * @param areaWidth the width of the layout area 2021 * @param areaHeight the height of the layout area 2022 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2023 * @param halignment the horizontal alignment for the child within the area 2024 * @param valignment the vertical alignment for the child within the area 2025 * 2026 */ 2027 protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, 2028 double areaBaselineOffset, HPos halignment, VPos valignment) { 2029 positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 2030 Insets.EMPTY, halignment, valignment, isSnapToPixel()); 2031 } 2032 2033 /** 2034 * Utility method which positions the child within an area of this 2035 * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2036 * with a baseline offset relative to that area. 2037 * <p> 2038 * This function does <i>not</i> resize the node and uses the node's layout bounds 2039 * width and height to determine how it should be positioned within the area. 2040 * <p> 2041 * If the vertical alignment is {@code VPos.BASELINE} then it 2042 * will position the node so that its own baseline aligns with the passed in 2043 * {@code baselineOffset}, otherwise the baseline parameter is ignored. 2044 * <p> 2045 * If {@code snapToPixel} is {@code true} for this region, then the x/y position 2046 * values will be rounded to their nearest pixel boundaries. 2047 * <p> 2048 * If {@code margin} is non-null, then that space will be allocated around the 2049 * child within the layout area. margin may be null. 2050 * 2051 * @param child the child being positioned within this region 2052 * @param areaX the horizontal offset of the layout area relative to this region 2053 * @param areaY the vertical offset of the layout area relative to this region 2054 * @param areaWidth the width of the layout area 2055 * @param areaHeight the height of the layout area 2056 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2057 * @param margin the margin of space to be allocated around the child 2058 * @param halignment the horizontal alignment for the child within the area 2059 * @param valignment the vertical alignment for the child within the area 2060 * 2061 * @since JavaFX 8.0 2062 */ 2063 public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, 2064 double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) { 2065 Insets childMargin = margin != null? margin : Insets.EMPTY; 2066 2067 position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 2068 snapSpace(childMargin.getTop(), isSnapToPixel), 2069 snapSpace(childMargin.getRight(), isSnapToPixel), 2070 snapSpace(childMargin.getBottom(), isSnapToPixel), 2071 snapSpace(childMargin.getLeft(), isSnapToPixel), 2072 halignment, valignment, isSnapToPixel); 2073 } 2074 2075 /** 2076 * Utility method which lays out the child within an area of this 2077 * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2078 * with a baseline offset relative to that area. 2079 * <p> 2080 * If the child is resizable, this method will resize it to fill the specified 2081 * area unless the node's maximum size prevents it. If the node's maximum 2082 * size preference is less than the area size, the maximum size will be used. 2083 * If node's maximum is greater than the area size, then the node will be 2084 * resized to fit within the area, unless its minimum size prevents it. 2085 * <p> 2086 * If the child has a non-null contentBias, then this method will use it when 2087 * resizing the child. If the contentBias is horizontal, it will set its width 2088 * first to the area's width (up to the child's max width limit) and then pass 2089 * that value to compute the child's height. If child's contentBias is vertical, 2090 * then it will set its height to the area height (up to child's max height limit) 2091 * and pass that height to compute the child's width. If the child's contentBias 2092 * is null, then it's width and height have no dependencies on each other. 2093 * <p> 2094 * If the child is not resizable (Shape, Group, etc) then it will only be 2095 * positioned and not resized. 2096 * <p> 2097 * If the child's resulting size differs from the area's size (either 2098 * because it was not resizable or it's sizing preferences prevented it), then 2099 * this function will align the node relative to the area using horizontal and 2100 * vertical alignment values. 2101 * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned 2102 * with the area baseline offset parameter, otherwise the baseline parameter 2103 * is ignored. 2104 * <p> 2105 * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y 2106 * values will be rounded to their nearest pixel boundaries and the 2107 * width/height values will be ceiled to the next pixel boundary. 2108 * 2109 * @param child the child being positioned within this region 2110 * @param areaX the horizontal offset of the layout area relative to this region 2111 * @param areaY the vertical offset of the layout area relative to this region 2112 * @param areaWidth the width of the layout area 2113 * @param areaHeight the height of the layout area 2114 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2115 * @param halignment the horizontal alignment for the child within the area 2116 * @param valignment the vertical alignment for the child within the area 2117 * 2118 */ 2119 protected void layoutInArea(Node child, double areaX, double areaY, 2120 double areaWidth, double areaHeight, 2121 double areaBaselineOffset, 2122 HPos halignment, VPos valignment) { 2123 layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 2124 Insets.EMPTY, halignment, valignment); 2125 } 2126 2127 /** 2128 * Utility method which lays out the child within an area of this 2129 * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2130 * with a baseline offset relative to that area. 2131 * <p> 2132 * If the child is resizable, this method will resize it to fill the specified 2133 * area unless the node's maximum size prevents it. If the node's maximum 2134 * size preference is less than the area size, the maximum size will be used. 2135 * If node's maximum is greater than the area size, then the node will be 2136 * resized to fit within the area, unless its minimum size prevents it. 2137 * <p> 2138 * If the child has a non-null contentBias, then this method will use it when 2139 * resizing the child. If the contentBias is horizontal, it will set its width 2140 * first to the area's width (up to the child's max width limit) and then pass 2141 * that value to compute the child's height. If child's contentBias is vertical, 2142 * then it will set its height to the area height (up to child's max height limit) 2143 * and pass that height to compute the child's width. If the child's contentBias 2144 * is null, then it's width and height have no dependencies on each other. 2145 * <p> 2146 * If the child is not resizable (Shape, Group, etc) then it will only be 2147 * positioned and not resized. 2148 * <p> 2149 * If the child's resulting size differs from the area's size (either 2150 * because it was not resizable or it's sizing preferences prevented it), then 2151 * this function will align the node relative to the area using horizontal and 2152 * vertical alignment values. 2153 * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned 2154 * with the area baseline offset parameter, otherwise the baseline parameter 2155 * is ignored. 2156 * <p> 2157 * If {@code margin} is non-null, then that space will be allocated around the 2158 * child within the layout area. margin may be null. 2159 * <p> 2160 * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y 2161 * values will be rounded to their nearest pixel boundaries and the 2162 * width/height values will be ceiled to the next pixel boundary. 2163 * 2164 * @param child the child being positioned within this region 2165 * @param areaX the horizontal offset of the layout area relative to this region 2166 * @param areaY the vertical offset of the layout area relative to this region 2167 * @param areaWidth the width of the layout area 2168 * @param areaHeight the height of the layout area 2169 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2170 * @param margin the margin of space to be allocated around the child 2171 * @param halignment the horizontal alignment for the child within the area 2172 * @param valignment the vertical alignment for the child within the area 2173 */ 2174 protected void layoutInArea(Node child, double areaX, double areaY, 2175 double areaWidth, double areaHeight, 2176 double areaBaselineOffset, 2177 Insets margin, 2178 HPos halignment, VPos valignment) { 2179 layoutInArea(child, areaX, areaY, areaWidth, areaHeight, 2180 areaBaselineOffset, margin, true, true, halignment, valignment); 2181 } 2182 2183 /** 2184 * Utility method which lays out the child within an area of this 2185 * region defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2186 * with a baseline offset relative to that area. 2187 * <p> 2188 * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight} 2189 * to determine whether to resize it to fill the area or keep the child at its 2190 * preferred dimension. If fillWidth/fillHeight are true, then this method 2191 * will only resize the child up to its max size limits. If the node's maximum 2192 * size preference is less than the area size, the maximum size will be used. 2193 * If node's maximum is greater than the area size, then the node will be 2194 * resized to fit within the area, unless its minimum size prevents it. 2195 * <p> 2196 * If the child has a non-null contentBias, then this method will use it when 2197 * resizing the child. If the contentBias is horizontal, it will set its width 2198 * first and then pass that value to compute the child's height. If child's 2199 * contentBias is vertical, then it will set its height first 2200 * and pass that value to compute the child's width. If the child's contentBias 2201 * is null, then it's width and height have no dependencies on each other. 2202 * <p> 2203 * If the child is not resizable (Shape, Group, etc) then it will only be 2204 * positioned and not resized. 2205 * <p> 2206 * If the child's resulting size differs from the area's size (either 2207 * because it was not resizable or it's sizing preferences prevented it), then 2208 * this function will align the node relative to the area using horizontal and 2209 * vertical alignment values. 2210 * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned 2211 * with the area baseline offset parameter, otherwise the baseline parameter 2212 * is ignored. 2213 * <p> 2214 * If {@code margin} is non-null, then that space will be allocated around the 2215 * child within the layout area. margin may be null. 2216 * <p> 2217 * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y 2218 * values will be rounded to their nearest pixel boundaries and the 2219 * width/height values will be ceiled to the next pixel boundary. 2220 * 2221 * @param child the child being positioned within this region 2222 * @param areaX the horizontal offset of the layout area relative to this region 2223 * @param areaY the vertical offset of the layout area relative to this region 2224 * @param areaWidth the width of the layout area 2225 * @param areaHeight the height of the layout area 2226 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2227 * @param margin the margin of space to be allocated around the child 2228 * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width 2229 * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height 2230 * @param halignment the horizontal alignment for the child within the area 2231 * @param valignment the vertical alignment for the child within the area 2232 */ 2233 protected void layoutInArea(Node child, double areaX, double areaY, 2234 double areaWidth, double areaHeight, 2235 double areaBaselineOffset, 2236 Insets margin, boolean fillWidth, boolean fillHeight, 2237 HPos halignment, VPos valignment) { 2238 layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, isSnapToPixel()); 2239 } 2240 2241 /** 2242 * Utility method which lays out the child within an area of it's 2243 * parent defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, 2244 * with a baseline offset relative to that area. 2245 * <p> 2246 * If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight} 2247 * to determine whether to resize it to fill the area or keep the child at its 2248 * preferred dimension. If fillWidth/fillHeight are true, then this method 2249 * will only resize the child up to its max size limits. If the node's maximum 2250 * size preference is less than the area size, the maximum size will be used. 2251 * If node's maximum is greater than the area size, then the node will be 2252 * resized to fit within the area, unless its minimum size prevents it. 2253 * <p> 2254 * If the child has a non-null contentBias, then this method will use it when 2255 * resizing the child. If the contentBias is horizontal, it will set its width 2256 * first and then pass that value to compute the child's height. If child's 2257 * contentBias is vertical, then it will set its height first 2258 * and pass that value to compute the child's width. If the child's contentBias 2259 * is null, then it's width and height have no dependencies on each other. 2260 * <p> 2261 * If the child is not resizable (Shape, Group, etc) then it will only be 2262 * positioned and not resized. 2263 * <p> 2264 * If the child's resulting size differs from the area's size (either 2265 * because it was not resizable or it's sizing preferences prevented it), then 2266 * this function will align the node relative to the area using horizontal and 2267 * vertical alignment values. 2268 * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned 2269 * with the area baseline offset parameter, otherwise the baseline parameter 2270 * is ignored. 2271 * <p> 2272 * If {@code margin} is non-null, then that space will be allocated around the 2273 * child within the layout area. margin may be null. 2274 * <p> 2275 * If {@code snapToPixel} is {@code true} for this region, then the resulting x,y 2276 * values will be rounded to their nearest pixel boundaries and the 2277 * width/height values will be ceiled to the next pixel boundary. 2278 * 2279 * @param child the child being positioned within this region 2280 * @param areaX the horizontal offset of the layout area relative to this region 2281 * @param areaY the vertical offset of the layout area relative to this region 2282 * @param areaWidth the width of the layout area 2283 * @param areaHeight the height of the layout area 2284 * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE 2285 * @param margin the margin of space to be allocated around the child 2286 * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width 2287 * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height 2288 * @param halignment the horizontal alignment for the child within the area 2289 * @param valignment the vertical alignment for the child within the area 2290 * @param isSnapToPixel whether to snap size and position to pixels 2291 * @since JavaFX 8.0 2292 */ 2293 public static void layoutInArea(Node child, double areaX, double areaY, 2294 double areaWidth, double areaHeight, 2295 double areaBaselineOffset, 2296 Insets margin, boolean fillWidth, boolean fillHeight, 2297 HPos halignment, VPos valignment, boolean isSnapToPixel) { 2298 2299 Insets childMargin = margin != null ? margin : Insets.EMPTY; 2300 2301 double top = snapSpace(childMargin.getTop(), isSnapToPixel); 2302 double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel); 2303 double left = snapSpace(childMargin.getLeft(), isSnapToPixel); 2304 double right = snapSpace(childMargin.getRight(), isSnapToPixel); 2305 2306 if (valignment == VPos.BASELINE) { 2307 double bo = child.getBaselineOffset(); 2308 if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) { 2309 if (child.isResizable()) { 2310 // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot 2311 // be resized to this area 2312 bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel); 2313 } else { 2314 top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel); 2315 } 2316 } else { 2317 top = snapSpace(areaBaselineOffset - bo, isSnapToPixel); 2318 } 2319 } 2320 2321 2322 if (child.isResizable()) { 2323 Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom, 2324 fillWidth, fillHeight, TEMP_VEC2D); 2325 child.resize(snapSize(size.x, isSnapToPixel),snapSize(size.y, isSnapToPixel)); 2326 } 2327 position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 2328 top, right, bottom, left, halignment, valignment, isSnapToPixel); 2329 } 2330 2331 private static void position(Node child, double areaX, double areaY, double areaWidth, double areaHeight, 2332 double areaBaselineOffset, 2333 double topMargin, double rightMargin, double bottomMargin, double leftMargin, 2334 HPos hpos, VPos vpos, boolean isSnapToPixel) { 2335 final double xoffset = leftMargin + computeXOffset(areaWidth - leftMargin - rightMargin, 2336 child.getLayoutBounds().getWidth(), hpos); 2337 final double yoffset; 2338 if (vpos == VPos.BASELINE) { 2339 double bo = child.getBaselineOffset(); 2340 if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) { 2341 // We already know the layout bounds at this stage, so we can use them 2342 yoffset = areaBaselineOffset - child.getLayoutBounds().getHeight(); 2343 } else { 2344 yoffset = areaBaselineOffset - bo; 2345 } 2346 } else { 2347 yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin, 2348 child.getLayoutBounds().getHeight(), vpos); 2349 } 2350 final double x = snapPosition(areaX + xoffset, isSnapToPixel); 2351 final double y = snapPosition(areaY + yoffset, isSnapToPixel); 2352 2353 child.relocate(x,y); 2354 } 2355 2356 /************************************************************************** 2357 * * 2358 * PG Implementation * 2359 * * 2360 **************************************************************************/ 2361 2362 /** @treatAsPrivate */ 2363 @Override public void impl_updatePeer() { 2364 // TODO I think we have a bug, where if you create a Region with an Image that hasn't 2365 // been loaded, we have no listeners on that image so as to cause a pulse & repaint 2366 // to happen once the image is loaded. We just assume the image has been loaded 2367 // (since when the image is created using new Image(url) or CSS it happens eagerly). 2368 super.impl_updatePeer(); 2369 if (_shape != null) _shape.impl_syncPeer(); 2370 NGRegion pg = impl_getPeer(); 2371 2372 if (!cornersValid) { 2373 validateCorners(); 2374 } 2375 2376 final boolean sizeChanged = impl_isDirty(DirtyBits.NODE_GEOMETRY); 2377 if (sizeChanged) { 2378 pg.setSize((float)getWidth(), (float)getHeight()); 2379 } 2380 2381 // NOTE: The order here is very important. There is logic in NGRegion which determines 2382 // whether we can cache an image representing this region, and for this to work correctly, 2383 // the shape must be specified before the background which is before the border. 2384 final boolean shapeChanged = impl_isDirty(DirtyBits.REGION_SHAPE); 2385 if (shapeChanged) { 2386 pg.updateShape(_shape, isScaleShape(), isCenterShape(), isCacheShape()); 2387 } 2388 2389 // The normalized corners can always be updated since they require no 2390 // processing at the NG layer. 2391 pg.updateFillCorners(normalizedFillCorners); 2392 final boolean backgroundChanged = impl_isDirty(DirtyBits.SHAPE_FILL); 2393 final Background bg = getBackground(); 2394 if (backgroundChanged) { 2395 pg.updateBackground(bg); 2396 } 2397 2398 // This will be true if an image that makes up the background or border of this 2399 // region has changed, such that we need to redraw the region. 2400 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 2401 pg.imagesUpdated(); 2402 } 2403 2404 // The normalized corners can always be updated since they require no 2405 // processing at the NG layer. 2406 pg.updateStrokeCorners(normalizedStrokeCorners); 2407 if (impl_isDirty(DirtyBits.SHAPE_STROKE)) { 2408 pg.updateBorder(getBorder()); 2409 } 2410 2411 // TODO given the note above, this *must* be called when an image which makes up the 2412 // background images and border images changes (is loaded) if it was being loaded asynchronously 2413 // Also note, one day we can add support for automatic opaque insets determination for border images. 2414 // However right now it is impractical because the image pixel format is almost undoubtedly going 2415 // to have alpha, and so without inspecting the source image's actual pixels for the filled center 2416 // we can't automatically determine whether the interior is filled. 2417 if (sizeChanged || backgroundChanged || shapeChanged) { 2418 // These are the opaque insets, as specified by the developer in code or CSS. If null, 2419 // then we must compute the opaque insets. If not null, then we will still compute the 2420 // opaque insets and combine them with these insets, as appropriate. We do ignore these 2421 // developer specified insets in cases where we know without a doubt that the developer 2422 // gave us bad data. 2423 final Insets i = getOpaqueInsets(); 2424 2425 // If the background is determined by a shape, then we don't attempt to calculate the 2426 // opaque insets. If the developer specified opaque insets, we will use them, otherwise 2427 // we will make sure the opaque insets are cleared 2428 if (_shape != null) { 2429 if (i != null) { 2430 pg.setOpaqueInsets((float) i.getTop(), (float) i.getRight(), 2431 (float) i.getBottom(), (float) i.getLeft()); 2432 } else { 2433 pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN); 2434 } 2435 } else { 2436 // This is a rectangle (not shape) region. The opaque insets must be calculated, 2437 // even if the developer has supplied their own opaque insets. The first (and cheapest) 2438 // check is whether the region has any backgrounds at all. If not, then 2439 // we will ignore the developer supplied insets because they are clearly wrong. 2440 if (bg == null || bg.isEmpty()) { 2441 pg.setOpaqueInsets(Float.NaN, Float.NaN, Float.NaN, Float.NaN); 2442 } else { 2443 // There is a background, so it is conceivable that there are 2444 // opaque insets. From this point on, we have to honor the developer's supplied 2445 // insets, only expanding them if we know for certain the opaque insets are 2446 // bigger than what was supplied by the developer. Start by defining our initial 2447 // values for top, right, bottom, and left. If the developer supplied us 2448 // insets, use those. Otherwise initialize to NaN. Note that the developer may 2449 // also have given us NaN values (so we'd have to check for these anyway). We use 2450 // NaN to mean "not defined". 2451 final double[] trbl = new double[4]; 2452 bg.computeOpaqueInsets(getWidth(), getHeight(), trbl); 2453 2454 if (i != null) { 2455 trbl[0] = Double.isNaN(trbl[0]) ? i.getTop() : Double.isNaN(i.getTop()) ? trbl[0] : Math.min(trbl[0], i.getTop()); 2456 trbl[1] = Double.isNaN(trbl[1]) ? i.getRight() : Double.isNaN(i.getRight()) ? trbl[1] : Math.min(trbl[1], i.getRight()); 2457 trbl[2] = Double.isNaN(trbl[2]) ? i.getBottom() : Double.isNaN(i.getBottom()) ? trbl[2] : Math.min(trbl[2], i.getBottom()); 2458 trbl[3] = Double.isNaN(trbl[3]) ? i.getLeft() : Double.isNaN(i.getLeft()) ? trbl[3] : Math.min(trbl[3], i.getLeft()); 2459 } 2460 2461 // Now set the insets onto the peer. Passing NaN here is perfectly 2462 // acceptable (even encouraged, to mean "unknown" or "disabled"). 2463 pg.setOpaqueInsets((float) trbl[0], (float) trbl[1], (float) trbl[2], (float) trbl[3]); 2464 } 2465 } 2466 } 2467 } 2468 2469 /** @treatAsPrivate */ 2470 @Override public NGNode impl_createPeer() { 2471 return new NGRegion(); 2472 } 2473 2474 /** 2475 * Transform x, y in local Region coordinates to local coordinates of scaled/centered shape and 2476 * check if the shape contains the coordinates. 2477 * The transformations here are basically an inversion of transformations being done in NGShape#resizeShape. 2478 */ 2479 private boolean shapeContains(com.sun.javafx.geom.Shape shape, 2480 final double x, final double y, 2481 double topOffset, double rightOffset, double bottomOffset, double leftOffset) { 2482 double resX = x; 2483 double resY = y; 2484 // The bounds of the shape, before any centering / scaling takes place 2485 final RectBounds bounds = shape.getBounds(); 2486 if (isScaleShape()) { 2487 // Modify the transform to scale the shape so that it will fit 2488 // within the insets. 2489 resX -= leftOffset; 2490 resY -= topOffset; 2491 2492 //denominator represents the width and height of the box within which the new shape must fit. 2493 resX *= bounds.getWidth() / (getWidth() - leftOffset - rightOffset); 2494 resY *= bounds.getHeight() / (getHeight() - topOffset - bottomOffset); 2495 2496 // If we also need to center it, we need to adjust the transform so as to place 2497 // the shape in the center of the bounds 2498 if (isCenterShape()) { 2499 resX += bounds.getMinX(); 2500 resY += bounds.getMinY(); 2501 } 2502 } else if (isCenterShape()) { 2503 // We are only centering. In this case, what we want is for the 2504 // original shape to be centered. If there are offsets (insets) 2505 // then we must pre-scale about the center to account for it. 2506 2507 double boundsWidth = bounds.getWidth(); 2508 double boundsHeight = bounds.getHeight(); 2509 2510 double scaleFactorX = boundsWidth / (boundsWidth - leftOffset - rightOffset); 2511 double scaleFactorY = boundsHeight / (boundsHeight - topOffset - bottomOffset); 2512 2513 //This is equivalent to: 2514 // translate(bounds.getMinX(), bounds.getMinY()) 2515 // scale(scaleFactorX, scaleFactorY) 2516 // translate(-bounds.getMinX(), -bounds.getMinY()) 2517 // translate(-leftOffset - (getWidth() - boundsWidth)/2 + bounds.getMinX(), 2518 // -topOffset - (getHeight() - boundsHeight)/2 + bounds.getMinY()); 2519 // which is an inversion of an transformation done to the shape 2520 // This gives us 2521 // 2522 //resX = resX * scaleFactorX - scaleFactorX * bounds.getMinX() - scaleFactorX * (leftOffset + (getWidth() - boundsWidth) / 2 - bounds.getMinX()) + bounds.getMinX(); 2523 //resY = resY * scaleFactorY - scaleFactorY * bounds.getMinY() - scaleFactorY * (topOffset + (getHeight() - boundsHeight) / 2 - bounds.getMinY()) + bounds.getMinY(); 2524 // 2525 // which can further reduced to 2526 2527 resX = scaleFactorX * (resX -(leftOffset + (getWidth() - boundsWidth) / 2)) + bounds.getMinX(); 2528 resY = scaleFactorY * (resY -(topOffset + (getHeight() - boundsHeight) / 2)) + bounds.getMinY(); 2529 2530 } else if (topOffset != 0 || rightOffset != 0 || bottomOffset != 0 || leftOffset != 0) { 2531 // We are neither centering nor scaling, but we still have to resize the 2532 // shape because we have to fit within the bounds defined by the offsets 2533 double scaleFactorX = bounds.getWidth() / (bounds.getWidth() - leftOffset - rightOffset); 2534 double scaleFactorY = bounds.getHeight() / (bounds.getHeight() - topOffset - bottomOffset); 2535 2536 // This is equivalent to: 2537 // translate(bounds.getMinX(), bounds.getMinY()) 2538 // scale(scaleFactorX, scaleFactorY) 2539 // translate(-bounds.getMinX(), -bounds.getMinY()) 2540 // translate(-leftOffset, -topOffset) 2541 // 2542 // which is an inversion of an transformation done to the shape 2543 // This gives us 2544 // 2545 //resX = resX * scaleFactorX - scaleFactorX * leftOffset - scaleFactorX * bounds.getMinX() + bounds.getMinX(); 2546 //resY = resY * scaleFactorY - scaleFactorY * topOffset - scaleFactorY * bounds.getMinY() + bounds.getMinY(); 2547 // 2548 // which can be further reduceD to 2549 resX = scaleFactorX * (resX - leftOffset - bounds.getMinX()) + bounds.getMinX(); 2550 resY = scaleFactorY * (resY - topOffset - bounds.getMinY()) + bounds.getMinY(); 2551 2552 } 2553 return shape.contains((float)resX, (float)resY); 2554 } 2555 2556 /** 2557 * @treatAsPrivate implementation detail 2558 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2559 */ 2560 @Deprecated 2561 @Override protected boolean impl_computeContains(double localX, double localY) { 2562 // NOTE: This method only gets called if a quick check of bounds has already 2563 // occurred, so there is no need to test against bound again. We know that the 2564 // point (localX, localY) falls within the bounds of this node, now we need 2565 // to determine if it falls within the geometry of this node. 2566 // Also note that because Region defaults pickOnBounds to true, this code is 2567 // not usually executed. It will only be executed if pickOnBounds is set to false. 2568 2569 final double x2 = getWidth(); 2570 final double y2 = getHeight(); 2571 2572 final Background background = getBackground(); 2573 // First check the shape. Shape could be impacted by scaleShape & positionShape properties. 2574 if (_shape != null) { 2575 if (background != null && !background.getFills().isEmpty()) { 2576 final List<BackgroundFill> fills = background.getFills(); 2577 double topO = Double.MAX_VALUE; 2578 double leftO = Double.MAX_VALUE; 2579 double bottomO = Double.MAX_VALUE; 2580 double rightO = Double.MAX_VALUE; 2581 for (int i = 0, max = fills.size(); i < max; i++) { 2582 BackgroundFill bf = fills.get(0); 2583 topO = Math.min(topO, bf.getInsets().getTop()); 2584 leftO = Math.min(leftO, bf.getInsets().getLeft()); 2585 bottomO = Math.min(bottomO, bf.getInsets().getBottom()); 2586 rightO = Math.min(rightO, bf.getInsets().getRight()); 2587 } 2588 return shapeContains(_shape.impl_configShape(), localX, localY, topO, leftO, bottomO, rightO); 2589 } 2590 return false; 2591 } 2592 2593 // OK, there was no background shape, so I'm going to work on the principle of 2594 // nested rounded rectangles. We'll start by checking the backgrounds. The 2595 // first background which passes the test is good enough for us! 2596 if (background != null) { 2597 final List<BackgroundFill> fills = background.getFills(); 2598 for (int i = 0, max = fills.size(); i < max; i++) { 2599 final BackgroundFill bgFill = fills.get(i); 2600 if (contains(localX, localY, 0, 0, x2, y2, bgFill.getInsets(), getNormalizedFillCorner(i))) { 2601 return true; 2602 } 2603 } 2604 } 2605 2606 // If we are here then either there were no background fills or there were no background 2607 // fills which contained the point, and the region is not defined by a shape. 2608 final Border border = getBorder(); 2609 if (border != null) { 2610 // Check all the stroke borders first. If the pick occurs on any stroke border 2611 // then we consider the contains test to have passed. Semantically we will treat a Region 2612 // with a border as if it were a rectangle with a stroke but no fill. 2613 final List<BorderStroke> strokes = border.getStrokes(); 2614 for (int i=0, max=strokes.size(); i<max; i++) { 2615 final BorderStroke strokeBorder = strokes.get(i); 2616 if (contains(localX, localY, 0, 0, x2, y2, strokeBorder.getWidths(), false, strokeBorder.getInsets(), 2617 getNormalizedStrokeCorner(i))) { 2618 return true; 2619 } 2620 } 2621 2622 // Check the image borders. We treat the image border as though it is opaque. 2623 final List<BorderImage> images = border.getImages(); 2624 for (int i = 0, max = images.size(); i < max; i++) { 2625 final BorderImage borderImage = images.get(i); 2626 if (contains(localX, localY, 0, 0, x2, y2, borderImage.getWidths(), borderImage.isFilled(), 2627 borderImage.getInsets(), CornerRadii.EMPTY)) { 2628 return true; 2629 } 2630 } 2631 } 2632 return false; 2633 } 2634 2635 /** 2636 * Basically we will perform two contains tests. For a point to be on the stroke, it must 2637 * be within the outermost edge of the stroke, but outside the innermost edge of the stroke. 2638 * Unless it is filled, in which case it is really just a normal contains test. 2639 * 2640 * @param px The x position of the point to test 2641 * @param py The y position of the point to test 2642 * @param x1 The x1 position of the bounds to test 2643 * @param y1 The y1 position of the bounds to test 2644 * @param x2 The x2 position of the bounds to test 2645 * @param y2 The y2 position of the bounds to test 2646 * @param widths The widths of the stroke on each side 2647 * @param filled Whether the area is filled or is just stroked 2648 * @param insets The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test 2649 * @param rad The corner radii to test with. Must not be null. 2650 * @param maxRadius The maximum possible radius value 2651 * @return True if (px, py) is within the stroke, taking into account insets and corner radii. 2652 */ 2653 private boolean contains(final double px, final double py, 2654 final double x1, final double y1, final double x2, final double y2, 2655 BorderWidths widths, boolean filled, 2656 final Insets insets, final CornerRadii rad) { 2657 if (filled) { 2658 if (contains(px, py, x1, y1, x2, y2, insets, rad)) { 2659 return true; 2660 } 2661 } else { 2662 boolean insideOuterEdge = contains(px, py, x1, y1, x2, y2, insets, rad); 2663 if (insideOuterEdge) { 2664 boolean outsideInnerEdge = !contains(px, py, 2665 x1 + (widths.isLeftAsPercentage() ? getWidth() * widths.getLeft() : widths.getLeft()), 2666 y1 + (widths.isTopAsPercentage() ? getHeight() * widths.getTop() : widths.getTop()), 2667 x2 - (widths.isRightAsPercentage() ? getWidth() * widths.getRight() : widths.getRight()), 2668 y2 - (widths.isBottomAsPercentage() ? getHeight() * widths.getBottom() : widths.getBottom()), 2669 insets, rad); 2670 if (outsideInnerEdge) return true; 2671 } 2672 } 2673 return false; 2674 } 2675 2676 /** 2677 * Determines whether the point (px, py) is contained within the the bounds (x1, y1)-(x2, y2), 2678 * after taking into account the insets and the corner radii. 2679 * 2680 * @param px The x position of the point to test 2681 * @param py The y position of the point to test 2682 * @param x1 The x1 position of the bounds to test 2683 * @param y1 The y1 position of the bounds to test 2684 * @param x2 The x2 position of the bounds to test 2685 * @param y2 The y2 position of the bounds to test 2686 * @param insets The insets to apply to (x1,y1)-(x2,y2) to get the final bounds to test 2687 * @param rad The corner radii to test with. Must not be null. 2688 * @param maxRadius The maximum possible radius value 2689 * @return True if (px, py) is within the bounds, taking into account insets and corner radii. 2690 */ 2691 private boolean contains(final double px, final double py, 2692 final double x1, final double y1, final double x2, final double y2, 2693 final Insets insets, CornerRadii rad) { 2694 // These four values are the x0, y0, x1, y1 bounding box after 2695 // having taken into account the insets of this particular 2696 // background fill. 2697 final double rrx0 = x1 + insets.getLeft(); 2698 final double rry0 = y1 + insets.getTop(); 2699 final double rrx1 = x2 - insets.getRight(); 2700 final double rry1 = y2 - insets.getBottom(); 2701 2702 // assert rad.hasPercentBasedRadii == false; 2703 2704 // Check for trivial rejection - point is inside bounding rectangle 2705 if (px >= rrx0 && py >= rry0 && px <= rrx1 && py <= rry1) { 2706 // The point was within the index bounding box. Now we need to analyze the 2707 // corner radii to see if the point lies within the corners or not. If the 2708 // point is within a corner then we reject this one. 2709 final double tlhr = rad.getTopLeftHorizontalRadius(); 2710 if (rad.isUniform() && tlhr == 0) { 2711 // This is a simple square! Since we know the point is already within 2712 // the insets of this fill, we can simply return true. 2713 return true; 2714 } else { 2715 final double tlvr = rad.getTopLeftVerticalRadius(); 2716 final double trhr = rad.getTopRightHorizontalRadius(); 2717 final double trvr = rad.getTopRightVerticalRadius(); 2718 final double blhr = rad.getBottomLeftHorizontalRadius(); 2719 final double blvr = rad.getBottomLeftVerticalRadius(); 2720 final double brhr = rad.getBottomRightHorizontalRadius(); 2721 final double brvr = rad.getBottomRightVerticalRadius(); 2722 2723 // The four corners can each be described as a quarter of an ellipse 2724 double centerX, centerY, a, b; 2725 2726 if (px <= rrx0 + tlhr && py <= rry0 + tlvr) { 2727 // Point is in the top left corner 2728 centerX = rrx0 + tlhr; 2729 centerY = rry0 + tlvr; 2730 a = tlhr; 2731 b = tlvr; 2732 } else if (px >= rrx1 - trhr && py <= rry0 + trvr) { 2733 // Point is in the top right corner 2734 centerX = rrx1 - trhr; 2735 centerY = rry0 + trvr; 2736 a = trhr; 2737 b = trvr; 2738 } else if (px >= rrx1 - brhr && py >= rry1 - brvr) { 2739 // Point is in the bottom right corner 2740 centerX = rrx1 - brhr; 2741 centerY = rry1 - brvr; 2742 a = brhr; 2743 b = brvr; 2744 } else if (px <= rrx0 + blhr && py >= rry1 - blvr) { 2745 // Point is in the bottom left corner 2746 centerX = rrx0 + blhr; 2747 centerY = rry1 - blvr; 2748 a = blhr; 2749 b = blvr; 2750 } else { 2751 // The point must have been in the solid body someplace 2752 return true; 2753 } 2754 2755 double x = px - centerX; 2756 double y = py - centerY; 2757 double result = ((x*x)/(a*a) + (y*y)/(b*b)); 2758 // The .0000001 is fudge to help in cases where double arithmetic isn't quite right 2759 if (result - .0000001 <= 1) return true; 2760 } 2761 } 2762 return false; 2763 } 2764 2765 /* 2766 * The normalized corner radii are unmodifiable List objects shared between 2767 * the NG layer and the FX layer. As cached shadow copies of the objects 2768 * in the BackgroundFill and BorderStroke objects they should be considered 2769 * read-only and will only be updated by replacing the original objects 2770 * when validation is needed. 2771 */ 2772 private boolean cornersValid; // = false 2773 private List<CornerRadii> normalizedFillCorners; // = null 2774 private List<CornerRadii> normalizedStrokeCorners; // = null 2775 2776 /** 2777 * Returns the normalized absolute radii for the indicated BackgroundFill, 2778 * taking the current size of the region into account to eliminate any 2779 * percentage-based measurements and to scale the radii to prevent 2780 * overflowing the width or height. 2781 * 2782 * @param i the index of the BackgroundFill whose radii will be normalized. 2783 * @return the normalized (non-percentage, non-overflowing) radii 2784 */ 2785 private CornerRadii getNormalizedFillCorner(int i) { 2786 if (!cornersValid) { 2787 validateCorners(); 2788 } 2789 return (normalizedFillCorners == null 2790 ? getBackground().getFills().get(i).getRadii() 2791 : normalizedFillCorners.get(i)); 2792 } 2793 2794 /** 2795 * Returns the normalized absolute radii for the indicated BorderStroke, 2796 * taking the current size of the region into account to eliminate any 2797 * percentage-based measurements and to scale the radii to prevent 2798 * overflowing the width or height. 2799 * 2800 * @param i the index of the BorderStroke whose radii will be normalized. 2801 * @return the normalized (non-percentage, non-overflowing) radii 2802 */ 2803 private CornerRadii getNormalizedStrokeCorner(int i) { 2804 if (!cornersValid) { 2805 validateCorners(); 2806 } 2807 return (normalizedStrokeCorners == null 2808 ? getBorder().getStrokes().get(i).getRadii() 2809 : normalizedStrokeCorners.get(i)); 2810 } 2811 2812 /** 2813 * This method validates all CornerRadii objects in both the set of 2814 * BackgroundFills and BorderStrokes and saves the normalized values 2815 * into the private fields above. 2816 */ 2817 private void validateCorners() { 2818 final double width = getWidth(); 2819 final double height = getHeight(); 2820 List<CornerRadii> newFillCorners = null; 2821 List<CornerRadii> newStrokeCorners = null; 2822 final Background background = getBackground(); 2823 final List<BackgroundFill> fills = background == null ? Collections.EMPTY_LIST : background.getFills(); 2824 for (int i = 0; i < fills.size(); i++) { 2825 final BackgroundFill fill = fills.get(i); 2826 final CornerRadii origRadii = fill.getRadii(); 2827 final Insets origInsets = fill.getInsets(); 2828 final CornerRadii newRadii = normalize(origRadii, origInsets, width, height); 2829 if (origRadii != newRadii) { 2830 if (newFillCorners == null) { 2831 newFillCorners = Arrays.asList(new CornerRadii[fills.size()]); 2832 } 2833 newFillCorners.set(i, newRadii); 2834 } 2835 } 2836 final Border border = getBorder(); 2837 final List<BorderStroke> strokes = (border == null ? Collections.EMPTY_LIST : border.getStrokes()); 2838 for (int i = 0; i < strokes.size(); i++) { 2839 final BorderStroke stroke = strokes.get(i); 2840 final CornerRadii origRadii = stroke.getRadii(); 2841 final Insets origInsets = stroke.getInsets(); 2842 final CornerRadii newRadii = normalize(origRadii, origInsets, width, height); 2843 if (origRadii != newRadii) { 2844 if (newStrokeCorners == null) { 2845 newStrokeCorners = Arrays.asList(new CornerRadii[strokes.size()]); 2846 } 2847 newStrokeCorners.set(i, newRadii); 2848 } 2849 } 2850 if (newFillCorners != null) { 2851 for (int i = 0; i < fills.size(); i++) { 2852 if (newFillCorners.get(i) == null) { 2853 newFillCorners.set(i, fills.get(i).getRadii()); 2854 } 2855 } 2856 newFillCorners = Collections.unmodifiableList(newFillCorners); 2857 } 2858 if (newStrokeCorners != null) { 2859 for (int i = 0; i < strokes.size(); i++) { 2860 if (newStrokeCorners.get(i) == null) { 2861 newStrokeCorners.set(i, strokes.get(i).getRadii()); 2862 } 2863 } 2864 newStrokeCorners = Collections.unmodifiableList(newStrokeCorners); 2865 } 2866 normalizedFillCorners = newFillCorners; 2867 normalizedStrokeCorners = newStrokeCorners; 2868 cornersValid = true; 2869 } 2870 2871 /** 2872 * Return a version of the radii that is not percentage based and is scaled to 2873 * fit the indicated inset rectangle without overflow. 2874 * This method may return the original CornerRadii if none of the radii 2875 * values in the given object are percentages or require scaling. 2876 * 2877 * @param radii The radii. 2878 * @param insets The insets for the associated background or stroke. 2879 * @param width The width of the region before insets are applied. 2880 * @param height The height of the region before insets are applied. 2881 * @return Normalized radii. 2882 */ 2883 private static CornerRadii normalize(CornerRadii radii, Insets insets, double width, double height) { 2884 width -= insets.getLeft() + insets.getRight(); 2885 height -= insets.getTop() + insets.getBottom(); 2886 if (width <= 0 || height <= 0) return CornerRadii.EMPTY; 2887 double tlvr = radii.getTopLeftVerticalRadius(); 2888 double tlhr = radii.getTopLeftHorizontalRadius(); 2889 double trvr = radii.getTopRightVerticalRadius(); 2890 double trhr = radii.getTopRightHorizontalRadius(); 2891 double brvr = radii.getBottomRightVerticalRadius(); 2892 double brhr = radii.getBottomRightHorizontalRadius(); 2893 double blvr = radii.getBottomLeftVerticalRadius(); 2894 double blhr = radii.getBottomLeftHorizontalRadius(); 2895 if (radii.hasPercentBasedRadii) { 2896 if (radii.isTopLeftVerticalRadiusAsPercentage()) tlvr *= height; 2897 if (radii.isTopLeftHorizontalRadiusAsPercentage()) tlhr *= width; 2898 if (radii.isTopRightVerticalRadiusAsPercentage()) trvr *= height; 2899 if (radii.isTopRightHorizontalRadiusAsPercentage()) trhr *= width; 2900 if (radii.isBottomRightVerticalRadiusAsPercentage()) brvr *= height; 2901 if (radii.isBottomRightHorizontalRadiusAsPercentage()) brhr *= width; 2902 if (radii.isBottomLeftVerticalRadiusAsPercentage()) blvr *= height; 2903 if (radii.isBottomLeftHorizontalRadiusAsPercentage()) blhr *= width; 2904 } 2905 double scale = 1.0; 2906 if (tlhr + trhr > width) { scale = Math.min(scale, width / (tlhr + trhr)); } 2907 if (blhr + brhr > width) { scale = Math.min(scale, width / (blhr + brhr)); } 2908 if (tlvr + blvr > height) { scale = Math.min(scale, height / (tlvr + blvr)); } 2909 if (trvr + brvr > height) { scale = Math.min(scale, height / (trvr + brvr)); } 2910 if (scale < 1.0) { 2911 tlvr *= scale; tlhr *= scale; 2912 trvr *= scale; trhr *= scale; 2913 brvr *= scale; brhr *= scale; 2914 blvr *= scale; blhr *= scale; 2915 } 2916 if (radii.hasPercentBasedRadii || scale < 1.0) { 2917 return new CornerRadii(tlhr, tlvr, trvr, trhr, brhr, brvr, blvr, blhr, 2918 false, false, false, false, false, false, false, false); 2919 } 2920 return radii; 2921 } 2922 2923 /** 2924 * Some skins relying on this 2925 * @treatAsPrivate implementation detail 2926 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2927 */ 2928 @Deprecated 2929 @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) { 2930 2931 double boundsDistance = impl_intersectsBounds(pickRay); 2932 2933 if (!Double.isNaN(boundsDistance)) { 2934 ObservableList<Node> children = getChildren(); 2935 for (int i = children.size()-1; i >= 0; i--) { 2936 children.get(i).impl_pickNode(pickRay, result); 2937 if (result.isClosed()) { 2938 return; 2939 } 2940 } 2941 2942 impl_intersects(pickRay, result); 2943 } 2944 } 2945 2946 private Bounds boundingBox; 2947 2948 /** 2949 * The layout bounds of this region: {@code 0, 0 width x height} 2950 * 2951 * @treatAsPrivate implementation detail 2952 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2953 */ 2954 @Deprecated 2955 @Override protected final Bounds impl_computeLayoutBounds() { 2956 if (boundingBox == null) { 2957 // we reuse the bounding box if the width and height haven't changed. 2958 boundingBox = new BoundingBox(0, 0, 0, getWidth(), getHeight(), 0); 2959 } 2960 return boundingBox; 2961 } 2962 2963 /** 2964 * @treatAsPrivate implementation detail 2965 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2966 */ 2967 @Deprecated 2968 @Override final protected void impl_notifyLayoutBoundsChanged() { 2969 // override Node's default behavior of having a geometric bounds change 2970 // trigger a change in layoutBounds. For Resizable nodes, layoutBounds 2971 // is unrelated to geometric bounds. 2972 } 2973 2974 private BaseBounds computeShapeBounds(BaseBounds bounds) 2975 { 2976 com.sun.javafx.geom.Shape s = _shape.impl_configShape(); 2977 2978 float[] bbox = { 2979 Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, 2980 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 2981 }; 2982 2983 Background bg = getBackground(); 2984 if (bg != null) { 2985 final RectBounds sBounds = s.getBounds(); 2986 final Insets bgOutsets = bg.getOutsets(); 2987 bbox[0] = sBounds.getMinX() - (float) bgOutsets.getLeft(); 2988 bbox[1] = sBounds.getMinY() - (float) bgOutsets.getTop(); 2989 bbox[2] = sBounds.getMaxX() + (float) bgOutsets.getBottom(); 2990 bbox[3] = sBounds.getMaxY() + (float) bgOutsets.getRight(); 2991 } 2992 2993 final Border b = getBorder(); 2994 if (b != null && b.getStrokes().size() > 0) { 2995 for (BorderStroke bs : b.getStrokes()) { 2996 // This order of border strokes is used in NGRegion.renderAsShape/setBorderStyle 2997 BorderStrokeStyle bss = bs.getTopStyle() != null ? bs.getTopStyle() : 2998 bs.getLeftStyle() != null ? bs.getLeftStyle() : 2999 bs.getBottomStyle() != null ? bs.getBottomStyle() : 3000 bs.getRightStyle() != null ? bs.getRightStyle() : null; 3001 3002 if (bss == null || bss == BorderStrokeStyle.NONE) { 3003 continue; 3004 } 3005 3006 final StrokeType type = bss.getType(); 3007 double sw = Math.max(bs.getWidths().top, 0d); 3008 StrokeLineCap cap = bss.getLineCap(); 3009 StrokeLineJoin join = bss.getLineJoin(); 3010 float miterlimit = (float) Math.max(bss.getMiterLimit(), 1d); 3011 Toolkit.getToolkit().accumulateStrokeBounds( 3012 s, 3013 bbox, type, sw, 3014 cap, join, miterlimit, BaseTransform.IDENTITY_TRANSFORM); 3015 3016 } 3017 } 3018 3019 if (bbox[2] < bbox[0] || bbox[3] < bbox[1]) { 3020 return bounds.makeEmpty(); 3021 } 3022 3023 return bounds.deriveWithNewBounds(bbox[0], bbox[1], 0.0f, 3024 bbox[2], bbox[3], 0.0f); 3025 } 3026 3027 /** 3028 * @treatAsPrivate implementation detail 3029 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 3030 */ 3031 @Deprecated 3032 @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 3033 // Unlike Group, a Region has its own intrinsic geometric bounds, even if it has no children. 3034 // The bounds of the Region must take into account any backgrounds and borders and how 3035 // they are used to draw the Region. The geom bounds must always take into account 3036 // all pixels drawn (because the geom bounds forms the basis of the dirty regions). 3037 // Note that the layout bounds of a Region is not based on the geom bounds. 3038 3039 // Define some variables to hold the top-left and bottom-right corners of the bounds 3040 double bx1 = 0; 3041 double by1 = 0; 3042 double bx2 = getWidth(); 3043 double by2 = getHeight(); 3044 3045 // If the shape is defined, then the top-left and bottom-right corner positions 3046 // need to be redefined 3047 if (_shape != null && isScaleShape() == false) { 3048 // We will hijack the bounds here temporarily just to compute the shape bounds 3049 final BaseBounds shapeBounds = computeShapeBounds(bounds); 3050 final double shapeWidth = shapeBounds.getWidth(); 3051 final double shapeHeight = shapeBounds.getHeight(); 3052 if (isCenterShape()) { 3053 bx1 = (bx2 - shapeWidth) / 2; 3054 by1 = (by2 - shapeHeight) / 2; 3055 bx2 = bx1 + shapeWidth; 3056 by2 = by1 + shapeHeight; 3057 } else { 3058 bx1 = shapeBounds.getMinX(); 3059 by1 = shapeBounds.getMinY(); 3060 bx2 = shapeBounds.getMaxX(); 3061 by2 = shapeBounds.getMaxY(); 3062 } 3063 } else { 3064 // Expand the bounds to include the outsets from the background and border. 3065 // The outsets are the opposite of insets -- a measure of distance from the 3066 // edge of the Region outward. The outsets cannot, however, be negative. 3067 final Background background = getBackground(); 3068 final Border border = getBorder(); 3069 final Insets backgroundOutsets = background == null ? Insets.EMPTY : background.getOutsets(); 3070 final Insets borderOutsets = border == null ? Insets.EMPTY : border.getOutsets(); 3071 bx1 -= Math.max(backgroundOutsets.getLeft(), borderOutsets.getLeft()); 3072 by1 -= Math.max(backgroundOutsets.getTop(), borderOutsets.getTop()); 3073 bx2 += Math.max(backgroundOutsets.getRight(), borderOutsets.getRight()); 3074 by2 += Math.max(backgroundOutsets.getBottom(), borderOutsets.getBottom()); 3075 } 3076 // NOTE: Okay to call impl_computeGeomBounds with tx even in the 3D case 3077 // since Parent.impl_computeGeomBounds does handle 3D correctly. 3078 BaseBounds cb = super.impl_computeGeomBounds(bounds, tx); 3079 /* 3080 * This is a work around for RT-7680. Parent returns invalid bounds from 3081 * impl_computeGeomBounds when it has no children or if all its children 3082 * have invalid bounds. If RT-7680 were fixed, then we could omit this 3083 * first branch of the if and only use the else since the correct value 3084 * would be computed. 3085 */ 3086 if (cb.isEmpty()) { 3087 // There are no children bounds, so 3088 bounds = bounds.deriveWithNewBounds( 3089 (float)bx1, (float)by1, 0.0f, 3090 (float)bx2, (float)by2, 0.0f); 3091 bounds = tx.transform(bounds, bounds); 3092 return bounds; 3093 } else { 3094 // Union with children's bounds 3095 BaseBounds tempBounds = TempState.getInstance().bounds; 3096 tempBounds = tempBounds.deriveWithNewBounds( 3097 (float)bx1, (float)by1, 0.0f, 3098 (float)bx2, (float)by2, 0.0f); 3099 BaseBounds bb = tx.transform(tempBounds, tempBounds); 3100 cb = cb.deriveWithUnion(bb); 3101 return cb; 3102 } 3103 } 3104 3105 /*************************************************************************** 3106 * * 3107 * CSS * 3108 * * 3109 **************************************************************************/ 3110 3111 /** 3112 * An implementation may specify its own user-agent styles for this Region, and its children, 3113 * by overriding this method. These styles are used in addition to whatever user-agent stylesheets 3114 * are in use. This provides a mechanism for third parties to introduce styles for custom controls. 3115 * <p> 3116 * The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL 3117 * does not have a [scheme:] component, the URL is considered to be the [path] component only. 3118 * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to 3119 * the root of the application's classpath. 3120 * </p> 3121 * <code><pre> 3122 * 3123 * package com.example.javafx.app; 3124 * 3125 * import javafx.application.Application; 3126 * import javafx.scene.Group; 3127 * import javafx.scene.Scene; 3128 * import javafx.stage.Stage; 3129 * 3130 * public class MyApp extends Application { 3131 * 3132 * {@literal @}Override public void start(Stage stage) { 3133 * Scene scene = new Scene(new Group()); 3134 * scene.getStylesheets().add("/com/example/javafx/app/mystyles.css"); 3135 * stage.setScene(scene); 3136 * stage.show(); 3137 * } 3138 * 3139 * public static void main(String[] args) { 3140 * launch(args); 3141 * } 3142 * } 3143 * </pre></code> 3144 * For additional information about using CSS with the scene graph, 3145 * see the <a href="../doc-files/cssref.html">CSS Reference Guide</a>. 3146 * 3147 * @return A string URL 3148 * @since JavaFX 8u40 3149 */ 3150 public String getUserAgentStylesheet() { 3151 return null; 3152 } 3153 3154 /** 3155 * Super-lazy instantiation pattern from Bill Pugh. 3156 * @treatAsPrivate implementation detail 3157 */ 3158 private static class StyleableProperties { 3159 private static final CssMetaData<Region,Insets> PADDING = 3160 new CssMetaData<Region,Insets>("-fx-padding", 3161 InsetsConverter.getInstance(), Insets.EMPTY) { 3162 3163 @Override public boolean isSettable(Region node) { 3164 return node.padding == null || !node.padding.isBound(); 3165 } 3166 3167 @Override public StyleableProperty<Insets> getStyleableProperty(Region node) { 3168 return (StyleableProperty<Insets>)node.paddingProperty(); 3169 } 3170 }; 3171 3172 private static final CssMetaData<Region,Insets> OPAQUE_INSETS = 3173 new CssMetaData<Region,Insets>("-fx-opaque-insets", 3174 InsetsConverter.getInstance(), null) { 3175 3176 @Override 3177 public boolean isSettable(Region node) { 3178 return node.opaqueInsets == null || !node.opaqueInsets.isBound(); 3179 } 3180 3181 @Override 3182 public StyleableProperty<Insets> getStyleableProperty(Region node) { 3183 return (StyleableProperty<Insets>)node.opaqueInsetsProperty(); 3184 } 3185 3186 }; 3187 3188 private static final CssMetaData<Region,Background> BACKGROUND = 3189 new CssMetaData<Region,Background>("-fx-region-background", 3190 BackgroundConverter.INSTANCE, 3191 null, 3192 false, 3193 Background.getClassCssMetaData()) { 3194 3195 @Override public boolean isSettable(Region node) { 3196 return !node.background.isBound(); 3197 } 3198 3199 @Override public StyleableProperty<Background> getStyleableProperty(Region node) { 3200 return (StyleableProperty<Background>)node.background; 3201 } 3202 }; 3203 3204 private static final CssMetaData<Region,Border> BORDER = 3205 new CssMetaData<Region,Border>("-fx-region-border", 3206 BorderConverter.getInstance(), 3207 null, 3208 false, 3209 Border.getClassCssMetaData()) { 3210 3211 @Override public boolean isSettable(Region node) { 3212 return !node.background.isBound(); 3213 } 3214 3215 @Override public StyleableProperty<Border> getStyleableProperty(Region node) { 3216 return (StyleableProperty<Border>)node.border; 3217 } 3218 }; 3219 3220 private static final CssMetaData<Region,Shape> SHAPE = 3221 new CssMetaData<Region,Shape>("-fx-shape", 3222 ShapeConverter.getInstance()) { 3223 3224 @Override public boolean isSettable(Region node) { 3225 // isSettable depends on node.shape, not node.shapeContent 3226 return node.shape == null || !node.shape.isBound(); 3227 } 3228 3229 @Override public StyleableProperty<Shape> getStyleableProperty(Region node) { 3230 return (StyleableProperty<Shape>)node.shapeProperty(); 3231 } 3232 }; 3233 3234 private static final CssMetaData<Region, Boolean> SCALE_SHAPE = 3235 new CssMetaData<Region,Boolean>("-fx-scale-shape", 3236 BooleanConverter.getInstance(), Boolean.TRUE){ 3237 3238 @Override public boolean isSettable(Region node) { 3239 return node.scaleShape == null || !node.scaleShape.isBound(); 3240 } 3241 3242 @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) { 3243 return (StyleableProperty<Boolean>)node.scaleShapeProperty(); 3244 } 3245 }; 3246 3247 private static final CssMetaData<Region,Boolean> POSITION_SHAPE = 3248 new CssMetaData<Region,Boolean>("-fx-position-shape", 3249 BooleanConverter.getInstance(), Boolean.TRUE){ 3250 3251 @Override public boolean isSettable(Region node) { 3252 return node.centerShape == null || !node.centerShape.isBound(); 3253 } 3254 3255 @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) { 3256 return (StyleableProperty<Boolean>)node.centerShapeProperty(); 3257 } 3258 }; 3259 3260 private static final CssMetaData<Region,Boolean> CACHE_SHAPE = 3261 new CssMetaData<Region,Boolean>("-fx-cache-shape", 3262 BooleanConverter.getInstance(), Boolean.TRUE){ 3263 3264 @Override public boolean isSettable(Region node) { 3265 return node.cacheShape == null || !node.cacheShape.isBound(); 3266 } 3267 3268 @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) { 3269 return (StyleableProperty<Boolean>)node.cacheShapeProperty(); 3270 } 3271 }; 3272 3273 private static final CssMetaData<Region, Boolean> SNAP_TO_PIXEL = 3274 new CssMetaData<Region,Boolean>("-fx-snap-to-pixel", 3275 BooleanConverter.getInstance(), Boolean.TRUE){ 3276 3277 @Override public boolean isSettable(Region node) { 3278 return node.snapToPixel == null || 3279 !node.snapToPixel.isBound(); 3280 } 3281 3282 @Override public StyleableProperty<Boolean> getStyleableProperty(Region node) { 3283 return (StyleableProperty<Boolean>)node.snapToPixelProperty(); 3284 } 3285 }; 3286 3287 private static final CssMetaData<Region, Number> MIN_HEIGHT = 3288 new CssMetaData<Region,Number>("-fx-min-height", 3289 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3290 3291 @Override public boolean isSettable(Region node) { 3292 return node.minHeight == null || 3293 !node.minHeight.isBound(); 3294 } 3295 3296 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3297 return (StyleableProperty<Number>)node.minHeightProperty(); 3298 } 3299 }; 3300 3301 private static final CssMetaData<Region, Number> PREF_HEIGHT = 3302 new CssMetaData<Region,Number>("-fx-pref-height", 3303 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3304 3305 @Override public boolean isSettable(Region node) { 3306 return node.prefHeight == null || 3307 !node.prefHeight.isBound(); 3308 } 3309 3310 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3311 return (StyleableProperty<Number>)node.prefHeightProperty(); 3312 } 3313 }; 3314 3315 private static final CssMetaData<Region, Number> MAX_HEIGHT = 3316 new CssMetaData<Region,Number>("-fx-max-height", 3317 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3318 3319 @Override public boolean isSettable(Region node) { 3320 return node.maxHeight == null || 3321 !node.maxHeight.isBound(); 3322 } 3323 3324 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3325 return (StyleableProperty<Number>)node.maxHeightProperty(); 3326 } 3327 }; 3328 3329 private static final CssMetaData<Region, Number> MIN_WIDTH = 3330 new CssMetaData<Region,Number>("-fx-min-width", 3331 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3332 3333 @Override public boolean isSettable(Region node) { 3334 return node.minWidth == null || 3335 !node.minWidth.isBound(); 3336 } 3337 3338 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3339 return (StyleableProperty<Number>)node.minWidthProperty(); 3340 } 3341 }; 3342 3343 private static final CssMetaData<Region, Number> PREF_WIDTH = 3344 new CssMetaData<Region,Number>("-fx-pref-width", 3345 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3346 3347 @Override public boolean isSettable(Region node) { 3348 return node.prefWidth == null || 3349 !node.prefWidth.isBound(); 3350 } 3351 3352 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3353 return (StyleableProperty<Number>)node.prefWidthProperty(); 3354 } 3355 }; 3356 3357 private static final CssMetaData<Region, Number> MAX_WIDTH = 3358 new CssMetaData<Region,Number>("-fx-max-width", 3359 SizeConverter.getInstance(), USE_COMPUTED_SIZE){ 3360 3361 @Override public boolean isSettable(Region node) { 3362 return node.maxWidth == null || 3363 !node.maxWidth.isBound(); 3364 } 3365 3366 @Override public StyleableProperty<Number> getStyleableProperty(Region node) { 3367 return (StyleableProperty<Number>)node.maxWidthProperty(); 3368 } 3369 }; 3370 3371 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 3372 static { 3373 3374 final List<CssMetaData<? extends Styleable, ?>> styleables = 3375 new ArrayList<CssMetaData<? extends Styleable, ?>>(Parent.getClassCssMetaData()); 3376 styleables.add(PADDING); 3377 styleables.add(BACKGROUND); 3378 styleables.add(BORDER); 3379 styleables.add(OPAQUE_INSETS); 3380 styleables.add(SHAPE); 3381 styleables.add(SCALE_SHAPE); 3382 styleables.add(POSITION_SHAPE); 3383 styleables.add(SNAP_TO_PIXEL); 3384 styleables.add(MIN_WIDTH); 3385 styleables.add(PREF_WIDTH); 3386 styleables.add(MAX_WIDTH); 3387 styleables.add(MIN_HEIGHT); 3388 styleables.add(PREF_HEIGHT); 3389 styleables.add(MAX_HEIGHT); 3390 STYLEABLES = Collections.unmodifiableList(styleables); 3391 } 3392 } 3393 3394 /** 3395 * @return The CssMetaData associated with this class, which may include the 3396 * CssMetaData of its super classes. 3397 * @since JavaFX 8.0 3398 */ 3399 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 3400 return StyleableProperties.STYLEABLES; 3401 } 3402 3403 /** 3404 * {@inheritDoc} 3405 * 3406 * @since JavaFX 8.0 3407 */ 3408 3409 3410 @Override 3411 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 3412 return getClassCssMetaData(); 3413 } 3414 3415 }