Package tripleplay.ui.layout

Source Code of tripleplay.ui.layout.AxisLayout$Metrics

//
// Triple Play - utilities for use in PlayN-based games
// Copyright (c) 2011-2014, Three Rings Design, Inc. - All rights reserved.
// http://github.com/threerings/tripleplay/blob/master/LICENSE

package tripleplay.ui.layout;

import pythagoras.f.Dimension;
import pythagoras.f.IDimension;

import tripleplay.ui.Container;
import tripleplay.ui.Element;
import tripleplay.ui.Layout;
import tripleplay.ui.Style;

/**
* Lays out elements in a horizontal or vertical group. Separate policies are enforced for on-axis
* and off-axis sizing.
*
* <p> On-axis, the available space is divided up as follows: non-stretched elements are given
* their preferred size, and remaining space is divided up among the stretched elements
* proportional to their configured weight (which defaults to one). If no stretched elements exist,
* elements are aligned per the {@link tripleplay.ui.Style.HAlign} and
* {@link tripleplay.ui.Style.VAlign} properties on the containing group. </p>
*
* <p> Off-axis sizing can be configured to either size elements to their preferred size, stretch
* them all to a uniform size (equal to the preferred size of the largest element), or to stretch
* them all to the size allotted to the container. When elements are not stretched to fill the size
* allotted to the container, they may be aligned as above. </p>
*/
public abstract class AxisLayout extends Layout
{
    /** Specifies the off-axis layout policy. */
    public static enum Policy {
        DEFAULT {
            @Override public float computeSize (float size, float maxSize, float extent) {
                return Math.min(size, extent);
            }
        },
        STRETCH {
            @Override public float computeSize (float size, float maxSize, float extent) {
                return extent;
            }
        },
        EQUALIZE {
            @Override public float computeSize (float size, float maxSize, float extent) {
                return Math.min(maxSize, extent);
            }
        },
        CONSTRAIN {
            @Override public float computeSize (float size, float maxSize, float extent) {
                return Math.min(size, extent);
            }
        };

        public abstract float computeSize (float size, float maxSize, float extent);
    }

    /** Defines axis layout constraints. */
    public static final class Constraint extends Layout.Constraint {
        public final boolean stretch;
        public final float weight;

        public Constraint (boolean stretch, float weight) {
            this.stretch = stretch;
            this.weight = weight;
        }

        public float computeSize (float size, float totalWeight, float availSize) {
            return stretch ? (availSize * weight / totalWeight) : size;
        }
    }

    /** A vertical axis layout. */
    public static class Vertical extends AxisLayout {
        @Override public Dimension computeSize (Container<?> elems, float hintX, float hintY) {
            Metrics m = computeMetrics(elems, hintX, hintY, true);
            return new Dimension(m.maxWidth, m.prefHeight + m.gaps(_gap));
        }

        @Override public void layout (Container<?> elems,
                                      float left, float top, float width, float height) {
            Style.HAlign halign = resolveStyle(elems, Style.HALIGN);
            Style.VAlign valign = resolveStyle(elems, Style.VALIGN);
            Metrics m = computeMetrics(elems, width, height, true);
            float stretchHeight = Math.max(0, height - m.gaps(_gap) - m.fixHeight);
            float y = top + ((m.stretchers > 0) ? 0 :
                             valign.offset(m.fixHeight + m.gaps(_gap), height));
            for (Element<?> elem : elems) {
                if (!elem.isVisible()) continue;
                IDimension psize = preferredSize(elem, width, height); // will be cached
                Constraint c = constraint(elem);
                float ewidth = _offPolicy.computeSize(psize.width(), m.maxWidth, width);
                float eheight = c.computeSize(psize.height(), m.totalWeight, stretchHeight);
                setBounds(elem, left + halign.offset(ewidth, width), y, ewidth, eheight);
                y += (eheight + _gap);
            }
        }
    }

    /** A horizontal axis layout. */
    public static class Horizontal extends AxisLayout {
        @Override public Dimension computeSize (Container<?> elems, float hintX, float hintY) {
            Metrics m = computeMetrics(elems, hintX, hintY, false);
            return new Dimension(m.prefWidth + m.gaps(_gap), m.maxHeight);
        }

        @Override public void layout (Container<?> elems,
                                      float left, float top, float width, float height) {
            Style.HAlign halign = resolveStyle(elems, Style.HALIGN);
            Style.VAlign valign = resolveStyle(elems, Style.VALIGN);
            Metrics m = computeMetrics(elems, width, height, false);
            float stretchWidth = Math.max(0, width - m.gaps(_gap) - m.fixWidth);
            float x = left + ((m.stretchers > 0) ? 0 :
                              halign.offset(m.fixWidth + m.gaps(_gap), width));
            for (Element<?> elem : elems) {
                if (!elem.isVisible()) continue;
                IDimension psize = preferredSize(elem, width, height); // will be cached
                Constraint c = constraint(elem);
                float ewidth = c.computeSize(psize.width(), m.totalWeight, stretchWidth);
                float eheight = _offPolicy.computeSize(psize.height(), m.maxHeight, height);
                setBounds(elem, x, top + valign.offset(eheight, height), ewidth, eheight);
                x += (ewidth + _gap);
            }
        }
    }

    /**
     * Creates a vertical axis layout with default gap (5), and off-axis sizing policy (preferred
     * size).
     */
    public static Vertical vertical () {
        return new Vertical();
    }

