[JUCE] ホットリローダーの実装

# hot_reloaderモジュール
各種ファイルのホットリローダーが欲しい時、毎回実装してしまっていたのですがせっかくなのでmodule化して使いまわせるようにしました。
ファイル(もしくはディレクトリの場合は再帰的に子ファイル)の更新状況を監視し、変更があればコールバックを呼びます。
ソースコード一つのみなのでJUCE moduleではなくてソースコードに直接含めるのでもいいかもしれません。

https://github.com/migizo/hot_reloader

## 使用方法
projucerの場合は「Modules」でhot_reloaderモジュールを追加し以下のように使用可能です。
```c++
    // .h
    std::unique_ptr<HotReloader> fileHotReloader;

    // .cpp
    auto fileReloadCallback = [this](const juce::File& file)
    {
        // ファイル更新時の処理
    };
    fileHotReloader.reset(new HotReloader(/* 監視対象のjuce::File */, fileReloadCallback));

```


# サンプルコード

https://github.com/migizo/HotReloaderTest

![hotReloaderTest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136558/a4d409a6-a66b-08e1-e4b2-f6f6aba83e05.gif)

以下のようなファイル階層でsample.txtとsampleFolderの更新があった場合にコンポーネントの更新を行っています。

```
Resources/
├ sample.txt
└ sampleFolder/
  ├ sample1.txt
  ├ sample2.txt
  └ ...
```

sampleFolderフォルダ以下は子を再帰的にチェックしてそのファイルの数だけテキスト表示コンポーネントを追加しています。
(例: sampleFolder/sub/sample.txtファイルを追加するとテキスト表示コンポーネントが追加表示されます。)

### projucer設定
- 「Modules」でhot_reloaderを追加、
-  「Exporters/Xcode(macOS)」「Custom Xcode Resource Folders」に
    - Resources/sample.txt
    - Resources/sampleFolder を追加

### ソースコード
```c++:MainComponent.h

#pragma once

#include <JuceHeader.h>

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

    //==============================================================================
    void paint (juce::Graphics&) override;
    void resized() override;

private:
    //==============================================================================
    // Resources/sample.txt
    std::unique_ptr<HotReloader> fileHotReloader;
    juce::TextEditor fileTextView;

    // Resources/sampleFolderフォルダ
    void resetFolderViewBounds(juce::Rectangle<int> area);
    std::unique_ptr<HotReloader> folderHotReloader;
    juce::OwnedArray<juce::TextEditor> folderFileTextView;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

```

```c++:MainComponent.cpp
#include "MainComponent.h"

//==============================================================================
MainComponent::MainComponent()
{
    juce::File rsrcDir = juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentApplicationFile).getChildFile("Contents/Resources");
    
    //------------------------------------------
    // Resources/sample.txt
    //------------------------------------------
    addAndMakeVisible(fileTextView);
    fileTextView.setMultiLine(true);
    fileTextView.setReadOnly(true);
    
    auto fileReloadCallback = [this](const juce::File& file)
    {
        fileTextView.setText(file.loadFileAsString());
    };
    fileHotReloader.reset(new HotReloader(rsrcDir.getChildFile("sample.txt"), fileReloadCallback));
    
    //------------------------------------------
    // Resources/sampleFolderフォルダ
    //------------------------------------------
    auto folderReloadCallback = [this](const juce::File& folder)
    {
        juce::Array<juce::File> fileList = folder.findChildFiles(juce::File::TypesOfFileToFind::findFiles, true);

        folderFileTextView.clear();
        for (auto& f: fileList)
        {
            auto* textView = new juce::TextEditor();
            addAndMakeVisible(textView);
            textView->setMultiLine(true);
            textView->setReadOnly(true);
            textView->setText(f.loadFileAsString());
            folderFileTextView.add(textView);
        }
        
        auto area = getLocalBounds();
        area.removeFromTop(80);
        resetFolderViewBounds(area);
    };
    folderHotReloader.reset(new HotReloader(rsrcDir.getChildFile("sampleFolder"), folderReloadCallback));
    
    setSize (600, 400);
}

MainComponent::~MainComponent()
{
}

//==============================================================================
void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
}

void MainComponent::resized()
{
    auto area = getLocalBounds();
    fileTextView.setBounds(area.removeFromTop(80));
    resetFolderViewBounds(area);
}

//==============================================================================
void MainComponent::resetFolderViewBounds(juce::Rectangle<int> area)
{
    area.removeFromLeft(20);
    for (auto* const textView : folderFileTextView)
    {
        textView->setBounds(area.removeFromTop(80));
    }
}

```