지난 주 모처에서 타이젠 앱개발을 주제로 발표를 하였습니다.

발표에 참여하는 분들이 여러가지 면에서 흥미로운 요소를 지니고 있어서,

그 동안 한 번도 보여준 적이 없는 형태로 발표를 준비하였습니다.

발표물은 한 번 쓰고 폐기처분해야하는 상황이라 아쉽긴 하지만,

 발표문에서 타이젠 네이티브앱 초보 개발자에게 흥미로운 내용만을 추려서 포스팅 합니다.



1. 앱라이프사이클


타이젠 앱라이프사이클은 앱의 동작방식을 확정지을 수 있다는 것만으로도 의미가 있지만,

라이프사이클을 활용하여 런칭속도를 최적화할 수 있는 포인트가 있기에 매우 중요합니다.


지난 데브랩때도 이 부분을 강조하여 설명을 드렸었죠.

(관련 포스팅 : http://storycompiler.tistory.com/138)


우선 최적화 포인트는 잠시 접어두고 앱사이클을 따져봐야겠지요.

타이젠의 앱 라이프사이클은 아래 5가지 콜백으로 관리됩니다.


- app_create

- app_terminate

위의 두가지는 앱이 각각 생성될때와 종료될때 불립니다.

가장 기본적인 라이프사이클을 관리하는 함수입니다.


- app_resume

- app_pause

위의 두가지는 앱윈도우의 속성과 관련이 있습니다.

윈도우가 1px이라도 화면에 보여지는 순간 resume이 되고,

1px 조차도 보이지 않게 되면 pause가 됩니다.


네이티브앱에서는 ui가 없는 service 앱도 개발할 수 있는데요,

service 앱의 경우는 resume / pause 콜백이 불리지 않습니다.

왜냐하면 위에서 언급했듯,

resume / pause는 윈도우와 함께 동작하는 피쳐이기 때문이지요.


- app_control

control은 앱을 런칭할 때 부가적인 정보를 전달하는 수단으로 사용됩니다.

caller 앱에서 callee 앱에게 정보를 전달할 수도 있고,

service 앱이 ui 앱에게 정보를 전달할 수도 있겠지요.


타이젠 네이티브앱은 최초 런칭시,

앱이 사전에 콜백으로 등록한,

app_create_cb(), app_control_cb(), app_resume_cb()이 순서대로 불립니다.

최초로 불리는 app_create_cb() 함수는,

앱이 mainloop에 진입하기 직전에 수행하는 함수입니다.

앱이 app_create_cb()에서 리턴되면,

바로 mainloop에 진입하여 app_create_cb()에서 요청한 대상에 대해 렌더링을 실시합니다.

이 때 벌어지는 렌더링이 앱 라이프사이클 중에 최초의 렌더링입니다.


그리고 두번째 렌더링은 통상 app_control_cb()을 실행하고 이뤄집니다.

그렇기 때문에 첫번째 렌더링이 빠르게 이뤄질 수 있도록,

app_create_cb()을 간소하게 짤 필요가 있습니다.

바로 이 부분이 최적화 포인트인 셈입니다.


간단하게 라이프사이클을 등록하는 코드를 보면 아래와 같습니다.


int main(int argc, char *argv[])
{
    struct appdata ad;
    ui_app_lifecycle_callback_s event_callback;

    event_callback.create = app_create;
    event_callback.terminate = app_terminate;
    event_callback.pause = app_pause;
    event_callback.resume = app_resume;
    event_callback.app_control = app_control;
    return ui_app_main(argc, argv, &event_callback, &ad);
}



2. 레이아웃

UI를 가진 앱이라면 앱과 사용자와 교감하는 인터페이스를 우선적으로 고려해야합니다.

네이티브 앱의 경우 레이아웃은 edc를 빼놓고 이야기할 수 없겠지요.


edc로 화면의 레이아웃을 구성하고 C로 edc로 짠 레이아웃에 위젯들을 탑재합니다.

C코드에서 edc를 로딩할 때 사용하는 함수가,

elm_layout_add()입니다.

그리고 로딩된 edc 레이아웃에 C에서 작성한 오브젝트를 탑재할 수 있습니다.

elm_object_part_content_set()

위의 함수를 사용하면 간단하게 탑재가능하지요.


탑재를 했으면 반대로 해제를 하는 함수도 있겠지요.

elm_object_part_content_unset()


위의 함수와 함께 메모리 관리에 대해 고민하는 것도 흥미로울 것 같네요.

이 부분은 SOSCON에서 진행할 Devlab이나 EFL 트랙 발표에서 좀 더 다룰 예정입니다.


edc에서 이뤄지는 레이아웃은 아래와 같은 형태가 되겠네요.

edc에서는 rel1 / rel2를 사용하여 각각의 파트마다 위치와 크기를 지정할 수 있습니다.

이미 사용하고 계시다면 굉장히 간단하게 코딩 가능한 스크립트라는 것을 아시겠죠?



3. 뷰플로우

앱은 다수의 뷰로 구성이 됩니다.

하나의 뷰로만 구성된 간단한 앱도 있습니다만,

보통은 몇 단계의 depth로 앱의 세부항목에 다다르는 구조로 되어 있지요.


각각의 뷰는 기본적으로 stack에 넣어 관리를 하는데요,

그 stacking을 도와주는 객체가 elm_naviframe_xxxx() 함수입니다.


naviframe의 stack에 뷰를 push / pop하며 화면 전환을 할 수 있습니다.

화면 전환간에 default로 동작하는 이펙트는 오른쪽에서 왼쪽으로 기존 뷰를 덮으며 나타나는 이펙트입니다.

이펙트는 customize를 통해 신규로 적용이 가능합니다.



naviframe에서 default로 보여지는 타이틀 영역은,

elm_naviframe_item_title_enabled_set() 함수를 사용하여 없앨 수 있습니다.


그리고 elm_naviframe_items_get() 함수로,

naviframe에 들어간 아이템의 리스트를 얻어올 수 있습니다.


stack은 top에만 넣고 뺄 수 있는 구조기에,

중간에도 넣을 수 있도록 insert 관련 함수도 사용할 수 있습니다.

elm_naviframe_item_insert_before()

elm_naviframe_item_insert_after()



4.  비주얼 인터액션

화면을 구성하고 있는 개별 컴포넌트에 각종 효과를 적용할 수도 있습니다.

C에서는 elm_transit 계열의 함수를 사용하여 효과를 줄 수 있고,

edc에서는 program을 활용하여 효과를 줄 수 있습니다.


여기서는 비교적 직관적으로 사용 가능한 edc를 살펴 보겠습니다.

edc는 화면의 구성요소를 의미하는 part와 part 간의 동작을 정의하는 program으로 나뉩니다.


			part {
				name: "popup";
				type: RECT;
				description {
					state: "default" 0.0;
					rel1 { relative: 1.0 0.0; }
					rel2 { relative: 2.0 1.0; }
					color: 154 187 211 255;
					visible: 1;
				}
				description {
					state: "show" 0.0;
					inherit: "default" 0.0;
					rel1 { relative: 0.0 0.0; }
					rel2 { relative: 1.0 1.0; }
				}


위의 파트는 2가지 description을 가지고 있습니다.

각각의 description은 컴포넌트의 위치를 확정하는 rel1 / rel2 값을 달리 가져가고 있습니다.

위처럼 앱의 각각의 상태에 대한 확정은 part에서 진행합니다.


			program {
				name: "popup_show";
				signal: "popup_show";
				action: STATE_SET "show" 0.0;
				target: "popup";
				transition: DECELERATE 0.3;
			}
			program {
				name: "popup_hide";
				signal: "popup_hide";
				action: STATE_SET "default" 0.0;
				target: "popup";
				transition: DECELERATE 0.3;
			}


그리고 part에 위처럼 program이 붙어서 동작제어를 가능하게 합니다.

위의 program은 popup_show라는 시그널이 C파일로부터 날라오면,

popup 파트의 description을 "show"로 변경해줍니다.

만약 transition이 설정이 되어 있다면,

transition의 값을 참고하여 timer를 돌려 부드러운 이펙트로 보여줍니다.



5. 최적화

앱을 위한 화면구성을 완료했다면, 이제는 최적화에 손을 쓸 차례입니다.

Native 앱에는 최적화를 다양한 방법이 있습니다.


앱단에서 가장 손쉽게 관리할 수 잇는 방법은,

elm_gengrid와 elm_genlist 윈셋을 적극적으로 사용하는 것이겠지요.


elm_gen 시리즈는 화면에 보여지고 있는 영역 위주로 메모리에 로딩하고,

보이지 않는 영역은 메모리에서 언로딩하는 방법을 사용하여 메모리를 절약합니다.



genlist에 아이템을 삽입할때는,

아이템의 레이아웃을 edc로 정의해줄 수 있습니다.

위의 Class의 .item_style 필드가 커스터마이즈한 edc입니다.



위의 edc에서는 레이아웃에 필요한,

icon과 name part를 정의하고 있습니다.



그리고 레이아웃에 채워넣을 icon과 텍스트는,

content_get 필드에 대입되어 있는 함수와 text_get 필드에 정의한 _text_cb의 리턴값으로 결정됩니다.



6. 언어변경

언어는 오픈소스인 gettext를 사용하고 있습니다.

gettext 함수를 사용하려면 국가마다 po 파일이 있어야 하지요.

앱에서 지원하고자 하는 나라수만큼 po 파일을 준비해두어야 합니다.


po 파일을 구성하는 msgid에 모든 언어에서 공통적으로 지칭할 고유아이디를 적어둡니다.

C파일에서 텍스트를 노출해야하면,

msgid에 있는 값을 노출해야하는 곳에 적어두어야 합니다.

msgstr에는 고유아이디와 설정언어의 조합을 통해 gettext()로 번역되어 나옵니다.


시스템 상에서 언어가 바뀌면,

앱라이프사이클과 함께 등록한 language_changed_cb()이 불립니다.

그 함수 안에서 변경된 언어를 설정해두고,

다시 gettext()가 불리도록 update만 시켜주면 됩니다.



이상이 초보개발자들이 궁금해할만한 내용이었습니다.

각각의 내용은 하나의 포스팅으로 완결될 수 있는바,

시간을 마련하여 언젠가 포스팅을 하도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_

  1. 전광하 2015.11.26 20:53

    " naviframe의 stack에 뷰를 push / pop하며 화면 전환을 할 수 있습니다.
    화면 전환간에 default로 동작하는 이펙트는 오른쪽에서 왼쪽으로 기존 뷰를 덮으며 나타나는 이펙트입니다.
    이펙트는 customize를 통해 신규로 적용이 가능합니다 " 라고 하셨는데.. 어떻게 customize를 하는지 알 수 있을까요? 아무리 검색해도 안나오네요.

    • 안녕하세요, 전광하님.
      커스터마이징을 하기 위해서는 edc 파일을 수정해야하는데요,
      답글로는 그 내용을 담을 수 없고(너무 이야기할게 많습니다),
      빠른 시일내로 전체적으로 정리하는 시간을 마련해보도록 하겠습니다.
      언젠가 문의가 들어올 것으로 예상은 했었는데요,
      문의가 들어오기 전에 준비를 했었어야 했는데...
      아무튼 11월은 벌써 끝나가니 12월 중엔... 기필코 정리하도록 하겠습니다.
      그 전까지는 현재 naviframe을 사용하시고,
      아니면 edc에서 naviframe 역할을 하는 그룹을 만드시면 됩니다(edc를 어느정도 아시는지요?).

      곧, 커밍순 하겠습니다. :)

      감사합니다!

  2. 전광하 2015.12.01 00:48

    답변감사합니다. 열정적으로 글들을 읽고있고, 모든 edc 글들을 읽고 연습중입니다. 네비프레임은 포스팅되면 해볼게요 감사합니다~

    • 안녕하세요, 전광하님. edc가 마의 산맥으로 여겨질 수도 있을텐데요. 어려움이 생기면 언제든지 주저말고 문의주세요. :) 그럼 즐거운 코딩하시길~!

