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