Connecting NumberBox to CustomComponents

Introduction

In this article, we will explain how to connect NumberBox to CustomComponents. What this means is that when you change the value of the NumberBox, it will change the corresponding component. Like this:

Prerequisites

  • A new project must have been created.

NumberBox

In this chapter, we will prepare a NumberBox. The following article explains in detail how to implement the NumberBox, so we will skip the detailed explanation.

Adding a new cpp & header file

We will create a new cpp file and a header file because the amount of code required to implement NumberBox is large.

It is okay if two files are created under the Source directory as shown below:

We will also prepare a header file that implements a customized LookAndFeel class to be used in NumberBox.

Customizing LookAndFeel class

The member function of the LookAndFeel class that is related to the depiction of the NumberBox is createSliderTextBox(). Override it as shown below:

#pragma once

#include <JuceHeader.h>

class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
    CustomLookAndFeel() {};
    ~CustomLookAndFeel() {};
    
    juce::Label* createSliderTextBox (juce::Slider& slider) override
    {
        auto* l = new juce::Label();
        l->setJustificationType (juce::Justification::centred);
        l->setColour (juce::Label::textColourId, slider.findColour(juce::Slider::textBoxTextColourId));
        l->setColour (juce::Label::textWhenEditingColourId, slider.findColour(juce::Slider::textBoxTextColourId));
        l->setColour (juce::Label::outlineWhenEditingColourId, slider.findColour(juce::Slider::textBoxOutlineColourId));
        l->setFont (20);
                
        return l;
    }
        
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomLookAndFeel);
};

Customizing Slider class

NumberBox inherits from the slider class. In addition, we implement this so that it is marked when clicked and dragged. The header file and cpp file are as follows:

#pragma once

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

class NumberBox  : public juce::Slider
{
public:
    NumberBox();
    ~NumberBox() override;

    void paint (juce::Graphics&) override;
    
    void mouseDown (const juce::MouseEvent& event) override;
    void mouseUp (const juce::MouseEvent& event) override;

    void focusGained (juce::Component::FocusChangeType) override;
    void focusLost (juce::Component::FocusChangeType) override;

    bool getLockedOnState ();
    void setLockedOnState (bool state);

private:    
    bool isLockedOn = false;

    CustomLookAndFeel customLookAndFeel;
    
    juce::Colour grey = juce::Colour::fromFloatRGBA(0.42, 0.42, 0.42, 1.0);
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NumberBox)
};
#include <JuceHeader.h>
#include "NumberBox.h"

NumberBox::NumberBox()
{
    setSliderStyle (juce::Slider::LinearBarVertical);
    setLookAndFeel(&customLookAndFeel);
    setColour (juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
    setColour (juce::Slider::trackColourId, juce::Colours::transparentWhite);
    setTextBoxIsEditable (false);
    setVelocityBasedMode (true);
    setVelocityModeParameters (0.5, 1, 0.09, false);
    setRange (0, 100, 0.01);
    setWantsKeyboardFocus (true);
    onValueChange = [&]()
    {
        if (getValue() < 10)
            setNumDecimalPlacesToDisplay(2);
        else if (getValue() >= 10 && getValue() < 100)
            setNumDecimalPlacesToDisplay(1);
        else 
            setNumDecimalPlacesToDisplay(0);
    };
}

NumberBox::~NumberBox()
{
}

void NumberBox::paint (juce::Graphics& g)
{
    if (isLockedOn)
    {
        auto length = getHeight() > 15 ? 4 : 3;
        auto thick  = getHeight() > 15 ? 3.0 : 2.5;
        
        g.setColour (findColour (juce::Slider::textBoxTextColourId));
        //          fromX       fromY        toX                  toY
        g.drawLine (0,          0,           0,                   length,               thick);
        g.drawLine (0,          0,           length,              0,                    thick);
        g.drawLine (0,          getHeight(), 0,                   getHeight() - length, thick);
        g.drawLine (0,          getHeight(), length,              getHeight(),          thick);
        g.drawLine (getWidth(), getHeight(), getWidth() - length, getHeight(),          thick);
        g.drawLine (getWidth(), getHeight(), getWidth(),          getHeight() - length, thick);
        g.drawLine (getWidth(), 0,           getWidth() - length, 0,                    thick);
        g.drawLine (getWidth(), 0,           getWidth(),          length,               thick);
    }
}

void NumberBox::mouseDown (const juce::MouseEvent& event)
{
    juce::Slider::mouseDown (event);
    setMouseCursor (juce::MouseCursor::NoCursor);
}

void NumberBox::mouseUp (const juce::MouseEvent& event)
{
    juce::Slider::mouseUp (event);
    juce::Desktop::getInstance().getMainMouseSource().setScreenPosition(event.source.getLastMouseDownPosition());
    setMouseCursor (juce::MouseCursor::NormalCursor);
}
    
void NumberBox::focusGained (juce::Component::FocusChangeType)
{
    setLockedOnState (true); 
}

void NumberBox::focusLost (juce::Component::FocusChangeType)
{
    setLockedOnState (false); 
}

bool NumberBox::getLockedOnState ()
{
    return isLockedOn;
}

void NumberBox::setLockedOnState (bool state)
{
    isLockedOn = state;
    repaint();
}

Preparing the object

Include the header file for the NumberBox you created.

#pragma once

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

Next, declare the NumberBox object.

class TestNumberBoxAudioProcessorEditor : public juce::AudioProcessorEditor
{
public:
    TestNumberBoxAudioProcessorEditor (TestNumberBoxAudioProcessor&);
    ~TestNumberBoxAudioProcessorEditor() override;

    void paint (juce::Graphics&) override;
    void resized() override;
       
private:
    TestNumberBoxAudioProcessor& audioProcessor;
    
    NumberBox numberBox;
     
    juce::Colour stGreen = juce::Colour::fromFloatRGBA (0.34f, 0.74f, 0.66f, 1.f);
    juce::Colour black   = juce::Colour::fromFloatRGBA (0.08f, 0.08f, 0.08f, 1.f);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestNumberBoxAudioProcessorEditor)
};

