음악, 삶, 개발
Juce 의 Component 클래스와 LookAndFeel_V4 클래스 본문
기본
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;
};