text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    The original JUCE look-and-feel, as used back from 2002 to about 2007ish.
    @see LookAndFeel, LookAndFeel_V2, LookAndFeel_V3
*/
class JUCE_API  LookAndFeel_V1    : public LookAndFeel_V2
{
public:
    LookAndFeel_V1();
    ~LookAndFeel_V1();

    //==============================================================================
    void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour,
                               bool isMouseOverButton, bool isButtonDown) override;

    void drawToggleButton (Graphics&, ToggleButton&, bool isMouseOverButton, bool isButtonDown) override;

    void drawTickBox (Graphics&, Component&, float x, float y, float w, float h,
                      bool ticked, bool isEnabled, bool isMouseOverButton, bool isButtonDown) override;

    void drawProgressBar (Graphics&, ProgressBar&, int width, int height,
                          double progress, const String& textToShow) override;

    //==============================================================================
    void drawScrollbarButton (Graphics&, ScrollBar&, int width, int height,
                              int buttonDirection, bool isScrollbarVertical,
                              bool isMouseOverButton, bool isButtonDown) override;

    void drawScrollbar (Graphics&, ScrollBar&, int x, int y, int width, int height,
                        bool isScrollbarVertical, int thumbStartPosition, int thumbSize,
                        bool isMouseOver, bool isMouseDown) override;

    ImageEffectFilter* getScrollbarEffect() override;

    //==============================================================================
    void drawTextEditorOutline (Graphics&, int width, int height, TextEditor&) override;

    //==============================================================================
    void drawPopupMenuBackground (Graphics&, int width, int height) override;
    void drawMenuBarBackground (Graphics&, int width, int height, bool isMouseOverBar, MenuBarComponent&) override;

    //==============================================================================
    void drawComboBox (Graphics&, int width, int height, bool isButtonDown,
                       int buttonX, int buttonY, int buttonW, int buttonH, ComboBox&) override;

    Font getComboBoxFont (ComboBox&) override;

    //==============================================================================
    void drawLinearSlider (Graphics&, int x, int y, int width, int height,
                           float sliderPos, float minSliderPos, float maxSliderPos,
                           const Slider::SliderStyle, Slider&) override;

    int getSliderThumbRadius (Slider&) override;
    Button* createSliderButton (Slider&, bool isIncrement) override;
    ImageEffectFilter* getSliderEffect (Slider&) override;

    //==============================================================================
    void drawCornerResizer (Graphics&, int w, int h, bool isMouseOver, bool isMouseDragging) override;

    Button* createDocumentWindowButton (int buttonType) override;

    void positionDocumentWindowButtons (DocumentWindow&,
                                        int titleBarX, int titleBarY, int titleBarW, int titleBarH,
                                        Button* minimiseButton, Button* maximiseButton, Button* closeButton,
                                        bool positionTitleBarButtonsOnLeft) override;

private:
    DropShadowEffect scrollbarShadow;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeel_V1)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    For laying out a set of components, where the components have preferred sizes
    and size limits, but where they are allowed to stretch to fill the available
    space.

    For example, if you have a component containing several other components, and
    each one should be given a share of the total size, you could use one of these
    to resize the child components when the parent component is resized. Then
    you could add a StretchableLayoutResizerBar to easily let the user rescale them.

    A StretchableLayoutManager operates only in one dimension, so if you have a set
    of components stacked vertically on top of each other, you'd use one to manage their
    heights. To build up complex arrangements of components, e.g. for applications
    with multiple nested panels, you would use more than one StretchableLayoutManager.
    E.g. by using two (one vertical, one horizontal), you could create a resizable
    spreadsheet-style table.

    E.g.
    @code
    class MyComp  : public Component
    {
        StretchableLayoutManager myLayout;

        MyComp()
        {
            myLayout.setItemLayout (0,          // for item 0
                                    50, 100,    // must be between 50 and 100 pixels in size
                                    -0.6);      // and its preferred size is 60% of the total available space

            myLayout.setItemLayout (1,          // for item 1
                                    -0.2, -0.6, // size must be between 20% and 60% of the available space
                                    50);        // and its preferred size is 50 pixels
        }

        void resized()
        {
            // make a list of two of our child components that we want to reposition
            Component* comps[] = { myComp1, myComp2 };

            // this will position the 2 components, one above the other, to fit
            // vertically into the rectangle provided.
            myLayout.layOutComponents (comps, 2,
                                       0, 0, getWidth(), getHeight(),
                                       true);
        }
    };
    @endcode

    @see StretchableLayoutResizerBar
*/
class JUCE_API  StretchableLayoutManager
{
public:
    //==============================================================================
    /** Creates an empty layout.

        You'll need to add some item properties to the layout before it can be used
        to resize things - see setItemLayout().
    */
    StretchableLayoutManager();

    /** Destructor. */
    ~StretchableLayoutManager();

    //==============================================================================
    /** For a numbered item, this sets its size limits and preferred size.

        @param itemIndex        the index of the item to change.
        @param minimumSize      the minimum size that this item is allowed to be - a positive number
                                indicates an absolute size in pixels. A negative number indicates a
                                proportion of the available space (e.g -0.5 is 50%)
        @param maximumSize      the maximum size that this item is allowed to be - a positive number
                                indicates an absolute size in pixels. A negative number indicates a
                                proportion of the available space
        @param preferredSize    the size that this item would like to be, if there's enough room. A
                                positive number indicates an absolute size in pixels. A negative number
                                indicates a proportion of the available space
        @see getItemLayout
    */
    void setItemLayout (int itemIndex,
                        double minimumSize,
                        double maximumSize,
                        double preferredSize);

    /** For a numbered item, this returns its size limits and preferred size.

        @param itemIndex        the index of the item.
        @param minimumSize      the minimum size that this item is allowed to be - a positive number
                                indicates an absolute size in pixels. A negative number indicates a
                                proportion of the available space (e.g -0.5 is 50%)
        @param maximumSize      the maximum size that this item is allowed to be - a positive number
                                indicates an absolute size in pixels. A negative number indicates a
                                proportion of the available space
        @param preferredSize    the size that this item would like to be, if there's enough room. A
                                positive number indicates an absolute size in pixels. A negative number
                                indicates a proportion of the available space
        @returns false if the item's properties hadn't been set
        @see setItemLayout
    */
    bool getItemLayout (int itemIndex,
                        double& minimumSize,
                        double& maximumSize,
                        double& preferredSize) const;

    /** Clears all the properties that have been set with setItemLayout() and resets
        this object to its initial state.
    */
    void clearAllItems();

