All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.ui.layouts.LayeredLayout Maven / Gradle / Ivy

There is a newer version: 7.0.164
Show newest version
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */
package com.codename1.ui.layouts;

import com.codename1.io.Log;
import com.codename1.io.Util;
import com.codename1.l10n.L10NManager;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Font;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.LayeredLayout.LayeredLayoutConstraint.Inset;
import com.codename1.ui.plaf.Style;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

/**
 * 

* The {@code LayeredLayout} places the components in order one on top of the * other and sizes them all to the size of the largest component. This is useful * when trying to create an overlay on top of an existing component. E.g. an "x" * button to allow removing the component as shown here

* * The X on this button was placed there using the layered layout code below * *

* The code to generate this UI is slightly complex and contains very little * relevant pieces. The only truly relevant piece the last line of code:

* * * * * *

* We are doing three distinct things here:

*
    * . *
  • We are adding a layered layout to the form.
  • *
  • We are creating a layered layout and placing two components within. This * would be the equivalent of just creating a {@code LayeredLaout} * {@link com.codename1.ui.Container} and invoking `add` twice.
  • * .*
  • We use * https://www.codenameone.com/javadoc/com/codename1/ui/layouts/FlowLayout.html[FlowLayout] * to position the `X` close button in the right position.
  • *
* *

* A common use case for {@code LayeredLayout} is the iOS carousel effect which * we can achieve by combing the {@code LayeredLayout} with * {@link com.codename1.ui.Tabs}. *

* * Tabs carousel page 1 * *

* Notice that the layered layout sizes all components to the exact same size * one on top of the other. It usually requires that we use another container * within; in order to position the components correctly.
* * Forms have a built in layered layout that you can access via * `getLayeredPane()`, this allows you to overlay elements on top of the content * pane.
* * The layered pane is used internally by components such as {@link com.codename1.components.InteractionDialog}, * {@link com.codename1.u./AutoCompleteTextField} etc. *

*

* Warning: Placing native widgets within a layered layout is problematic due to * the behavior of peer components. Sample of peer components include the * {@link com.codename1.ui.BrowserComponent}, video playback etc. *

* *

Insets

* *

This layout optionally supports insets for laying out its children. Use of insets can allow you to * achieve precise placement of components while adjusting properly to screen resizing.

* *

Insets may be either fixed or flexible. Fixed insets may be specified in pixels ({@link #UNIT_PIXELS}), * millimetres ({@link #UNIT_DIPS}), or percentage ({@link #UNIT_PERCENT}). Insets may also be specified as just "auto" ({@link #UNIT_AUTO}), * in which case it is considered to be flexible (it will adapt to the component size and other insets).

* *

Insets may also be anchored to a "reference component" so that it will always be measured from that reference component.

* *

Insets Example

* *

Adding a button to the top right of the parent:

*
 * {@code
 * Container cnt = new Container(new LayeredLayout());
 * LayeredLayout ll = (LayeredLayout)cnt.getLayout();
 * Button btn = new Button("My Button");
 * cnt.add(btn);
 * ll.setInsets(btn, "0 0 auto auto");
 *     // NOTE: Insets are expressed in same order as "margin" in CSS.  Clockwise starting on top.
 * }
 * 
* *

Changing top inset to 2mm, and right inset to 1mm:

*
{@code ll.setInsets(btn, "2mm 1mm auto auto");}
* *

Using percentage insets:

*
{@code ll.setInsets(btn, "25% 25% auto auto");}
* *

NOTE: When using percent units, the percentage is always in terms of the "reference box" of the component. * The "reference box" is the bounding rectangle from which the insets are measured. If none of the insets * is anchored to a reference component, then the bounding box will simply be the inner bounds of the parent container (i.e. * the bounds of the inside padding in the container.

* *

Using "auto" insets

*

An "auto" inset is an inset that is flexible. If all 4 insets are set to auto, then the component will tend to the * centre of the parent component, and its size will be the component's preferred size (though the size will be bounded by the size of the component's reference box). * If one inset is fixed, and the opposite inset is "auto", then the fixed inset and the component's preferred size will dictate the' * calculated size of the inset. *

* *

Reference Components

* *

Insets may also have reference componnents. E.g. If you want a button to be anchored to the right side of a search field, you could * make the button's left inset "reference" the text field. This would be achieved as follows: * *

 * {@code
 * Container cnt = new Container(new LayeredLayout());
 * LayeredLayout ll = (LayeredLayout)cnt.getLayout();
 * TextField searchField = new TextField();
 * Button btn = new Button("Search");
 * cnt.add(searchField).add(btn);
 * ll
 *   .setInsets(searchField, "1mm auto auto auto")
 *   .setInsets(btn, "0 auto auto 0")
 *   .setReferenceComponentLeft(btn, searchField, 1f)
 *   .setReferenceComponentTop(btn, searchField, 0);
 * }
 * 
* *

In the above example we set the search field to be anchored to the top of its parent (1mm inset), * but for all other insets to be auto. This will result it being centered horizontally in its parent. We then * anchor the button to the left and top of the search field so that the top and left insets of button will always be * calculated relative to the position of searchField. In particular since the button has top and left insets of 0, * the button will always be placed just to the right of the search field, with its top edge aligned with the top edge * of search field.

* *

Reference Positions

*

The second parameter of {@code setReferenceComponentLeft(btn, searchField, 1f)} is the reference position and it dictates * which edge of the reference component ({@literal searchField}) the inset should be anchored to. A value of {@literal 1} indicates that * it should anchor to the opposite side of the inset (e.g. in this case it is the "left" inset we are setting, so the {@literal 1} * value dictates that it is anchored to the "right" side of the text field. A value of {@literal 0} indicates that it should anchor * to the same side as the inset. This is why we used {@literal 0} in the subsequent call to {@code .setReferenceComponentTop(btn, searchField, 0);}, * because we want to anchor the "top" inset of {@literal button} to the "top" edge of {@literal searchField}.

* * * * * @see com.codename1.ui.Form#getLayeredPane() * @see com.codename1.ui.Form#getLayeredPane(java.lang.Class, boolean) * @see com.codename1.ui.Form#setGlassPane(com.codename1.ui.Painter) * @author Shai Almog */ public class LayeredLayout extends Layout { /** * Unit used for insets. Millimetres. * * @see Inset#unit(byte) * @see Inset#changeUnits(byte) */ public static final byte UNIT_DIPS = Style.UNIT_TYPE_DIPS; /** * Unit used for insets. Pixels. * * @see Inset#unit(byte) * @see Inset#changeUnits(byte) */ public static final byte UNIT_PIXELS = Style.UNIT_TYPE_PIXELS; /** * Unit used for insets. Percent. * * @see Inset#unit(byte) * @see Inset#changeUnits(byte) */ public static final byte UNIT_PERCENT = Style.UNIT_TYPE_SCREEN_PERCENTAGE; /** * Unit used for insets. Auto. Auto unit type for an inset indicates the * the inset will be automatically determined at layout time. * * @see Inset#unit(byte) * @see Inset#changeUnits(byte) */ public static final byte UNIT_AUTO = 100; /** * Unit used for insets. Baseline. Baseline unit type for an inset indicates * the inset will be aligned with the baseline of the reference component. This only * makes sense for the top inset. The height will automatically become the preferred * height and the bottom inset will become "auto" if the top inset uses the baseline unit. */ public static final byte UNIT_BASELINE = 101; /** * Temp collection to keep track of which components in the container * have been laid out. */ private HashSet tmpLaidOut = new HashSet(); /** * The preferred height in MM of this layout which serves as a sort of minimum * height even when the components in the layout don't demand space. * *

The actual preferred height will be the max of this value and the * calculated preferred height based on the container's children.

*/ private float preferredHeightMM; /** * The preferred width (in MM) of this layout which serves as a sort of minimum * width even when the components in the layout don't demand space. * *

The actual preferred width will be the max of this value and the * calculated preferred width based on the container's children.

*/ private float preferredWidthMM; /** * Sets the preferred size of this layout in MM. This serves as a minimum * size that will be returned by calcPreferredSize(). * @param width The preferred width in MM. * @param height The preferred height in MM. */ public void setPreferredSizeMM(float width, float height) { this.preferredHeightMM = height; this.preferredWidthMM = width; } /** * The preferred height in MM of this layout which serves as a sort of minimum * height even when the components in the layout don't demand space. * *

The actual preferred height will be the max of this value and the * calculated preferred height based on the container's children.

*/ public float getPreferredHeightMM() { return preferredHeightMM; } /** * Sets the preferred height of this layout in MM. * @param mm */ public void setPreferredHeightMM(float mm) { preferredHeightMM = mm; } /** * Sets the preferred width of this layout in MM. * @param mm */ public void setPreferredWidthMM(float mm) { preferredWidthMM = mm; } /** * The preferred width (in MM) of this layout which serves as a sort of minimum * width even when the components in the layout don't demand space. * *

The actual preferred width will be the max of this value and the * calculated preferred width based on the container's children.

*/ public float getPreferredWidthMM() { return preferredWidthMM; } @Override public void addLayoutComponent(Object value, Component comp, Container c) { if (value instanceof LayeredLayoutConstraint.Inset) { value = ((LayeredLayoutConstraint.Inset)value).constraint(); } if (value instanceof LayeredLayoutConstraint) { installConstraint((LayeredLayoutConstraint)value, comp); } } /** * Wraps {@link #getComponentConstraint(com.codename1.ui.Component) } and casts it * directly to {@link LayeredLayoutConstraint}. * @param cmp The component whose constraint we want to retrieve. * @return The layered layout constraint for this component. */ public LayeredLayoutConstraint getLayeredLayoutConstraint(Component cmp) { return (LayeredLayoutConstraint)getComponentConstraint(cmp); } /** * Installs the given constraint in the provided component. * @param constraint * @param cmp * @return */ private LayeredLayoutConstraint installConstraint(LayeredLayoutConstraint constraint, Component cmp) { if (constraint.outer() != this || (constraint.cmp != null && constraint.cmp != cmp)) { LayeredLayoutConstraint tmp = createConstraint(); constraint.copyTo(tmp); constraint = tmp; } constraint.cmp = cmp; cmp.putClientProperty("$$LayeredLayoutConstraint", constraint); return constraint; } /** * Makes a copy of the given constraint. * @param constraint The constraint to copy. * @return The copied constraint. */ @Override public Object cloneConstraint(Object constraint) { if (constraint instanceof LayeredLayoutConstraint) { return ((LayeredLayoutConstraint)constraint).copy(); } return super.cloneConstraint(constraint); } /* // We don't see to use this right now so commenting out. However // it is conceivable that we might want to reintroduce this ability later, so // I'm leaving the code here. private void uninstallConstraint(Component cmp) { LayeredLayoutConstraint constraint = (LayeredLayoutConstraint)getComponentConstraint(cmp); if (constraint != null) { constraint.cmp = null; } cmp.putClientProperty("$$LayeredLayoutConstraint", null); } */ /** * Gets the LayeredLayoutConstraint associated with the given component. * * May return null if there is no constraint. * @param comp * @return */ @Override public Object getComponentConstraint(Component comp) { return comp.getClientProperty("$$LayeredLayoutConstraint"); } /** * Creates a default layered layout constraint. Default constraint * has zero insets on all four sides. * @param constraint * @return */ public LayeredLayoutConstraint createConstraint(String constraint) { return new LayeredLayoutConstraint().setInsets(constraint); } /** * If the given component already has a LayeredLayoutConstraint, then this * will return it. Otherwise it will create a constraint, install it in {@literal cmp} * and return the constraint for inspection or manipulation. * @param cmp The component whose constraint we wish to retrieve. * @return The constraint for a given component. */ public LayeredLayoutConstraint getOrCreateConstraint(Component cmp) { LayeredLayoutConstraint constraint = (LayeredLayoutConstraint)getComponentConstraint(cmp); if (constraint == null) { //System.out.println("Constraint is null... creating a new one"); constraint = createConstraint(); constraint = installConstraint(constraint, cmp); } return constraint; } /** * Gets an {@link Inset} associated with the provided component * @param cmp The component whose inset we wish to retrieve. * @param side The side of the inset. One of {@link Component#TOP}, {@link Component#LEFT}, {@link Component#BOTTOM} * or {@link Component#RIGHT}. * @return The {@link Inset} for the given side of the component. */ public Inset getInset(Component cmp, int side) { return getOrCreateConstraint(cmp).insets[side]; } /** * Returns the insets for the given component as a string. This can return the * insets in one of two formats depending on the value of the {@literal withLabels} * parameter. * @param cmp The component whose insets we wish to retrieve. * @param withLabels If {@literal false}, then this returns a string of the format {@code "top right bottom left"} * e.g {@code "2mm 2mm 2mm 2mm"}. If {@literal true}, then it will be formatted like CSS properties: {@code "top:2mm; right:2mm; bottom:2mm; left:2mm"}. * @return The insets associated with {@literal cmp} as a string. Each inset will include the unit. E.g.: *

  • {@literal 2mm} = 2 millimetres/dips
  • {@literal 2px} = 2 pixels
  • {@literal 25%} = 25%
  • {@literal auto} = Flexible inset
* */ public String getInsetsAsString(Component cmp, boolean withLabels) { return getOrCreateConstraint(cmp).getInsetsAsString(withLabels); } /** * Gets the top inset as a string. Return value will include the unit, so the following * are possible values: *

*

  • {@literal 2mm} = 2 millimetres
  • *
  • {@literal 2px} = 2 pixels
  • *
  • {@literal 25%} = 25%
  • *
  • {@literal auto} = Flexible Inset
  • *
*

* @param cmp The component whose inset we wish to retrieve. * @return The inset formatted as a string with the unit abbreviation ("mm", "px", or "%") suffixed. */ public String getTopInsetAsString(Component cmp) { return getOrCreateConstraint(cmp).top().getValueAsString(); } /** * Gets the bottom inset as a string. Return value will include the unit, so the following * are possible values: *

*

  • {@literal 2mm} = 2 millimetres
  • *
  • {@literal 2px} = 2 pixels
  • *
  • {@literal 25%} = 25%
  • *
  • {@literal auto} = Flexible Inset
  • *
*

* @param cmp The component whose inset we wish to retrieve. * @return The inset formatted as a string with the unit abbreviation ("mm", "px", or "%") suffixed. */ public String getBottomInsetAsString(Component cmp) { return getOrCreateConstraint(cmp).bottom().getValueAsString(); } /** * Gets the left inset as a string. Return value will include the unit, so the following * are possible values: *

*

  • {@literal 2mm} = 2 millimetres
  • *
  • {@literal 2px} = 2 pixels
  • *
  • {@literal 25%} = 25%
  • *
  • {@literal auto} = Flexible Inset
  • *
*

* @param cmp The component whose inset we wish to retrieve. * @return The inset formatted as a string with the unit abbreviation ("mm", "px", or "%") suffixed. */ public String getLeftInsetAsString(Component cmp) { return getOrCreateConstraint(cmp).left().getValueAsString(); } /** * Gets the right inset as a string. Return value will include the unit, so the following * are possible values: *

*

  • {@literal 2mm} = 2 millimetres
  • *
  • {@literal 2px} = 2 pixels
  • *
  • {@literal 25%} = 25%
  • *
  • {@literal auto} = Flexible Inset
  • *
*

* @param cmp The component whose inset we wish to retrieve. * @return The inset formatted as a string with the unit abbreviation ("mm", "px", or "%") suffixed. */ public String getRightInsetAsString(Component cmp) { return getOrCreateConstraint(cmp).right().getValueAsString(); } /** * Sets the insets for the component {@literal cmp} to the values specified in {@literal insets}. * @param cmp The component whose insets we wish to set. * @param insets The insets expressed as a string. See {@link LayeredLayoutConstraint#setInsets(java.lang.String) } for * details on the format of this parameter. * @return Self for chaining. * @see LayeredLayoutConstraint#setInsets(java.lang.String) For details on the {@literal insets} parameter * format. */ public LayeredLayout setInsets(Component cmp, String insets) { getOrCreateConstraint(cmp).setInsets(insets); return this; } /** * Sets the top inset for this component to the prescribed value. * @param cmp The component whose inset we wish to set. * @param inset The inset value, including unit. Units are Percent (%), Millimetres (mm), Pixels (px), and "auto". E.g. the * following insets values would all be acceptable: *

*

    *
  • {@code "2mm"} = 2 millimetres
  • *
  • {@code "2px"} = 2 pixels
  • *
  • {@code "25%"} = 25 percent.
  • *
  • {@code "auto"} = Flexible inset
  • *
*

* @return Self for chaining. */ public LayeredLayout setInsetTop(Component cmp, String inset) { getOrCreateConstraint(cmp).top().setValue(inset); return this; } /** * Sets the top inset for this component to the prescribed value. * @param cmp The component whose inset we wish to set. * @param inset The inset value, including unit. Units are Percent (%), Millimetres (mm), Pixels (px), and "auto". E.g. the * following insets values would all be acceptable: *

*

    *
  • {@code "2mm"} = 2 millimetres
  • *
  • {@code "2px"} = 2 pixels
  • *
  • {@code "25%"} = 25 percent.
  • *
  • {@code "auto"} = Flexible inset
  • *
*

* @return Self for chaining. */ public LayeredLayout setInsetBottom(Component cmp, String inset) { getOrCreateConstraint(cmp).bottom().setValue(inset); return this; } /** * Sets the left inset for this component to the prescribed value. * @param cmp The component whose inset we wish to set. * @param inset The inset value, including unit. Units are Percent (%), Millimetres (mm), Pixels (px), and "auto". E.g. the * following insets values would all be acceptable: *

*

    *
  • {@code "2mm"} = 2 millimetres
  • *
  • {@code "2px"} = 2 pixels
  • *
  • {@code "25%"} = 25 percent.
  • *
  • {@code "auto"} = Flexible inset
  • *
*

* @return Self for chaining. */ public LayeredLayout setInsetLeft(Component cmp, String inset) { getOrCreateConstraint(cmp).left().setValue(inset); return this; } /** * Sets the right inset for this component to the prescribed value. * @param cmp The component whose inset we wish to set. * @param inset The inset value, including unit. Units are Percent (%), Millimetres (mm), Pixels (px), and "auto". E.g. the * following insets values would all be acceptable: *

*

    *
  • {@code "2mm"} = 2 millimetres
  • *
  • {@code "2px"} = 2 pixels
  • *
  • {@code "25%"} = 25 percent.
  • *
  • {@code "auto"} = Flexible inset
  • *
*

* @return Self for chaining. */ public LayeredLayout setInsetRight(Component cmp, String inset) { getOrCreateConstraint(cmp).right().setValue(inset); return this; } /** * Sets the reference components for the insets of {@literal cmp}. See {@link LayeredLayoutConstraint#setReferenceComponents(com.codename1.ui.Component...) } * for a full description of the parameters. * @param cmp The component whose reference components we wish to check. * @param referenceComponents The reference components. This var arg may contain 1 to 4 values. See {@link LayeredLayoutConstraint#setReferenceComponents(com.codename1.ui.Component...) } * for a full description. * @return Self for chaining. */ public LayeredLayout setReferenceComponents(Component cmp, Component... referenceComponents) { getOrCreateConstraint(cmp).setReferenceComponents(referenceComponents); return this; } /** * Sets the reference components for this component as a string of 1 to 4 component indices separated by spaces. An * index of {@literal -1} indicates no reference for the corresponding inset. See {@link LayeredLayoutConstraint#setReferenceComponentIndices(com.codename1.ui.Container, java.lang.String) } * for a description of the {@literal refs} parameter. * @param cmp The component whose references we're setting. * @param refs Reference components as a string of component indices in the parent. * @return Self for chaining. */ public LayeredLayout setReferenceComponents(Component cmp, String refs) { getOrCreateConstraint(cmp).setReferenceComponentIndices(cmp.getParent(), refs); return this; } /** * Sets the reference component for the top inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to anchor the inset to. * @return Self for chaining. */ public LayeredLayout setReferenceComponentTop(Component cmp, Component referenceComponent) { getOrCreateConstraint(cmp).top().referenceComponent(referenceComponent); return this; } /** * Sets the reference component for the bottom inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to anchor the inset to. * @return Self for chaining. */ public LayeredLayout setReferenceComponentBottom(Component cmp, Component referenceComponent) { getOrCreateConstraint(cmp).bottom().referenceComponent(referenceComponent); return this; } /** * Sets the reference component for the left inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to anchor the inset to. * @return Self for chaining. */ public LayeredLayout setReferenceComponentLeft(Component cmp, Component referenceComponent) { getOrCreateConstraint(cmp).left().referenceComponent(referenceComponent); return this; } /** * Sets the reference component for the right inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to anchor the inset to. * @return Self for chaining. */ public LayeredLayout setReferenceComponentRight(Component cmp, Component referenceComponent) { getOrCreateConstraint(cmp).top().referenceComponent(referenceComponent); return this; } /** * Sets the reference positions for reference components. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a description of the parameters. * @param cmp The component whose insets we are manipulating. * @param referencePositions The reference positions for the reference components. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of this parameter. * @return Self for chaining. */ public LayeredLayout setReferencePositions(Component cmp, float... referencePositions) { getOrCreateConstraint(cmp).setReferencePositions(referencePositions); return this; } /** * Sets the reference positions for reference components. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a description of the parameters. * @param cmp The component whose insets we are manipulating. * @param positions The reference positions for the reference components. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of this parameter. * @return Self for chaining. */ public LayeredLayout setReferencePositions(Component cmp, String positions) { getOrCreateConstraint(cmp).setReferencePositions(positions); return this; } /** * Sets the top inset reference position. Only applicable if the top inset has a reference * component specified. * @param cmp The component whose insets were are manipulating. * @param position The position. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } for a full * description of the possible values here. * @return */ public LayeredLayout setReferencePositionTop(Component cmp, float position) { getOrCreateConstraint(cmp).top().referencePosition(position); return this; } /** * Sets the reference component for the top inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to which the inset should be anchored. * @param position The position of the reference anchor. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of reference positions. * @return */ public LayeredLayout setReferenceComponentTop(Component cmp, Component referenceComponent, float position) { getOrCreateConstraint(cmp).top().referenceComponent(referenceComponent).referencePosition(position); return this; } /** * Sets the bottom inset reference position. Only applicable if the top inset has a reference * component specified. * @param cmp The component whose insets were are manipulating. * @param position The position. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } for a full * description of the possible values here. * @return */ public LayeredLayout setReferencePositionBottom(Component cmp, float position) { getOrCreateConstraint(cmp).bottom().referencePosition(position); return this; } /** * Sets the reference component for the bottom inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to which the inset should be anchored. * @param position The position of the reference anchor. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of reference positions. * @return */ public LayeredLayout setReferenceComponentBottom(Component cmp, Component referenceComponent, float position) { getOrCreateConstraint(cmp).bottom().referenceComponent(referenceComponent).referencePosition(position); return this; } /** * Sets the left inset reference position. Only applicable if the top inset has a reference * component specified. * @param cmp The component whose insets were are manipulating. * @param position The position. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } for a full * description of the possible values here. * @return */ public LayeredLayout setReferencePositionLeft(Component cmp, float position) { getOrCreateConstraint(cmp).left().referencePosition(position); return this; } /** * Sets the reference component for the left inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to which the inset should be anchored. * @param position The position of the reference anchor. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of reference positions. * @return */ public LayeredLayout setReferenceComponentLeft(Component cmp, Component referenceComponent, float position) { getOrCreateConstraint(cmp).left().referenceComponent(referenceComponent).referencePosition(position); return this; } /** * Sets the right inset reference position. Only applicable if the top inset has a reference * component specified. * @param cmp The component whose insets were are manipulating. * @param position The position. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } for a full * description of the possible values here. * @return */ public LayeredLayout setReferencePositionRight(Component cmp, float position) { getOrCreateConstraint(cmp).right().referencePosition(position); return this; } /** * Sets the reference component for the right inset of the given component. * @param cmp The component whose insets we are manipulating. * @param referenceComponent The component to which the inset should be anchored. * @param position The position of the reference anchor. See {@link LayeredLayoutConstraint#setReferencePositions(float...) } * for a full description of reference positions. * @return */ public LayeredLayout setReferenceComponentRight(Component cmp, Component referenceComponent, float position) { getOrCreateConstraint(cmp).right().referenceComponent(referenceComponent).referencePosition(position); return this; } /** * See {@link LayeredLayoutConstraint#setPercentInsetAnchorHorizontal(float) } * @param cmp * @param anchor * @return Self for chaining */ public LayeredLayout setPercentInsetAnchorHorizontal(Component cmp, float anchor) { getOrCreateConstraint(cmp).setPercentInsetAnchorHorizontal(anchor); return this; } /** * See {@link LayeredLayoutConstraint#setPercentInsetAnchorVertical(float) } * @param cmp * @param anchor * @return Self for chaining */ public LayeredLayout setPercentInsetAnchorVertical(Component cmp, float anchor) { getOrCreateConstraint(cmp).setPercentInsetAnchorVertical(anchor); return this; } /** * See {@link LayeredLayoutConstraint#getPercentInsetAnchorHorizontal() } * @param cmp * @return */ public float getPercentInsetAnchorHorizontal(Component cmp) { return getOrCreateConstraint(cmp).getPercentInsetAnchorHorizontal(); } /** * See {@link LayeredLayoutConstraint#getPercentInsetAnchorVertical() } * @param cmp * @return */ public float getPercentInsetAnchorVertical(Component cmp) { return getOrCreateConstraint(cmp).getPercentInsetAnchorVertical(); } /** * {@inheritDoc} */ public void layoutContainer(Container parent) { Style s = parent.getStyle(); int top = s.getPaddingTop(); int bottom = parent.getLayoutHeight() - parent.getBottomGap() - s.getPaddingBottom(); int left = s.getPaddingLeft(parent.isRTL()); int right = parent.getLayoutWidth() - parent.getSideGap() - s.getPaddingRight(parent.isRTL()); int numOfcomponents = parent.getComponentCount(); tmpLaidOut.clear(); for (int i = 0; i < numOfcomponents; i++) { Component cmp = parent.getComponentAt(i); layoutComponent(parent, cmp, top, left, bottom, right); } } /** * Lays out the specific component within the container. This will first lay out any components that it depends on. * @param parent The parent container being laid out. * @param cmp The component being laid out. * @param top * @param left * @param bottom * @param right */ private void layoutComponent(Container parent, Component cmp, int top, int left, int bottom, int right) { if (tmpLaidOut.contains(cmp)) { return; } tmpLaidOut.add(cmp); LayeredLayoutConstraint constraint = (LayeredLayoutConstraint) getComponentConstraint(cmp); if (constraint != null) { constraint.fixDependencies(parent); for (LayeredLayoutConstraint.Inset inset : constraint.insets) { if (inset.referenceComponent != null && inset.referenceComponent.getParent() == parent) { layoutComponent(parent, inset.referenceComponent, top, left, bottom, right); } } } Style s = cmp.getStyle(); if (constraint != null) { //int innerTop = top; //int innerBottom = bottom; //left = 0; //right = parent.getLayoutWidth(); int leftInset = constraint.insets[Component.LEFT].calculate(cmp, top, left, bottom, right); int rightInset = constraint.insets[Component.RIGHT].calculate(cmp, top, left, bottom, right); int topInset = constraint.insets[Component.TOP].calculate(cmp, top, left, bottom, right); int bottomInset = constraint.insets[Component.BOTTOM].calculate(cmp, top, left, bottom, right); cmp.setX(left + leftInset + s.getMarginLeft(parent.isRTL())); cmp.setY(top + topInset + s.getMarginTop()); cmp.setWidth(Math.max(0, right - cmp.getX() - s.getMarginRight(parent.isRTL()) - rightInset)); //cmp.setWidth(Math.max(0, right - left - s.getHorizontalMargins() - rightInset - leftInset)); //cmp.setHeight(Math.max(0, bottom - top - s.getVerticalMargins() - bottomInset - topInset)); cmp.setHeight(Math.max(0, bottom - cmp.getY() - s.getMarginBottom() - bottomInset)); } else { int x = left + s.getMarginLeft(parent.isRTL()); int y = top + s.getMarginTop(); int w = right - left - s.getHorizontalMargins(); int h = bottom - top - s.getVerticalMargins(); cmp.setX(x); cmp.setY(y); cmp.setWidth(Math.max(0,w)); cmp.setHeight(Math.max(0,h)); //System.out.println("Component laid out "+cmp); } } private void calcPreferredValues(Component cmp) { if (tmpLaidOut.contains(cmp)) { return; } tmpLaidOut.add(cmp); LayeredLayoutConstraint constraint = (LayeredLayoutConstraint) getComponentConstraint(cmp); if (constraint != null) { constraint.fixDependencies(cmp.getParent()); for (LayeredLayoutConstraint.Inset inset : constraint.insets) { if (inset.referenceComponent != null && inset.referenceComponent.getParent() == cmp.getParent()) { calcPreferredValues(inset.referenceComponent); } inset.calcPreferredValue(cmp.getParent(), cmp); } } } /** * {@inheritDoc} */ @Override public Dimension getPreferredSize(Container parent) { int maxWidth = 0; int maxHeight = 0; int numOfcomponents = parent.getComponentCount(); tmpLaidOut.clear(); boolean requiresSecondPassToCalculatePercentInsets = false; for (int i = 0; i < numOfcomponents; i++) { Component cmp = parent.getComponentAt(i); calcPreferredValues(cmp); LayeredLayoutConstraint constraint = (LayeredLayoutConstraint) getComponentConstraint(cmp); int vInsets = 0; int hInsets = 0; if (constraint != null) { if (!requiresSecondPassToCalculatePercentInsets) { for (Inset ins : constraint.insets) { if (ins.unit == UNIT_PERCENT && ins.referenceComponent == null) { requiresSecondPassToCalculatePercentInsets = true; break; } } } vInsets += constraint.insets[Component.TOP].preferredValue + constraint.insets[Component.BOTTOM].preferredValue; hInsets += constraint.insets[Component.LEFT].preferredValue + constraint.insets[Component.RIGHT].preferredValue; /* // Commenting all this stuff out because the calcPreferredValues() call should // take all of this into account already. Component topRef = constraint.top().getReferenceComponent(); LayeredLayoutConstraint currConstraint = constraint; int maxIterations = numOfcomponents; int iter = 0; while (topRef != null) { if (iter++ >= maxIterations) break; vInsets += Math.max(0, topRef.getOuterPreferredH() * currConstraint.top().getReferencePosition()); currConstraint = getOrCreateConstraint(topRef); topRef = currConstraint.top().getReferenceComponent(); } Component bottomRef = constraint.bottom().getReferenceComponent(); currConstraint = constraint; iter = 0; while (bottomRef != null) { if (iter++ >= maxIterations) break; vInsets += Math.max(0, bottomRef.getOuterPreferredH() * currConstraint.bottom().getReferencePosition()); currConstraint = getOrCreateConstraint(bottomRef); bottomRef = currConstraint.bottom().getReferenceComponent(); } Component leftRef = constraint.left().getReferenceComponent(); currConstraint = constraint; iter = 0; while (leftRef != null) { if (iter++ >= maxIterations) break; hInsets += Math.max(0, leftRef.getOuterPreferredW() * currConstraint.left().getReferencePosition()); currConstraint = getOrCreateConstraint(leftRef); leftRef = currConstraint.left().getReferenceComponent(); } Component rightRef = constraint.right().getReferenceComponent(); currConstraint = constraint; iter = 0; while (rightRef != null) { if (iter++ >= maxIterations) break; hInsets += Math.max(0, rightRef.getOuterPreferredW() * currConstraint.right().getReferencePosition()); currConstraint = getOrCreateConstraint(rightRef); rightRef = currConstraint.right().getReferenceComponent(); } */ } maxHeight = Math.max(maxHeight, cmp.getPreferredH() + cmp.getStyle().getMarginTop() + cmp.getStyle().getMarginBottom() + vInsets); maxWidth = Math.max(maxWidth, cmp.getPreferredW() + cmp.getStyle().getMarginLeftNoRTL() + cmp.getStyle().getMarginRightNoRTL() + hInsets); } Style s = parent.getStyle(); Dimension d = new Dimension(maxWidth + s.getPaddingLeftNoRTL() + s.getPaddingRightNoRTL(), maxHeight + s.getPaddingTop() + s.getPaddingBottom() + parent.getBottomGap()); if (preferredWidthMM > 0) { int minW = Display.getInstance().convertToPixels(preferredWidthMM); if (d.getWidth() < minW) { d.setWidth(minW); } } if (preferredHeightMM > 0) { int minH = Display.getInstance().convertToPixels(preferredHeightMM); if (d.getHeight() < Display.getInstance().convertToPixels(preferredHeightMM)) { d.setHeight(minH); } } if (requiresSecondPassToCalculatePercentInsets) { // We will do a second pass to deal with percent unit insets // since these were set to have zero preferred sizes in the calculation. // This is still a bit of a hack as it only deals with components that // don't depend on any other components. E.g. If we have a label that is // supposed to have a top inset of 75%. The preferred height should then // be 4 times the preferred height of the label rather than just the // preferred height of the label itself. // This still doesn't deal with the case where there is another label // that references that label and has an inset of an additional 20% // Ref https://github.com/codenameone/CodenameOne/issues/2720 float maxHRatio = 0; float maxWRatio = 0; for (int i=0; i< numOfcomponents; i++) { Component cmp = parent.getComponentAt(i); LayeredLayoutConstraint constraint = (LayeredLayoutConstraint) getComponentConstraint(cmp); if (constraint != null) { float hRatio = 0; if (constraint.top().unit == UNIT_PERCENT && constraint.top().referenceComponent == null) { hRatio += constraint.top().value / 100f; } if (constraint.bottom().unit == UNIT_PERCENT && constraint.bottom().referenceComponent == null) { hRatio += constraint.bottom().value / 100f; } hRatio = Math.min(1f, hRatio); maxHRatio = Math.max(maxHRatio, hRatio); float wRatio = 0; if (constraint.left().unit == UNIT_PERCENT && constraint.left().referenceComponent == null) { wRatio += constraint.left().value / 100f; } if (constraint.right().unit == UNIT_PERCENT && constraint.right().referenceComponent == null) { wRatio += constraint.right().value / 100f; } wRatio = Math.min(1f, wRatio); maxWRatio = Math.max(maxWRatio, wRatio); } } if (maxHRatio > 0 && maxHRatio <1) { d.setHeight((int)Math.round(d.getHeight()/(1-maxHRatio))); } if (maxWRatio > 0 && maxWRatio <1) { d.setWidth((int)Math.round(d.getWidth()/(1-maxWRatio))); } } return d; } /** * {@inheritDoc} */ public String toString() { return "LayeredLayout"; } /** * {@inheritDoc} */ public boolean isOverlapSupported() { return true; } /** * {@inheritDoc} */ public boolean obscuresPotential(Container parent) { return true; } /** * Shorthand for Container.encloseIn(new LayeredLayout(), cmps); * * @param cmps the components to add to a new layered layout container * @return a newly created layered layout */ public static Container encloseIn(Component... cmps) { return Container.encloseIn(new LayeredLayout(), cmps); } /** * Creates a new {@link LayeredLayoutConstraint} * @return */ public LayeredLayoutConstraint createConstraint() { return new LayeredLayoutConstraint(); } private static int getOuterHeight(Component cmp) { Style s = cmp.getStyle(); return cmp.getHeight() + s.getVerticalMargins(); } private static int getOuterPreferredH(Component cmp) { Style s = cmp.getStyle(); return cmp.getPreferredH() + s.getVerticalMargins(); } private static int getInnerHeight(Component cmp) { Style s = cmp.getStyle(); return cmp.getHeight() - s.getPaddingTop() - s.getPaddingBottom(); } private static int getInnerPreferredH(Component cmp) { Style s = cmp.getStyle(); return cmp.getPreferredH() - s.getPaddingTop() - s.getPaddingBottom(); } private static int getOuterWidth(Component cmp) { Style s = cmp.getStyle(); return cmp.getWidth() + s.getHorizontalMargins(); } private static int getOuterPreferredW(Component cmp) { Style s = cmp.getStyle(); return cmp.getPreferredW() + s.getHorizontalMargins(); } private static int getOuterX(Component cmp) { return cmp.getX() - cmp.getStyle().getMarginLeftNoRTL(); } private static int getOuterY(Component cmp) { return cmp.getY() - cmp.getStyle().getMarginTop(); } /** * A class that encapsulates the insets for a component in layered layout. */ public class LayeredLayoutConstraint { /** * The component that this constraint belongs to. If you try to add * this constraint to a different component, then it will cause a copy * to be made rather than using the same component so that constraints * to bleed into other components. */ private Component cmp; /** * Gets the insets as a string. * @return */ public String toString() { return getInsetsAsString(true); } private LayeredLayout outer() { return LayeredLayout.this; } /** * Recursively fixes all dependencies so that they are contained inside * the provided parent. A dependency is a "referenceComponent". * @param parent The parent container within which all dependencies should reside. * @return Self for chaining. * @see #setReferenceComponents(com.codename1.ui.Component...) */ public LayeredLayoutConstraint fixDependencies(Container parent) { for (Inset inset : insets) { inset.fixDependencies(parent); } return this; } /** * Checks to see if this constraint has any circular dependencies. E.g. * Component A has an inset that has Component B as a reference, which has * an inset that depends on Component A. * * @param start The start component to check. * @return True this forms a circular dependency. */ public boolean hasCircularDependency(Component start) { return dependsOn(start); } /** * Gets the inset for a particular side. * @param inset One of {@link Component#TOP}, {@link Component#BOTTOM }, {@link Component#LEFT} or * {@link Component#RIGHT}. * @return The inset. */ public Inset getInset(int inset) { return insets[inset]; } /** * Makes a full copy of this inset. * @return */ public LayeredLayoutConstraint copy() { return copyTo(new LayeredLayoutConstraint()); } /** * Copies the settings of this constraint into another constraint. * @param dest The inset to copy to. * @return Self for chaining. */ public LayeredLayoutConstraint copyTo(LayeredLayoutConstraint dest) { for (int i=0; i<4; i++) { //Inset inset = new Inset(i); dest.insets[i] = insets[i].copyTo(dest.insets[i]); } return dest; } /** * Returns a reference box within which insets of the given component are calculated. If * {@link cmp} has no reference components in any of its insets, then the resulting box will * just bee the inner box of the parent (e.g. the parent's inner bounds. * @param parent The parent container. * @param parent * @param box An out parameter. This will store the bounds of the box. * @return The reference box. (This will be the same object that is passed in the {@literal box} parameter. */ public Rectangle getReferenceBox(Container parent, Rectangle box) { return getReferenceBox(parent, null, box); } /** * Returns a reference box within which insets of the given component are calculated. If * {@link cmp} has no reference components in any of its insets, then the resulting box will * just bee the inner box of the parent (e.g. the parent's inner bounds. * @param parent The parent container. * @param cmp The component whose reference box we are obtaining. Not used. May be null. * @param box An out parameter. This will store the bounds of the box. * @return The reference box. (This will be the same object that is passed in the {@literal box} parameter. * @deprecated Use {@link #getReferenceBox(com.codename1.ui.Container, com.codename1.ui.geom.Rectangle) } instead. */ public Rectangle getReferenceBox(Container parent, Component cmp2, Rectangle box) { Style parentStyle = parent.getStyle(); //Style cmpStyle = cmp.getStyle(); if (top().getReferenceComponent() == null) { box.setY(parentStyle.getPaddingTop()); } else { Component ref = top().getReferenceComponent(); box.setY((int)(getOuterY(ref) + (top().getReferencePosition() * getOuterHeight(ref)))); } if (left().getReferenceComponent() == null) { box.setX(parentStyle.getPaddingLeftNoRTL()); } else { Component ref = left().getReferenceComponent(); box.setX((int)(getOuterX(ref) + (left().getReferencePosition() * getOuterWidth(ref)))); } if (right().getReferenceComponent() == null) { box.setWidth(parent.getLayoutWidth() - box.getX() - parentStyle.getPaddingRightNoRTL() - parent.getSideGap()); } else { Component ref = right().getReferenceComponent(); int refX = (int)(getOuterX(ref) + getOuterWidth(ref) - (right().getReferencePosition() * getOuterWidth(ref))); box.setWidth(refX - box.getX()); } if (bottom().getReferenceComponent() == null) { box.setHeight(parent.getLayoutHeight() - box.getY() - parentStyle.getPaddingBottom() - parent.getBottomGap()); } else { Component ref = bottom().getReferenceComponent(); int refY = (int)(getOuterY(ref) + getOuterHeight(ref) - (bottom().getReferencePosition() * getOuterHeight(ref))); box.setHeight(refY - box.getY()); } return box; } /** * Returns a reference box within which insets of the given component are calculated. If * {@link cmp} has no reference components in any of its insets, then the resulting box will * just bee the inner box of the parent (e.g. the parent's inner bounds. * @param parent The parent container. * @param cmp The component whose reference box we are obtaining. * @return The reference box. * @deprecated Use {@link #getReferenceBox(com.codename1.ui.Container) */ public Rectangle getReferenceBox(Container parent, Component cmp) { return getReferenceBox(parent, cmp, new Rectangle()); } /** * Returns a reference box within which insets of the given component are calculated. If * {@link cmp} has no reference components in any of its insets, then the resulting box will * just bee the inner box of the parent (e.g. the parent's inner bounds. * @param parent The parent container. * @return The reference box. */ public Rectangle getReferenceBox(Container parent) { return getReferenceBox(parent, (Component)null); } /** * Shifts the constraint by the specified number of pixels while maintaining the same units. This is * used mainly in the GUI builder to facilitate dragging and resizing of the component. * @param x The number of pixels that the insets should be shifted on the x axis. * @param y The number of pixels that the insets should be shifted on the y axis. * @param preferMM If an inset needs to be switched from flexible to fixed, then this indicates where it will * be changed to millimetres or pixels. {@literal true} for millimetres. * @param parent The parent container in which calculations should be performed. * @return Self for chaining. * @see #translateMM(float, float, boolean, com.codename1.ui.Container) */ public LayeredLayoutConstraint translatePixels(int x, int y, boolean preferMM, Container parent) { if (y != 0) { if (top().getUnit() == UNIT_BASELINE) { top().changeUnits(preferMM ? UNIT_DIPS : UNIT_PIXELS); } else if (top().isFlexible() && top().autoIsClipped) { top().changeUnits(preferMM ? UNIT_DIPS : UNIT_PIXELS); } if (bottom().isFlexible() && bottom().autoIsClipped) { bottom().changeUnits(preferMM ? UNIT_DIPS : UNIT_PIXELS); } if (top().isFlexible() && bottom().isFlexible()) { // Both top and bottom are flexible... we need to make one of these // fixed if (y > 0) { // we're moving it to toward the bottom, so we'll choose the bottom // as an anchor point. bottom().translatePixels(-y, preferMM, parent); } else { top().translatePixels(y, preferMM, parent); } } else { if (top().isFixed()) { top().translatePixels(y, preferMM, parent); } if (bottom().isFixed()) { bottom().translatePixels(-y, preferMM, parent); } } } if (x != 0) { if (left().isFlexible() && left().autoIsClipped) { left().changeUnits(preferMM ? UNIT_DIPS : UNIT_PIXELS); } if (right().isFlexible() && right().autoIsClipped) { right().changeUnits(preferMM ? UNIT_DIPS : UNIT_PIXELS); } if (left().isFlexible() && right().isFlexible()) { // Both top and bottom are flexible... we need to make one of these // fixed if (x > 0) { // we're moving it to toward the bottom, so we'll choose the bottom // as an anchor point. right().translatePixels(-x, preferMM, parent); } else { left().translatePixels(x, preferMM, parent); } } else { if (left().isFixed()) { left().translatePixels(x, preferMM, parent); } if (right().isFixed()) { right().translatePixels(-x, preferMM, parent); } } } return this; } /** * Shifts the constraint by the specified number of millimetres while maintaining the same units. This is * used mainly in the GUI builder to facilitate dragging and resizing of the component. * @param x The number of pixels that the insets should be shifted on the x axis. * @param y The number of pixels that the insets should be shifted on the y axis. * @param preferMM If an inset needs to be switched from flexible to fixed, then this indicates where it will * be changed to millimetres or pixels. {@literal true} for millimetres. * @param parent The parent container in which calculations should be performed. * @return Self for chaining. * @see #translatePixels(int, int, boolean, com.codename1.ui.Container) */ public LayeredLayoutConstraint translateMM(float x, float y, boolean preferMM, Container parent) { return translatePixels(Display.getInstance().convertToPixels(x), Display.getInstance().convertToPixels(y), preferMM, parent); } /** * Gets the set of insets on this constraint that are fixed. An inset is * considered fixed if it's unit is NOT {@link #UNIT_AUTO}. * @return */ public Collection getFixedInsets() { ArrayList out = new ArrayList(); for (Inset i : insets) { if (i.unit != UNIT_AUTO) { out.add(i); } } return out; } /** * Gets the set of insets in this constraint that are flexible. An inset is * considered flexible if it's unit is {@link #UNIT_AUTO}. * @return */ public Collection getFlexibleInsets() { ArrayList out = new ArrayList(); for (Inset i : insets) { if (i.unit == UNIT_AUTO) { out.add(i); } } return out; } /** * Gets the reference positions of this constraint as a string. * @param withLabels True to return the string in CSS format: e.g. {@code "top:1.0; right:0; bottom:1.0; left:1.0"} {@literal false} * to return as a space-delimited string of inset reference positions in the order "top right bottom left". E.g. {@literal "1.0 0 1.0 1.0"} * @return The reference positions as a string. */ public String getReferencePositionsAsString(boolean withLabels) { StringBuilder sb = new StringBuilder(); if (withLabels) { sb.append("top:").append(top().referencePosition).append("; ") .append("right:").append(right().referencePosition).append("; ") .append("bottom:").append(bottom().referencePosition).append("; ") .append("left:").append(left().referencePosition); } else { sb.append(top().referencePosition).append(" ") .append(right().referencePosition).append(" ") .append(bottom().referencePosition).append(" ") .append(left().referencePosition); } return sb.toString(); } /** * Sets the reference component positions for this constraint from a string. The string format * may be either using labels following the same output format of {@literal getReferencePositionsAsString(true)} * or as a space-delimited string (e.g. {@literal getReferencePositionsAsString(false)}. When using the label * format, you may provide one or more inset values in the string. E.g. the following are all acceptable: *

*

  • {@literal top:1.0; left:0; right:0; bottom:1.0}
  • *
  • {@literal left:0.5}
  • *
  • {@literal left:1.0; right:0.5}
  • *
*

*

* If you provide the positions as a space-delimited string, then they are expected to follow the same format * as is used in CSS for providing margin. To summarize: *

*

* {@code //Apply to all four sides 1.0 //vertical | horizontal 1.0 0 // top | horizontal | bottom 1.0 0.0 0.5 // top | right | bottom | left 1.0 1.0 1.0 1.0 } *

* *

Interpretation of Reference Positions:

*

When an inset includes a reference component, that means that the inset is "anchored" to that * reference component. I.e. An inset of {@literal 1mm} is measured 1mm from the outer edge of the * reference component. By default it chooses the edge of on the same side as the inset. So * if this is a "left" inset, then it will measure against the "left" outer edge of the reference component. * This is the meaning of a {@literal 0} value for the associated reference positions.

* *

A reference position of {@literal 1.0} will start measuring from the opposite edge. So for a "left" inset, * it will measure from the "right" outer edge of the reference component. You can choose any real value for the * reference position, and it will cause the measurement to be scaled accordingly. E.g. {@literal 0.5.} would measure * from the center point of the reference component.

* @param positionsStr The reference positions. * @return Self for chaining. */ public LayeredLayoutConstraint setReferencePositions(String positionsStr) { positionsStr = positionsStr.trim(); LayeredLayoutConstraint cnst = this; if (positionsStr.indexOf(":") != -1) { String[] parts = Util.split(positionsStr, ";"); for (String part : parts) { if (part.trim().length() == 0) { continue; } String[] kv = Util.split(part, ":"); String key = kv[0].trim(); String val = kv[1].trim(); if ("top".equals(key)) { cnst.top().referencePosition = Float.parseFloat(val); } else if ("bottom".equals(key)) { cnst.bottom().referencePosition = Float.parseFloat(val); } else if ("left".equals(key)) { cnst.left().referencePosition = Float.parseFloat(val); } else if ("right".equals(key)) { cnst.right().referencePosition = Float.parseFloat(val); } } } else { String[] parts = Util.split(positionsStr, " "); if (parts.length == 1) { float f = Float.parseFloat(parts[0]); top().referencePosition = f; right().referencePosition = f; bottom().referencePosition = f; left().referencePosition = f; } else if (parts.length == 2) { float f0 = Float.parseFloat(parts[0]); float f1 = Float.parseFloat(parts[1]); top().referencePosition = f0; bottom().referencePosition = f0; left().referencePosition = f1; right().referencePosition = f1; } else if (parts.length == 3) { float f0 = Float.parseFloat(parts[0]); float f1 = Float.parseFloat(parts[1]); float f2 = Float.parseFloat(parts[2]); top().referencePosition = f0; left().referencePosition = f1; right().referencePosition = f1; bottom().referencePosition = f2; } else if (parts.length == 4) { float f0 = Float.parseFloat(parts[0]); float f1 = Float.parseFloat(parts[1]); float f2 = Float.parseFloat(parts[2]); float f3 = Float.parseFloat(parts[3]); top().referencePosition = f0; right().referencePosition = f1; bottom().referencePosition = f2; left().referencePosition = f3; } } return this; } /** * Gets the reference component indexes within the provided {@literal parent} container as a string. * If an inset doesn't have a reference component, then the corresponding index will be {@literal -1}. *

* Use the {@literal withLabels} parameter to choose whether to include labels with the indices or not. E.g: * * {@code * String indices = getReferenceComponentIndicesAsString(parent, true); * // Would return something like * // "top:-1; right:2; bottom:-1; left: 0" * * indices = getReferenceComponentIndicesAsString(parent, false); * // Would return something like: * // "-1 2 -1 0" (i.e. Top Right Bottom Left) * * // Interpretation: * // Top inset has no reference component * // Right inset has component with index 2 (i.e. parent.getComponentIndex(rightReferenceComponent) == 2) * // Bottom inset has no reference component * // Left inset has component with index 0 as a reference component. * } *

* @param parent * @param withLabels * @return */ public String getReferenceComponentIndicesAsString(Container parent, boolean withLabels) { fixDependencies(parent); StringBuilder sb = new StringBuilder(); if (withLabels) { if (top().referenceComponent != null && top().referenceComponent.getParent() != null) { Component cmp = top().referenceComponent; sb.append("top:").append(cmp.getParent().getComponentIndex(cmp)).append("; "); } else { sb.append("top:-1; "); } if (right().referenceComponent != null && right().referenceComponent.getParent() != null) { Component cmp = right().referenceComponent; sb.append("right:").append(cmp.getParent().getComponentIndex(cmp)).append("; "); } else { sb.append("right:-1; "); } if (bottom().referenceComponent != null && bottom().referenceComponent.getParent() != null) { Component cmp = bottom().referenceComponent; sb.append("bottom:").append(cmp.getParent().getComponentIndex(cmp)).append("; "); } else { sb.append("bottom:-1; "); } if (left().referenceComponent != null && left().referenceComponent.getParent() != null) { Component cmp = left().referenceComponent; sb.append("left:").append(cmp.getParent().getComponentIndex(cmp)).append("; "); } else { sb.append("left:-1"); } } else { if (top().referenceComponent != null && top().referenceComponent.getParent() != null) { Component cmp = top().referenceComponent; sb.append(cmp.getParent().getComponentIndex(cmp)).append(" "); } else { sb.append("-1 "); } if (right().referenceComponent != null && right().referenceComponent.getParent() != null) { Component cmp = right().referenceComponent; sb.append(cmp.getParent().getComponentIndex(cmp)).append(" "); } else { sb.append("-1 "); } if (bottom().referenceComponent != null && bottom().referenceComponent.getParent() != null) { Component cmp = bottom().referenceComponent; sb.append(cmp.getParent().getComponentIndex(cmp)).append(" "); } else { sb.append("-1 "); } if (left().referenceComponent != null && left().referenceComponent.getParent() != null) { Component cmp = left().referenceComponent; sb.append(cmp.getParent().getComponentIndex(cmp)).append(" "); } else { sb.append("-1"); } } return sb.toString(); } /** * Sets the reference components of the insets of this constraint as indices of the provided parent * container. * @param parent The parent container whose children are to be used as reference components. * @param indices The indices to set as the reference components. *

The string format * may be either using labels following the same output format of {@literal cnst.getReferenceComponentIndicesAsString(true)} * or as a space-delimited string (e.g. {@literal cnst.getReferenceComponentIndicesAsString(false)}. When using the label * format, you may provide one or more inset values in the string. E.g. the following are all acceptable:

*

*

  • {@literal top:-1; left:0; right:0; bottom:1}
  • *
  • {@literal left:1}
  • *
  • {@literal left:10; right:-1}
  • *
*

*

* If you provide the positions as a space-delimited string, then they are expected to follow the same format * as is used in CSS for providing margin. To summarize: *

*

* {@code //Set component at index 0 as reference for all 4 insets. 0 //vertical insets use component index 2 | horizontal insets use component index 1 2 1 // top | horizontal | bottom -1 3 10 // top | right | bottom | left -1 -1 -1 -1 } * *

*

Note: An index of {@literal -1} means that the corresponding inset has no reference component.

* @return */ public LayeredLayoutConstraint setReferenceComponentIndices(Container parent, String indices) { indices = indices.trim(); LayeredLayoutConstraint cnst = this; if (indices.indexOf(":") != -1) { String[] parts = Util.split(indices, ";"); for (String part : parts) { if (part.trim().length() == 0) { continue; } String[] kv = Util.split(part, ":"); String key = kv[0].trim(); String val = kv[1].trim(); if ("top".equals(key)) { int index = Integer.parseInt(val); if (index >= 0) { cnst.top().referenceComponent = parent.getComponentAt(index); } else { cnst.top().referenceComponent = null; } } else if ("bottom".equals(key)) { int index = Integer.parseInt(val); if (index >= 0) { cnst.bottom().referenceComponent = parent.getComponentAt(index); } else { cnst.bottom().referenceComponent = null; } } else if ("left".equals(key)) { int index = Integer.parseInt(val); if (index >= 0) { cnst.left().referenceComponent = parent.getComponentAt(index); } else { cnst.left().referenceComponent = null; } } else if ("right".equals(key)) { int index = Integer.parseInt(val); if (index >= 0) { cnst.right().referenceComponent = parent.getComponentAt(index); } else { cnst.right().referenceComponent = null; } } } } else { String[] parts = Util.split(indices, " "); if (parts.length == 1) { int i0 = Integer.parseInt(parts[0]); if (i0 == -1) { top().referenceComponent = null; right().referenceComponent = null; bottom().referenceComponent = null; left().referenceComponent = null; } else { Component cmp = parent.getComponentAt(i0); top().referenceComponent = cmp; right().referenceComponent = cmp; bottom().referenceComponent = cmp; left().referenceComponent = cmp; } } else if (parts.length == 2) { int i0 = Integer.parseInt(parts[0]); int i1 = Integer.parseInt(parts[1]); Component cmp = null; if (i0 != -1) { cmp = parent.getComponentAt(i0); } top().referenceComponent = cmp; bottom().referenceComponent = cmp; cmp = null; if (i1 != -1) { cmp = parent.getComponentAt(i1); } left().referenceComponent = cmp; right().referenceComponent = cmp; } else if (parts.length == 3) { int i0 = Integer.parseInt(parts[0]); int i1 = Integer.parseInt(parts[1]); int i2 = Integer.parseInt(parts[2]); Component cmp = null; if (i0 != -1) { cmp = parent.getComponentAt(i0); } top().referenceComponent = cmp; cmp = null; if (i1 != -1) { cmp = parent.getComponentAt(i1); } left().referenceComponent = cmp; right().referenceComponent = cmp; cmp = null; if (i2 != -1) { cmp = parent.getComponentAt(i2); } bottom().referenceComponent = cmp; } else if (parts.length == 4) { int i0 = Integer.parseInt(parts[0]); int i1 = Integer.parseInt(parts[1]); int i2 = Integer.parseInt(parts[2]); int i3 = Integer.parseInt(parts[3]); Component cmp = null; if (i0 != -1) { cmp = parent.getComponentAt(i0); } top().referenceComponent = cmp; cmp = null; if (i1 != -1) { cmp = parent.getComponentAt(i1); } right().referenceComponent = cmp; cmp = null; if (i2 != -1) { cmp = parent.getComponentAt(i2); } bottom().referenceComponent = cmp; cmp = null; if (i3 != -1) { cmp = parent.getComponentAt(i3); } left().referenceComponent = cmp; } } return this; } /** * Gets the insets of this constraint as a string. If {@literal withLabels} is {@literal true}, then it * will return a string of the format: *

{@literal top:2mm; right:0; bottom:10%; left:auto}

*

If {@literal withLabels} is {@literal false} then it will return a space-delimited string with * the inset values ordered "top right bottom left" (the same as for CSS margins) order.

* * @param withLabels * @return */ public String getInsetsAsString(boolean withLabels) { StringBuilder sb = new StringBuilder(); if (withLabels) { sb.append("top:").append(top().getValueAsString()).append("; ") .append("right:").append(right().getValueAsString()).append("; ") .append("bottom:").append(bottom().getValueAsString()).append("; ") .append("left:").append(left().getValueAsString()); } else { sb.append(top().getValueAsString()).append(" ") .append(right().getValueAsString()).append(" ") .append(bottom().getValueAsString()).append(" ") .append(left().getValueAsString()); } return sb.toString(); } /** * Sets the reference components for the constraint. * @param refs May contain 1, 2, 3, or 4 values. If only 1 value is passed, then it is * set on all 4 insets. If two values are passed, then the first is set on the top and bottom * insets, and the 2nd is set on the left and right insets (i.e. vertical | horizontal). * If 3 values are passed, then, they are used for top, horizontal, and bottom. * If 4 values are passed, then they are used for top, right, bottom, left (in that order). * @return Self for chaining. */ public LayeredLayoutConstraint setReferenceComponents(Component... refs) { if (refs.length == 1) { top().referenceComponent = refs[0]; right().referenceComponent = refs[0]; bottom().referenceComponent = refs[0]; left().referenceComponent = refs[0]; } else if (refs.length == 2) { top().referenceComponent = refs[0]; bottom().referenceComponent = refs[0]; left().referenceComponent = refs[1]; right().referenceComponent = refs[1]; } else if (refs.length == 3) { top().referenceComponent = refs[0]; left().referenceComponent = refs[1]; right().referenceComponent = refs[1]; bottom().referenceComponent = refs[2]; } else if (refs.length == 4) { top().referenceComponent = refs[0]; right().referenceComponent = refs[1]; bottom().referenceComponent = refs[2]; left().referenceComponent = refs[3]; } return this; } /** * Sets the reference positions for the constraint. *

Interpretation of Reference Positions:

*

When an inset includes a reference component, that means that the inset is "anchored" to that * reference component. I.e. An inset of {@literal 1mm} is measured 1mm from the outer edge of the * reference component. By default it chooses the edge of on the same side as the inset. So * if this is a "left" inset, then it will measure against the "left" outer edge of the reference component. * This is the meaning of a {@literal 0} value for the associated reference positions.

* *

A reference position of {@literal 1.0} will start measuring from the opposite edge. So for a "left" inset, * it will measure from the "right" outer edge of the reference component. You can choose any real value for the * reference position, and it will cause the measurement to be scaled accordingly. E.g. {@literal 0.5.} would measure * from the center point of the reference component.

* * @param p May contain 1, 2, 3, or 4 values. If only 1 value is passed, then it is * set on all 4 insets. If two values are passed, then the first is set on the top and bottom * insets, and the 2nd is set on the left and right insets (i.e. vertical | horizontal). * If 3 values are passed, then, they are used for top, horizontal, and bottom. * If 4 values are passed, then they are used for top, right, bottom, left (in that order). * @return Self for chaining. */ public LayeredLayoutConstraint setReferencePositions(float... p) { if (p.length == 1) { for (Inset i : insets) { i.referencePosition = p[0]; } } else if (p.length == 2) { for (Inset i : insets) { switch (i.side) { case Component.TOP: case Component.BOTTOM: i.referencePosition = p[0]; break; default: i.referencePosition = p[1]; } } } else if (p.length == 3) { for (Inset i : insets) { switch (i.side) { case Component.TOP: i.referencePosition = p[0]; break; case Component.LEFT: case Component.RIGHT: i.referencePosition = p[1]; break; default: i.referencePosition = p[2]; } } } else if (p.length == 4) { for (Inset i : insets) { switch (i.side) { case Component.TOP: i.referencePosition = p[0]; break; case Component.RIGHT: i.referencePosition = p[1]; break; case Component.BOTTOM: i.referencePosition = p[2]; break; default: i.referencePosition = p[3]; } } } return this; } /** * Sets the insets for this constraint as a string. The string may include labels * or it may be a space delimited string of values with "top right bottom left" order. * *

* If providing as a space-delimited string of inset values, then you can provide 1, 2, 3, or 4 * values. If only 1 value is passed, then it is * set on all 4 insets. If two values are passed, then the first is set on the top and bottom * insets, and the 2nd is set on the left and right insets (i.e. vertical | horizontal). * If 3 values are passed, then, they are used for top, horizontal, and bottom. * If 4 values are passed, then they are used for top, right, bottom, left (in that order). *

*

* Example Inputs *

*

*

    *
  • {@literal "0 0 0 0"} = all 4 insets are zero pixels
  • *
  • {@literal "0 1mm"} = Vertical insets are zero. Horizontal insets are 1mm
  • *
  • {@literal "10% auto 20%"} = Top inset is 10%. Horizontal insets are flexible. Bottom is 20%
  • *
  • {@literal "1mm 2mm 3mm 4mm"} = Top=1mm, Right=2mm, Bottom=3mm, Left=4mm
  • *
*

* @param insetStr * @return Self for chaining. */ public LayeredLayoutConstraint setInsets(String insetStr) { LayeredLayoutConstraint cnst = this; if (insetStr.indexOf(":") != -1) { String[] parts = Util.split(insetStr, ";"); for (String part : parts) { if (part.trim().length() == 0) { continue; } String[] kv = Util.split(part, ":"); String key = kv[0].trim(); String val = kv[1].trim(); if ("top".equals(key)) { cnst.top().setValue(val); } else if ("bottom".equals(key)) { cnst.bottom().setValue(val); } else if ("left".equals(key)) { cnst.left().setValue(val); } else if ("right".equals(key)) { cnst.right().setValue(val); } } } else { String[] parts = Util.split(insetStr, " "); if (parts.length == 1) { top().setValue(parts[0]); right().setValue(parts[0]); bottom().setValue(parts[0]); left().setValue(parts[0]); } else if (parts.length == 2) { top().setValue(parts[0]); bottom().setValue(parts[0]); left().setValue(parts[1]); right().setValue(parts[1]); } else if (parts.length == 3) { top().setValue(parts[0]); left().setValue(parts[1]); right().setValue(parts[1]); bottom().setValue(parts[2]); } else if (parts.length == 4) { top().setValue(parts[0]); right().setValue(parts[1]); bottom().setValue(parts[2]); left().setValue(parts[3]); } } return this; } /** * Gets the left inset. * @return The left inset */ public Inset left() { return insets[Component.LEFT]; } /** * Gets the right inset. * @return The right inset. */ public Inset right() { return insets[Component.RIGHT]; } /** * Gets the top inset * @return The top inset */ public Inset top() { return insets[Component.TOP]; } /** * Gets the bottom inset. * @return The bottom inset */ public Inset bottom() { return insets[Component.BOTTOM]; } /** * Sets the anchor used for left and right percentage insets. An anchor * of {@literal 0} points to the component's edge which is on that side * the inset refers to (e.g. in case of the left inset the left edge). * An anchor of {@literal 1} points to the edge on the opposite side. * By default {@literal 0} is used as anchor. * * @param anchor * @return Self for chaining */ public LayeredLayoutConstraint setPercentInsetAnchorHorizontal(float anchor) { this.percAnchorH = anchor; return this; } /** * Sets the anchor used for top and bottom percentage insets. An anchor * of {@literal 0} points to the component's edge which is on that side * the inset refers to (e.g. in case of the top inset the top edge). * An anchor of {@literal 1} points to the edge on the opposite side. * By default {@literal 0} is used as anchor. * * @param anchor * @return Self for chaining */ public LayeredLayoutConstraint setPercentInsetAnchorVertical(float anchor) { this.percAnchorV = anchor; return this; } /** * @return anchor used for left and right percentage insets */ public float getPercentInsetAnchorHorizontal() { return this.percAnchorH; } /** * @return anchor used for top and bottom percentage insets */ public float getPercentInsetAnchorVertical() { return this.percAnchorV; } /** * Gets the constraint itself. * @return */ public LayeredLayoutConstraint constraint() { return this; } /** * The insets for this constraint. */ private final Inset[] insets = new Inset[]{ new Inset(Component.TOP), new Inset(Component.LEFT), new Inset(Component.BOTTOM), new Inset(Component.RIGHT) }; /** * Anchors used for percentage insets */ private float percAnchorH = 0f; private float percAnchorV = 0f; //private Rectangle preferredBounds; /** * Gets the dependencies (i.e. recursively gets all reference components). * @param deps A set to add the dependencies to. (An "out" parameter). * @return The set of dependencies. Same as {@literal dep} parameter. */ public Set getDependencies(Set deps) { for (Inset inset : insets) { inset.getDependencies(deps); } return deps; } /** * Gets the dependencies (i.e. recursively gets all reference components). * @return The set of dependencies. */ public Set getDependencies() { return getDependencies(new HashSet()); } /** * Checks to see if this constraint has the given component in its set of dependencies. * @param cmp The component to check. * @return True if {@literal cmp} is a reference component of some inset in this * constraint (recursively). */ public boolean dependsOn(Component cmp) { return getDependencies().contains(cmp); } /** * Encapsulates an inset. */ public class Inset { int delta; /** * Creates a new inset for the given side. * @param side One of {@link Component#TOP}, {@link Component#BOTTOM}, {@link Component#LEFT}, or {@link Component#RIGHT}. */ public Inset(int side) { this.side = side; } /** * Prints this inset as a string. * @return */ public String toString() { switch (side) { case Component.TOP : return "top="+getValueAsString(); case Component.BOTTOM: return "bottom="+getValueAsString(); case Component.LEFT: return "left="+getValueAsString(); default: return "right="+getValueAsString(); } } /** * Gets the value of this inset as a string. Values will be in the format {@literal }, e.g. * {@literal 2mm}, {@literal 15%}, {@literal 5px}, {@literal auto} (meaning it is flexible. * @return The value of this inset as a string. */ public String getValueAsString() { switch (unit) { case UNIT_DIPS: return value +"mm"; case UNIT_PIXELS: return ((int)value)+"px"; case UNIT_PERCENT: return value + "%"; case UNIT_AUTO: return "auto"; case UNIT_BASELINE: return "baseline"; } return null; } /** * Gets the value of this inset as a string rounding to the specified number of decimal places. * Values will be in the format {@literal }, e.g. * {@literal 2mm}, {@literal 15%}, {@literal 5px}, {@literal auto} (meaning it is flexible. * @return The value of this inset as a string. * @see #getValueAsString() */ public String getValueAsString(int decimalPlaces) { L10NManager l10n = L10NManager.getInstance(); switch (unit) { case UNIT_DIPS: return l10n.format(value, decimalPlaces) +"mm"; case UNIT_PIXELS: return ((int)value)+"px"; case UNIT_PERCENT: return l10n.format(value, decimalPlaces) + "%"; case UNIT_AUTO: return "auto"; case UNIT_BASELINE: return "baseline"; } return null; } /** * Fixes dependencies in this inset recursively so that all reference * components are children of the given parent container. If a reference * component is not in the parent, then it will first check to find a * child of {@literal parent} with the same name as the reference component. * Failing that, it will try to find a child of {@literal parent} with the * same index. * * If an appropriate match is found, it will replace the referenceComponent * with the match. * * * @param parent The container in which all reference components should reside. * @return Self for chaining. */ private Inset fixDependencies(Container parent) { Container refParent; if (referenceComponent != null && (refParent = referenceComponent.getParent()) != parent) { // The reference component is not in this parent String name = referenceComponent.getName(); boolean found = false; if (name != null && name.length() > 0) { for (Component child : parent) { if (name.equals(child.getName())) { referenceComponent = child; found = true; break; } } } if (!found && refParent != null) { int index = refParent.getComponentIndex(referenceComponent); if (index != -1 && parent.getComponentCount() > index) { referenceComponent = parent.getComponentAt(index); found = true; } } if (found) { LayeredLayoutConstraint refCnst = getOrCreateConstraint(referenceComponent); refCnst.getInset(side).fixDependencies(parent); } } return this; } /** * Sets the value of this inset as a string. E.g. "2mm", or "2px", or "3%", "auto", or "baseline". * @param value The value of this inset. * @return Self for chaining. */ public Inset setValueAsString(String value) { setValue(value); return this; } /** * Gets the left inset in this constraint. * @return The left inset of the constraint. */ public Inset left() { return constraint().left(); } /** * Gets the right inset in the constraint. * @return The right inset of the constraint. */ public Inset right() { return constraint().right(); } /** * Gets the top inset in this constraint. * @return The top inset in this constraint. */ public Inset top() { return constraint().top(); } /** * Gets the bottom inset in this constraint. * @return The bottom inset. */ public Inset bottom() { return constraint().bottom(); } /** * Gets the constraint that contains this inset. * @return The parent constraint of this inset. */ public LayeredLayoutConstraint constraint() { return LayeredLayoutConstraint.this; } /** * Sets the unit for this constraint. This doesn't perform any recalculation * on the value. Just sets the unit. * @param unit The unit. One of {@link #UNIT_AUTO}, {@link #UNIT_DIPS}, {@link #UNIT_PIXELS}, or {@link #UNIT_PERCENT}. * @return Self for chaining. * @see #setAuto() * @see #setDips() * @see #setPixels() * @see #setPercent() * @see #changeUnits(byte) To change units while recalculating the value to be effectively equivalent. */ public Inset unit(byte unit) { if (unit == UNIT_BASELINE && side != Component.TOP) { throw new IllegalArgumentException("baseline unit can only be used on the top inset."); } this.unit = unit; if (unit == UNIT_BASELINE) { referencePosition = 0f; getOppositeInset().setAuto().referenceComponent(null).referencePosition(0f); } return this; } /** * Sets the units to "auto" (i.e. makes the inset flexible). Doesn't perform any calculations * on the value. * @return Self for chaining. * @see #unit(byte) * */ public Inset setAuto() { return unit(UNIT_AUTO); } /** * Sets the units to "dips" (millimetres). Doesn't perform any calculations on the value. * @return Self for chaining. * @see #unit(byte) */ public Inset setDips() { return unit(UNIT_DIPS); } /** * Sets the units to percent. Doesn't perform any calculations on the value. * @return Self for chaining. * @see #unit(byte) */ public Inset setPercent() { return unit(UNIT_PERCENT); } /** * Sets the units to pixels. Doesn't perform any calculations on the value. * @return Self for chaining. */ public Inset setPixels() { return unit(UNIT_PIXELS); } /** * Sets the inset value to the provided number of pixels. This will chnage the unit * to pixels. * @param px The pixel value of the inset. * @return Self for chaining. */ public Inset setPixels(int px) { this.value = px; return unit(UNIT_PIXELS); } /** * Sets the inset value to the provided dips/millimetre value. This will change * the unit to millimetres. * @param dips The inset value in millimetres. * @return Self for chaining. */ public Inset setDips(float dips) { this.value = dips; return unit(UNIT_DIPS); } /** * Sets the inset value in percentage. This will change the unit to percentage. * @param percent The inset value as a percentage. * @return Self for chaining. */ public Inset setPercent(float percent) { if (percent == Float.POSITIVE_INFINITY || percent == Float.NEGATIVE_INFINITY) { throw new IllegalArgumentException("Attempt to set illegal percent value"); } this.value = percent; return unit(UNIT_PERCENT); } /** * Sets the reference component for this inset. * @param cmp The reference component. (I.e. the component that the inset is "anchored" to). * @return Self for chaining. * @see #referencePosition(float) * @see LayeredLayoutConstraint#setReferenceComponents(com.codename1.ui.Component...) */ public Inset referenceComponent(Component cmp) { referenceComponent = cmp; return this; } /** * Sets the reference position for this inset. A value of {@literal 0} indicates that the inset * is anchored to the same side of the reference component (e.g. right inset anchored to right edge of reference component, * left inset anchored to left edge of reference component). A value of {@literal 1} indicates that the * inset is anchored to the opposite side of the reference component. E.g. right inset to left edge. * @param position The reference position. * @return Self for chaining. * @see #setReferencePositions(java.lang.String) * @see #setReferencePositions(com.codename1.ui.Component, java.lang.String) * @see #setReferencePositions(com.codename1.ui.Component, float...) */ public Inset referencePosition(float position) { this.referencePosition = position; return this; } /** * Sets the value of this inset. The interpretation of the value will depend on the {@link #unit}. * If the unit is {@link #UNIT_DIPS}, then this value is interpreted in millimetres, etc.. * @param value The value to set this inset to. * @return Self for chaining. */ public Inset value(float value) { this.value = value; return this; } /** * Gets the side of this inset. One of {@link Component#TOP}, {@link Component#Bottom}, {@link Component#LEFT}, {@link Component#RIGHT} * @return The side of this inset. One of {@link Component#TOP}, {@link Component#Bottom}, {@link Component#LEFT}, {@link Component#RIGHT} */ public int getSide() { return side; } /** * Gets the reference component for this inset. * @return The reference component for this inset. * @see #referenceComponent(com.codename1.ui.Component) */ public Component getReferenceComponent() { return referenceComponent; } /** * Gets the reference position for this inset. * @return The reference position for this inset. */ public float getReferencePosition() { return referencePosition; } /** * One of * {@link Component#TOP}, {@link Component#Bottom}, {@link Component#LEFT}, {@link Component#RIGHT} */ private int side; /** * The component that is used a reference for this inset. * {@literal null} for the parent component. */ private Component referenceComponent; /** * {@code 0.0 } = left/top of {@link #referenceComponent}. {@code 1.0 } for bottom/right or {@link #referenceComponent}. */ private float referencePosition; /** * The value of this inset. Interpreted in {@link #unit} units. */ private float value; /** * The unit of this inset. */ private byte unit = UNIT_PIXELS; /** * Caches the preferred value of this inset last time it was calculated. */ private int preferredValue; /** * The calculated value of this inset in pixels. This is calculated in the {@link #calculate(com.codename1.ui.Component, int, int, int, int) } * method which is only called during layout. It will be the absolute size of the inset in pixels * including all reference components. */ private int calculatedValue; /** * The calculated base value of this inset in pixels. This is calculated during the layout step, so * this will always be the pixel "base" value the last time layout was performed. The base value * is the absolute value of the reference box inset. E.g. if this inset has no reference component, * then this will always be zero. If there is a reference componnet, then this will be the value * of the "zero" point for measuing the inset. {@link #calculatedValue} - {@link #calculatedBaseValue} should * be equal to {@link #value} (if value is in pixels). */ private int calculatedBaseValue; /** * Tracks whether the size of the component was clipped during the last layout. This will occur * when the preferred size of the component would have it overflowing the reference box. In such cases * the component is "clipped" to not obtain its full preferred value. */ private boolean autoIsClipped; /** * Calculate the preferred value of this inset. * @param parent The parent container. * @param cmp The component * @return The preferred value of this inset in pixels. */ public int calcPreferredValue(Container parent, Component cmp) { if (referenceComponent == null) { // There is no reference component for this inset so we measure // against the parent component directly. switch (unit) { case UNIT_PIXELS: preferredValue = (int) value; break; case UNIT_DIPS: preferredValue = Display.getInstance().convertToPixels(value); break; case UNIT_PERCENT: preferredValue = 0; break; case UNIT_AUTO: preferredValue = 0; break; case UNIT_BASELINE: preferredValue = 0; break; default: throw new RuntimeException("Invalid unit " + unit); } return preferredValue; } else { // There is a reference component so we need to add our own value // to the base inset of the reference component. LayeredLayoutConstraint refCnst = (LayeredLayoutConstraint) getComponentConstraint(referenceComponent); int baseValue = 0; if (refCnst != null) { baseValue = refCnst.insets[side].preferredValue; } // We should have already calculated the preferred size of the // reference component. //Dimension refPreferredSize = referenceComponent.getPreferredSize(); int refPreferredH = getOuterPreferredH(referenceComponent); int refPreferredW = getOuterPreferredW(referenceComponent); if (referencePosition != 0) { // If the inset is not in reference to the edge of the component // then we need to adjust the base value accordingly. switch (side) { case Component.TOP: case Component.BOTTOM: baseValue += ((float) refPreferredH ) * referencePosition; break; default: baseValue += ((float) refPreferredW) * referencePosition; } } // Now we add our own value to the base value. switch (unit) { case UNIT_PIXELS: preferredValue = baseValue + (int) value; break; case UNIT_DIPS: preferredValue = baseValue + Display.getInstance().convertToPixels(value); break; case UNIT_PERCENT: preferredValue = baseValue; break; case UNIT_AUTO: preferredValue = baseValue; break; case UNIT_BASELINE: { Style rs = referenceComponent.getStyle(); Style s = cmp.getStyle(); preferredValue = baseValue + (referenceComponent.getPreferredH() - cmp.getPreferredH())/2 + (rs.getFont().getAscent() - s.getFont().getAscent()) + (rs.getPaddingTop() - s.getPaddingTop()); break; } default: throw new RuntimeException("Invalid unit " + unit); } return preferredValue; } } /** * Calculates the "base" value off of which the inset's value should be calculated. * * @param top The top "y" coordinate within the parent container from which insets are measured. * @param left The left "x" coordinate within the parent container from which insets are measured. * @param bottom The bottom "y" coordinate within the parent container from which insets are measured. * @param right The right "x" coordinate within the parent container from which insets are measured. * @return */ private int calcBaseValue(int top, int left, int bottom, int right) {//, int paddingTop, int paddingLeft, int paddingBottom, int paddingRight) { int h = bottom - top; int w = right - left; int baseValue = 0; if (referenceComponent != null) { switch (side) { case Component.TOP: baseValue = getOuterY(referenceComponent) + (int)(getOuterHeight(referenceComponent) * referencePosition) - top; break; case Component.BOTTOM: baseValue = (bottom - getOuterHeight(referenceComponent) - getOuterY(referenceComponent)) + (int)(getOuterHeight(referenceComponent) * referencePosition); break; case Component.LEFT: baseValue = getOuterX(referenceComponent) + (int)(getOuterWidth(referenceComponent) * referencePosition) - left; break; default: baseValue = (right - getOuterWidth(referenceComponent) - getOuterX(referenceComponent)) + (int)(getOuterWidth(referenceComponent)* referencePosition); break; } calculatedBaseValue = baseValue; return baseValue; } if (referencePosition != 0) { switch (side) { case Component.TOP: baseValue = (int) ((float) h * referencePosition); break; case Component.BOTTOM: baseValue = (int) ((float) h * referencePosition); break; case Component.LEFT: baseValue = (int) ((float) w * referencePosition); break; case Component.RIGHT: baseValue = (int) ((float) w * referencePosition); break; default: throw new RuntimeException("Illegal side for inset: " + side); } } calculatedBaseValue = baseValue; return baseValue; } /** * True if this is top or bottom. * @return */ private boolean isVerticalInset() { return side == Component.TOP || side == Component.BOTTOM; } /** * True if this is left or right. * @return */ private boolean isHorizontalInset() { return side == Component.LEFT || side == Component.RIGHT; } /** * Calculates the actual value of this inset. This is used inside {@link #layoutComponent(com.codename1.ui.Container, com.codename1.ui.Component, int, int, int, int) }. * * @param cmp The component. * @param top * @param left * @param bottom * @param right * @return The actual value of this inset. */ private int calculate(Component cmp, int top, int left, int bottom, int right) { if (side == Component.BOTTOM && getOppositeInset().unit == UNIT_BASELINE) { unit = UNIT_AUTO; } int w = right - left; int h = bottom - top; int baseValue = calcBaseValue(top, left, bottom ,right); autoIsClipped = false; switch (unit) { case UNIT_PIXELS: calculatedValue = baseValue + (int) value; break; case UNIT_DIPS: calculatedValue = baseValue + Display.getInstance().convertToPixels(value); break; case UNIT_PERCENT: { Inset oppositeInset = getOppositeInset(); int oppositeBaseValue = oppositeInset.calcBaseValue(top, left, bottom, right); if (isVerticalInset()) { float anchorV = LayeredLayoutConstraint.this.getPercentInsetAnchorVertical(); calculatedValue = (int)(baseValue + (h - oppositeBaseValue - baseValue) * value / 100f - (anchorV!=0? (getOuterPreferredH(cmp) * anchorV):0)); } else { float anchorH = LayeredLayoutConstraint.this.getPercentInsetAnchorHorizontal(); calculatedValue = (int)(baseValue + (w - oppositeBaseValue - baseValue) * value / 100f - (anchorH != 0 ? (getOuterPreferredW(cmp) * anchorH) : 0)); } break; } case UNIT_BASELINE: { if (getReferenceComponent() == null) { calculatedValue = baseValue; } else { Component ref = getReferenceComponent(); Style rs = ref.getStyle(); Style s = cmp.getStyle(); Font rf = rs.getFont(); Font sf = s.getFont(); int ra = rf == null || sf == null ? 0 : rf.getAscent(); int sa = rf == null || sf == null ? 0 : sf.getAscent(); calculatedValue = baseValue + (ref.getHeight() - cmp.getPreferredH())/2 + (rs.getPaddingTop() - s.getPaddingTop()) + (rs.getMarginTop() - s.getMarginTop()) + (ra - sa); } break; } case UNIT_AUTO: { Inset oppositeInset = getOppositeInset(); int oppositeBaseValue = oppositeInset.calcBaseValue(top, left, bottom, right); if (oppositeInset.unit == UNIT_AUTO) { if (isVerticalInset()) { if (cmp.getPreferredH() <= 0) { calculatedValue = baseValue; autoIsClipped = true; } else { calculatedValue = baseValue + (h - oppositeBaseValue - baseValue - getOuterPreferredH(cmp))/2; } } else { if (cmp.getPreferredW() <= 0) { calculatedValue = baseValue; autoIsClipped = true; } else { calculatedValue = baseValue + (w - oppositeBaseValue - baseValue - getOuterPreferredW(cmp))/2; } } if (calculatedValue < 0) { autoIsClipped = true; } calculatedValue = Math.max(0, calculatedValue); } else { if (isVerticalInset()) { if (cmp.getPreferredH() <= 0) { calculatedValue = baseValue; autoIsClipped = true; } else { calculatedValue = h - oppositeInset.calculate(cmp, top, left, bottom, right) - getOuterPreferredH(cmp); } } else { if (cmp.getPreferredW() <= 0) { calculatedValue = baseValue; autoIsClipped = true; } else { calculatedValue = w - oppositeInset.calculate(cmp, top, left, bottom, right) - getOuterPreferredW(cmp); } } if (calculatedValue < 0) { autoIsClipped = true; } calculatedValue = Math.max(0, calculatedValue); } break; } default: throw new RuntimeException("Invalid unit " + unit); } delta = 0; return calculatedValue; } /** * Recursively gets all of the reference components of this inset. * @param deps An "out" parameter. The set that will hold the dependencies. * @return The set of all reference components (crawled recursively of this inset. */ public Set getDependencies(Set deps) { if (referenceComponent != null) { if (deps.contains(referenceComponent)) { return deps; } deps.add(referenceComponent); getOrCreateConstraint(referenceComponent).getDependencies(deps); } return deps; } /** * Recursively gets all of the reference components of this inset. * @return The set of all reference components (crawled recursively of this inset. */ public Set getDependencies() { return getDependencies(new HashSet()); } /** * Gets the opposite inset of this inset within its parent constraint. E.g. if this is the * left inset, it will get the associated right inset. * @return The opposite inset. */ public Inset getOppositeInset() { LayeredLayoutConstraint cnst = LayeredLayoutConstraint.this; if (cnst != null) { int oppSide = 0; switch (side) { case Component.TOP: oppSide = Component.BOTTOM; break; case Component.BOTTOM: oppSide = Component.TOP; break; case Component.LEFT: oppSide = Component.RIGHT; break; default: oppSide = Component.LEFT; } return cnst.insets[oppSide]; } return null; } /** * Sets the value of this inset. E.g. "2mm", "1px", "25%", or "auto". * @param val */ private void setValue(String val) { int pos; if ((pos=val.indexOf("mm")) != -1) { this.setDips(Float.parseFloat(val.substring(0, pos))); } else if ((pos=val.indexOf("px")) != -1) { this.setPixels(Integer.parseInt(val.substring(0, pos))); } else if ((pos=val.indexOf("%")) != -1) { this.setPercent(Float.parseFloat(val.substring(0, pos))); } else if ("auto".equals(val)) { this.setAuto(); } else if ("baseline".equals(val)) { this.unit(UNIT_BASELINE); } else { this.setPixels(Integer.parseInt(val)); } } /** * Copies this inset into another inset. * @param dest The inset to copy to. * @return The copied inset. */ public Inset copyTo(Inset dest) { dest.autoIsClipped = autoIsClipped; dest.calculatedValue = calculatedValue; dest.delta = delta; dest.calculatedBaseValue = calculatedBaseValue; dest.preferredValue = preferredValue; dest.value = value; dest.unit = unit; // We won't copy the side since that allows us to f things up //dest.side = side; dest.referenceComponent = referenceComponent; dest.referencePosition = referencePosition; return dest; } /** * Copies this inset to the corresponding inset of the provided constraint. * @param dest The constraint to copy the inset into. * @return The corresponding inset in {@literal dest} that we copied the inset into. */ public Inset copyTo(LayeredLayoutConstraint dest) { copyTo(dest.insets[side]); return dest.insets[side]; } /** * Copies this inset into the corresponding inset of the provided component. * @param cmp The component that we are copying the inset into. * @return The copied inset. */ public Inset copyTo(Component cmp) { copyTo(getOrCreateConstraint(cmp)); return this; } /** * Creates a copy of this inset. * @return */ public Inset copy() { return copyTo(new Inset(side)); } /** * Gets the unit of this inset. * @return One of {@link #UNIT_AUTO}, {@link #UNIT_DIPS}, {@link #UNIT_PIXELS}, or {@link #UNIT_PERCENT}. */ public byte getUnit() { return unit; } /** * Checks if this is a fixed inset. An inset is considered "fixed" if its unit is not {@link #UNIT_AUTO} * @return True if the inset is fixed. */ public boolean isFixed() { return unit != UNIT_AUTO; } /** * Gets the current value of this inset in millimetres. If the inset uses a different unit, then * this will calculate the corresponding value. * @return */ public float getCurrentValueMM() { if (unit == UNIT_DIPS) { return value; } else if (unit == UNIT_PIXELS) { float pixelsPerDip = Display.getInstance().convertToPixels(1000)/1000f; return value / pixelsPerDip; } else { // In both auto and percent cases, we'll use the existing calculated value as our base float pixelsPerDip = Display.getInstance().convertToPixels(1000)/1000f; int calc = calculatedValue; //System.out.println("Calculated value of side "+side+" = "+calc); //new RuntimeException("Foobar").printStackTrace(); if (referenceComponent != null) { calc -= calculatedBaseValue; } float out = calc / pixelsPerDip; //System.out.println("calc="+out+"mm"); return out + (delta / pixelsPerDip); } } /** * Gets the current value of this inset in pixels. If the inset uses a different unit * then this will calculate the corresponding value. * @return The value of this inset in pixels. */ public int getCurrentValuePx() { if (unit == UNIT_DIPS) { return Display.getInstance().convertToPixels(value); } else if (unit == UNIT_PIXELS) { return (int)value; } else { // In both auto and percent cases, we'll use the existing calculated value as our source. int calc = calculatedValue; if (referenceComponent != null) { calc -= calculatedBaseValue; } return calc + delta; } } /** * True if this is a vertical inset (top or bottom). * @return */ public boolean isVertical() { return side == Component.TOP || side == Component.BOTTOM; } /** * True if this is a horizontal inset (left or right). * @return */ public boolean isHorizontal() { return side == Component.LEFT || side == Component.RIGHT; } /** * Changes the units of this inset, and updates the value to remain * the same as the current value. * @param unit The unit. One of {@link #UNIT_AUTO}, {@link #UNIT_DIPS}, {@link #UNIT_PIXELS}, or {@link #UNIT_PERCENT}. * @return Self for chaining. * @deprecated Use {@link #changeUnitsTo(byte, com.codename1.ui.Container) } */ public Inset changeUnits(byte unit) { return changeUnits(unit, cmp); } /** * Changes the units of this inset, and updates the value to remain * the same as the current value. * @param unit The unit. One of {@link #UNIT_AUTO}, {@link #UNIT_DIPS}, {@link #UNIT_PIXELS}, or {@link #UNIT_PERCENT}. * @param cmp The component for which the inset is applying. * @return Self for chaining. * @deprecated Use {@link #changeUnitsTo(byte, com.codename1.ui.Container) } */ public Inset changeUnits(byte unit, Component cmp) { return changeUnitsTo(unit, cmp == null ? null : cmp.getParent()); } /** * Changes the units of this inset, and updates the value to remain * the same as the current value. * @param unit The unit. One of {@link #UNIT_AUTO}, {@link #UNIT_DIPS}, {@link #UNIT_PIXELS}, or {@link #UNIT_PERCENT}. * @param parent The container in which the layout applies. * @return Self for chaining. */ public Inset changeUnitsTo(byte unit, Container parent) { if (unit != this.unit) { if (unit == UNIT_PIXELS) { setPixels(getCurrentValuePx()); } else if (unit == UNIT_DIPS) { setDips(getCurrentValueMM()); } else if (unit == UNIT_PERCENT) { try { if (parent != null) { Rectangle refBox = constraint().getReferenceBox(parent); setPercent(getCurrentValuePx() * 100f / (isVertical()?refBox.getHeight() : refBox.getWidth())); } else { throw new IllegalArgumentException("Cannot change unit to percent without specifying the target component."); } } catch (IllegalArgumentException ex) { Log.p("Unable to calculate percentage because height or width is zero. Setting to 100%"); setPercent(100f); } } else if (unit == UNIT_BASELINE) { if (side != Component.TOP) { throw new IllegalArgumentException("Baseline unit only allowed on top inset"); } getOppositeInset().changeUnitsTo(UNIT_AUTO, parent); unit(unit); } else { unit(unit); } } return this; } public Inset changeUnitsTo(byte unit, Rectangle refBox) { if (unit != this.unit) { if (unit == UNIT_PIXELS) { setPixels(getCurrentValuePx()); } else if (unit == UNIT_DIPS) { setDips(getCurrentValueMM()); } else if (unit == UNIT_PERCENT) { try { setPercent(getCurrentValuePx() * 100f / (isVertical()?refBox.getHeight() : refBox.getWidth())); } catch (IllegalArgumentException ex) { Log.p(ex.getMessage()); setPercent(100f); } } else if (unit == UNIT_BASELINE) { if (side != Component.TOP) { throw new IllegalArgumentException("Baseline unit only allowed on top inset"); } getOppositeInset().changeUnitsTo(UNIT_AUTO, refBox); unit(unit); } else { unit(unit); } } return this; } /** * Changes the reference component, while updating the value to remain in the same * absolute position. * @param parent The parent container. * @param newRef The new reference component. * @param pos The reference position. * @return Self for chaining. */ public Inset changeReference(Container parent, Component newRef, float pos) { if (newRef != null) { LayeredLayoutConstraint refCnst = getOrCreateConstraint(newRef); if (cmp != null && refCnst.dependsOn(cmp)) { throw new IllegalArgumentException("Attempted to set a reference that would produce a circular dependency in LayeredLayout"); } } //if (isFlexible()) { // // This could potentially affect the opposite inset if it is a percentage // referenceComponent(newRef).referencePosition(pos); //} else { if (newRef != referenceComponent || pos != referencePosition) { // This may potentially affect both this inset // and the opposite inset if it is either flexible or // percent. if (unit == UNIT_BASELINE) { changeUnitsTo(UNIT_DIPS, parent); } byte restoreUnit = -1; if (unit == UNIT_PERCENT || isFlexible()) { restoreUnit = unit; changeUnitsTo(UNIT_DIPS, parent); } byte oppRestoreUnit = -1; if (getOppositeInset().unit == UNIT_PERCENT || isFlexible()) { oppRestoreUnit = getOppositeInset().unit; getOppositeInset().changeUnitsTo(UNIT_DIPS, parent); } LayeredLayoutConstraint cpy = constraint().copy(); cpy.insets[side].referenceComponent(newRef).referencePosition(pos); //Container parent = context.getParent(); Style s = parent.getStyle(); int top = s.getPaddingTop(); int bottom = parent.getLayoutHeight() - parent.getBottomGap() - s.getPaddingBottom(); int left = s.getPaddingLeft(parent.isRTL()); int right = parent.getLayoutWidth() - parent.getSideGap() - s.getPaddingRight(parent.isRTL()); int newBase = cpy.insets[side].calcBaseValue(top, left, bottom, right); int oldBase = calcBaseValue(top, left, bottom, right); referenceComponent(newRef).referencePosition(pos); calculatedBaseValue += (newBase - oldBase); calculatedValue += (newBase - oldBase); if (getOppositeInset().isFlexible()) { getOppositeInset().delta -= (newBase - oldBase); } translatePixels(oldBase - newBase, true, parent); if (restoreUnit >=0) { changeUnitsTo(restoreUnit, parent); } if (oppRestoreUnit >= 0) { getOppositeInset().changeUnitsTo(oppRestoreUnit, parent); } } //} return this; } /** * Checks if this is a flexible inset. An inset is considered flexible if its unit is {@link #UNIT_AUTO}. * @return True if this is a flexible inset. * @see #isFixed() */ public boolean isFlexible() { return unit == UNIT_AUTO; } /** * Returns the total inset of this inset when applied to the given component. * This will calculate and sum all of the insets of reference components to * get the total inset in pixels from the parent component. * @param cmp The component context. * @return The total inset in pixels from the parent. */ public int getAbsolutePixels(Component cmp) { Container parent = cmp.getParent(); Style s = parent.getStyle(); int top = s.getPaddingTop(); int bottom = parent.getLayoutHeight() - parent.getBottomGap() - s.getPaddingBottom(); int left = s.getPaddingLeft(parent.isRTL()); int right = parent.getLayoutWidth() - parent.getSideGap() - s.getPaddingRight(parent.isRTL()); int baseValue = calcBaseValue(top, left, bottom, right); //Rectangle baseRect = getReferenceBox(cmp.getParent(), cmp); switch (unit) { case UNIT_PIXELS : return baseValue + (int)value; case UNIT_DIPS : return baseValue + Display.getInstance().convertToPixels(value); case UNIT_PERCENT : { Rectangle baseRect = getReferenceBox(parent, cmp); //System.out.println("Baserect is "+baseRect+" baseValue="+baseValue+" for percent "+value); int out = (int)(baseValue + (isHorizontalInset() ? baseRect.getWidth() : baseRect.getHeight()) * value / 100f); //System.out.println("Result is "+out); return out; } case UNIT_BASELINE : { Component ref = getReferenceComponent(); if (ref == null) { return baseValue; } else { Style rs = ref.getStyle(); Style cs = cmp.getStyle(); Font rf = rs.getFont(); Font cf = cs.getFont(); int ra = rf == null || cf == null ? 0 : rf.getAscent(); int ca = rf == null || cf == null ? 0 : cf.getAscent(); return baseValue + (ref.getHeight()-cmp.getPreferredH())/2 + (rs.getPaddingTop() - cs.getPaddingTop()) + (rs.getMarginTop() - cs.getMarginTop()) + (ra - ca); } } case UNIT_AUTO : { Inset oppositeInset = getOppositeInset(); if (oppositeInset.unit == UNIT_AUTO) { Rectangle baseRect = getReferenceBox(parent, cmp); // they're both auto, //int oppositeBase = oppositeInset.calcBaseValue(top, left, bottom, right); if (isVerticalInset()) { return (baseRect.getHeight() - getOuterPreferredH(cmp)) / 2; } else { return (baseRect.getWidth() - getOuterPreferredW(cmp)) / 2; } } else { if (isVerticalInset()) { return bottom - top - oppositeInset.getAbsolutePixels(cmp) - baseValue - getOuterPreferredH(cmp); } else { //System.out.println("Checking opposite inset for value"); int out = right - left - oppositeInset.getAbsolutePixels(cmp) - baseValue - getOuterPreferredW(cmp); //System.out.println("Auto value is "+out); return out; } } } default : throw new RuntimeException("Illegal state in inset. Unknown unit "+unit); } } /** * Translates the inset by {@literal delta} pixels. * @param delta Pixels to translate this inset by. * @param preferMM If this is a flexible inset, then translating it will require changing it to fixed. {@literal true} to use millimetres. {@literal false} to use pixels. * @param parent The parent container used for calculating equivalent percent if this is a percent inset. * @return Self for chaining. * @see #translateMM(float, boolean, com.codename1.ui.Container) */ public Inset translatePixels(int delta, boolean preferMM, Container parent) { switch (unit) { case UNIT_PIXELS : value += delta; break; case UNIT_DIPS : { float pixelsPerDip = Display.getInstance().convertToPixels(1000)/1000f; //System.out.println("Old dips for side "+side+" = "+value); value += (delta / pixelsPerDip); //System.out.println("New dips for side "+side+" = "+value); break; } case UNIT_PERCENT: { //Container parent = cmp.getParent(); //Style parentStyle = parent.getStyle(); Style s = parent.getStyle(); int top = s.getPaddingTop(); int bottom = parent.getLayoutHeight() - parent.getBottomGap() - s.getPaddingBottom(); int left = s.getPaddingLeft(parent.isRTL()); int right = parent.getLayoutWidth() - parent.getSideGap() - s.getPaddingRight(parent.isRTL()); int baseValue = calculatedBaseValue; int oppositeBaseValue = getOppositeInset().calculatedBaseValue; if (isVerticalInset()) { float relH = bottom - top - baseValue - oppositeBaseValue; if (Math.abs(relH) < 1f) { return this; } float percentDelta = delta / (float)relH * 100f; if (percentDelta == Float.NEGATIVE_INFINITY || percentDelta == Float.POSITIVE_INFINITY) { percentDelta = 0f; } value += percentDelta; } else { float relH = right - left - baseValue - oppositeBaseValue; //System.out.println("relH="+relH+" delta="+delta); if (Math.abs(relH) < 1f) { return this; } float percentDelta = delta / relH * 100f; //System.out.println("percentDelta="+percentDelta); if (percentDelta == Float.NEGATIVE_INFINITY || percentDelta == Float.POSITIVE_INFINITY) { percentDelta = 0f; } value += percentDelta; //System.out.println("Value="+value); } break; } case UNIT_BASELINE : { changeUnitsTo(UNIT_DIPS, parent); return translatePixels(delta, preferMM, parent); } case UNIT_AUTO : { // If this is auto then we'll need to make it fixed... but we'll start // by making it fixed unit = preferMM ? UNIT_DIPS : UNIT_PIXELS; if (unit == UNIT_PIXELS) { value = calculatedValue + delta - calculatedBaseValue; } else { float pixelsPerDip = Display.getInstance().convertToPixels(1000)/1000f; value = (calculatedValue + delta - calculatedBaseValue) / pixelsPerDip; } break; } } //calculatedValue += delta; this.delta += delta; if (getOppositeInset().isFlexible()) { getOppositeInset().delta -= delta; } return this; } /** * Translates the inset by {@literal delta} millimetres. * @param delta Pixels to translate this inset by. * @param preferMM If this is a flexible inset, then translating it will require changing it to fixed. {@literal true} to use millimetres. {@literal false} to use pixels. * @param parent The parent container used for calculating equivalent percent if this is a percent inset. * @return Self for chaining. */ public Inset translateMM(float delta, boolean preferMM, Container parent) { return translatePixels(Display.getInstance().convertToPixels(delta), preferMM, parent); } } } @Override public boolean overridesTabIndices(Container parent) { return true; } @Override protected Component[] getChildrenInTraversalOrder(Container parent) { java.util.List cmps = new ArrayList(); for (Component cmp : parent) { cmps.add(cmp); } Collections.sort(cmps, new Comparator() { @Override public int compare(Component o1, Component o2) { if (o1.getY() < o2.getY()) { return -1; } else if (o1.getY() > o2.getY()) { return 1; } else { if (o1.getX() < o2.getX()) { return -1; } else if (o1.getX() > o2.getX()) { return 1; } else { return 0; } } } }); Component[] cmpArr = cmps.toArray(new Component[cmps.size()]); return cmpArr; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy