Smoother TabbedButtonBar scaling and LookAndFeel extension

This patch contains 3 parts:

  • Scaling tabs to fit into a too small tabbed-button-bar is smoother and less jumpy while resizing
  • Parameters affecting scaling can be configured and scaling can be disabled completely
  • The position and size of the extra tabs button is controlled by the look-and-feel

Perhaps you want to merge this into the official JUCE codebase, jules.

[code]diff -d -r -u old/juce/juce_LookAndFeel.cpp new/juce/juce_LookAndFeel.cpp
— old/juce/juce_LookAndFeel.cpp
+++ new/juce/juce_LookAndFeel.cpp
@@ -2194,6 +2194,32 @@
return db;
}

+int LookAndFeel::positionTabBarExtrasButton (TabbedButtonBar &tabbedButtonBar,

  •                                         int width, int height,
    
  •                                         int orientation,
    
  •                                         Button *tabBarExtrasButton)
    

+{

  • int centre;
  • const int buttonSize = jmin (tabbedButtonBar.proportionOfWidth (0.7f),
  •                             tabbedButtonBar.proportionOfHeight (0.7f));
    
  • tabBarExtrasButton->setSize (buttonSize, buttonSize);
  • if (orientation == TabbedButtonBar::TabsAtTop || orientation == TabbedButtonBar::TabsAtBottom)
  • {
  •    centre = width - buttonSize / 2 - 1;
    
  •    tabBarExtrasButton->setCentrePosition (centre, height / 2);
    
  • }
  • else
  • {
  •    centre = height - buttonSize / 2 - 1;
    
  •    tabBarExtrasButton->setCentrePosition (width / 2, centre);
    
  • }
  • return centre;
    +}

//==============================================================================
void LookAndFeel::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header)
diff -d -r -u old/juce/juce_LookAndFeel.h new/juce/juce_LookAndFeel.h
— old/juce/juce_LookAndFeel.h
+++ new/juce/juce_LookAndFeel.h
@@ -514,6 +514,11 @@

 virtual Button* createTabBarExtrasButton();
  • virtual int positionTabBarExtrasButton (TabbedButtonBar &tabbedButtonBar,
  •                                        int width, int height,
    
  •                                        int orientation,
    
  •                                        Button *tabBarExtrasButton);
    
  • //==============================================================================
    virtual void drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header);

diff -d -r -u old/juce/juce_TabbedButtonBar.cpp new/juce/juce_TabbedButtonBar.cpp
— old/juce/juce_TabbedButtonBar.cpp
+++ new/juce/juce_TabbedButtonBar.cpp
@@ -183,7 +183,9 @@
TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
: orientation (orientation_),
currentTabIndex (-1),

  •  extraTabsButton (0)
    
  •  extraTabsButton (0),
    
  •  minimumScale (0.7),
    
  •  upscalingEnabled (true)
    

{
setInterceptsMouseClicks (false, true);
addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this));
@@ -368,9 +370,22 @@
resized();
}

+void TabbedButtonBar::setMinimumTabButtonScaleFactor (const double factor)
+{

  • jassert (factor >= 0.0 && factor <= 1.0);
  • minimumScale = factor;
  • resized();
    +}

+void TabbedButtonBar::setTabButtonUpscalingEnabled (const bool shouldBeEnabled)
+{

  • upscalingEnabled = shouldBeEnabled;
  • resized();
    +}