    //==============================================================================
    /** Takes a set of components that correspond to the layout's items, and positions
        them to fill a space.

        This will try to give each item its preferred size, whether that's a relative size
        or an absolute one.

        @param components       an array of components that correspond to each of the
                                numbered items that the StretchableLayoutManager object
                                has been told about with setItemLayout()
        @param numComponents    the number of components in the array that is passed-in. This
                                should be the same as the number of items this object has been
                                told about.
        @param x                the left of the rectangle in which the components should
                                be laid out
        @param y                the top of the rectangle in which the components should
                                be laid out
        @param width            the width of the rectangle in which the components should
                                be laid out
        @param height           the height of the rectangle in which the components should
                                be laid out
        @param vertically       if true, the components will be positioned in a vertical stack,
                                so that they fill the height of the rectangle. If false, they
                                will be placed side-by-side in a horizontal line, filling the
                                available width
        @param resizeOtherDimension     if true, this means that the components will have their
                                other dimension resized to fit the space - i.e. if the 'vertically'
                                parameter is true, their x-positions and widths are adjusted to fit
                                the x and width parameters; if 'vertically' is false, their y-positions
                                and heights are adjusted to fit the y and height parameters.
    */
    void layOutComponents (Component** components,
                           int numComponents,
                           int x, int y, int width, int height,
                           bool vertically,
                           bool resizeOtherDimension);

    //==============================================================================
    /** Returns the current position of one of the items.

        This is only a valid call after layOutComponents() has been called, as it
        returns the last position that this item was placed at. If the layout was
        vertical, the value returned will be the y position of the top of the item,
        relative to the top of the rectangle in which the items were placed (so for
        example, item 0 will always have position of 0, even in the rectangle passed
        in to layOutComponents() wasn't at y = 0). If the layout was done horizontally,
        the position returned is the item's left-hand position, again relative to the
        x position of the rectangle used.

        @see getItemCurrentSize, setItemPosition
    */
    int getItemCurrentPosition (int itemIndex) const;

    /** Returns the current size of one of the items.

        This is only meaningful after layOutComponents() has been called, as it
        returns the last size that this item was given. If the layout was done
        vertically, it'll return the item's height in pixels; if it was horizontal,
        it'll return its width.

        @see getItemCurrentRelativeSize
    */
    int getItemCurrentAbsoluteSize (int itemIndex) const;

    /** Returns the current size of one of the items.

        This is only meaningful after layOutComponents() has been called, as it
        returns the last size that this item was given. If the layout was done
        vertically, it'll return a negative value representing the item's height relative
        to the last size used for laying the components out; if the layout was done
        horizontally it'll be the proportion of its width.

        @see getItemCurrentAbsoluteSize
    */
    double getItemCurrentRelativeSize (int itemIndex) const;

    //==============================================================================
    /** Moves one of the items, shifting along any other items as necessary in
        order to get it to the desired position.

        Calling this method will also update the preferred sizes of the items it
        shuffles along, so that they reflect their new positions.

        (This is the method that a StretchableLayoutResizerBar uses to shift the items
        about when it's dragged).

        @param itemIndex        the item to move
        @param newPosition      the absolute position that you'd like this item to move
                                to. The item might not be able to always reach exactly this position,
                                because other items may have minimum sizes that constrain how
                                far it can go
    */
    void setItemPosition (int itemIndex,
                          int newPosition);

private:
    //==============================================================================
    struct ItemLayoutProperties
    {
        int itemIndex;
        int currentSize;
        double minSize, maxSize, preferredSize;
    };

    OwnedArray<ItemLayoutProperties> items;
    int totalSize = 0;

    //==============================================================================
    static int sizeToRealSize (double size, int totalSpace);
    ItemLayoutProperties* getInfoFor (int itemIndex) const;
    void setTotalSize (int newTotalSize);
    int fitComponentsIntoSpace (int startIndex, int endIndex, int availableSpace, int startPos);
    int getMinimumSizeOfItems (int startIndex, int endIndex) const;
    int getMaximumSizeOfItems (int startIndex, int endIndex) const;
    void updatePrefSizesToMatchCurrentPositions();

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableLayoutManager)
};

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

Viewport::Viewport (const String& name)  : Component (name)
{
    // content holder is used to clip the contents so they don't overlap the scrollbars
    addAndMakeVisible (contentHolder);
    contentHolder.setInterceptsMouseClicks (false, true);

    scrollBarThickness = getLookAndFeel().getDefaultScrollbarWidth();

    setInterceptsMouseClicks (false, true);
    setWantsKeyboardFocus (true);

  #if JUCE_ANDROID || JUCE_IOS
    setScrollOnDragEnabled (true);
  #endif

    recreateScrollbars();
}

Viewport::~Viewport()
{
    setScrollOnDragEnabled (false);
    deleteOrRemoveContentComp();
}

//==============================================================================
void Viewport::visibleAreaChanged (const Rectangle<int>&) {}
void Viewport::viewedComponentChanged (Component*) {}

//==============================================================================
void Viewport::deleteOrRemoveContentComp()
{
    if (contentComp != nullptr)
    {
        contentComp->removeComponentListener (this);

        if (deleteContent)
        {
            // This sets the content comp to a null pointer before deleting the old one, in case
            // anything tries to use the old one while it's in mid-deletion..
            ScopedPointer<Component> oldCompDeleter (contentComp);
            contentComp = nullptr;
        }
        else
        {
            contentHolder.removeChildComponent (contentComp);
            contentComp = nullptr;
        }
    }
}

void Viewport::setViewedComponent (Component* const newViewedComponent, const bool deleteComponentWhenNoLongerNeeded)
{
    if (contentComp.get() != newViewedComponent)
    {
        deleteOrRemoveContentComp();
        contentComp = newViewedComponent;
        deleteContent = deleteComponentWhenNoLongerNeeded;

        if (contentComp != nullptr)
        {
            contentHolder.addAndMakeVisible (contentComp);
            setViewPosition (Point<int>());
            contentComp->addComponentListener (this);
        }

        viewedComponentChanged (contentComp);
        updateVisibleArea();
    }
}

void Viewport::recreateScrollbars()
{
    verticalScrollBar = nullptr;
    horizontalScrollBar = nullptr;

    addChildComponent (verticalScrollBar   = createScrollBarComponent (true));
    addChildComponent (horizontalScrollBar = createScrollBarComponent (false));

    getVerticalScrollBar().addListener (this);
    getHorizontalScrollBar().addListener (this);

    resized();
}

int Viewport::getMaximumVisibleWidth() const            { return contentHolder.getWidth(); }
int Viewport::getMaximumVisibleHeight() const           { return contentHolder.getHeight(); }

bool Viewport::canScrollVertically() const noexcept     { return contentComp->getY() < 0 || contentComp->getBottom() > getHeight(); }
bool Viewport::canScrollHorizontally() const noexcept   { return contentComp->getX() < 0 || contentComp->getRight()  > getWidth(); }

