
/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "SkStackViewLayout.h"

SkStackViewLayout::SkStackViewLayout()
{
    fMargin.set(0, 0, 0, 0);
    fSpacer    = 0;
    fOrient    = kHorizontal_Orient;
    fPack    = kStart_Pack;
    fAlign    = kStart_Align;
    fRound    = false;
}

void SkStackViewLayout::setOrient(Orient ori)
{
    SkASSERT((unsigned)ori < kOrientCount);
    fOrient = SkToU8(ori);
}

void SkStackViewLayout::getMargin(SkRect* margin) const
{
    if (margin)
        *margin = fMargin;
}

void SkStackViewLayout::setMargin(const SkRect& margin)
{
    fMargin = margin;
}

void SkStackViewLayout::setSpacer(SkScalar spacer)
{
    fSpacer = spacer;
}

void SkStackViewLayout::setPack(Pack pack)
{
    SkASSERT((unsigned)pack < kPackCount);
    fPack = SkToU8(pack);
}

void SkStackViewLayout::setAlign(Align align)
{
    SkASSERT((unsigned)align < kAlignCount);
    fAlign = SkToU8(align);
}

void SkStackViewLayout::setRound(bool r)
{
    fRound = SkToU8(r);
}

////////////////////////////////////////////////////////////////////////////////

typedef SkScalar (*AlignProc)(SkScalar childLimit, SkScalar parentLimit);
typedef SkScalar (SkView::*GetSizeProc)() const;
typedef void (SkView::*SetLocProc)(SkScalar coord);
typedef void (SkView::*SetSizeProc)(SkScalar coord);

static SkScalar left_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }
static SkScalar center_align_proc(SkScalar childLimit, SkScalar parentLimit) { return SkScalarHalf(parentLimit - childLimit); }
static SkScalar right_align_proc(SkScalar childLimit, SkScalar parentLimit) { return parentLimit - childLimit; }
static SkScalar fill_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }

/*    Measure the main-dimension for all the children. If a child is marked flex in that direction
    ignore its current value but increment the counter for flexChildren
*/
static SkScalar compute_children_limit(SkView* parent, GetSizeProc sizeProc, int* count,
                                       uint32_t flexMask, int* flexCount)
{
    SkView::B2FIter    iter(parent);
    SkView*            child;
    SkScalar        limit = 0;
    int                n = 0, flex = 0;

    while ((child = iter.next()) != nullptr)
    {
        n += 1;
        if (child->getFlags() & flexMask)
            flex += 1;
        else
            limit += (child->*sizeProc)();
    }
    if (count)
        *count = n;
    if (flexCount)
        *flexCount = flex;
    return limit;
}

