음악, 삶, 개발

Juce 의 Component 클래스와 LookAndFeel_V4 클래스 본문

개발 공부/Juce 공부방

Juce 의 Component 클래스와 LookAndFeel_V4 클래스

Lee_____ 2020. 9. 25. 18:18

기본

 

Juce 의 GUI 를 사용하는 방법은 2가지가 있다.

1. 내가 직접 그린다. = Component 클래스를 상속받아 paint() 를 override 한다.

2. Juce 가 주는 GUI 를 사용한다 = Component 클래스를 상속받은 클래스에  private member 로 GUI 객체를 추가한다.

3. 내가 만든 Component 클래스를 다른 Component 클래스의 멤버로 사용한다. (부모 자식 관계) - 일종의 포토샵 레이어


내가 직접 그린다. = Component 클래스를 상속받아 paint() 를 override 한다.

 

1. Component 클래스를 상속받은 클래스를 생성한다.

2. paint() 를 override 하여, 이 안에 그린다.

3. resized() 라는 콜백안에서 추가적인 설정을 한다.

 


Juce 가 주는 GUI 를 사용한다 = Component 클래스를 상속받은 클래스에  private member 로 GUI 객체를 추가한다.

 

1. Component 클래스를 상속받은 클래스를 생성한다.

2. 이 클래스의 private 멤버로 Juce 가 주는 GUI 클래스의 객체를 생성한다.

3. 이 클래스의 constructor 에서 이 멤버들에 대한 셋팅을 해주고, addAndMakeVisible(Juce객체) 를 해준다.

(이때, set 함수들을 다 실행후 addAndMakeVisible 을 해야한다는것을 기억하라)

4. resized() 를 override 하여, 이안에 이 GUI 멤버들의 setBounds() 를 설정한다. (크기설정)

 

여기서 Juce 가 주는 GUI 를 커스터마이즈하고싶을수있는데, 추가적인 step 이 더 필요하다.

Juce GUI 는 sub클래스로 LookAndFeelMethod 클래스를 가지고있고,

이를 내가 override 하여 커스터마이즈하는것이다.

 

5. LookAndFeel_V4 클래스를 상속받은 또다른 클래스를 생성한다.

6. 이 안에 내가 사용하고있는 멤버의 method 를 override 한다. 

7. 이 클래스의 객체를 1번 클래스의 멤버로 추가한다.

8. 1번 클래스의 struct 에서 setLookAndFeel(&otherLookAndFeel) 을 실행한다.

만약 여러 멤버가 있는 경우, 각 멤버.setLookAndFeel(&otherLookAndFeel) 로 할수있다.

 

이때 주의할점은 setLookAndFeel(&otherLookAndFeel) 을 보면 주소값을 인자로, 즉 포인터를 사용하기때문에

1번 클래스의 destructor 에서 반드시 setLookAndFeel(nullptr) 을 해주어야한다.


코드

class OtherLookAndFeel : public juce::LookAndFeel_V4
{
    public:

        OtherLookAndFeel()
        {
            setColour(juce::Slider::thumbColourId, juce::Colours::red);
        }

        void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
            const float rotaryStartAngle, const float rotaryEndAngle, juce::Slider&) override
        {
            auto radius = (float)juce::jmin(width / 2, height / 2) - 4.0f;
            auto centreX = (float)x + (float)width * 0.5f;
            auto centreY = (float)y + (float)height * 0.5f;
            auto rx = centreX - radius;
            auto ry = centreY - radius;
            auto rw = radius * 2.0f;
            auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);

            // fill
            g.setColour(juce::Colours::orange);
            g.fillEllipse(rx, ry, rw, rw);

            // outline
            g.setColour(juce::Colours::red);
            g.drawEllipse(rx, ry, rw, rw, 1.0f);

            juce::Path p;
            auto pointerLength = radius * 0.33f;
            auto pointerThickness = 2.0f;
            p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength);
            p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));

            // pointer
            g.setColour(juce::Colours::yellow);
            g.fillPath(p);
        }

        void drawButtonBackground(juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour,
            bool, bool isButtonDown) override
        {
            auto buttonArea = button.getLocalBounds();
            auto edge = 4;

            buttonArea.removeFromLeft(edge);
            buttonArea.removeFromTop(edge);

            // shadow
            g.setColour(juce::Colours::darkgrey.withAlpha(0.5f));
            g.fillRect(buttonArea);

            auto offset = isButtonDown ? -edge / 2 : -edge;
            buttonArea.translate(offset, offset);

            g.setColour(backgroundColour);
            g.fillRect(buttonArea);
        }

        void drawButtonText(juce::Graphics& g, juce::TextButton& button, bool, bool isButtonDown) override
        {
            auto font = getTextButtonFont(button, button.getHeight());
            g.setFont(font);
            g.setColour(button.findColour(button.getToggleState() ? juce::TextButton::textColourOnId
                : juce::TextButton::textColourOffId)
                .withMultipliedAlpha(button.isEnabled() ? 1.0f : 0.5f));

            auto yIndent = juce::jmin(4, button.proportionOfHeight(0.3f));
            auto cornerSize = juce::jmin(button.getHeight(), button.getWidth()) / 2;

            auto fontHeight = juce::roundToInt(font.getHeight() * 0.6f);
            auto leftIndent = juce::jmin(fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2));
            auto rightIndent = juce::jmin(fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2));
            auto textWidth = button.getWidth() - leftIndent - rightIndent;

            auto edge = 4;
            auto offset = isButtonDown ? edge / 2 : 0;

            if (textWidth > 0)
                g.drawFittedText(button.getButtonText(),
                    leftIndent + offset, yIndent + offset, textWidth, button.getHeight() - yIndent * 2 - edge,
                    juce::Justification::centred, 2);
        }

};

class Gui : public juce::Component, juce::ValueTree::Listener {

    public:

        Gui(juce::ValueTree v) : tree(v)
        {   
                    
            tree.addListener(this);
                    
            setLookAndFeel(&otherLookAndFeel);

            dial1.setSliderStyle(juce::Slider::Rotary);
            dial1.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0);
            addAndMakeVisible(dial1);

            dial2.setSliderStyle(juce::Slider::Rotary);
            dial2.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0);
            addAndMakeVisible(dial2);

            button1.setButtonText("Button 1");
            addAndMakeVisible(button1);

            button2.setButtonText("Button 2");
            addAndMakeVisible(button2);

            setSize(300, 200);
        }

        ~Gui() override
        {
            setLookAndFeel(nullptr);
        }

        void paint(juce::Graphics& g) override
        {
            g.fillAll(juce::Colours::lightgrey);
        }

        void resized() override
        {
            auto border = 4;

            auto area = getLocalBounds();

            auto dialArea = area.removeFromTop(area.getHeight() / 2);
            dial1.setBounds(dialArea.removeFromLeft(dialArea.getWidth() / 2).reduced(border));
            dial2.setBounds(dialArea.reduced(border));

            auto buttonHeight = 30;

            button1.setBounds(area.removeFromTop(buttonHeight).reduced(border));
            button2.setBounds(area.removeFromTop(buttonHeight).reduced(border));
        }

    private:

        OtherLookAndFeel otherLookAndFeel; // [2]
        juce::Slider dial1;
        juce::Slider dial2;
        juce::TextButton button1;
        juce::TextButton button2;

        juce::ValueTree tree;

};