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.