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