Contribution of a series of EFL(Enlightenment Foundation Libraries) articles.

Korean Title: 월간 마이크로소프트웨어 (마소인터렉티브)
Publication Date: 2012.07 ~ 2012.12

Enlightenment started as a project to build a Window Manager for X11. That was way back in 1996. It has grown much since. It still produces this Window Manager, but it has evolved to also cover Mobile, Wearable and TV UI needs for projects such as Tizen as well as traditional the “desktop” UI. In the process of developing a Window Manager, A set of libraries were developed in order to achieve that goal. These libraries are known collectively as EFL. They cover a range of functionality from main-loop, to graphics, scene graphs, networking, widgets, data storage, IPC and much more.







저작자 표시
신고


At times, it's hard to forget your enthusiasm.
Material: H pencil, A4


저작자 표시
신고
2. 프로그램 확장하기


자, 잠깐의 휴식을 가졌으므로(?) 검은 화면에 뭔가를 채워볼 시간이다. 먼저 윈도우 내에 사각형을 하나 생성하고 색상을 칠할 것이다. 물론 EFL 라이브러리에서는 이를 쉽게 하기 위한 기능을 제공하고 있다.

예제 소스 코드부터 보도록 하자.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);
 
   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);
 
   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);
   evas_object_resize(rect, 480, 400);
   evas_object_color_set(rect, 0, 0, 255, 255);
   evas_object_show(rect);

   elm_run();

   elm_shutdown();

   return 0;
} 


사각형 오브젝트를 생성하기 전에, 먼저 우리는 윈도우 오브젝트로부터 Evas 핸들을 얻어오고 있다. 이를 위해, evas_object_evas_get() API를 호출하며, 이 API는 오브젝트 포인터를 요구하고, 해당 오브젝트로부터 오브젝트가 소속된 Evas 핸들을 반환한다.

Evas *evas_object_evas_get(const Evas_Object *obj);

Evas에 대해 아직은 충분한 설명이 없지만, 다음 장에서 Evas에 대해 구체적으로 살펴볼 예정이므로 지금은 간략하게만 요약해 보도록 하자.

우리는 Evas를 캔버스 엔진으로 정의할 수 있다. Evas는 내부적으로 출력 버퍼를 초기화하고 이를 대상으로 렌더링 작업을 수행한다. 렌더링을 위해서는 화면에 그려질 오브젝트들의 그래픽 정보를 실질적으로 보유하고 관리를 한다. 그 외에도 Evas는 오브젝트들의 Z 순서를 위한 계층(Layer) 정보 및 마우스 입력과 같은 사용자 입력에 대한 이벤트 등을 처리하고 있다. Evas는 사용자 입력 이벤트를 어떤 오브젝트로 전달해야 할지, 어떤 오브젝트를 화면에 그려야 할지, 오브젝트들의 라이프 사이클을 결정하고 이들을 렌더링하는 역할을 한다.

사실 이 예제의 윈도우 오브젝트 역시 Evas에서 관리하는 하나의 Evas_Object에 해당되기 때문에 우리는 윈도우 오브젝트를 통해 Evas인스턴스에 대한 핸들을 얻어올 수 있으며, Evas핸들을 이용하면 Evas에 사각형 오브젝트를 추가할 수 있다. 일부 독자들은 "그럼 우리가 언제 Evas인스턴스를 생성한 거지?" 라고 의아해 할 수 있겠지만, 사실 이 부분은 elm_win_add()가 호출될 시에 내부적으로 수행되었다. 이 말은 즉, 기본적으로 윈도우를 생성할 때마다 하나의 Evas인스턴스가 생성되며 각 윈도우마다 렌더링 출력물을 갖게 된다.


그림 3 윈도우마다 Evas 인스턴스가 존재한다.


우리는 화면에 출력할 사각형 오브젝트를 생성하기 위해 evas_object_rectangle_add() API를 이용하였다. 다시 한번 말하지만, 이 API 역시 evas 접두사로 시작하며 이 말은 해당 기능이 Evas에서 제공하는 API임을 알 수 있다. 호출 시, Evas인자를 넘겨준 후 Evas_Object 타입의 사각형 오브젝트를 반환 받는다. 이 후 우리는 반환받은 rect 핸들을 가지고 이 사각형 오브젝트에 대한 세부적인 설정을 수행할 수가 있다.

