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