1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.layout; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import javafx.beans.property.BooleanProperty; 32 import javafx.beans.property.DoubleProperty; 33 import javafx.beans.property.ObjectProperty; 34 import javafx.css.CssMetaData; 35 import javafx.css.StyleableBooleanProperty; 36 import javafx.css.StyleableDoubleProperty; 37 import javafx.css.StyleableObjectProperty; 38 import javafx.css.StyleableProperty; 39 import javafx.geometry.Insets; 40 import javafx.geometry.Orientation; 41 import javafx.geometry.Pos; 42 import javafx.geometry.VPos; 43 import javafx.scene.Node; 44 import javafx.css.converter.BooleanConverter; 45 import javafx.css.converter.EnumConverter; 46 import javafx.css.converter.SizeConverter; 47 import javafx.css.Styleable; 48 import javafx.geometry.HPos; 49 import javafx.util.Callback; 50 51 52 53 /** 54 * HBox lays out its children in a single horizontal row. 55 * If the hbox has a border and/or padding set, then the contents will be layed 56 * out within those insets. 57 * <p> 58 * HBox example: 59 * <pre><code> 60 * HBox hbox = new HBox(8); // spacing = 8 61 * hbox.getChildren().addAll(new Label("Name:), new TextBox()); 62 * </code></pre> 63 * 64 * HBox will resize children (if resizable) to their preferred widths and uses its 65 * fillHeight property to determine whether to resize their heights to 66 * fill its own height or keep their heights to their preferred (fillHeight defaults to true). 67 * The alignment of the content is controlled by the alignment property, 68 * which defaults to Pos.TOP_LEFT. 69 * <p> 70 * If an hbox is resized larger than its preferred width, by default it will keep 71 * children to their preferred widths, leaving the extra space unused. If an 72 * application wishes to have one or more children be allocated that extra space 73 * it may optionally set an hgrow constraint on the child. See "Optional Layout 74 * Constraints" for details. 75 * <p> 76 * HBox lays out each managed child regardless of the child's 77 * visible property value; unmanaged children are ignored.</p> 78 * 79 * <h4>Resizable Range</h4> 80 * 81 * An hbox's parent will resize the hbox within the hbox's resizable range 82 * during layout. By default the hbox computes this range based on its content 83 * as outlined in the table below. 84 * <table border="1"> 85 * <tr><td></td><th>width</th><th>height</th></tr> 86 * <tr><th>minimum</th> 87 * <td>left/right insets plus the sum of each child's min width plus spacing between each child.</td> 88 * <td>top/bottom insets plus the largest of the children's min heights.</td></tr> 89 * <tr><th>preferred</th> 90 * <td>left/right insets plus the sum of each child's pref width plus spacing between each child.</td> 91 * <td>top/bottom insets plus the largest of the children's pref heights.</td></tr> 92 * <tr><th>maximum</th> 93 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 94 * </table> 95 * <p> 96 * An hbox's unbounded maximum width and height are an indication to the parent that 97 * it may be resized beyond its preferred size to fill whatever space is assigned 98 * to it. 99 * <p> 100 * HBox provides properties for setting the size range directly. These 101 * properties default to the sentinel value USE_COMPUTED_SIZE, however the 102 * application may set them to other values as needed: 103 * <pre><code> 104 * <b>hbox.setPrefWidth(400);</b> 105 * </code></pre> 106 * Applications may restore the computed values by setting these properties back 107 * to USE_COMPUTED_SIZE. 108 * <p> 109 * HBox does not clip its content by default, so it is possible that childrens' 110 * bounds may extend outside its own bounds if a child's min size prevents it from 111 * being fit within the hbox.</p> 112 * 113 * <h4>Optional Layout Constraints</h4> 114 * 115 * An application may set constraints on individual children to customize HBox's layout. 116 * For each constraint, HBox provides a static method for setting it on the child. 117 * <p> 118 * <table border="1"> 119 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr> 120 * <tr><td>hgrow</td><td>javafx.scene.layout.Priority</td><td>The horizontal grow priority for the child.</td></tr> 121 * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr> 122 * </table> 123 * <p> 124 * For example, if an hbox needs the TextField to be allocated all extra space: 125 * <pre><code> 126 * HBox hbox = new HBox(); 127 * TextField field = new TextField(); 128 * <b>HBox.setHgrow(field, Priority.ALWAYS);</b> 129 * hbox.getChildren().addAll(new Label("Search:"), field, new Button("Go")); 130 * </code></pre> 131 * 132 * If more than one child has the same grow priority set, then the hbox will 133 * allocate equal amounts of space to each. HBox will only grow a child up to 134 * its maximum width, so if the child has a max width other than Double.MAX_VALUE, 135 * the application may need to override the max to allow it to grow. 136 * For example: 137 * <pre><code> 138 * HBox hbox = new HBox(); 139 * Button button1 = new Button("Add"); 140 * Button button2 = new Button("Remove"); 141 * <b>HBox.setHgrow(button1, Priority.ALWAYS); 142 * HBox.setHgrow(button2, Priority.ALWAYS); 143 * button1.setMaxWidth(Double.MAX_VALUE); 144 * button2.setMaxWidth(Double.MAX_VALUE);</b> 145 * hbox.getChildren().addAll(button1, button2); 146 * </code></pre> 147 * @since JavaFX 2.0 148 */ 149 public class HBox extends Pane { 150 151 private boolean biasDirty = true; 152 private boolean performingLayout = false; 153 private double minBaselineComplement = Double.NaN; 154 private double prefBaselineComplement = Double.NaN; 155 private Orientation bias; 156 private double[][] tempArray; 157 158 /******************************************************************** 159 * BEGIN static methods 160 ********************************************************************/ 161 private static final String MARGIN_CONSTRAINT = "hbox-margin"; 162 private static final String HGROW_CONSTRAINT = "hbox-hgrow"; 163 164 /** 165 * Sets the horizontal grow priority for the child when contained by an hbox. 166 * If set, the hbox will use the priority to allocate additional space if the 167 * hbox is resized larger than it's preferred width. 168 * If multiple hbox children have the same horizontal grow priority, then the 169 * extra space will be split evening between them. 170 * If no horizontal grow priority is set on a child, the hbox will never 171 * allocate it additional horizontal space if available. 172 * Setting the value to null will remove the constraint. 173 * @param child the child of an hbox 174 * @param value the horizontal grow priority for the child 175 */ 176 public static void setHgrow(Node child, Priority value) { 177 setConstraint(child, HGROW_CONSTRAINT, value); 178 } 179 180 /** 181 * Returns the child's hgrow constraint if set. 182 * @param child the child node of an hbox 183 * @return the horizontal grow priority for the child or null if no priority was set 184 */ 185 public static Priority getHgrow(Node child) { 186 return (Priority)getConstraint(child, HGROW_CONSTRAINT); 187 } 188 189 /** 190 * Sets the margin for the child when contained by an hbox. 191 * If set, the hbox will layout the child with the margin space around it. 192 * Setting the value to null will remove the constraint. 193 * @param child the child mode of the hbox 194 * @param value the margin of space around the child 195 */ 196 public static void setMargin(Node child, Insets value) { 197 setConstraint(child, MARGIN_CONSTRAINT, value); 198 } 199 200 /** 201 * Returns the child's margin constraint if set. 202 * @param child the child node of an hbox 203 * @return the margin for the child or null if no margin was set 204 */ 205 public static Insets getMargin(Node child) { 206 return (Insets)getConstraint(child, MARGIN_CONSTRAINT); 207 } 208 209 private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n); 210 211 /** 212 * Removes all hbox constraints from the child node. 213 * @param child the child node 214 */ 215 public static void clearConstraints(Node child) { 216 setHgrow(child, null); 217 setMargin(child, null); 218 } 219 220 /******************************************************************** 221 * END static methods 222 ********************************************************************/ 223 224 /** 225 * Creates an HBox layout with spacing = 0. 226 */ 227 public HBox() { 228 super(); 229 } 230 231 /** 232 * Creates an HBox layout with the specified spacing between children. 233 * @param spacing the amount of horizontal space between each child 234 */ 235 public HBox(double spacing) { 236 this(); 237 setSpacing(spacing); 238 } 239 240 /** 241 * Creates an HBox layout with spacing = 0. 242 * @param children The initial set of children for this pane. 243 * @since JavaFX 8.0 244 */ 245 public HBox(Node... children) { 246 super(); 247 getChildren().addAll(children); 248 } 249 250 /** 251 * Creates an HBox layout with the specified spacing between children. 252 * @param spacing the amount of horizontal space between each child 253 * @param children The initial set of children for this pane. 254 * @since JavaFX 8.0 255 */ 256 public HBox(double spacing, Node... children) { 257 this(); 258 setSpacing(spacing); 259 getChildren().addAll(children); 260 } 261 262 /** 263 * The amount of horizontal space between each child in the hbox. 264 */ 265 public final DoubleProperty spacingProperty() { 266 if (spacing == null) { 267 spacing = new StyleableDoubleProperty() { 268 @Override 269 public void invalidated() { 270 requestLayout(); 271 } 272 273 @Override 274 public CssMetaData getCssMetaData () { 275 return StyleableProperties.SPACING; 276 } 277 278 @Override 279 public Object getBean() { 280 return HBox.this; 281 } 282 283 @Override 284 public String getName() { 285 return "spacing"; 286 } 287 }; 288 } 289 return spacing; 290 } 291 292 private DoubleProperty spacing; 293 public final void setSpacing(double value) { spacingProperty().set(value); } 294 public final double getSpacing() { return spacing == null ? 0 : spacing.get(); } 295 296 /** 297 * The overall alignment of children within the hbox's width and height. 298 */ 299 public final ObjectProperty<Pos> alignmentProperty() { 300 if (alignment == null) { 301 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 302 @Override 303 public void invalidated() { 304 requestLayout(); 305 } 306 307 @Override 308 public CssMetaData<HBox, Pos> getCssMetaData() { 309 return StyleableProperties.ALIGNMENT; 310 } 311 312 @Override 313 public Object getBean() { 314 return HBox.this; 315 } 316 317 @Override 318 public String getName() { 319 return "alignment"; 320 } 321 }; 322 } 323 return alignment; 324 } 325 326 private ObjectProperty<Pos> alignment; 327 public final void setAlignment(Pos value) { alignmentProperty().set(value); } 328 public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); } 329 private Pos getAlignmentInternal() { 330 Pos localPos = getAlignment(); 331 return localPos == null ? Pos.TOP_LEFT : localPos; 332 } 333 334 /** 335 * Whether or not resizable children will be resized to fill the full height of the hbox 336 * or be resized to their preferred height and aligned according to the <code>alignment</code> 337 * vpos value. Note that if the hbox vertical alignment is set to BASELINE, then this 338 * property will be ignored and children will be resized to their preferred heights. 339 */ 340 public final BooleanProperty fillHeightProperty() { 341 if (fillHeight == null) { 342 fillHeight = new StyleableBooleanProperty(true) { 343 @Override 344 public void invalidated() { 345 requestLayout(); 346 } 347 348 @Override 349 public CssMetaData<HBox, Boolean> getCssMetaData() { 350 return StyleableProperties.FILL_HEIGHT; 351 } 352 353 @Override 354 public Object getBean() { 355 return HBox.this; 356 } 357 358 @Override 359 public String getName() { 360 return "fillHeight"; 361 } 362 }; 363 } 364 return fillHeight; 365 } 366 367 private BooleanProperty fillHeight; 368 public final void setFillHeight(boolean value) { fillHeightProperty().set(value); } 369 public final boolean isFillHeight() { return fillHeight == null ? true : fillHeight.get(); } 370 371 private boolean shouldFillHeight() { 372 return isFillHeight() && getAlignmentInternal().getVpos() != VPos.BASELINE; 373 } 374 375 /** 376 * 377 * @return null unless one of its children has a content bias. 378 */ 379 @Override public Orientation getContentBias() { 380 if (biasDirty) { 381 bias = null; 382 final List<Node> children = getManagedChildren(); 383 for (Node child : children) { 384 Orientation contentBias = child.getContentBias(); 385 if (contentBias != null) { 386 bias = contentBias; 387 if (contentBias == Orientation.HORIZONTAL) { 388 break; 389 } 390 } 391 } 392 biasDirty = false; 393 } 394 return bias; 395 } 396 397 @Override protected double computeMinWidth(double height) { 398 Insets insets = getInsets(); 399 return snapSpaceX(insets.getLeft()) + 400 computeContentWidth(getManagedChildren(), height, true) + 401 snapSpaceX(insets.getRight()); 402 } 403 404 @Override protected double computeMinHeight(double width) { 405 Insets insets = getInsets(); 406 List<Node>managed = getManagedChildren(); 407 double contentHeight = 0; 408 if (width != -1 && getContentBias() != null) { 409 double prefWidths[][] = getAreaWidths(managed, -1, false); 410 adjustAreaWidths(managed, prefWidths, width, -1); 411 contentHeight = computeMaxMinAreaHeight(managed, marginAccessor, prefWidths[0], getAlignmentInternal().getVpos()); 412 } else { 413 contentHeight = computeMaxMinAreaHeight(managed, marginAccessor, getAlignmentInternal().getVpos()); 414 } 415 return snapSpaceY(insets.getTop()) + 416 contentHeight + 417 snapSpaceY(insets.getBottom()); 418 } 419 420 @Override protected double computePrefWidth(double height) { 421 Insets insets = getInsets(); 422 return snapSpaceX(insets.getLeft()) + 423 computeContentWidth(getManagedChildren(), height, false) + 424 snapSpaceX(insets.getRight()); 425 } 426 427 @Override protected double computePrefHeight(double width) { 428 Insets insets = getInsets(); 429 List<Node>managed = getManagedChildren(); 430 double contentHeight = 0; 431 if (width != -1 && getContentBias() != null) { 432 double prefWidths[][] = getAreaWidths(managed, -1, false); 433 adjustAreaWidths(managed, prefWidths, width, -1); 434 contentHeight = computeMaxPrefAreaHeight(managed, marginAccessor, prefWidths[0], getAlignmentInternal().getVpos()); 435 } else { 436 contentHeight = computeMaxPrefAreaHeight(managed, marginAccessor, getAlignmentInternal().getVpos()); 437 } 438 return snapSpaceY(insets.getTop()) + 439 contentHeight + 440 snapSpaceY(insets.getBottom()); 441 } 442 443 private double[][] getAreaWidths(List<Node>managed, double height, boolean minimum) { 444 // height could be -1 445 double[][] temp = getTempArray(managed.size()); 446 final double insideHeight = height == -1? -1 : height - 447 snapSpaceY(getInsets().getTop()) - snapSpaceY(getInsets().getBottom()); 448 final boolean shouldFillHeight = shouldFillHeight(); 449 for (int i = 0, size = managed.size(); i < size; i++) { 450 Node child = managed.get(i); 451 Insets margin = getMargin(child); 452 if (minimum) { 453 temp[0][i] = computeChildMinAreaWidth(child, getMinBaselineComplement(), margin, insideHeight, shouldFillHeight); 454 } else { 455 temp[0][i] = computeChildPrefAreaWidth(child, getPrefBaselineComplement(), margin, insideHeight, shouldFillHeight); 456 } 457 } 458 return temp; 459 } 460 461 private double adjustAreaWidths(List<Node>managed, double areaWidths[][], double width, double height) { 462 Insets insets = getInsets(); 463 double top = snapSpaceY(insets.getTop()); 464 double bottom = snapSpaceY(insets.getBottom()); 465 466 double contentWidth = sum(areaWidths[0], managed.size()) + (managed.size()-1)*snapSpaceX(getSpacing()); 467 double extraWidth = width - 468 snapSpaceX(insets.getLeft()) - snapSpaceX(insets.getRight()) - contentWidth; 469 470 if (extraWidth != 0) { 471 final double refHeight = shouldFillHeight() && height != -1? height - top - bottom : -1; 472 double remaining = growOrShrinkAreaWidths(managed, areaWidths, Priority.ALWAYS, extraWidth, refHeight); 473 remaining = growOrShrinkAreaWidths(managed, areaWidths, Priority.SOMETIMES, remaining, refHeight); 474 contentWidth += (extraWidth - remaining); 475 } 476 return contentWidth; 477 } 478 479 private double growOrShrinkAreaWidths(List<Node>managed, double areaWidths[][], Priority priority, double extraWidth, double height) { 480 final boolean shrinking = extraWidth < 0; 481 int adjustingNumber = 0; 482 483 double[] usedWidths = areaWidths[0]; 484 double[] temp = areaWidths[1]; 485 final boolean shouldFillHeight = shouldFillHeight(); 486 487 if (shrinking) { 488 adjustingNumber = managed.size(); 489 for (int i = 0, size = managed.size(); i < size; i++) { 490 final Node child = managed.get(i); 491 temp[i] = computeChildMinAreaWidth(child, getMinBaselineComplement(), getMargin(child), height, shouldFillHeight); 492 } 493 } else { 494 for (int i = 0, size = managed.size(); i < size; i++) { 495 final Node child = managed.get(i); 496 if (getHgrow(child) == priority) { 497 temp[i] = computeChildMaxAreaWidth(child, getMinBaselineComplement(), getMargin(child), height, shouldFillHeight); 498 adjustingNumber++; 499 } else { 500 temp[i] = -1; 501 } 502 } 503 } 504 505 double available = extraWidth; // will be negative in shrinking case 506 outer:while (Math.abs(available) > 1 && adjustingNumber > 0) { 507 final double portion = snapPortionX(available / adjustingNumber); // negative in shrinking case 508 for (int i = 0, size = managed.size(); i < size; i++) { 509 if (temp[i] == -1) { 510 continue; 511 } 512 final double limit = temp[i] - usedWidths[i]; // negative in shrinking case 513 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 514 usedWidths[i] += change; 515 available -= change; 516 if (Math.abs(available) < 1) { 517 break outer; 518 } 519 if (Math.abs(change) < Math.abs(portion)) { 520 temp[i] = -1; 521 adjustingNumber--; 522 } 523 } 524 } 525 526 return available; // might be negative in shrinking case 527 } 528 529 private double computeContentWidth(List<Node> managedChildren, double height, boolean minimum) { 530 return sum(getAreaWidths(managedChildren, height, minimum)[0], managedChildren.size()) 531 + (managedChildren.size()-1)*snapSpaceX(getSpacing()); 532 } 533 534 private static double sum(double[] array, int size) { 535 int i = 0; 536 double res = 0; 537 while (i != size) { 538 res += array[i++]; 539 } 540 return res; 541 } 542 543 @Override public void requestLayout() { 544 if (performingLayout) { 545 return; 546 } 547 biasDirty = true; 548 bias = null; 549 minBaselineComplement = Double.NaN; 550 prefBaselineComplement = Double.NaN; 551 baselineOffset = Double.NaN; 552 super.requestLayout(); 553 } 554 555 private double getMinBaselineComplement() { 556 if (Double.isNaN(minBaselineComplement)) { 557 if (getAlignmentInternal().getVpos() == VPos.BASELINE) { 558 minBaselineComplement = getMinBaselineComplement(getManagedChildren()); 559 } else { 560 minBaselineComplement = -1; 561 } 562 } 563 return minBaselineComplement; 564 } 565 566 private double getPrefBaselineComplement() { 567 if (Double.isNaN(prefBaselineComplement)) { 568 if (getAlignmentInternal().getVpos() == VPos.BASELINE) { 569 prefBaselineComplement = getPrefBaselineComplement(getManagedChildren()); 570 } else { 571 prefBaselineComplement = -1; 572 } 573 } 574 return prefBaselineComplement; 575 } 576 577 private double baselineOffset = Double.NaN; 578 579 @Override 580 public double getBaselineOffset() { 581 List<Node> managed = getManagedChildren(); 582 if (managed.isEmpty()) { 583 return BASELINE_OFFSET_SAME_AS_HEIGHT; 584 } 585 if (Double.isNaN(baselineOffset)) { 586 VPos vpos = getAlignmentInternal().getVpos(); 587 if (vpos == VPos.BASELINE) { 588 double max = 0; 589 for (int i =0, sz = managed.size(); i < sz; ++i) { 590 final Node child = managed.get(i); 591 double offset = child.getBaselineOffset(); 592 if (offset == BASELINE_OFFSET_SAME_AS_HEIGHT) { 593 baselineOffset = BASELINE_OFFSET_SAME_AS_HEIGHT; 594 break; 595 } else { 596 Insets margin = getMargin(child); 597 double top = margin != null ? margin.getTop() : 0; 598 max = Math.max(max, top + child.getLayoutBounds().getMinY() + offset); 599 } 600 } 601 baselineOffset = max + snappedTopInset(); 602 } else { 603 baselineOffset = BASELINE_OFFSET_SAME_AS_HEIGHT; 604 } 605 } 606 return baselineOffset; 607 } 608 609 @Override protected void layoutChildren() { 610 performingLayout = true; 611 List<Node> managed = getManagedChildren(); 612 Insets insets = getInsets(); 613 Pos align = getAlignmentInternal(); 614 HPos alignHpos = align.getHpos(); 615 VPos alignVpos = align.getVpos(); 616 double width = getWidth(); 617 double height = getHeight(); 618 double top = snapSpaceY(insets.getTop()); 619 double left = snapSpaceX(insets.getLeft()); 620 double bottom = snapSpaceY(insets.getBottom()); 621 double right = snapSpaceX(insets.getRight()); 622 double space = snapSpaceX(getSpacing()); 623 boolean shouldFillHeight = shouldFillHeight(); 624 625 final double[][] actualAreaWidths = getAreaWidths(managed, height, false); 626 double contentWidth = adjustAreaWidths(managed, actualAreaWidths, width, height); 627 double contentHeight = height - top - bottom; 628 629 double x = left + computeXOffset(width - left - right, contentWidth, align.getHpos()); 630 double y = top; 631 double baselineOffset = -1; 632 if (alignVpos == VPos.BASELINE) { 633 double baselineComplement = getMinBaselineComplement(); 634 baselineOffset = getAreaBaselineOffset(managed, marginAccessor, i -> actualAreaWidths[0][i], 635 contentHeight, shouldFillHeight, baselineComplement); 636 } 637 638 for (int i = 0, size = managed.size(); i < size; i++) { 639 Node child = managed.get(i); 640 Insets margin = getMargin(child); 641 layoutInArea(child, x, y, actualAreaWidths[0][i], contentHeight, 642 baselineOffset, margin, true, shouldFillHeight, 643 alignHpos, alignVpos); 644 x += actualAreaWidths[0][i] + space; 645 } 646 performingLayout = false; 647 } 648 649 private double[][] getTempArray(int size) { 650 if (tempArray == null) { 651 tempArray = new double[2][size]; // First array for the result, second for temporary computations 652 } else if (tempArray[0].length < size) { 653 tempArray = new double[2][Math.max(tempArray.length * 3, size)]; 654 } 655 return tempArray; 656 657 } 658 659 /*************************************************************************** 660 * * 661 * Stylesheet Handling * 662 * * 663 **************************************************************************/ 664 665 /** 666 * Super-lazy instantiation pattern from Bill Pugh. 667 * @treatAsPrivate implementation detail 668 */ 669 private static class StyleableProperties { 670 671 private static final CssMetaData<HBox,Pos> ALIGNMENT = 672 new CssMetaData<HBox,Pos>("-fx-alignment", 673 new EnumConverter<Pos>(Pos.class), 674 Pos.TOP_LEFT) { 675 676 @Override 677 public boolean isSettable(HBox node) { 678 return node.alignment == null || !node.alignment.isBound(); 679 } 680 681 @Override 682 public StyleableProperty<Pos> getStyleableProperty(HBox node) { 683 return (StyleableProperty<Pos>)node.alignmentProperty(); 684 } 685 686 }; 687 688 private static final CssMetaData<HBox,Boolean> FILL_HEIGHT = 689 new CssMetaData<HBox,Boolean>("-fx-fill-height", 690 BooleanConverter.getInstance(), Boolean.TRUE) { 691 692 @Override 693 public boolean isSettable(HBox node) { 694 return node.fillHeight == null || 695 !node.fillHeight.isBound(); 696 } 697 698 @Override 699 public StyleableProperty<Boolean> getStyleableProperty(HBox node) { 700 return (StyleableProperty<Boolean>)node.fillHeightProperty(); 701 } 702 703 }; 704 705 private static final CssMetaData<HBox,Number> SPACING = 706 new CssMetaData<HBox,Number>("-fx-spacing", 707 SizeConverter.getInstance(), 0.0){ 708 709 @Override 710 public boolean isSettable(HBox node) { 711 return node.spacing == null || !node.spacing.isBound(); 712 } 713 714 @Override 715 public StyleableProperty<Number> getStyleableProperty(HBox node) { 716 return (StyleableProperty<Number>)node.spacingProperty(); 717 } 718 719 }; 720 721 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 722 static { 723 final List<CssMetaData<? extends Styleable, ?>> styleables = 724 new ArrayList<CssMetaData<? extends Styleable, ?>>(Pane.getClassCssMetaData()); 725 styleables.add(FILL_HEIGHT); 726 styleables.add(ALIGNMENT); 727 styleables.add(SPACING); 728 STYLEABLES = Collections.unmodifiableList(styleables); 729 } 730 } 731 732 /** 733 * @return The CssMetaData associated with this class, which may include the 734 * CssMetaData of its super classes. 735 * @since JavaFX 8.0 736 */ 737 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 738 return StyleableProperties.STYLEABLES; 739 } 740 741 /** 742 * {@inheritDoc} 743 * 744 * @since JavaFX 8.0 745 */ 746 747 748 @Override 749 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 750 return getClassCssMetaData(); 751 } 752 753 }