# Photo wall(C)

클러터 그림 보기
Chris Kühl
chrisk@openismus.com
Johannes Schmid
jhs@gnome.org
Marta Maria Casetti
mmcasetti@gmail.com 2013
조성호
shcho@gnome.org 2017-2019.

# 포토 월

      이 예제에서는 클러터로 간단한 그림 보기 프로그램을 만들겠습니다. 다음을 익히는 과정입니다:

        ClutterActor에서 크기 위치 설정하는 방법
        ClutterActor에서 그림 배치하는 방법
        클러터 애니메이션 프레임워크로 간단하게 트랜지션 처리하는 방법
        마우스 이벤트에 대해 ClutterActor에서 응답하는 방법
        디렉터리에서 파일 이름 가져오기

# 도입부

클러터는 하드웨어 가속 OpenGL을 활용하여 동적 사용자 인터페이스를 만드는 라이브러리입니다. 이 예제는 작은 일부를 보여주지만, 중심적으로는, 간단하지만 매력적인 그림 보기 프로그램을 만드는 클러터 라이브러리의 일부 동작을 시연합니다.

목적을 위해 GLib 같은 일부 일반적인 부분도 마찬가지로 활용합니다. 상당히 중요하게 `GPtrArray` 동적 포인터 배열을 활용하여 파일 경로 이름을 넣어두겠습니다. 또한 디렉터리를 다루는 `GDir`로 그림 디렉터리에 접근하고 경로를 가져올 때 활용하겠습니다.

# 안주타에서 프로젝트 만들기

코딩을 시작하기 전에 안주타에서 새 프로젝트를 설정해야합니다. 이 프로그램은 빌드에 필요한 모든 파일을 만들고 그 다음 코드를 실행합니다. 또한 이 모든 상태를 유지 관리하는데 쓸만합니다.

안주타를 시작하고 파일새로 만들기프로젝트 를 눌러 프로젝트 마법사를 여십시오.

C 탭에서 GTK+ (단순)를 선택하고, 계속을 누른 다음, 나타난 페이지에서 몇가지 자세한 내용을 입력하십시오. 프로젝트 이름과 디렉터리에 photo-wall을 입력하십시오.

앞서 따라하기 지침을 통해 사용자 인터페이스를 직접 만들 예정이므로 사용자 인터페이스에 GtkBuilder 사용 설정을 껐는지 확인하십시오. 인터페이스 빌더 사용법을 알아보려면 기타 조율 프로그램 따라하기 지침서를 확인하십시오.

외부 패키지 설정을 켬으로 설정했는지 확인하십시오. 다음 페이지의 목록에서 gstreamer-0.10를 선택하여 프로젝트에 지스트리머 라이브러리를 넣으십시오.

적용을 누르면 프로젝트를 만들어줍니다. 프로젝트나 파일탭에서 src/main.c 파일을 여십시오. 다음 줄로 시작하는 일부 코드를 볼 수 있어야합니다:

#include <config.h> #include <gtk/gtk.h>

# 포토 월 살펴보기

그립 보기 프로그램에서는 그림 벽으로 사용자를 나타냅니다.

그림을 누르면 보기 영역을 채울 때 움직입니다. 포커스를 획득한 그림이 눌린 상태면, 500 밀리초 동안 움직임을 활용하여 원래 위치로 돌아옵니다.

# 초기 설정

다음 코드 일부에는 다음 섹션에서 활용할 정의와 변수 대부분이 들어갑니다. 이 부분을 다음 섹션 참고 용도로 활용하십시오. 이 코드를 src/main.c 시작 부분에 복사해두십시오:

#include <gdk-pixbuf/gdk-pixbuf.h> #include <clutter/clutter.h>

#define STAGE_WIDTH 800 #define STAGE_HEIGHT 600

#define THUMBNAIL_SIZE 200 #define ROW_COUNT (STAGE_HEIGHT / THUMBNAIL_SIZE) #define COL_COUNT (STAGE_WIDTH / THUMBNAIL_SIZE) #define THUMBNAIL_COUNT (ROW_COUNT * COL_COUNT)

#define ANIMATION_DURATION_MS 500

#define IMAGE_DIR_PATH "./berlin_images/"

static GPtrArray *img_paths;

static ClutterPoint unfocused_pos;

# 코드로 넘어가기

`main()` 함수를 전체로서 먼저 살펴보겠습니다. 그 다음 다른 코드 부분을 자세하게 살펴보면서 이야기하겠습니다. src/main.c 파일을 이 `main()` 함수 코드가 들어가도록 바꾸십시오. 이 예제를 실행할 때 `create_window()` 함수는 필요 없으므로 삭제할 수 있습니다.