Evas_Object *evas_object_rectangle_add(Evas *e);

우선 사각형의 사이즈를 윈도우와 같은 크기인 480 x 400로 변경하고 위치는 0, 0으로 화면의 좌측, 최상단으로 지정한다. 물론 기본 위치 값이 0, 0 이기 때문에 여기서는 애써 evas_object_move()를 호출하지 않았다. 이렇게 되면 사각형은 윈도우 내부에 가득 찬다. 다음으로 사각형의 색상을 지정한다. 이를 위해 evas_object_color_set() API를 호출하며 여기서는 파란색인 0, 0, 255, 255 (Red, Green, Blue, Alpha) 값을 인자로 전달한다.

void evas_object_color_set(Evas_Object *obj, int r, int g, int b, int a);

evas_object_move() 및 evas_object_resize()와 같이 색상 지정 API 역시 일반적으로 이용되는 API이기 때문에 다른 타입의 오브젝트에도 적용이 가능하다. 여기서 주의할 점은, 매개변수 r, g, b 값들은 알파 값에 의해 미리 계산된(pre-multiplied) 값을 요구하며 사용자는 직접 그 값을 계산해서 전달해야 한다.

예를 들어, 반투명한 흰색의 사각형을 원한다면,

evas_object_color_set(rect, 255, 255, 255, 127);

를 호출하는 것이 아니라,

evas_object_color_set(rect, 127, 127, 127, 127);


와 같이 호출해야 한다. 각 R, G, B 각 요소는

R = 255(R) * 127(A) / 255
G = 255(G) * 127(A) / 255
B = 255(B) * 127(A) / 255

와 같이 계산한 결과 값이다.

경험 상, 이 부분은 많은 개발자들이 오용하는 경우가 많았으므로 여러분들도 꼭 상기하여 실수하지 않기를 바란다.

다음으로는 evas_object_show() API를 호출하여 사각형 오브젝트가 화면에 보이도록 한다.

자, 그럼 이쯤에서 결과물을 확인해 보도록 하자. 만약 여러분의 애플리케이션이 검정색에서 파란색 배경으로 바뀌었다면 성공한 것이다.


4. 윈도우에 사각형 오브젝트를 추가한 결과


만약, 여러분이 배경 색을 다른 색상으로 바꾸고 싶다면 evas_object_color_set()의 r, g, b 의 인자 값을 변경해 보길 바란다.

마지막으로 콘솔이 아닌, 윈도우에 "Hello E World"를 출력해 보도록 하자. 다음 예제 소스 코드를 보면 이 역시 얼마나 쉬운지 알게 될 것이다.

#include <Elementary.h>
 
int main(int argc, char **argv)
{
   elm_init(argc, argv);

  //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementray Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);

   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);
   evas_object_resize(rect, 480, 400);
   evas_object_color_set(rect, 0, 0, 255, 255);
   evas_object_show(rect);

   //레이블 위젯을 생성한다. 이 위젯은 텍스트를 출력하는 기능을 수행한다.
   Evas_Object *label = elm_label_add(win);
   elm_object_text_set(label, "Hello E World!");
   evas_object_move(label, 100, 100);
   evas_object_resize(label, 200, 100);
   evas_object_show(label);

   elm_run();

   elm_shutdown();

   return 0;
}

이제 여러분은 EFL 프로그래밍 방식에 어느 정도 감이 잡혔을 것이라고 생각한다. 새로 추가한 볼드체 코드를 보도록 하자. 가장 먼저 elm_label_add()를 호출함으로써 Elementary에서 제공하는 레이블 위젯을 생성한다. 이 때 매개변수로 윈도우 오브젝트를 전달하는 것에 주목하도록 하자.

Evas_Object *elm_label_add(Evas_Object *parent);

이번에는 Evas가 아닌 Elementary API를 이용하여 레이블을 추가하고 있는 점에 주목하자. 사실, Evas에서도 텍스트를 출력하는 텍스트 오브젝트가 존재하며 글자를 출력한다는 측면에서 레이블 위젯과 기능적으로 동일하나, Evas에서 제공하는 원초적인 오브젝트에 비해 Elementary의 위젯들은 애플리케이션에 있어서 필요한 기반 기능을 보다 많이 가지고 있다. 다음은 대표적인 장점들을 나열한다.

