Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 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.property.BooleanProperty; 032import javafx.beans.property.DoubleProperty; 033import javafx.beans.property.ObjectProperty; 034import javafx.css.CssMetaData; 035import javafx.css.StyleableBooleanProperty; 036import javafx.css.StyleableDoubleProperty; 037import javafx.css.StyleableObjectProperty; 038import javafx.css.StyleableProperty; 039import javafx.geometry.HPos; 040import javafx.geometry.Insets; 041import javafx.geometry.Orientation; 042import javafx.geometry.Pos; 043import javafx.geometry.VPos; 044import javafx.scene.Node; 045import com.sun.javafx.css.converters.BooleanConverter; 046import com.sun.javafx.css.converters.EnumConverter; 047import com.sun.javafx.css.converters.SizeConverter; 048import javafx.css.Styleable; 049 050/** 051 * VBox lays out its children in a single vertical column. 052 * If the vbox has a border and/or padding set, then the contents will be layed 053 * out within those insets. 054 * <p> 055 * VBox example: 056 * <pre><code> 057 * VBox vbox = new VBox(8); // spacing = 8 058 * vbox.getChildren().addAll(new Button("Cut"), new Button("Copy"), new Button("Paste")); 059 * </code></pre> 060 * 061 * VBox will resize children (if resizable) to their preferred heights and uses its 062 * {@link #fillWidth} property to determine whether to resize their widths to 063 * fill its own width or keep their widths to their preferred (fillWidth defaults to true). 064 * The alignment of the content is controlled by the {@link #alignment} property, 065 * which defaults to Pos.TOP_LEFT. 066 * <p> 067 * If a vbox is resized larger than its preferred height, by default it will keep 068 * children to their preferred heights, leaving the extra space unused. If an 069 * application wishes to have one or more children be allocated that extra space 070 * it may optionally set a vgrow constraint on the child. See "Optional Layout 071 * Constraints" for details. 072 * <p> 073 * VBox lays out each managed child regardless of the child's 074 * visible property value; unmanaged children are ignored.</p> 075 * 076 * <h4>Resizable Range</h4> 077 * 078 * A vbox's parent will resize the vbox within the vbox's resizable range 079 * during layout. By default the vbox computes this range based on its content 080 * as outlined in the table below. 081 * <table border="1"> 082 * <tr><td></td><th>width</th><th>height</th></tr> 083 * <tr><th>minimum</th> 084 * <td>left/right insets plus the largest of the children's min widths.</td> 085 * <td>top/bottom insets plus the sum of each child's min height plus spacing between each child.</td> 086 * </tr> 087 * <tr><th>preferred</th> 088 * <td>left/right insets plus the largest of the children's pref widths.</td> 089 * <td>top/bottom insets plus the sum of each child's pref height plus spacing between each child.</td> 090 * </tr> 091 * <tr><th>maximum</th> 092 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 093 * </table> 094 * <p> 095 * A vbox's unbounded maximum width and height are an indication to the parent that 096 * it may be resized beyond its preferred size to fill whatever space is assigned 097 * to it. 098 * <p> 099 * VBox provides properties for setting the size range directly. These 100 * properties default to the sentinel value USE_COMPUTED_SIZE, however the 101 * application may set them to other values as needed: 102 * <pre><code> 103 * <b>vbox.setPrefWidth(400);</b> 104 * </code></pre> 105 * Applications may restore the computed values by setting these properties back 106 * to USE_COMPUTED_SIZE. 107 * <p> 108 * VBox does not clip its content by default, so it is possible that childrens' 109 * bounds may extend outside its own bounds if a child's min size prevents it from 110 * being fit within the vbox.</p> 111 * 112 * <h4>Optional Layout Constraints</h4> 113 * 114 * An application may set constraints on individual children to customize VBox's layout. 115 * For each constraint, VBox provides a static method for setting it on the child. 116 * <p> 117 * <table border="1"> 118 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr> 119 * <tr><td>vgrow</td><td>javafx.scene.layout.Priority</td><td>The vertical grow priority for the child.</td></tr> 120 * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr> 121 * </table> 122 * <p> 123 * For example, if a vbox needs the ListView to be allocated all extra space: 124 * <pre><code> 125 * VBox vbox = new VBox(); 126 * ListView list = new ListView(); 127 * <b>VBox.setVgrow(list, Priority.ALWAYS);</b> 128 * vbox.getChildren().addAll(new Label("Names:"), list); 129 * </code></pre> 130 * 131 * If more than one child has the same grow priority set, then the vbox will 132 * allocate equal amounts of space to each. VBox will only grow a child up to 133 * its maximum height, so if the child has a max height other than Double.MAX_VALUE, 134 * the application may need to override the max to allow it to grow. 135 */ 136public class VBox extends Pane { 137 138 private boolean biasDirty = true; 139 private boolean performingLayout = false; 140 private Orientation bias; 141 142/******************************************************************** 143 * BEGIN static methods 144 ********************************************************************/ 145 private static final String MARGIN_CONSTRAINT = "vbox-margin"; 146 private static final String VGROW_CONSTRAINT = "vbox-vgrow"; 147 148 /** 149 * Sets the vertical grow priority for the child when contained by an vbox. 150 * If set, the vbox will use the priority to allocate additional space if the 151 * vbox is resized larger than it's preferred height. 152 * If multiple vbox children have the same vertical grow priority, then the 153 * extra space will be split evenly between them. 154 * If no vertical grow priority is set on a child, the vbox will never 155 * allocate it additional vertical space if available. 156 * Setting the value to null will remove the constraint. 157 * @param child the child of a vbox 158 * @param value the horizontal grow priority for the child 159 */ 160 public static void setVgrow(Node child, Priority value) { 161 setConstraint(child, VGROW_CONSTRAINT, value); 162 } 163 164 /** 165 * Returns the child's vgrow property if set. 166 * @param child the child node of a vbox 167 * @return the vertical grow priority for the child or null if no priority was set 168 */ 169 public static Priority getVgrow(Node child) { 170 return (Priority)getConstraint(child, VGROW_CONSTRAINT); 171 } 172 173 /** 174 * Sets the margin for the child when contained by a vbox. 175 * If set, the vbox will layout the child so that it has the margin space around it. 176 * Setting the value to null will remove the constraint. 177 * @param child the child mode of a vbox 178 * @param value the margin of space around the child 179 */ 180 public static void setMargin(Node child, Insets value) { 181 setConstraint(child, MARGIN_CONSTRAINT, value); 182 } 183 184 /** 185 * Returns the child's margin property if set. 186 * @param child the child node of a vbox 187 * @return the margin for the child or null if no margin was set 188 */ 189 public static Insets getMargin(Node child) { 190 return (Insets)getConstraint(child, MARGIN_CONSTRAINT); 191 } 192 193 /** 194 * Removes all vbox constraints from the child node. 195 * @param child the child node 196 */ 197 public static void clearConstraints(Node child) { 198 setVgrow(child, null); 199 setMargin(child, null); 200 } 201 202 /******************************************************************** 203 * END static methods 204 ********************************************************************/ 205 206 /** 207 * Creates a VBox layout with spacing = 0 and alignment at TOP_LEFT. 208 */ 209 public VBox() { 210 super(); 211 } 212 213 /** 214 * Creates a VBox layout with the specified spacing between children. 215 * @param spacing the amount of vertical space between each child 216 */ 217 public VBox(double spacing) { 218 this(); 219 setSpacing(spacing); 220 } 221 222 /** 223 * Creates an VBox layout with spacing = 0. 224 * @param children The initial set of children for this pane. 225 */ 226 public VBox(Node... children) { 227 super(); 228 getChildren().addAll(children); 229 } 230 231 /** 232 * Creates an VBox layout with the specified spacing between children. 233 * @param spacing the amount of horizontal space between each child 234 * @param children The initial set of children for this pane. 235 */ 236 public VBox(double spacing, Node... children) { 237 this(); 238 setSpacing(spacing); 239 getChildren().addAll(children); 240 } 241 242 /** 243 * The amount of vertical space between each child in the vbox. 244 */ 245 public final DoubleProperty spacingProperty() { 246 if (spacing == null) { 247 spacing = new StyleableDoubleProperty() { 248 @Override 249 public void invalidated() { 250 requestLayout(); 251 } 252 253 @Override 254 public Object getBean() { 255 return VBox.this; 256 } 257 258 @Override 259 public String getName() { 260 return "spacing"; 261 } 262 263 @Override 264 public CssMetaData<VBox, Number> getCssMetaData() { 265 return StyleableProperties.SPACING; 266 } 267 }; 268 } 269 return spacing; 270 } 271 272 private DoubleProperty spacing; 273 public final void setSpacing(double value) { spacingProperty().set(value); } 274 public final double getSpacing() { return spacing == null ? 0 : spacing.get(); } 275 276 /** 277 * The overall alignment of children within the vbox's width and height. 278 */ 279 public final ObjectProperty<Pos> alignmentProperty() { 280 if (alignment == null) { 281 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 282 @Override 283 public void invalidated() { 284 requestLayout(); 285 } 286 287 @Override 288 public Object getBean() { 289 return VBox.this; 290 } 291 292 @Override 293 public String getName() { 294 return "alignment"; 295 } 296 297 @Override 298 public CssMetaData<VBox, Pos> getCssMetaData() { 299 return StyleableProperties.ALIGNMENT; 300 } 301 }; 302 } 303 return alignment; 304 } 305 306 private ObjectProperty<Pos> alignment; 307 public final void setAlignment(Pos value) { alignmentProperty().set(value); } 308 public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); } 309 private Pos getAlignmentInternal() { 310 Pos localPos = getAlignment(); 311 return localPos == null ? Pos.TOP_LEFT : localPos; 312 } 313 314 /** 315 * Whether or not resizable children will be resized to fill the full width of the vbox 316 * or be kept to their preferred width and aligned according to the <code>alignment</code> 317 * hpos value. 318 */ 319 public final BooleanProperty fillWidthProperty() { 320 if (fillWidth == null) { 321 fillWidth = new StyleableBooleanProperty(true) { 322 @Override 323 public void invalidated() { 324 requestLayout(); 325 } 326 327 @Override 328 public Object getBean() { 329 return VBox.this; 330 } 331 332 @Override 333 public String getName() { 334 return "fillWidth"; 335 } 336 337 @Override 338 public CssMetaData<VBox, Boolean> getCssMetaData() { 339 return StyleableProperties.FILL_WIDTH; 340 } 341 }; 342 } 343 return fillWidth; 344 } 345 346 private BooleanProperty fillWidth; 347 public final void setFillWidth(boolean value) { fillWidthProperty().set(value); } 348 public final boolean isFillWidth() { return fillWidth == null ? true : fillWidth.get(); } 349 350 /** 351 * 352 * @return null unless one of its children has a content bias. 353 */ 354 @Override public Orientation getContentBias() { 355 if (biasDirty) { 356 final List<Node> children = getChildren(); 357 for (Node child : children) { 358 Orientation contentBias = child.getContentBias(); 359 if (child.isManaged() && contentBias != null) { 360 bias = contentBias; 361 break; 362 } 363 } 364 biasDirty = false; 365 } 366 return bias; 367 } 368 369 @Override protected double computeMinWidth(double height) { 370 Insets insets = getInsets(); 371 List<Node>managed = getManagedChildren(); 372 double contentWidth = 0; 373 if (getContentBias() == Orientation.VERTICAL) { 374 double prefHeights[] = getAreaHeights(managed, -1, false); 375 adjustAreaHeights(managed, prefHeights, height, -1); 376 contentWidth = computeMaxMinAreaWidth(managed, getMargins(managed), prefHeights, getAlignmentInternal().getHpos()); 377 } else { 378 contentWidth = computeMaxMinAreaWidth(managed, getMargins(managed), getAlignmentInternal().getHpos()); 379 } 380 return snapSpace(insets.getLeft()) + contentWidth + snapSpace(insets.getRight()); 381 } 382 383 @Override protected double computeMinHeight(double width) { 384 Insets insets = getInsets(); 385 return snapSpace(insets.getTop()) + 386 computeContentHeight(getAreaHeights(getManagedChildren(), width, true)) + 387 snapSpace(insets.getBottom()); 388 } 389 390 @Override protected double computePrefWidth(double height) { 391 Insets insets = getInsets(); 392 List<Node>managed = getManagedChildren(); 393 double contentWidth = 0; 394 if (getContentBias() == Orientation.VERTICAL) { 395 double prefHeights[] = getAreaHeights(managed, -1, false); 396 adjustAreaHeights(managed, prefHeights, height, -1); 397 contentWidth = computeMaxPrefAreaWidth(managed, getMargins(managed), prefHeights, getAlignmentInternal().getHpos()); 398 } else { 399 contentWidth = computeMaxPrefAreaWidth(managed, getMargins(managed), getAlignmentInternal().getHpos()); 400 } 401 return snapSpace(insets.getLeft()) + contentWidth + snapSpace(insets.getRight()); 402 } 403 404 @Override protected double computePrefHeight(double width) { 405 Insets insets = getInsets(); 406 double d = snapSpace(insets.getTop()) + 407 computeContentHeight(getAreaHeights(getManagedChildren(), width, false)) + 408 snapSpace(insets.getBottom()); 409 return d; 410 } 411 412 private Insets[] getMargins(List<Node>managed) { 413 Insets margins[] = new Insets[managed.size()]; 414 for(int i = 0; i < margins.length; i++) { 415 margins[i] = getMargin(managed.get(i)); 416 } 417 return margins; 418 } 419 420 private double[] getAreaHeights(List<Node>managed, double width, boolean minimum) { 421 double[] prefAreaHeights = new double [managed.size()]; 422 final double insideWidth = width == -1? -1 : width - 423 snapSpace(getInsets().getLeft()) - snapSpace(getInsets().getRight()); 424 for (int i = 0, size = managed.size(); i < size; i++) { 425 Node child = managed.get(i); 426 Insets margin = getMargin(child); 427 prefAreaHeights[i] = minimum? 428 computeChildMinAreaHeight(child, margin, 429 isFillWidth()? insideWidth : child.minWidth(-1)) : 430 computeChildPrefAreaHeight(child, margin, 431 isFillWidth()? insideWidth : child.prefWidth(-1)); 432 } 433 return prefAreaHeights; 434 } 435 436 private double adjustAreaHeights(List<Node>managed, double areaHeights[], double height, double width) { 437 Insets insets = getInsets(); 438 double left = snapSpace(insets.getLeft()); 439 double right = snapSpace(insets.getRight()); 440 441 double contentHeight = computeContentHeight(areaHeights); 442 double extraHeight = (height == -1 ? prefHeight(-1) : height) - 443 snapSpace(insets.getTop()) - snapSpace(insets.getBottom()) - contentHeight; 444 445 if (extraHeight != 0) { 446 double remaining = growOrShrinkAreaHeights(managed, areaHeights, 447 Priority.ALWAYS, extraHeight, isFillWidth() && width != -1? width - left - right: -1); 448 remaining = growOrShrinkAreaHeights(managed, areaHeights, 449 Priority.SOMETIMES, remaining, isFillWidth() && width != -1? width - left - right: -1); 450 contentHeight += (extraHeight - remaining); 451 } 452 453 return contentHeight; 454 } 455 456 private double growOrShrinkAreaHeights(List<Node>managed, double areaHeights[], Priority priority, double extraHeight, double width) { 457 final boolean shrinking = extraHeight < 0; 458 List<Node> adjustList = new ArrayList<Node>(); 459 List<Node> adjusting = new ArrayList<Node>(); 460 461 for (int i = 0, size = managed.size(); i < size; i++) { 462 Node child = managed.get(i); 463 if (shrinking || getVgrow(child) == priority) { 464 adjustList.add(child); 465 adjusting.add(child); 466 } 467 } 468 469 double[] areaLimitHeights = new double[adjustList.size()]; 470 for (int i = 0, size = adjustList.size(); i < size; i++) { 471 Node child = adjustList.get(i); 472 Insets margin = getMargin(child); 473 areaLimitHeights[i] = shrinking? 474 computeChildMinAreaHeight(child, margin, width) : computeChildMaxAreaHeight(child, margin, width); 475 } 476 477 double available = extraHeight; // will be negative in shrinking case 478 while (Math.abs(available) > 1.0 && adjusting.size() > 0) { 479 Node[] adjusted = new Node[adjustList.size()]; 480 final double portion = available / adjusting.size(); // negative in shrinking case 481 for (int i = 0, size = adjusting.size(); i < size; i++) { 482 final Node child = adjusting.get(i); 483 final int childIndex = managed.indexOf(child); 484 final double limit = areaLimitHeights[adjustList.indexOf(child)] - areaHeights[childIndex]; // negative in shrinking case 485 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 486 areaHeights[childIndex] += change; 487 //if (node.id.startsWith("debug.")) println("{if (shrinking) "shrink" else "grow"}: {node.id} portion({portion})=available({available})/({sizeof adjusting}) change={change}"); 488 available -= change; 489 if (Math.abs(change) < Math.abs(portion)) { 490 adjusted[i] = child; 491 } 492 } 493 for (Node node : adjusted) { 494 if (node != null) { 495 adjusting.remove(node); 496 } 497 } 498 } 499 return available; // might be negative in shrinking case 500 } 501 502 private double computeContentHeight(double[] heights) { 503 double total = 0; 504 for (double h : heights) { 505 total += h; 506 } 507 return total + (heights.length-1)*snapSpace(getSpacing()); 508 } 509 510 private double[] actualAreaHeights; 511 512 @Override public void requestLayout() { 513 if (performingLayout) { 514 return; 515 } 516 biasDirty = true; 517 bias = null; 518 super.requestLayout(); 519 } 520 521 @Override protected void layoutChildren() { 522 performingLayout = true; 523 List<Node> managed = getManagedChildren(); 524 Insets insets = getInsets(); 525 double width = getWidth(); 526 double height = getHeight(); 527 double top = snapSpace(insets.getTop()); 528 double left = snapSpace(insets.getLeft()); 529 double bottom = snapSpace(insets.getBottom()); 530 double right = snapSpace(insets.getRight()); 531 double space = snapSpace(getSpacing()); 532 HPos hpos = getAlignmentInternal().getHpos(); 533 VPos vpos = getAlignmentInternal().getVpos(); 534 boolean isFillWidth = isFillWidth(); 535 536 actualAreaHeights = getAreaHeights(managed, width, false); 537 double contentWidth = width - left - right; 538 double contentHeight = adjustAreaHeights(managed, actualAreaHeights, height, width); 539 540 double x = left; 541 double y = top + computeYOffset(height - top - bottom, contentHeight, vpos); 542 543 for (int i = 0, size = managed.size(); i < size; i++) { 544 Node child = managed.get(i); 545 layoutInArea(child, x, y, contentWidth, actualAreaHeights[i], 546 /* baseline shouldn't matter */actualAreaHeights[i], 547 getMargin(child), isFillWidth, true, 548 hpos, vpos); 549 y += actualAreaHeights[i] + space; 550 } 551 performingLayout = false; 552 } 553 554 /*************************************************************************** 555 * * 556 * Stylesheet Handling * 557 * * 558 **************************************************************************/ 559 560 /** 561 * Super-lazy instantiation pattern from Bill Pugh. 562 * @treatAsPrivate implementation detail 563 */ 564 private static class StyleableProperties { 565 private static final CssMetaData<VBox,Pos> ALIGNMENT = 566 new CssMetaData<VBox,Pos>("-fx-alignment", 567 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT){ 568 569 @Override 570 public boolean isSettable(VBox node) { 571 return node.alignment == null || !node.alignment.isBound(); 572 } 573 574 @Override 575 public StyleableProperty<Pos> getStyleableProperty(VBox node) { 576 return (StyleableProperty<Pos>)node.alignmentProperty(); 577 } 578 }; 579 580 private static final CssMetaData<VBox,Boolean> FILL_WIDTH = 581 new CssMetaData<VBox,Boolean>("-fx-fill-width", 582 BooleanConverter.getInstance(), Boolean.TRUE) { 583 584 @Override 585 public boolean isSettable(VBox node) { 586 return node.fillWidth == null || !node.fillWidth.isBound(); 587 } 588 589 @Override 590 public StyleableProperty<Boolean> getStyleableProperty(VBox node) { 591 return (StyleableProperty<Boolean>)node.fillWidthProperty(); 592 } 593 }; 594 595 private static final CssMetaData<VBox,Number> SPACING = 596 new CssMetaData<VBox,Number>("-fx-spacing", 597 SizeConverter.getInstance(), 0d) { 598 599 @Override 600 public boolean isSettable(VBox node) { 601 return node.spacing == null || !node.spacing.isBound(); 602 } 603 604 @Override 605 public StyleableProperty<Number> getStyleableProperty(VBox node) { 606 return (StyleableProperty<Number>)node.spacingProperty(); 607 } 608 }; 609 610 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 611 static { 612 final List<CssMetaData<? extends Styleable, ?>> styleables = 613 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 614 styleables.add(ALIGNMENT); 615 styleables.add(FILL_WIDTH); 616 styleables.add(SPACING); 617 STYLEABLES = Collections.unmodifiableList(styleables); 618 } 619 } 620 621 /** 622 * @return The CssMetaData associated with this class, which may include the 623 * CssMetaData of its super classes. 624 */ 625 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 626 return StyleableProperties.STYLEABLES; 627 } 628 629 /** 630 * {@inheritDoc} 631 * 632 */ 633 634 635 @Override 636 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 637 return getClassCssMetaData(); 638 } 639 640}