본문 바로가기

IT

[EFL] 라이브러리를 사용하는 개발자가 원하는건 자유도? (EFL Smart Object)

아직 추위가 가시지 않은 어느 봄날,

다수의 앱에서 사용할 수 있는 라이브러리를 하나 개발해달라는 요청을 받았습니다.

라이브러리는 화면을 구성하는 컴포넌트를 포함하고 있었기 때문에

EFL 라이브러리로 컴포넌트를 구성하기로 하였습니다.


라이브러리 설계를 위한 고민이 시작되었습니다.

라이브러리를 사용하는 개발자의 '자유도'를 고려하여 설계할 것인지,

라이브러리를 쉽게 사용할 수 있게 '접근성'을 고려하여 설계할 것인지,

몇 명의 개발자와 머리를 맞대고 방향을 고민했습니다.


'자유도'를 제공하기 위해 사용한 방식은,

Smart object를 interface로 사용하여 함수를 제공하는 것이었습니다.




안녕하세요, Tizen 개발자 윤진입니다.


자유도.

자유도가 높으면 그만큼 많은 함수를 숙지해야할 책임이 생깁니다.

대신 이 많은 함수들을 절묘하게 조합하면,

제법 재미난 결과물을 도출해낼 수도 있습니다.


하지만, 간단한 컴포넌트를 하나 그리려고 하는데,

함수를 수십개씩 사용해야 한다는 단점도 있겠죠.

개발자가 익혀야 하는 함수의 갯수만큼 진입장벽은 높아집니다.


이에 반해 자유는 최대한 제한하지만,

단 한두개의 함수로 컴포넌트를 구성하는 방법도 있습니다.

개발자가 세부적인 설정을 할 수는 없지만,

기본적으로 제공하는 몇 개의 함수만으로도 컴포넌트를 사용할 수 있습니다.


넓은 자유도 안겨줄 것인가.

낮은 진입장벽을 제공할 것인가.


라이브러리를 사용하는 최종 소비층이 누구인지에 따라 함수 프로토타이핑을 달리 해야합니다.


만약 라이브러리의 주사용계층이 앱개발자라면?

간단한 함수 몇 개만으로 앱을 짤 수 있는 방법을 제공해야합니다.

쉽고- 간단하고- 빠르게- 사용할 수 있는 함수군을 만들어두고,

필요하다면 자유도를 위한 함수군도 부가적으로 제공하는 편이 낫습니다.


플랫폼 간에 치열한 경쟁에서,

플랫폼의 고객, 앱개발자'님'들을 모셔오기 위해서는,

앱개발자가 손쉽게 접근할 수 있는 환경을 갖춰두는 것이 필요합니다.


이런 맥락에서,

우리는 Smart object를 갖다버리기로 하였습니다.

앱개발자의 '자유'는 다음 정권때 갖는 걸로.


하지만,

그럼에도 불구하고 Smart object로 프로토타이핑하며 얻은 몇 가지 정보를 공유하고자 합니다.


EFL은 Evas에서 기본적으로 제공하는 오브젝트인 rectangle, image, text, textblock, line, ... 외에,

새로운 형태의 object를 생성하도록 지원합니다.


개발자는 Smart object로 작성된 오브젝트를-

evas_object_del, evas_object_move, evas_object_resize, evas_object_show, evas_object_hide와 같은-

기본 Evas 함수들과 함께 사용할 수 있습니다.


이 말을 다시 풀면,

Smart object를 만드려면 기본 Evas 함수들에도 동작하게 준비를 해야 한다는 것입니다.


static struct {
Evas_Smart_Class smart_class;
Evas_Smart *smart;
} story_compiler_class_info = {
.smart_class = EVAS_SMART_CLASS_INIT_NAME_VERSION(STORY_COMPILER_CLASS_NAME),
.smart = NULL,
};

Evas_Object *story_compiler_add(Evas_Object *parent)
{
Evas_Object *story_compiler = NULL;
Evas *e = NULL;

retv_if(!parent, NULL);

e = evas_object_evas_get(parent);
retv_if(!e, NULL);

/* We need only one story compiler class even if there are many story compilers */
if (!story_compiler_class_info.smart) {
story_compiler_class_info.smart_class.add = __story_compiler_add;
story_compiler_class_info.smart_class.del = __story_compiler_del;
story_compiler_class_info.smart_class.move = __story_compiler_move;
story_compiler_class_info.smart_class.resize = __story_compiler_resize;
story_compiler_class_info.smart_class.show = __story_compiler_show;
story_compiler_class_info.smart_class.hide = __story_compiler_hide;
story_compiler_class_info.smart_class.color_set = __story_compiler_color_set;
story_compiler_class_info.smart_class.clip_set = __story_compiler_clip_set;
story_compiler_class_info.smart_class.clip_unset = __story_compiler_clip_unset;
story_compiler_class_info.smart = evas_smart_class_new(&story_compiler_class_info.smart_class);
retv_if(!story_compiler_class_info.smart, NULL);
}

story_compiler = evas_object_smart_add(e, story_compiler_class_info.smart);
retv_if(!story_compiler, NULL);

return story_compiler;
}


위의 코드를 보면,

우선 Evas_Smart_Class 구조체를 EVAS_SMART_CLASS_INIT_NAME_VERSION를 사용하여 초기화합니다.

그리고 Evas_Smart_Class 구조체에 Evas의 기본 함수 역할을 수행할 내부함수를 아래와 같이 매핑해둡니다.


.del

 evas_object_del

 .move

 evas_object_move

 .resize

 evas_object_resize

 .show

 evas_object_show

 .hide

 evas_object_hide

 .color_set

 evas_object_color_set

 .clip_set

 evas_object_clip_set

 .clip_unset

 evas_object_clip_unset


