Feature Request: Multistate Button


#1

I would like to propose a new Button component that can have multiple states (usually three) it can cycle through.

It would behave like a ToggleButton, but instead of simply turning on/off, it would increase it’s internal state and wrap around at the predefined number of states. This allows for a simple and small switch between e.g. filter-modes, tri-state filter states (ignore, has to be included, can’t be included) etc.

If helpful, I can provide a JUCE style implementation and send pull request.

Thanks,
Mike


#2

Hmmm not sure about this one. JUCE already supports the most common types of multi-state controls. You could use a combo-box, a slider with discrete steps (which also can be a rotary slider) or you could group toggle buttons in a radio group (Button::setRadioGroupId). On top of that you can change the layout of all those controls with a custom look and feel. Maybe, in your case, using a slider with discrete steps and custom look & feel might do the job best. Anything else seems pretty non-standard to me and is probably worth deriving your own class from Button.


#3

Fair enough. But one has to wonder why there are so many different button classes with identical behavior (!) except their display. TextButton, Imagebutton, DrawableButton etc.

But I guess these are pretty old and the code design principles not as solidified as they are now.

Thanks,
Mike


#4

+1 for this feature request.

I fail to understand how a slider is going to transition between it’s values on a click (or how it’s going to wrap around at the end). I have implemented a (incredibly cludgey) tri-state Button derived class in my first plugin with JUCE, and there was no way of using ButtonAttachment to communicate between the editor and processor since I was maintaining my own button triple-state. I suppose I should have looked at also deriving from ButtonAttachment to solve that problem?


#5

It’s really simple to write your own button class… I have one I wrote to do something like this which is fairly simple:

class CTriStateButton : public Component,
                           public ChangeBroadcaster,
                           public SettableTooltipClient
{
public:
    
    CTriStateButton (Image image1, Image image2, Image image3);
    
    void    paint (Graphics& g) override;
    
    void    mouseDown  (const MouseEvent&) override;
    
    int     getState() const noexcept   { return m_iState;  }
    
    void    setState (int iState);
    
    //==============================================================================
    /** You can assign a lambda to this callback object to have it called when the button is clicked. */
    std::function<void()> onClick;
    
private:
    
    Image       m_State1_Image;
    Image       m_State2_Image;
    Image       m_State3_Image;
    
    int         m_iState;
    
    /////////////////////////////////////////
    
    void clickButton();
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CTriStateButton);
};
#include "TriStateButton.h"

CTriStateButton::CTriStateButton (Image image1, Image image2, Image image3) : m_State1_Image (image1),
                                                                              m_State2_Image (image2),
                                                                              m_State3_Image (image3),
                                                                              m_iState       (0)
{
    
}

void  CTriStateButton::paint (Graphics& g)
{
    switch (m_iState)
    {
        case 0:     g.drawImageAt (m_State1_Image, 0, 0);
                    break;
        
        case 1:     g.drawImageAt (m_State2_Image, 0, 0);
                    break;
        
        case 2:     g.drawImageAt (m_State3_Image, 0, 0);
                    break;
        
        default:
                    break;
    }
}

void  CTriStateButton::mouseDown (const MouseEvent&)
{
    m_iState++;
    
    if (m_iState > 2)
        m_iState = 0;
    
    clickButton();
    
    repaint();
}

void  CTriStateButton::clickButton()
{
    if (onClick == nullptr)
        {
        sendSynchronousChangeMessage();
        }
    else
        {
        Component::BailOutChecker checker (this);
        
        if (checker.shouldBailOut())
            return;
        
        onClick();
        }
}

void  CTriStateButton::setState (int iState)
{
    m_iState = iState;
    
    repaint();
}

Rail


#6

For slightly more JUCEsque behavior one could add a NotificationType notification parameter to setState. And I’d be tempted to put the three images in an array, to do away with the switch in paint. Which of course means you would have to bounds checking in setState.


#7

Yep, there are lots of simple improvements you could make – this is just a simple/quick implementation (which demonstrates you don’t have to be limited to the built-in JUCE buttons) :slight_smile:

Cheers,

Rail


#8

My comments were aimed at the poster, for that exact reason. :slight_smile: