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