MOD-D3のUIをJUCEで再現する③ : ドラッグ可能なNumberBox

概要

※追記 Reproducing the UI of MOD-D3 with JUCE (2) : NumberBox英語ですがこちらの記事の方が最新です。より簡単なNumberBoxとスライダーの連携方法も含めてまとめ直しています。

前回に引き続き、今回もMOD-D3のUIを実装していきます。

前回は、xyz mapボタンの作成とTwoValue/ThreeValueの切り替え等を実装し、下記UIを作成するところまでやりました。

当記事では下記のように、メインUIであるCUBEの実装に備えて、ウィンドウサイズとコンポーネントの配置を変更します。また、各RangeFaderに対応するNumberBoxの実装をしていきます。

前回作成したNumberBoxはラベルコンポーネントでした。しかし今回は、NumberBox上のドラッグでも値の変更ができるようにしたいので、スライダーコンポーネントに変更します。

当記事で実装するものを大まかにまとめると下記のようになります。

  • ウィンドウサイズとコンポーネントの配置変更
  • ドラッグ可能なNumberBox
  • MidValueの値をTwoValue間の範囲内での割合に変更
  • LookAndFeelのカスタマイズ

また、より分かりやすい変数名や呼び方(スライダーをRangeFaderと呼んだり)に変更している箇所が多々あるのでご注意ください。

環境

  • macOS Big Sur 11.2.3
  • JUCE v6.0.8

CUBEの実装準備

前回でRangeFaderの外観は本家にほぼ近づきました。そのため、次のステップであるCUBEの実装に向けて、ウィンドウサイズやコンポーネントの配置を調整しておきます。

ウィンドウサイズの変更

まずウィンドウサイズの横幅を200pxから400pxに変更して倍にします。

