/**
 * @file lllayoutstack.cpp
 * @brief LLLayout class - dynamic stacking of UI elements
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

// Opaque view with a background and a border.  Can contain LLUICtrls.

#include "linden_common.h"

#include "lllayoutstack.h"

#include "lllocalcliprect.h"
#include "llpanel.h"
#include "llcriticaldamp.h"
#include "lliconctrl.h"
#include "boost/foreach.hpp"

static const F32 MIN_FRACTIONAL_SIZE = 0.00001f;
static const F32 MAX_FRACTIONAL_SIZE = 1.f;

static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("layout_stack");
static LLLayoutStack::LayoutStackRegistry::Register<LLLayoutPanel> register_layout_panel("layout_panel");

//
// LLLayoutPanel
//
LLLayoutPanel::Params::Params()
:   expanded_min_dim("expanded_min_dim", 0),
    min_dim("min_dim", -1),
    user_resize("user_resize", false),
    auto_resize("auto_resize", true)
{
    addSynonym(min_dim, "min_width");
    addSynonym(min_dim, "min_height");
}

LLLayoutPanel::LLLayoutPanel(const Params& p)
:   LLPanel(p),
    mExpandedMinDim(p.expanded_min_dim.isProvided() ? p.expanded_min_dim : p.min_dim),
    mMinDim(p.min_dim),
    mAutoResize(p.auto_resize),
    mUserResize(p.user_resize),
    mCollapsed(false),
    mCollapseAmt(0.f),
    mVisibleAmt(1.f), // default to fully visible
    mResizeBar(NULL),
    mFractionalSize(0.f),
    mTargetDim(0),
    mIgnoreReshape(false),
    mOrientation(LLLayoutStack::HORIZONTAL)
{
    // panels initialized as hidden should not start out partially visible
    if (!getVisible())
    {
        mVisibleAmt = 0.f;
    }
}

void LLLayoutPanel::initFromParams(const Params& p)
{
    LLPanel::initFromParams(p);
    setFollowsNone();
}


LLLayoutPanel::~LLLayoutPanel()
{
    gFocusMgr.removeKeyboardFocusWithoutCallback(this);
}

F32 LLLayoutPanel::getAutoResizeFactor() const
{
    return mVisibleAmt * (1.f - mCollapseAmt);
}

F32 LLLayoutPanel::getVisibleAmount() const
{
    return mVisibleAmt;
}

S32 LLLayoutPanel::getLayoutDim() const
{
    return ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL)
                    ? getRect().getWidth()
                    : getRect().getHeight()));
}

S32 LLLayoutPanel::getTargetDim() const
{
    return mTargetDim;
}

void LLLayoutPanel::setTargetDim(S32 value)
{
    LLRect new_rect(getRect());
    if (mOrientation == LLLayoutStack::HORIZONTAL)
    {
        new_rect.mRight = new_rect.mLeft + value;
    }
    else
    {
        new_rect.mTop = new_rect.mBottom + value;
    }
    setShape(new_rect, true);
}

S32 LLLayoutPanel::getVisibleDim() const
{
    F32 min_dim = (F32)getRelevantMinDim();
    return ll_round(mVisibleAmt
                    * (min_dim
                        + (((F32)mTargetDim - min_dim) * (1.f - mCollapseAmt))));
}

void LLLayoutPanel::setOrientation( LLView::EOrientation orientation )
{
    mOrientation = orientation;
    S32 layout_dim = ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL)
        ? getRect().getWidth()
        : getRect().getHeight()));

    if (!mAutoResize && mUserResize && mMinDim == -1)
    {
        setMinDim(layout_dim);
    }
    mTargetDim = llmax(layout_dim, getMinDim());
}

void LLLayoutPanel::setVisible( bool visible )
{
    if (visible != getVisible())
    {
        LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent());
        if (stackp)
        {
            stackp->mNeedsLayout = true;
        }
    }
    LLPanel::setVisible(visible);
}

void LLLayoutPanel::reshape( S32 width, S32 height, bool called_from_parent /*= true*/ )
{
    if (width == getRect().getWidth() && height == getRect().getHeight() && !LLView::sForceReshape) return;

    if (!mIgnoreReshape && !mAutoResize)
    {
        mTargetDim = (mOrientation == LLLayoutStack::HORIZONTAL) ? width : height;
        LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent());
        if (stackp)
        {
            stackp->mNeedsLayout = true;
        }
    }
    LLPanel::reshape(width, height, called_from_parent);
}

