ComboBox Customization

Introduction

This article explains how to customize the ComboBox component to make it cooler. The before and after pictures are shown below.

Before
After

Prerequisites

In this article, we will create a new project named ComboBoxTutorial. Let’s get started!

Creating a basic ComboBox

In this chapter, we will quickly create the default ComboBox, as it is easy to see how it changes before and after customization.

Declaring an object

First, declare an object of the ComboBox class.

class ComboBoxTutorialAudioProcessorEditor  : public juce::AudioProcessorEditor
{
public:
・・・
private:
    ComboBoxTutorialAudioProcessor& audioProcessor;
    
    juce::ComboBox comboBox;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxTutorialAudioProcessorEditor)
};

Setting object properties

Next, call various member functions on the ComboBox object to configure the settings as shown below:

ComboBoxTutorialAudioProcessorEditor::ComboBoxTutorialAudioProcessorEditor (ComboBoxTutorialAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
    setSize (200, 300);
    
    comboBox.addItem ("Test 1", 1);
    comboBox.addItem ("Test 2", 2);
    comboBox.addItem ("Test 3", 3);
    comboBox.setSelectedId (1);
    
    addAndMakeVisible (comboBox);
}

In this case, we added three items for testing, and made sure that the item with ID 1 is displayed by default.

Then, delete the hello world part in paint() and add a process to fill the background of the editor with black.

void ComboBoxTutorialAudioProcessorEditor::paint (juce::Graphics& g)
{
    g.fillAll (juce::Colours::black);
}

void ComboBoxTutorialAudioProcessorEditor::resized()
{
    comboBox.setBounds (30, 100, 80, 25);
}

Now that we have the minimum implementation, let’s build it.

Okay, we’ll start making this UI cooler in the next chapter.

Customizing a ComboBox class

In this chapter, we will prepare to customize the default ComboBox.

Adding a new header file

The customized ComboBox we will create in this article does not require a lot of code to be written, so we will only create a header file and implement it there. To create a header file, open the Producer and select “Add New Header File” as shown in the image below.

Setting up a customized ComboBox

Now let’s implement it. All we need to do is to call setColour() in the constructor below to set the color.

#pragma once

#include <JuceHeader.h>

class CustomComboBox  : public juce::ComboBox
{
public:
    CustomComboBox()
    {
        setColour (juce::ComboBox::outlineColourId, juce::Colours::transparentWhite);
        setColour (juce::ComboBox::textColourId, grey);
        setColour (juce::ComboBox::arrowColourId, grey);
        setColour (juce::ComboBox::backgroundColourId, juce::Colours::black);
    }

private:
    juce::Colour grey = juce::Colour::fromFloatRGBA (0.42f, 0.42f, 0.42f, 1.f);    

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComboBox)
};

Then, include this header file in PluginEditor.h and declare the object of your customized class. Also, delete or replace the objects created in the previous chapter.

#pragma once

#include <JuceHeader.h>
#include "PluginProcessor.h"
#include "CustomComboBox.h"

class ComboBoxTutorialAudioProcessorEditor  : public juce::AudioProcessorEditor
{
public:
・・・
private:
    ComboBoxTutorialAudioProcessor& audioProcessor;
    
    CustomComboBox customComboBox;
ComboBoxTutorialAudioProcessorEditor::ComboBoxTutorialAudioProcessorEditor (ComboBoxTutorialAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
    setSize (200, 300);
    
    customComboBox.addItem ("Test 1", 1);
    customComboBox.addItem ("Test 2", 2);
    customComboBox.addItem ("Test 3", 3);
    customComboBox.setSelectedId (1);
    
    addAndMakeVisible (customComboBox);
}

void ComboBoxTutorialAudioProcessorEditor::resized()
{
    customComboBox.setBounds (30, 100, 80, 25);
}

Let’s build it and see what happens!

Everything looks cool except for the drop-down list. To make it even cooler from here, we need to customize the LookAndFeel class. We’ll do that in the next chapter!

Customizing a LookAndFeel class

In this chapter, we will customize the LookAndFeel class to evolve ComboBox into a cooler UI.

Adding a new cpp & header file

We’ll prepare a new cpp file and a new header file, since the amount of code to write will be large. Open Producer and select “Add New CPP & Header File” to create a file as show below:

Changing an arrow into a triangle

Before
After

To change the arrow mark to a triangle mark, it is necessary to override drawComboBox().

#pragma once

#include <JuceHeader.h>

class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
    CustomLookAndFeel();
    ~CustomLookAndFeel();
        
    void drawComboBox (juce::Graphics& g, int width, int height, bool,
                       int, int, int, int, juce::ComboBox& box) override;            

private:
    juce::Colour grey      = juce::Colour::fromFloatRGBA (0.42, 0.42, 0.42, 1.0);
    juce::Colour black     = juce::Colour::fromFloatRGBA (0.08f, 0.08f, 0.08f, 1.f);
    juce::Colour blackGrey = juce::Colour::fromFloatRGBA (0.2, 0.2, 0.2, 1.0);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomLookAndFeel);
};

