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