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}