UIがリッチになると、いいプラグインになる気がしてきました。
視認性がいいと、作業性もあがるし、雰囲気も高価になるよね~
JUCEのプラグインのUIを改善しています。前回のダイヤルUI作成に引き続き、ボタン画像を作成しました。また、ボタンは、オンの状態とオフの状態がわかりやすいように変化するような仕組みを追加しました。今回のボタンはモーメンタリの動作をするような雰囲気をだしたかったので、一回押して、すぐオフになる一瞬の間だけオン状態に変化するような挙動にしてみました。
こんな人の役に立つかも
・UIデザインを始めたいプログラマーの人
・JUCEプラグインでリッチな画像ボタンを作成したい人
・JUCEプラグインでボタンの画像を状態変化させたい人
画像の準備
イラストレータを利用して、次のようなボタンを作成しました。前回ダイヤルノブを作成したときの、ダイヤルのベースを流用して、作成しました。
ダイヤルのUI画像を作成した記事は、こちらです。
オン時の光の具合は、youtubeの動画を参考にしてみました。
それぞれのボタンは、50×50で作成しました。次の4つの画像パーツとして書き出しました。(PNG形式)
Projucerのプロジェクト内、Resourcesフォルダに画像を配置しました。(画像データ本体は、プロジェクトフォルダのSourceフォルダ内に入れました。)
プログラムの実装
実装の概要
アンドゥボタンを押したタイミングで、bool型「undo_light_flg」をtrueとします。
毎秒30回実行されるタイマー処理関数「timerCallback」関数で、「undo_light_flg」がtrueのとき、int型の変数「undo_light_cnt」を増加させます。
「undo_light_cnt」が(例えば)11以上となったときに、「undo_light_flg」とfalseとします。
「undo_light_flg」がfalseのときは「undo_light_cnt」には0を代入し続けます。
ルックアンドフィールクラスには、undo_light_cnt変数への参照をわたして、この値が0以外の値のときにオン状態の画像を表示するように描画します。
3つの関数が複合的に作用して、ボタンの状態の変化を表現します。図でも表現してみました。上から下へ時間が進んでいきます。
リドゥボタンも同様の処理を行います。
PluginEditor.h
ボタンのルックアンドフィールクラスを作成
undoボタン用のルックアンドフィールクラス「UndoButtonLookAndFeelStyle01」を作成しました。
LookAndFeel_V4クラスを継承します。
class UndoButtonLookAndFeelStyle01 : public juce::LookAndFeel_V4
{
public:
//[1]コンストラクタです。
UndoButtonLookAndFeelStyle01(int& i) : counter(i){}
//[2]描画関数です。
void drawButtonBackground(juce::Graphics& g, juce::Button&, const juce::Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
{
if (counter == 0) {
juce::Image button_img = juce::ImageCache::getFromMemory(BinaryData::undo_off_png, BinaryData::undo_off_pngSize);
g.drawImageAt(button_img, 0, 0);
}
else {
juce::Image button_img = juce::ImageCache::getFromMemory(BinaryData::undo_on_png, BinaryData::undo_on_pngSize);
g.drawImageAt(button_img, 0, 0);
}
}
private:
//[3]カウンタ変数の参照を定義しました。
int& counter;
};
[1]のコンストラクタでは、int型の変数の参照を受け取り、初期化します。[3]に、privateなメンバとしてcounterというint型の参照を定義しています。これをメンバイニシャライザで初期化しています。
[2]は、ボタン画像の描画関数です。ボタンの背景を画像とすることでボタンの画像を表示します。受け取ったint型の参照は、undo_light_cnt変数を見ることになりますので、このカウンタ値が0のときは、オフ時のボタン画像を表示します。このカウンタ値が0以外のとき(タイマーコールバックでカウンタが増加している時)は、オンのボタン画像を表示するように条件を作成しています。
ここで、少しはまった点として、クラス内の参照の初期化方法です。C++のクラス内メンバの参照の初期化には、メンバイニシャライザで行う必要があるとのことです。こちらのサイトを参考にさせて頂きました。
redoボタン用のルックアンドフィールクラスを↑のクラスと同じように作成します。「ReduButtonLookAndFeelStyle01」というクラス名にしました。redoのカウンタ変数は、redo_light_cntとなります。
class RedoButtonLookAndFeelStyle01 : public juce::LookAndFeel_V4
{
public:
RedoButtonLookAndFeelStyle01(int& i) : counter(i) {}
void drawButtonBackground(juce::Graphics& g, juce::Button&, const juce::Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
{
if (counter == 0) {
juce::Image button_img = juce::ImageCache::getFromMemory(BinaryData::redo_off_png, BinaryData::redo_off_pngSize);
g.drawImageAt(button_img, 0, 0);
}
else {
juce::Image button_img = juce::ImageCache::getFromMemory(BinaryData::redo_on_png, BinaryData::redo_on_pngSize);
g.drawImageAt(button_img, 0, 0);
}
}
private:
int& counter;
};
プラグイン本体のEditorクラス「〇〇AudioProcessorEditor」
timerCallback関数
1秒間に30回実行されるタイマーコールバック関数の実装です。[1]のカウンター変数とフラグの変数を準備します。そして、[2]の条件を追加して、カウンターの数値によってボタンが光る時間を設定しています。
class _01_panda_filterAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer
{
public:
//...略...
//[1]カウントアップ用の変数を追加しました。
bool undo_light_flg = false;
int undo_light_cnt = 0;
bool redo_light_flg = false;
int redo_light_cnt = 0;
void timerCallback() override
{
//[2]タイマーコールバックに以下の処理を追加しました。
if (undo_light_flg == true) {
undo_light_cnt++;
//[2-1]3の数字が光るフレーム数になります。
if(undo_light_cnt > 3) undo_light_flg = false;
}
else {
undo_light_cnt = 0;
}
if (redo_light_flg == true) {
redo_light_cnt++;
if (redo_light_cnt > 3) redo_light_flg = false;
}
else {
redo_light_cnt = 0;
}
//...略...
}
privateなメンバ
privateなメンバとして、アンドゥ、リドゥボタンのルックアンドフィールクラスを追加しました。
UndoButtonLookAndFeelStyle01 UndoButtonLookAndFeel01;
RedoButtonLookAndFeelStyle01 RedoButtonLookAndFeel01;
PluginEditor.cpp
コンストラクタ
メンバイニシャライザを変更しました。
_01_panda_filterAudioProcessorEditor::_01_panda_filterAudioProcessorEditor (_01_panda_filterAudioProcessor& p,
juce::AudioProcessorValueTreeState& vts,
juce::UndoManager& um)
//...略...
,audioProcessor(p)
, undoButton(), redoButton()//[1]ボタンテキストを削除します。
, undoManager(um)
//[2]ボタンのルックアンドフィールクラス初期化です。
, UndoButtonLookAndFeel01(undo_light_cnt)//アンドゥボタンのルックアンドフィール
, RedoButtonLookAndFeel01(redo_light_cnt)//リドゥボタンのルックアンドフィール
{
//...略...
テキストボタンのテキストを表示しないように、[1]の初期化時に与える文字列を削除しました。
次に、[2]のように、undoボタン、redoボタンのルックアンドフィールクラスにカウンターの変数を渡します。先ほど定義したルックアンドフィールクラスのコンストラクタでは、仮引数として、「int& i」として、参照を受け取るようにしているので、それぞれ、undo_light_cntとredo_light_cnt変数わたして、これらの変数を見ることができるようにします。C++の参照の渡し方で個人的に少し混乱するのですが、参照に対しては、普通に変数を渡せばよいので、ここに与える引数は、int型の変数を指定します。
次に、ボタンを押した時のラムダ式にフラグを立てるようにします。このフラグがtrueになる部分が今回のボタンの光る仕組みのトリガーになります。
//...略...
addAndMakeVisible(undoButton);
addAndMakeVisible(redoButton);
undoButton.onClick = [this] {
undoManager.undo();
//[1]ボタンクリックでフラグをオンにします。
undo_light_flg = true;
};
redoButton.onClick = [this] {
undoManager.redo();
//[2]ボタンクリックでフラグをオンにします。
redo_light_flg = true;
};
//[3]ルックアンドフィールをアンドゥ、リドゥボタンに紐づけます。
undoButton.setLookAndFeel(&UndoButtonLookAndFeel01);
redoButton.setLookAndFeel(&RedoButtonLookAndFeel01);
[1]または、[2]でそれぞれ、アンドゥ、リドゥボタンを押したタイミングでフラグ変数「undo_light_flg」、「redo_light_flg」をtrueにします。これによって、タイマー処理でカウンター値(undo_light_cntまたはredo_light_cnt)が増加するようにします。
すごく原始的な仕組み・・・
デストラクタ
最後に、デストラクタでボタンのルックアンドフィールを解除します。
_01_panda_filterAudioProcessorEditor::~_01_panda_filterAudioProcessorEditor()
{
//...略...
//ルックアンドフィールクラスをヌルポインタに設定します。
undoButton.setLookAndFeel(nullptr);
redoButton.setLookAndFeel(nullptr);
}