# Mural de fotos (C)

Un visor de imaxes Clutter
Chris Kühl
chrisk@openismus.com
Johannes Schmid
jhs@gnome.org

# Mural de fotos

      Para este exemplo construirase un sinxelo visor de imaxes usando Clutter. Aprenderá:

        Como dimensionar e posicionar varios ClutterActor
        Como posicionar unha imaxe nun ClutterActor
        Como facer transicións sinxelas usando o framework de animacións Clutter.
        Como facer que os ClutterActor respondan a eventos do rato
        Como obter nomes de ficheiros dun cartafol

# Introdución

Clutter é unha biblioteca para crear interfaces de usuario dinámicas usando OpenGL para a aceleración gráfica. Este exemplo demostra unha parte pequena, porén central, da biblioteca Clutter para crear un sinxelo, máis atractivo, programa de visualización de imaxes.

Para axudarlle a alcanzar o seu destino usaremos tamén algunhas pezas comúns de GLib. A máis importante, usaremos `GSList`, unha lista ligada para manter os nosos `ClutterActor`s e os nomes de ruta de ficheiro.

# Cree un proxecto de Anjuta

Antes de comezar a programar, deberá configurar un proxecto novo en Anjuta. Isto creará todos os ficheiros que precise para construír e executar o código máis adiante. Tamén é útil para manter todo ordenado.

Inicie Anjuta e prema Ficheiro Novo Proxecto para abrir o asistente de proxectos.

Choose Gtk+ (simple) from the C tab, click Continue, and fill out your details on the next few pages. Use photo-wall as project name and directory.

Asegúrese que Usar GtkBuilder para a interface de usuario está desactivado xa que crearemos a UI manualmente neste titorial. Comprobe o titorial Guitar-Tuner usando o construtor de interface.

Active Configurar paquetes externos. Na seguinte páxina seleccione clutter-1.0 desde a lista para incluír a biblioteca Clutter no seu proxecto.

Prema Aplicar para crear o proxecto. Abra src/main.c desde as lapelas Proxecto ou Ficheiro. Debería ver algún código que comeza coas liñas:

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

# Unha ollada ao Muro de fotos

O novo visor de imaxes móstralle ao usuario un muro de imaxes.

Cando se fai clic sobre unha imaxe, esta anímase para ocupar todo o área de visualización. Cando se fai clic sobre unha imaxe que ten o foco volverá á súa posición orixinal usando unha animación da mesma duración de 500 milisegundos.

# Configuración inicial

O seguinte fragmento de código contén moitas definicións e variábeis que se usarán nas seguintes seccións. Úseo como referencia para as próximas seccións. Copie este código ao principio de src/main.c:

#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 FOCUS_DEPTH 100.0 #define UNFOCUS_DEPTH 0.0

#define IMAGE_DIR_PATH "./berlin_images/"

static GSList *actor_list = NULL; static GSList *img_path_list = NULL;

typedef struct Position { float x; float y; } Position;

static Position origin = {0, 0};

# Saltando ao código

Comezaremos ollando a función `main()`. Logo discutiremos as outras seccións de código en detalle. Cambie o ficheiro src/main.c para que conte;a a función `main()`. Pode elminar a función `create_window()` xa que non a necesitamos neste exemplo.

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

clutter_init(&argc, &argv);

stage = clutter_stage_get_default(); clutter_actor_set_size(stage, STAGE_WIDTH, STAGE_HEIGHT); clutter_stage_set_color(CLUTTER_STAGE (stage), &stage_color);

load_image_path_names();

guint row = 0; guint col = 0; for(row=0; row < ROW_COUNT; ++row) { for(col=0; col < COL_COUNT; ++col) { GSList *img_path_node = g_slist_nth(img_path_list, (row * COL_COUNT) + col); ClutterActor *actor = clutter_texture_new_from_file((gchar *)(img_path_node->data), NULL); initialize_actor(actor, row, col); clutter_container_add_actor(CLUTTER_CONTAINER(stage), actor); actor_list = g_slist_prepend(actor_list, actor); } }

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

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

return 0; }

Liña 4: `ClutterColor` é definido estabelecendo o valor a vermello, verde, azul ou transparente (alfa). O rango de valores están entre 0-255. Para a transparencia 255 é opaco.

Liña 7: Debe inicializar Clutter. Se esqueceu isto, haberá moitos erros estraños. Está avisado.

Liñas 9-11: aquí é onde se obtén o `ClutterStage` predeterminado que forneceu `clutter_init`. Entón estabelécese o tamaño usando o definido na sección anterior e os enderezos do `ClutterColor` que xa están definidas.

Un `CluterStage` é o nivel superior dun `ClutterActor` no que se localizan outros `ClutterActor`.

Liña 12: aquí chámase á función para obter as rutas das imaxes. Isto verase en breve.