void TabbedButtonBar::resized()
{

  • const double minimumScale = 0.7;
    int depth = getWidth();
    int length = getHeight();

@@ -397,10 +412,17 @@
double scale = 1.0;

 if (totalLength > length)
  •    scale = jmax (minimumScale, length / (double) totalLength);
    
  • {
  •    if (upscalingEnabled)
    
  •        scale = jmax (minimumScale, length / (double) totalLength);
    
  •    else
    
  •        scale = jlimit (minimumScale, 1.0, length / (double) totalLength);
    
  • }
  • const bool isTooBig = totalLength * scale > length;
  • int tabsButtonPos = 0;
  • // subtract 0.5 here to handle rounding errors and suppress flickering popup

  • // of the extra tabs button

  • const bool isTooBig = totalLength * scale - 0.5 > length;

  • int remainingLength = length;

    if (isTooBig)
    {
    @@ -412,19 +434,10 @@
    extraTabsButton->setTriggeredOnMouseDown (true);

    }
    
  •    const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
    
  •    extraTabsButton->setSize (buttonSize, buttonSize);
    
  •    if (orientation == TabsAtTop || orientation == TabsAtBottom)
    
  •    {
    
  •        tabsButtonPos = getWidth() - buttonSize / 2 - 1;
    
  •        extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
    
  •    }
    
  •    else
    
  •    {
    
  •        tabsButtonPos = getHeight() - buttonSize / 2 - 1;
    
  •        extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
    
  •    }
    
  •    remainingLength = getLookAndFeel().positionTabBarExtrasButton (*this,
    
  •                                                                   getWidth(), getHeight(),
    
  •                                                                   orientation,
    
  •                                                                   extraTabsButton);
    
       totalLength = 0;
    

@@ -436,7 +449,7 @@
{
const int newLength = totalLength + tb->getBestTabLength (depth);

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

@@ -444,11 +457,13 @@

             numVisibleButtons = i + 1;
             totalLength = newLength - overlap;
  •        }
       }
    
  •    scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
    
  •    if (upscalingEnabled)
    
  •        scale = jmax (minimumScale, remainingLength / (double) totalLength);
    
  •    else
    
  •        scale = jlimit (minimumScale, 1.0, remainingLength / (double) totalLength);
    

    }
    else
    {
    @@ -458,6 +473,7 @@
    int pos = 0;

    TabBarButton* frontTab = 0;

  • double fraction = 0.0;

    for (i = 0; i < tabs.size(); ++i)
    {
    @@ -465,10 +481,35 @@

    if (tb != 0)
    {
    
  •        const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth));
    
  •        // scale the tab length but not the overlap
    
  •        double bestLengthAsDouble = scale * (tb->getBestTabLength (depth) - overlap) + overlap;
    
  •        int bestLength = roundDoubleToInt (bestLengthAsDouble);
    
  •        // don't just drop the fractional part of the tab length but sum
    
  •        // them up and add whole pixel back to the tab length. this results
    
  •        // in less jumpy scaling
    
  •        fraction += bestLengthAsDouble - (double) bestLength;
    
  •        while (fraction >= 1.0)
    
  •        {
    
  •            ++bestLength;
    
  •            --fraction;
    
  •        }
    
  •        while (fraction <= -1.0)
    
  •        {
    
  •            --bestLength;
    
  •            ++fraction;
    
  •        }
    
           if (i < numVisibleButtons)
           {
    
  •            // make sure the last tabs border matches with the border of the remaining length
    
  •            if (upscalingEnabled && totalLength >= remainingLength && i == numVisibleButtons - 1)
    
  •            {
    
  •                bestLength = remainingLength - pos;
    
  •            }
    
  •            if (orientation == TabsAtTop || orientation == TabsAtBottom)
                   tb->setBounds (pos, 0, bestLength, getHeight());
               else
    

diff -d -r -u old/juce_TabbedButtonBar.h new/juce/juce_TabbedButtonBar.h
— old/juce/juce_TabbedButtonBar.h
+++ new/juce/juce_TabbedButtonBar.h
@@ -238,6 +238,44 @@
*/
void setTabBackgroundColour (const int tabIndex, const Colour& newColour);

  • /** Returns the minimum factor for tab scaling.

  •    @see setMinimumTabButtonScaleFactor, setTabButtonUpscalingEnabled
    
  • */

  • double getMinimumTabButtonScaleFactor () const throw() { return minimumScale; }

  • /** Changes the minimum factor for tab scaling.

  •    If there is not enough space to show all tabs they will be scaled down
    
  •    until they fit all in. The scaling stops at this minimal factor to prevent
    
  •    the tabs from beeing scaled to far down. If the minimal scale factor is
    
  •    reached and not all tabs fit in some tabs will be hidden. This hidden
    
  •    tabs can be accessed via a popup menu.
    
  •    A valid factor is a value from 0.0 to 1.0, with 0.0 tabs can be scaled
    
  •    without lower limit, with 1.0 tabs can't be scaled down.
    
  •    The default factor is 0.7
    
  •    @see getMinimumTabButtonScaleFactor, isTabButtonUpscalingEnabled
    
  • */

  • void setMinimumTabButtonScaleFactor (const double factor);

  • /** Returns true if upscaling is enabled.

  •    @see setMinimumTabButtonScaleFactor, setTabButtonUpscalingEnabled
    
  • */

  • bool isTabButtonUpscalingEnabled () const throw() { return upscalingEnabled; }

  • /** Enabled tab upscaling to fill the free space.

  •    When a tab is hidden because there is not enough space to show it the
    
  •    remaining tabs can be scaled up to fill the free space.
    
  •    @see getMinimumTabButtonScaleFactor, isTabButtonUpscalingEnabled
    
  • */

  • void setTabButtonUpscalingEnabled (const bool shouldBeEnabled);

  • //==============================================================================
    /** @internal /
    void resized();
    @@ -266,6 +304,8 @@
    int currentTabIndex;
    Component
    behindFrontTab;
    Button* extraTabsButton;

  • double minimumScale;

  • bool upscalingEnabled;

    TabBarButton* getTabButton (const int index) const;
    [/code]