# Fotostěna (C)

Prohlížeč obrázků používající Clutter.
Chris Kühl
chrisk@openismus.com
Johannes Schmid
jhs@gnome.org
Marta Maria Casetti
mmcasetti@gmail.com 2013

# Fotostěna

      Pro tento příklad vytvoříme jednoduchý prohlížeč obrázků používající Clutter. Naučíte se:

        jak nastavit velikost a pozici objektů ClutterActor
        jak umístit obrázek do objektu ClutterActor
        jak udělat jednoduché přechody pomocí základní animační konstrukce Clutter
        jak přimět objekty ClutterActor reagovat na události myši
        jak získat názvy souborů ze složky

# Úvod

Clutter je knihovna sloužící k vytváření dynamického uživatelského rozhraní pomocí OpenGL s podporou hardwarové akcelerace. Tento příklad předvádí malou, ale podstatnou část knihovny Clutter na vytvoření jednoduchého, ale atraktivního programu k prohlížení obrázků.

Abychom dosáhli našeho cíle, využijeme také pár dalších částí GLib. Nejpodstatnější je použití `GPtrArray`, což je dynamické pole ukazatelů, pro uchování názvů cest. Dále použijeme `GDir`, což je pomůcka pro práci se složkami, k přístupu k našim složkám s obrázky a ke shromáždění cest k souborům.

# Vytvoření projektu ve studiu Anjuta

Než začnete s kódováním, musíte ve studiu Anjuta vytvořit nový projekt. Tím se vytvoří všechny soubory, které budete později potřebovat k sestavení a spuštění kódu. Je to také užitečné kvůli udržení všeho pohromadě.

Spusťte IDE Anjuta a klikněte na Soubor Nový Projekt, aby se otevřel průvodce projektem.

Na kartě C zvolte GTK+ (jednoduchý), klikněte na Pokračovat a na několika následujících stránkách vyplňte své údaje. Jako název projektu a složky použijte photo-wall.

Ujistěte se, že Použít GtkBuilder k tvorbě uživatelského rozhraní je vypnuto, protože jej chceme v této lekci vytvořit ručně. Na použití návrháře uživatelského rozhraní se podívejte do lekce Kytarová ladička.

Zapněte Konfigurovat externí balíčky. Na následující stránce vyberte v seznamu clutter-1.0, aby se knihovna Clutter zahrnula do vašeho projektu.

Klikněte na Použít a vytvoří se vám projekt. Otevřete src/main.c na kartě Projekt nebo Soubor. Měli byste vidět kód, který začíná řádky:

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

# Pohled na fotostěnu

Náš prohlížeč obrázků zobrazuje uživateli stěnu plnou obrázků.

Když se na obrázek klikne, je animován s cílem vyplnit prohlížecí oblast. Když se na zaměřený obrázek klikne, vrátí se na své původní místo, opět pomocí animace. Obě animace trvají 500 milisekund.

# Počáteční nastavení

Následující úseku kódu obsahuje řadu definic a proměnných, které budeme používat v následujících částech. Použijte to jako vodítko pro následující části. Zkopírujte tento kód na začátek 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;

# Vzhůru do kódu

Začneme tím, že se podíváme na celou funkci `main()`. Pak rozebereme podrobně další části kódu. Změňte src/main.c, aby obsahoval tuto funkci `main()`. Funkci `create_window()` můžete smazat, protože ji v tomto příkladu nebudeme potřebovat.

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); } }

/* Zobrazí scénu */ clutter_actor_show(stage);

/* Spustí hlavní smyčku clutter */ clutter_main();

g_ptr_array_unref(img_paths);

return 0; }

Řádek 4: Je definován `ClutterColor` nastavením hodnot červené, zelené, modré a průhledné (alfa). Hodnoty jsou z rozsahu 0 až 255. Pro průhlednou složku znamená 255 plné krytí.

Řádek 7: Musíte inicializovat Clutter. Pokud na to zapomenete, obdržíte velmi podivné chyby. Dávejte si na to pozor.

Řádky 10 – 14: Zde vytvoříme nový `ClutterStage`. Pak nastavíme jeho velikost pomocí definicí z předchozí části a adresu objektu `ClutterColor`, který jsme právě nadefinovali.

