カテゴリー
C++ JUCE

JUCE その2 UIの基礎

UIについては公式Tutorialを以下の順番にやるのが良いと思います。

Application Window

Component

Graphics

以下はJUCEApplicationを継承したApplication classの最小コードです。

class MainComponentTutorialApplication: public juce::JUCEApplication
{
public:
  MainComponentTutorialApplication() {}
  const juce::String getApplicationName() override
  {
    return ProjectInfo::projectName; 
  }
  const juce::String getApplicationVersion() override 
  { 
    return ProjectInfo::versionString;
  }
  bool moreThanOneInstanceAllowed() override { return true; }
  void initialize (const juce::String& commandLine) override
  {
    // [3] initializing the mainWindow instance.
    mainWindow.reset(new MainWindow(getApplicationName()));
  }
  void shutdown() override
  {
    // [4] nullifying the pointer 
    mainWindow = nullptr;
  }
  void systemRequestedQuit() override
  {
    quit();
  }

  // [1] nested class inside the application class
  class MainWindow: public juce::DocumentWindow {
  public:
    MainWindow(juce::String name): DocumentWindow(name,
                                          juce::Colours::lightgrey,
                                          DocumentWindow::allButtons)
    {
      // [1] setup
      setUsingNativeTitleBar(true);
      // [5] resize will work automatically when setResizable is set
     // you can write custom layout logic inside resized() 
     // virtual func of Component class
   setResizable(true, false);
      centreWithSize(300, 200);
      setVisible(true);
    }
    void closeButtonPressed() override
    {
      JUCEApplication::getInstance()->systemRequestedQuit();
    }
  private:
   JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
  };
private:
  // [2] declare as a private member of the class. 
  // unique_ptr is a scoped pointer.
   // It gets deleted automatically when it becomes out of scope.
  std::unique_ptr<MainWindow> mainWindow;
};

この状態からComponentを追加していきます。

JUCEでは、全てのGUIエレメント(button, slider, textFieldなど)がComponentクラスを継承(derive, inherit)しています。

ApplicationにまずmainComponentを追加し、その他のGUIエレメントはmainComponentのchildrenとして追加されます。

ProjucerのFile Explorerの+ボタンをクリックし、Add New Component class(split between a CPP & header)を選択し、ファイル名をMainComponentとします。

// MainComponent.h

class MainComponent  : public juce::Component
{
public:
    MainComponent();
    ~MainComponent() override;

    // [5] two important virtual functions
    void paint (juce::Graphics&) override;
    void resized() override;

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Componentベースクラスには2つ重要なvirtual function(subclassでoverrideするfunction)があり、全てのcomponentで定義される必要があります。

Component::paint() はこのcomponentがスクリーン上にどのように描画されるかを定義し、

Component::resized() は画面リサイズ時の挙動を定義します。

// MainComponent.cpp
void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll (juce::Colours::lightblue);
 
    g.setColour (juce::Colours::darkblue);
    g.setFont (20.0f);
    g.drawText ("Hello, World!",
                getLocalBounds(), 
                juce::Justification::centred, 
                true);
}
// Main.cpp
#include "MainComponent.h"

// MainWindow constructor
MainWindow(juce::String name): DocumentWindow(name,
                                    juce::Colours::lightgrey,
                                   DocumentWindow::allButtons)
{
  setUsingNativeTitleBar(true);

  // assigning MainComponent instance in setContentOwned()
  setContentOwned(new MainComponent(), true);

  // [6 ] in order for getWidth() and getHeight() to work, 
  // MainComponent needs to have setSize() set in it's constructor
  centreWithSize(getWidth(), getHeight());
  setVisible(true);
}
// MainComponent.cpp
MainComponent::MainComponent()
{
  setSize(400, 300);
}

[6] componentのサイズ設定し忘れはJUCEでよくあるbugの原因、、、とのことです。

これらvirtual functions はresized()、paint()の順で、componentのinitialize時に一度呼ばれます。これらは必要に応じてシステムがコールするので、プログラマーが自分でコールしてはいけません。

[5] MainWindowのconstructorでsetResizable()を設定すると、Component::resized()に何も記述しなくてもリサイズは動作します。child elementなどのレイアウトに関することをresized()に記述出来ます。

paint(juce::Graphics& g) にはGraphicsクラスのインスタンスのアドレス g が渡されています。この g を使って色々なUIエレメントを描画出来ます。ほぼ全ての場合、Graphicsクラスはpaint()内でのみ使用されます。

Fontの設定

g.setFont(20.0f); // set size of the font

juce::Font mainComponentFont ("Times New Roman", 20.0f, juce::Font::italic);
// font styles can be used as a bitmask
juce::Font mainComponentFont ("Times New Roman", 20.0f, juce::Font::bold | juce::Font::italic);
g.setFont(mainComponentFont); // can pass a Font object

// using getLocalBounds()
g.drawText(currentSizeAsString, getLocalBounds(), juce::Justification::centred, true);
// using Justification::Flags
g.drawText(currentSizeAsString, getLocalBounds(), juce::Justification::topLeft, true);
// using explicit size and position
g.drawText(currentSizeAsString, 20, 40, 200, 40, juce::Justification::centred, true);

// draws a line with 5 pixels width from (10, 300) to (590, 300), 
g.drawLine (10, 300, 590, 300, 5);
// draws a rectangle with origin (300, 120), width 200, height 170
g.drawRect (300, 120, 200, 170);
// draws a ellipse inside a rectangle with origin (530, 120), width and height 60 pixels
g.drawEllipse (530, 120, 60, 60, 3);