Point<int> Viewport::viewportPosToCompPos (Point<int> pos) const
{
    jassert (contentComp != nullptr);

    auto contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds());

    Point<int> p (jmax (jmin (0, contentHolder.getWidth()  - contentBounds.getWidth()),  jmin (0, -(pos.x))),
                  jmax (jmin (0, contentHolder.getHeight() - contentBounds.getHeight()), jmin (0, -(pos.y))));

    return p.transformedBy (contentComp->getTransform().inverted());
}

void Viewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset)
{
    setViewPosition ({ xPixelsOffset, yPixelsOffset });
}

void Viewport::setViewPosition (Point<int> newPosition)
{
    if (contentComp != nullptr)
        contentComp->setTopLeftPosition (viewportPosToCompPos (newPosition));
}

void Viewport::setViewPositionProportionately (const double x, const double y)
{
    if (contentComp != nullptr)
        setViewPosition (jmax (0, roundToInt (x * (contentComp->getWidth()  - getWidth()))),
                         jmax (0, roundToInt (y * (contentComp->getHeight() - getHeight()))));
}

bool Viewport::autoScroll (const int mouseX, const int mouseY, const int activeBorderThickness, const int maximumSpeed)
{
    if (contentComp != nullptr)
    {
        int dx = 0, dy = 0;

        if (getHorizontalScrollBar().isVisible() || canScrollHorizontally())
        {
            if (mouseX < activeBorderThickness)
                dx = activeBorderThickness - mouseX;
            else if (mouseX >= contentHolder.getWidth() - activeBorderThickness)
                dx = (contentHolder.getWidth() - activeBorderThickness) - mouseX;

            if (dx < 0)
                dx = jmax (dx, -maximumSpeed, contentHolder.getWidth() - contentComp->getRight());
            else
                dx = jmin (dx, maximumSpeed, -contentComp->getX());
        }

        if (getVerticalScrollBar().isVisible() || canScrollVertically())
        {
            if (mouseY < activeBorderThickness)
                dy = activeBorderThickness - mouseY;
            else if (mouseY >= contentHolder.getHeight() - activeBorderThickness)
                dy = (contentHolder.getHeight() - activeBorderThickness) - mouseY;

            if (dy < 0)
                dy = jmax (dy, -maximumSpeed, contentHolder.getHeight() - contentComp->getBottom());
            else
                dy = jmin (dy, maximumSpeed, -contentComp->getY());
        }

        if (dx != 0 || dy != 0)
        {
            contentComp->setTopLeftPosition (contentComp->getX() + dx,
                                             contentComp->getY() + dy);

            return true;
        }
    }

    return false;
}

void Viewport::componentMovedOrResized (Component&, bool, bool)
{
    updateVisibleArea();
}

//==============================================================================
typedef AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum> ViewportDragPosition;

struct Viewport::DragToScrollListener   : private MouseListener,
                                          private ViewportDragPosition::Listener
{
    DragToScrollListener (Viewport& v)  : viewport (v)
    {
        viewport.contentHolder.addMouseListener (this, true);
        offsetX.addListener (this);
        offsetY.addListener (this);
        offsetX.behaviour.setMinimumVelocity (60);
        offsetY.behaviour.setMinimumVelocity (60);
    }

    ~DragToScrollListener()
    {
        viewport.contentHolder.removeMouseListener (this);
    }

    void positionChanged (ViewportDragPosition&, double) override
    {
        viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(),
                                                                (int) offsetY.getPosition()));
    }

    void mouseDown (const MouseEvent& e) override
    {
        if (doesMouseEventComponentBlockViewportDrag (e.eventComponent))
            isViewportDragBlocked = true;

        offsetX.setPosition (offsetX.getPosition());
        offsetY.setPosition (offsetY.getPosition());
        ++numTouches;
    }

    void mouseDrag (const MouseEvent& e) override
    {
        if (numTouches == 1 && ! isViewportDragBlocked)
        {
            auto totalOffset = e.getOffsetFromDragStart().toFloat();

            if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f)
            {
                isDragging = true;

                originalViewPos = viewport.getViewPosition();
                offsetX.setPosition (0.0);
                offsetX.beginDrag();
                offsetY.setPosition (0.0);
                offsetY.beginDrag();
            }

            if (isDragging)
            {
                offsetX.drag (totalOffset.x);
                offsetY.drag (totalOffset.y);
            }
        }
    }

    void mouseUp (const MouseEvent& e) override
    {
        if (doesMouseEventComponentBlockViewportDrag (e.eventComponent))
            isViewportDragBlocked = false;

        if (--numTouches <= 0)
        {
            offsetX.endDrag();
            offsetY.endDrag();
            isDragging = false;
            numTouches = 0;
        }
    }

    bool doesMouseEventComponentBlockViewportDrag (const Component* eventComp)
    {
        for (auto c = eventComp; c != nullptr && c != &viewport; c = c->getParentComponent())
            if (c->getViewportIgnoreDragFlag())
                return true;

        return false;
    }

    Viewport& viewport;
    ViewportDragPosition offsetX, offsetY;
    Point<int> originalViewPos;
    int numTouches = 0;
    bool isDragging = false;
    bool isViewportDragBlocked = false;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener)
};

void Viewport::setScrollOnDragEnabled (bool shouldScrollOnDrag)
{
    if (isScrollOnDragEnabled() != shouldScrollOnDrag)
    {
        if (shouldScrollOnDrag)
            dragToScrollListener = new DragToScrollListener (*this);
        else
            dragToScrollListener = nullptr;
    }
}

bool Viewport::isScrollOnDragEnabled() const noexcept
{
    return dragToScrollListener != nullptr;
}

bool Viewport::isCurrentlyScrollingOnDrag() const noexcept
{
    return dragToScrollListener != nullptr && dragToScrollListener->isDragging;
}

//==============================================================================
void Viewport::lookAndFeelChanged()
{
    if (! customScrollBarThickness)
    {
        scrollBarThickness = getLookAndFeel().getDefaultScrollbarWidth();
        resized();
    }
}

void Viewport::resized()
{
    updateVisibleArea();
}

