1 /* 2 * Copyright (c) 2011, 2017, 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.DoubleProperty; 32 import javafx.beans.property.DoublePropertyBase; 33 import javafx.beans.property.ObjectProperty; 34 import javafx.css.CssMetaData; 35 import javafx.css.StyleableDoubleProperty; 36 import javafx.css.StyleableObjectProperty; 37 import javafx.css.StyleableProperty; 38 import javafx.geometry.HPos; 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.EnumConverter; 45 import javafx.css.converter.SizeConverter; 46 import javafx.css.Styleable; 47 48 import static javafx.geometry.Orientation.*; 49 import javafx.util.Callback; 50 51 /** 52 * FlowPane lays out its children in a flow that wraps at the flowpane's boundary. 53 * <p> 54 * A horizontal flowpane (the default) will layout nodes in rows, wrapping at the 55 * flowpane's width. A vertical flowpane lays out nodes in columns, 56 * wrapping at the flowpane's height. If the flowpane has a border and/or padding set, 57 * the content will be flowed within those insets. 58 * <p> 59 * FlowPane's prefWrapLength property establishes it's preferred width 60 * (for horizontal) or preferred height (for vertical). Applications should set 61 * prefWrapLength if the default value (400) doesn't suffice. Note that prefWrapLength 62 * is used only for calculating the preferred size and may not reflect the actual 63 * wrapping dimension, which tracks the actual size of the flowpane. 64 * <p> 65 * The alignment property controls how the rows and columns are aligned 66 * within the bounds of the flowpane and defaults to Pos.TOP_LEFT. It is also possible 67 * to control the alignment of nodes within the rows and columns by setting 68 * rowValignment for horizontal or columnHalignment for vertical. 69 * <p> 70 * Example of a horizontal flowpane: 71 * <pre>{@code 72 * Image images[] = { ... }; 73 * FlowPane flow = new FlowPane(); 74 * flow.setVgap(8); 75 * flow.setHgap(4); 76 * flow.setPrefWrapLength(300); // preferred width = 300 77 * for (int i = 0; i < images.length; i++) { 78 * flow.getChildren().add(new ImageView(image[i]); 79 * } 80 * }</pre> 81 * 82 *<p> 83 * Example of a vertical flowpane: 84 * <pre>{@code 85 * FlowPane flow = new FlowPane(Orientation.VERTICAL); 86 * flow.setColumnHalignment(HPos.LEFT); // align labels on left 87 * flow.setPrefWrapLength(200); // preferred height = 200 88 * for (int i = 0; i < titles.size(); i++) { 89 * flow.getChildren().add(new Label(titles[i]); 90 * } 91 * }</pre> 92 * 93 * <p> 94 * FlowPane lays out each managed child regardless of the child's visible property value; 95 * unmanaged children are ignored for all layout calculations.</p> 96 * 97 * <p> 98 * FlowPane may be styled with backgrounds and borders using CSS. See 99 * {@link javafx.scene.layout.Region Region} superclass for details.</p> 100 * 101 * <h3>Resizable Range</h3> 102 * 103 * <p> 104 * A flowpane's parent will resize the flowpane within the flowpane's resizable range 105 * during layout. By default the flowpane computes this range based on its content 106 * as outlined in the tables below. 107 * </p> 108 * <table border="1"> 109 * <caption>Horizontal</caption> 110 * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr> 111 * <tr><th scope="row">minimum</th> 112 * <td>left/right insets plus largest of children's pref widths</td> 113 * <td>top/bottom insets plus height required to display all children at their preferred heights when wrapped at a specified width</td></tr> 114 * <tr><th scope="row">preferred</th> 115 * <td>left/right insets plus prefWrapLength</td> 116 * <td>top/bottom insets plus height required to display all children at their pref heights when wrapped at a specified width</td></tr> 117 * <tr><th scope="row">maximum</th> 118 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 119 * </table> 120 * <br> 121 * <table border="1"> 122 * <caption>Vertical</caption> 123 * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr> 124 * <tr><th scope="row">minimum</th> 125 * <td>left/right insets plus width required to display all children at their preferred widths when wrapped at a specified height</td> 126 * <td>top/bottom insets plus largest of children's pref heights</td></tr> 127 * <tr><th scope="row">preferred</th> 128 * <td>left/right insets plus width required to display all children at their pref widths when wrapped at the specified height</td> 129 * <td>top/bottom insets plus prefWrapLength</td></tr> 130 * <tr><th scope="row">maximum</th> 131 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 132 * </table> 133 * <p> 134 * A flowpane's unbounded maximum width and height are an indication to the parent that 135 * it may be resized beyond its preferred size to fill whatever space is assigned to it. 136 * <p> 137 * FlowPane provides properties for setting the size range directly. These 138 * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the 139 * application may set them to other values as needed: 140 * <pre>{@code 141 * <b>flowpane.setMaxWidth(500);</b> 142 * }</pre> 143 * Applications may restore the computed values by setting these properties back 144 * to Region.USE_COMPUTED_SIZE. 145 * <p> 146 * FlowPane does not clip its content by default, so it is possible that childrens' 147 * bounds may extend outside its own bounds if a child's pref size is larger than 148 * the space flowpane has to allocate for it.</p> 149 * 150 * @since JavaFX 2.0 151 */ 152 public class FlowPane extends Pane { 153 154 /******************************************************************** 155 * BEGIN static methods 156 ********************************************************************/ 157 private static final String MARGIN_CONSTRAINT = "flowpane-margin"; 158 159 /** 160 * Sets the margin for the child when contained by a flowpane. 161 * If set, the flowpane will layout it out with the margin space around it. 162 * Setting the value to null will remove the constraint. 163 * @param child the child node of a flowpane 164 * @param value the margin of space around the child 165 */ 166 public static void setMargin(Node child, Insets value) { 167 setConstraint(child, MARGIN_CONSTRAINT, value); 168 } 169 170 /** 171 * Returns the child's margin constraint if set. 172 * @param child the child node of a flowpane 173 * @return the margin for the child or null if no margin was set 174 */ 175 public static Insets getMargin(Node child) { 176 return (Insets)getConstraint(child, MARGIN_CONSTRAINT); 177 } 178 179 private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n); 180 181 /** 182 * Removes all flowpane constraints from the child node. 183 * @param child the child node 184 */ 185 public static void clearConstraints(Node child) { 186 setMargin(child, null); 187 } 188 189 /******************************************************************** 190 * END static methods 191 ********************************************************************/ 192 193 /** 194 * Creates a horizontal FlowPane layout with hgap/vgap = 0. 195 */ 196 public FlowPane() { 197 super(); 198 } 199 200 /** 201 * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0. 202 * @param orientation the direction the tiles should flow & wrap 203 */ 204 public FlowPane(Orientation orientation) { 205 this(); 206 setOrientation(orientation); 207 } 208 209 /** 210 * Creates a horizontal FlowPane layout with the specified hgap/vgap. 211 * @param hgap the amount of horizontal space between each tile 212 * @param vgap the amount of vertical space between each tile 213 */ 214 public FlowPane(double hgap, double vgap) { 215 this(); 216 setHgap(hgap); 217 setVgap(vgap); 218 } 219 220 /** 221 * Creates a FlowPane layout with the specified orientation and hgap/vgap. 222 * @param orientation the direction the tiles should flow & wrap 223 * @param hgap the amount of horizontal space between each tile 224 * @param vgap the amount of vertical space between each tile 225 */ 226 public FlowPane(Orientation orientation, double hgap, double vgap) { 227 this(); 228 setOrientation(orientation); 229 setHgap(hgap); 230 setVgap(vgap); 231 } 232 233 /** 234 * Creates a horizontal FlowPane layout with hgap/vgap = 0. 235 * @param children The initial set of children for this pane. 236 * @since JavaFX 8.0 237 */ 238 public FlowPane(Node... children) { 239 super(); 240 getChildren().addAll(children); 241 } 242 243 /** 244 * Creates a FlowPane layout with the specified orientation and hgap/vgap = 0. 245 * @param orientation the direction the tiles should flow & wrap 246 * @param children The initial set of children for this pane. 247 * @since JavaFX 8.0 248 */ 249 public FlowPane(Orientation orientation, Node... children) { 250 this(); 251 setOrientation(orientation); 252 getChildren().addAll(children); 253 } 254 255 /** 256 * Creates a horizontal FlowPane layout with the specified hgap/vgap. 257 * @param hgap the amount of horizontal space between each tile 258 * @param vgap the amount of vertical space between each tile 259 * @param children The initial set of children for this pane. 260 * @since JavaFX 8.0 261 */ 262 public FlowPane(double hgap, double vgap, Node... children) { 263 this(); 264 setHgap(hgap); 265 setVgap(vgap); 266 getChildren().addAll(children); 267 } 268 269 /** 270 * Creates a FlowPane layout with the specified orientation and hgap/vgap. 271 * @param orientation the direction the tiles should flow & wrap 272 * @param hgap the amount of horizontal space between each tile 273 * @param vgap the amount of vertical space between each tile 274 * @param children The initial set of children for this pane. 275 * @since JavaFX 8.0 276 */ 277 public FlowPane(Orientation orientation, double hgap, double vgap, Node... children) { 278 this(); 279 setOrientation(orientation); 280 setHgap(hgap); 281 setVgap(vgap); 282 getChildren().addAll(children); 283 } 284 285 /** 286 * The orientation of this flowpane. 287 * A horizontal flowpane lays out children left to right, wrapping at the 288 * flowpane's width boundary. A vertical flowpane lays out children top to 289 * bottom, wrapping at the flowpane's height. 290 * The default is horizontal. 291 * @return the orientation of this flowpane 292 */ 293 public final ObjectProperty<Orientation> orientationProperty() { 294 if (orientation == null) { 295 orientation = new StyleableObjectProperty(HORIZONTAL) { 296 @Override 297 public void invalidated() { 298 requestLayout(); 299 } 300 301 @Override 302 public CssMetaData<FlowPane, Orientation> getCssMetaData() { 303 return StyleableProperties.ORIENTATION; 304 } 305 306 @Override 307 public Object getBean() { 308 return FlowPane.this; 309 } 310 311 @Override 312 public String getName() { 313 return "orientation"; 314 } 315 }; 316 } 317 return orientation; 318 } 319 320 private ObjectProperty<Orientation> orientation; 321 public final void setOrientation(Orientation value) { orientationProperty().set(value); } 322 public final Orientation getOrientation() { return orientation == null ? HORIZONTAL : orientation.get(); } 323 324 /** 325 * The amount of horizontal space between each node in a horizontal flowpane 326 * or the space between columns in a vertical flowpane. 327 * @return the amount of horizontal space between each node in a horizontal 328 * flowpane or the space between columns in a vertical flowpane 329 */ 330 public final DoubleProperty hgapProperty() { 331 if (hgap == null) { 332 hgap = new StyleableDoubleProperty() { 333 334 @Override 335 public void invalidated() { 336 requestLayout(); 337 } 338 339 @Override 340 public CssMetaData<FlowPane, Number> getCssMetaData() { 341 return StyleableProperties.HGAP; 342 } 343 344 @Override 345 public Object getBean() { 346 return FlowPane.this; 347 } 348 349 @Override 350 public String getName() { 351 return "hgap"; 352 } 353 }; 354 } 355 return hgap; 356 } 357 358 private DoubleProperty hgap; 359 public final void setHgap(double value) { hgapProperty().set(value); } 360 public final double getHgap() { return hgap == null ? 0 : hgap.get(); } 361 362 /** 363 * The amount of vertical space between each node in a vertical flowpane 364 * or the space between rows in a horizontal flowpane. 365 * @return the amount of vertical space between each node in a vertical 366 * flowpane or the space between rows in a horizontal flowpane 367 */ 368 public final DoubleProperty vgapProperty() { 369 if (vgap == null) { 370 vgap = new StyleableDoubleProperty() { 371 @Override 372 public void invalidated() { 373 requestLayout(); 374 } 375 376 @Override 377 public CssMetaData<FlowPane, Number> getCssMetaData() { 378 return StyleableProperties.VGAP; 379 } 380 381 @Override 382 public Object getBean() { 383 return FlowPane.this; 384 } 385 386 @Override 387 public String getName() { 388 return "vgap"; 389 } 390 }; 391 } 392 return vgap; 393 } 394 395 private DoubleProperty vgap; 396 public final void setVgap(double value) { vgapProperty().set(value); } 397 public final double getVgap() { return vgap == null ? 0 : vgap.get(); } 398 399 400 /** 401 * The preferred width where content should wrap in a horizontal flowpane or 402 * the preferred height where content should wrap in a vertical flowpane. 403 * <p> 404 * This value is used only to compute the preferred size of the flowpane and may 405 * not reflect the actual width or height, which may change if the flowpane is 406 * resized to something other than its preferred size. 407 * <p> 408 * Applications should initialize this value to define a reasonable span 409 * for wrapping the content. 410 * 411 * @return the preferred width where content should wrap in a horizontal 412 * flowpane or the preferred height where content should wrap in a vertical 413 * flowpane 414 */ 415 public final DoubleProperty prefWrapLengthProperty() { 416 if (prefWrapLength == null) { 417 prefWrapLength = new DoublePropertyBase(400) { 418 @Override 419 protected void invalidated() { 420 requestLayout(); 421 } 422 423 @Override 424 public Object getBean() { 425 return FlowPane.this; 426 } 427 428 @Override 429 public String getName() { 430 return "prefWrapLength"; 431 } 432 }; 433 } 434 return prefWrapLength; 435 } 436 private DoubleProperty prefWrapLength; 437 public final void setPrefWrapLength(double value) { prefWrapLengthProperty().set(value); } 438 public final double getPrefWrapLength() { return prefWrapLength == null ? 400 : prefWrapLength.get(); } 439 440 441 /** 442 * The overall alignment of the flowpane's content within its width and height. 443 * <p>For a horizontal flowpane, each row will be aligned within the flowpane's width 444 * using the alignment's hpos value, and the rows will be aligned within the 445 * flowpane's height using the alignment's vpos value. 446 * <p>For a vertical flowpane, each column will be aligned within the flowpane's height 447 * using the alignment's vpos value, and the columns will be aligned within the 448 * flowpane's width using the alignment's hpos value. 449 * @return the overall alignment of the flowpane's content within its width 450 * and height 451 */ 452 public final ObjectProperty<Pos> alignmentProperty() { 453 if (alignment == null) { 454 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 455 456 @Override 457 public void invalidated() { 458 requestLayout(); 459 } 460 461 @Override 462 public CssMetaData<FlowPane, Pos> getCssMetaData() { 463 return StyleableProperties.ALIGNMENT; 464 } 465 466 @Override 467 public Object getBean() { 468 return FlowPane.this; 469 } 470 471 @Override 472 public String getName() { 473 return "alignment"; 474 } 475 }; 476 } 477 return alignment; 478 } 479 480 private ObjectProperty<Pos> alignment; 481 public final void setAlignment(Pos value) { alignmentProperty().set(value); } 482 public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); } 483 private Pos getAlignmentInternal() { 484 Pos localPos = getAlignment(); 485 return localPos == null ? Pos.TOP_LEFT : localPos; 486 } 487 488 /** 489 * The horizontal alignment of nodes within each column of a vertical flowpane. 490 * The property is ignored for horizontal flowpanes. 491 * @return the horizontal alignment of nodes within each column of a 492 * vertical flowpane 493 */ 494 public final ObjectProperty<HPos> columnHalignmentProperty() { 495 if (columnHalignment == null) { 496 columnHalignment = new StyleableObjectProperty<HPos>(HPos.LEFT) { 497 498 @Override 499 public void invalidated() { 500 requestLayout(); 501 } 502 503 @Override 504 public CssMetaData<FlowPane, HPos> getCssMetaData() { 505 return StyleableProperties.COLUMN_HALIGNMENT; 506 } 507 508 @Override 509 public Object getBean() { 510 return FlowPane.this; 511 } 512 513 @Override 514 public String getName() { 515 return "columnHalignment"; 516 } 517 }; 518 } 519 return columnHalignment; 520 } 521 522 private ObjectProperty<HPos> columnHalignment; 523 public final void setColumnHalignment(HPos value) { columnHalignmentProperty().set(value); } 524 public final HPos getColumnHalignment() { return columnHalignment == null ? HPos.LEFT : columnHalignment.get(); } 525 private HPos getColumnHalignmentInternal() { 526 HPos localPos = getColumnHalignment(); 527 return localPos == null ? HPos.LEFT : localPos; 528 } 529 530 /** 531 * The vertical alignment of nodes within each row of a horizontal flowpane. 532 * If this property is set to VPos.BASELINE, then the flowpane will always 533 * resize children to their preferred heights, rather than expanding heights 534 * to fill the row height. 535 * The property is ignored for vertical flowpanes. 536 * @return the vertical alignment of nodes within each row of a horizontal 537 * flowpane 538 */ 539 public final ObjectProperty<VPos> rowValignmentProperty() { 540 if (rowValignment == null) { 541 rowValignment = new StyleableObjectProperty<VPos>(VPos.CENTER) { 542 @Override 543 public void invalidated() { 544 requestLayout(); 545 } 546 547 @Override 548 public CssMetaData<FlowPane, VPos> getCssMetaData() { 549 return StyleableProperties.ROW_VALIGNMENT; 550 } 551 552 @Override 553 public Object getBean() { 554 return FlowPane.this; 555 } 556 557 @Override 558 public String getName() { 559 return "rowValignment"; 560 } 561 }; 562 } 563 return rowValignment; 564 } 565 566 private ObjectProperty<VPos> rowValignment; 567 public final void setRowValignment(VPos value) { rowValignmentProperty().set(value); } 568 public final VPos getRowValignment() { return rowValignment == null ? VPos.CENTER : rowValignment.get(); } 569 private VPos getRowValignmentInternal() { 570 VPos localPos = getRowValignment(); 571 return localPos == null ? VPos.CENTER : localPos; 572 } 573 574 @Override public Orientation getContentBias() { 575 return getOrientation(); 576 } 577 578 @Override protected double computeMinWidth(double height) { 579 if (getContentBias() == HORIZONTAL) { 580 double maxPref = 0; 581 final List<Node> children = getChildren(); 582 for (int i=0, size=children.size(); i<size; i++) { 583 Node child = children.get(i); 584 if (child.isManaged()) { 585 maxPref = Math.max(maxPref, child.prefWidth(-1)); 586 } 587 } 588 final Insets insets = getInsets(); 589 return insets.getLeft() + snapSizeX(maxPref) + insets.getRight(); 590 } 591 return computePrefWidth(height); 592 } 593 594 @Override protected double computeMinHeight(double width) { 595 if (getContentBias() == VERTICAL) { 596 double maxPref = 0; 597 final List<Node> children = getChildren(); 598 for (int i=0, size=children.size(); i<size; i++) { 599 Node child = children.get(i); 600 if (child.isManaged()) { 601 maxPref = Math.max(maxPref, child.prefHeight(-1)); 602 } 603 } 604 final Insets insets = getInsets(); 605 return insets.getTop() + snapSizeY(maxPref) + insets.getBottom(); 606 } 607 return computePrefHeight(width); 608 } 609 610 @Override protected double computePrefWidth(double forHeight) { 611 final Insets insets = getInsets(); 612 if (getOrientation() == HORIZONTAL) { 613 // horizontal 614 double maxRunWidth = getPrefWrapLength(); 615 List<Run> hruns = getRuns(maxRunWidth); 616 double w = computeContentWidth(hruns); 617 w = getPrefWrapLength() > w ? getPrefWrapLength() : w; 618 return insets.getLeft() + snapSizeX(w) + insets.getRight(); 619 } else { 620 // vertical 621 double maxRunHeight = forHeight != -1? 622 forHeight - insets.getTop() - insets.getBottom() : getPrefWrapLength(); 623 List<Run> vruns = getRuns(maxRunHeight); 624 return insets.getLeft() + computeContentWidth(vruns) + insets.getRight(); 625 } 626 } 627 628 @Override protected double computePrefHeight(double forWidth) { 629 final Insets insets = getInsets(); 630 if (getOrientation() == HORIZONTAL) { 631 // horizontal 632 double maxRunWidth = forWidth != -1? 633 forWidth - insets.getLeft() - insets.getRight() : getPrefWrapLength(); 634 List<Run> hruns = getRuns(maxRunWidth); 635 return insets.getTop() + computeContentHeight(hruns) + insets.getBottom(); 636 } else { 637 // vertical 638 double maxRunHeight = getPrefWrapLength(); 639 List<Run> vruns = getRuns(maxRunHeight); 640 double h = computeContentHeight(vruns); 641 h = getPrefWrapLength() > h ? getPrefWrapLength() : h; 642 return insets.getTop() + snapSizeY(h) + insets.getBottom(); 643 } 644 } 645 646 @Override public void requestLayout() { 647 if (!computingRuns) { 648 runs = null; 649 } 650 super.requestLayout(); 651 } 652 653 private List<Run> runs = null; 654 private double lastMaxRunLength = -1; 655 boolean computingRuns = false; 656 657 private List<Run> getRuns(double maxRunLength) { 658 if (runs == null || maxRunLength != lastMaxRunLength) { 659 computingRuns = true; 660 lastMaxRunLength = maxRunLength; 661 runs = new ArrayList(); 662 double runLength = 0; 663 double runOffset = 0; 664 Run run = new Run(); 665 double vgap = snapSpaceY(this.getVgap()); 666 double hgap = snapSpaceX(this.getHgap()); 667 668 final List<Node> children = getChildren(); 669 for (int i=0, size=children.size(); i<size; i++) { 670 Node child = children.get(i); 671 if (child.isManaged()) { 672 LayoutRect nodeRect = new LayoutRect(); 673 nodeRect.node = child; 674 Insets margin = getMargin(child); 675 nodeRect.width = computeChildPrefAreaWidth(child, margin); 676 nodeRect.height = computeChildPrefAreaHeight(child, margin); 677 double nodeLength = getOrientation() == HORIZONTAL ? nodeRect.width : nodeRect.height; 678 if (runLength + nodeLength > maxRunLength && runLength > 0) { 679 // wrap to next run *unless* its the only node in the run 680 normalizeRun(run, runOffset); 681 if (getOrientation() == HORIZONTAL) { 682 // horizontal 683 runOffset += run.height + vgap; 684 } else { 685 // vertical 686 runOffset += run.width + hgap; 687 } 688 runs.add(run); 689 runLength = 0; 690 run = new Run(); 691 } 692 if (getOrientation() == HORIZONTAL) { 693 // horizontal 694 nodeRect.x = runLength; 695 runLength += nodeRect.width + hgap; 696 } else { 697 // vertical 698 nodeRect.y = runLength; 699 runLength += nodeRect.height + vgap; 700 } 701 run.rects.add(nodeRect); 702 } 703 704 } 705 // insert last run 706 normalizeRun(run, runOffset); 707 runs.add(run); 708 computingRuns = false; 709 } 710 return runs; 711 } 712 713 private void normalizeRun(final Run run, double runOffset) { 714 if (getOrientation() == HORIZONTAL) { 715 // horizontal 716 ArrayList<Node> rownodes = new ArrayList(); 717 run.width = (run.rects.size()-1)*snapSpaceX(getHgap()); 718 for (int i=0, max=run.rects.size(); i<max; i++) { 719 LayoutRect lrect = run.rects.get(i); 720 rownodes.add(lrect.node); 721 run.width += lrect.width; 722 lrect.y = runOffset; 723 } 724 run.height = computeMaxPrefAreaHeight(rownodes, marginAccessor, getRowValignment()); 725 run.baselineOffset = getRowValignment() == VPos.BASELINE? 726 getAreaBaselineOffset(rownodes, marginAccessor, i -> run.rects.get(i).width, run.height, true) : 0; 727 728 } else { 729 // vertical 730 run.height = (run.rects.size()-1)*snapSpaceY(getVgap()); 731 double maxw = 0; 732 for (int i=0, max=run.rects.size(); i<max; i++) { 733 LayoutRect lrect = run.rects.get(i); 734 run.height += lrect.height; 735 lrect.x = runOffset; 736 maxw = Math.max(maxw, lrect.width); 737 } 738 739 run.width = maxw; 740 run.baselineOffset = run.height; 741 } 742 } 743 744 private double computeContentWidth(List<Run> runs) { 745 double cwidth = getOrientation() == HORIZONTAL ? 0 : (runs.size()-1)*snapSpaceX(getHgap()); 746 for (int i=0, max=runs.size(); i<max; i++) { 747 Run run = runs.get(i); 748 if (getOrientation() == HORIZONTAL) { 749 cwidth = Math.max(cwidth, run.width); 750 } else { 751 // vertical 752 cwidth += run.width; 753 } 754 } 755 return cwidth; 756 } 757 758 private double computeContentHeight(List<Run> runs) { 759 double cheight = getOrientation() == VERTICAL ? 0 : (runs.size()-1)*snapSpaceY(getVgap()); 760 for (int i=0, max=runs.size(); i<max; i++) { 761 Run run = runs.get(i); 762 if (getOrientation() == VERTICAL) { 763 cheight = Math.max(cheight, run.height); 764 } else { 765 // horizontal 766 cheight += run.height; 767 } 768 } 769 return cheight; 770 } 771 772 @Override protected void layoutChildren() { 773 final Insets insets = getInsets(); 774 final double width = getWidth(); 775 final double height = getHeight(); 776 final double top = insets.getTop(); 777 final double left = insets.getLeft(); 778 final double bottom = insets.getBottom(); 779 final double right = insets.getRight(); 780 final double insideWidth = width - left - right; 781 final double insideHeight = height - top - bottom; 782 783 //REMIND(aim): need to figure out how to cache the runs to avoid over-calculation 784 final List<Run> runs = getRuns(getOrientation() == HORIZONTAL ? insideWidth : insideHeight); 785 786 // Now that the nodes are broken into runs, figure out alignments 787 for (int i=0, max=runs.size(); i<max; i++) { 788 final Run run = runs.get(i); 789 final double xoffset = left + computeXOffset(insideWidth, 790 getOrientation() == HORIZONTAL ? run.width : computeContentWidth(runs), 791 getAlignmentInternal().getHpos()); 792 final double yoffset = top + computeYOffset(insideHeight, 793 getOrientation() == VERTICAL ? run.height : computeContentHeight(runs), 794 getAlignmentInternal().getVpos()); 795 for (int j = 0; j < run.rects.size(); j++) { 796 final LayoutRect lrect = run.rects.get(j); 797 // System.out.println("flowpane.layout: run="+i+" "+run.width+"x"+run.height+" xoffset="+xoffset+" yoffset="+yoffset+" lrect="+lrect); 798 final double x = xoffset + lrect.x; 799 final double y = yoffset + lrect.y; 800 layoutInArea(lrect.node, x, y, 801 getOrientation() == HORIZONTAL? lrect.width : run.width, 802 getOrientation() == VERTICAL? lrect.height : run.height, 803 run.baselineOffset, getMargin(lrect.node), 804 getColumnHalignmentInternal(), getRowValignmentInternal()); 805 } 806 } 807 } 808 809 /*************************************************************************** 810 * * 811 * Stylesheet Handling * 812 * * 813 **************************************************************************/ 814 815 816 /* 817 * Super-lazy instantiation pattern from Bill Pugh. 818 */ 819 private static class StyleableProperties { 820 821 private static final CssMetaData<FlowPane,Pos> ALIGNMENT = 822 new CssMetaData<FlowPane,Pos>("-fx-alignment", 823 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT) { 824 825 @Override 826 public boolean isSettable(FlowPane node) { 827 return node.alignment == null || !node.alignment.isBound(); 828 } 829 830 @Override 831 public StyleableProperty<Pos> getStyleableProperty(FlowPane node) { 832 return (StyleableProperty<Pos>)node.alignmentProperty(); 833 } 834 835 }; 836 837 private static final CssMetaData<FlowPane,HPos> COLUMN_HALIGNMENT = 838 new CssMetaData<FlowPane,HPos>("-fx-column-halignment", 839 new EnumConverter<HPos>(HPos.class), HPos.LEFT) { 840 841 @Override 842 public boolean isSettable(FlowPane node) { 843 return node.columnHalignment == null || !node.columnHalignment.isBound(); 844 } 845 846 @Override 847 public StyleableProperty<HPos> getStyleableProperty(FlowPane node) { 848 return (StyleableProperty<HPos>)node.columnHalignmentProperty(); 849 } 850 851 }; 852 853 private static final CssMetaData<FlowPane,Number> HGAP = 854 new CssMetaData<FlowPane,Number>("-fx-hgap", 855 SizeConverter.getInstance(), 0.0){ 856 857 @Override 858 public boolean isSettable(FlowPane node) { 859 return node.hgap == null || !node.hgap.isBound(); 860 } 861 862 @Override 863 public StyleableProperty<Number> getStyleableProperty(FlowPane node) { 864 return (StyleableProperty<Number>)node.hgapProperty(); 865 } 866 867 }; 868 869 private static final CssMetaData<FlowPane,VPos> ROW_VALIGNMENT = 870 new CssMetaData<FlowPane,VPos>("-fx-row-valignment", 871 new EnumConverter<VPos>(VPos.class), VPos.CENTER) { 872 873 @Override 874 public boolean isSettable(FlowPane node) { 875 return node.rowValignment == null || !node.rowValignment.isBound(); 876 } 877 878 @Override 879 public StyleableProperty<VPos> getStyleableProperty(FlowPane node) { 880 return (StyleableProperty<VPos>)node.rowValignmentProperty(); 881 } 882 883 }; 884 885 private static final CssMetaData<FlowPane,Orientation> ORIENTATION = 886 new CssMetaData<FlowPane,Orientation>("-fx-orientation", 887 new EnumConverter<Orientation>(Orientation.class), 888 Orientation.HORIZONTAL) { 889 890 @Override 891 public Orientation getInitialValue(FlowPane node) { 892 // A vertical flow pane should remain vertical 893 return node.getOrientation(); 894 } 895 896 @Override 897 public boolean isSettable(FlowPane node) { 898 return node.orientation == null || !node.orientation.isBound(); 899 } 900 901 @Override 902 public StyleableProperty<Orientation> getStyleableProperty(FlowPane node) { 903 return (StyleableProperty<Orientation>)node.orientationProperty(); 904 } 905 906 }; 907 908 private static final CssMetaData<FlowPane,Number> VGAP = 909 new CssMetaData<FlowPane,Number>("-fx-vgap", 910 SizeConverter.getInstance(), 0.0){ 911 912 @Override 913 public boolean isSettable(FlowPane node) { 914 return node.vgap == null || !node.vgap.isBound(); 915 } 916 917 @Override 918 public StyleableProperty<Number> getStyleableProperty(FlowPane node) { 919 return (StyleableProperty<Number>)node.vgapProperty(); 920 } 921 922 }; 923 924 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 925 static { 926 927 final List<CssMetaData<? extends Styleable, ?>> styleables = 928 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 929 styleables.add(ALIGNMENT); 930 styleables.add(COLUMN_HALIGNMENT); 931 styleables.add(HGAP); 932 styleables.add(ROW_VALIGNMENT); 933 styleables.add(ORIENTATION); 934 styleables.add(VGAP); 935 936 STYLEABLES = Collections.unmodifiableList(styleables); 937 } 938 } 939 940 941 /** 942 * @return The CssMetaData associated with this class, which may include the 943 * CssMetaData of its superclasses. 944 * @since JavaFX 8.0 945 */ 946 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 947 return StyleableProperties.STYLEABLES; 948 } 949 950 /** 951 * {@inheritDoc} 952 * 953 * @since JavaFX 8.0 954 */ 955 956 957 @Override 958 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 959 return getClassCssMetaData(); 960 } 961 962 //REMIND(aim); replace when we get mutable rects 963 private static class LayoutRect { 964 public Node node; 965 double x; 966 double y; 967 double width; 968 double height; 969 970 @Override public String toString() { 971 return "LayoutRect node id="+node.getId()+" "+x+","+y+" "+width+"x"+height; 972 } 973 } 974 975 private static class Run { 976 ArrayList<LayoutRect> rects = new ArrayList(); 977 double width; 978 double height; 979 double baselineOffset; 980 } 981 }