Juce 라이브러리를 처음 생성하면 마주하게 되는 모듈이다. 왜냐하면 JUCEApplication 클래스를 상속받아야 메인 루프를 정의 및 실행을 할 수 있기 때문이다. Juce 애플리케이션의 초기화호 및 진입점(entry point)와 종료점(exit point)과 관련한 기능을 수행하며, 이외에는 프로젝트의 정보를 관리한다. 예제 코드는 다음과 같다.
class SweetJuce : public JUCEApplication
{
public:
//Don't put any kind of JUCE code in constructor and destructor!
SweetJuce() {}
~SweetJuce() {}
//this method will be called when application starts
void initialise (const String& commandLine) override {
//assign new window instance
sweetWindow.reset (new SweetWindow());
sweetWindow->setBounds (100, 100, 400, 500);
sweetWindow->setVisible (true);
}
//this method will be called when application quits
void shutdown() override {
sweetWindow = nullptr;
}
const String getApplicationName() override {
return "Sweet Juce!";
}
const String getApplicationVersion() override {
return "0.00001";
}
private:
//smart pointer and window instance
std::unique_ptr<SweetWindow> sweetWindow;
};
// this generates boilerplate code to launch our app class:
START_JUCE_APPLICATION (SweetJuce)
위의 예제 코드에서 눈여겨 볼 부분은 initialise
와 shutdown
이다. initialise
메소드에서 초기화를 진행하고, shutdown
메소드에서는 애플리케이션을 종료하면서 종료해야 할 서브 프로세스라던가, 해제되어야 할 인스턴스들을 처리해주면 된다. 그리고 override
키워드가 붙은 것을 보면 알 수 있지만, initialise
와 shutdown
은 가상 함수들이다.
이외에는 프로젝트의 정보를 반환하는 정도이며, 주석으로도 명시했지만 생성자 및 소멸자에서 Juce와 관련한 함수들을 호출하면 안된다.
GUI 프로젝트를 만든다면 자주 보게 될 클래스다. Juce의 윈도우 클래스로 윈도우를 생성 및 관리하는 것과 관련한 모듈이다. 보통 '애플리케이션 = 윈도우'로 생각하는 경우가 많은데, 애플리케이션에서 편의를 제공하기 위해 윈도우를 사용하는 것이다. 때문에 윈도우와 애플리케이션 모듈은 분리하는 것이 맞다.
그리고 보통의 GUI 라이브러리들은 윈도우와 그 컨텐츠들을 통합하여 관리하는데, Juce는 윈도우와 컨텐츠를 분리하여 관리한다. 때문에 공식 홈페이지에서 DocumentWindow
를 순수하게 윈도우를 관리하는 데에만 사용 할 것을 권장한다.
그렇다면 윈도우에 필요한 내용들을 어떻게 다룰 것인가 하는 문제가 남는다. 이와 관련하여서는 후술하도록 하겠다.
class SweetWindow : public juce::DocumentWindow {
public:
SweetWindow (juce::String name) : DocumentWindow (
name, //윈도우의 이름
juce::Desktop::getInstance() //윈도우의 색상값
.getDefaultLookAndFeel()
.findColour (juce::ResizableWindow::backgroundColourId),
DocumentWindow::allButtons //윈도우 헤드바에 표시할 버튼(종료 및 최소화 등)
) {
setUsingNativeTitleBar (false);
setContentOwned (new SweetComponent(), true);
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
#else
setResizable (true, true);
centreWithSize (getWidth(), getHeight());
#endif
setVisible (true);
}
void closeButtonPressed() override {
JUCEApplication::getInstance()->systemRequestedQuit();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
생성자 코드를 보면 상당히 많은 인자를 넘겨주는 것을 볼 수 있다. 첫번째로는 윈도우의 이름을 정해준다. String
이 C++에서 제공하는 표준 std::string
이 아닌데, Juce에서 자체적으로 제공하는 String
라이브러리를 사용한다. 지금은 언급하는 정도로 다루고, 좀 더 상세한 내용은 추후에 다루도록 하겠다.
그리고 생성자의 초기화 리스트에서 특이한 함수를 볼수 있는데, 다음 코드를 보도록하자.
juce::Desktop::getInstance() //Desktop 인스턴스를 호출
.getDefaultLookAndFeel() //기본LookAndFeel 인스턴스 호출
.findColour(juce::ResizableWindow::backgroundColourId)
//색상값 탐색
이 코드들 또한 사전지식이 있어야 이해가 가능한데, Desktop
과 LookAndFeel
에 대해 어느정도 이해를 하고 있어야한다. 먼저, Desktop
클래스는 이름 그대로 데스크탑, 머신에 대한 정보를 담은 모듈로 다른 프레임워크에서는 context
라는 이름으로 유사하게 사용되기도 한다. 그리고 LookAndFeel
클래스는 이름이 상당히 특이한데, 겉 모양새에 대한 모듈이다. 어쩌면 스킨이라는 표현이 더 와닿을 지도 모르겠다.
그 다음으로는 어떤 버튼들을 사용 할 것인지를 지정하면 기본적으로 넘겨준다. 그리고 생성자 블록 안의 코드들을 보면 몇 가지 설정들을 해주고 있다. 가장 눈여겨 보아야 할 부분이 setOwnedComponent
이다. 앞서 설명 했듯이, DocumentWindow
는 순수하게 윈도우와 관련한 기능들만 다루며, 그 내용에 대한 것들 별도로 정의해주어야 한다. 그 내용들의 정의가 바로 SweetComponent
안에 들어가 있는 것이다.
그래서 Component
를 상속받아 윈도우에 필요한 컨트롤(UI)와 동작들을 정의해줘야 한다. 지금은 이 정도로만 다루기로 하며, Component
에 대한 더 자세한 내용은 다른 포스팅에서 다루도록 하겠다.
마지막으로 매크로를 호출하는데, 해당 클래스가 복사가 불가능하게 만들고, 동시에 메모리 누수 여부를 관찰하는 것이다. C++의 고질적인 문제중 하나가 객체의 복사 문제와 메모리 누수이다. 그래서 복사를 불가능하게 하고, GC가 없기 때문에 자체적으로 이에 대한 대책을 강구해야 한다. Juce에서는 LeackObjectDetector
라는 모듈을 통해서 메모리를 관리 할 수 있는데, 이에 대한 자세한 내용은 다른 포스팅에서 다루겠다.