void LLLayoutPanel::handleReshape(const LLRect& new_rect, bool by_user)
{
    LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent());
    if (stackp)
    {
        if (by_user)
        {   // tell layout stack to account for new shape

            // make sure that panels have already been auto resized
            stackp->updateLayout();
            // now apply requested size to panel
            stackp->updatePanelRect(this, new_rect);
        }
        stackp->mNeedsLayout = true;
    }
    LLPanel::handleReshape(new_rect, by_user);
}

//
// LLLayoutStack
//

LLLayoutStack::Params::Params()
:   orientation("orientation"),
    animate("animate", true),
    clip("clip", true),
    open_time_constant("open_time_constant", 0.02f),
    close_time_constant("close_time_constant", 0.03f),
    resize_bar_overlap("resize_bar_overlap", 1),
    border_size("border_size", LLUI::getInstance()->mSettingGroups["config"]->getS32("UIResizeBarHeight")),
    show_drag_handle("show_drag_handle", false),
    drag_handle_first_indent("drag_handle_first_indent", 0),
    drag_handle_second_indent("drag_handle_second_indent", 0),
    drag_handle_thickness("drag_handle_thickness", 5),
    drag_handle_shift("drag_handle_shift", 2),
    drag_handle_color("drag_handle_color", LLUIColorTable::instance().getColor("ResizebarBody"))
{
    addSynonym(border_size, "drag_handle_gap");
}

LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p)
:   LLView(p),
    mPanelSpacing(p.border_size),
    mOrientation(p.orientation),
    mAnimate(p.animate),
    mAnimatedThisFrame(false),
    mNeedsLayout(true),
    mClip(p.clip),
    mOpenTimeConstant(p.open_time_constant),
    mCloseTimeConstant(p.close_time_constant),
    mResizeBarOverlap(p.resize_bar_overlap),
    mShowDragHandle(p.show_drag_handle),
    mDragHandleFirstIndent(p.drag_handle_first_indent),
    mDragHandleSecondIndent(p.drag_handle_second_indent),
    mDragHandleThickness(p.drag_handle_thickness),
    mDragHandleShift(p.drag_handle_shift),
    mDragHandleColor(p.drag_handle_color())
{
}

LLLayoutStack::~LLLayoutStack()
{
}

// virtual
void LLLayoutStack::draw()
{
    updateLayout();

    // always clip to stack itself
    LLLocalClipRect clip(getLocalRect());
    for (LLLayoutPanel* panelp : mPanels)
    {
        if ((!panelp->getVisible() || panelp->mCollapsed)
            && (panelp->mVisibleAmt < 0.001f || !mAnimate))
        {
            // essentially invisible
            continue;
        }
        // clip to layout rectangle, not bounding rectangle
        LLRect clip_rect = panelp->getRect();
        // scale clipping rectangle by visible amount
        if (mOrientation == HORIZONTAL)
        {
            clip_rect.mRight = clip_rect.mLeft + panelp->getVisibleDim();
        }
        else
        {
            clip_rect.mBottom = clip_rect.mTop - panelp->getVisibleDim();
        }

        {LLLocalClipRect clip(clip_rect, mClip);
            // only force drawing invisible children if visible amount is non-zero
            drawChild(panelp, 0, 0, !clip_rect.isEmpty());
        }
        if (panelp->getResizeBar()->getVisible())
        {
            drawChild(panelp->getResizeBar());
        }
    }
}

