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