//==============================================================================
void Viewport::updateVisibleArea()
{
    auto scrollbarWidth = getScrollBarThickness();
    const bool canShowAnyBars = getWidth() > scrollbarWidth && getHeight() > scrollbarWidth;
    const bool canShowHBar = showHScrollbar && canShowAnyBars;
    const bool canShowVBar = showVScrollbar && canShowAnyBars;

    bool hBarVisible = false, vBarVisible = false;
    Rectangle<int> contentArea;

    for (int i = 3; --i >= 0;)
    {
        hBarVisible = canShowHBar && ! getHorizontalScrollBar().autoHides();
        vBarVisible = canShowVBar && ! getVerticalScrollBar().autoHides();
        contentArea = getLocalBounds();

        if (contentComp != nullptr && ! contentArea.contains (contentComp->getBounds()))
        {
            hBarVisible = canShowHBar && (hBarVisible || contentComp->getX() < 0 || contentComp->getRight() > contentArea.getWidth());
            vBarVisible = canShowVBar && (vBarVisible || contentComp->getY() < 0 || contentComp->getBottom() > contentArea.getHeight());

            if (vBarVisible)
                contentArea.setWidth (getWidth() - scrollbarWidth);

            if (hBarVisible)
                contentArea.setHeight (getHeight() - scrollbarWidth);

            if (! contentArea.contains (contentComp->getBounds()))
            {
                hBarVisible = canShowHBar && (hBarVisible || contentComp->getRight() > contentArea.getWidth());
                vBarVisible = canShowVBar && (vBarVisible || contentComp->getBottom() > contentArea.getHeight());
            }
        }

        if (vBarVisible)  contentArea.setWidth  (getWidth()  - scrollbarWidth);
        if (hBarVisible)  contentArea.setHeight (getHeight() - scrollbarWidth);

        if (contentComp == nullptr)
        {
            contentHolder.setBounds (contentArea);
            break;
        }

        auto oldContentBounds = contentComp->getBounds();
        contentHolder.setBounds (contentArea);

        // If the content has changed its size, that might affect our scrollbars, so go round again and re-caclulate..
        if (oldContentBounds == contentComp->getBounds())
            break;
    }

    Rectangle<int> contentBounds;
    if (contentComp != nullptr)
        contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds());

    auto visibleOrigin = -contentBounds.getPosition();

    auto& hbar = getHorizontalScrollBar();
    auto& vbar = getVerticalScrollBar();

    hbar.setBounds (0, contentArea.getHeight(), contentArea.getWidth(), scrollbarWidth);
    hbar.setRangeLimits (0.0, contentBounds.getWidth());
    hbar.setCurrentRange (visibleOrigin.x, contentArea.getWidth());
    hbar.setSingleStepSize (singleStepX);
    hbar.cancelPendingUpdate();

    if (canShowHBar && ! hBarVisible)
        visibleOrigin.setX (0);

    vbar.setBounds (contentArea.getWidth(), 0, scrollbarWidth, contentArea.getHeight());
    vbar.setRangeLimits (0.0, contentBounds.getHeight());
    vbar.setCurrentRange (visibleOrigin.y, contentArea.getHeight());
    vbar.setSingleStepSize (singleStepY);
    vbar.cancelPendingUpdate();

    if (canShowVBar && ! vBarVisible)
        visibleOrigin.setY (0);

    // Force the visibility *after* setting the ranges to avoid flicker caused by edge conditions in the numbers.
    hbar.setVisible (hBarVisible);
    vbar.setVisible (vBarVisible);

    if (contentComp != nullptr)
    {
        auto newContentCompPos = viewportPosToCompPos (visibleOrigin);

        if (contentComp->getBounds().getPosition() != newContentCompPos)
        {
            contentComp->setTopLeftPosition (newContentCompPos);  // (this will re-entrantly call updateVisibleArea again)
            return;
        }
    }

    const Rectangle<int> visibleArea (visibleOrigin.x, visibleOrigin.y,
                                      jmin (contentBounds.getWidth()  - visibleOrigin.x, contentArea.getWidth()),
                                      jmin (contentBounds.getHeight() - visibleOrigin.y, contentArea.getHeight()));

    if (lastVisibleArea != visibleArea)
    {
        lastVisibleArea = visibleArea;
        visibleAreaChanged (visibleArea);
    }

    hbar.handleUpdateNowIfNeeded();
    vbar.handleUpdateNowIfNeeded();
}

//==============================================================================
void Viewport::setSingleStepSizes (const int stepX, const int stepY)
{
    if (singleStepX != stepX || singleStepY != stepY)
    {
        singleStepX = stepX;
        singleStepY = stepY;
        updateVisibleArea();
    }
}

void Viewport::setScrollBarsShown (const bool showVerticalScrollbarIfNeeded,
                                   const bool showHorizontalScrollbarIfNeeded,
                                   const bool allowVerticalScrollingWithoutScrollbar,
                                   const bool allowHorizontalScrollingWithoutScrollbar)
{
    allowScrollingWithoutScrollbarV = allowVerticalScrollingWithoutScrollbar;
    allowScrollingWithoutScrollbarH = allowHorizontalScrollingWithoutScrollbar;

    if (showVScrollbar != showVerticalScrollbarIfNeeded
         || showHScrollbar != showHorizontalScrollbarIfNeeded)
    {
        showVScrollbar = showVerticalScrollbarIfNeeded;
        showHScrollbar = showHorizontalScrollbarIfNeeded;
        updateVisibleArea();
    }
}

void Viewport::setScrollBarThickness (const int thickness)
{
    int newThickness;

    // To stay compatible with the previous code: use the
    // default thickness if thickness parameter is zero
    // or negative
    if (thickness <= 0)
    {
        customScrollBarThickness = false;
        newThickness = getLookAndFeel().getDefaultScrollbarWidth();
    }
    else
    {
        customScrollBarThickness = true;
        newThickness = thickness;
    }

    if (scrollBarThickness != newThickness)
    {
        scrollBarThickness = newThickness;
        updateVisibleArea();
    }
}

int Viewport::getScrollBarThickness() const
{
    return scrollBarThickness;
}

void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart)
{
    const int newRangeStartInt = roundToInt (newRangeStart);

    if (scrollBarThatHasMoved == horizontalScrollBar)
    {
        setViewPosition (newRangeStartInt, getViewPositionY());
    }
    else if (scrollBarThatHasMoved == verticalScrollBar)
    {
        setViewPosition (getViewPositionX(), newRangeStartInt);
    }
}

void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
{
    if (! useMouseWheelMoveIfNeeded (e, wheel))
        Component::mouseWheelMove (e, wheel);
}

static int rescaleMouseWheelDistance (float distance, int singleStepSize) noexcept
{
    if (distance == 0.0f)
        return 0;

    distance *= 14.0f * singleStepSize;

    return roundToInt (distance < 0 ? jmin (distance, -1.0f)
                                    : jmax (distance,  1.0f));
}

bool Viewport::useMouseWheelMoveIfNeeded (const MouseEvent& e, const MouseWheelDetails& wheel)
{
    if (! (e.mods.isAltDown() || e.mods.isCtrlDown() || e.mods.isCommandDown()))
    {
        const bool canScrollVert = (allowScrollingWithoutScrollbarV || getVerticalScrollBar().isVisible());
        const bool canScrollHorz = (allowScrollingWithoutScrollbarH || getHorizontalScrollBar().isVisible());

        if (canScrollHorz || canScrollVert)
        {
            auto deltaX = rescaleMouseWheelDistance (wheel.deltaX, singleStepX);
            auto deltaY = rescaleMouseWheelDistance (wheel.deltaY, singleStepY);

            auto pos = getViewPosition();

            if (deltaX != 0 && deltaY != 0 && canScrollHorz && canScrollVert)
            {
                pos.x -= deltaX;
                pos.y -= deltaY;
            }
            else if (canScrollHorz && (deltaX != 0 || e.mods.isShiftDown() || ! canScrollVert))
            {
                pos.x -= deltaX != 0 ? deltaX : deltaY;
            }
            else if (canScrollVert && deltaY != 0)
            {
                pos.y -= deltaY;
            }

            if (pos != getViewPosition())
            {
                setViewPosition (pos);
                return true;
            }
        }
    }

    return false;
}