// virtual
void LLLayoutStack::deleteAllChildren()
{
    for (LLLayoutPanel* p : mPanels)
    {
        p->mResizeBar = nullptr;
    }

    mPanels.clear();
    LLView::deleteAllChildren();

    // Not really needed since nothing is left to
    // display, but for the sake of consistency
    updateFractionalSizes();
    mNeedsLayout = true;
}

// virtual
void LLLayoutStack::removeChild(LLView* view)
{
    if (LLLayoutPanel* embedded_panelp = dynamic_cast<LLLayoutPanel*>(view))
    {
        auto it = std::find(mPanels.begin(), mPanels.end(), embedded_panelp);
        if (it != mPanels.end())
        {
            mPanels.erase(it);
        }
        if (embedded_panelp->mResizeBar)
        {
            LLView::removeChild(embedded_panelp->mResizeBar);
            embedded_panelp->mResizeBar = nullptr;
        }
    }
    else if (LLResizeBar* resize_bar = dynamic_cast<LLResizeBar*>(view))
    {
        for (LLLayoutPanel* p : mPanels)
        {
            if (p->mResizeBar == resize_bar)
            {
                p->mResizeBar = nullptr;
            }
        }
    }

    LLView::removeChild(view);

    updateFractionalSizes();
    mNeedsLayout = true;
}

// virtual
bool LLLayoutStack::postBuild()
{
    updateLayout();
    return true;
}

// virtual
bool LLLayoutStack::addChild(LLView* child, S32 tab_group)
{
    LLLayoutPanel* panelp = dynamic_cast<LLLayoutPanel*>(child);
    if (panelp)
    {
        panelp->setOrientation(mOrientation);
        mPanels.push_back(panelp);
        createResizeBar(panelp);
        mNeedsLayout = true;
    }
    bool result = LLView::addChild(child, tab_group);

    updateFractionalSizes();
    return result;
}

void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate)
{
    addChild(panel);

    // panel starts off invisible (collapsed)
    if (animate == ANIMATE)
    {
        panel->mVisibleAmt = 0.f;
        panel->setVisible(true);
    }
}

void LLLayoutStack::collapsePanel(LLPanel* panel, bool collapsed)
{
    LLLayoutPanel* panel_container = findEmbeddedPanel(panel);
    if (!panel_container) return;

    panel_container->mCollapsed = collapsed;
    mNeedsLayout = true;
}

class LLImagePanel : public LLPanel
{
public:
    struct Params : public LLInitParam::Block<Params, LLPanel::Params>
    {
        Optional<bool> horizontal;
        Params() : horizontal("horizontal", false) {}
    };
    LLImagePanel(const Params& p) : LLPanel(p), mHorizontal(p.horizontal) {}
    virtual ~LLImagePanel() {}

    void draw()
    {
        const LLRect& parent_rect = getParent()->getRect();
        const LLRect& rect = getRect();
        LLRect clip_rect( -rect.mLeft, parent_rect.getHeight() - rect.mBottom - 2
            , parent_rect.getWidth() - rect.mLeft - (mHorizontal ? 2 : 0), -rect.mBottom);
        LLLocalClipRect clip(clip_rect);
        LLPanel::draw();
    }

private:
    bool mHorizontal;
};

