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.List;
029import javafx.geometry.Bounds;
030import javafx.geometry.Insets;
031import javafx.geometry.Orientation;
032import javafx.scene.Node;
033
034/**
035 * AnchorPane allows the edges of child nodes to be anchored to an offset from
036 * the anchor pane's edges.  If the anchor pane has a border and/or padding set, the
037 * offsets will be measured from the inside edge of those insets.
038 * <p>
039 * AnchorPane lays out each managed child regardless of the child's visible property value;
040 * unmanaged children are ignored for all layout calculations.</p>
041 * <p>
042 * AnchorPanes may be styled with backgrounds and borders using CSS.  See
043 * {@link javafx.scene.layout.Region Region} superclass for details.</p>
044 *
045 * <h4>Anchor Constraints</h4>
046 * The application sets anchor constraints on each child to configure the anchors
047 * on one or more sides.  If a child is anchored on opposite sides (and is resizable), the
048 * anchor pane will resize it to maintain both offsets, otherwise the anchor pane
049 * will resize it to its preferred size.  If in the former case (anchored on opposite
050 * sides) and the child is not resizable, then only the top/left anchor will be honored.
051 * AnchorPane provides a static method for setting each anchor constraint.
052 * <p>
053 * <table border="1">
054 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr>
055 * <tr><td>topAnchor</td><td>double</td><td>distance from the anchor pane's top insets to the child's top edge.</td></tr>
056 * <tr><td>leftAnchor</td><td>double</td><td>distance from the anchor pane's left insets to the child's left edge.</td></tr>
057 * <tr><td>bottomAnchor</td><td>double</td><td>distance from the anchor pane's bottom insets to the child's bottom edge.</td></tr>
058 * <tr><td>rightAnchor</td><td>double</td><td>distance from the anchor pane's right insets to the child's right edge.</td></tr>
059 * </table>
060 * <p>
061 * AnchorPane Example:
062 * <pre><code>     AnchorPane anchorPane = new AnchorPane();
063 *     // List should stretch as anchorPane is resized
064 *     ListView list = new ListView();
065 *    <b> AnchorPane.setTopAnchor(list, 10.0);
066 *     AnchorPane.setLeftAnchor(list, 10.0);
067 *     AnchorPane.setRightAnchor(list, 65.0);</b>
068 *     // Button will float on right edge
069 *     Button button = new Button("Add");
070 *     <b>AnchorPane.setTopAnchor(button, 10.0);
071 *     AnchorPane.setRightAnchor(button, 10.0);</b>
072 *     anchorPane.getChildren().addAll(list, button);
073 * </code></pre>
074 *
075 * <h4>Resizable Range</h4>
076 * An anchor pane's parent will resize the anchor pane within the anchor pane's resizable range
077 * during layout.   By default the anchor pane computes this range based on its content
078 * as outlined in the table below.
079 * <p>
080 * <table border="1">
081 * <tr><td></td><th>width</th><th>height</th></tr>
082 * <tr><th>minimum</th>
083 * <td>left/right insets plus width required to display children anchored at left/right with at least their min widths</td>
084 * <td>top/bottom insets plus height required to display children anchored at top/bottom with at least their min heights</td></tr>
085 * <tr><th>preferred</th>
086 * <td>left/right insets plus width required to display children anchored at left/right with at least their pref widths</td>
087 * <td>top/bottom insets plus height required to display children anchored at top/bottom with at least their pref heights</td></tr>
088 * <tr><th>maximum</th>
089 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
090 * </table>
091 * <p>
092 * An anchor pane's unbounded maximum width and height are an indication to the parent that
093 * it may be resized beyond its preferred size to fill whatever space is assigned
094 * to it.
095 * <p>
096 * AnchorPane provides properties for setting the size range directly.  These
097 * properties default to the sentinel value Region.USE_COMPUTED_SIZE, however the
098 * application may set them to other values as needed:
099 * <pre><code>     <b>anchorPane.setPrefSize(300, 300);</b>
100 * </code></pre>
101 * Applications may restore the computed values by setting these properties back
102 * to Region.USE_COMPUTED_SIZE.
103 * <p>
104 * AnchorPane does not clip its content by default, so it is possible that childrens'
105 * bounds may extend outside its own bounds if the anchor pane is resized smaller
106 * than its preferred size.</p>
107 *
108 */
109public class AnchorPane extends Pane {
110
111    private static final String TOP_ANCHOR = "pane-top-anchor";
112    private static final String LEFT_ANCHOR = "pane-left-anchor";
113    private static final String BOTTOM_ANCHOR = "pane-bottom-anchor";
114    private static final String RIGHT_ANCHOR = "pane-right-anchor";
115
116    /********************************************************************
117     *  BEGIN static methods
118     ********************************************************************/
119
120    /**
121     * Sets the top anchor for the child when contained by an anchor pane.
122     * If set, the anchor pane will maintain the child's size and position so
123     * that it's top is always offset by that amount from the anchor pane's top
124     * content edge.
125     * Setting the value to null will remove the constraint.
126     * @param child the child node of an anchor pane
127     * @param value the offset from the top of the anchor pane
128     */
129    public static void setTopAnchor(Node child, Double value) {
130        setConstraint(child, TOP_ANCHOR, value);
131    }
132
133    /**
134     * Returns the child's top anchor constraint if set.
135     * @param child the child node of an anchor pane
136     * @return the offset from the top of the anchor pane or null if no top anchor was set
137     */
138    public static Double getTopAnchor(Node child) {
139        return (Double)getConstraint(child, TOP_ANCHOR);
140    }
141
142    /**
143     * Sets the left anchor for the child when contained by an anchor pane.
144     * If set, the anchor pane will maintain the child's size and position so
145     * that it's left is always offset by that amount from the anchor pane's left
146     * content edge.
147     * Setting the value to null will remove the constraint.
148     * @param child the child node of an anchor pane
149     * @param value the offset from the left of the anchor pane
150     */
151    public static void setLeftAnchor(Node child, Double value) {
152        setConstraint(child, LEFT_ANCHOR, value);
153    }
154
155    /**
156     * Returns the child's left anchor constraint if set.
157     * @param child the child node of an anchor pane
158     * @return the offset from the left of the anchor pane or null if no left anchor was set
159     */
160    public static Double getLeftAnchor(Node child) {
161        return (Double)getConstraint(child, LEFT_ANCHOR);
162    }
163
164    /**
165     * Sets the bottom anchor for the child when contained by an anchor pane.
166     * If set, the anchor pane will maintain the child's size and position so
167     * that it's bottom is always offset by that amount from the anchor pane's bottom
168     * content edge.
169     * Setting the value to null will remove the constraint.
170     * @param child the child node of an anchor pane
171     * @param value the offset from the bottom of the anchor pane
172     */
173    public static void setBottomAnchor(Node child, Double value) {
174        setConstraint(child, BOTTOM_ANCHOR, value);
175    }
176
177    /**
178     * Returns the child's bottom anchor constraint if set.
179     * @param child the child node of an anchor pane
180     * @return the offset from the bottom of the anchor pane or null if no bottom anchor was set
181     */
182    public static Double getBottomAnchor(Node child) {
183        return (Double)getConstraint(child, BOTTOM_ANCHOR);
184    }
185
186    /**
187     * Sets the right anchor for the child when contained by an anchor pane.
188     * If set, the anchor pane will maintain the child's size and position so
189     * that it's right is always offset by that amount from the anchor pane's right
190     * content edge.
191     * Setting the value to null will remove the constraint.
192     * @param child the child node of an anchor pane
193     * @param value the offset from the right of the anchor pane
194     */
195    public static void setRightAnchor(Node child, Double value) {
196        setConstraint(child, RIGHT_ANCHOR, value);
197    }
198
199    /**
200     * Returns the child's right anchor constraint if set.
201     * @param child the child node of an anchor pane
202     * @return the offset from the right of the anchor pane or null if no right anchor was set
203     */
204    public static Double getRightAnchor(Node child) {
205        return (Double)getConstraint(child, RIGHT_ANCHOR);
206    }
207
208    /**
209     * Removes all anchor pane constraints from the child node.
210     * @param child the child node
211     */
212    public static void clearConstraints(Node child) {
213        setTopAnchor(child, null);
214        setRightAnchor(child, null);
215        setBottomAnchor(child, null);
216        setLeftAnchor(child, null);
217    }
218
219    /********************************************************************
220     *  END static methods
221     ********************************************************************/
222
223    /**
224     * Creates an AnchorPane layout.
225     */
226    public AnchorPane() {
227        super();
228    }
229
230    /**
231     * Creates an AnchorPane layout with the given children.
232     * @param children    The initial set of children for this pane.
233     */
234    public AnchorPane(Node... children) {
235        super();
236        getChildren().addAll(children);
237    }
238
239    @Override protected double computeMinWidth(double height) {
240        return computeWidth(true, height);
241    }
242
243    @Override protected double computeMinHeight(double width) {
244        return computeHeight(true, width);
245    }
246
247    @Override protected double computePrefWidth(double height) {
248        return computeWidth(false, height);
249    }
250
251    @Override protected double computePrefHeight(double width) {
252        return computeHeight(false, width);
253    }
254
255    private double computeWidth(final boolean minimum, final double height) {
256        double max = 0;
257        double contentHeight = height != -1 ? height - getInsets().getTop() - getInsets().getBottom() : -1;
258        final List<Node> children = getChildren();
259        for (int i=0, size=children.size(); i<size; i++) {
260            Node child = children.get(i);
261            if (child.isManaged()) {
262                Double leftAnchor = getLeftAnchor(child);
263                Double rightAnchor = getRightAnchor(child);
264
265                double left = leftAnchor != null? leftAnchor :
266                    (rightAnchor != null? 0 : child.getLayoutBounds().getMinX() + child.getLayoutX());
267                double right = rightAnchor != null? rightAnchor : 0;
268                double childHeight = -1;
269                if (child.getContentBias() == Orientation.VERTICAL && contentHeight != -1) {
270                    // The width depends on the node's height!
271                    childHeight = computeChildHeight(child, getTopAnchor(child), getBottomAnchor(child), contentHeight, -1);
272                }
273                max = Math.max(max, left + (minimum && leftAnchor != null && rightAnchor != null?
274                        child.minWidth(childHeight) : child.prefWidth(childHeight)) + right);
275            }
276        }
277
278        final Insets insets = getInsets();
279        return insets.getLeft() + max + insets.getRight();
280    }
281
282    private double computeHeight(final boolean minimum, final double width) {
283        double max = 0;
284        double contentWidth = width != -1 ? width - getInsets().getLeft()- getInsets().getRight() : -1;
285        final List<Node> children = getChildren();
286        for (int i=0, size=children.size(); i<size; i++) {
287            Node child = children.get(i);
288            if (child.isManaged()) {
289                Double topAnchor = getTopAnchor(child);
290                Double bottomAnchor = getBottomAnchor(child);
291
292                double top = topAnchor != null? topAnchor :
293                    (bottomAnchor != null? 0 : child.getLayoutBounds().getMinY() + child.getLayoutY());
294                double bottom = bottomAnchor != null? bottomAnchor : 0;
295                double childWidth = -1;
296                if (child.getContentBias() == Orientation.HORIZONTAL && contentWidth != -1) {
297                    childWidth = computeChildWidth(child, getLeftAnchor(child), getRightAnchor(child), contentWidth, -1);
298                }
299                max = Math.max(max, top + (minimum && topAnchor != null && bottomAnchor != null?
300                        child.minHeight(childWidth) : child.prefHeight(childWidth)) + bottom);
301            }
302        }
303
304        final Insets insets = getInsets();
305        return insets.getTop() + max + insets.getBottom();
306    }
307
308    private double computeChildWidth(Node child, Double leftAnchor, Double rightAnchor, double areaWidth, double height) {
309        if (leftAnchor != null && rightAnchor != null && child.isResizable()) {
310            final Insets insets = getInsets();
311            return areaWidth - insets.getLeft() - insets.getRight() - leftAnchor - rightAnchor;
312        }
313        return computeChildPrefAreaWidth(child, Insets.EMPTY, height);
314    }
315
316    private double computeChildHeight(Node child, Double topAnchor, Double bottomAnchor, double areaHeight, double width) {
317        if (topAnchor != null && bottomAnchor != null && child.isResizable()) {
318            final Insets insets = getInsets();
319            return areaHeight - insets.getTop() - insets.getBottom() - topAnchor - bottomAnchor;
320        }
321        return computeChildPrefAreaHeight(child, Insets.EMPTY, width);
322    }
323
324    @Override protected void layoutChildren() {
325        final Insets insets = getInsets();
326        final List<Node> children = getChildren();
327        for (int i=0, size=children.size(); i<size; i++) {
328            Node child = children.get(i);
329            if (child.isManaged()) {
330                final Double topAnchor = getTopAnchor(child);
331                final Double bottomAnchor = getBottomAnchor(child);
332                final Double leftAnchor = getLeftAnchor(child);
333                final Double rightAnchor = getRightAnchor(child);
334                final Bounds childLayoutBounds = child.getLayoutBounds();
335                final Orientation bias = child.getContentBias();
336
337                double x = child.getLayoutX() + childLayoutBounds.getMinX();
338                double y = child.getLayoutY() + childLayoutBounds.getMinY();
339                double w;
340                double h;
341
342                if (bias == Orientation.VERTICAL) {
343                    // width depends on height
344                    // WARNING: The order of these calls is crucial, there is some
345                    // hidden ordering dependency here!
346                    h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1);
347                    w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), h);
348                } else if (bias == Orientation.HORIZONTAL) {
349                    w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1);
350                    h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), w);
351                } else {
352                    // bias may be null
353                    w = computeChildWidth(child, leftAnchor, rightAnchor, getWidth(), -1);
354                    h = computeChildHeight(child, topAnchor, bottomAnchor, getHeight(), -1);
355                }
356
357                if (leftAnchor != null) {
358                    x = insets.getLeft() + leftAnchor;
359                } else if (rightAnchor != null) {
360                    x = getWidth() - insets.getRight() - rightAnchor - w;
361                }
362
363                if (topAnchor != null) {
364                    y = insets.getTop() + topAnchor;
365                } else if (bottomAnchor != null) {
366                    y = getHeight() - insets.getBottom() - bottomAnchor - h;
367                }
368
369                child.resizeRelocate(x, y, w, h);
370            }
371        }
372    }
373}