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