void LLLayoutStack::updateLayout()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;

    if (!mNeedsLayout) return;

    bool continue_animating = animatePanels();
    F32 total_visible_fraction = 0.f;
    S32 space_to_distribute = (mOrientation == HORIZONTAL)
                            ? getRect().getWidth()
                            : getRect().getHeight();

    // first, assign minimum dimensions
    for (LLLayoutPanel* panelp : mPanels)
    {
        if (panelp->mAutoResize)
        {
            panelp->mTargetDim = panelp->getRelevantMinDim();
        }
        space_to_distribute -= panelp->getVisibleDim() + ll_round((F32)mPanelSpacing * panelp->getVisibleAmount());
        total_visible_fraction += panelp->mFractionalSize * panelp->getAutoResizeFactor();
    }

    llassert(total_visible_fraction < 1.05f);

    // don't need spacing after last panel
    if (!mPanels.empty())
    {
        space_to_distribute += ll_round(F32(mPanelSpacing) * mPanels.back()->getVisibleAmount());
    }

    S32 remaining_space = space_to_distribute;
    if (space_to_distribute > 0 && total_visible_fraction > 0.f)
    {   // give space proportionally to visible auto resize panels
        for (LLLayoutPanel* panelp : mPanels)
        {
            if (panelp->mAutoResize)
            {
                F32 fraction_to_distribute = (panelp->mFractionalSize * panelp->getAutoResizeFactor()) / (total_visible_fraction);
                S32 delta = ll_round((F32)space_to_distribute * fraction_to_distribute);
                panelp->mTargetDim += delta;
                remaining_space -= delta;
            }
        }
    }

    // distribute any left over pixels to non-collapsed, visible panels
    for (LLLayoutPanel* panelp : mPanels)
    {
        if (remaining_space == 0) break;

        if (panelp->mAutoResize
            && !panelp->mCollapsed
            && panelp->getVisible())
        {
            S32 space_for_panel = remaining_space > 0 ? 1 : -1;
            panelp->mTargetDim += space_for_panel;
            remaining_space -= space_for_panel;
        }
    }

    F32 cur_pos = (mOrientation == HORIZONTAL) ? 0.f : (F32)getRect().getHeight();

    for (LLLayoutPanel* panelp : mPanels)
    {
        F32 panel_dim = (F32)llmax(panelp->getExpandedMinDim(), panelp->mTargetDim);

        LLRect panel_rect;
        if (mOrientation == HORIZONTAL)
        {
            panel_rect.setLeftTopAndSize(ll_round(cur_pos),
                                        getRect().getHeight(),
                                        ll_round(panel_dim),
                                        getRect().getHeight());
        }
        else
        {
            panel_rect.setLeftTopAndSize(0,
                                        ll_round(cur_pos),
                                        getRect().getWidth(),
                                        ll_round(panel_dim));
        }

        LLRect resize_bar_rect(panel_rect);
        F32 panel_spacing = (F32)mPanelSpacing * panelp->getVisibleAmount();
        F32 panel_visible_dim = (F32)panelp->getVisibleDim();
        S32 panel_spacing_round = (S32)(ll_round(panel_spacing));

        if (mOrientation == HORIZONTAL)
        {
            cur_pos += panel_visible_dim + panel_spacing;

            if (mShowDragHandle && panel_spacing_round > mDragHandleThickness)
            {
                resize_bar_rect.mLeft = panel_rect.mRight + mDragHandleShift;
                resize_bar_rect.mRight = resize_bar_rect.mLeft + mDragHandleThickness;
            }
            else
            {
                resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap;
                resize_bar_rect.mRight = panel_rect.mRight + panel_spacing_round + mResizeBarOverlap;
            }

            if (mShowDragHandle)
            {
                resize_bar_rect.mBottom += mDragHandleSecondIndent;
                resize_bar_rect.mTop -= mDragHandleFirstIndent;
            }

        }
        else //VERTICAL
        {
            cur_pos -= panel_visible_dim + panel_spacing;

            if (mShowDragHandle && panel_spacing_round > mDragHandleThickness)
            {
                resize_bar_rect.mTop = panel_rect.mBottom - mDragHandleShift;
                resize_bar_rect.mBottom = resize_bar_rect.mTop - mDragHandleThickness;
            }
            else
            {
                resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap;
                resize_bar_rect.mBottom = panel_rect.mBottom - panel_spacing_round - mResizeBarOverlap;
            }

            if (mShowDragHandle)
            {
                resize_bar_rect.mLeft += mDragHandleFirstIndent;
                resize_bar_rect.mRight -= mDragHandleSecondIndent;
            }
        }

        panelp->setIgnoreReshape(true);
        panelp->setShape(panel_rect);
        panelp->setIgnoreReshape(false);
        panelp->mResizeBar->setShape(resize_bar_rect);
    }

    updateResizeBarLimits();

    // clear animation flag at end, since panel resizes will set it
    // and leave it set if there is any animation in progress
    mNeedsLayout = continue_animating;
} // end LLLayoutStack::updateLayout

