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