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