1 /*
   2  * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.layout;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import javafx.beans.property.ObjectProperty;
  32 import javafx.css.CssMetaData;
  33 import javafx.css.StyleableObjectProperty;
  34 import javafx.css.StyleableProperty;
  35 import javafx.geometry.Insets;
  36 import javafx.geometry.Orientation;
  37 import javafx.geometry.Pos;
  38 import javafx.geometry.VPos;
  39 import javafx.scene.Node;
  40 import javafx.css.converter.EnumConverter;
  41 import javafx.css.Styleable;
  42 import javafx.geometry.HPos;
  43 import javafx.util.Callback;
  44 
  45 /**
  46  *
  47  * StackPane lays out its children in a back-to-front stack.
  48  * <p>
  49  * The z-order of the children is defined by the order of the children list
  50  * with the 0th child being the bottom and last child on top.  If a border and/or
  51  * padding have been set, the children will be laid out within those insets.
  52  * <p>
  53  * The stackpane will attempt to resize each child to fill its content area.
  54  * If the child could not be sized to fill the stackpane (either because it was
  55  * not resizable or its max size prevented it) then it will be aligned within
  56  * the area using the alignment property, which defaults to Pos.CENTER.
  57  * <p>
  58  * StackPane example:
  59  * <pre>{@code
  60  *     StackPane stack = new StackPane();
  61  *     stack.getChildren().addAll(new Rectangle(100,100,Color.BLUE), new Label("Go!));
  62  * }</pre>
  63  * <p>
  64  * StackPane lays out each managed child regardless of the child's
  65  * visible property value; unmanaged children are ignored.</p>
  66  * <p>
  67  * StackPane may be styled with backgrounds and borders using CSS.  See
  68  * {@link javafx.scene.layout.Region Region} for details.</p>
  69  *
  70  * <h3>Resizable Range</h3>
  71  *
  72  * <p>
  73  * A stackpane's parent will resize the stackpane within the stackpane's resizable range
  74  * during layout.   By default the stackpane computes this range based on its content
  75  * as outlined in the table below.
  76  * </p>
  77  *
  78  * <table border="1">
  79  * <caption>StackPane Resize Table</caption>
  80  * <tr><td></td><th scope="col">width</th><th scope="col">height</th></tr>
  81  * <tr><th scope="row">minimum</th>
  82  * <td>left/right insets plus the largest of the children's min widths.</td>
  83  * <td>top/bottom insets plus the largest of the children's min heights.</td></tr>
  84  * <tr><th scope="row">preferred</th>
  85  * <td>left/right insets plus the largest of the children's pref widths.</td>
  86  * <td>top/bottom insets plus the largest of the children's pref heights.</td></tr>
  87  * <tr><th scope="row">maximum</th>
  88  * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr>
  89  * </table>
  90  * <p>
  91  * A stackpane's unbounded maximum width and height are an indication to the parent that
  92  * it may be resized beyond its preferred size to fill whatever space is assigned
  93  * to it.
  94  * <p>
  95  * StackPane provides properties for setting the size range directly.  These
  96  * properties default to the sentinel value USE_COMPUTED_SIZE, however the
  97  * application may set them to other values as needed:
  98  * <pre><code>     // ensure stackpane is never resized beyond it's preferred size
  99  *     <b>stackpane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);</b>
 100  * </code></pre>
 101  * Applications may restore the computed values by setting these properties back
 102  * to USE_COMPUTED_SIZE.
 103  *
 104  * <p>
 105  * StackPane does not clip its content by default, so it is possible that children's
 106  * bounds may extend outside its own bounds if a child's min size prevents it from
 107  * being fit within the stackpane.</p>
 108  *
 109  * <h3>Optional Layout Constraints</h3>
 110  *
 111  * <p>
 112  * An application may set constraints on individual children to customize StackPane's layout.
 113  * For each constraint, StackPane provides a static method for setting it on the child.
 114  * </p>
 115  *
 116  * <table border="1">
 117  * <caption>StackPane Constraint Table</caption>
 118  * <tr><th>Constraint</th><th scope="col">Type</th><th scope="col">Description</th></tr>
 119  * <tr><th scope="row">alignment</th><td>javafx.geometry.Pos</td><td>The alignment of the child within the stackpane.</td></tr>
 120  * <tr><th scope="row">margin</th><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr>
 121  * </table>
 122  * <p>
 123  * Examples:
 124  * <pre><code>     // Align the title Label at the bottom-center of the stackpane
 125  *     Label title = new Label();
 126  *     <b>StackPane.setAlignment(title, Pos.BOTTOM_CENTER);</b>
 127  *     stackpane.getChildren.addAll(new ImageView(...), title);
 128  *
 129  *     // Create an 8 pixel margin around a listview in the stackpane
 130  *     ListView list = new ListView();
 131  *     <b>StackPane.setMargin(list, new Insets(8,8,8,8);</b>
 132  *     stackpane.getChildren().add(list);
 133  * </code></pre>
 134  *
 135  * @since JavaFX 2.0
 136  */
 137 
 138 public class StackPane extends Pane {
 139 
 140     private boolean biasDirty = true;
 141     private Orientation bias;
 142 
 143     /********************************************************************
 144      *  BEGIN static methods
 145      ********************************************************************/
 146 
 147     private static final String MARGIN_CONSTRAINT = "stackpane-margin";
 148     private static final String ALIGNMENT_CONSTRAINT = "stackpane-alignment";
 149 
 150    /**
 151      * Sets the alignment for the child when contained by a stackpane.
 152      * If set, will override the stackpane's default alignment.
 153      * Setting the value to null will remove the constraint.
 154      * @param child the child node of a stackpane
 155      * @param value the alignment position for the child
 156      */
 157     public static void setAlignment(Node child, Pos value) {
 158         setConstraint(child, ALIGNMENT_CONSTRAINT, value);
 159     }
 160 
 161     /**
 162      * Returns the child's alignment constraint if set.
 163      * @param child the child node of a stackpane
 164      * @return the alignment position for the child or null if no alignment was set
 165      */
 166     public static Pos getAlignment(Node child) {
 167         return (Pos)getConstraint(child, ALIGNMENT_CONSTRAINT);
 168     }
 169 
 170     /**
 171      * Sets the margin for the child when contained by a stackpane.
 172      * If set, the stackpane will layout the child with the margin space around it.
 173      * Setting the value to null will remove the constraint.
 174      * @param child the child node of a stackpane
 175      * @param value the margin of space around the child
 176      */
 177     public static void setMargin(Node child, Insets value) {
 178         setConstraint(child, MARGIN_CONSTRAINT, value);
 179     }
 180 
 181     /**
 182      * Returns the child's margin constraints if set.
 183      * @param child the child node of a stackpane
 184      * @return the margin for the child or null if no margin was set
 185      */
 186     public static Insets getMargin(Node child) {
 187         return (Insets)getConstraint(child, MARGIN_CONSTRAINT);
 188     }
 189 
 190     private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n);
 191 
 192     /**
 193      * Removes all stackpane constraints from the child node.
 194      * @param child the child node
 195      */
 196     public static void clearConstraints(Node child) {
 197         setAlignment(child, null);
 198         setMargin(child, null);
 199     }
 200     /********************************************************************
 201      *  END static methods
 202      ********************************************************************/
 203 
 204     /**
 205      * Creates a StackPane layout with default CENTER alignment.
 206      */
 207     public StackPane() {
 208         super();
 209     }
 210 
 211     /**
 212      * Creates a StackPane layout with default CENTER alignment.
 213      * @param children The initial set of children for this pane.
 214      * @since JavaFX 8.0
 215      */
 216     public StackPane(Node... children) {
 217         super();
 218         getChildren().addAll(children);
 219     }
 220 
 221     /**
 222      * The default alignment of children within the stackpane's width and height.
 223      * This may be overridden on individual children by setting the child's
 224      * alignment constraint.
 225      * @return the alignment of children within this stackpane
 226      */
 227     public final ObjectProperty<Pos> alignmentProperty() {
 228         if (alignment == null) {
 229             alignment = new StyleableObjectProperty<Pos>(Pos.CENTER) {
 230                 @Override
 231                 public void invalidated() {
 232                     requestLayout();
 233                 }
 234 
 235                 @Override
 236                 public CssMetaData<StackPane, Pos> getCssMetaData() {
 237                     return StyleableProperties.ALIGNMENT;
 238                 }
 239 
 240                 @Override
 241                 public Object getBean() {
 242                     return StackPane.this;
 243                 }
 244 
 245                 @Override
 246                 public String getName() {
 247                     return "alignment";
 248                 }
 249             };
 250         }
 251         return alignment;
 252     }
 253 
 254     private ObjectProperty<Pos> alignment;
 255     public final void setAlignment(Pos value) { alignmentProperty().set(value); }
 256     public final Pos getAlignment() { return alignment == null ? Pos.CENTER : alignment.get(); }
 257     private Pos getAlignmentInternal() {
 258         Pos localPos = getAlignment();
 259         return localPos == null ? Pos.CENTER : localPos;
 260     }
 261 
 262     /**
 263      *
 264      * @return the first non-null contentBias of its managed children or null if no managed children
 265      * have a content bias.
 266      */
 267     @Override public Orientation getContentBias() {
 268         if (biasDirty) {
 269             bias = null;
 270             final List<Node> children = getManagedChildren();
 271             for (Node child : children) {
 272                 Orientation contentBias = child.getContentBias();
 273                 if (contentBias != null) {
 274                     bias = contentBias;
 275                     if (contentBias == Orientation.HORIZONTAL) {
 276                         break;
 277                     }
 278                 }
 279             }
 280             biasDirty = false;
 281         }
 282         return bias;
 283     }
 284 
 285     @Override protected double computeMinWidth(double height) {
 286         List<Node>managed = getManagedChildren();
 287         return getInsets().getLeft() +
 288                computeMaxMinAreaWidth(managed, marginAccessor, height, true) +
 289                getInsets().getRight();
 290     }
 291 
 292     @Override protected double computeMinHeight(double width) {
 293         List<Node>managed = getManagedChildren();
 294         return getInsets().getTop() +
 295                computeMaxMinAreaHeight(managed, marginAccessor, getAlignmentInternal().getVpos(), width) +
 296                getInsets().getBottom();
 297     }
 298 
 299     @Override protected double computePrefWidth(double height) {
 300         List<Node>managed = getManagedChildren();
 301         Insets padding = getInsets();
 302         return padding.getLeft() +
 303                computeMaxPrefAreaWidth(managed, marginAccessor,
 304                                        (height == -1) ? -1 : (height - padding.getTop() - padding.getBottom()), true) +
 305                padding.getRight();
 306     }
 307 
 308     @Override protected double computePrefHeight(double width) {
 309         List<Node>managed = getManagedChildren();
 310         Insets padding = getInsets();
 311         return padding.getTop() +
 312                computeMaxPrefAreaHeight(managed, marginAccessor,
 313                                         (width == -1) ? -1 : (width - padding.getLeft() - padding.getRight()),
 314                                         getAlignmentInternal().getVpos()) +
 315                padding.getBottom();
 316     }
 317 
 318 
 319     @Override public void requestLayout() {
 320         biasDirty = true;
 321         bias = null;
 322         super.requestLayout();
 323     }
 324 
 325     @Override protected void layoutChildren() {
 326         List<Node> managed = getManagedChildren();
 327         Pos align = getAlignmentInternal();
 328         HPos alignHpos = align.getHpos();
 329         VPos alignVpos = align.getVpos();
 330         final double width = getWidth();
 331         double height = getHeight();
 332         double top = getInsets().getTop();
 333         double right = getInsets().getRight();
 334         double left = getInsets().getLeft();
 335         double bottom = getInsets().getBottom();
 336         double contentWidth = width - left - right;
 337         double contentHeight = height - top - bottom;
 338         double baselineOffset = alignVpos == VPos.BASELINE ?
 339                 getAreaBaselineOffset(managed, marginAccessor, i -> width, contentHeight, true)
 340                                     : 0;
 341         for (int i = 0, size = managed.size(); i < size; i++) {
 342             Node child = managed.get(i);
 343             Pos childAlignment = StackPane.getAlignment(child);
 344             layoutInArea(child, left, top,
 345                            contentWidth, contentHeight,
 346                            baselineOffset, getMargin(child),
 347                            childAlignment != null? childAlignment.getHpos() : alignHpos,
 348                            childAlignment != null? childAlignment.getVpos() : alignVpos);
 349         }
 350     }
 351 
 352     /***************************************************************************
 353      *                                                                         *
 354      *                         Stylesheet Handling                             *
 355      *                                                                         *
 356      **************************************************************************/
 357 
 358      /*
 359       * Super-lazy instantiation pattern from Bill Pugh.
 360       */
 361      private static class StyleableProperties {
 362          private static final CssMetaData<StackPane,Pos> ALIGNMENT =
 363              new CssMetaData<StackPane,Pos>("-fx-alignment",
 364                  new EnumConverter<Pos>(Pos.class),
 365                  Pos.CENTER) {
 366 
 367             @Override
 368             public boolean isSettable(StackPane node) {
 369                 return node.alignment == null ||
 370                         !node.alignment.isBound();
 371             }
 372 
 373             @Override
 374             public StyleableProperty<Pos> getStyleableProperty(StackPane node) {
 375                 return (StyleableProperty<Pos>)node.alignmentProperty();
 376             }
 377         };
 378 
 379          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 380          static {
 381             final List<CssMetaData<? extends Styleable, ?>> styleables =
 382                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
 383             styleables.add(ALIGNMENT);
 384             STYLEABLES = Collections.unmodifiableList(styleables);
 385          }
 386     }
 387 
 388     /**
 389      * @return The CssMetaData associated with this class, which may include the
 390      * CssMetaData of its superclasses.
 391      * @since JavaFX 8.0
 392      */
 393     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 394         return StyleableProperties.STYLEABLES;
 395     }
 396 
 397     /**
 398      * {@inheritDoc}
 399      *
 400      * @since JavaFX 8.0
 401      */
 402 
 403 
 404     @Override
 405     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 406         return getClassCssMetaData();
 407     }
 408 
 409 }