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