part의 영역 중 일부만 노출해야하는 경우가 있습니다.

포토샵에 마스킹을 생각하시면 됩니다.

자신이 지정한 영역만 보이고 그 외의 영역은 아예 렌더링도 하지 않습니다.

바로 그러한 기능이 필요할 때 clip_to를 사용합니다.


clip이 자르다 혹은 깎다는 의미가 있는데요,

clip to로 지정한 영역 외에는 잘라버려서 보이지 않는다고 보면 됩니다.



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


clip_to: [another part's name];

clip_to는 파트블록 내에서 사용합니다.

필드값으로 다른 파트의 이름을 적어주어야 합니다.

현 파트의 영역 중 필드값으로 지정한 파트의 영역와 겹치는 부분만 렌더링하게 됩니다.


우선, 간단하게 이미지를 화면에 그려보도록 하겠습니다.

images {
  image: "bg.jpg" COMP;
}
collections {
  group {
    name: "storycompiler";
    parts {
      part {
        name: "bg";
        type: RECT;
        description {
          state: "default" 0.0;
          rel1.relative: 0.0 0.0;
          rel2.relative: 1.0 1.0;
          color: 0 0 0 255;
          visible: 1;
        }
      }
      part {
        name: "img";
        type: IMAGE;
        scale: 0;
        description {
          state: "default" 0.0;
          image.normal: "bg.jpg";
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          min: 1145 425;
          fixed: 1 1;
          align: 0.5 0.5;
          visible: 1;
        }
      }
    }
  }
}


위의 코드에 있는 collections, group, parts, part 블록은 이미 지난 포스팅에서 설명을 드렸습니다.

image 타입에 대해서 아직 다루진 않았지만,

최소한의 기능만을 사용하였기에 코드만 봐도 이해에 어려움이 없으리라 생각합니다.


IMAGE 타입을 가진 img 파트는 bg 파트의 정중앙(0.5 0.5)에 위치하는 '점'입니다.

하지만, min값을 가지고 있기 때문에 min값만큼 영역이 늘어나게 됩니다.

그래서 나온 결과는 위의 캡쳐이미지와 같습니다.


코드에는 bg와 img 파트만 있는데요,

clip_to를 테스트하기 위하여 가운데 점으로부터 100 x 100짜리 사각형을 하나 만들어보겠습니다.


      part {
        name: "clip";
        type: RECT;
        description {
          state: "default" 0.0;
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          color: 255 0 0 255;
          min: 100 100;
          align: 0.5 0.5;
          visible: 1;
        }
      }


위의 파트까지 포함하여 출력하면 아래 이미지를 볼 수 있습니다.

clip 사격형의 color는 RGBA 기준으로 255 0 0 0으로 붉게 두었습니다.


          color: 255 0 0 255;

자, 이제 clip_to를 위한 준비를 마쳤습니다.

img 파트에 clip_to 필드를 추가합니다.


      part {
        name: "img";
        type: IMAGE;
        scale: 0;
        clip_to: "clip";
        description {
          state: "default" 0.0;
          image.normal: "bg.jpg";
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          min: 1145 425;
          fixed: 1 1;
          align: 0.5 0.5;
          visible: 1;
        }
      }


위처럼 한 줄 추가하면 굉장히 극적인 변화가 일어납니다.

clip 파트는 clip_to의 대상이 되기 때문에 img파트를 위한 '체'만 제공할 뿐입니다.

clip 파트가 '붉은색' 사각형이었기 때문에 '붉은 체'가 되겠지요.


img파트는 clip파트 영역만큼만 화면에 보이게 됩니다.

clip 파트가 '붉은 체'이기 때문에 붉게 물든 이미지가 보이겠네요.

그 외의 영역은 렌더링을 하지 않겠지요.

img보다 뒷편에 있는 bg의 검은 영역만 보이겠죠.



이전 이미지에서는 붉은 색 사각형에 가려져 보이지 않던 영역이 보이는 것을 확인할 수 있습니다.

붉은 색 사각형 영역만큼만 보이고 있습니다.


clip 영역에 애니메이션을 주면,

여러가지 재미난 효과를 추가할 수도 있습니다.


오늘은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_


edc의 part에 들어가는 필드 중에 ignore_flags가 있습니다.

무언가를 무시하기 위한 목적으로 만들어진 필드일텐데요,

이름만 봐서는 역할이 분명하게 다가오지 않네요.


이럴 때는 구글링으로 궁금증을 해소하거나,

EFL 소스를 뒤져보거나,

시간과 노력을 들여 순수한 삽질로 기능을 유추해야 합니다.

아니면, EFL 커미터에게 헬프를 외치면 됩니다.



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


우선, EFL 공식 사이트에서 설명을 찾아보겠습니다.

"Specifies whether events with the given flags should be ignored,

i.e., will not have the signals emitted to the parts."


플래그에 대한 이야기가 나옵니다.

플래그를 설정하면 이벤트가 무시될 수 있다고 하네요.

하지만, 단순히 ignore_flags에 넣는 값에 따라 이벤트를 무시한다면,

이전 포스팅에서 다룬 mouse_events와 다를 바가 없겠지요.


ignore_flags는 mouse_events처럼 무조건적으로 이벤트를 통제하는 것이 아닙니다.

경우에 따라 이벤트를 막거나 혹은 받을 수 있습니다.

그렇다면 그 '경우에 따라'는 어디에서 기인하는 것일까요?


파트 자신이 스스로 각각의 경우를 만들어낼 수 있을까요?

아닙니다. ignore_flags를 적어놓고 있는 파트 자신은 스스로 각각의 경우를 발생하지 못합니다.

대신 위의 파트와 관련이 있는 다른 파트들이 각각의 경우를 만들어냅니다.


어째서 이러한 구조가 필요한 것일까요?

스크롤이 가능한 영역을 생각해보지요.

스크롤이 가능한 영역에 다수의 객체가 삽입되었습니다.

스크롤을 하기 위해 press를 한 후 스크롤을 좌우로 움직입니다.

그리고 release를 하면 스크롤 내에 있던 객체는 이벤트를 받아야할까요?


분명 사용자는 스크롤을 목적으로 press - move - release를 했는데,

사용자의 의도와는 다르게 스크롤 내에 삽입된 객체가 release에 대한 이벤트를 처리할 수 있습니다.

사실 삽입객체는 이벤트를 ignore해야하는데 말이지요.


바로 여기에 ignore_flags 필드가 필요한 겁니다.

삽입객체에 "ignore_flags: ON_HOLD;"를 지정하면,

누군가가 ON_HOLD라고 외치는 순간 자신에게 오는 모든 이벤트는 무시하게 됩니다.

스크롤 와중에 삽입객체들은 이벤트를 받으면 안되지요.


이 사실을 파악하기 이해 먼저 evas 소스를 뒤져보았습니다.

Repository : git://review.tizen.org/framework/uifw/evas

Branch : tizen_2.4


/**
 * @brief Enumeration for events.
 */
typedef enum _Evas_Event_Flags
{
   EVAS_EVENT_FLAG_NONE = 0, /**< No fancy flags set */
   EVAS_EVENT_FLAG_ON_HOLD = (1 << 0), /**< The event is being delivered but should be put "on hold" until the on hold flag is unset \n
   The event should be used for informational purposes and maybe for some indications visually, but should not actually perform anything. */
   EVAS_EVENT_FLAG_ON_SCROLL = (1 << 1) /**< The event occurs while scrolling \n
   For example, DOWN event occurs during scrolling; the event should be used for informational purposes and maybe for some indications visually, but should not actually perform anything. */
} Evas_Event_Flags; /**< Flags for Events */


Evas 단에서 이미 ON_HOLD에 대한 enum을 정의하고 있습니다.

EVAS_EVENT_FLAG_ON_HOLD가 설정된 객체(Evas_Object 혹은 group 혹은 part)는,

타 객체에서 설정한 EVAS_EVENT_FLAG_ON_HOLD에 의해 이벤트를 받지 못합니다.


elementary 소스를 뒤져보면,

대부분의 위젯들이 event를 처리하는 콜백에서 해당 객체가 ON_HOLD 처리가 되어 있는지 확인하고,

ON_HOLD가 set되어 있으면 event 콜백 진입 직후에 return 해버립니다.


Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.4

./elm_video.c:31:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_flip.c:1541:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_flip.c:1572:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_flip.c:1634:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_scroller.c:127:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_popup.c:2148:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_flipselector.c:440:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slideshow.c:34:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_multibuttonentry.c:80:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_gengrid.c:852:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_gengrid.c:2312:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_diskselector.c:913:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_web.c:180:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_entry.c:3208:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_button.c:297:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_interface_scrollable.c:2143:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_map.c:4378:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_panel.c:818:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_ctxpopup.c:1198:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_genlist.c:2682:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_genlist.c:3547:   //if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_radio.c:164:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./els_scroller.c:1863:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:1931:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:2193:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:2512:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./elm_list.c:426:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_colorselector.c:1760:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slider.c:381:   if (mev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slider.c:391:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_naviframe.c:1332:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elc_naviframe.c:1773:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_check.c:155:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_player.c:51:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_toolbar.c:756:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_photocam.c:944:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;


스크롤러 등에 삽입되어 이벤트를 유연하게 처리하고자 한다면,

위의 위젯들처럼 ON_HOLD가 설정된 event에는 return 처리를 해야겠지요.


그렇다면, 최초 발생한 이벤트는 어떻게 되는 것일까요?

ON_HOLD된 파트들은 건너뛰고,

계속 부모 객체를 찾아갑니다.

ON_HOLD가 설정되지 않은 부모 객체까지 타고 올라가서 이벤트를 처리하게 합니다.

그와 관련된 부분은 아래 propagate 함수에서 엿보실 수 있습니다.


Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.4

src/lib/elm_widget.c

EAPI Eina_Bool
elm_widget_event_propagate(Evas_Object *obj,
                           Evas_Callback_Type type,
                           void *event_info,
                           Evas_Event_Flags *event_flags)
{
   API_ENTRY return EINA_FALSE; //TODO reduce.

   if (!_elm_widget_is(obj)) return EINA_FALSE;
   Evas_Object *parent = obj;
   Elm_Event_Cb_Data *ecd;
   Eina_List *l, *l_prev;

   while (parent &&
          (!(event_flags && ((*event_flags) & EVAS_EVENT_FLAG_ON_HOLD))))
     {   
        sd = evas_object_smart_data_get(parent);
        if ((!sd) || (!_elm_widget_is(obj)))
          return EINA_FALSE;  //Not Elm Widget
        if (!sd->api) return EINA_FALSE;

        if (sd->api->event(parent, obj, type, event_info))
          return EINA_TRUE;

        EINA_LIST_FOREACH_SAFE(sd->event_cb, l, l_prev, ecd)
          {https://docs.enlightenment.org/auto/edje/edcref.html
             if (ecd->func((void *)ecd->data, parent, obj, type, event_info) ||
                 (event_flags && ((*event_flags) & EVAS_EVENT_FLAG_ON_HOLD)))
               return EINA_TRUE;
          }
        parent = sd->parent_obj;
     }   

   return EINA_FALSE;
}


이상입니다.

다음 포스팅에서도 part의 기본필드를 알아보도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_


* References

https://docs.enlightenment.org/auto/edje/edcref.html


part 블록에서는 자칫 정신을 잃으면, 급류에 휩쓸려 내려가 죽습니다;

워낙에 많은 복병과 암초가 기다리고 있기 때문에,

가급적이면 많은 예외사항을 다뤄보도록 하겠습니다.

만약 누락된 것이 있다면 댓글로 남겨주세요.

힘 닿는 만큼 추가해보도록 하겠습니다.



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


part는 parts 블록 안에 위치합니다.

parts 블록 안에는 1개 이상의 part를 배치할 수 있습니다.

parts 안에는 part 말고 다른 블록은 없지요.


parts 안에 쌓이는 part의 순서에도 의미가 있습니다.

위에 있는 part 일수록 레이어는 가장 아래에 위치합니다.

하나씩 part를 만들때 가장 상단에 있는 part부터 차곡차곡 쌓는다고 생각하면 됩니다.



개별적인 part는 완결된 화면 구성 단위인 group 내에 위치한 구성 '요소'입니다.

뒤집에 말하면, 각각의 구성요소가 모여서 하나의 의미있는 group이 되지요.


group {
   parts {
      part{} /* 최하단 레이어 */
      part{}
      part{}
      part{}
      part{}
      part{} /* 최상단 레이어 */
   }
}


part는 선이나 사각형이 될 수 있고,

이미지나 텍스트가 될 수도 있습니다.

버튼과 같은 다른 group을 통째로 넣을 수도 있습니다.

각각의 쓰임에 따라 syntax가 조금씩 달라집니다.


group {
   parts {
      ...
      part {
         name: "part_name";
         type: IMAGE;
         mouse_events:  1;
         repeat_events: 0;
         ignore_flags: NONE;
         clip_to: "another_part";
         source:  "group_name";
         pointer_mode: AUTOGRAB;
         description { }
         dragable { }
         items { }
      }
      ...
   }
}


part 내에서 사용하는 기본적인 필드를 우선 살펴보겠습니다.

여기서 언급하는 필드로 파트의 속성을 엿볼 수 있습니다.

필드의 세부적인 속성값이나 여기에 언급하지 않은 필드도 다른 포스팅에서 다룰 예정입니다.

한 번에 모두를 다루기에는 그 양이 너무 많네요.


- name: [part_name];

   파트의 이름을 지정할 수 있습니다.

   파트 이름은 program에서 애니메이션 효과를 줄 때 사용할 수 있습니다.

   c코드에서 파트이름으로 파트에 직접 접근할 수도 있습니다만 일반적인 용례는 아닙니다.


   어쨌든 외부에서 접근할 수 있으니,

   다른 파트 이름과 구별되는 고유의 이름을 지어주어야 합니다.

   파트이름은 큰 따옴표로 감싸주세요.



- type: [TYPE];

   타입은 아래처럼 확정된 값을 따옴표 없이 대문자로 넣을 수 있습니다.

   만약 아무런 값도 지정하지 않았다면 default로 IMAGE가 설정됩니다.

•    TEXT : 싱글라인 텍스트
•    TEXTBLOCK : 멀티라인/스타일이 가능한 텍스트블록
•    RECT : 사각형
•    IMAGE : 이미지
•    SWALLOW : group을 통째로 탑재할 수 있는 SWALLOW 타입(c에서 확정)
•    GROUP : group을 통째로 탑재할 수 있는 GROUP 타입(edc에서 확정)

•    BOX : 박스에 다른 그룹을 차곡차곡 삽입
•    TABLE : 테이블

•    EXTERNAL : 익스터널 오브젝트
•    PROXY : 파트 컨텐트 재사용(속도 최적화)
•    SPACER : 파트 위치+크기 지정(메모리 최적화)



- mouse_events:  [1 or 0];

   이름은 마우스 이벤트이지만 파트영역에서 발생하는 모든 이벤트를 통제할 수 있습니다.

   설정할 수 있는 값은 True를 뜻하는 1과 False를 뜻하는 0입니다.

   True를 설정하면 해당 파트가 '이벤트를 받을 수 있는 상황에서' 이벤트를 받게 됩니다.


   이벤트를 받을 수 있는 상황이란,

   - 파트영역이 화면에 보여야하고,

   - 파트영역을 덮는 다른 파트가 있으면 안됩니다.


   흔히 저지르는 실수 중에 하나가 투명한 파트를 상위 레이어에 만들어 두고,

   그 아래에 있는 파트에서 이벤트를 기다리는 것이지요.

   아무리 기다려도 위에 있는 레이어가 낚아채버리면 이벤트는 오지 않습니다.

   가장 기본적인 원칙이지요.


   만약 0으로 설정하면 어떤 이벤트도 받지 않습니다.

   이벤트를 받을 수 있는 상황인데 이벤트를 받지 않으면,

   그 하위에 있는 레이어에게 기회가 돌아가게 됩니다.

   역으로 자기보다 상단에 레이어가 있지만,

   상단 레이어가 mouse_events를 0으로 설정하면 자기가 이벤트를 받을 수 있습니다.

   default 값은 이벤트를 처리한다는 뜻으로 1로 설정하지요.



- repeat_events: [1 or 0];

   위에서도 언급하였지만 이벤트는 레이어의 높낮이에 영향을 받습니다.

   최상위 레이어부터 최하위 레이어까지 이벤트가 순서대로 전달될 수 있습니다.

   만약 상위 레이어가 이벤트를 처리하였지만, 하위레이어도 이벤트를 받아야하는 경우도 있을 수 있습니다.

   그럴 때 repeat_events를 사용하여 이벤트를 하위 레이어에 전달할 수도 있습니다.

   default는 0이기 때문에 이벤트를 받은 레이어 아래에 이벤트를 전달하지 않습니다.



- scale: [1 or 0];

   scale은 매우 중요한 개념이지만 그 중요도만큼이나 까다로운 필드입니다.

   scale 값을 True인 1로 설정하면, scale factor의 영향을 받는다는 의미이고,

   False인 0으로 설정하면, scale factor의 영향을 받지 않는다는 의미입니다.


   scale factor는 무엇일까요?

   scale factor는 double 형의 상수입니다.

   해상도와 화면사이즈가 제각각인 디스플레이에서,

   육안으로 봤을때 유사한 크기로 교정하기 위해 플랫폼에서 제공하는 계산된 인자입니다.


   예를 들어 동일한 해상도의 1인치/3인치 디스플레이가 있다고 합시다.

   1인치 화면에서 1cm 너비로 보이는 파트영역을,

   3인치 화면에서 3cm가 아닌 여전히 1cm로 너비를 지정하고 싶을 경우,

   1인치에서 1로 scale factor를 지정하였다면,

   3인치에서는 1/3로 scale factor를 지정합니다.

   그러면 1인치에서는 1cm x 1scale factor = 1cm로 설정이 되고,

   3인치에서는 3cm x 1/3scale factor = 1cm로 설정이 되겠지요.


   굉장히 간단하게 설명을 드렸지만, 대강의 의미는 전달되었을거라 생각합니다.

   차후에 scale factor에 대한 이야기를 제대로 할 기회가 있겠지요.


   이러한 scale factor에 영향을 받는 값은,

   min, max 그리고 font size입니다.

   위의 세값을 설정하지 않은 파트는 scale을 1로 지정하여도 scale의 영향을 받지 않습니다.

   반대로 min, max, font size를 지정한 파트에 scale 값을 0으로 줘버리면,

   scale factor의 영향을 받지 않고 지정한 사이즈 그대로 화면에 보여집니다.

  

   default값은 1.0입니다.

   스케일 값을 사용하는 것이 default이지요.



- pointer_mode: [MODE];

   파트영역 내에서 시작하였지만, 파트영역 밖에서 끝난 이벤트를 처리하는 방식을 지정할 수 있습니다.

   mouse down과 mouse up 이벤트로 설명하는 것이 빠르겠네요.

   파트영역 내에서 mouse down을 하였지만,

   mouse를 move하여 파트영역 밖으로 빠져나간 후 mouse up을 할 수 있습니다.

   그렇게 되면 mouse up이벤트를 파트영역이 받아야 할까요?

   그에 대한 선택을 사용자가 pointer_mode로 할 수 있습니다.


   AUTOGRAB 값을 주면,

   mouse up 이벤트가 파트영역 외부에서 이뤄지더라도 mouse down이 발생한 파트가 이벤트를 받습니다.


   NOGRAB 값을 주면,

   mouse up 이벤트가 파트영역 외부에서 이뤄지면 mouse down이 발생한 파트로는 이벤트가 오지 않습니다.


   default는 AUTOGRAB이기 때문에 NOGRAB을 사용하지 않는 이상,

   pointer_mode를 굳이 사용할 필요는 없습니다.



- precise_is_inside: [1 or 0];

   투명한 영역을 가지고 있는 이미지에서 빛을 발하는 '훌륭한' 기능입니다.

   이미지가 이벤트를 받는 경우,

   이미지의 투명한 영역은 이벤트를 받지 않고,

   불투명한 영역에서만 이벤트를 받고 싶을 수도 있습니다.

   바로 그럴 경우, precise_is_inside를 1로 지정하면 됩니다.

   그러면 투명한 영역은 파트영역 내부(inside)로 포함되지 않습니다.


   default 값은 0입니다.

   투명한 영역도 이미지의 영역이므로 이벤트를 모두 처리합니다.


오늘은 여기까지 하겠습니다.

part가 가지는 다른 무수한 기능은 다음 포스팅에서 하나씩 살펴보겠습니다.


그럼 좋은 하루 보내세요~

끝_


+ Recent posts