static bool isUpDownKeyPress (const KeyPress& key)
{
    return key == KeyPress::upKey
        || key == KeyPress::downKey
        || key == KeyPress::pageUpKey
        || key == KeyPress::pageDownKey
        || key == KeyPress::homeKey
        || key == KeyPress::endKey;
}

static bool isLeftRightKeyPress (const KeyPress& key)
{
    return key == KeyPress::leftKey
        || key == KeyPress::rightKey;
}

bool Viewport::keyPressed (const KeyPress& key)
{
    const bool isUpDownKey = isUpDownKeyPress (key);

    if (getVerticalScrollBar().isVisible() && isUpDownKey)
        return getVerticalScrollBar().keyPressed (key);

    const bool isLeftRightKey = isLeftRightKeyPress (key);

    if (getHorizontalScrollBar().isVisible() && (isUpDownKey || isLeftRightKey))
        return getHorizontalScrollBar().keyPressed (key);

    return false;
}

bool Viewport::respondsToKey (const KeyPress& key)
{
    return isUpDownKeyPress (key) || isLeftRightKeyPress (key);
}

ScrollBar* Viewport::createScrollBarComponent (bool isVertical)
{
    return new ScrollBar (isVertical);
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    A panel which holds a vertical stack of components which can be expanded
    and contracted.

    Each section has its own header bar which can be dragged up and down
    to resize it, or double-clicked to fully expand that section.
*/
class JUCE_API  ConcertinaPanel   : public Component
{
public:
    /** Creates an empty concertina panel.
        You can call addPanel() to add some components to it.
    */
    ConcertinaPanel();

    /** Destructor. */
    ~ConcertinaPanel();

    /** Adds a component to the panel.
        @param insertIndex          the index at which this component will be inserted, or
                                    -1 to append it to the end of the list.
        @param component            the component that will be shown
        @param takeOwnership        if true, then the ConcertinaPanel will take ownership
                                    of the content component, and will delete it later when
                                    it's no longer needed. If false, it won't delete it, and
                                    you must make sure it doesn't get deleted while in use.
    */
    void addPanel (int insertIndex, Component* component, bool takeOwnership);

    /** Removes one of the panels.
        If the takeOwnership flag was set when the panel was added, then
        this will also delete the component.
    */
    void removePanel (Component* panelComponent);

    /** Returns the number of panels.
        @see getPanel
    */
    int getNumPanels() const noexcept;

    /** Returns one of the panels.
        @see getNumPanels()
    */
    Component* getPanel (int index) const noexcept;

    /** Resizes one of the panels.
        The panelComponent must point to  a valid panel component.
        If animate is true, the panels will be animated into their new positions;
        if false, they will just be immediately resized.
    */
    bool setPanelSize (Component* panelComponent, int newHeight, bool animate);

    /** Attempts to make one of the panels full-height.
        The panelComponent must point to  a valid panel component.
        If this component has had a maximum size set, then it will be
        expanded to that size. Otherwise, it'll fill as much of the total
        space as possible.
    */
    bool expandPanelFully (Component* panelComponent, bool animate);

    /** Sets a maximum size for one of the panels. */
    void setMaximumPanelSize (Component* panelComponent, int maximumSize);

    /** Sets the height of the header section for one of the panels. */
    void setPanelHeaderSize (Component* panelComponent, int headerSize);

    /** Sets a custom header Component for one of the panels.

        @param panelComponent           the panel component to add the custom header to.
        @param customHeaderComponent    the custom component to use for the panel header.
                                        This can be nullptr to clear the custom header component
                                        and just use the standard LookAndFeel panel.
        @param takeOwnership            if true, then the PanelHolder will take ownership
                                        of the custom header component, and will delete it later when
                                        it's no longer needed. If false, it won't delete it, and
                                        you must make sure it doesn't get deleted while in use.
     */
    void setCustomPanelHeader (Component* panelComponent, Component* customHeaderComponent, bool takeOwnership);

    //==============================================================================
    /** This abstract base class is implemented by LookAndFeel classes. */
    struct JUCE_API  LookAndFeelMethods
    {
        virtual ~LookAndFeelMethods() {}

        virtual void drawConcertinaPanelHeader (Graphics&, const Rectangle<int>& area,
                                                bool isMouseOver, bool isMouseDown,
                                                ConcertinaPanel&, Component&) = 0;
    };

private:
    void resized() override;

    class PanelHolder;
    struct PanelSizes;
    friend class PanelHolder;
    friend struct PanelSizes;
    friend struct ContainerDeletePolicy<PanelSizes>;
    friend struct ContainerDeletePolicy<PanelHolder>;

    ScopedPointer<PanelSizes> currentSizes;
    OwnedArray<PanelHolder> holders;
    ComponentAnimator animator;
    int headerHeight;

    int indexOfComp (Component*) const noexcept;
    PanelSizes getFittedSizes() const;
    void applyLayout (const PanelSizes&, bool animate);
    void setLayout (const PanelSizes&, bool animate);
    void panelHeaderDoubleClicked (Component*);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaPanel)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace TabbedComponentHelpers
{
    const Identifier deleteComponentId ("deleteByTabComp_");

    static void deleteIfNecessary (Component* const comp)
    {
        if (comp != nullptr && (bool) comp->getProperties() [deleteComponentId])
            delete comp;
    }

    static Rectangle<int> getTabArea (Rectangle<int>& content, BorderSize<int>& outline,
                                      const TabbedButtonBar::Orientation orientation, const int tabDepth)
    {
        switch (orientation)
        {
            case TabbedButtonBar::TabsAtTop:    outline.setTop (0);     return content.removeFromTop (tabDepth);
            case TabbedButtonBar::TabsAtBottom: outline.setBottom (0);  return content.removeFromBottom (tabDepth);
            case TabbedButtonBar::TabsAtLeft:   outline.setLeft (0);    return content.removeFromLeft (tabDepth);
            case TabbedButtonBar::TabsAtRight:  outline.setRight (0);   return content.removeFromRight (tabDepth);
            default: jassertfalse; break;
        }

        return Rectangle<int>();
    }
}

//==============================================================================
struct TabbedComponent::ButtonBar  : public TabbedButtonBar
{
    ButtonBar (TabbedComponent& tabComp, TabbedButtonBar::Orientation o)
        : TabbedButtonBar (o), owner (tabComp)
    {
    }

    void currentTabChanged (int newCurrentTabIndex, const String& newTabName)
    {
        owner.changeCallback (newCurrentTabIndex, newTabName);
    }

    void popupMenuClickOnTab (int tabIndex, const String& tabName)
    {
        owner.popupMenuClickOnTab (tabIndex, tabName);
    }

    Colour getTabBackgroundColour (const int tabIndex)
    {
        return owner.tabs->getTabBackgroundColour (tabIndex);
    }

    TabBarButton* createTabButton (const String& tabName, int tabIndex)
    {
        return owner.createTabButton (tabName, tabIndex);
    }

    TabbedComponent& owner;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonBar)
};