int main(int argc, char *argv[]) { ClutterColor stage_color = { 16, 16, 16, 255 }; ClutterActor *stage = NULL;

if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1;

stage = clutter_stage_new(); clutter_actor_set_size(stage, STAGE_WIDTH, STAGE_HEIGHT); clutter_actor_set_background_color(stage, &stage_color); clutter_stage_set_title(CLUTTER_STAGE (stage), "Photo Wall"); g_signal_connect(stage, "destroy", G_CALLBACK(clutter_main_quit), NULL);

load_image_path_names();

guint row = 0; guint col = 0; for(row=0; row < ROW_COUNT; ++row) { for(col=0; col < COL_COUNT; ++col) { const char *img_path = g_ptr_array_index(img_paths, (row * COL_COUNT) + col); GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(img_path, STAGE_HEIGHT, STAGE_HEIGHT, NULL); ClutterContent *image = clutter_image_new (); ClutterActor *actor = clutter_actor_new ();

if (pixbuf != NULL) { clutter_image_set_data(CLUTTER_IMAGE(image), gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_has_alpha(pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_rowstride(pixbuf), NULL); }

clutter_actor_set_content(actor, image); g_object_unref(image); g_object_unref(pixbuf);

initialize_actor(actor, row, col); clutter_actor_add_child(stage, actor); } }

/* Show the stage. */ clutter_actor_show(stage);

/* Start the clutter main loop. */ clutter_main();

g_ptr_array_unref(img_paths);

return 0; }

4번째 줄: `ClutterColor`는 적색, 녹색, 청색, 투명도(alpha) 값을 설정하여 정의합니다. 값 범위는 0부터 255까지입니다. 255는 투명 수치가 가장 낮습니다.

7번째 줄: 클러터를 초기화해야 합니다. 까먹었다면 이상한 오류가 납니다. 유의하세요.

10~14번째 줄: 여기서 새 `ClutterStage`를 만듭니다. 앞 부분에서 정의한 코드로 사이즈를 설정하고 방금 정의한 `ClutterColor` 의 위치를 정의합니다.

`ClutterStage`는 다른 `ClutterActor`가 놓인 곳의 최상위 `ClutterActor` 입니다.

16번째 줄: 여기서 그림 파일 경로를 가져오는 함수를 호출합니다. 이 부분을 조금 살펴보겠습니다.

18~49번째 줄: 이 부분은 그림을 불러오고 그림 벽 부분에 두는 `ClutterActor`을 설정하는 부분입니다. 다음 섹션에서 자세하게 이 부분을 살펴보겠습니다.

52번째 줄: 그림의 스테이지와 하위 요소를 나타냅니다.

55번째 줄: 클러터 메인 루프를 시작합니다.

# 그림 액터 구성

클러터에서 액터는 사장 기본적인 시각 구성 요소입니다. 근본적으로 여러분이 보는 모든 구성 요소는 액터입니다.

이 부분에서는 그림을 나타내는 `ClutterActor`를 설정하는 루프를 더 자세하게 살펴보겠습니다.

guint row = 0; guint col = 0; for(row=0; row < ROW_COUNT; ++row) { for(col=0; col < COL_COUNT; ++col) { const char *img_path = g_ptr_array_index(img_paths, (row * COL_COUNT) + col); GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(img_path, STAGE_HEIGHT, STAGE_HEIGHT, NULL); ClutterContent *image = clutter_image_new (); ClutterActor *actor = clutter_actor_new ();

if (pixbuf != NULL) { clutter_image_set_data(CLUTTER_IMAGE(image), gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_has_alpha(pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_rowstride(pixbuf), NULL); }

clutter_actor_set_content(actor, image); g_object_unref(image); g_object_unref(pixbuf);

initialize_actor(actor, row, col); clutter_actor_add_child(stage, actor); } }

7번째 줄: 여기서는 그림 경로 이름을 넣는 `GPtrArray`의 n번째 위치에서 경로를 가져오려고 합니다. n번째 위치는 `행`과 `열` 기반으로 계산합니다.

8~23번째 줄: 실제로 `ClutterActor`를 만들고 액터에 그림을 넣는 부분입니다. 첫 인자는 `GSList` 노드로 접근할 경로입니다. 두번째 인자는 오류 보고용이지만 단순성을 유지할 목적으로 무시합니다.

47번째 줄: `ClutterActor`를 컨테이너 스테이지에 추가합니다. 또한 여러분이 그놈 개발상 깊숙한 부분을 보려는 `ClutterActor`의 소유자를 간주합니다. 적나라한 세부 내용은 `GObject` 문서를 참고하십시오.

# 그림 불러오기

그림 디렉터리에서 파일 이름을 가져오는 방법을 살펴볼 참에 클러터 내용 다루는 부분은 잠시 접어두겠습니다.

static void load_image_path_names() { /* Ensure we can access the directory. */ GError *error = NULL; GDir *dir = g_dir_open(IMAGE_DIR_PATH, 0, &error); if(error) { g_warning("g_dir_open() failed with error: %s\n", error->message); g_clear_error(&error); return; }

img_paths = g_ptr_array_new_with_free_func (g_free);

const gchar *filename = g_dir_read_name(dir); while(filename) { if(g_str_has_suffix(filename, ".jpg") || g_str_has_suffix(filename, ".png")) { gchar *path = g_build_filename(IMAGE_DIR_PATH, filename, NULL); g_ptr_array_add (img_paths, path); } filename = g_dir_read_name(dir); } }

5~12번째 줄: 디렉터리를 열거나 오류가 발생했을 경우 오류 메시지를 출력한 후 값을 반환합니다.

16~25번째 줄: 첫 줄은 앞서 열어둔 `GDir`에서 다른 파일 이름을 가져옵니다. 디렉터리에 그림 파일(".png" 또는 ".jpg" 확장자를 보고 확인)이 있다면 파일 이름에 그림 디렉터리 경로를 붙이고 앞서 설정한 목록에 추가합니다. 마지막으로, 그 다음 경로 이름을 가져온 다음, 해당 경로에서 다른 파일을 찾으면 반복문에 다시 들어갑니다.

# 액터 구성

이제 `ClutterActor`의 크기 조절 및 위치 설정, 사용자 반응 대기 부분을 살펴보겠습니다.

/* This function handles setting up and placing the rectangles. */ static void initialize_actor(ClutterActor *actor, guint row, guint col) { clutter_actor_set_size(actor, THUMBNAIL_SIZE, THUMBNAIL_SIZE); clutter_actor_set_position(actor, col * THUMBNAIL_SIZE, row * THUMBNAIL_SIZE); clutter_actor_set_reactive(actor, TRUE);

g_signal_connect(actor, "button-press-event", G_CALLBACK(actor_clicked_cb), NULL); }

7번째 줄: 액터 반응 설정은 액터가 `button-press-event` 같은 이벤트에 반응하게 합니다. 포토 월에서는 벽에 있는 모든 `ClutterActor`에 반응 규칙을 초기화해야합니다.

9~12번째 줄: 이제 `button-press-event` 이벤트를 다음에 살펴볼 `actor_clicked_cb` 콜백 함수에 연결합니다.

여기서 볼 준비를 마친 그림 벽을 가져옵니다.

# 누름 동작에 반응

static gboolean actor_clicked_cb(ClutterActor *actor, ClutterEvent *event, gpointer user_data) { /* Flag to keep track of our state. */ static gboolean is_focused = FALSE; ClutterActorIter iter; ClutterActor *child;

/* Reset the focus state on all the images */ clutter_actor_iter_init (&iter, clutter_actor_get_parent(actor)); while (clutter_actor_iter_next(&iter, &child)) clutter_actor_set_reactive(child, is_focused);

clutter_actor_save_easing_state(actor); clutter_actor_set_easing_duration(actor, ANIMATION_DURATION_MS);

if(is_focused) { /* Restore the old location and size. */ clutter_actor_set_position(actor, unfocused_pos.x, unfocused_pos.y); clutter_actor_set_size(actor, THUMBNAIL_SIZE, THUMBNAIL_SIZE); } else { /* Save the current location before animating. */ clutter_actor_get_position(actor, &unfocused_pos.x, &unfocused_pos.y); /* Only the currently focused image should receive events. */ clutter_actor_set_reactive(actor, TRUE);

/* Put the focused image on top. */ clutter_actor_set_child_above_sibling(clutter_actor_get_parent(actor), actor, NULL);

clutter_actor_set_position(actor, (STAGE_WIDTH - STAGE_HEIGHT) / 2.0, 0); clutter_actor_set_size(actor, STAGE_HEIGHT, STAGE_HEIGHT); }

clutter_actor_restore_easing_state(actor);

/* Toggle our flag. */ is_focused = !is_focused;

return TRUE; }

1~4번째 줄: `button_clicked_event` 시그널에 필요한 시그니쳐에 일치하는 콜백 함수를 확인해야합니다. 예를 들면 실제로 누를 `ClutterActor`가 들어가는 첫번째 인자만 씁니다.

이 예제에서 사용하지 않는 인자에 대해 몇가지 알아둘 내용이 있습니다. `ClutterEvent`는 어떤 이벤트를 처리할지에 따라 다릅니다. 예를 들면, 키 이벤트는 여러 이벤트 중에 키누름 이벤트를 받을 수 있는 `ClutterKeyEvent`를 내보냅니다. 마우스 단추 누름 이벤트 상황에선 `x`, `y` 값을 받을 수 있는 `ClutterButtonEvent`를 취할 수 있습니다. 다른 `ClutterEvent` 형식을 알아보려면 클러터 문서를 참고하십시오.

`user_data`는 함수에 데이터를 전달할 때 활용합니다. 어떤 데이터 형식의 포인터든 전달할 수 있습니다. 콜백에 여러 데이터를 넘겨야 한다면, 구조체에 데이터를 넣고 전달할 수 있습니다.

7번째 줄: 우리가 보는 상태를 보여줄 정적 플래그 벽 모드 또는 포커스 모드를 설정합니다. 벽 모드로 시작해서 어떤 이미지에도 포커스를 맞추지 않도록 합니다. 따라서 초기 플래그 값은 `FALSE` 입니다.

12~14번째 줄: 이 부분에서 그림에 포커스를 맞췄을 때 이벤트를 받을 그림 액터를 설정합니다.

16~17번째 줄: 여기서 움직임 시간을 설정하고 현재 상태를 저장합니다.

21~23번째 줄: 이 코드에 도달한다는 건 그림 하나에 포커스를 맞췄으며, 벽 모드로 돌아가려 함을 의미합니다. `ClutterActor`에 위치를 설정하면 17번째 줄에서 설정한 시간만큼 그림 움직임을 시작합니다.

24번째 줄: 이 코드 줄에 도달하면 현재 벽 상태에 있으며 `ClutterActor` 포커스를 주려는 상황입니다. 여기서 시작 위치를 저장하여 나중에 다시 이 위치로 돌아올 수 있습니다.

25번째 줄: `ClutterActor`의 `reactive` 속성을 `TRUE`로 설정하면, `ClutterActor`에서 이벤트에 반응합니다. 이 포커스 상태에서 우리가 이벤트를 받으려는 `ClutterActor`는 우리가 보고 있는 `ClutterActor`입니다. `ClutterActor`를 누르면 액터의 시작 위치로 돌아갑니다.

27~36번째 줄: 이 부분은 그림의 현재 위치를 저장하고, 이벤트를 받도록 설정하며, 다른 그림 위로 나타나 스테이지를 채우는 움직임을 시작하는 부분입니다.

39번째 줄: 이 부분에서는 16번째 줄에서 바꾸기 전의 움직임 처리 상태로 복원합니다.

42번째 줄: 여기서 `is_focused` 값을 현재 상태 값으로 바꿉니다.

앞에서 말씀드린대로, 좀 더 큰 `depth` 값이 들어간 `ClutterActor`에서 이벤트를 받는데 `ClutterActor`의 아래에 있는 `ClutterActor`도 이벤트를 받을 수 있습니다. `TRUE`를 반환하면 이벤트를 전달해 내려가는 과정을 멈추며, `FALSE`를 반환하면 이벤트를 계속 하부로 전달해내려갑니다.

허나, `ClutterActor` 이벤트를 받으려면 `reactive`를 설정해야합니다.

# 프로그램 빌드 및 실행

모든 코드 준비가 끝났습니다. 이제 그림을 불러오는 일이 필요합니다. 기본적으로 그림은 berlin_images 디렉터리에서 불러옵니다. 원한다면 사진 디렉터리 또는 프로젝트새 디렉터리…를 누른 후 photo-wall 디렉터리의 하위 디렉터리로 berlin_images 디렉터리를 만든 후 참조하도록 상단 근처의 `#define IMAGE_DIR_PATH` 줄을 바꿀 수 있습니다. 디렉터리에 그림을 최소한 12개 넣었는지 확인하십시오.

다 끝났으면 빌드프로젝트 빌드를 눌러 모두 다시 빌드하고, 실행실행을 눌러 프로그램을 시작하십시오.

아직 마무리가 안됐다면 나타나는 대화상자에서 Debug/src/photo-wall 프로그램을 선택하십시오. 마지막으로 실행을 치고 즐기시죠!

# 참조 구현체

지침서를 따라하는 실행하는 과정에 문제가 있다면, 참조 코드와 여러분의 코드를 비교해보십시오.