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.IntegerProperty; 033import javafx.beans.property.ObjectProperty; 034import javafx.beans.property.ReadOnlyDoubleProperty; 035import javafx.beans.property.ReadOnlyDoubleWrapper; 036import javafx.css.CssMetaData; 037import javafx.css.StyleOrigin; 038import javafx.css.StyleableDoubleProperty; 039import javafx.css.StyleableIntegerProperty; 040import javafx.css.StyleableObjectProperty; 041import javafx.css.StyleableProperty; 042import javafx.geometry.HPos; 043import javafx.geometry.Insets; 044import javafx.geometry.Orientation; 045import javafx.geometry.Pos; 046import javafx.geometry.VPos; 047import javafx.scene.Node; 048import com.sun.javafx.css.converters.EnumConverter; 049import com.sun.javafx.css.converters.SizeConverter; 050import javafx.css.Styleable; 051 052import static javafx.geometry.Orientation.*; 053 054 055/** 056 * TilePane lays out its children in a grid of uniformly sized "tiles". 057 * <p> 058 * A horizontal tilepane (the default) will tile nodes in rows, wrapping at the 059 * tilepane's width. A vertical tilepane will tile nodes in columns, 060 * wrapping at the tilepane's height. 061 * <p> 062 * The size of each "tile" defaults to the size needed to encompass the largest 063 * preferred width and height of the tilepane's children and the tilepane 064 * will recompute the size of the tiles as needed to accommodate the largest preferred 065 * size of its children as it changes. The application may also control the size 066 * of the tiles directly by setting prefTileWidth/prefTileHeight 067 * properties to a value other than USE_COMPUTED_SIZE (the default). 068 * <p> 069 * Applications should initialize either <code>prefColumns</code> (for horizontal) 070 * or <code>prefRows</code> (for vertical) to establish the tilepane's preferred 071 * size (the arbitrary default is 5). Note that prefColumns/prefRows 072 * is used only for calculating the preferred size and may not reflect the actual 073 * number of rows or columns, which may change as the tilepane is resized and 074 * the tiles are wrapped at its actual boundaries. 075 * <p> 076 * The alignment property controls how the rows and columns are aligned 077 * within the bounds of the tilepane and defaults to Pos.TOP_LEFT. It is also possible 078 * to control the alignment of nodes within the individual tiles by setting 079 * {@link #tileAlignmentProperty() tileAlignment}, which defaults to Pos.CENTER. 080 * <p> 081 * A horizontal tilepane example: 082 * <pre><code> 083 * TilePane tile = new TilePane(); 084 * tile.setHgap(8); 085 * tile.setPrefColumns(4); 086 * for (int i = 0; i < 20; i++) { 087 * tile.getChildren().add(new ImageView(...)); 088 * } 089 * </code></pre> 090 * <p> 091 * A vertical TilePane example: 092 * <pre><code> 093 * TilePane tile = new TilePane(Orientation.VERTICAL); 094 * tile.setTileAlignment(Pos.CENTER_LEFT); 095 * tile.setPrefRows(10); 096 * for (int i = 0; i < 50; i++) { 097 * tile.getChildren().add(new ImageView(...)); 098 * } 099 * </code></pre> 100 * 101 * The TilePane will attempt to resize each child to fill its tile. 102 * If the child could not be sized to fill the tile (either because it was not 103 * resizable or its size limits prevented it) then it will be aligned within the 104 * tile using tileAlignment. 105 * 106 * <h4>Resizable Range</h4> 107 * 108 * A tilepane's parent will resize the tilepane within the tilepane's resizable range 109 * during layout. By default the tilepane computes this range based on its content 110 * as outlined in the tables below. 111 * <p> 112 * Horizontal: 113 * <table border="1"> 114 * <tr><td></td><th>width</th><th>height</th></tr> 115 * <tr><th>minimum</th> 116 * <td>left/right insets plus the tile width.</td> 117 * <td>top/bottom insets plus height required to display all tiles when wrapped at a specified width with a vgap between each row.</td></tr> 118 * <tr><th>preferred</th> 119 * <td>left/right insets plus prefColumns multiplied by the tile width.</td> 120 * <td>top/bottom insets plus height required to display all tiles when wrapped at a specified width with a vgap between each row.</td></tr> 121 * <tr><th>maximum</th> 122 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 123 * </table> 124 * <p> 125 * Vertical: 126 * <table border="1"> 127 * <tr><td></td><th>width</th><th>height</th></tr> 128 * <tr><th>minimum</th> 129 * <td>left/right insets plus width required to display all tiles when wrapped at a specified height with an hgap between each column.</td> 130 * <td>top/bottom insets plus the tile height.</td><tr> 131 * <tr><th>preferred</th> 132 * <td>left/right insets plus width required to display all tiles when wrapped at the specified height with an hgap between each column.</td> 133 * <td>top/bottom insets plus prefRows multiplied by the tile height.</td><tr> 134 * <tr><th>maximum</th> 135 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 136 * </table> 137 * <p> 138 * A tilepane's unbounded maximum width and height are an indication to the parent that 139 * it may be resized beyond its preferred size to fill whatever space is assigned to it. 140 * <p> 141 * TilePane provides properties for setting the size range directly. These 142 * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the 143 * application may set them to other values as needed: 144 * <pre><code> 145 * <b>tilepane.setMaxWidth(500);</b> 146 * </code></pre> 147 * Applications may restore the computed values by setting these properties back 148 * to Region.USE_COMPUTED_SIZE. 149 * <p> 150 * TilePane does not clip its content by default, so it is possible that childrens' 151 * bounds may extend outside the tiles (and possibly the tilepane bounds) if a 152 * child's pref size prevents it from being fit within its tile. Also, if the tilepane 153 * is resized smaller than its preferred size, it may not be able to fit all the 154 * tiles within its bounds and the content will extend outside. 155 * 156 * <h4>Optional Layout Constraints</h4> 157 * 158 * An application may set constraints on individual children to customize TilePane's layout. 159 * For each constraint, TilePane provides a static method for setting it on the child. 160 * <p> 161 * <table border="1"> 162 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr> 163 * <tr><td>alignment</td><td>javafx.geometry.Pos</td><td>The alignment of the child within its tile.</td></tr> 164 * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr> 165 * </table> 166 * <p> 167 * Example: 168 * <pre><code> 169 * TilePane tilepane = new TilePane(); 170 * for (int i = 0; i < 20; i++) { 171 * Label title = new Label(imageTitle[i]): 172 * Imageview imageview = new ImageView(new Image(imageName[i])); 173 * TilePane.setAlignment(label, Pos.BOTTOM_RIGHT); 174 * tilepane.getChildren().addAll(title, imageview); 175 * } 176 * </code></pre> 177 */ 178public class TilePane extends Pane { 179 180 /******************************************************************** 181 * BEGIN static methods 182 ********************************************************************/ 183 184 private static final String MARGIN_CONSTRAINT = "tilepane-margin"; 185 private static final String ALIGNMENT_CONSTRAINT = "tilepane-alignment"; 186 187 /** 188 * Sets the alignment for the child when contained by a tilepane. 189 * If set, will override the tilepane's default alignment for children 190 * within their 'tiles'. 191 * Setting the value to null will remove the constraint. 192 * @param node the child node of a tilepane 193 * @param value the alignment position for the child 194 */ 195 public static void setAlignment(Node node, Pos value) { 196 setConstraint(node, ALIGNMENT_CONSTRAINT, value); 197 } 198 199 /** 200 * Returns the child's alignment constraint if set. 201 * @param node the child node of a tilepane 202 * @return the alignment position for the child or null if no alignment was set 203 */ 204 public static Pos getAlignment(Node node) { 205 return (Pos)getConstraint(node, ALIGNMENT_CONSTRAINT); 206 } 207 208 /** 209 * Sets the margin for the child when contained by a tilepane. 210 * If set, the tilepane will layout the child with the margin space around it. 211 * Setting the value to null will remove the constraint. 212 * @param node the child node of a tilepane 213 * @param value the margin of space around the child 214 */ 215 public static void setMargin(Node node, Insets value) { 216 setConstraint(node, MARGIN_CONSTRAINT, value); 217 } 218 219 /** 220 * Returns the child's margin constraint if set. 221 * @param node the child node of a tilepane 222 * @return the margin for the child or null if no margin was set 223 */ 224 public static Insets getMargin(Node node) { 225 return (Insets)getConstraint(node, MARGIN_CONSTRAINT); 226 } 227 228 /** 229 * Removes all tilepane constraints from the child node. 230 * @param child the child node 231 */ 232 public static void clearConstraints(Node child) { 233 setAlignment(child, null); 234 setMargin(child, null); 235 } 236 237 /******************************************************************** 238 * END static methods 239 ********************************************************************/ 240 241 private double computedTileWidth = -1; 242 private double computedTileHeight = -1; 243 244 /** 245 * Creates a horizontal TilePane layout with prefColumn = 5 and hgap/vgap = 0. 246 */ 247 public TilePane() { 248 super(); 249 } 250 251 /** 252 * Creates a TilePane layout with the specified orientation, 253 * prefColumn/prefRows = 5 and hgap/vgap = 0. 254 * @param orientation the direction the tiles should flow & wrap 255 */ 256 public TilePane(Orientation orientation) { 257 super(); 258 setOrientation(orientation); 259 } 260 261 /** 262 * Creates a horizontal TilePane layout with prefColumn = 5 and the specified 263 * hgap/vgap. 264 * @param hgap the amount of horizontal space between each tile 265 * @param vgap the amount of vertical space between each tile 266 */ 267 public TilePane(double hgap, double vgap) { 268 super(); 269 setHgap(hgap); 270 setVgap(vgap); 271 } 272 273 /** 274 * Creates a TilePane layout with the specified orientation, hgap/vgap, 275 * and prefRows/prefColumns = 5. 276 * @param orientation the direction the tiles should flow & wrap 277 * @param hgap the amount of horizontal space between each tile 278 * @param vgap the amount of vertical space between each tile 279 */ 280 public TilePane(Orientation orientation, double hgap, double vgap) { 281 this(); 282 setOrientation(orientation); 283 setHgap(hgap); 284 setVgap(vgap); 285 } 286 287 /** 288 * Creates a horizontal TilePane layout with prefColumn = 5 and hgap/vgap = 0. 289 * @param children The initial set of children for this pane. 290 */ 291 public TilePane(Node... children) { 292 super(); 293 getChildren().addAll(children); 294 } 295 296 /** 297 * Creates a TilePane layout with the specified orientation, 298 * prefColumn/prefRows = 5 and hgap/vgap = 0. 299 * @param orientation the direction the tiles should flow & wrap 300 * @param children The initial set of children for this pane. 301 */ 302 public TilePane(Orientation orientation, Node... children) { 303 super(); 304 setOrientation(orientation); 305 getChildren().addAll(children); 306 } 307 308 /** 309 * Creates a horizontal TilePane layout with prefColumn = 5 and the specified 310 * hgap/vgap. 311 * @param hgap the amount of horizontal space between each tile 312 * @param vgap the amount of vertical space between each tile 313 * @param children The initial set of children for this pane. 314 */ 315 public TilePane(double hgap, double vgap, Node... children) { 316 super(); 317 setHgap(hgap); 318 setVgap(vgap); 319 getChildren().addAll(children); 320 } 321 322 /** 323 * Creates a TilePane layout with the specified orientation, hgap/vgap, 324 * and prefRows/prefColumns = 5. 325 * @param orientation the direction the tiles should flow & wrap 326 * @param hgap the amount of horizontal space between each tile 327 * @param vgap the amount of vertical space between each tile 328 * @param children The initial set of children for this pane. 329 */ 330 public TilePane(Orientation orientation, double hgap, double vgap, Node... children) { 331 this(); 332 setOrientation(orientation); 333 setHgap(hgap); 334 setVgap(vgap); 335 getChildren().addAll(children); 336 } 337 338 /** 339 * The orientation of this tilepane. 340 * A horizontal tilepane lays out children in tiles, left to right, wrapping 341 * tiles at the tilepane's width boundary. A vertical tilepane lays out 342 * children in tiles, top to bottom, wrapping at the tilepane's height. 343 * The default is horizontal. 344 */ 345 public final ObjectProperty<Orientation> orientationProperty() { 346 if (orientation == null) { 347 orientation = new StyleableObjectProperty(HORIZONTAL) { 348 @Override 349 public void invalidated() { 350 requestLayout(); 351 } 352 353 @Override 354 public CssMetaData<TilePane, Orientation> getCssMetaData() { 355 return StyleableProperties.ORIENTATION; 356 } 357 358 @Override 359 public Object getBean() { 360 return TilePane.this; 361 } 362 363 @Override 364 public String getName() { 365 return "orientation"; 366 } 367 }; 368 } 369 return orientation; 370 } 371 372 private ObjectProperty<Orientation> orientation; 373 public final void setOrientation(Orientation value) { orientationProperty().set(value); } 374 public final Orientation getOrientation() { return orientation == null ? HORIZONTAL : orientation.get(); } 375 376 377 /** 378 * The preferred number of rows for a vertical tilepane. 379 * This value is used only to compute the preferred size of the tilepane 380 * and may not reflect the actual number of rows, which may change 381 * if the tilepane is resized to something other than its preferred height. 382 * This property is ignored for a horizontal tilepane. 383 * <p> 384 * It is recommended that the application initialize this value for a 385 * vertical tilepane. 386 */ 387 public final IntegerProperty prefRowsProperty() { 388 if (prefRows == null) { 389 prefRows = new StyleableIntegerProperty(5) { 390 @Override 391 public void invalidated() { 392 requestLayout(); 393 } 394 395 @Override 396 public CssMetaData<TilePane, Number> getCssMetaData() { 397 return StyleableProperties.PREF_ROWS; 398 } 399 400 @Override 401 public Object getBean() { 402 return TilePane.this; 403 } 404 405 @Override 406 public String getName() { 407 return "prefRows"; 408 } 409 }; 410 } 411 return prefRows; 412 } 413 414 private IntegerProperty prefRows; 415 public final void setPrefRows(int value) { prefRowsProperty().set(value); } 416 public final int getPrefRows() { return prefRows == null ? 5 : prefRows.get(); } 417 418 /** 419 * The preferred number of columns for a horizontal tilepane. 420 * This value is used only to compute the preferred size of the tilepane 421 * and may not reflect the actual number of rows, which may change if the 422 * tilepane is resized to something other than its preferred height. 423 * This property is ignored for a vertical tilepane. 424 * <p> 425 * It is recommended that the application initialize this value for a 426 * horizontal tilepane. 427 */ 428 public final IntegerProperty prefColumnsProperty() { 429 if (prefColumns == null) { 430 prefColumns = new StyleableIntegerProperty(5) { 431 @Override 432 public void invalidated() { 433 requestLayout(); 434 } 435 436 @Override 437 public CssMetaData<TilePane, Number> getCssMetaData() { 438 return StyleableProperties.PREF_COLUMNS; 439 } 440 441 @Override 442 public Object getBean() { 443 return TilePane.this; 444 } 445 446 @Override 447 public String getName() { 448 return "prefColumns"; 449 } 450 }; 451 } 452 return prefColumns; 453 } 454 455 private IntegerProperty prefColumns; 456 public final void setPrefColumns(int value) { prefColumnsProperty().set(value); } 457 public final int getPrefColumns() { return prefColumns == null ? 5 : prefColumns.get(); } 458 459 /** 460 * The preferred width of each tile. 461 * If equal to USE_COMPUTED_SIZE (the default) the tile width wlll be 462 * automatically recomputed by the tilepane when the preferred size of children 463 * changes to accommodate the widest child. If the application sets this property 464 * to value greater than 0, then tiles will be set to that width and the tilepane 465 * will attempt to resize children to fit within that width (if they are resizable and 466 * their min-max width range allows it). 467 */ 468 public final DoubleProperty prefTileWidthProperty() { 469 if (prefTileWidth == null) { 470 prefTileWidth = new StyleableDoubleProperty(USE_COMPUTED_SIZE) { 471 @Override 472 public void invalidated() { 473 requestLayout(); 474 } 475 476 @Override 477 public CssMetaData<TilePane, Number> getCssMetaData() { 478 return StyleableProperties.PREF_TILE_WIDTH; 479 } 480 481 @Override 482 public Object getBean() { 483 return TilePane.this; 484 } 485 486 @Override 487 public String getName() { 488 return "prefTileWidth"; 489 } 490 }; 491 } 492 return prefTileWidth; 493 } 494 495 // TODO: DAVID AND AMY PLEASE LOOK AT THIS 496 private DoubleProperty prefTileWidth; 497 public final void setPrefTileWidth(double value) { prefTileWidthProperty().set(value); } 498 public final double getPrefTileWidth() { return prefTileWidth == null ? USE_COMPUTED_SIZE : prefTileWidth.get(); } 499 500 /** 501 * The preferred height of each tile. 502 * If equal to USE_COMPUTED_SIZE (the default) the tile height wlll be 503 * automatically recomputed by the tilepane when the preferred size of children 504 * changes to accommodate the tallest child. If the application sets this property 505 * to value greater than 0, then tiles will be set to that height and the tilepane 506 * will attempt to resize children to fit within that height (if they are resizable and 507 * their min-max height range allows it). 508 */ 509 public final DoubleProperty prefTileHeightProperty() { 510 if (prefTileHeight == null) { 511 prefTileHeight = new StyleableDoubleProperty(USE_COMPUTED_SIZE) { 512 @Override 513 public void invalidated() { 514 requestLayout(); 515 } 516 517 @Override 518 public CssMetaData<TilePane, Number> getCssMetaData() { 519 return StyleableProperties.PREF_TILE_HEIGHT; 520 } 521 522 @Override 523 public Object getBean() { 524 return TilePane.this; 525 } 526 527 @Override 528 public String getName() { 529 return "prefTileHeight"; 530 } 531 }; 532 } 533 return prefTileHeight; 534 } 535 536 // TODO: DAVID AND AMY PLEASE LOOK AT THIS 537 private DoubleProperty prefTileHeight; 538 public final void setPrefTileHeight(double value) { prefTileHeightProperty().set(value); } 539 public final double getPrefTileHeight() { return prefTileHeight == null ? USE_COMPUTED_SIZE : prefTileHeight.get(); } 540 541 /** 542 * The actual width of each tile. This property is read-only. 543 */ 544 public final ReadOnlyDoubleProperty tileWidthProperty() { 545 return tileWidthPropertyImpl().getReadOnlyProperty(); 546 } 547 private ReadOnlyDoubleWrapper tileWidthPropertyImpl() { 548 if (tileWidth == null) { 549 tileWidth = new ReadOnlyDoubleWrapper(this, "tileWidth", 0); 550 } 551 return tileWidth; 552 } 553 private ReadOnlyDoubleWrapper tileWidth; 554 private void setTileWidth(double value) { tileWidthPropertyImpl().set(value); } 555 public final double getTileWidth() { return tileWidth == null ? 0.0 : tileWidth.get(); } 556 557 /** 558 * The actual height of each tile. This property is read-only. 559 */ 560 public final ReadOnlyDoubleProperty tileHeightProperty() { 561 return tileHeightPropertyImpl().getReadOnlyProperty(); 562 } 563 private ReadOnlyDoubleWrapper tileHeightPropertyImpl() { 564 if (tileHeight == null) { 565 tileHeight = new ReadOnlyDoubleWrapper(this, "tileHeight", 0); 566 } 567 return tileHeight; 568 } 569 private ReadOnlyDoubleWrapper tileHeight; 570 private void setTileHeight(double value) { tileHeightPropertyImpl().set(value); } 571 public final double getTileHeight() { return tileHeight == null ? 0.0 : tileHeight.get(); } 572 573 /** 574 * The amount of horizontal space between each tile in a row. 575 */ 576 public final DoubleProperty hgapProperty() { 577 if (hgap == null) { 578 hgap = new StyleableDoubleProperty() { 579 @Override 580 public void invalidated() { 581 requestLayout(); 582 } 583 584 @Override 585 public CssMetaData<TilePane, Number> getCssMetaData() { 586 return StyleableProperties.HGAP; 587 } 588 589 @Override 590 public Object getBean() { 591 return TilePane.this; 592 } 593 594 @Override 595 public String getName() { 596 return "hgap"; 597 } 598 }; 599 } 600 return hgap; 601 } 602 603 private DoubleProperty hgap; 604 public final void setHgap(double value) { hgapProperty().set(value); } 605 public final double getHgap() { return hgap == null ? 0 : hgap.get(); } 606 607 /** 608 * The amount of vertical space between each tile in a column. 609 */ 610 public final DoubleProperty vgapProperty() { 611 if (vgap == null) { 612 vgap = new StyleableDoubleProperty() { 613 @Override 614 public void invalidated() { 615 requestLayout(); 616 } 617 618 @Override 619 public CssMetaData<TilePane, Number> getCssMetaData() { 620 return StyleableProperties.VGAP; 621 } 622 623 @Override 624 public Object getBean() { 625 return TilePane.this; 626 } 627 628 @Override 629 public String getName() { 630 return "vgap"; 631 } 632 }; 633 } 634 return vgap; 635 } 636 637 private DoubleProperty vgap; 638 public final void setVgap(double value) { vgapProperty().set(value); } 639 public final double getVgap() { return vgap == null ? 0 : vgap.get(); } 640 641 /** 642 * The overall alignment of the tilepane's content within its width and height. 643 * <p>For a horizontal tilepane, each row will be aligned within the tilepane's width 644 * using the alignment's hpos value, and the rows will be aligned within the 645 * tilepane's height using the alignment's vpos value. 646 * <p>For a vertical tilepane, each column will be aligned within the tilepane's height 647 * using the alignment's vpos value, and the columns will be aligned within the 648 * tilepane's width using the alignment's hpos value. 649 * 650 */ 651 public final ObjectProperty<Pos> alignmentProperty() { 652 if (alignment == null) { 653 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 654 @Override 655 public void invalidated() { 656 requestLayout(); 657 } 658 659 @Override 660 public CssMetaData<TilePane, Pos> getCssMetaData() { 661 return StyleableProperties.ALIGNMENT; 662 } 663 664 @Override 665 public Object getBean() { 666 return TilePane.this; 667 } 668 669 @Override 670 public String getName() { 671 return "alignment"; 672 } 673 }; 674 } 675 return alignment; 676 } 677 678 private ObjectProperty<Pos> alignment; 679 public final void setAlignment(Pos value) { alignmentProperty().set(value); } 680 public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); } 681 private Pos getAlignmentInternal() { 682 Pos localPos = getAlignment(); 683 return localPos == null ? Pos.TOP_LEFT : localPos; 684 } 685 686 /** 687 * The default alignment of each child within its tile. 688 * This may be overridden on individual children by setting the child's 689 * alignment constraint. 690 */ 691 public final ObjectProperty<Pos> tileAlignmentProperty() { 692 if (tileAlignment == null) { 693 tileAlignment = new StyleableObjectProperty<Pos>(Pos.CENTER) { 694 @Override 695 public void invalidated() { 696 requestLayout(); 697 } 698 699 @Override 700 public CssMetaData<TilePane, Pos> getCssMetaData() { 701 return StyleableProperties.TILE_ALIGNMENT; 702 } 703 704 @Override 705 public Object getBean() { 706 return TilePane.this; 707 } 708 709 @Override 710 public String getName() { 711 return "tileAlignment"; 712 } 713 }; 714 } 715 return tileAlignment; 716 } 717 718 private ObjectProperty<Pos> tileAlignment; 719 public final void setTileAlignment(Pos value) { tileAlignmentProperty().set(value); } 720 public final Pos getTileAlignment() { return tileAlignment == null ? Pos.CENTER : tileAlignment.get(); } 721 private Pos getTileAlignmentInternal() { 722 Pos localPos = getTileAlignment(); 723 return localPos == null ? Pos.CENTER : localPos; 724 } 725 726 @Override public Orientation getContentBias() { 727 return getOrientation(); 728 } 729 730 @Override public void requestLayout() { 731 computedTileWidth = -1; 732 computedTileHeight = -1; 733 super.requestLayout(); 734 } 735 736 @Override protected double computeMinWidth(double height) { 737 if (getContentBias() == Orientation.HORIZONTAL) { 738 return getInsets().getLeft() + computeTileWidth(getManagedChildren()) + getInsets().getRight(); 739 } 740 return computePrefWidth(height); 741 } 742 743 @Override protected double computeMinHeight(double width) { 744 if (getContentBias() == Orientation.VERTICAL) { 745 return getInsets().getTop() + computeTileHeight(getManagedChildren()) + getInsets().getBottom(); 746 } 747 return computePrefHeight(width); 748 } 749 750 @Override protected double computePrefWidth(double forHeight) { 751 List<Node> managed = getManagedChildren(); 752 final Insets insets = getInsets(); 753 setTileWidth(computeTileWidth(managed)); 754 int prefCols = 0; 755 if (forHeight != -1) { 756 // first compute number of rows that will fit in given height and 757 // compute pref columns from that 758 setTileHeight(computeTileHeight(managed)); 759 int prefRows = computeRows(forHeight - snapSpace(insets.getTop()) - snapSpace(insets.getBottom()), getTileHeight()); 760 prefCols = computeOther(managed.size(), prefRows); 761 } else { 762 prefCols = getOrientation() == HORIZONTAL? getPrefColumns() : computeOther(managed.size(), getPrefRows()); 763 } 764 return snapSpace(insets.getLeft()) + 765 computeContentWidth(prefCols, getTileWidth()) + 766 snapSpace(insets.getRight()); 767 } 768 769 @Override protected double computePrefHeight(double forWidth) { 770 List<Node> managed = getManagedChildren(); 771 final Insets insets = getInsets(); 772 setTileHeight(computeTileHeight(managed)); 773 int prefRows = 0; 774 if (forWidth != -1) { 775 // first compute number of columns that will fit in given width and 776 // compute pref rows from that 777 setTileWidth(computeTileWidth(managed)); 778 int prefCols = computeColumns(forWidth - snapSpace(insets.getLeft()) - snapSpace(insets.getRight()), getTileWidth()); 779 prefRows = computeOther(managed.size(), prefCols); 780 } else { 781 prefRows = getOrientation() == HORIZONTAL? computeOther(managed.size(), getPrefColumns()) : getPrefRows(); 782 } 783 return snapSpace(insets.getTop()) + 784 computeContentHeight(prefRows, getTileHeight()) + 785 snapSpace(insets.getBottom()); 786 } 787 788 private Insets[] getMargins(List<Node>managed) { 789 Insets margins[] = new Insets[managed.size()]; 790 for(int i = 0; i < margins.length; i++) { 791 margins[i] = getMargin(managed.get(i)); 792 } 793 return margins; 794 } 795 796 private double computeTileWidth(List<Node>managed) { 797 double preftilewidth = getPrefTileWidth(); 798 if (preftilewidth == USE_COMPUTED_SIZE) { 799 if (computedTileWidth == -1) { 800 Insets[] margins = getMargins(managed); 801 double h = -1; 802 boolean vertBias = false; 803 for (int i = 0, size = managed.size(); i < size; i++) { 804 Node child = managed.get(i); 805 if (child.getContentBias() == VERTICAL) { 806 vertBias = true; 807 break; 808 } 809 } 810 if (vertBias) { 811 // widest may depend on height of tile 812 h = computeMaxPrefAreaHeight(managed, margins, -1, getTileAlignmentInternal().getVpos()); 813 } 814 computedTileWidth = computeMaxPrefAreaWidth(managed, margins, h, getTileAlignmentInternal().getHpos()); 815 } 816 return snapSize(computedTileWidth); 817 } 818 return snapSize(preftilewidth); 819 } 820 821 private double computeTileHeight(List<Node>managed) { 822 double preftileheight = getPrefTileHeight(); 823 if (preftileheight == USE_COMPUTED_SIZE) { 824 if (computedTileHeight == -1) { 825 Insets[] margins = getMargins(managed); 826 double w = -1; 827 boolean horizBias = false; 828 for (int i = 0, size = managed.size(); i < size; i++) { 829 Node child = managed.get(i); 830 if (child.getContentBias() == Orientation.HORIZONTAL) { 831 horizBias = true; 832 break; 833 } 834 } 835 if (horizBias) { 836 // tallest may depend on width of tile 837 w = computeMaxPrefAreaWidth(managed, margins, -1, getTileAlignmentInternal().getHpos()); 838 } 839 computedTileHeight = computeMaxPrefAreaHeight(managed, getMargins(managed), w, getTileAlignmentInternal().getVpos()); 840 } 841 return snapSize(computedTileHeight); 842 } 843 return snapSize(preftileheight); 844 } 845 846 private int computeOther(int numNodes, int numCells) { 847 double other = (double)numNodes/(double)Math.max(1, numCells); 848 return (int)Math.ceil(other); 849 } 850 851 private int computeColumns(double width, double tilewidth) { 852 return Math.max(1,(int)((width + snapSpace(getHgap())) / (tilewidth + snapSpace(getHgap())))); 853 } 854 855 private int computeRows(double height, double tileheight) { 856 return Math.max(1, (int)((height + snapSpace(getVgap())) / (tileheight + snapSpace(getVgap())))); 857 } 858 859 private double computeContentWidth(int columns, double tilewidth) { 860 return columns * tilewidth + (columns - 1) * snapSpace(getHgap()); 861 } 862 863 private double computeContentHeight(int rows, double tileheight) { 864 return rows * tileheight + (rows - 1) * snapSpace(getVgap()); 865 } 866 867 @Override protected void layoutChildren() { 868 List<Node> managed = getManagedChildren(); 869 HPos hpos = getAlignmentInternal().getHpos(); 870 VPos vpos = getAlignmentInternal().getVpos(); 871 double width = getWidth(); 872 double height = getHeight(); 873 double top = snapSpace(getInsets().getTop()); 874 double left = snapSpace(getInsets().getLeft()); 875 double bottom = snapSpace(getInsets().getBottom()); 876 double right = snapSpace(getInsets().getRight()); 877 double vgap = snapSpace(getVgap()); 878 double hgap = snapSpace(getHgap()); 879 double insideWidth = width - left - right; 880 double insideHeight = height - top - bottom; 881 882 setTileWidth(computeTileWidth(managed)); 883 setTileHeight(computeTileHeight(managed)); 884 885 int lastRowRemainder = 0; 886 int lastColumnRemainder = 0; 887 if (getOrientation() == HORIZONTAL) { 888 actualColumns = computeColumns(insideWidth, getTileWidth()); 889 actualRows = computeOther(managed.size(), actualColumns); 890 // remainder will be 0 if last row is filled 891 lastRowRemainder = hpos != HPos.LEFT? 892 actualColumns - (actualColumns*actualRows - managed.size()) : 0; 893 } else { 894 // vertical 895 actualRows = computeRows(insideHeight, getTileHeight()); 896 actualColumns = computeOther(managed.size(), actualRows); 897 // remainder will be 0 if last column is filled 898 lastColumnRemainder = vpos != VPos.TOP? 899 actualRows - (actualColumns*actualRows - managed.size()) : 0; 900 } 901 double rowX = left + computeXOffset(insideWidth, 902 computeContentWidth(actualColumns, getTileWidth()), 903 hpos); 904 double columnY = top + computeYOffset(insideHeight, 905 computeContentHeight(actualRows, getTileHeight()), 906 vpos); 907 908 double lastRowX = lastRowRemainder > 0? 909 left + computeXOffset(insideWidth, 910 computeContentWidth(lastRowRemainder, getTileWidth()), 911 hpos) : rowX; 912 double lastColumnY = lastColumnRemainder > 0? 913 top + computeYOffset(insideHeight, 914 computeContentHeight(lastColumnRemainder, getTileHeight()), 915 vpos) : columnY; 916 double baselineOffset = getMaxBaselineOffset(managed); 917 918 int r = 0; 919 int c = 0; 920 for (int i = 0, size = managed.size(); i < size; i++) { 921 Node child = managed.get(i); 922 double xoffset = r == (actualRows - 1)? lastRowX : rowX; 923 double yoffset = c == (actualColumns - 1)? lastColumnY : columnY; 924 925 double tileX = xoffset + (c * (getTileWidth() + hgap)); 926 double tileY = yoffset + (r * (getTileHeight() + vgap)); 927 928 Pos childAlignment = getAlignment(child); 929 930 layoutInArea(child, tileX, tileY, getTileWidth(), getTileHeight(), baselineOffset, 931 getMargin(child), 932 childAlignment != null? childAlignment.getHpos() : getTileAlignmentInternal().getHpos(), 933 childAlignment != null? childAlignment.getVpos() : getTileAlignmentInternal().getVpos()); 934 935 if (getOrientation() == HORIZONTAL) { 936 if (++c == actualColumns) { 937 c = 0; 938 r++; 939 } 940 } else { 941 // vertical 942 if (++r == actualRows) { 943 r = 0; 944 c++; 945 } 946 } 947 } 948 } 949 950 private int actualRows = 0; 951 private int actualColumns = 0; 952 953 /*************************************************************************** 954 * * 955 * Stylesheet Handling * 956 * * 957 **************************************************************************/ 958 959 960 /** 961 * Super-lazy instantiation pattern from Bill Pugh. 962 * @treatAsPrivate implementation detail 963 */ 964 private static class StyleableProperties { 965 966 private static final CssMetaData<TilePane,Pos> ALIGNMENT = 967 new CssMetaData<TilePane,Pos>("-fx-alignment", 968 new EnumConverter<Pos>(Pos.class), 969 Pos.TOP_LEFT) { 970 971 @Override 972 public boolean isSettable(TilePane node) { 973 return node.alignment == null || !node.alignment.isBound(); 974 } 975 976 @Override 977 public StyleableProperty<Pos> getStyleableProperty(TilePane node) { 978 return (StyleableProperty<Pos>)node.alignmentProperty(); 979 } 980 }; 981 982 private static final CssMetaData<TilePane,Number> PREF_COLUMNS = 983 new CssMetaData<TilePane,Number>("-fx-pref-columns", 984 SizeConverter.getInstance(), 5.0) { 985 986 @Override 987 public boolean isSettable(TilePane node) { 988 return node.prefColumns == null || 989 !node.prefColumns.isBound(); 990 } 991 992 @Override 993 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 994 return (StyleableProperty<Number>)node.prefColumnsProperty(); 995 } 996 }; 997 998 private static final CssMetaData<TilePane,Number> HGAP = 999 new CssMetaData<TilePane,Number>("-fx-hgap", 1000 SizeConverter.getInstance(), 0.0) { 1001 1002 @Override 1003 public boolean isSettable(TilePane node) { 1004 return node.hgap == null || 1005 !node.hgap.isBound(); 1006 } 1007 1008 @Override 1009 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 1010 return (StyleableProperty<Number>)node.hgapProperty(); 1011 } 1012 }; 1013 1014 private static final CssMetaData<TilePane,Number> PREF_ROWS = 1015 new CssMetaData<TilePane,Number>("-fx-pref-rows", 1016 SizeConverter.getInstance(), 5.0) { 1017 1018 @Override 1019 public boolean isSettable(TilePane node) { 1020 return node.prefRows == null || 1021 !node.prefRows.isBound(); 1022 } 1023 1024 @Override 1025 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 1026 return (StyleableProperty<Number>)node.prefRowsProperty(); 1027 } 1028 }; 1029 1030 private static final CssMetaData<TilePane,Pos> TILE_ALIGNMENT = 1031 new CssMetaData<TilePane,Pos>("-fx-tile-alignment", 1032 new EnumConverter<Pos>(Pos.class), 1033 Pos.CENTER) { 1034 1035 @Override 1036 public boolean isSettable(TilePane node) { 1037 return node.tileAlignment == null || 1038 !node.tileAlignment.isBound(); 1039 } 1040 1041 @Override 1042 public StyleableProperty<Pos> getStyleableProperty(TilePane node) { 1043 return (StyleableProperty<Pos>)node.tileAlignmentProperty(); 1044 } 1045 }; 1046 1047 private static final CssMetaData<TilePane,Number> PREF_TILE_WIDTH = 1048 new CssMetaData<TilePane,Number>("-fx-pref-tile-width", 1049 SizeConverter.getInstance(), USE_COMPUTED_SIZE) { 1050 1051 @Override 1052 public boolean isSettable(TilePane node) { 1053 return node.prefTileWidth == null || 1054 !node.prefTileWidth.isBound(); 1055 } 1056 1057 @Override 1058 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 1059 return (StyleableProperty<Number>)node.prefTileWidthProperty(); 1060 } 1061 }; 1062 1063 private static final CssMetaData<TilePane,Number> PREF_TILE_HEIGHT = 1064 new CssMetaData<TilePane,Number>("-fx-pref-tile-height", 1065 SizeConverter.getInstance(), USE_COMPUTED_SIZE) { 1066 1067 @Override 1068 public boolean isSettable(TilePane node) { 1069 return node.prefTileHeight == null || 1070 !node.prefTileHeight.isBound(); 1071 } 1072 1073 @Override 1074 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 1075 return (StyleableProperty<Number>)node.prefTileHeightProperty(); 1076 } 1077 }; 1078 1079 private static final CssMetaData<TilePane,Orientation> ORIENTATION = 1080 new CssMetaData<TilePane,Orientation>("-fx-orientation", 1081 new EnumConverter<Orientation>(Orientation.class), 1082 Orientation.HORIZONTAL) { 1083 1084 @Override 1085 public Orientation getInitialValue(TilePane node) { 1086 // A vertical TilePane should remain vertical 1087 return node.getOrientation(); 1088 } 1089 1090 @Override 1091 public boolean isSettable(TilePane node) { 1092 return node.orientation == null || 1093 !node.orientation.isBound(); 1094 } 1095 1096 @Override 1097 public StyleableProperty<Orientation> getStyleableProperty(TilePane node) { 1098 return (StyleableProperty<Orientation>)node.orientationProperty(); 1099 } 1100 }; 1101 1102 private static final CssMetaData<TilePane,Number> VGAP = 1103 new CssMetaData<TilePane,Number>("-fx-vgap", 1104 SizeConverter.getInstance(), 0.0) { 1105 1106 @Override 1107 public boolean isSettable(TilePane node) { 1108 return node.vgap == null || 1109 !node.vgap.isBound(); 1110 } 1111 1112 @Override 1113 public StyleableProperty<Number> getStyleableProperty(TilePane node) { 1114 return (StyleableProperty<Number>)node.vgapProperty(); 1115 } 1116 }; 1117 1118 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 1119 static { 1120 final List<CssMetaData<? extends Styleable, ?>> styleables = 1121 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 1122 styleables.add(ALIGNMENT); 1123 styleables.add(HGAP); 1124 styleables.add(ORIENTATION); 1125 styleables.add(PREF_COLUMNS); 1126 styleables.add(PREF_ROWS); 1127 styleables.add(PREF_TILE_WIDTH); 1128 styleables.add(PREF_TILE_HEIGHT); 1129 styleables.add(TILE_ALIGNMENT); 1130 styleables.add(VGAP); 1131 STYLEABLES = Collections.unmodifiableList(styleables); 1132 } 1133 } 1134 1135 /** 1136 * @return The CssMetaData associated with this class, which may include the 1137 * CssMetaData of its super classes. 1138 */ 1139 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 1140 return StyleableProperties.STYLEABLES; 1141 } 1142 1143 /** 1144 * {@inheritDoc} 1145 * 1146 */ 1147 1148 1149 @Override 1150 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 1151 return getClassCssMetaData(); 1152 } 1153 1154}