//==============================================================================
TabbedComponent::TabbedComponent (const TabbedButtonBar::Orientation orientation)
{
    addAndMakeVisible (tabs = new ButtonBar (*this, orientation));
}

TabbedComponent::~TabbedComponent()
{
    clearTabs();
    tabs = nullptr;
}

//==============================================================================
void TabbedComponent::setOrientation (const TabbedButtonBar::Orientation orientation)
{
    tabs->setOrientation (orientation);
    resized();
}

TabbedButtonBar::Orientation TabbedComponent::getOrientation() const noexcept
{
    return tabs->getOrientation();
}

void TabbedComponent::setTabBarDepth (const int newDepth)
{
    if (tabDepth != newDepth)
    {
        tabDepth = newDepth;
        resized();
    }
}

TabBarButton* TabbedComponent::createTabButton (const String& tabName, const int /*tabIndex*/)
{
    return new TabBarButton (tabName, *tabs);
}

//==============================================================================
void TabbedComponent::clearTabs()
{
    if (panelComponent != nullptr)
    {
        panelComponent->setVisible (false);
        removeChildComponent (panelComponent);
        panelComponent = nullptr;
    }

    tabs->clearTabs();

    for (int i = contentComponents.size(); --i >= 0;)
        TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (i));

    contentComponents.clear();
}

void TabbedComponent::addTab (const String& tabName,
                              Colour tabBackgroundColour,
                              Component* const contentComponent,
                              const bool deleteComponentWhenNotNeeded,
                              const int insertIndex)
{
    contentComponents.insert (insertIndex, WeakReference<Component> (contentComponent));

    if (deleteComponentWhenNotNeeded && contentComponent != nullptr)
        contentComponent->getProperties().set (TabbedComponentHelpers::deleteComponentId, true);

    tabs->addTab (tabName, tabBackgroundColour, insertIndex);
    resized();
}

void TabbedComponent::setTabName (const int tabIndex, const String& newName)
{
    tabs->setTabName (tabIndex, newName);
}

void TabbedComponent::removeTab (const int tabIndex)
{
    if (isPositiveAndBelow (tabIndex, contentComponents.size()))
    {
        TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (tabIndex));
        contentComponents.remove (tabIndex);
        tabs->removeTab (tabIndex);
    }
}

void TabbedComponent::moveTab (const int currentIndex, const int newIndex, const bool animate)
{
    contentComponents.move (currentIndex, newIndex);
    tabs->moveTab (currentIndex, newIndex, animate);
}

int TabbedComponent::getNumTabs() const
{
    return tabs->getNumTabs();
}

StringArray TabbedComponent::getTabNames() const
{
    return tabs->getTabNames();
}

Component* TabbedComponent::getTabContentComponent (const int tabIndex) const noexcept
{
    return contentComponents [tabIndex];
}

Colour TabbedComponent::getTabBackgroundColour (const int tabIndex) const noexcept
{
    return tabs->getTabBackgroundColour (tabIndex);
}

void TabbedComponent::setTabBackgroundColour (const int tabIndex, Colour newColour)
{
    tabs->setTabBackgroundColour (tabIndex, newColour);

    if (getCurrentTabIndex() == tabIndex)
        repaint();
}

void TabbedComponent::setCurrentTabIndex (const int newTabIndex, const bool sendChangeMessage)
{
    tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage);
}

int TabbedComponent::getCurrentTabIndex() const
{
    return tabs->getCurrentTabIndex();
}

String TabbedComponent::getCurrentTabName() const
{
    return tabs->getCurrentTabName();
}

void TabbedComponent::setOutline (const int thickness)
{
    outlineThickness = thickness;
    resized();
    repaint();
}

void TabbedComponent::setIndent (const int indentThickness)
{
    edgeIndent = indentThickness;
    resized();
    repaint();
}

void TabbedComponent::paint (Graphics& g)
{
    g.fillAll (findColour (backgroundColourId));

    auto content = getLocalBounds();
    BorderSize<int> outline (outlineThickness);
    TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth);

    g.reduceClipRegion (content);
    g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex()));

    if (outlineThickness > 0)
    {
        RectangleList<int> rl (content);
        rl.subtract (outline.subtractedFrom (content));

        g.reduceClipRegion (rl);
        g.fillAll (findColour (outlineColourId));
    }
}

void TabbedComponent::resized()
{
    auto content = getLocalBounds();
    BorderSize<int> outline (outlineThickness);

    tabs->setBounds (TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth));
    content = BorderSize<int> (edgeIndent).subtractedFrom (outline.subtractedFrom (content));

    for (int i = contentComponents.size(); --i >= 0;)
        if (Component* c = contentComponents.getReference(i))
            c->setBounds (content);
}

void TabbedComponent::lookAndFeelChanged()
{
    for (int i = contentComponents.size(); --i >= 0;)
        if (Component* c = contentComponents.getReference(i))
            c->lookAndFeelChanged();
}

void TabbedComponent::changeCallback (const int newCurrentTabIndex, const String& newTabName)
{
    auto* newPanelComp = getTabContentComponent (getCurrentTabIndex());

    if (newPanelComp != panelComponent)
    {
        if (panelComponent != nullptr)
        {
            panelComponent->setVisible (false);
            removeChildComponent (panelComponent);
        }

        panelComponent = newPanelComp;

        if (panelComponent != nullptr)
        {
            // do these ops as two stages instead of addAndMakeVisible() so that the
            // component has always got a parent when it gets the visibilityChanged() callback
            addChildComponent (panelComponent);
            panelComponent->sendLookAndFeelChange();
            panelComponent->setVisible (true);
            panelComponent->toFront (true);
        }

        repaint();
    }

    resized();
    currentTabChanged (newCurrentTabIndex, newTabName);
}

void TabbedComponent::currentTabChanged (int, const String&) {}
void TabbedComponent::popupMenuClickOnTab (int, const String&) {}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

TabBarButton::TabBarButton (const String& name, TabbedButtonBar& bar)
    : Button (name), owner (bar)
{
    setWantsKeyboardFocus (false);
}

TabBarButton::~TabBarButton() {}

int TabBarButton::getIndex() const                      { return owner.indexOfTabButton (this); }
Colour TabBarButton::getTabBackgroundColour() const     { return owner.getTabBackgroundColour (getIndex()); }
bool TabBarButton::isFrontTab() const                   { return getToggleState(); }

