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}