[Suggestion] Improvements to the way colours are managed in Components

Let me start with some example code to illustrate my thoughts on what’s wrong with the current way of managing colours in Components:

/** A simple widget that displays some text with a background colour
    and that responds to the mouse by changing colour.
*/
class MyWidget : public juce::Component
{
    //...
    enum ColourIds
    {
        backgroundColourId =          ..., // defaults to grey
        mouseOverBackgroundColourId = ..., // defaults to light grey
        mouseDownBackgroundColourId = ..., // defaults to dark grey
        
        textColourId =          ...,
        mouseOverTextColourId = ...,
        mouseDownTextColourId = ...
    }
    //...
};

//...
// The constructor for my editor that displays my widgets
MyEditor::MyEditor()
{
    // For widget1 I don't want any of the default colours so I need to change them all
    MyWidget widget1;
    widget1.setColour(MyWidget::backgroundColourId, Colours::purple);
    widget1.setColour(MyWidget::mouseOverBackgroundColourId, Colours::red);
    widget1.setColour(MyWidget::mouseDownBackgroundColourId, Colours::blue);
    widget1.setColour(MyWidget::textColourId, Colours::yellow);
    widget1.setColour(MyWidget::mouseOverTextColourId, Colours::orange);
    widget1.setColour(MyWidget::mouseDownTextColourId, Colours::green);

    // For widget 2, I don't want it to react to the mouse,
    // so I want the background and text to be a fixed colour.
    // However, I still have to set the colour for each and every ColourId... Booo!
    MyWidget widget2;
    widget2.setColour(MyWidget::backgroundColourId, Colours::green);
    widget2.setColour(MyWidget::mouseOverBackgroundColourId, Colours::green);
    widget2.setColour(MyWidget::mouseDownBackgroundColourId, Colours::green);
    widget2.setColour(MyWidget::textColourId, Colours::black);
    widget2.setColour(MyWidget::mouseOverTextColourId, Colours::black);
    widget2.setColour(MyWidget::mouseDownTextColourId, Colours::black);
}

The issue with this current method is that I ALWAYS have to change ALL of the colours (if I don’t want the default colours). This isn’t so bad for simple widgets, but for widgets with several elements each of different colours (Sliders, for example) that I want to also react to the mouse, I have to write dozens of lines of initialisation code for each widget… which gets messy very quickly!

So, here’s my solution: fallbacks.

This idea comes from the way CSS handles this sort of situation. In CSS I can change the colour of an HTML element like so:

p {
    color: #FF0000
}

This sets the text colour of all <p> elements to red, reguardless of the mouse’s current state. If I want the text to be blue when the mouse is hovering over the element, I can do so by adding:

p:hover {
    color: #0000FF
}

The text will still display as red until the mouse is hovering over it, when it will now use blue instead.

My solution for JUCE’s Components would work in a similar way. When setting a Component’s colour, allow the user to specify an optional MouseState argument which would be an enum containing states such as Hover, Down, Dragging, etc. (and maybe a similar KeyboardState argument as well).
Now, when searching for colours, the user can again specify the optional MouseState argument to find the correct colour for the given state. If a specific colour cannot be found for the given state, the API should instead fallback to use the default colour for that ColourID (or use a Normal state).
This system should also allow for additional states to be specified - for example Button’s should have an addition ToggleState and many widgets would need an EnablementState.

Now, my example code could be rewritten like so:

/** A simple widget that displays some text with a background colour and that responds to the mouse by changing colour. */
class MyWidget : public juce::Component
{
    //...
    enum ColourIds
    {
        backgroundColourId = ...,
        textColourId =       ...,
    }
    //...
};

//...
// The constructor for my editor that displays my widgets
MyEditor::MyEditor()
{
    // Less messy code - we can clearly see distict blocks of ColourIds
    MyWidget widget1;
    widget1.setColour(MyWidget::backgroundColourId, Colours::purple);
    widget1.setColour(MyWidget::backgroundColourId, Colours::red,  MouseState::Hover);
    widget1.setColour(MyWidget::backgroundColourId, Colours::blue, MouseState::Down);
    widget1.setColour(MyWidget::textColourId, Colours::yellow);
    widget1.setColour(MyWidget::textColourId, Colours::orange, MouseState::Hover);
    widget1.setColour(MyWidget::textColourId, Colours::green,  MouseState::Down);

    // I only have to specify the default colours for the text and background
    // because the findColour() API will fallback to use these when
    // it can't find any state-specific colours.
    MyWidget widget2;
    widget2.setColour(MyWidget::backgroundColourId, Colours::green);
    widget2.setColour(MyWidget::textColourId, Colours::black);
}

Exactly how this would work behind the scenes, I’m not sure. I tested a simple example with using a vector of std::tuple's to store the state-specific colours which seemed to work quite well.

I’d like to hear other user’s thoughts on this idea or maybe you have found other solutions that work well with the current version of JUCE?

If you set a colour on the lookAndFeel, it will have an effect for all components using that LookAndFeel.

getLookAndFeel().setColour (Slider::thumbColourId, Colours::red);

will change all slider’s thumb to red, if they use that LookAndFeel.
If a Component doesn’t have it’s own LookAndFeel, it will use the parent’s one. So this kind of inheritance is already happening.
The colours you set directly on a Component is only used for that particular Component.

OT: I wanted designers to be able to create all the layout and colour schemes for my plugins, thats why I wrote this module, that lets you design your plugin, and it uses the CSS paradigm for colouring and other style effects (borders, captions, margins, etc.)

Maybe that is an alternative…

1 Like