The triangle mark can be set by preparing an object of the Path class and calling addTriangle() on it. Also, since we want to fill rather than draw a line, we call fillPath() on the Graphics object and specify path as the argument. In this way, we can describe a beautiful triangle.

#include "CustomLookAndFeel.h"

CustomLookAndFeel::CustomLookAndFeel () {}
CustomLookAndFeel::~CustomLookAndFeel () {}

void CustomLookAndFeel::drawComboBox (juce::Graphics& g, int width, int height, bool,
                                      int, int, int, int, juce::ComboBox& box)
{
    auto arrowZone = juce::Rectangle<int> (width - 20, 0, 10, height).reduced (0, 8);
    juce::Path path;
    
    path.addTriangle (juce::Point<float> ((float) arrowZone.getX(),       (float) arrowZone.getY()),
                      juce::Point<float> ((float) arrowZone.getRight(),   (float) arrowZone.getY()),
                      juce::Point<float> ((float) arrowZone.getCentreX(), (float) arrowZone.getBottom()));


    g.setColour (box.findColour (juce::ComboBox::arrowColourId));
    g.fillPath (path);
}

Next, include CustomLookAndFeel.h and call setLookAndFeel on the CustomComboBox object to apply the customized LookAndFeel.

#pragma once

#include <JuceHeader.h>
#include "CustomLookAndFeel.h"

class CustomComboBox  : public juce::ComboBox
{
public:
    CustomComboBox()
    {
        setLookAndFeel (&customLookAndFeel);
        setColour (juce::ComboBox::outlineColourId, juce::Colours::transparentWhite);
        setColour (juce::ComboBox::textColourId, grey);
        setColour (juce::ComboBox::arrowColourId, grey);
        setColour (juce::ComboBox::backgroundColourId, juce::Colours::black);
    }

private:
    CustomLookAndFeel customLookAndFeel;
    
    juce::Colour grey = juce::Colour::fromFloatRGBA (0.42f, 0.42f, 0.42f, 1.f);
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComboBox)
};

After the build, it should change to a triangle mark as shown below:

Customizing the drop-down list

At the moment, a check mark is displayed in the drop-down list. We will remove it and also set the text color to gray. To do this, we need to override drawPopupMenuItem().

class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
・・・
    void drawPopupMenuItem (juce::Graphics& g, const juce::Rectangle<int>& area,
                            const bool isSeparator, const bool isActive,
                            const bool isHighlighted, const bool isTicked,
                            const bool hasSubMenu, const juce::String& text,
                            const juce::String& shortcutKeyText,
                            const juce::Drawable* icon, const juce::Colour* const textColourToUse) override;

Delete the check mark implementation as shown below, and set the text color and the background color of the selected item.

void CustomLookAndFeel::drawPopupMenuItem (juce::Graphics& g, const juce::Rectangle<int>& area,
                                           const bool isSeparator, const bool isActive,
                                           const bool isHighlighted, const bool isTicked,
                                           const bool hasSubMenu, const juce::String& text,
                                           const juce::String& shortcutKeyText,
                                           const juce::Drawable* icon, const juce::Colour* const textColourToUse)
{
    g.setColour (grey);
    
    if (isHighlighted && isActive)
    {
        g.fillRect (area);
        g.setColour (black);
    }
    
    g.setFont (16);
    g.drawFittedText (text, area.reduced (6, 0), juce::Justification::left, 1);
}

Let’s build and check it out.

It was very difficult to see the background color and text color, but I was able to remove the check mark and change the text color. Immediately after this, change the background color of the drop-down list.

Changing the background color

In order to change the background color of the drop-down list, we need to override drawPopupMenuBackground().

class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
・・・
    void drawPopupMenuBackground (juce::Graphics& g, int width, int height) override;

Set the background color of the drop-down list to black, the same as the background color of the editor, and also set a gray border.

void CustomLookAndFeel::drawPopupMenuBackground (juce::Graphics& g, int width, int height)
{
    g.fillAll (juce::Colours::black);
    
    g.setColour (grey);
    g.drawRect (0.f, 0.f, (float) width, (float) height, 1.5f);
}

It is now very easy to see, as shown below.

Adjusting the width of the drop-down list

Finally, adjust the width of the drop-down list because it is too long. To do this, override getIdealPopupMenuitemSize().

class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
・・・
    void getIdealPopupMenuItemSize (const juce::String& text, const bool isSeparator,
                                    int standardMenuItemHeight, int& idealWidth, int& idealHeight) override;

To be honest, I don’t really understand the implementation part here, all I know is that I can adjust it by changing the value of idealWidth.

void CustomLookAndFeel::getIdealPopupMenuItemSize (const juce::String& text, const bool isSeparator,
                                                   int standardMenuItemHeight, int& idealWidth, int& idealHeight)
{
    idealHeight = standardMenuItemHeight;
    idealWidth = 0;
}

All implementation is now complete. Let’s build and check it out!

Conclusion

In this article, I explained how to make the ComboBox component more cool. I personally think it is more difficult to customize the UI compared to other components. I hope this article will help those who are having trouble customizing this component. Thank you for reading to the end.

References

Comments

Copied title and URL