    /**
     * Creates a horizontal axis layout with default gap (5), and off-axis sizing policy (preferred
     * size).
     */
    public static Horizontal horizontal () {
        return new Horizontal();
    }

    /**
     * Returns a layout constraint indicating that the associated element should be stretched to
     * consume extra space, with weight 1.
     */
    public static Constraint stretched () {
        return UNIFORM_STRETCHED;
    }

    /**
     * Returns a layout constraint indicating that the associated element should not be stretched.
     */
    public static Constraint fixed () {
        return UNSTRETCHED;
    }

    /**
     * Returns a layout constraint indicating that the associated element should be stretched to
     * consume extra space, with the specified weight.
     */
    public static Constraint stretched (float weight) {
        return new Constraint(true, weight);
    }

    /**
     * Configures the supplied element with a {@link #stretched} constraint.
     */
    public static <T extends Element<?>> T stretch (T elem) {
        elem.setConstraint(stretched());
        return elem;
    }

    /**
     * Configures the supplied element with a weighted {@link #stretched(float)} constraint.
     */
    public static <T extends Element<?>> T stretch (T elem, float weight) {
        elem.setConstraint(stretched(weight));
        return elem;
    }

    /**
     * Configures the default constraint for elements added to this layout to be stretched. This
     * is equivalent to calling {@link Element#setConstraint(Layout.Constraint)} with
     * {@link #stretched()} for each element added to the parent container.
     */
    public AxisLayout stretchByDefault () {
        _stretchByDefault = true;
        return this;
    }

    /**
     * Configures the off-axis sizing policy for this layout.
     */
    public AxisLayout offPolicy (Policy policy) {
        _offPolicy = policy;
        return this;
    }

    /**
     * Configures this layout to stretch all elements to the available size on the off-axis.
     */
    public AxisLayout offStretch () {
        return offPolicy(Policy.STRETCH);
    }

    /**
     * Configures this layout to stretch all elements to the size of the largest element on the
     * off-axis.
     */
    public AxisLayout offEqualize () {
        return offPolicy(Policy.EQUALIZE);
    }

    /**
     * Configures this layout to constrain elements to the size of this container on the off-axis,
     * leaving their size alone if it is smaller.
     */
    public AxisLayout offConstrain () {
        return offPolicy(Policy.CONSTRAIN);
    }

    /**
     * Configures the inter-element gap, in pixels.
     */
    public AxisLayout gap (int gap) {
        _gap = gap;
        return this;
    }

    protected Metrics computeMetrics (Container<?> elems, float hintX, float hintY,
                                      boolean vert) {
        Metrics m = new Metrics();
        for (Element<?> elem : elems) {
            if (!elem.isVisible()) continue;
            m.count++;

            // only compute the preferred size for the fixed elements in this pass
            Constraint c = constraint(elem);
            if (!c.stretch) {
                IDimension psize = preferredSize(elem, hintX, hintY);
                float pwidth = psize.width(), pheight = psize.height();
                m.prefWidth += pwidth;
                m.prefHeight += pheight;
                m.maxWidth = Math.max(m.maxWidth, pwidth);
                m.maxHeight = Math.max(m.maxHeight, pheight);
                m.fixWidth += pwidth;
                m.fixHeight += pheight;
            } else {
                m.stretchers++;
                m.totalWeight += c.weight;
            }
        }

        // now compute the preferred size for the stretched elements, providing them with more
        // accurate width/height hints
        for (Element<?> elem : elems) {
            if (!elem.isVisible()) continue;
            Constraint c = constraint(elem);
            if (!c.stretch) continue;

            // the first argument to computeSize is not used for stretched elements
            float availX = hintX - m.gaps(_gap), availY = hintY - m.gaps(_gap);
            float ehintX = vert ? availX : c.computeSize(0, m.totalWeight, availX - m.fixWidth);
            float ehintY = vert ? c.computeSize(0, m.totalWeight, availY - m.fixHeight) : availY;
            IDimension psize = preferredSize(elem, ehintX, ehintY);
            float pwidth = psize.width(), pheight = psize.height();
            m.unitWidth = Math.max(m.unitWidth, pwidth / c.weight);
            m.unitHeight = Math.max(m.unitHeight, pheight / c.weight);
            m.maxWidth = Math.max(m.maxWidth, pwidth);
            m.maxHeight = Math.max(m.maxHeight, pheight);
        }
        m.prefWidth += m.stretchers * m.unitWidth;
        m.prefHeight += m.stretchers * m.unitHeight;

        return m;
    }

    protected Constraint constraint (Element<?> elem) {
        Layout.Constraint c = elem.constraint();
        return (c instanceof Constraint) ? (Constraint)c :
            _stretchByDefault ? UNIFORM_STRETCHED : UNSTRETCHED;
    }

    protected static class Metrics {
        public int count;

        public float prefWidth;
        public float prefHeight;

        public float maxWidth;
        public float maxHeight;

        public float fixWidth;
        public float fixHeight;

        public float unitWidth;
        public float unitHeight;

        public int stretchers;
        public float totalWeight;

        public float gaps (float gap) {
            return gap * (count-1);
        }
    }

    protected int _gap = 5;
    protected boolean _stretchByDefault;
    protected Policy _offPolicy = Policy.DEFAULT;

    protected static final Constraint UNSTRETCHED = new Constraint(false, 1);
    protected static final Constraint UNIFORM_STRETCHED = new Constraint(true, 1);
}
TOP

Related Classes of tripleplay.ui.layout.AxisLayout$Metrics

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.