void LLLayoutStack::setPanelSpacing(S32 val)
{
    if (mPanelSpacing != val)
    {
        mPanelSpacing = val;
        mNeedsLayout = true;
    }
}

LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const
{
    if (!panelp) return NULL;

    for (LLLayoutPanel* p : mPanels)
    {
        if (p == panelp)
        {
            return p;
        }
    }
    return NULL;
}

LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(std::string_view name) const
{
    LLLayoutPanel* result = NULL;

    for (LLLayoutPanel* p : mPanels)
    {
        if (p->getName() == name)
        {
            result = p;
            break;
        }
    }

    return result;
}

void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp)
{
    for (LLLayoutPanel* lp : mPanels)
    {
        if (lp->mResizeBar == NULL)
        {
            LLResizeBar::Params resize_params;
            resize_params.name("resize");
            resize_params.resizing_view(lp);
            resize_params.min_size(lp->getRelevantMinDim());
            resize_params.side((mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM);
            resize_params.snapping_enabled(false);
            LLResizeBar* resize_bar = LLUICtrlFactory::create<LLResizeBar>(resize_params);
            lp->mResizeBar = resize_bar;

            if (mShowDragHandle)
            {
                LLPanel::Params resize_bar_bg_panel_p;
                resize_bar_bg_panel_p.name = "resize_handle_bg_panel";
                resize_bar_bg_panel_p.rect = lp->mResizeBar->getLocalRect();
                resize_bar_bg_panel_p.follows.flags = FOLLOWS_ALL;
                resize_bar_bg_panel_p.tab_stop = false;
                resize_bar_bg_panel_p.background_visible = true;
                resize_bar_bg_panel_p.bg_alpha_color = mDragHandleColor;
                resize_bar_bg_panel_p.has_border = true;
                resize_bar_bg_panel_p.border.border_thickness = 1;
                resize_bar_bg_panel_p.border.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight");
                resize_bar_bg_panel_p.border.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark");

                LLPanel* resize_bar_bg_panel = LLUICtrlFactory::create<LLPanel>(resize_bar_bg_panel_p);

                LLIconCtrl::Params icon_p;
                icon_p.name = "resize_handle_image";
                icon_p.rect = lp->mResizeBar->getLocalRect();
                icon_p.follows.flags = FOLLOWS_ALL;
                icon_p.image = LLUI::getUIImage(mOrientation == HORIZONTAL ? "Vertical Drag Handle" : "Horizontal Drag Handle");
                resize_bar_bg_panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p));

                lp->mResizeBar->addChild(resize_bar_bg_panel);
            }

            /*if (mShowDragHandle)
            {
                LLViewBorder::Params border_params;
                border_params.border_thickness = 1;
                border_params.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight");
                border_params.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark");

                addBorder(border_params);
                setBorderVisible(true);

                LLImagePanel::Params image_panel;
                mDragHandleImage = LLUI::getUIImage(LLResizeBar::RIGHT == mSide ? "Vertical Drag Handle" : "Horizontal Drag Handle");
                image_panel.bg_alpha_image = mDragHandleImage;
                image_panel.background_visible = true;
                image_panel.horizontal = (LLResizeBar::BOTTOM == mSide);
                mImagePanel = LLUICtrlFactory::create<LLImagePanel>(image_panel);
                setImagePanel(mImagePanel);
            }*/

            //if (mShowDragHandle)
            //{
            //  setBackgroundVisible(true);
            //  setTransparentColor(LLUIColorTable::instance().getColor("ResizebarBody"));
            //}

            /*if (mShowDragHandle)
            {
            S32 image_width = mDragHandleImage->getTextureWidth();
            S32 image_height = mDragHandleImage->getTextureHeight();
            const LLRect& panel_rect = getRect();
            S32 image_left = (panel_rect.getWidth() - image_width) / 2 - 1;
            S32 image_bottom = (panel_rect.getHeight() - image_height) / 2;
            mImagePanel->setRect(LLRect(image_left, image_bottom + image_height, image_left + image_width, image_bottom));
            }*/
            LLView::addChild(resize_bar, 0);
        }
    }
    // bring all resize bars to the front so that they are clickable even over the panels
    // with a bit of overlap
    for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
    {
        LLResizeBar* resize_barp = (*panel_it)->mResizeBar;
        sendChildToFront(resize_barp);
    }
}

