juce::Component の properties について語る

本記事は [JUCE Advent Calendar 2021](https://qiita.com/advent-calendar/2021/juce) の12月15日向けに投稿した記事です。

マルチメディア向けC++フレームワークであるJUCEには、独自のGUIライブラリが含まれています。本記事では、JUCE独自のGUIライブラリを使いこなすにあたって知っておくといつか役に立つであろう、JUCEの中身について紹介します。


# JUCEのGUI要素について

多くのGUIフレームワークでは、ウインドウ内にGUI要素（コンポーネント、ウィジェット、アイコン等）を配置していくことでGUIを構築する仕組みを採用しています。JUCEフレームワークにおいても考え方は同様です。JUCEにおいてGUI要素の基本実装は`juce::Component`クラス*1によって提供されます。
開発者は、`juce::Component`を継承したクラスを宣言・定義をすることで、独自のGUI要素（以下コンポーネントと呼ぶ）の実装をアプリケーションコードに追加してウインドウ内に配置することができます。

*1 [juce::Componentクラス](https://docs.juce.com/master/classComponent.html)


# `juce::Component`のメンバ変数`properties`について

`juce::Component`クラスのソースコードを見てみると、`juce::NamedValueSet`クラス型の変数である`properties`というメンバ変数を確認することができます。*1

この`juce::NamedValueSet`クラス型とは、`juce::NamedValueSet::NamedValue`クラス型を扱うコンテナであり、開発者はこのクラス型の変数をKey-Valueストアとして利用することができます。*2

また`juce::NamedValueSet::NamedValue`クラスは、`juce::Identifier`クラス型で表される「文字列による識別子」と、`juce::var`クラス型で表されるバリアント型で表される「様々な型を格納可能なデータ」のペアを表現するクラスとして定義されています。*3

*1 [juce::Componentクラスのヘッダ](https://github.com/juce-framework/JUCE/blob/master/modules/juce_gui_basics/components/juce_Component.h#L2532)
*2 [juce::NamedValueSetクラス](https://docs.juce.com/master/classNamedValueSet.html)
*3 [juce::NamedValueSet::NamedValueクラス](https://docs.juce.com/master/structNamedValueSet_1_1NamedValue.html)


# `properties`へのアクセス方法について

`juce::Component::getProperties`関数*1を介して`juce::Component`インスタンスが保持する`properties`にアクセスすることができます。非const関数が提供されているので、非const関数を介して`properties`の値を変更することができます。

```cpp
   //==============================================================================
    /** Returns the set of properties that belong to this component.
        Each component has a NamedValueSet object which you can use to attach arbitrary
        items of data to it.
    */
    NamedValueSet& getProperties() noexcept                             { return properties; }

    /** Returns the set of properties that belong to this component.
        Each component has a NamedValueSet object which you can use to attach arbitrary
        items of data to it.
    */
    const NamedValueSet& getProperties() const noexcept                 { return properties; }
```

*1 [juce::Component::getProperties関数](https://docs.juce.com/master/classComponent.html#a23985453622396b6037eb5ec38dbd398)

# `properties`があると何ができる？

既に述べた通り、`juce::Component`のメンバ変数`properties`は「文字列による識別子」と「様々な型を格納可能なデータ」のペアを複数格納することができるKey-Valueストアです。
このメンバ変数を利用することで、`juce::Component`およびそれを継承したクラスは、型に縛られない任意のデータを各インスタンス毎に個別に保持することができるようになります。

# `properties`の利用用途: JUCE標準のColour系メソッド

## `juce::Component`クラスのColour系メソッド

`juce::Component`クラスにはコンポーネント描画時のColour設定をAPIを介して行える仕組みが予め用意されています。
これらのメソッドの内部実装は、int型で受け取った値をColourIdの文字列に変換したものをKeyとし、juce::Colour型の値をint型にキャストしたものをValueとするKey-Valueペアのデータを`properties`変数にGet/Setするものとなっています。

■ juce_Component.h

```cpp
   //==============================================================================
    /** Looks for a colour that has been registered with the given colour ID number.

        If a colour has been set for this ID number using setColour(), then it is
        returned. If none has been set, the method will try calling the component's
        LookAndFeel class's findColour() method. If none has been registered with the
        look-and-feel either, it will just return black.

        The colour IDs for various purposes are stored as enums in the components that
        they are relevant to - for an example, see Slider::ColourIds,
        Label::ColourIds, TextEditor::ColourIds, TreeView::ColourIds, etc.

        @see setColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour
    */
    Colour findColour (int colourID, bool inheritFromParent = false) const;

    /** Registers a colour to be used for a particular purpose.

        Changing a colour will cause a synchronous callback to the colourChanged()
        method, which your component can override if it needs to do something when
        colours are altered.

        For more details about colour IDs, see the comments for findColour().

        @see findColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour
    */
    void setColour (int colourID, Colour newColour);

    /** If a colour has been set with setColour(), this will remove it.
        This allows you to make a colour revert to its default state.
    */
    void removeColour (int colourID);

    /** Returns true if the specified colour ID has been explicitly set for this
        component using the setColour() method.
    */
    bool isColourSpecified (int colourID) const;
```

■ juce_Component.cpp

```cpp
Colour Component::findColour (int colourID, bool inheritFromParent) const
{
    if (auto* v = properties.getVarPointer (ComponentHelpers::getColourPropertyID (colourID)))
        return Colour ((uint32) static_cast<int> (*v));

    if (inheritFromParent && parentComponent != nullptr
         && (lookAndFeel == nullptr || ! lookAndFeel->isColourSpecified (colourID)))
        return parentComponent->findColour (colourID, true);

    return getLookAndFeel().findColour (colourID);
}

bool Component::isColourSpecified (int colourID) const
{
    return properties.contains (ComponentHelpers::getColourPropertyID (colourID));
}

void Component::removeColour (int colourID)
{
    if (properties.remove (ComponentHelpers::getColourPropertyID (colourID)))
        colourChanged();
}

void Component::setColour (int colourID, Colour colour)
{
    if (properties.set (ComponentHelpers::getColourPropertyID (colourID), (int) colour.getARGB()))
        colourChanged();
}
```

## JUCE標準ウィジェットのColourIdsについて

これらAPIの使い方は、`juce_gui_basics`モジュールに含まれるスライダー等のウィジェットを見てみることで、具体的な利用方法を確認することができます。例えば、`juce::Slider`クラス*1を実例としてリファレンス及びソースコードを見てみると、`juce::Slider`クラスにはenum型の`juce::Slider::ColourIds`の定義が追加されていることが確認できます。

■ juce_Slider.h

```cpp
   //==============================================================================
    /** A set of colour IDs to use to change the colour of various aspects of the slider.

        These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
        methods.

        @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
    */
    enum ColourIds
    {
        backgroundColourId          = 0x1001200,  /**< A colour to use to fill the slider's background. */
        thumbColourId               = 0x1001300,  /**< The colour to draw the thumb with. It's up to the look
                                                       and feel class how this is used. */
        trackColourId               = 0x1001310,  /**< The colour to draw the groove that the thumb moves along. */
        rotarySliderFillColourId    = 0x1001311,  /**< For rotary sliders, this colour fills the outer curve. */
        rotarySliderOutlineColourId = 0x1001312,  /**< For rotary sliders, this colour is used to draw the outer curve's outline. */

        textBoxTextColourId         = 0x1001400,  /**< The colour for the text in the text-editor box used for editing the value. */
        textBoxBackgroundColourId   = 0x1001500,  /**< The background colour for the text-editor box. */
        textBoxHighlightColourId    = 0x1001600,  /**< The text highlight colour for the text-editor box. */
        textBoxOutlineColourId      = 0x1001700   /**< The colour to use for a border around the text-editor box. */
    };
```

## ColourIdsを使用してColour系メソッドを呼ぶ

前述した`juce::Component`クラスのColour系メソッドを使うことで、ColourIdsの値をKeyとして`juce::Colour`型の値をValueとするKey-Valueペアをコンポーネントの`properties`に追加します。
この辺りの詳細は公式サイトのチュートリアルでも触れられています。*2

■ `juce::Slider`のオブジェクトに対してAPIからColour設定を行う例

```cpp
juce::Slider slider;
slider.setColour (juce::Slider::ColourIds::thumbColourId, juce::Colours::red);
```

*1 [juce::Slider::ColourIds](https://docs.juce.com/master/classSlider.html#a1012002c53381ccc7c1fe7e604a75f44)
*2 [Tutorial: Colours in JUCE](https://docs.juce.com/master/tutorial_colours.html)


## コンポーネントの描画メソッド内でColour設定を参照する

コンポーネントを描画するメソッドにおいて、`properties`メンバ変数に保持されている「ColourIds-`juce::Colour`」のペアを取得することで、開発者がAPIを介して設定したColour設定を描画メソッドに反映することができます。
`juce::Slider`クラスのコンポーネント描画メソッドの場合を例に示すと、`juce::Slider`クラスの描画メソッドは `juce::LookAndFeel`の派生クラスに置かれています。以下に`juce::LookAndFeel_V4::drawRotarySlider`のコードの一部を抜粋します。*1

■ juce_LookAndFeel_V4.cpp

```cpp
void LookAndFeel_V4::drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos,
                                       const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider)
{
    ...

    g.setColour (slider.findColour (Slider::thumbColourId));
    g.fillEllipse (Rectangle<float> (thumbWidth, thumbWidth).withCentre (thumbPoint));
}
```

*1 [juce_LookAndFeel_V4.cpp](https://github.com/juce-framework/JUCE/blob/master/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V4.cpp#L1094)

# まとめ

本記事では、`juce::Component`のメンバ変数`properties`について、その仕組みや用途について語ってみました。
また、`properties`が提供するKey-Valueストア機能の利用用途の実例として、JUCE標準で用意されているColour系メソッドの内部実装についても紹介しました。
`properties`という仕組みについて把握しておくと、カスタムコンポーネントを実装する際に役に立つと思われます。