Liña 14-26: aquí é onde se configuran os `ClutterActor`, cárganse as imaxes e colócanse no seu sitio no mural de imaxes. Isto verase con máis detalle na seguinte sección.

Liña 29: mostra o escenario e todos os seus fillos, é dicir, as imaxes.

Liña 32: inicia o bucle principal de Clutter.

# Configurar os nosos actores de imaxe

En Clutter, un actor é o elemento visual máis básico. Basicamente, todo o que ve é un actor.

Nesta sección, imos botar unha ollada máis polo miúdo ao búcle usado para configurar os `ClutterActor`s que mostrarán as nosas imaxes.

for(row=0; row < ROW_COUNT; ++row) { for(col=0; col < COL_COUNT; ++col) { GSList *img_path_node = g_slist_nth(img_path_list, (row * COL_COUNT) + col); ClutterActor *actor = clutter_texture_new_from_file((gchar *)(img_path_node->data), NULL); initialize_actor(actor, row, col); clutter_container_add_actor(CLUTTER_CONTAINER(stage), actor); actor_list = g_slist_prepend(actor_list, actor); } }

Liña 5: aquí quérese obter a ruta á localización n-ésima na `GSList` que contén os nomes das rutas das imaxes. A n-ésima posición calcúlase baseándose en `row` e `col`. O valor de retorno é un punteiro a `GSList` que é simplemente un nodo na lista. Isto usarase para obter a ruta actual na seguinte liña. O primeiro parámetro é un punteiro á cabeceira da lista.

Liña 6: aquí é onde actualmente se crea o `ClutterActor` e localízase a imaxe no actor. O primeiro argumento é a ruta coa que se acceder ao nodo da `GSList`. O seguindo argumento é para informar dun error, pero ignórase para simplificar.

Liña 7: esta función verase nunha sección posterior.

Liña 8: isto engade o `ClutterActor` ao escenario, que é un contedor. Asume o propietario do `ClutterActor`, que é algo que quererá cando profundice no desenvolvemento de GNOME. Para obter máis detalles, consulte a documentación de `GObject`.

Liña 9: isto engade o `ClutterActor` a unha `GSList` que logo se poderá iterar sobre os `ClutterActor`.

É interesante ter en conta que prefírese antepoñer os `ClutterActor` no lugar de anexalos, o que evita atravesar a lista en cada inserción. A miúdo verá `g_slist_prepend` seguido de `g_slist_reverse`, xa que é máis rápido que inserir varios obxectos ao final da lista.

# Cargar as imaxes

Botemos unha pequena ollada a Clutter para ver como obter os nomes dos ficheiros desde o noso cartafol de imaxes.

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

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); img_path_list = g_slist_prepend(img_path_list, path); } filename = g_dir_read_name(dir); } }

Liñas 5 e 12: Isto abre o noso cartafol ou, se se produce un erro, devolve despois de imprimir unha mensaxe de erro.

Liñas 14‒23: la primeira liña obtén outro nome de ficheiro do `GDir` aberto anteriormente. Se hai un ficheiro de imaxe (compróbase mirando se a extensión é «.png» o «.jpg») no cartafol, procédese a antepoñer a ruta do cartafol da imaxe ao nome do ficheiro e anteponse na lista creada anteriormente. Por último, inténtase obter a seguinte ruta e vólvese entrar no bucle se se atopa outro ficheiro.

# Configurar os actores

Vote unha ollada ao tamaño e ao posicionamento dos `ClutterActor` e a como se deixa listo o `ClutterActor` para a interacción do usuario.

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

Liña 7: configurar un actor como «reactivo» significa que reacciona aos eventos tales como `button-press-event` no noso caso. Para o mural de fotos, todos os `ClutterActor` do mural deben ser inicialmente reactivos.

Liñas 9-12: agora conéctase o evento `button-press-evento` á chamada `actor_clicked_cb` que veremos máis adiante.

Neste punto obteremos un muro de imaxes que están listas para ser mostradas.

# Reaccionar aos clics

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

g_slist_foreach(actor_list, foreach_set_focus_state, &is_focused);

if(is_focused) { clutter_actor_animate(actor, CLUTTER_LINEAR, ANIMATION_DURATION_MS, "x", origin.x, "y", origin.y, "depth", UNFOCUS_DEPTH, "width", (float) THUMBNAIL_SIZE, "height", (float) THUMBNAIL_SIZE, NULL); } else { /*Save the current location before animating. */ clutter_actor_get_position(actor, &origin.x, &origin.y); clutter_actor_set_reactive(actor, TRUE); clutter_actor_animate(actor, CLUTTER_LINEAR, ANIMATION_DURATION_MS, "x", (STAGE_WIDTH - STAGE_HEIGHT) / 2.0, "y", 0.0, "depth", FOCUS_DEPTH, "width", (float) STAGE_HEIGHT, "height", (float) STAGE_HEIGHT, NULL); }

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