// update layout stack animations, etc. once per frame
// NOTE: we use this to size world view based on animating UI, *before* we draw the UI
// we might still need to call updateLayout during UI draw phase, in case UI elements
// are resizing themselves dynamically
//static
void LLLayoutStack::updateClass()
{
    for (auto& layout : instance_snapshot())
    {
        layout.updateLayout();
        layout.mAnimatedThisFrame = false;
    }
}

void LLLayoutStack::updateFractionalSizes()
{
    F32 total_resizable_dim = 0.f;

    for (LLLayoutPanel* panelp : mPanels)
    {
        if (panelp->mAutoResize)
        {
            total_resizable_dim += llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim()));
        }
    }

    for (LLLayoutPanel* panelp : mPanels)
    {
        if (panelp->mAutoResize)
        {
            F32 panel_resizable_dim = llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim()));
            panelp->mFractionalSize = panel_resizable_dim > 0.f
                ? llclamp(panel_resizable_dim / total_resizable_dim, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE)
                : MIN_FRACTIONAL_SIZE;
            llassert(!llisnan(panelp->mFractionalSize));
        }
    }

    normalizeFractionalSizes();
}


void LLLayoutStack::normalizeFractionalSizes()
{
    S32 num_auto_resize_panels = 0;
    F32 total_fractional_size = 0.f;

    for (LLLayoutPanel* panelp : mPanels)
    {
        if (panelp->mAutoResize)
        {
            total_fractional_size += panelp->mFractionalSize;
            num_auto_resize_panels++;
        }
    }

    if (total_fractional_size == 0.f)
    { // equal distribution
        for (LLLayoutPanel* panelp : mPanels)
        {
            if (panelp->mAutoResize)
            {
                panelp->mFractionalSize = MAX_FRACTIONAL_SIZE / (F32)num_auto_resize_panels;
            }
        }
    }
    else
    { // renormalize
        for (LLLayoutPanel* panelp : mPanels)
        {
            if (panelp->mAutoResize)
            {
                panelp->mFractionalSize /= total_fractional_size;
            }
        }
    }
}

bool LLLayoutStack::animatePanels()
{
    bool continue_animating = false;

    //
    // animate visibility
    //
    for (LLLayoutPanel* panelp : mPanels)
    {
        if (panelp->getVisible())
        {
            if (mAnimate && panelp->mVisibleAmt < 1.f)
            {
                if (!mAnimatedThisFrame)
                {
                    panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 1.f, LLSmoothInterpolation::getInterpolant(mOpenTimeConstant));
                    if (panelp->mVisibleAmt > 0.99f)
                    {
                        panelp->mVisibleAmt = 1.f;
                    }
                }

                mAnimatedThisFrame = true;
                continue_animating = true;
            }
            else
            {
                if (panelp->mVisibleAmt != 1.f)
                {
                    panelp->mVisibleAmt = 1.f;
                    mAnimatedThisFrame = true;
                }
            }
        }
        else // not visible
        {
            if (mAnimate && panelp->mVisibleAmt > 0.f)
            {
                if (!mAnimatedThisFrame)
                {
                    panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 0.f, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant));
                    if (panelp->mVisibleAmt < 0.001f)
                    {
                        panelp->mVisibleAmt = 0.f;
                    }
                }

                continue_animating = true;
                mAnimatedThisFrame = true;
            }
            else
            {
                if (panelp->mVisibleAmt != 0.f)
                {
                    panelp->mVisibleAmt = 0.f;
                    mAnimatedThisFrame = true;
                }
            }
        }

        F32 collapse_state = panelp->mCollapsed ? 1.f : 0.f;
        if (panelp->mCollapseAmt != collapse_state)
        {
            if (mAnimate)
            {
                if (!mAnimatedThisFrame)
                {
                    panelp->mCollapseAmt = lerp(panelp->mCollapseAmt, collapse_state, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant));
                }

                if (llabs(panelp->mCollapseAmt - collapse_state) < 0.001f)
                {
                    panelp->mCollapseAmt = collapse_state;
                }

                mAnimatedThisFrame = true;
                continue_animating = true;
            }
            else
            {
                panelp->mCollapseAmt = collapse_state;
                mAnimatedThisFrame = true;
            }
        }
    }

    if (mAnimatedThisFrame) mNeedsLayout = true;
    return continue_animating;
}