그리고 위처럼 만들어놓은 class 구조체는 아래 함수를 사용하여 EFL에서 '보증하는' class가 됩니다.

EAPI Evas_Smart                       *evas_smart_class_new(const Evas_Smart_Class *sc)

위의 함수는 유효한 Smart Class인지 체크하기 위해 매직넘버를 설정하고,

callback 함수를 효율적으로 관리하기 위해 내부정리를 합니다.

이 과정을 거쳐서, Smart Object를 생성할 때 사용할 수 있는-

진정한 의미의 클래스 Evas_Smart가 비로소 생성됩니다.


위에서 생성한 Evas_Smart를 인자로 아래 함수를 이용하여 Evas_Object 객체를 만듭니다.

EAPI Evas_Object *evas_object_smart_add(Evas *e, Evas_Smart *s)

위의 함수에서 리턴되어 나온 Evas_Object 객체는,

Evas 기본 함수들과 함께 사용할 수 있습니다.

Evas 기본 함수들과 함께 사용하기 위해 만든 Smart Object이므로,

위의 함수가 이번 장의 핵심이라 볼 수 있습니다.


각각의 매핑함수들은 아래와 같이 정의할 수 있습니다.

매핑함수의 내용을 보면 알겠지만,

내부적으로 관리하는 object들에 직접 Evas 기본함수들을 사용하여 동작하게 합니다.


static void __story_compiler_add(Evas_Object *story_compiler)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = calloc(1, sizeof(story_compiler_s));
ret_if(!story_compiler_info);
evas_object_smart_data_set(story_compiler, story_compiler_info);

story_compiler_info->story_compiler = story_compiler;
story_compiler_info->layout = evas_object_rectangle_add(evas_object_evas_get(story_compiler));
if (story_compiler_info->layout) {
evas_object_smart_member_add(story_compiler_info->layout, story_compiler);
} else {
free(story_compiler_info);
}
}



static void __story_compiler_del(Evas_Object *story_compiler)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);

evas_object_smart_member_del(story_compiler_info->layout);
evas_object_del(story_compiler_info->layout);
free(story_compiler_info);
}



static void __story_compiler_move(Evas_Object *story_compiler, Evas_Coord x, Evas_Coord y)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_move(story_compiler_info->layout, x, y);
}



static void __story_compiler_resize(Evas_Object *story_compiler, Evas_Coord w, Evas_Coord h)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_resize(story_compiler_info->layout, w, h);
}



static void __story_compiler_show(Evas_Object *story_compiler)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_show(story_compiler_info->layout);
}



static void __story_compiler_hide(Evas_Object *story_compiler)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_hide(story_compiler_info->layout);
}



static void __story_compiler_color_set(Evas_Object *story_compiler, int r, int g, int b, int a)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_color_set(story_compiler_info->layout, r, g, b, a);
}



static void __story_compiler_clip_set(Evas_Object *story_compiler, Evas_Object *clip)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_clip_set(story_compiler_info->layout, clip);
}



static void __story_compiler_clip_unset(Evas_Object *story_compiler)
{
story_compiler_s *story_compiler_info = NULL;

ret_if(!story_compiler);

story_compiler_info = evas_object_smart_data_get(story_compiler);
ret_if(!story_compiler_info);
ret_if(!story_compiler_info->layout);

evas_object_clip_unset(story_compiler_info->layout);
}

위의 함수군에서 일괄적으로 사용하는 함수가 있습니다.

위의 함수는 smart object 내부에서 관리할 data를 설정(set)하고, 가져올 수 있게(get) 해줍니다.

evas_object_smart_data_del과 같은 함수는 없기 때문에,

외부에서 smart object에서 관리하는 데이터를 삭제할 수 없습니다.

smart object 자체가 사라지지 않는한 data도 생명력을 유지합니다.


위의 예제에서는 내부적으로 관리할 story_compiler_s 구조체를,

evas_object_smart_data_set으로 선언해두고,

다른 매핑함수에서 evas_object_smart_data_get으로 꺼내서 재사용하고 있습니다.



위의 함수를 사용하여 smart object에 내에서 함께 layering이 될 수 있도록 다수의 오브젝트를 하나도 묶을 수 있습니다.

_story_compiler_add() 내에서 evas_object_smart_member_add()를 이용하여,

새롭게 생성한 layout 객체를 smart object와 함께 layering 혹은 stacking될 수 있도록 처리하였습니다.

예제에서는 하나의 Evas_Object 객체만 있지만,

다수의 Evas_Object 객체를 함께 관리하는 경우에도,

모두 evas_object_smart_member_add()를 이용하여 함께 layering 되도록 처리할 필요가 있습니다.


이렇게 하여 생성한 story_compiler_add()로 얻는 Evas_Object 객체는,

과연 다른 part에 elm_object_part_content_set()이 될까요?

결론부터 말하자면,

content_set은 안됩니다.


EFL에서 elm_object_part_content_set() 함수의 소스를 쭈욱 따라 내려가다 보면,

아래 함수를 마주치게 됩니다.


EAPI void 
elm_widget_content_part_set(Evas_Object *obj,
                            const char *part,
                            Evas_Object *content)
{
   ELM_WIDGET_CHECK(obj);
   eo_do(obj, elm_obj_container_content_set(part, content));
}


함수 정의 첫번째 줄에서,

content_set 하려는 오브젝트가 ELM_WIDGET인지 여부를 체크합니다.

하지만 안타깝게도,

smart object는 widget이 아니기 때문에 content_set을 수행하기 전에 return 됩니다.


smart object는 evas_object_move로 위치를 잡아주는 수밖에 없습니다.


별로...

Smart 해보이지 않네요.


_끝