포커스(Focus): 각 위젯 간의 포커스 이동. 포커스를 통해 어떠한 오브젝트가 키 이벤트를 전달받을 지 결정하며, 부모에서 자식으로, 자식에서 부모로 또는 자식에서 자식으로 포커스가 이동될 수 있다.
테마(Theme): 동일한 애플리케이션일지라도 다양한 룩앤필(Look & Feel)를 지원하기 위한 테마 설정이 가능하다.
스케일(Scale): 동일한 애플리케이션으로부터 다양한 화면 해상도에서 동일한 화면 구성을 유지하기 위한 GUI 확장성을 제공한다.
미러(Mirror): 여러 나라의 언어 특성에 맞게 각 위젯이 좌/우 대칭으로 반전되는 기능을 제공한다.
언어(Language): 다국어 변경을 지원하기 위해 Elementary는 언어와 관련된 기반 기능을 제공하여 애플리케이션에서 보다 쉽게 언어를 변경할 수 있도록 도와준다.

간략하게나마 설명을 덧붙여 놨는데 자세한 사항은 6장의 “위젯 툴킷: Elementary”에서 살펴보도록 하며 여기서는 일단 이러한 기능들을 Elementary 위젯으로 이용 가능하다는 사실만 알고 넘어가자.

앞서 윈도우를 생성할 때, Elementary에서는 부모-자식 관계가 존재한다고 언급했었는데 elm_label_add() API에서는 매개변수로 부모 오브젝트를 요구하고 있다는 점을 주목하길 바란다. 실제로 모든 Elementary 위젯들을 생성하는 API들은 부모 오브젝트를 인자로 요구하며 이 예제의 경우 윈도우는 레이블의 부모가 되며, 레이블은 윈도우의 자식이 될 것이다. 내부적으로 부모-자식 간의 트리 구조가 성립되어 자식은 부모에게 종속되는데 여기서 우리가 기억해야 하는 점은 부모 오브젝트가 제거될 시 자식도 같이 제거된다는 사실이다.

다음으로 elm_object_text_set() API를 호출함으로써 레이블 위젯이 출력할 텍스트를 지정하고 있다. elm_object_text_set()은 레이아웃을 상속받은 Elementary 위젯이라면 모두 이용할 수 있는 공용 API인데, 사실 텍스트를 출력하는 기능이 있는 위젯이라면 이 API를 통해 화면에 출력할 텍스트를 지정할 수 있다. API의 접두사가 "elm_label"이 아닌, "elm_object"로 시작되고 있다는 점을 주목하면 보다 기억하기 쉬울 것이다.

어쨌든 간에, 우리는 elm_object_text_set() API를 이용하여 "Hello E World!"를 출력하도록 지정하였다.

void elm_object_text_set(Evas_Object *obj, const char *label);  

이 API는 첫 번째 인자로 대상 오브젝트를, 두 번째 인자로 출력할 문자열을 요구한다.

다음으로 텍스트가 출력될 영역이 100, 100에서 200, 100이 되도록 지정하고 화면 상에 출력되도록 Evas API들을 이용하였다. 자, 그럼 결과물을 확인해 보도록 하자.


5. 윈도우에 Hello E World!를 출력한 결과


만약 여러분이100, 100에서 200, 100 영역에 "Hello E World!" 문자열이 가득 채워져 출력되길 바랬다면 오산이다. 해당 영역은 단지 레이블 위젯이 차지할 영역일 뿐이며, 실제로 출력될 텍스트는 레이블 위젯의 폰트명과 폰트 크기를 기반으로 출력될 뿐이다.

자, 이쯤이면 첫 번째 예제가 거의 완성되었다. 하지만 할 일이 하나 더 남아있다. 우리는 Ctrl+C를 통한 애플리케이션 종료가 아닌, 어떤 키 입력이 왔을 때 애플리케이션을 종료할 수 있어야 한다. 이를 위해, 키 입력 사실을 알 수 있어야 하며 어떤 키가 눌러졌는지도 파악할 수 있어야 한다. 이번 예제에서는 Evas를 통해 이를 구현할 것이며 Esc 키가 눌렸을 때 애플리케이션을 종료하도록 할 것이다. 소스 코드를 보도록 하자.