void LLLayoutStack::updatePanelRect( LLLayoutPanel* resized_panel, const LLRect& new_rect )
{
    S32 new_dim = (mOrientation == HORIZONTAL)
                    ? new_rect.getWidth()
                    : new_rect.getHeight();
    S32 delta_panel_dim = new_dim - resized_panel->getVisibleDim();
    if (delta_panel_dim == 0) return;

    F32 total_visible_fraction = 0.f;
    F32 delta_auto_resize_headroom = 0.f;
    F32 old_auto_resize_headroom = 0.f;

    LLLayoutPanel* other_resize_panel = NULL;
    LLLayoutPanel* following_panel = NULL;

    BOOST_REVERSE_FOREACH(LLLayoutPanel* panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available...
    {
        if (panelp->mAutoResize)
        {
            old_auto_resize_headroom += (F32)(panelp->mTargetDim - panelp->getRelevantMinDim());
            if (panelp->getVisible() && !panelp->mCollapsed)
            {
                total_visible_fraction += panelp->mFractionalSize;
            }
        }

        if (panelp == resized_panel)
        {
            other_resize_panel = following_panel;
        }

        if (panelp->getVisible() && !panelp->mCollapsed)
        {
            following_panel = panelp;
        }
    }

    if (resized_panel->mAutoResize)
    {
        if (!other_resize_panel || !other_resize_panel->mAutoResize)
        {
            delta_auto_resize_headroom += delta_panel_dim;
        }
    }
    else
    {
        if (!other_resize_panel || other_resize_panel->mAutoResize)
        {
            delta_auto_resize_headroom -= delta_panel_dim;
        }
    }

    F32 fraction_given_up = 0.f;
    F32 fraction_remaining = 1.f;
    F32 new_auto_resize_headroom = old_auto_resize_headroom + delta_auto_resize_headroom;

    enum
    {
        BEFORE_RESIZED_PANEL,
        RESIZED_PANEL,
        NEXT_PANEL,
        AFTER_RESIZED_PANEL
    } which_panel = BEFORE_RESIZED_PANEL;

    for (LLLayoutPanel* panelp : mPanels)
    {
        if (!panelp->getVisible() || panelp->mCollapsed)
        {
            if (panelp->mAutoResize)
            {
                fraction_remaining -= panelp->mFractionalSize;
            }
            continue;
        }

        if (panelp == resized_panel)
        {
            which_panel = RESIZED_PANEL;
        }

        switch(which_panel)
        {
        case BEFORE_RESIZED_PANEL:
            if (panelp->mAutoResize)
            {   // freeze current size as fraction of overall auto_resize space
                F32 fractional_adjustment_factor = new_auto_resize_headroom == 0.f
                                                    ? 1.f
                                                    : old_auto_resize_headroom / new_auto_resize_headroom;
                F32 new_fractional_size = llclamp(panelp->mFractionalSize * fractional_adjustment_factor,
                                                    MIN_FRACTIONAL_SIZE,
                                                    MAX_FRACTIONAL_SIZE);
                fraction_given_up -= new_fractional_size - panelp->mFractionalSize;
                fraction_remaining -= panelp->mFractionalSize;
                panelp->mFractionalSize = new_fractional_size;
                llassert(!llisnan(panelp->mFractionalSize));
            }
            else
            {
                // leave non auto-resize panels alone
            }
            break;
        case RESIZED_PANEL:
            if (panelp->mAutoResize)
            {   // freeze new size as fraction
                F32 new_fractional_size = (new_auto_resize_headroom == 0.f)
                    ? MAX_FRACTIONAL_SIZE
                    : llclamp(total_visible_fraction * (F32)(new_dim - panelp->getRelevantMinDim()) / new_auto_resize_headroom, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE);
                fraction_given_up -= new_fractional_size - panelp->mFractionalSize;
                fraction_remaining -= panelp->mFractionalSize;
                panelp->mFractionalSize = new_fractional_size;
                llassert(!llisnan(panelp->mFractionalSize));
            }
            else
            {   // freeze new size as original size
                panelp->mTargetDim = new_dim;
            }
            which_panel = NEXT_PANEL;
            break;
        case NEXT_PANEL:
            if (panelp->mAutoResize)
            {
                fraction_remaining -= panelp->mFractionalSize;
                if (resized_panel->mAutoResize)
                {
                    panelp->mFractionalSize = llclamp(panelp->mFractionalSize + fraction_given_up, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE);
                    fraction_given_up = 0.f;
                }
                else
                {
                    if (new_auto_resize_headroom < 1.f)
                    {
                        new_auto_resize_headroom = 1.f;
                    }

                    F32 new_fractional_size = llclamp(total_visible_fraction * (F32)(panelp->mTargetDim - panelp->getRelevantMinDim() + delta_auto_resize_headroom)
                                                        / new_auto_resize_headroom,
                                                    MIN_FRACTIONAL_SIZE,
                                                    MAX_FRACTIONAL_SIZE);
                    fraction_given_up -= new_fractional_size - panelp->mFractionalSize;
                    panelp->mFractionalSize = new_fractional_size;
                }
            }
            else
            {
                panelp->mTargetDim -= delta_panel_dim;
            }
            which_panel = AFTER_RESIZED_PANEL;
            break;
        case AFTER_RESIZED_PANEL:
            if (panelp->mAutoResize && fraction_given_up != 0.f)
            {
                panelp->mFractionalSize = llclamp(panelp->mFractionalSize + (panelp->mFractionalSize / fraction_remaining) * fraction_given_up,
                                                MIN_FRACTIONAL_SIZE,
                                                MAX_FRACTIONAL_SIZE);
            }
            break;
        default:
            break;
        }
    }
    updateLayout();
    //normalizeFractionalSizes();
}

// virtual
void LLLayoutStack::reshape(S32 width, S32 height, bool called_from_parent)
{
    mNeedsLayout = true;
    LLView::reshape(width, height, called_from_parent);
}

void LLLayoutStack::updateResizeBarLimits()
{
    LLLayoutPanel* previous_visible_panelp{ nullptr };
    BOOST_REVERSE_FOREACH(LLLayoutPanel* visible_panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available...
    {
        if (!visible_panelp->getVisible() || visible_panelp->mCollapsed)
        {
            visible_panelp->mResizeBar->setVisible(false);
            continue;
        }

        // toggle resize bars based on panel visibility, resizability, etc
        if (previous_visible_panelp
            && (visible_panelp->mUserResize || previous_visible_panelp->mUserResize)                // one of the pair is user resizable
            && (visible_panelp->mAutoResize || visible_panelp->mUserResize)                         // current panel is resizable
            && (previous_visible_panelp->mAutoResize || previous_visible_panelp->mUserResize))      // previous panel is resizable
        {
            visible_panelp->mResizeBar->setVisible(true);
            S32 previous_panel_headroom = previous_visible_panelp->getVisibleDim() - previous_visible_panelp->getRelevantMinDim();
            visible_panelp->mResizeBar->setResizeLimits(visible_panelp->getRelevantMinDim(),
                                                        visible_panelp->getVisibleDim() + previous_panel_headroom);
        }
        else
        {
            visible_panelp->mResizeBar->setVisible(false);
        }

        previous_visible_panelp = visible_panelp;
    }
}