void TabBarButton::paintButton (Graphics& g, const bool isMouseOverButton, const bool isButtonDown)
{
    getLookAndFeel().drawTabButton (*this, g, isMouseOverButton, isButtonDown);
}

void TabBarButton::clicked (const ModifierKeys& mods)
{
    if (mods.isPopupMenu())
        owner.popupMenuClickOnTab (getIndex(), getButtonText());
    else
        owner.setCurrentTabIndex (getIndex());
}

bool TabBarButton::hitTest (int mx, int my)
{
    auto area = getActiveArea();

    if (owner.isVertical())
    {
        if (isPositiveAndBelow (mx, getWidth())
             && my >= area.getY() + overlapPixels && my < area.getBottom() - overlapPixels)
            return true;
    }
    else
    {
        if (isPositiveAndBelow (my, getHeight())
             && mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels)
            return true;
    }

    Path p;
    getLookAndFeel().createTabButtonShape (*this, p, false, false);

    return p.contains ((float) (mx - area.getX()),
                       (float) (my - area.getY()));
}

int TabBarButton::getBestTabLength (const int depth)
{
    return getLookAndFeel().getTabButtonBestWidth (*this, depth);
}

void TabBarButton::calcAreas (Rectangle<int>& extraComp, Rectangle<int>& textArea) const
{
    auto& lf = getLookAndFeel();
    textArea = getActiveArea();

    auto depth = owner.isVertical() ? textArea.getWidth() : textArea.getHeight();
    auto overlap = lf.getTabButtonOverlap (depth);

    if (overlap > 0)
    {
        if (owner.isVertical())
            textArea.reduce (0, overlap);
        else
            textArea.reduce (overlap, 0);
    }

    if (extraComponent != nullptr)
    {
        extraComp = lf.getTabButtonExtraComponentBounds (*this, textArea, *extraComponent);

        auto orientation = owner.getOrientation();

        if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight)
        {
            if (extraComp.getCentreY() > textArea.getCentreY())
                textArea.setBottom (jmin (textArea.getBottom(), extraComp.getY()));
            else
                textArea.setTop (jmax (textArea.getY(), extraComp.getBottom()));
        }
        else
        {
            if (extraComp.getCentreX() > textArea.getCentreX())
                textArea.setRight (jmin (textArea.getRight(), extraComp.getX()));
            else
                textArea.setLeft (jmax (textArea.getX(), extraComp.getRight()));
        }
    }
}

Rectangle<int> TabBarButton::getTextArea() const
{
    Rectangle<int> extraComp, textArea;
    calcAreas (extraComp, textArea);
    return textArea;
}

Rectangle<int> TabBarButton::getActiveArea() const
{
    auto r = getLocalBounds();
    auto spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
    auto orientation = owner.getOrientation();

    if (orientation != TabbedButtonBar::TabsAtLeft)      r.removeFromRight  (spaceAroundImage);
    if (orientation != TabbedButtonBar::TabsAtRight)     r.removeFromLeft   (spaceAroundImage);
    if (orientation != TabbedButtonBar::TabsAtBottom)    r.removeFromTop    (spaceAroundImage);
    if (orientation != TabbedButtonBar::TabsAtTop)       r.removeFromBottom (spaceAroundImage);

    return r;
}

void TabBarButton::setExtraComponent (Component* comp, ExtraComponentPlacement placement)
{
    jassert (extraCompPlacement == beforeText || extraCompPlacement == afterText);
    extraCompPlacement = placement;
    addAndMakeVisible (extraComponent = comp);
    resized();
}

void TabBarButton::childBoundsChanged (Component* c)
{
    if (c == extraComponent)
    {
        owner.resized();
        resized();
    }
}

void TabBarButton::resized()
{
    if (extraComponent != nullptr)
    {
        Rectangle<int> extraComp, textArea;
        calcAreas (extraComp, textArea);

        if (! extraComp.isEmpty())
            extraComponent->setBounds (extraComp);
    }
}

//==============================================================================
class TabbedButtonBar::BehindFrontTabComp  : public Component,
                                             public Button::Listener
{
public:
    BehindFrontTabComp (TabbedButtonBar& tb)  : owner (tb)
    {
        setInterceptsMouseClicks (false, false);
    }

    void paint (Graphics& g) override
    {
        getLookAndFeel().drawTabAreaBehindFrontButton (owner, g, getWidth(), getHeight());
    }

    void enablementChanged() override
    {
        repaint();
    }

    void buttonClicked (Button*) override
    {
        owner.showExtraItemsMenu();
    }

private:
    TabbedButtonBar& owner;

    JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp)
};

//==============================================================================
TabbedButtonBar::TabbedButtonBar (Orientation orientationToUse)
    : orientation (orientationToUse)
{
    setInterceptsMouseClicks (false, true);
    addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
    setFocusContainer (true);
}

TabbedButtonBar::~TabbedButtonBar()
{
    tabs.clear();
    extraTabsButton = nullptr;
}

//==============================================================================
void TabbedButtonBar::setOrientation (const Orientation newOrientation)
{
    orientation = newOrientation;

    for (auto* child : getChildren())
        child->resized();

    resized();
}

TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
{
    return new TabBarButton (name, *this);
}

void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
{
    minimumScale = newMinimumScale;
    resized();
}

//==============================================================================
void TabbedButtonBar::clearTabs()
{
    tabs.clear();
    extraTabsButton = nullptr;
    setCurrentTabIndex (-1);
}

void TabbedButtonBar::addTab (const String& tabName,
                              Colour tabBackgroundColour,
                              int insertIndex)
{
    jassert (tabName.isNotEmpty()); // you have to give them all a name..

    if (tabName.isNotEmpty())
    {
        if (! isPositiveAndBelow (insertIndex, tabs.size()))
            insertIndex = tabs.size();

        auto* currentTab = tabs[currentTabIndex];

        auto* newTab = new TabInfo();
        newTab->name = tabName;
        newTab->colour = tabBackgroundColour;
        newTab->button = createTabButton (tabName, insertIndex);
        jassert (newTab->button != nullptr);

        tabs.insert (insertIndex, newTab);
        currentTabIndex = tabs.indexOf (currentTab);
        addAndMakeVisible (newTab->button, insertIndex);

        resized();

        if (currentTabIndex < 0)
            setCurrentTabIndex (0);
    }
}

void TabbedButtonBar::setTabName (int tabIndex, const String& newName)
{
    if (auto* tab = tabs[tabIndex])
    {
        if (tab->name != newName)
        {
            tab->name = newName;
            tab->button->setButtonText (newName);
            resized();
        }
    }
}