return TRUE; }

Lines 1‒4: We have to make sure our callback function matches the signature required for the `button_clicked_event` signal. For our example, we will only use the first argument, the `ClutterActor` that is actually clicked.

A few words on the arguments we are not using in this example. The `ClutterEvent` is different depending on what event is being handled. For example, a key event produces a `ClutterKeyEvent` from which you can get the key being pressed among other information. For mouse click events you get a `ClutterButtonEvent` from which you can get the `x` and `y` values. See the Clutter documentation for other `ClutterEvent` types.

The `user_data` is what one uses to pass data into the the function. A pointer to any data type can be passed in. If you need multiple data to be passed into the callback, you can place the data into a struct and pass its address in.

Line 7: We set up a static flag to track which state we are in: wall mode or focus mode. We start out in wall mode so no image has focus. Thus, we set the flag to `FALSE` initially.

Line 9: This line of code runs a custom function, `foreach_set_focus_state`, for each element in our `actor_list`, passing it the address to the `is_focused` flag. We'll see the definition of the `foreach_set_focus_state` function in the next section.

Lines 13‒19: Reaching this code means that one image currently has focus and we want to return to wall mode. The `clutter_actor_animate` function is used to animate a `ClutterActor`'s property or properties from the current state(s) to the specified state(s). The arguments are as follows:

O enderezo do `ClutterActor` a animar

The animation mode to use. Here we use `CLUTTER_LINEAR` so that we have a constant speed for animation.

The duration of the animation in milliseconds. I've chosen 500 ms for this example.

The remaining arguments are property/value pairs. Here we want to set the `x` value to the starting `x` value this `ClutterActor` was at before being brought into focus.

The last argument must always be `NULL` to indicate that there are no more properties to be set.

The `depth` property needs a little more explaining. We need to raise the focused image so that it doesn't slide behind other `ClutterActor`s. In this section we are returning it to the same depth as the others on the wall.

Depth also determines which `ClutterActor`s receive events. A `ClutterActor` with a higher depth value receives the click events and can choose whether the event gets sent to `ClutterActor`s under it. We'll see how that works in a few steps.

Line 24: Reaching this line of code means we are currently in the wall state and are about to give a `ClutterActor` focus. Here we save the starting position so that we can return to it later.

Line 25: Setting the `ClutterActor`'s `reactive` property to `TRUE` makes this `ClutterActor` react to events. In this focused state the only `ClutterActor` that we want to receive events will be the `ClutterActor` being viewed. Clicking on the `ClutterActor` will return it to its starting position.

Lines 27‒33: This is similar to the above block of code. Notice that we are setting the the depth to raise it above the other images.

Line 37: Here we toggle the `is_focused` flag to the current state.

As mentioned previously, the `ClutterActor`s with higher `depth` values receive events but can allow `ClutterActor`s below them to also receive events. Returning `TRUE` will stop events from being passed down, while `FALSE` will pass events down.

Remember, however, that to receive events the `ClutterActor`s must be set `reactive`.

The following is the convenience function passed to `g_slist_foreach`.

static void foreach_set_focus_state(gpointer data, gpointer user_data) { ClutterActor *actor = CLUTTER_ACTOR(data); gboolean is_reactive = *((gboolean*)user_data);

clutter_actor_set_reactive(actor, is_reactive); }

Lines 2‒5: The signature of this function requires two `gpointer`s. The first is a pointer to the `ClutterActor` that our `GSList` holds and the other is the `is_focused` flag that we've passed in the previous section. We want to cast these and store them for easy use.

Line 7: Depending on which boolean value is passed in, the `ClutterActor` will be set to respond to events or not.

# Construír e executar o aplicativo

Todo o código debería estar listo para executarse. Todo o que necesita son algunhas imaxes para cargar. De maneira predeterminada, as imaxes cárganse desde o cartafol berlin_images. Se quere, pode cambiar a liña `#define IMAGE_DIR_PATH` do principio para que faga referencia ao seu cartafol de fotos, ou crear un cartafol berlin_images premendo en Proxecto Cartafol novo… e creando un cartafol berlin_images como subcartafol do cartafol mural-fotos. Asegúrese de poñer cando menos doce imaxes no cartafol.

Cando teña que facer iso, prema Construír Construír proxecto para construír todo de novo, logo Executar Executar para iniciar o aplicativo.

Se non o fixo aínda, seleccione o aplicativo Debug/src/photo-wall no diálogo que aparece. Finalmente, prema Executar e desfrute!

# Implementación de referencia

Se ten problemas ao executar este titorial compare o seu código con este código de referencia.