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.BooleanProperty;
032import javafx.beans.property.DoubleProperty;
033import javafx.beans.property.ObjectProperty;
034import javafx.css.CssMetaData;
035import javafx.css.StyleableBooleanProperty;
036import javafx.css.StyleableDoubleProperty;
037import javafx.css.StyleableObjectProperty;
038import javafx.css.StyleableProperty;
039import javafx.geometry.HPos;
040import javafx.geometry.Insets;
041import javafx.geometry.Orientation;
042import javafx.geometry.Pos;
043import javafx.geometry.VPos;
044import javafx.scene.Node;
045import com.sun.javafx.css.converters.BooleanConverter;
046import com.sun.javafx.css.converters.EnumConverter;
047import com.sun.javafx.css.converters.SizeConverter;
048import javafx.css.Styleable;
049
050/**
051 * VBox lays out its children in a single vertical column.
052 * If the vbox has a border and/or padding set, then the contents will be layed
053 * out within those insets.
054 * <p>
055 * VBox example:
056 * <pre><code>
057 *     VBox vbox = new VBox(8); // spacing = 8
058 *     vbox.getChildren().addAll(new Button("Cut"), new Button("Copy"), new Button("Paste"));
059 * </code></pre>
060 *
061 * VBox will resize children (if resizable) to their preferred heights and uses its
062 * {@link #fillWidth} property to determine whether to resize their widths to
063 * fill its own width or keep their widths to their preferred (fillWidth defaults to true).
064 * The alignment of the content is controlled by the {@link #alignment} property,
065 * which defaults to Pos.TOP_LEFT.
066 * <p>
067 * If a vbox is resized larger than its preferred height, by default it will keep
068 * children to their preferred heights, leaving the extra space unused.  If an
069 * application wishes to have one or more children be allocated that extra space
070 * it may optionally set a vgrow constraint on the child.  See "Optional Layout
071 * Constraints" for details.
072 * <p>
073 * VBox lays out each managed child regardless of the child's
074 * visible property value; unmanaged children are ignored.</p>
075 *
076 * <h4>Resizable Range</h4>
077 *
078 * A vbox's parent will resize the vbox within the vbox's resizable range
079 * during layout.   By default the vbox computes this range based on its content
080 * as outlined in the table below.
081 * <table border="1">
082 * <tr><td></td><th>width</th><th>height</th></tr>
083 * <tr><th>minimum</th>
084 * <td>left/right insets plus the largest of the children's min widths.</td>
085 * <td>top/bottom insets plus the sum of each child's min height plus spacing between each child.</td>
086 * </tr>
087 * <tr><th>preferred</th>
088 * <td>left/right insets plus the largest of the children's pref widths.</td>
089 * <td>top/bottom insets plus the sum of each child's pref height plus spacing between each child.</td>
090 * </tr>
091 * <tr><th>maximum</th>
092 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
093 * </table>
094 * <p>
095 * A vbox's unbounded maximum width and height are an indication to the parent that
096 * it may be resized beyond its preferred size to fill whatever space is assigned
097 * to it.
098 * <p>
099 * VBox provides properties for setting the size range directly.  These
100 * properties default to the sentinel value USE_COMPUTED_SIZE, however the
101 * application may set them to other values as needed:
102 * <pre><code>
103 *     <b>vbox.setPrefWidth(400);</b>
104 * </code></pre>
105 * Applications may restore the computed values by setting these properties back
106 * to USE_COMPUTED_SIZE.
107 * <p>
108 * VBox does not clip its content by default, so it is possible that childrens'
109 * bounds may extend outside its own bounds if a child's min size prevents it from
110 * being fit within the vbox.</p>
111 *
112 * <h4>Optional Layout Constraints</h4>
113 *
114 * An application may set constraints on individual children to customize VBox's layout.
115 * For each constraint, VBox provides a static method for setting it on the child.
116 * <p>
117 * <table border="1">
118 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr>
119 * <tr><td>vgrow</td><td>javafx.scene.layout.Priority</td><td>The vertical grow priority for the child.</td></tr>
120 * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr>
121 * </table>
122 * <p>
123 * For example, if a vbox needs the ListView to be allocated all extra space:
124 * <pre><code>
125 *     VBox vbox = new VBox();
126 *     ListView list = new ListView();
127 *     <b>VBox.setVgrow(list, Priority.ALWAYS);</b>
128 *     vbox.getChildren().addAll(new Label("Names:"), list);
129 * </code></pre>
130 *
131 * If more than one child has the same grow priority set, then the vbox will
132 * allocate equal amounts of space to each.  VBox will only grow a child up to
133 * its maximum height, so if the child has a max height other than Double.MAX_VALUE,
134 * the application may need to override the max to allow it to grow.
135 */
136public class VBox extends Pane {
137
138    private boolean biasDirty = true;
139    private boolean performingLayout = false;
140    private Orientation bias;
141
142/********************************************************************
143     *  BEGIN static methods
144     ********************************************************************/
145    private static final String MARGIN_CONSTRAINT = "vbox-margin";
146    private static final String VGROW_CONSTRAINT = "vbox-vgrow";
147
148    /**
149     * Sets the vertical grow priority for the child when contained by an vbox.
150     * If set, the vbox will use the priority to allocate additional space if the
151     * vbox is resized larger than it's preferred height.
152     * If multiple vbox children have the same vertical grow priority, then the
153     * extra space will be split evenly between them.
154     * If no vertical grow priority is set on a child, the vbox will never
155     * allocate it additional vertical space if available.
156     * Setting the value to null will remove the constraint.
157     * @param child the child of a vbox
158     * @param value the horizontal grow priority for the child
159     */
160    public static void setVgrow(Node child, Priority value) {
161        setConstraint(child, VGROW_CONSTRAINT, value);
162    }
163
164    /**
165     * Returns the child's vgrow property if set.
166     * @param child the child node of a vbox
167     * @return the vertical grow priority for the child or null if no priority was set
168     */
169    public static Priority getVgrow(Node child) {
170        return (Priority)getConstraint(child, VGROW_CONSTRAINT);
171    }
172
173    /**
174     * Sets the margin for the child when contained by a vbox.
175     * If set, the vbox will layout the child so that it has the margin space around it.
176     * Setting the value to null will remove the constraint.
177     * @param child the child mode of a vbox
178     * @param value the margin of space around the child
179     */
180    public static void setMargin(Node child, Insets value) {
181        setConstraint(child, MARGIN_CONSTRAINT, value);
182    }
183
184    /**
185     * Returns the child's margin property if set.
186     * @param child the child node of a vbox
187     * @return the margin for the child or null if no margin was set
188     */
189    public static Insets getMargin(Node child) {
190        return (Insets)getConstraint(child, MARGIN_CONSTRAINT);
191    }
192
193    /**
194     * Removes all vbox constraints from the child node.
195     * @param child the child node
196     */
197    public static void clearConstraints(Node child) {
198        setVgrow(child, null);
199        setMargin(child, null);
200    }
201
202    /********************************************************************
203     *  END static methods
204     ********************************************************************/
205
206    /**
207     * Creates a VBox layout with spacing = 0 and alignment at TOP_LEFT.
208     */
209    public VBox() {
210        super();
211    }
212
213    /**
214     * Creates a VBox layout with the specified spacing between children.
215     * @param spacing the amount of vertical space between each child
216     */
217    public VBox(double spacing) {
218        this();
219        setSpacing(spacing);
220    }
221
222    /**
223     * Creates an VBox layout with spacing = 0.
224     * @param children The initial set of children for this pane.
225     */
226    public VBox(Node... children) {
227        super();
228        getChildren().addAll(children);
229    }
230
231    /**
232     * Creates an VBox layout with the specified spacing between children.
233     * @param spacing the amount of horizontal space between each child
234     * @param children The initial set of children for this pane.
235     */
236    public VBox(double spacing, Node... children) {
237        this();
238        setSpacing(spacing);
239        getChildren().addAll(children);
240    }
241
242    /**
243     * The amount of vertical space between each child in the vbox.
244     */
245    public final DoubleProperty spacingProperty() {
246        if (spacing == null) {
247            spacing = new StyleableDoubleProperty() {
248                @Override
249                public void invalidated() {
250                    requestLayout();
251                }
252
253                @Override
254                public Object getBean() {
255                    return VBox.this;
256                }
257
258                @Override
259                public String getName() {
260                    return "spacing";
261                }
262
263                @Override
264                public CssMetaData<VBox, Number> getCssMetaData() {
265                    return StyleableProperties.SPACING;
266                }
267            };
268        }
269        return spacing;
270    }
271
272    private DoubleProperty spacing;
273    public final void setSpacing(double value) { spacingProperty().set(value); }
274    public final double getSpacing() { return spacing == null ? 0 : spacing.get(); }
275
276    /**
277     * The overall alignment of children within the vbox's width and height.
278     */
279    public final ObjectProperty<Pos> alignmentProperty() {
280        if (alignment == null) {
281            alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
282                @Override
283                public void invalidated() {
284                    requestLayout();
285                }
286
287                @Override
288                public Object getBean() {
289                    return VBox.this;
290                }
291
292                @Override
293                public String getName() {
294                    return "alignment";
295                }
296
297                @Override
298                public CssMetaData<VBox, Pos> getCssMetaData() {
299                    return StyleableProperties.ALIGNMENT;
300                }
301            };
302        }
303        return alignment;
304    }
305
306    private ObjectProperty<Pos> alignment;
307    public final void setAlignment(Pos value) { alignmentProperty().set(value); }
308    public final Pos getAlignment() { return alignment == null ? Pos.TOP_LEFT : alignment.get(); }
309    private Pos getAlignmentInternal() {
310        Pos localPos = getAlignment();
311        return localPos == null ? Pos.TOP_LEFT : localPos;
312    }
313
314    /**
315     * Whether or not resizable children will be resized to fill the full width of the vbox
316     * or be kept to their preferred width and aligned according to the <code>alignment</code>
317     * hpos value.
318     */
319    public final BooleanProperty fillWidthProperty() {
320        if (fillWidth == null) {
321            fillWidth = new StyleableBooleanProperty(true) {
322                @Override
323                public void invalidated() {
324                    requestLayout();
325                }
326
327                @Override
328                public Object getBean() {
329                    return VBox.this;
330                }
331
332                @Override
333                public String getName() {
334                    return "fillWidth";
335                }
336
337                @Override
338                public CssMetaData<VBox, Boolean> getCssMetaData() {
339                    return StyleableProperties.FILL_WIDTH;
340                }
341            };
342        }
343        return fillWidth;
344    }
345
346    private BooleanProperty fillWidth;
347    public final void setFillWidth(boolean value) { fillWidthProperty().set(value); }
348    public final boolean isFillWidth() { return fillWidth == null ? true : fillWidth.get(); }
349
350    /**
351     *
352     * @return null unless one of its children has a content bias.
353     */
354    @Override public Orientation getContentBias() {
355        if (biasDirty) {
356            final List<Node> children = getChildren();
357            for (Node child : children) {
358                Orientation contentBias = child.getContentBias();
359                if (child.isManaged() && contentBias != null) {
360                    bias = contentBias;
361                    break;
362                }
363            }
364            biasDirty = false;
365        }        
366        return bias;
367    }
368
369    @Override protected double computeMinWidth(double height) {
370        Insets insets = getInsets();
371        List<Node>managed = getManagedChildren();
372        double contentWidth = 0;
373        if (getContentBias() == Orientation.VERTICAL) {
374            double prefHeights[] = getAreaHeights(managed, -1, false);
375            adjustAreaHeights(managed, prefHeights, height, -1);
376            contentWidth = computeMaxMinAreaWidth(managed, getMargins(managed), prefHeights, getAlignmentInternal().getHpos());
377        } else {
378            contentWidth = computeMaxMinAreaWidth(managed, getMargins(managed), getAlignmentInternal().getHpos());
379        }
380        return snapSpace(insets.getLeft()) + contentWidth + snapSpace(insets.getRight());
381    }
382
383    @Override protected double computeMinHeight(double width) {
384        Insets insets = getInsets();
385        return snapSpace(insets.getTop()) +
386               computeContentHeight(getAreaHeights(getManagedChildren(), width, true)) +
387               snapSpace(insets.getBottom());
388    }
389
390    @Override protected double computePrefWidth(double height) {
391        Insets insets = getInsets();
392        List<Node>managed = getManagedChildren();
393        double contentWidth = 0;
394        if (getContentBias() == Orientation.VERTICAL) {
395            double prefHeights[] = getAreaHeights(managed, -1, false);
396            adjustAreaHeights(managed, prefHeights, height, -1);
397            contentWidth = computeMaxPrefAreaWidth(managed, getMargins(managed), prefHeights, getAlignmentInternal().getHpos());
398        } else {
399            contentWidth = computeMaxPrefAreaWidth(managed, getMargins(managed), getAlignmentInternal().getHpos());
400        }
401        return snapSpace(insets.getLeft()) + contentWidth + snapSpace(insets.getRight());
402    }
403
404    @Override protected double computePrefHeight(double width) {
405        Insets insets = getInsets();
406        double d = snapSpace(insets.getTop()) +
407               computeContentHeight(getAreaHeights(getManagedChildren(), width, false)) +
408               snapSpace(insets.getBottom());
409        return d;
410    }
411
412    private Insets[] getMargins(List<Node>managed) {
413        Insets margins[] = new Insets[managed.size()];
414        for(int i = 0; i < margins.length; i++) {
415            margins[i] = getMargin(managed.get(i));
416        }
417        return margins;
418    }
419
420    private double[] getAreaHeights(List<Node>managed, double width, boolean minimum) {
421        double[] prefAreaHeights = new double [managed.size()];
422        final double insideWidth = width == -1? -1 : width -
423                snapSpace(getInsets().getLeft()) - snapSpace(getInsets().getRight());
424        for (int i = 0, size = managed.size(); i < size; i++) {
425            Node child = managed.get(i);
426            Insets margin = getMargin(child);
427            prefAreaHeights[i] = minimum?
428                               computeChildMinAreaHeight(child, margin,
429                                   isFillWidth()? insideWidth : child.minWidth(-1)) :
430                                   computeChildPrefAreaHeight(child, margin,
431                                       isFillWidth()? insideWidth : child.prefWidth(-1));
432        }
433        return prefAreaHeights;
434    }
435
436    private double adjustAreaHeights(List<Node>managed, double areaHeights[], double height, double width) {
437        Insets insets = getInsets();
438        double left = snapSpace(insets.getLeft());
439        double right = snapSpace(insets.getRight());
440
441        double contentHeight = computeContentHeight(areaHeights);
442        double extraHeight = (height == -1 ? prefHeight(-1) : height) -
443                snapSpace(insets.getTop()) - snapSpace(insets.getBottom()) - contentHeight;
444
445        if (extraHeight != 0) {
446            double remaining = growOrShrinkAreaHeights(managed, areaHeights,
447                    Priority.ALWAYS, extraHeight, isFillWidth() && width != -1? width - left - right: -1);
448            remaining = growOrShrinkAreaHeights(managed, areaHeights,
449                    Priority.SOMETIMES, remaining, isFillWidth() && width != -1? width - left - right: -1);
450            contentHeight += (extraHeight - remaining);
451        }
452
453        return contentHeight;
454    }
455
456    private double growOrShrinkAreaHeights(List<Node>managed, double areaHeights[], Priority priority, double extraHeight, double width) {
457        final boolean shrinking = extraHeight < 0;
458        List<Node> adjustList = new ArrayList<Node>();
459        List<Node> adjusting = new ArrayList<Node>();
460
461        for (int i = 0, size = managed.size(); i < size; i++) {
462            Node child = managed.get(i);
463            if (shrinking || getVgrow(child) == priority) {
464                adjustList.add(child);
465                adjusting.add(child);
466            }
467        }
468
469        double[] areaLimitHeights = new double[adjustList.size()];
470        for (int i = 0, size = adjustList.size(); i < size; i++) {
471            Node child = adjustList.get(i);
472            Insets margin  = getMargin(child);
473            areaLimitHeights[i] = shrinking?
474                computeChildMinAreaHeight(child, margin, width) : computeChildMaxAreaHeight(child, margin, width);
475        }
476
477        double available = extraHeight; // will be negative in shrinking case
478        while (Math.abs(available) > 1.0 && adjusting.size() > 0) {
479            Node[] adjusted = new Node[adjustList.size()];
480            final double portion = available / adjusting.size(); // negative in shrinking case
481            for (int i = 0, size = adjusting.size(); i < size; i++) {
482                final Node child = adjusting.get(i);
483                final int childIndex = managed.indexOf(child);
484                final double limit = areaLimitHeights[adjustList.indexOf(child)] - areaHeights[childIndex]; // negative in shrinking case
485                final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion;
486                areaHeights[childIndex] += change;
487                //if (node.id.startsWith("debug.")) println("{if (shrinking) "shrink" else "grow"}: {node.id} portion({portion})=available({available})/({sizeof adjusting}) change={change}");
488                available -= change;
489                if (Math.abs(change) < Math.abs(portion)) {
490                    adjusted[i] = child;
491                }
492            }
493            for (Node node : adjusted) {
494                if (node != null) {
495                    adjusting.remove(node);
496                }
497            }
498        }
499        return available; // might be negative in shrinking case
500    }
501
502    private double computeContentHeight(double[] heights) {
503        double total = 0;
504        for (double h : heights) {
505            total += h;
506        }
507        return total + (heights.length-1)*snapSpace(getSpacing());
508    }
509
510    private double[] actualAreaHeights;
511
512    @Override public void requestLayout() {
513        if (performingLayout) {
514            return;
515        }
516        biasDirty = true;
517        bias = null;
518        super.requestLayout();
519    }
520
521    @Override protected void layoutChildren() {
522        performingLayout = true;
523        List<Node> managed = getManagedChildren();
524        Insets insets = getInsets();
525        double width = getWidth();
526        double height = getHeight();
527        double top = snapSpace(insets.getTop());
528        double left = snapSpace(insets.getLeft());
529        double bottom = snapSpace(insets.getBottom());
530        double right = snapSpace(insets.getRight());
531        double space = snapSpace(getSpacing());
532        HPos hpos = getAlignmentInternal().getHpos();
533        VPos vpos = getAlignmentInternal().getVpos();
534        boolean isFillWidth = isFillWidth();
535
536        actualAreaHeights = getAreaHeights(managed, width, false);
537        double contentWidth = width - left - right;
538        double contentHeight = adjustAreaHeights(managed, actualAreaHeights, height, width);
539
540        double x = left;
541        double y = top + computeYOffset(height - top - bottom, contentHeight, vpos);
542
543        for (int i = 0, size = managed.size(); i < size; i++) {
544            Node child = managed.get(i);
545            layoutInArea(child, x, y, contentWidth, actualAreaHeights[i],
546                       /* baseline shouldn't matter */actualAreaHeights[i],
547                       getMargin(child), isFillWidth, true,
548                       hpos, vpos);
549            y += actualAreaHeights[i] + space;
550        }
551        performingLayout = false;
552    }
553
554    /***************************************************************************
555     *                                                                         *
556     *                         Stylesheet Handling                             *
557     *                                                                         *
558     **************************************************************************/
559
560     /**
561      * Super-lazy instantiation pattern from Bill Pugh.
562      * @treatAsPrivate implementation detail
563      */
564     private static class StyleableProperties {
565         private static final CssMetaData<VBox,Pos> ALIGNMENT =
566             new CssMetaData<VBox,Pos>("-fx-alignment",
567                 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT){
568
569            @Override
570            public boolean isSettable(VBox node) {
571                return node.alignment == null || !node.alignment.isBound();
572            }
573
574            @Override
575            public StyleableProperty<Pos> getStyleableProperty(VBox node) {
576                return (StyleableProperty<Pos>)node.alignmentProperty();
577            }
578        };
579
580         private static final CssMetaData<VBox,Boolean> FILL_WIDTH =
581             new CssMetaData<VBox,Boolean>("-fx-fill-width",
582                 BooleanConverter.getInstance(), Boolean.TRUE) {
583
584            @Override
585            public boolean isSettable(VBox node) {
586                return node.fillWidth == null || !node.fillWidth.isBound();
587            }
588
589            @Override
590            public StyleableProperty<Boolean> getStyleableProperty(VBox node) {
591                return (StyleableProperty<Boolean>)node.fillWidthProperty();
592            }
593        };
594
595         private static final CssMetaData<VBox,Number> SPACING =
596             new CssMetaData<VBox,Number>("-fx-spacing",
597                 SizeConverter.getInstance(), 0d) {
598
599            @Override
600            public boolean isSettable(VBox node) {
601                return node.spacing == null || !node.spacing.isBound();
602            }
603
604            @Override
605            public StyleableProperty<Number> getStyleableProperty(VBox node) {
606                return (StyleableProperty<Number>)node.spacingProperty();
607            }
608        };
609
610         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
611         static {
612            final List<CssMetaData<? extends Styleable, ?>> styleables =
613                new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
614            styleables.add(ALIGNMENT);
615            styleables.add(FILL_WIDTH);
616            styleables.add(SPACING);
617            STYLEABLES = Collections.unmodifiableList(styleables);
618         }
619    }
620
621    /**
622     * @return The CssMetaData associated with this class, which may include the
623     * CssMetaData of its super classes.
624     */
625    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
626        return StyleableProperties.STYLEABLES;
627    }
628
629    /**
630     * {@inheritDoc}
631     *
632     */
633
634
635    @Override
636    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
637        return getClassCssMetaData();
638    }
639
640}