`ClutterStage` je `ClutterActor` nejvyšší úrovně, do kterého jsou umístěny ostatní objekty `ClutterActor`.

Řádek 16: Zde voláme naši funkci pro získání cest k souborům s obrázky. Pojďme se na to trochu podívat.

Řádky 18 – 49: Zde nastavujeme objekty `ClutterActor`, načítáme obrázky a umisťujeme na správné místo na fotostěně. Podrobněji se na to podíváme v následující části.

Řádek 52: Zobrazí scénu a všechny její potomky, tzn. naše obrázky.

Řádek 55: Spuštění hlavní smyčky Clutter.

# Nastavení našich obrázkových účinkujících

V knihovně Clutter je účinkující (actor) nejzákladnější vizuální prvek. Zjednodušeně, vše co vidíte, jsou účinkující.

V této části se podrobněji podíváme na smyčku použitou k nastavení objektů `ClutterActor`, který budou zobrazovat naše obrázky.

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); } }

Řádek 7: Zde chceme získat cestu na n-tém místě v poli `GPtrArray`, které obsahuje názvy cest k našim obrázkům. n-tá pozice je vypočítána na základě řádku `row` a sloupce `col`.

Řádek 8 – 23: Zde doopravdy vytvoříme `ClutterActor` a umístíme do něj obrázek. První argument je cesta, přes kterou přistupujeme přes náš uzel `GList`. Druhý argument je pro hlášení chyb, ale zde jej budeme ignorovat, abychom udrželi rozumnou délku příkladu.

Řádek 47: Tímto se přidá `ClutterActor` do scény, což je vlastně kontejner. Předpokládá to také vlastnictví objektu `ClutterActor`, což je něco, kvůli čemuž byste se museli podívat hlouběji do vývoje GNOME. Drsné podrobnosti viz dokumentace k `GObject`.

# Načítání obrázků

Nyní se na chvíli ponechme Clutter stranou a podívejme se, jak získat názvy souborů z naší složky s obrázky.

static void load_image_path_names() { /* Ujištění, že máme přístup do složky. */ 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); } }

Řádky 5 a 12: Otevře naši složku nebo, když se objeví chyba, vrátí se po vypsání chybové zprávy.

Řádky 16 – 25: První řádek získá jiný název souboru z objektu `GDir`, který jsem otevřeli již dříve. Pokud se jednalo o soubor s obrázkem (což zkontrolujeme podle jeho přípony „.png“ nebo „.jpg“) ve složce, kterou zpracováváme, přidáme před název souboru cestu a celé to vložíme na začátek seznamu, který jsem dříve vytvořili. Nakonec se pokusíme získat následující název v cestě, a když je nějaký soubor nalezen, vrátíme se do smyčky.

# Nastavení účinkujících

Nyní se pojďme podívat na nastavení velikosti a polohy objektů `ClutterActor` a také na jejich přípravu pro komunikaci s uživatelem.

/* Tato funkce se stará o vytvoření a umístění obdélníků. */ 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); }

Řádek 7: Nastavení účinkujícího jako reagujícího znamená, že reaguje na události, jako je v našem případě `"button-press-event"`. Pro fotostěnu by měli být všechny objekty `ClutterActor` nastaveny jako reagující.

Řádek 9 – 12: Nyní připojíme `button-press-event` na zpětné volání `actor_clicked_cb`, na které se podíváme posléze.

V tomto bodě máme stěnu s obrázky, která je připravena k zobrazení.

# Reakce na kliknutí

static gboolean actor_clicked_cb(ClutterActor *actor, ClutterEvent *event, gpointer user_data) { /* Příznak, který bude uchovávat náš stav */ static gboolean is_focused = FALSE; ClutterActorIter iter; ClutterActor *child;

/* Vynuluje stav zaměření u všech obrázků */ 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) { /* Obnoví staré umístění a velikost */ clutter_actor_set_position(actor, unfocused_pos.x, unfocused_pos.y); clutter_actor_set_size(actor, THUMBNAIL_SIZE, THUMBNAIL_SIZE); } else { /* Uloží aktuální umístění, než se začne animovat */ 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);

/* Vloží zaměřený obrázek nahoru */ 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);

/* Přepne náš příznak */ is_focused = !is_focused;

return TRUE; }