void TabbedButtonBar::removeTab (const int indexToRemove, const bool animate)
{
    if (isPositiveAndBelow (indexToRemove, tabs.size()))
    {
        auto oldSelectedIndex = currentTabIndex;

        if (indexToRemove == currentTabIndex)
            oldSelectedIndex = -1;
        else if (indexToRemove < oldSelectedIndex)
            --oldSelectedIndex;

        tabs.remove (indexToRemove);

        setCurrentTabIndex (oldSelectedIndex);
        updateTabPositions (animate);
    }
}

void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex, const bool animate)
{
    auto* currentTab = tabs[currentTabIndex];
    tabs.move (currentIndex, newIndex);
    currentTabIndex = tabs.indexOf (currentTab);
    updateTabPositions (animate);
}

int TabbedButtonBar::getNumTabs() const
{
    return tabs.size();
}

String TabbedButtonBar::getCurrentTabName() const
{
    if (auto* tab = tabs [currentTabIndex])
        return tab->name;

    return {};
}

StringArray TabbedButtonBar::getTabNames() const
{
    StringArray names;

    for (auto* t : tabs)
        names.add (t->name);

    return names;
}

void TabbedButtonBar::setCurrentTabIndex (int newIndex, bool shouldSendChangeMessage)
{
    if (currentTabIndex != newIndex)
    {
        if (! isPositiveAndBelow (newIndex, tabs.size()))
            newIndex = -1;

        currentTabIndex = newIndex;

        for (int i = 0; i < tabs.size(); ++i)
            tabs.getUnchecked(i)->button->setToggleState (i == newIndex, dontSendNotification);

        resized();

        if (shouldSendChangeMessage)
            sendChangeMessage();

        currentTabChanged (newIndex, getCurrentTabName());
    }
}

TabBarButton* TabbedButtonBar::getTabButton (const int index) const
{
    if (auto* tab = tabs[index])
        return static_cast<TabBarButton*> (tab->button);

    return nullptr;
}

int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
{
    for (int i = tabs.size(); --i >= 0;)
        if (tabs.getUnchecked(i)->button == button)
            return i;

    return -1;
}

Rectangle<int> TabbedButtonBar::getTargetBounds (TabBarButton* button) const
{
    if (button == nullptr || indexOfTabButton (button) == -1)
        return {};

    auto& animator = Desktop::getInstance().getAnimator();

    return animator.isAnimating (button) ? animator.getComponentDestination (button)
                                         : button->getBounds();
}

void TabbedButtonBar::lookAndFeelChanged()
{
    extraTabsButton = nullptr;
    resized();
}

void TabbedButtonBar::paint (Graphics& g)
{
    getLookAndFeel().drawTabbedButtonBarBackground (*this, g);
}

void TabbedButtonBar::resized()
{
    updateTabPositions (false);
}

//==============================================================================
void TabbedButtonBar::updateTabPositions (bool animate)
{
    auto& lf = getLookAndFeel();

    auto depth = getWidth();
    auto length = getHeight();

    if (! isVertical())
        std::swap (depth, length);

    auto overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;

    auto totalLength = jmax (0, overlap);
    auto numVisibleButtons = tabs.size();

    for (int i = 0; i < tabs.size(); ++i)
    {
        auto* tb = tabs.getUnchecked(i)->button.get();

        totalLength += tb->getBestTabLength (depth) - overlap;
        tb->overlapPixels = jmax (0, overlap / 2);
    }

    double scale = 1.0;

    if (totalLength > length)
        scale = jmax (minimumScale, length / (double) totalLength);

    const bool isTooBig = (int) (totalLength * scale) > length;
    int tabsButtonPos = 0;

    if (isTooBig)
    {
        if (extraTabsButton == nullptr)
        {
            addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
            extraTabsButton->addListener (behindFrontTab);
            extraTabsButton->setAlwaysOnTop (true);
            extraTabsButton->setTriggeredOnMouseDown (true);
        }

        auto buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
        extraTabsButton->setSize (buttonSize, buttonSize);

        if (isVertical())
        {
            tabsButtonPos = getHeight() - buttonSize / 2 - 1;
            extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
        }
        else
        {
            tabsButtonPos = getWidth() - buttonSize / 2 - 1;
            extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
        }

        totalLength = 0;

        for (int i = 0; i < tabs.size(); ++i)
        {
            auto* tb = tabs.getUnchecked(i)->button.get();
            auto newLength = totalLength + tb->getBestTabLength (depth);

            if (i > 0 && newLength * minimumScale > tabsButtonPos)
            {
                totalLength += overlap;
                break;
            }

            numVisibleButtons = i + 1;
            totalLength = newLength - overlap;
        }

        scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
    }
    else
    {
        extraTabsButton = nullptr;
    }

    int pos = 0;

    TabBarButton* frontTab = nullptr;
    auto& animator = Desktop::getInstance().getAnimator();

    for (int i = 0; i < tabs.size(); ++i)
    {
        if (auto* tb = getTabButton (i))
        {
            auto bestLength = roundToInt (scale * tb->getBestTabLength (depth));

            if (i < numVisibleButtons)
            {
                auto newBounds = isVertical() ? Rectangle<int> (0, pos, getWidth(), bestLength)
                                              : Rectangle<int> (pos, 0, bestLength, getHeight());

                if (animate)
                {
                    animator.animateComponent (tb, newBounds, 1.0f, 200, false, 3.0, 0.0);
                }
                else
                {
                    animator.cancelAnimation (tb, false);
                    tb->setBounds (newBounds);
                }

                tb->toBack();

                if (i == currentTabIndex)
                    frontTab = tb;

                tb->setVisible (true);
            }
            else
            {
                tb->setVisible (false);
            }

            pos += bestLength - overlap;
        }
    }

    behindFrontTab->setBounds (getLocalBounds());

    if (frontTab != nullptr)
    {
        frontTab->toFront (false);
        behindFrontTab->toBehind (frontTab);
    }
}

//==============================================================================
Colour TabbedButtonBar::getTabBackgroundColour (int tabIndex)
{
    if (auto* tab = tabs[tabIndex])
        return tab->colour;

    return Colours::transparentBlack;
}

void TabbedButtonBar::setTabBackgroundColour (int tabIndex, Colour newColour)
{
    if (auto* tab = tabs [tabIndex])
    {
        if (tab->colour != newColour)
        {
            tab->colour = newColour;
            repaint();
        }
    }
}

void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
{
    if (bar != nullptr && result > 0)
        bar->setCurrentTabIndex (result - 1);
}

void TabbedButtonBar::showExtraItemsMenu()
{
    PopupMenu m;

    for (int i = 0; i < tabs.size(); ++i)
    {
        auto* tab = tabs.getUnchecked(i);

        if (! tab->button->isVisible())
            m.addItem (i + 1, tab->name, true, i == currentTabIndex);
    }

    m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
                     ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
}

//==============================================================================
void TabbedButtonBar::currentTabChanged (int, const String&) {}
void TabbedButtonBar::popupMenuClickOnTab (int, const String&) {}

} // namespace juce