Setting the properties

In the constructor below, we set the color of the NumberBox and the visualization settings.

TestNumberBoxAudioProcessorEditor::TestNumberBoxAudioProcessorEditor (TestNumberBoxAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{    
    setSize (450, 300);
    setWantsKeyboardFocus (true);
    
    numberBox.setColour (juce::Slider::textBoxTextColourId, stGreen);
    
    addAndMakeVisible (numberBox);
}

Fill the background with matte black.

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

Sets the placement and size of the object.

void TestNumberBoxAudioProcessorEditor::resized()
{
    numberBox.setBounds (getLocalBounds().getCentreX() - 30, 250, 60, 20);
}

This completes the minimal implementation. Let’s build and check it out.

CustomComponents

In this chapter, we will prepare a component to connect with NumberBox.

Adding a new header file

We will only create a header file since the amount of code will not be that large.

Customizing Component class

In this case, we will create a component that depicts a rectangle whose size changes depending on the variation of the NumberBox value. Like this:

class resizeableRect : public juce::Component
{
public:
    resizeableRect() {};
    ~resizeableRect() {};
    
    void paint (juce::Graphics& g) override
    {
        auto rectBounds = juce::Rectangle<float> (rectX, rectY, rectWidth, rectHeight);
        
        g.setColour (stGreen.withAlpha (0.3f));
        g.fillRect (rectBounds);
        
        g.setColour (stGreen.withAlpha (0.8f));
        g.drawRect (rectBounds, 1.8f);
    }
    
    void changeSize (float numberBoxValue)
    {
        auto bounds  = getLocalBounds().toFloat();
        auto centreX = bounds.getCentreX();
        auto centreY = bounds.getCentreY();
        auto width   = bounds.getWidth();
        auto height  = bounds.getHeight();
        
        rectX      = juce::jmap (numberBoxValue, 0.f, 100.f, centreX, 0.f);
        rectY      = juce::jmap (numberBoxValue, 0.f, 100.f, 0.f,     centreY);
        rectWidth  = juce::jmap (numberBoxValue, 0.f, 100.f, 2.f,     width);
        rectHeight = juce::jmap (numberBoxValue, 0.f, 100.f, height,  2.f);
        
        repaint();
    }
    
private:
    juce::Colour stGreen = juce::Colour::fromFloatRGBA (0.34f, 0.74f, 0.66f, 1.f);
    juce::Colour blue    = juce::Colour::fromFloatRGBA (0.43f, 0.83f, 1.f, 1.f);
    
    float rectX;
    float rectY;
    float rectWidth;
    float rectHeight;
};

Preparing the object

Don’t forget to include the header files.

#pragma once

#include <JuceHeader.h>
#include "PluginProcessor.h"
#include "NumberBox.h"
#include "resizeableRect.h"

Then declare the object of the customized component.

class TestNumberBoxAudioProcessorEditor : public juce::AudioProcessorEditor, public juce::Slider::Listener
{
・・・
private:
    TestNumberBoxAudioProcessor& audioProcessor;
    
    NumberBox numberBox;
    resizeableRect rect;
    
    juce::Colour stGreen   = juce::Colour::fromFloatRGBA (0.34f, 0.74f, 0.66f, 1.f);
    juce::Colour black     = juce::Colour::fromFloatRGBA (0.08f, 0.08f, 0.08f, 1.f);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestNumberBoxAudioProcessorEditor)
};

Setting the properties

Configure the visualization, placement, and size settings as shown below:

TestNumberBoxAudioProcessorEditor::TestNumberBoxAudioProcessorEditor (TestNumberBoxAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
・・・
    addAndMakeVisible (numberBox);
}
void TestNumberBoxAudioProcessorEditor::resized()
{
    rect.setBounds (getLocalBounds().withSizeKeepingCentre (300, 150));
    numberBox.setBounds (getLocalBounds().getCentreX() - 30, 250, 60, 20);
}

The minimum implementation of CustomComponent has been completed. Nothing is displayed yet after the build.

Connection

Overriding sliderValueChanged()

Override sliderValueChanged(), which is called when the value of the NumberBox changes.

class TestNumberBoxAudioProcessorEditor : public juce::AudioProcessorEditor, public juce::Slider::Listener
{
public:
    TestNumberBoxAudioProcessorEditor (TestNumberBoxAudioProcessor&);
    ~TestNumberBoxAudioProcessorEditor() override;

    void paint (juce::Graphics&) override;
    void resized() override;
    
    void sliderValueChanged (juce::Slider* slider) override;
    
    void mouseDown (const juce::MouseEvent& event) override;

private:

We had implemented a member function called changeSize in the custom component that changes the size of the rectangle to be depicted, so we will implement sliderValueChanged() as follows.

void TestNumberBoxAudioProcessorEditor::sliderValueChanged (juce::Slider *slider)
{
    if (slider == &numberBox)
        rect.changeSize ((float) numberBox.getValue());
}

That’s it, all implementation is complete! Let’s get to building!

Conclusion

In this article, we explained how to link NumberBox with other components. I have applied this to create the following UI.

If you know of a more efficient way, please comment on it. Thank you for reading to the end!

Comments

Copied title and URL