ModD3AudioProcessorEditor::ModD3AudioProcessorEditor (ModD3AudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
    setSize (400, 300);
・・・

もちろんこれをすると、下記のようになるためコンポーネントの配置もいい具合に変更しなければなりません。

コンポーネントの配置変更

どう配置を変更するかというと、左半分はCUBE用に空け、右半分に以前と同じようにRangeFaderを配置させます。

void ModD3AudioProcessorEditor::resized()
{
    // xyz RangeFader
    xRangeFader.setBounds(getWidth() / 2 + 5, getHeight() / 2 - 90, getWidth() / 2 - 10, 30);
    yRangeFader.setBounds(getWidth() / 2 + 5, getHeight() / 2, getWidth() / 2 - 10, 30);
    zRangeFader.setBounds(getWidth() / 2 + 5, getHeight() / 2 + 90, getWidth() / 2 - 10, 30);

    // xyz MapButton
    xMapButton.setBounds(getWidth() / 2 + 15, xRangeFader.getY() - 25, getWidth() / 2 - 32, 25);
    yMapButton.setBounds(getWidth() / 2 + 15, yRangeFader.getY() - 25, getWidth() / 2 - 32, 25);
    zMapButton.setBounds(getWidth() / 2 + 15, zRangeFader.getY() - 25, getWidth() / 2 - 32, 25);
   
//    leftLabelModSlider1.setBounds(xRangeFader.getWidth() / 2 - 65, xRangeFader.getY() + 30, 55, 15);
//    rightLabelModSlider1.setBounds(xRangeFader.getWidth() / 2 + 15, xRangeFader.getY() + 30, 55, 15);
//    leftLabelModSlider2.setBounds(yRangeFader.getWidth() / 2 - 65, yRangeFader.getY() + 30, 55, 15);
//    rightLabelModSlider2.setBounds(yRangeFader.getWidth() / 2 + 15, yRangeFader.getY() + 30, 55, 15);
//    leftLabelModSlider3.setBounds(zRangeFader.getWidth() / 2 - 65, zRangeFader.getY() + 30, 55, 15);
//    rightLabelModSlider3.setBounds(zRangeFader.getWidth() / 2 + 15, zRangeFader.getY() + 30, 55, 15);
・・・

なるべくgetWidth()やgetY()などを使用して配置すれば、リサイズ時にいい具合に調整されますし、位置をいちいち計算しなくていいので楽です。

後述しますが、前回作成したラベルはスライダーコンポーネントに置き換えるため、削除しておきます。

これでCUBEの実装準備は整いました。ビルドすると下記のようになります。

ドラッグ可能なNumberBox

それではNumberBoxの実装をしていきます。

前回作成したラベルをNumberBoxにしようとすると、ドラッグで値を変更するという実装に躓いてしまいます。極めれば可能なのかもしれないですが、手っ取り早く実装できる方法が見つかりました。

それは、透明なスライダーを用いるという方法です。

Sliderクラスのカスタマイズ

まずはNumBoxというSliderクラスを継承したカスタムクラスを作成します。

同じNumberBoxを何個も作成するので、下記のようなコンストラクタを実装しておきます。

class NumBox : public juce::Slider
{
public:
    NumBox()
    {
        setSliderStyle(juce::Slider::LinearBarVertical);
        setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
        setColour(juce::Slider::trackColourId, juce::Colours::transparentWhite);
        setVelocityBasedMode(true);
        setVelocityModeParameters(0.5, 1, 0.09, false);
        setRange(0, 100, 0.5);
        setTextValueSuffix(" %");
    }
};

重要なのはsetVelocityBasedMode()setVelocityModeParameters()です。

まずVelocityBasedModeをオンにすることによって、以下のような効果を得ることができます。

  • ドラッグ時にマウスが消える
  • ドラッグし終わってもマウスの位置が固定されている
  • クリック時に値が飛ばない
  • ある程度ドラッグが速くなると大きく値が変化するようになる

ドラッグの速さに応じた値の詳細な設定は、setVelocityModeParameters()で行います。ある速さに達してから、どれくらい値を大きく変動させるかなど細かく設定することができます。

色の設定に関しては、外枠線やトラックの色をtransparentWhite(透明)にし、スライダーっぽさを無くしています。

NumBoxオブジェクトの作成

それではカスタマイズしたNumBoxクラスのオブジェクトを宣言します。各RageFader用のNumberBoxを二つずつ作成したいので、下記のようになります。

class ModD3AudioProcessorEditor  : public juce::AudioProcessorEditor, private juce::Slider::Listener, public juce::Button::Listener
{
・・・
private:
    NumBox xMinNumBox;
    NumBox xMaxNumBox;
    NumBox yMinNumBox;
    NumBox yMaxNumBox;
    NumBox zMinNumBox;
    NumBox zMaxNumBox;
・・・

次に下記コンストラクタで各NumBoxオブジェクトを可視化します。また、NumberBoxの値が変化した時にRangeFaderも動かしたいので、リスナーを登録しておきます。

ModD3AudioProcessorEditor::ModD3AudioProcessorEditor (ModD3AudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
・・・
    addAndMakeVisible(&xMinNumBox);
    xMinNumBox.addListener(this);
    
    addAndMakeVisible(&xMaxNumBox);
    xMaxNumBox.addListener(this);
    
    addAndMakeVisible(&yMinNumBox);
    yMinNumBox.addListener(this);
    
    addAndMakeVisible(&yMaxNumBox);
    yMaxNumBox.addListener(this);

    addAndMakeVisible(&zMinNumBox);
    zMinNumBox.addListener(this);
   
    addAndMakeVisible(&zMaxNumBox);
    zMaxNumBox.addListener(this);
}

各NumberBoxの配置は下記のように設定します。それぞれのRangeFaderのX座標、Y座標を基準にして配置させてます。

void ModD3AudioProcessorEditor::resized()
{
・・・
    xMinNumBox.setBounds(xRangeFader.getX() + 30, xRangeFader.getY() + 30, 55, 15);
    xMaxNumBox.setBounds(xRangeFader.getX() + 110, xRangeFader.getY() + 30, 55, 15);
    yMinNumBox.setBounds(yRangeFader.getX() + 30, yRangeFader.getY() + 30, 55, 15);
    yMaxNumBox.setBounds(yRangeFader.getX() + 110, yRangeFader.getY() + 30, 55, 15);
    zMinNumBox.setBounds(zRangeFader.getX() + 30, zRangeFader.getY() + 30, 55, 15);
    zMaxNumBox.setBounds(zRangeFader.getX() + 110, zRangeFader.getY() + 30, 55, 15);
}

現時点でビルドをすると下記のような見た目になります。

こうしてドラッグにより値を変更することができるNumberBoxを作成することができました。

しかし、まだNumberBoxとRangeFaderを連携させていないので、NumberBoxの値を変化させてもRangeFaderは動きません。

NumberBox → RangeFaderの連携

それではまず、NumberBoxの値を変化させた時、RangeFaderの値も変化させるように実装していきます。

先ほど作成したNumBoxオブジェクトには既にリスナーを登録してあるので、sliderValueChanged()内を下記のように実装するだけです。

void ModD3AudioProcessorEditor::sliderValueChanged(juce::Slider *slider)
{
・・・    
    if (slider == &xMinNumBox)
    {
        xRangeFader.setMinValue(xMinNumBox.getValue());
    }
    
    if (slider == &xMaxNumBox)
    {
        xRangeFader.setMaxValue(xMaxNumBox.getValue());
    }
    
    if (slider == &yMinNumBox)
    {
        yRangeFader.setMinValue(yMinNumBox.getValue());
    }
    
    if (slider == &yMaxNumBox)
    {
        yRangeFader.setMaxValue(yMaxNumBox.getValue());
    }
    
    if (slider == &zMinNumBox)
    {
        zRangeFader.setMinValue(zMinNumBox.getValue());
    }
    
    if (slider == &zMaxNumBox)
    {
        zRangeFader.setMaxValue(zMaxNumBox.getValue());
    }
}

やっていることは単純で、各NumberBoxの値が変化した時、それぞれに対応したRangeFaderの値を、NumberBoxの値に設定するということです。

しかし現時点だと、NumberBoxをいじるとRangeFaderも一緒に変化しますが、RangeFaderをいじった時、NumberBoxの値は変化しません。

RangeFader → NumberBoxの連携

各RangeFaderの値を変化させた時、対応するNumberBoxの値も変化するように実装していきます。

先ほどと同じように、sliderValueChanged()内を変更していきます。

void ModD3AudioProcessorEditor::sliderValueChanged(juce::Slider *slider)
{
    if (slider == &xRangeFader)
    {
        xMinNumBox.setValue(xRangeFader.getMinValue());
        xMaxNumBox.setValue(xRangeFader.getMaxValue());
        
        if (xRangeFader.getMinValue() >= 100)
        {
            xMinNumBox.setNumDecimalPlacesToDisplay(0);
        }
        else
        {
            xMinNumBox.setNumDecimalPlacesToDisplay(1);
        }

        if (xRangeFader.getMaxValue() >= 100)
        {
            xMaxNumBox.setNumDecimalPlacesToDisplay(0);
        }
        else
        {
            xMaxNumBox.setNumDecimalPlacesToDisplay(1);
        }
    }
・・・
}

ここでは、ついでにsetNumDecimalPlacesToDisplay()を使用して、100.0%に達した時に100%と表示されるように設定しています。値が100%以外の時は、これまで通り小数点第一位まで表示されます。

これで下記のようにNumberBoxで値を変化させると、RangeFaderの値も変化しますし、その逆も上手くいくようになりました。

CUBE側のNumberBox

次にCUBE(まだ無いですが)側のNumberBoxを実装していきます。

このNumberBoxには、mapボタンをオンにした時(ThreeValue時)のMidValueの値を表示させます。

また、NumberBoxの値を変化させた時にMidValueの値も一緒に変化するようにします。

NumBoxオブジェクトの作成

先ほどと同じように、まずはNumBoxオブジェクトをxyzごとに宣言します。

class ModD3AudioProcessorEditor  : public juce::AudioProcessorEditor, public juce::Slider::Listener, public juce::Button::Listener
{
・・・
private:
    NumBox xMidNumBox;
    NumBox yMidNumBox;
    NumBox zMidNumBox;

次に、下記コンストラクタ内で複数のメンバ関数を呼び出し、詳細設定をしていきます。

ModD3AudioProcessorEditor::ModD3AudioProcessorEditor (ModD3AudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
・・・
    juce::Colour blue = juce::Colour::fromFloatRGBA(0.427, 0.831, 1.0, 1.0);
    juce::Colour stGreen = juce::Colour::fromFloatRGBA(0.341, 0.741, 0.659, 1.0);
    juce::Colour yellow = juce::Colour::fromFloatRGBA(1.0, 0.706, 0.196, 1.0);

    addAndMakeVisible(&xMidNumBox);
    xMidNumBox.setColour(juce::Slider::textBoxTextColourId, blue);
    xMidNumBox.setValue(50);
    xMidNumBox.addListener(this);
    
    addAndMakeVisible(&yMidNumBox);
    yMidNumBox.setColour(juce::Slider::textBoxTextColourId, yellow);
    yMidNumBox.setValue(50);
    yMidNumBox.addListener(this);
    
    addAndMakeVisible(&zMidNumBox);
    zMidNumBox.setColour(juce::Slider::textBoxTextColourId, stGreen);
    zMidNumBox.setValue(50);
    zMidNumBox.addListener(this);

MOD-D3の初期値は50%となっているので、setValue()には50を指定しました。

また、RGBAをfloat型の数値で設定し変数に代入しています。この色は後述するxyzラベルの設定時にも使用します。

配置と大きさは下記のように設定します。

void ModD3AudioProcessorEditor::resized()
{
・・・
    xMidNumBox.setBounds(getWidth() / 4 - 30, getHeight() - 88, 60, 20);
    yMidNumBox.setBounds(getWidth() / 4 - 30, getHeight() - 63, 60, 20);
    zMidNumBox.setBounds(getWidth() / 4 - 30, getHeight() - 38, 60, 20);

現時点でビルドをすると下記のようになります。

NumberBox ⇄ RangeFaderの連携

それではNumberBoxとRangeFaderの値を連携させるために、sliderValueChanged()内をいじっていきます。

非常に長くなるので、x部分の実装のみ載せています。

void ModD3AudioProcessorEditor::sliderValueChanged(juce::Slider *slider)
{
    if (slider == &xRangeFader)
    {
        xMinNumBox.setValue(xRangeFader.getMinValue());
        xMaxNumBox.setValue(xRangeFader.getMaxValue());
        
        if (xRangeFader.getMinValue() >= 100)
        {
            xMinNumBox.setNumDecimalPlacesToDisplay(0);
        }
        else
        {
            xMinNumBox.setNumDecimalPlacesToDisplay(1);
        }

        if (xRangeFader.getMaxValue() >= 100)
        {
            xMaxNumBox.setNumDecimalPlacesToDisplay(0);
        }
        else
        {
            xMaxNumBox.setNumDecimalPlacesToDisplay(1);
        }

        if (xRangeFader.isThreeValue())
        {
            float range = xRangeFader.getMaxValue() - xRangeFader.getMinValue();
            xRangeFader.setValue((range * xMidNumBox.getValue() * 0.01) + xRangeFader.getMinValue());
        }
    }
・・・
}       
void ModD3AudioProcessorEditor::sliderValueChanged(juce::Slider *slider)
{
・・・
    if (slider == &xMidNumBox)
    {
        if (xMidNumBox.getValue() >= 100)
        {
            xMidNumBox.setNumDecimalPlacesToDisplay(0);
        }
        else
        {
            xMidNumBox.setNumDecimalPlacesToDisplay(1);
        }
        
        if (xRangeFader.isThreeValue())
        {
            float xRange = xRangeFader.getMaxValue() - xRangeFader.getMinValue();
            xRangeFader.setValue((xRange * xMidNumBox.getValue() * 0.01) + xRangeFader.getMinValue());
        }
    }

先程と違うのは、MidValueの値をTwoValue間の範囲内での割合になるようにしているところです。

例えば、MidValueの値が50%で、RangeFaderのMinValueが20%、MaxValueが60%であったとします。この時、MidValueの50%というのは、スライダーの範囲(0%〜100%)の50%という意味ではなく、TwoValueの範囲(20%〜60%)の50%になります。なので50%と表示されているものの、実際は40%を表していることになります。

下記の場合だと、xRangeFaderの範囲(MaxValue – MinValue)の71.5%でMidValueが設定されます。

xyzラベルの作成

作成したNumberBoxがどのRangeFaderと対応しているか分かりやすくするために、簡単な名前ラベルを作成していきます。

まずは下記Labelオブジェクトを宣言します。

class ModD3AudioProcessorEditor  : public juce::AudioProcessorEditor, private juce::Slider::Listener, public juce::Button::Listener
{
private:
・・・
    juce::Label xLabel;
    juce::Label yLabel;
    juce::Label zLabel;

次に、各NumberBoxの色と合わせて文字色や名前を設定していきます。

ModD3AudioProcessorEditor::ModD3AudioProcessorEditor (ModD3AudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
・・・
    addAndMakeVisible(&xLabel);
    xLabel.setColour(juce::Label::textColourId, blue);
    xLabel.setText("x", juce::NotificationType::dontSendNotification);

    addAndMakeVisible(&yLabel);
    yLabel.setColour(juce::Label::textColourId, yellow);
    yLabel.setText("y", juce::NotificationType::dontSendNotification);

    addAndMakeVisible(&zLabel);
    zLabel.setColour(juce::Label::textColourId, stGreen);
    zLabel.setText("z", juce::NotificationType::dontSendNotification);

ラベルの位置は、各NumberBoxの左側に少し隙間を空けた場所に配置します。

void ModD3AudioProcessorEditor::resized()
{
・・・
    xLabel.setBounds(xMidNumBox.getX() - 20, xMidNumBox.getY() + 2, 20, 15);
    yLabel.setBounds(yMidNumBox.getX() - 20, yMidNumBox.getY() + 2, 20, 15);
    zLabel.setBounds(zMidNumBox.getX() - 20, zMidNumBox.getY() + 2, 20, 15);

現時点でビルドをすると下記のようになります。これで残すところは細かい外観の設定です。

LookAndFeelのカスタマイズ

些細な違いですが、LookAndFeelをカスタマイズして外観を本家により近づけるようにします。

左側がビフォー、右側がアフターです。NumberBoxのフォントの大きさ、文字色等を変更していきます。

NumberBox用LookAndFeel

Sliderで作成したNumberBoxの外観は、LookAndFeelクラスのcreateSliderTextBox()で設定が行われています。

フォントを変えたいので、下記のようにNumberBox用にカスタマイズしたLookAndFeelクラスを作成し、当該メンバ関数をオーバーライドします。

class NumBoxLAF : public juce::LookAndFeel_V4
{
public:
    juce::Label* createSliderTextBox (juce::Slider& slider) override
    {
        auto* l = LookAndFeel_V2::createSliderTextBox (slider);
        l->setFont(18);
        return l;
    }
};

次にやることは、作成したクラスのオブジェクトを作成することです。

class ModD3AudioProcessorEditor  : public juce::AudioProcessorEditor, private juce::Slider::Listener, public juce::Button::Listener
{
private:
・・・
    NumBoxLAF numboxLAF;

最後に、適用させたい各NumberBoxオブジェクトに対して、setLookAndFeel()を呼び出すことで設定を行います。

ModD3AudioProcessorEditor::ModD3AudioProcessorEditor (ModD3AudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
    addAndMakeVisible(&xMidNumBox);
    xMidNumBox.setLookAndFeel(&numboxLAF);
    xMidNumBox.setColour(juce::Slider::textBoxTextColourId, blue);
    xMidNumBox.setTextBoxIsEditable(false);
    xMidNumBox.setValue(50);
    xMidNumBox.addListener(this);
    
    addAndMakeVisible(&yMidNumBox);
    yMidNumBox.setLookAndFeel(&numboxLAF);
    yMidNumBox.setColour(juce::Slider::textBoxTextColourId, yellow);
    yMidNumBox.setTextBoxIsEditable(false);
    yMidNumBox.setValue(50);
    yMidNumBox.addListener(this);
    
    addAndMakeVisible(&zMidNumBox);
    zMidNumBox.setLookAndFeel(&numboxLAF);
    zMidNumBox.setColour(juce::Slider::textBoxTextColourId, stGreen);
    zMidNumBox.setTextBoxIsEditable(false);
    zMidNumBox.setValue(50);
    zMidNumBox.addListener(this);

現時点でビルドをすると下記のようになります。

色の微調整

ここからは色の細かい調整をしていきます。MOD-D3の開発者suzuki kentaro氏からRGBAを教えてもらいました。

まずはMyLookAndFeelクラスで各色を定義しておきます。

class MyLookAndFeel : public juce::LookAndFeel_V4
{
・・・
private:
    juce::Colour blue = juce::Colour::fromFloatRGBA(0.427, 0.831, 1.0, 1.0);
    juce::Colour stYellow = juce::Colour::fromFloatRGBA(0.898, 0.902, 0.278, 1.0);
    juce::Colour offWhite = juce::Colour::fromFloatRGBA(0.831, 0.835, 0.890, 1.0);
    juce::Colour grey = juce::Colour::fromFloatRGBA(0.42, 0.42, 0.42, 1.0);
    juce::Colour blackGrey = juce::Colour::fromFloatRGBA(0.2, 0.2, 0.2, 1.0);
}

その後、下記のように定義した変数名で置き換えます。

class MyLookAndFeel : public juce::LookAndFeel_V4
{
public:
・・・
    void drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height,
                                           float sliderPos,
                                           float minSliderPos,
                                           float maxSliderPos,
                                           const juce::Slider::SliderStyle style, juce::Slider& slider) override
    {
        auto isThreeVal = (style == juce::Slider::SliderStyle::ThreeValueHorizontal);
        auto trackWidth = fmin (2.0f, (float) height * 0.25f);

        juce::Point<float> startPoint ((float) x, (float) y + (float) height * 0.5f);
        juce::Point<float> endPoint ((float) (width + x), startPoint.y);

        juce::Path backgroundTrack;
        backgroundTrack.startNewSubPath (startPoint);
        backgroundTrack.lineTo (endPoint);
        g.setColour (blackGrey);
        juce::PathStrokeType pathStrokeType(0.8);
        float dashedLength[2] = {2, 4};
        pathStrokeType.createDashedStroke(backgroundTrack, backgroundTrack, dashedLength, 2);
        g.strokePath(backgroundTrack, pathStrokeType);

        juce::Path valueTrack;
        juce::Point<float> minPoint, maxPoint, thumbPoint;
        
        minPoint = { minSliderPos, (float) height * 0.5f };
        maxPoint = { maxSliderPos, (float) height * 0.5f };
        thumbPoint = { sliderPos, (float) height * 0.5f };

                                
        auto minThumbWidth = getSliderThumbRadius (slider) - 22;
        auto maxThumbWidth = getSliderThumbRadius (slider) - 26;
        auto thumbWidth = getSliderThumbRadius (slider) - 19;
                        
        valueTrack.startNewSubPath (minPoint);
        valueTrack.lineTo (maxPoint);
        g.setColour (grey);
        g.strokePath (valueTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
                
        
        int thumbBeingDragged = slider.getThumbBeingDragged();
                
        if (thumbBeingDragged == 1 || thumbBeingDragged == 2)
        {
            slider.setMouseCursor(juce::MouseCursor::NoCursor);
            
            g.setColour (offWhite);
            g.drawRoundedRectangle (juce::Rectangle<float>(static_cast<float> (thumbBeingDragged == 1 ? maxThumbWidth : minThumbWidth), static_cast<float> (thumbBeingDragged == 1 ? maxThumbWidth : minThumbWidth)).withCentre(thumbBeingDragged == 1 ? maxPoint : minPoint), 50, 1.0);
            
            g.setColour (grey);
            g.fillEllipse (juce::Rectangle<float> (static_cast<float> (thumbBeingDragged == 1 ? minThumbWidth : maxThumbWidth), static_cast<float> (thumbBeingDragged == 1 ? minThumbWidth : maxThumbWidth)).withCentre (thumbBeingDragged == 1 ? minPoint : maxPoint));
        }
        else
        {
            slider.setMouseCursor(juce::MouseCursor::NormalCursor);
            g.setColour (offWhite);
            g.drawRoundedRectangle (juce::Rectangle<float>(static_cast<float> (maxThumbWidth), static_cast<float> (maxThumbWidth)).withCentre(maxPoint), 50, 1.0);
            g.drawRoundedRectangle (juce::Rectangle<float>(static_cast<float> (minThumbWidth), static_cast<float> (minThumbWidth)).withCentre(minPoint), 50, 1.0);
        }
        
        if (isThreeVal)
        {
            g.setColour (stYellow);
            g.fillEllipse (juce::Rectangle<float> (static_cast<float> (thumbWidth), static_cast<float> (thumbWidth)).withCentre (thumbPoint));
            
            if (minSliderPos >= maxSliderPos)
            {
                g.setColour (blue);
                g.fillEllipse (juce::Rectangle<float> (static_cast<float> (thumbWidth), static_cast<float> (thumbWidth)).withCentre (thumbPoint));
            }
        }
    }
    
void drawButtonBackground (juce::Graphics& g,
                               juce::Button& button,
                               const juce::Colour& backgroundColour,
                               bool shouldDrawButtonAsHighlighted,
                               bool shouldDrawButtonAsDown) override
    {
        auto bounds = button.getLocalBounds().toFloat().reduced (0.5f, 0.5f);

        g.setColour (backgroundColour);
        juce::Path path;
        path.addRectangle (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
        g.fillPath (path);

        g.setColour (blackGrey);
        g.strokePath (path, juce::PathStrokeType (1.0f));
    }

これでビルドをすると、下記のような見た目になります。本家に近づき、よりマットな感じになりました。

今後の課題

今回はCUBE実装に向けてレイアウトを整え、NumberBoxの実装を行いました。

次回からMOD-D3のメインUIであるCUBEの実装へと着手していきます。

ここが上手く実装できないと、JUCEで再現するという試みは失敗に終わると思います。気合を入れて、色々と試行錯誤しながらチャレンジしていきます。

Comments

Copied title and URL