지금까지 예제가 단편적이라면 이번에는 조금 제대로 된 기능하는 코드입니다. 이 예제는 이미지 파일을 읽어들여 타원 주위로 회전시키며 보여줍니다. 사용자가 이미지를 클릭하면 맨 앞으로 오면서 확대되면서 파일 이름도 보여줍니다. 먼저 스크린샷부터.
여러 타임라인과 움직임 객체를 이용해서 조금 복잡해 보이지만, 아마도 실제 어플리케이션은 이보다 훨씬 더 유연하고 기능적으로 동작해야겠지요. 주석을 우리말로 번역하고, 원본보다 조금 더 속도감있게 변경한 소스는 다음과 같습니다. (조금 길지요…)
#include <clutter/clutter.h> #include <stdlib.h> static ClutterActor *stage = NULL; /* 파일 이름을 보여주기 위한 레이블 액터 */ static ClutterActor *label_filename = NULL; /* 모든 이미지를 타원 주위로 회전화기 위한 타임라인 */ static ClutterTimeline *timeline_rotation = NULL; /* 이미지 하나를 위로 올리고 확대하기 위한 타임라인과 움직임 객체 */ static ClutterTimeline *timeline_moveup = NULL; static ClutterBehaviour *behaviour_scale = NULL; static ClutterBehaviour *behaviour_path = NULL; static ClutterBehaviour *behaviour_opacity = NULL; /* 이미지 목록을 보여줄 타원의 좌표와 크기 */ static const gint ELLIPSE_Y = 390; static const gint ELLIPSE_HEIGHT = 450; /* 90도 회전된 상태에서 앞뒤 거리 */ static const gint IMAGE_HEIGHT = 100; static double angle_step = 30; typedef struct _Item { ClutterActor *actor; ClutterBehaviour *ellipse_behaviour; gchar* filepath; } Item; /* 맨 앞으로 오게 할 이미지 항목 */ static Item *item_at_front = NULL; static GSList *list_items = NULL; static void rotate_all_until_item_is_at_front (Item *item); static gdouble angle_in_360 (gdouble angle) { gdouble result = angle; while (result >= 360) result -= 360; return result; } static void on_foreach_clear_list_items (gpointer data, gpointer user_data) { Item* item = (Item*)data; /* 액터는 스테이지가 없어질때 자동으로 정리되므로 해제할 필요가 없습니다. */ g_object_unref (item->ellipse_behaviour); g_free (item->filepath); g_free (item); } static void scale_texture_default (ClutterActor *texture) { int pixbuf_height = 0; /* 이미지의 세로 크기를 얻습니다. */ clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), NULL, &pixbuf_height); const gdouble scale = pixbuf_height ? IMAGE_HEIGHT / (gdouble)pixbuf_height : 0; /* 기준 높이에 맞게 스케일링합니다. */ clutter_actor_set_scale (texture, scale, scale); } static void load_images (const gchar* directory_path) { g_return_if_fail (directory_path); /* 현재 이미지 목록을 비웁니다. */ g_slist_foreach (list_items, on_foreach_clear_list_items, NULL); g_slist_free (list_items); /* 새로운 목록을 초기화합니다. */ list_items = NULL; /* 디렉토리에 있는 이미지 목록을 얻습니다. */ GError *error = NULL; GDir* dir = g_dir_open (directory_path, 0, &error); if (error) { g_warning ("g_dir_open() failed: %s\n", error->message); g_clear_error (&error); return; } const gchar* filename = NULL; while ((filename = g_dir_read_name(dir))) { gchar* path = g_build_filename (directory_path, filename, NULL); /* 이미지 파일로부터 텍스쳐 액터를 만듭니다. */ ClutterActor *actor = clutter_texture_new_from_file (path, NULL); if (actor) { Item* item = g_new0 (Item, 1); item->actor = actor; item->filepath = g_strdup (path); /* 모든 이미지가 같은 높이가 되도록 스케일링합니다. */ scale_texture_default (item->actor); list_items = g_slist_append (list_items, item); } g_free (path); } g_dir_close (dir); } static gboolean on_texture_button_press (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { /* 이미지 회전 타임라인이 실행중이면 이벤트를 무시합니다. * 즉, 이미지가 움직이고 있는 도중에 발생하는 마우스 버튼 클릭을 무시합니다. */ if (timeline_rotation && clutter_timeline_is_playing (timeline_rotation)) { printf ("on_texture_button_press(): ignoring\n"); return FALSE; } Item *item = (Item *) user_data; /* 선택한 아이템이 맨 앞에 올때까지 이미지 목록을 회전시킵니다. */ rotate_all_until_item_is_at_front (item); return TRUE; } static void add_to_ellipse_behaviour (ClutterTimeline *timeline_rotation, gdouble start_angle, Item *item) { g_return_if_fail (timeline_rotation); ClutterAlpha *alpha = clutter_alpha_new_full (timeline_rotation, CLUTTER_ALPHA_SINE_INC, NULL, NULL); /* 타원을 따라 동작할 움직임 객체를 만듭니다. */ item->ellipse_behaviour = clutter_behaviour_ellipse_new (alpha, 320, ELLIPSE_Y, ELLIPSE_HEIGHT, ELLIPSE_HEIGHT, CLUTTER_ROTATE_CW, angle_in_360 (start_angle), angle_in_360 (start_angle + 360)); /* X축 기준으로 타원의 축 기울입니다. */ clutter_behaviour_ellipse_set_angle_tilt ( CLUTTER_BEHAVIOUR_ELLIPSE (item->ellipse_behaviour), CLUTTER_X_AXIS, -90); /* ClutterAlpha 객체는 따로 해제할 필요가 없습니다. */ /* 액터에 움직임을 적용합니다. */ clutter_behaviour_apply (item->ellipse_behaviour, item->actor); } static void add_image_actors (void) { int x = 20; int y = 0; gdouble angle = 0; GSList *list = list_items; /* 이미지 갯수로 회전시 이미지 간격을 계산합니다. */ if (list) angle_step = 360 / g_slist_length (list); while (list) { /* 이미지 액터를 스테이지에 넣습니다. */ Item *item = (Item *) list->data; ClutterActor *actor = item->actor; clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor); /* 초기 좌표를 지정합니다. */ clutter_actor_set_position (actor, x, y); y += 100; /* 기본적으로 스테이지만 이벤트를 발생할 수 있으므로, * 액터도 이벤트를 발생할 수 있게 합니다. */ clutter_actor_set_reactive (actor, TRUE); /* 버튼 클릭 시그널에 핸들러 함수를 연결합니다. */ g_signal_connect (actor, "button-press-event", G_CALLBACK (on_texture_button_press), item); /* 타원 액터에 움직임을 추가합니다. */ add_to_ellipse_behaviour (timeline_rotation, angle, item); angle += angle_step; clutter_actor_show (actor); list = g_slist_next (list); } } /* 이 기그널 핸들러는 선택한 이미지가 확대되어 위로 이동하는 타임라인이 * 완료되었을때 호출됩니다. */ static void on_timeline_moveup_completed (ClutterTimeline* timeline, gpointer user_data) { /* 타임라인 객체를 해제합니다. */ g_object_unref (timeline_moveup); timeline_moveup = NULL; g_object_unref (behaviour_scale); behaviour_scale = NULL; g_object_unref (behaviour_path); behaviour_path = NULL; g_object_unref (behaviour_opacity); behaviour_opacity = NULL; } /* 이 시그널 핸들러는 이미지가 타원을 따라 회전하는 타임라인이 완료되었을때 * 호출됩니다. */ static void on_timeline_rotation_completed (ClutterTimeline* timeline, gpointer user_data) { /* 모든 이미지가 회전하다가 클릭한 이미지가 맨 앞에 온 상태입니다. * 이제 맨 앞의 이미지를 크게 보여주고 파일 이름도 표시합니다. */ /* 이미지를 변형합니다. */ ClutterActor *actor = item_at_front->actor; timeline_moveup = clutter_timeline_new(15 /* frames */, 30 /* frames per second */); ClutterAlpha *alpha = clutter_alpha_new_full (timeline_moveup, CLUTTER_ALPHA_SINE_INC, NULL, NULL); /* 현재 크기에서 약 2배 크기로 확대합니다. */ gdouble scale_start = 0; clutter_actor_get_scale (actor, &scale_start, NULL); const gdouble scale_end = scale_start * 1.8; behaviour_scale = clutter_behaviour_scale_new (alpha, scale_start, scale_start, scale_end, scale_end); clutter_behaviour_apply (behaviour_scale, actor); /* 그림을 위 방향을 이동합니다. */ ClutterKnot knots[2]; knots[0].x = clutter_actor_get_x (actor); knots[0].y = clutter_actor_get_y (actor); knots[1].x = knots[0].x; knots[1].y = knots[0].y - 250; behaviour_path = clutter_behaviour_path_new (alpha, knots, G_N_ELEMENTS(knots)); clutter_behaviour_apply (behaviour_path, actor); /* 파일 이름을 조금씩 보여줍니다. */ clutter_label_set_text (CLUTTER_LABEL (label_filename), item_at_front->filepath); behaviour_opacity = clutter_behaviour_opacity_new (alpha, 0, 255); clutter_behaviour_apply (behaviour_opacity, label_filename); /* 모든 움직임(behaviours)을 시작합니다. * 또한 완료되었을때 핸들러를 연결합니다. */ g_signal_connect (timeline_moveup, "completed", G_CALLBACK (on_timeline_moveup_completed), NULL); clutter_timeline_start (timeline_moveup); } static void rotate_all_until_item_is_at_front (Item *item) { g_return_if_fail (item); clutter_timeline_stop(timeline_rotation); /* 선택한 이미지를 위로 올려 보여주는 타임라인이 동작중이라면 * 당장 멈추게 합니다. */ if (timeline_moveup) clutter_timeline_stop (timeline_moveup); clutter_actor_set_opacity (label_filename, 0); /* 선택한 이미지 항목의 번호를 얻습니다. */ const gint pos = g_slist_index (list_items, item); g_assert (pos != -1); if (!item_at_front && list_items) item_at_front = (Item *) list_items->data; /* 현재 맨 앞에 있는 항목의 번호를 얻습니다. */ gint pos_front = 0; if (item_at_front) pos_front = g_slist_index (list_items, item_at_front); g_assert (pos_front != -1); /* 첫번째 항목의 시작 / 끝 각도를 계산합니다. */ const gdouble angle_front = 180; gdouble angle_start = angle_front - (angle_step * pos_front); angle_start = angle_in_360 (angle_start); gdouble angle_end = angle_front - (angle_step * pos); gdouble angle_diff = 0; /* 모든 이미지 항목의 끝 각도를 설정합니다. */ GSList *list = list_items; while (list) { Item *this_item = (Item*)list->data; /* 이미지 크기를 원래대로 되돌립니다. */ scale_texture_default (this_item->actor); angle_start = angle_in_360 (angle_start); angle_end = angle_in_360 (angle_end); /* 현재 맨 앞 있는 항목일 경우 360도 더 회전하게 함니다. */ if (item_at_front == item) angle_end += 360; angle_end = angle_in_360 (angle_end); clutter_behaviour_ellipse_set_angle_start ( CLUTTER_BEHAVIOUR_ELLIPSE (this_item->ellipse_behaviour), angle_start); clutter_behaviour_ellipse_set_angle_end ( CLUTTER_BEHAVIOUR_ELLIPSE (this_item->ellipse_behaviour), angle_end); /* 선택한 항목일 경우 */ if (this_item == item) { if (angle_start < angle_end) angle_diff = angle_end - angle_start; else angle_diff = 360 - (angle_start - angle_end); } /* TODO: Set the number of frames, depending on the angle. * otherwise the actor will take the same amount of time to reach * the end angle regardless of how far it must move, causing it to * move very slowly if it does not have far to move. */ angle_end += angle_step; angle_start += angle_step; list = g_slist_next (list); } /* 속도는 같지만 이동해야 할 거리만큼 프레임 수를 조절합니다. */ gint pos_to_move = 0; if (pos_front < pos) { const gint count = g_slist_length (list_items); pos_to_move = count + (pos - pos_front); } else { pos_to_move = pos_front - pos; } clutter_timeline_set_n_frames (timeline_rotation, angle_diff / 5); /* 타임라인이 끝난뒤 맨 앞에 위치해야할 항목을 기억합니다. */ item_at_front = item; clutter_timeline_start (timeline_rotation); } int main (int argc, char *argv[]) { ClutterColor stage_color = { 0xB0, 0xB0, 0xB0, 0xff }; /* light gray */ clutter_init (&argc, &argv); /* 스테이지를 얻어 크기와 색상을 정합니다. */ stage = clutter_stage_get_default (); clutter_actor_set_size (stage, 800, 600); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); /* 파일 이름을 보여주기 위한 레이블 액터를 만들고, 일단 안보이게 합니다. */ label_filename = clutter_label_new (); ClutterColor label_color = { 0x60, 0x60, 0x90, 0xff }; /* blueish */ clutter_label_set_color (CLUTTER_LABEL (label_filename), &label_color); clutter_label_set_font_name (CLUTTER_LABEL (label_filename), "Sans 24"); clutter_actor_set_position (label_filename, 10, 10); clutter_actor_set_opacity (label_filename, 0); /* hidden */ clutter_container_add_actor (CLUTTER_CONTAINER (stage), label_filename); clutter_actor_show (label_filename); /* 이미지 목록 밑에 보여줄 사각형을 만듭니다. */ ClutterColor rect_color = { 0xff, 0xff, 0xff, 0xff }; /* white */ ClutterActor *rect = clutter_rectangle_new_with_color (&rect_color); clutter_actor_set_height (rect, ELLIPSE_HEIGHT + 20); clutter_actor_set_width (rect, clutter_actor_get_width (stage) + 100); /* 사각형을 이미지 목록 밑에 위치하도록 합니다. */ clutter_actor_set_position (rect, -(clutter_actor_get_width (rect) - clutter_actor_get_width (stage)) / 2, ELLIPSE_Y + IMAGE_HEIGHT - (clutter_actor_get_height (rect) / 2)); /* X축을 기준으로 사각형을 눕힙니다. */ clutter_actor_set_rotation (rect, CLUTTER_X_AXIS, -90, 0, (clutter_actor_get_height (rect) / 2), 0); clutter_container_add_actor (CLUTTER_CONTAINER (stage), rect); clutter_actor_show (rect); /* 스테이지를 보이게 합니다. */ clutter_actor_show (stage); /** 이미지를 회전시킬 타임라인을 만들고, * 회전이 끝났을때 실행할 핸들러를 연결합니다. */ timeline_rotation = clutter_timeline_new(60 /* frames */, 30 /* frames per second */); g_signal_connect (timeline_rotation, "completed", G_CALLBACK (on_timeline_rotation_completed), NULL); /* 이미지를 로드하고 각각에 대한 액터를 만듭니다. */ load_images ("./images/"); add_image_actors (); /* clutter_timeline_set_loop(timeline_rotation, TRUE); */ /* 첫번째 이미지가 선택된 것처럼 회전을 시작합니다. */ if (list_items) rotate_all_until_item_is_at_front ((Item*)list_items->data); /* 메인 이벤트 루프를 시작합니다. */ clutter_main (); /* 모든 이미지 목록을 해제합니다. */ g_slist_foreach(list_items, on_foreach_clear_list_items, NULL); g_slist_free (list_items); g_object_unref (timeline_rotation); return EXIT_SUCCESS; }
이미지 파일을 포함한 소스 파일은 여기를 클릭하면 다운로드할 수 있습니다. 한 번 테스트해 보시길…

This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 2.0 Korea License.