Řádek 1 – 4: Musíme zajistit, že naše funkce zpětného volání bude formálně odpovídat požadavkům signálu `button_clicked_event`. Například musíme použít jen první argument, konkrétně `ClutterActor`, na který bylo kliknuto.

Pár slov k argumentům, které v tomto příkladu nepoužíváme. `ClutterEvent` se liší podle toho, která událost je obsluhována. Například událost klávesy zapříčiní `ClutterKeyEvent`, ze které můžeme mimo jiné údaje zjistit, která klávesa byla zmáčknuta. Pro událost kliknutí myší získáte `ClutterButtonEvent`, ze kterého můžete zjistit souřadnice `x` a `y`. Na další typy objektu `ClutterEvent` se podívejte do dokumentace knihovny Clutter.

Argment `user_data` jsou data předávaná funkci. Může být předán například ukazatel na libovolná data, takže když potřebujete zpětnému volání předat více údajů, umístěte data do struktury a předejte adresu na ni.

Ředek: Vytvoříme statický příznak pro sledování stavu, ve kterém se nacházíme: režim stěny nebo režim zaměření. Začínáme v režimu stěny, kdy žádný obrázek nemá zaměření. Proto nastavíme počáteční hodnotu příznaku na `FALSE`.

Řádek 12 – 14: Toto nastaví obrázkové účinkující, aby přijímali události, když jsou zaměření.

Řádek 16 – 17: Zde nastavíme délku trvání animace a uložíme aktuální stav.

Řádky 21 – 3: Dosažení tohoto místa znamená, že obrázek je právě zaměřen a my se chceme vrátit do režimu stěny. Nastavením pozice objektu `ClutterActor` začne animace v délce trvání nastavené na řádku 17.

Řádek 24: Dosažení tohoto místa v kódu znamená, že jsem právě v režimu stěny a chystáme se dát objektu `ClutterActor` zaměření. Uložíme zde počáteční pozici, abychom se na ni později mohli vrátit.

Řádek 25: Nastavení vlastnosti `reactive` objektu `ClutterActor` na `TRUE` zajistí, že bude `ClutterActor` reagovat na události. Ve stavu zaměření bude jediným účinkujícím, u kterého chceme, aby přijímal událost, ten, který je právě zobrazován. Kliknutí na účinkujícího jej vrátí na původní místo.

Řádek 27 – 36: Zde ukládáme aktuální pozici obrázku, nastavujeme jej, aby přijímal události a pak jej necháme objevit na ostatními obrázky a začneme jej animovat, aby zaplnil scénu.

Řádek 39: Zde obnovíme uvolněný stav na hodnotu nastavenou, než jsme ji změnili na řádku 16.

Řádek 42: Zde přepneme příznak `is_focused` (je zaměřeno) na aktuální stav.

Jak už jsem se zmínili dříve, události přijímá `ClutterActor` s vyšší hodnotou `depth`, ale můžete to umožnit i ostatním objektům `ClutterActor` pod ním. Když je vráceno `TRUE`, předávání událostí dolů se zastaví, zatímco `FALSE` bude předávat události dolů.

Pamatujte ale, že aby objekty `ClutterActor` přijímaly události, musí být nastavené jako `reactive` (reagující).

# Sestavení a spuštění aplikace

Veškerý kód by nyní měl být připraven k běhu. Vše, co nyní potřebujete, jsou nějaké obrázky k načtení. Ve výchozím stavu se obrázky načítají ze složky berlin_images. Jestli chcete, můžete změnit řádek `#define IMAGE_DIR_PATH` někde na začátku, který odkazuje na vaší složku s fotografiemi, nebo vytvořit složku berlin_images kliknutím na ProjektNová složka… a vytvořením složky berlin_images jako podsložky ve složce photo-wall. Do složky umístěte aspoň dvanáct obrázků!

Až to budete mít přichystané, klikněte na Sestavit Sestavit projekt, aby se vše znovu sestavilo, a pak na Spustit Spustit, aby se spustila aplikace.

Pokud jste tak ještě neučinili, zvolte aplikaci Debug/src/photo-wall v dialogovém okně, které se objeví. Nakonec klikněte na Spustit a užijte si ji!

# Ukázková implementace

Pokud v této lekci narazíte na nějaké problémy, porovnejte si svůj kód s tímto ukázkovým kódem.