#include <Elementary.h>

void key_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Evas_Event_Key_Down *ev = event_info;

   if (!strcmp(ev->keyname, "Escape"))
     {
        printf("Good Bye!\n");
        elm_exit();
     }
}

int main(int argc, char **argv)
{
   elm_init(argc, argv);

   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   //윈도우 오브젝트에 키 입력 이벤트를 추가한다.
   evas_object_event_callback_add(win, EVAS_CALLBACK_KEY_DOWN, key_down_cb, NULL);

   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);

   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);

   //...

   return 0;
}

새로 추가된 부분은 볼드체로 표시하였다. 먼저 main() 함수에서 윈도우 오브젝트를 생성하는 부분을 보면 윈도우 오브젝트에 이벤트 콜백 함수를 등록하는 부분이 추가되었다.

void evas_object_event_callback_add(Evas_Object *obj, Evas_Callback_Type type, Evas_Object_Event_Cb func, const void *data);

첫 번째 매개변수는 이벤트 대상이 되는 오브젝트를 가리키며 예제에서는 윈도우 오브젝트에 해당한다. 두 번째 매개변수는 이벤트 타입을 전달 받는데, 우리는 받고자 하는 이벤트 종류를 두 번째 인자로 결정할 수가 있다. 예제에서는 키가 눌렸을 때의 이벤트를 받고자 하기 때문에 EVAS_CALLBACK_KEY_DOWN을 전달한다. 이 외에도 우리가 확인할 수 있는 이벤트 종류는 다양하며 다음과 같은 이벤트들에 대해서 우리는 콜백 함수를 등록할 수 있다.

EVAS_CALLBACK_MOUSE_IN - 마우스 커서가 대상 안에 들어온 순간
EVAS_CALLBACK_MOUSE_OUT - 마우스 커서가 대상 밖으로 벗어날 때
EVAS_CALLBACK_MOUSE_DOWN - 마우스 버튼이 눌렸을 때
EVAS_CALLBACK_MOUSE_UP - 마우스 버튼이 해제되었을 때
EVAS_CALLBACK_MOUSE_MOVE - 마우스가 이동할 때
EVAS_CALLBACK_MOUSE_WHEEL - 마우스 휠이 작동할 때
EVAS_CALLBACK_MULTI_DOWN - 멀티 터치와 같은 환경에서 동시에 여러 터치 입력이 발생했을 때
EVAS_CALLBACK_MULTI_UP - 여러 터치 입력 중에서 첫 번째를 제외한 나머지 터치 입력들이 해제될 때
EVAS_CALLBACK_MULTI_MOVE - 여러 터치 입력 중에서 첫 번째를 제외한 나머지 터치 입력들이 이동할 때
EVAS_CALLBACK_FREE – 삭제 요청을 받은 후, Evas에서 완전히 제거될 때
EVAS_CALLBACK_KEY_DOWN - 키 입력이 발생할 때
EVAS_CALLBACK_KEY_UP - 키 입력이 해제될 때
EVAS_CALLBACK_FOCUS_IN – 포커스 상태가 될 때
EVAS_CALLBACK_FOCUS_OUT – 포커스 상태에서 해제될 때
EVAS_CALLBACK_SHOW - evas_object_show()가 호출될 때
EVAS_CALLBACK_HIDE - evas_object_hide()가 호출될 때
EVAS_CALLBACK_MOVE - evas_object_move()가 호출될 때
EVAS_CALLBACK_RESIZE - evas_object_resize()가 호출될 때
EVAS_CALLBACK_RESTACK - evas_object_stack_below(), evas_object_stack_above()등이 호출되어 레이어의 Z 오더의 순서가 바뀔 때
EVAS_CALLBACK_DEL - evas_object_del()이 호출될 때
EVAS_CALLBACK_HOLD - 이벤트 전달 상태가 변경될 때
EVAS_CALLBACK_CHANGED_SIZE_HINTS – evas_object_size_hint_weight_set(), evas_object_size_hint_align_set()과 같은 오브젝트의 크기 힌트 값이 변경될 때
EVAS_CALLBACK_IMAGE_PRELOADED - 이미지 오브젝트의 경우 이미지의 PRELOAD가 완료되었을 때