void SkStackViewLayout::onLayoutChildren(SkView* parent)
{
    static AlignProc gAlignProcs[] = {
        left_align_proc,
        center_align_proc,
        right_align_proc,
        fill_align_proc
    };

    SkScalar            startM, endM, crossStartM, crossLimit;
    GetSizeProc            mainGetSizeP, crossGetSizeP;
    SetLocProc            mainLocP, crossLocP;
    SetSizeProc            mainSetSizeP, crossSetSizeP;
    SkView::Flag_Mask    flexMask;

    if (fOrient == kHorizontal_Orient)
    {
        startM        = fMargin.fLeft;
        endM        = fMargin.fRight;
        crossStartM    = fMargin.fTop;
        crossLimit    = -fMargin.fTop - fMargin.fBottom;

        mainGetSizeP    = &SkView::width;
        crossGetSizeP    = &SkView::height;
        mainLocP    = &SkView::setLocX;
        crossLocP    = &SkView::setLocY;

        mainSetSizeP  = &SkView::setWidth;
        crossSetSizeP = &SkView::setHeight;

        flexMask    = SkView::kFlexH_Mask;
    }
    else
    {
        startM        = fMargin.fTop;
        endM        = fMargin.fBottom;
        crossStartM    = fMargin.fLeft;
        crossLimit    = -fMargin.fLeft - fMargin.fRight;

        mainGetSizeP    = &SkView::height;
        crossGetSizeP    = &SkView::width;
        mainLocP    = &SkView::setLocY;
        crossLocP    = &SkView::setLocX;

        mainSetSizeP  = &SkView::setHeight;
        crossSetSizeP = &SkView::setWidth;

        flexMask    = SkView::kFlexV_Mask;
    }
    crossLimit += (parent->*crossGetSizeP)();
    if (fAlign != kStretch_Align)
        crossSetSizeP = nullptr;

    int            childCount, flexCount;
    SkScalar    childLimit = compute_children_limit(parent, mainGetSizeP, &childCount, flexMask, &flexCount);

    if (childCount == 0)
        return;

    childLimit += (childCount - 1) * fSpacer;

    SkScalar        parentLimit = (parent->*mainGetSizeP)() - startM - endM;
    SkScalar        pos = startM + gAlignProcs[fPack](childLimit, parentLimit);
    SkScalar        flexAmount = 0;
    SkView::B2FIter    iter(parent);
    SkView*            child;

    if (flexCount > 0 && parentLimit > childLimit)
        flexAmount = (parentLimit - childLimit) / flexCount;

    while ((child = iter.next()) != nullptr)
    {
        if (fRound)
            pos = SkScalarRoundToScalar(pos);
        (child->*mainLocP)(pos);
        SkScalar crossLoc = crossStartM + gAlignProcs[fAlign]((child->*crossGetSizeP)(), crossLimit);
        if (fRound)
            crossLoc = SkScalarRoundToScalar(crossLoc);
        (child->*crossLocP)(crossLoc);

        if (crossSetSizeP)
            (child->*crossSetSizeP)(crossLimit);
        if (child->getFlags() & flexMask)
            (child->*mainSetSizeP)(flexAmount);
        pos += (child->*mainGetSizeP)() + fSpacer;
    }
}

//////////////////////////////////////////////////////////////////////////////////////

#ifdef SK_DEBUG
    static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
    {
        const char* value = dom.findAttr(node, attr);
        if (value)
            SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
    }
#else
    #define assert_no_attr(dom, node, attr)
#endif

void SkStackViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
    int            index;
    SkScalar    value[4];

    if ((index = dom.findList(node, "orient", "horizontal,vertical")) >= 0)
        this->setOrient((Orient)index);
    else {
        assert_no_attr(dom, node, "orient");
        }

    if (dom.findScalars(node, "margin", value, 4))
    {
        SkRect    margin;
        margin.set(value[0], value[1], value[2], value[3]);
        this->setMargin(margin);
    }
    else {
        assert_no_attr(dom, node, "margin");
        }

    if (dom.findScalar(node, "spacer", value))
        this->setSpacer(value[0]);
    else {
        assert_no_attr(dom, node, "spacer");
        }

    if ((index = dom.findList(node, "pack", "start,center,end")) >= 0)
        this->setPack((Pack)index);
    else {
        assert_no_attr(dom, node, "pack");
        }

    if ((index = dom.findList(node, "align", "start,center,end,stretch")) >= 0)
        this->setAlign((Align)index);
    else {
        assert_no_attr(dom, node, "align");
        }
}

///////////////////////////////////////////////////////////////////////////////////////////

SkFillViewLayout::SkFillViewLayout()
{
    fMargin.setEmpty();
}

void SkFillViewLayout::getMargin(SkRect* r) const
{
    if (r)
        *r = fMargin;
}

void SkFillViewLayout::setMargin(const SkRect& margin)
{
    fMargin = margin;
}

void SkFillViewLayout::onLayoutChildren(SkView* parent)
{
    SkView::B2FIter    iter(parent);
    SkView*            child;

    while ((child = iter.next()) != nullptr)
    {
        child->setLoc(fMargin.fLeft, fMargin.fTop);
        child->setSize(    parent->width() - fMargin.fRight - fMargin.fLeft,
                        parent->height() - fMargin.fBottom - fMargin.fTop);
    }
}

void SkFillViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
    this->INHERITED::onInflate(dom, node);
    (void)dom.findScalars(node, "margin", (SkScalar*)&fMargin, 4);
}
