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
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.
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
Na kartě
Ujistěte se, že
Zapněte
Klikněte na
#include <config.h>
#include <gtk/gtk.h>
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.
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
#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;
Začneme tím, že se podíváme na celou funkci main()
. Pak rozebereme podrobně další části kódu. Změňte 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.
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
.
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.
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í.
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í).
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 #define IMAGE_DIR_PATH
někde na začátku, který odkazuje na vaší složku s fotografiemi, nebo vytvořit složku
Až to budete mít přichystané, klikněte na
Pokud jste tak ještě neučinili, zvolte aplikaci
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.