세 번째 매개변수와 네 번째 매개변수는 각각 등록할 콜백 함수 포인터와 콜백 함수에 전달될 사용자 데이터 포인터를 전달 받는다. 이번 예제에서는 콜백 함수에 전달할 데이터가 딱히 없으므로 네 번째 인자로 NULL을 전달하고 있다. 다음은 콜백 함수의 원형이다.

void (*Evas_Object_Event_Cb)(void *data, Evas *e, Evas_Object *obj, void *event_info);

첫 번째 매개변수로 앞서 콜백 함수 등록 시 네 번째로 전달한 사용자 데이터 포인터가 전달되며, 두 번째는 Evas 핸들, 세 번째는 이벤트 대상 오브젝트, 네 번째는 이벤트 타입에 해당하는 부가적인 정보가 전달된다.

예제에서는 key_down_cb()을 main() 앞에 구현하였고 이를 이벤트 콜백 함수로서 추가하였다. 실제로 키 입력이 발생할 시 이 콜백 함수가 호출될 것이며 첫 번째 매개변수로는 NULL 값이, 두 번째는 Evas 핸들, 세 번째 매개변수로 윈도우 오브젝트가 전달되고 네 번째 매개변수로는 EVAS_CALLBACK_KEY_DOWN에 해당하는 부가적인 정보가 전달된다. 여기서 우리는 네 번째 인자 event_info를 Evas_Event_Key_Down 포인터 형으로 변환하고 우리가 원하는 정보를 확인할 수가 있다. 물론 EVAS_CALLBACK_MOUSE_IN, EVAS_CALLBACK_MULTI_UP와 같은 이벤트 콜백을 등록했다면, 그에 해당하는 데이터로 변환하여 이용해야 한다. 실제로 어떠한 데이터 형으로 변환해야 할지는 Evas 문서를 확인하거나 다음 장, “캔버스 엔진: Evas” 편을 확인해 보길 바란다.

우리는 event_info 인자를 Evas_Event_Key_Down 타입의 포인터 형으로 변환함으로써 구조체가 담고 있는 데이터에 접근할 수 있으며 이 중, keyname을 통해 현재 어떤 키가 눌려졌는지 확인할 수 있다. 예제에서는 Escape (ESC) 키가 눌러졌는지 확인하기 위해서 단순히 문자열 비교를 하고 있으며, 실제로 키 이름이 동일할 시 elm_exit()를 호출하여 elm_run()에 의해 가동 중인 메인 루프를 종료시킨다.

여기서 매우 추상적으로만 보았던 elm_run()의 메인 루프는 다음과 같은 코드로 표현할 수 있을 것이다.

#include <Elementary.h>
 
void key_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   //...
}

int main(int argc, char **argv)
{
   elm_init(argc, argv);

   //...

   //레이블 위젯을 생성한다. 이 위젯은 텍스트를 출력하는 기능을 제공한다.
   Evas_Object *label = elm_label_add(win);
   elm_object_text_set(label, "Hello E World!");
   evas_object_move(label, 100, 100);
   evas_object_resize(label, 200, 100);
   evas_object_show(label);
   
   //elm_run()이 호출되면 메인 루프가 가동되며 내부적으로 여러 작업들이 수행될 것이다.
   while(1)
   {
    //윈도우, 사각형, 레이블 등, 오브젝트들의 위치 및 크기 계산 후 렌더링
    //만약 시스템으로부터 키 입력이 발생했다면 사용자가 등록한 key_down_cb 호출
    //만약 elm_exit()가 호출 되었다면 while문 종료
   }
   
   elm_shutdown();

   return 0;
}

자, 그럼 실제로 애플리케이션을 가동 후 Esc 키를 눌러 종료해 보길 바란다. 이제, 윈도우가 생성되고 윈도우 내에 색상 및 텍스트가 출력되며 키 이벤트에 반응하는 그럴듯한 첫 번째 애플리케이션이 완성되었다!


저작자 표시
신고