lunedì 10 dicembre 2012

Programmare con Clutter (versione 1.12.2): Timeline e animazioni. Tutorial parte 2


Proseguiamo con la seconda parte del tutorial sulla programmazione con Clutter. Nella prima parte avevamo creato un programma che si limitava a disegnare un quadrato verde su sfondo nero, questa volta aggiungeremo un po di movimento!


Creiamo un'istanze della classe ClutterTimeline

Faremo in modo che il nostro “attore” ruoti sull'asse z (l'asse della profondità, quella retta passante per il vostro occhio che va dritta dentro lo schermo).

Per ottenere questa animazione utilizzeremo una nuova classe: ClutterTimeline.
Un timeline non è altro che un cronometro che chiama una funzione (generalmente chiamata call-back function) ogni n millisecondi.

Per prima cosa creiamo un timeline ed avviamo il cronometro, aggiungiamo queste righe prima di clutter_actor_show(stage);


ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), rect);
clutter_timeline_set_repeat_count (timeline_rotation, -1);
clutter_timeline_start(timeline_rotation);

Prima di proseguire guardiamo insieme il codice che abbiamo aggiunto:


  • clutter_timeline_new() crea l'istanza della classe  ClutterTimeline, timeline_rotation è il suo puntatore. 500 è un valore in millisecondi, il nostro cronometro chiamerà la funzione di ritorno (call-back function) dopo mezzo secondo.
  • g_signal_connect() collega la funzione di ritorno al nostro cronometro, vediamo i parametri passati: timeline_rotation è il puntatore al nostro timeline, “new-frame” è una stringa che definisce il tipo di funzione di ritorno, in questo caso è una funzione che disegna il nuovo frame dell'animazione. G_CALLBACK(on_timeline_rotation_new_frame) è la nostra funzione di ritorno, in realtà non l'abbiamo ancora definita, ma lo faremo tra poco. L'ultimo parametro è un puntatore a qualsiasi tipo di dato che vorremmo passare alla nostra funzione di ritorno, in questo caso passiamo il puntatore al nostro attore, rect.
  • clutter_timeline_set_repeat_count () imposta le volte che il cronometro deve ripetersi, passando -1 si ripeterà all'infinito;
  • clutter_timeline_start() avvia il cronometro e quindi la nostra animazione.

All'uscita del programma ci dobbiamo ricordare di liberare le risorse allocate dalla nostra timeline, aggiungiamo questa riga prima di return error;


g_object_unref (timeline_rotation);


Definiamo la funzione di ritorno del nostro timeline

Se provassimo a compilare adesso riceveremmo un errore che ci segnala che la nostra funzione di ritorno  on_timeline_rotation_new_frame non è stata definita, quindi mettiamoci al lavoro.


L'idea è quella di incrementare di n gradi la rotazione del nostro attore ad ogni chiamata di ritorno del cronometro, per farlo avremo bisogno di una variabile globale che contenga il valore di rotazione applicata al nostro attore.

Aggiungiamo quindi subito dopo #include <clutter/clutter.h>


gdouble rotation = 0;

Partiamo quindi da una rotazione di 0 gradi. Aggiungiamo di seguito la definizione della funzione di ritorno:


void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, ClutterActor* actor)
{
    rotation += 0.5;
    clutter_actor_set_rotation_angle(actor, CLUTTER_Z_AXIS, rotation);
}

Vediamo subito i parametri ricevuti dalla funzione:


  • timeline è il puntatore al cronometro che ha chiamato la funzione;
  • frame_num è il numero del frame. Noi non utilizzeremo questo valore, ma c'è perché è previsto nelle funzioni di ritorno di tipo “new-frame”;
  • actor è il nostro attore, questo parametro lo abbiamo aggiunto noi per ottenere il nostro scopo, cioè quello di applicare la rotazione.

Al suo interno la nostra funzione è piuttosto semplice, dapprima incrementa di 0.5 il valore di rotazione, quindi applica la rotazione con la funzione clutter_actor_set_rotation_angle(), alla quale passiamo il puntatore all'attore, l'asse sul quale viene applicata la rotazione e la variabile con il valore della rotazione.

Adesso possiamo compilare ed avviare il nostro programma per vedere quel bel quadrato verde ruotare sull'asse Z.


Avrete sicuramente notato che la rotazione avviene sul vertice in alto a sinistra del quadrato e non al centro. Questo avviene perché il vertice in alto a sinistra è il punto di riferimento di default per tutte le trasformazioni applicate all'attore.

Aggiungiamo un'istruzione che modifica il punto di riferimento del nostro attore subito prima di aggiungerlo allo stage:


clutter_actor_set_pivot_point (rect, 0.5, 0.5);

I due valori 0.5 sono riferiti rispettivamente all'asse X e a quello Y. Per quanto riguarda l'asse X, 0 corrisponde al vertice sinistro (default) e 1 è quello destro, 0.5 indica quindi al centro. Sull'asse Y, 0 è il vertice in alto (default), 1 è quello in basso, 0.5 è il centro.


Compiliamo ed avviamo il programma, il nostro quadrato verde adesso ruota correttamente.


Il programma completo fin qui dovrebbe avere questo aspetto:


#include <stdio.h>
#include <stdlib.h>
#include <clutter/clutter.h>
gdouble rotation = 0;
void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, ClutterActor* actor)
{
    rotation += 0.5;
    clutter_actor_set_rotation_angle(actor, CLUTTER_Z_AXIS, rotation);
}
int main(int argc, char *argv[])
{
    ClutterInitError error = clutter_init(&argc, &argv);
    ClutterColor stage_color = { 0, 0, 0, 255 };
    ClutterActor *stage = clutter_stage_new(); clutter_actor_set_size(stage, 512, 512);
    clutter_actor_set_background_color(stage, &stage_color);
    ClutterColor actor_color = { 0, 255, 0, 128 };
    ClutterActor *rect = clutter_actor_new();
    clutter_actor_set_background_color(rect, &actor_color);
    clutter_actor_set_size(rect, 100, 100);
    clutter_actor_set_position(rect, 100, 100);
    clutter_actor_set_pivot_point (rect, 0.5, 0.5);
    clutter_actor_add_child(stage, rect);
    ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
    g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), rect);
    clutter_timeline_set_repeat_count (timeline_rotation, -1);
    clutter_timeline_start(timeline_rotation);
    clutter_actor_show(stage);
    g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
    clutter_main();
    g_object_unref(timeline_rotation);
    return error;
}

Per poter introdurre l'utilizzo della classe ClutterTimeline abbiamo utilizzato una funzione di ritorno per creare l'animazione del nostro attore, ma in realtà, per ottenere questo tipo di animazioni, possiamo utilizzare le proprietà animabili della classe ClutterActor o la classe ClutterTransition. Non allarmatevi, non stiamo complicando le cose ma vedremo come può essere facile creare delle animazioni anche complesse con poche righe di codice.

Ci sono sostanzialmente due modi per definire l'animazione di un attore: utilizzare l'animazione implicita della classe ClutterActor o definire un'animazione in modo esplicito tramite la classe ClutterTransition. Iniziamo con il primo modo.



Animazione implicita della classe ClutterActor

Per sperimentare le funzioni di animazione integrate nella classe ClutterActor dobbiamo smantellare tutto quello che abbiamo fatto finora, cioè possiamo eliminare il timeline e la funzione di ritorno. Eliminiamo anche la variabile globale rotation e aggiungiamo queste righe di codice subito dopo clutter_actor_add_child(stage, rect);


clutter_actor_save_easing_state (rect);
clutter_actor_set_easing_delay (rect, 500);
clutter_actor_set_easing_duration (rect, 500);
clutter_actor_set_rotation_angle (rect, CLUTTER_Z_AXIS, 360.0);
clutter_actor_restore_easing_state (rect);

Provate a compilare ed eseguire il programma, vedrete il quadrato ruotare e quindi fermarsi definitivamente. Adesso guardiamo il codice che abbiamo aggiunto:


  • clutter_actor_save_easing_state() è la prima funzione che va chiamata per definire l'animazione implicita dell'attore, salva il così detto easing state;
  • clutter_actor_set_easing_delay() imposta un ritardo espresso in millisecondi prima di iniziare l'animazione, nel nostro caso è di mezzo secondo;
  • clutter_actor_set_easing_duration() imposta la durata dell'animazione, cioè quanti millisecondi impiegherà il nostro attore per portare a termine l'animazione che gli abbiamo assegnato. Ancora una volta abbiamo immesso un valore corrispondente a mezzo secondo;
  • clutter_actor_set_rotation_angle(), questa funzione ruota l'attore rect sull'asse Z di 360 gradi, cioè un giro completo;
  • clutter_actor_restore_easing_state() è la funzione che chiude la definizione dell'animazione implicita e l'avvia.

Ricapitolando abbiamo detto al nostro attore di aspettare mezzo secondo prima di iniziare a muoversi, quindi di ruotare di 360 gradi sull'asse Z e di farlo con la velocità necessaria per impiegare mezzo secondo per concludere la rotazione.

I parametri animabili di un attore sono molteplici, ad esempio le sue coordinate, la rotazione sugli assi, la trasparenza, ecc... L'animazione implicita è molto semplice da utilizzare e conveniente per animazioni semplici, ma non ci permette di definire animazioni che si ripetono all'infinito.

Per ottenere animazioni più complesse o infinite come quella che avevamo ottenuto inizialmente con l'utilizzo di ClutterTimeline è necessario utilizzare l'animazione esplicita.


Animazione esplicita con la classe ClutterTransition

Ancora una volta dobbiamo fare un passo indietro, rimuovete il codice che definiva l'animazione implicita che avevamo aggiunto precedentemente e sostituitelo con questo:



ClutterTransition *transition = clutter_property_transition_new ("rotation-angle-z");
clutter_transition_set_from (transition, G_TYPE_DOUBLE, 0.0);
clutter_transition_set_to (transition, G_TYPE_DOUBLE, 360.0);
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 500);
clutter_timeline_set_repeat_count (CLUTTER_TIMELINE (transition), -1);
clutter_actor_add_transition (rect, "centrifuga", transition);

Compilate ed eseguite il programma, il nostro quadrato ruota velocemente senza fermarsi! Vediamo che cosa abbiamo fatto nel dettaglio:

  • clutter_property_transition_new() crea un'istanza della classe ClutterTransition, il parametro “rotation_angle_z” definisce il tipo di animazione come rotazione sull'asse Z;
  • clutter_transition_set_from() imposta il valore di partenza dell'animazione, nel nostro caso la rotazione usa valori di tipo double ed il valore di partenza è 0;
  • clutter_transition_set_to() imposta invece il valore finale, 360 gradi è un giro completo;
  • clutter_timeline_set_duration() imposta la durata dell'animazione, intesa come velocità d'esecuzione. Notare che questa funzione fa parte della classe ClutterTimeline, ma noi gli passiamo il puntatore della nostra istanza di ClutterTransition utilizzando la macro per il cast CLUTTER_TIMELINE(). In poche parole gli diciamo di prendere il puntatore come fosse di tipo ClutterTimeline;
  • clutter_timeline_set_repeat_count() è la stessa identica funzione che abbiamo utilizzato con il nostro timeline all'inizio del tutorial, imposta ad infinito il numero di ripetizioni dell'animazione;
  • clutter_actor_add_transition() aggiunge l'animazione all'attore, da questo momento verrà avviata. “centrifuga” è il nome che abbiamo dato a questa animazione per identificarla, un attore infatti può contenere più animazioni definite con la classe ClutterTransition, aggiungendo più animazioni semplici otterremo animazioni complesse.

Con questo concludo la seconda parte del tutorial e mi impegno ad approfondire ulteriormente gli altri aspetti di Clutter nel prossimo tutorial.

Vi consiglio sempre di andare a guardare sulla guida di riferimento la sintassi completa delle istruzioni che abbiamo fin qui utilizzato. Utilizzate DevHelp o andate sul sito per farlo.


Per domande, suggerimenti, o segnalazioni di errori vi invito a lasciare dei commenti.


mercoledì 28 novembre 2012

Programmare con Clutter (versione 1.12.2): creare un'applicazione. Tutorial parte 1


Clutter è un libreria grafica open source che serve principalmente a creare interfacce grafiche basate su OpenGL ed in grado di sfruttare l'accelerazione della GPU. Con Clutter possiamo creare interfacce animate e gestire gli eventi di input dell'utente.
La libreria è multi piattaforma (X11, Darwin e Win32), è scritta in C ed offre anche la possibilità di utilizzarla attraverso altri linguaggi di programmazione.

Questo tutorial ha lo scopo di illustrare degli esempi pratici per imparare a sviluppare programmi con Clutter aggiornato all'ultima versione, la 1.12.2.
Mi sono avvicinato a questa libreria da poco tempo per creare una piccola applicazione per bambini, ed è stato difficile trovare su internet dei tutorial aggiornati all'ultima versione.
Visto che ci sono molte classi e funzioni deprecate dalle precedenti versioni, ecco una guida che va a colmare questa lacuna!

Per questo tutorial utilizzerò il linguaggio C (visto che sembra non sia ancora pronto il binding in C++) .

Prepariamo gli strumenti necessari per iniziare

Queste istruzioni si riferiscono ad Ubuntu (sto utilizzando la 12.10), per la preparazione degli strumenti di sviluppo su altri sistemi vi consiglio di consultare la documentazione sul sito ufficiale.

Il primo passo per iniziare a programmare e' installare gli strumenti di sviluppo sulla nostra macchina. Aprite Ubuntu Software Center, cliccate sul campo di ricerca in alto a destra e digitate “anjuta”, quindi selezionate Anjuta e cliccate su “Installa”.

Anjuta e' un ambiente di sviluppo integrato, cioè un software che aiuta i programmatori nello sviluppo del codice. In particolare e' rivolto allo sviluppo di applicazioni in ambiente GNOME.

Adesso vi serve la libreria Clutter, pulite il campo di ricerca di Ubuntu Software Center e digitate “libclutter dev”. Selezionate “OpenGL based interactive canvas library (development files)”, il nome del pacchetto è “libclutter-1.0-dev”, quindi cliccate su “ulteriori informazioni”. Quindi spuntate il componente aggiuntivo “OpenGL based interactive canvas library (documentation)“ e cliccate su Installa.

Ultimo strumento fondamentale è DevHelp, che vi permetterà di consultare la guida di riferimento di Clutter, cercate “devhelp” su Ubuntu Software Center ed installatelo, ora siete pronti all'azione.

Creiamo un nuovo progetto

Faremo un semplice programma che inizialmente si limita a creare una finestra con lo sfondo di colore nero e rappresentarla sullo schermo. Successivamente aggiungeremo altri elementi.

  • Iniziamo con lanciare Anjuta e selezionare Nuovo – Progetto nel menu File o sulla barra degli strumenti.
  • Quando compare la finestra di selezione del tipo di progetto, selezionate C e quindi Generico, quindi cliccate su continua.
  • Nella schermata successiva date un nome al progetto, ad esempio clutter_tutorial, quindi se volete il vostro nome ed indirizzo email. Il numero di versione non e' importante, andiamo avanti.
  • Nel campo destinazione cliccate sul tasto apri e selezionate la posizione dove mettere il progetto. Vi consiglio di creare una nuova cartella.
  • Spuntate l'opzione “Configura pacchetti esterni”, le altre opzioni le lasciamo con i valori di default, clicchiamo su continua.
  • Nella schermata che visualizza la lista dei pacchetti spuntiamo il pacchetto clutter-1.0 e clicchiamo su Continua, quindi su Applica.

Modifichiamo il file main.c

Apriamo il file main.c e aggiungiamo dopo #include<stdio.h>
#include <stdlib.h>
#include <clutter/clutter.h>

Cancelliamo le righe all'interno della funzione main() e sostituiamole con queste:

int main (int argc, char *argv[])
{
    ClutterInitError error = clutter_init(&argc, &argv);

    ClutterColor stage_color = { 0, 0, 0, 255 };

    ClutterActor *stage = clutter_stage_new();
    clutter_actor_set_size(stage, 512, 512);
    clutter_actor_set_background_color(stage, &stage_color);
    clutter_actor_show(stage);

    clutter_main();
    return error;
}

Adesso proviamo a compilare e guardiamo il nostro programma in azione.

clutter_tutorial crea una finestra nera sullo schermo

Per il momento il programma è molto grezzo, non possiamo fare molto oltre a vedere questa finestra nera sul nostro schermo. Dopo aver chiuso la finestra possiamo vedere che il programma rimane in esecuzione e siamo costretti a fermarlo da Anjuta, selezionando la voce “Ferma programma” dal menu Esegui.
Adesso analizziamo insieme il codice:

  • clutter_init() inizializza la libreria. Prima di qualsiasi chiamata di funzione bisogna inizializzare Clutter con questa istruzione. Come vedete riceve i parametri della funzione main(), infatti può ricevere dall'invocazione da riga di comando dei parametri. Il valore di ritorno è costituito da eventuali errori durante l'inizializzazione.
  • ClutterColor è la struttura che definisce il colore e viene inizializzata con i valori di rosso, verde, blu e di opacità, con valori che vanno da zero a 255. Nel nostro caso sono 3 zeri per ottenere il nero e 255 per avere un colore pieno senza trasparenze.

Prima di andare avanti bisogna fermarci per un po di teoria riguardante Clutter.
La classe ClutterActor è la base fondamentale di tutta libreria, ogni oggetto grafico rappresentato sullo schermo è un attore che si muove su un palcoscenico (stage). Lo stesso stage è comunque un'istanza della classe ClutterActor. Per prima cosa viene creato un palcoscenico, quindi vengono creati gli attori e vengono aggiunti al palcoscenico per diventare visibili.

  • clutter_stage_new() crea il palcoscenico, stage è il puntatore al nostro palcoscenico;
  • clutter_actor_set_size() definisce la larghezza e l'altezza di un attore, in questo caso viene utilizzato per definire le dimensioni del nostro palcoscenico, infatti nei parametri viene passato stage e le dimensioni di 512 di larghezza per 512 di altezza;
  • clutter_actor_set_background_color() definisce il colore di sfondo di un attore, nel nostro caso assegniamo il colore nero che avevamo definito in stage_color, al nostro palcoscenico stage;
  • clutter_actor_show() rende visibile l'attore, noi rendiamo visibile il nostro stage;
  • clutter_main() passa il controllo al main loop, cioè attende il verificarsi degli eventi, che possono essere ad esempio delle azioni da parte dell'utente;

Ora facciamo in modo che quando chiudiamo la finestra il programma si arresti, basta aggiungere questa riga dopo clutter_actor_show(stage);

g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

Questa funzione collega il segnale “destroy”, che viene lanciato da stage quando viene distrutto, con la chiamata alla funzione clutter_main_quit() che interrompe il main loop del programma. Ora, quando chiuderemo la finestra, stage verrà distrutto e lancerà il segnale “destroy”, quindi verrà chiamata la funzione clutter_main_quit() che terminerà il programma.
Compilate e provate a lanciare il programma e quindi a chiudere la finestra.

clutter_tutorial esce con codice d'errore 1 che corrisponde a CLUTTER_INIT_SUCCESS, cioè nessun errore.


E' arrivato il momento di aggiungere un attore sul nostro palcoscenico, aggiungiamo le seguenti righe dopo clutter_actor_set_background_color(stage, &stage_color);

ClutterColor actor_color = { 0, 255, 0, 128 };
ClutterActor *rect = clutter_actor_new();
clutter_actor_set_background_color(rect, &actor_color);
clutter_actor_set_size(rect, 100, 100);
clutter_actor_set_position(rect, 100, 100);
clutter_actor_add_child(stage, rect);

Analizziamo il codice che abbiamo aggiunto:
  • prima di tutto inizializziamo actor_color con un colore verde intenso;
  • clutter_actor_new() crea un nuovo attore, rect è il puntatore al nostro attore;
  • clutter_actor_set_background_color() è la stessa funzione che abbiamo usato per definire il colore del palcoscenico, adesso passiamo rect, che è il nostro attore e l'indirizzo del colore che abbiamo definito in actor_color;
  • anche clutter_actor_set_size() è lo stesso che abbiamo utilizzato prima, questa volta definiamo le dimensioni del nostro attore;
  • clutter_actor_set_position() definisce la posizione dell'attore sul palcoscenico, le coordinate sono rispettivamente la posizione sull'asse x seguita da quella sull'asse y. Le origini degli assi sono poste sull'angolo in alto a sinistra della finestra.
  • Ora che l'attore è pronto per andare sulla scena lo aggiungiamo al palcoscenico con la funzione clutter_actor_add_child(). Nel momento in cui l'attore viene aggiunto al palcoscenico questo diventa visibile.

Provate a compilare ed avviare il programma, ora vedrete un bel quadrato verde all'interno della nostra finestra nera.

un attore verde è sulla scena!
Il programma completo dovrebbe avere questo aspetto:


#include <stdio.h>
#include <stdlib.h>
#include <clutter/clutter.h> 
int main(int argc, char *argv[])
{
    ClutterInitError error = clutter_init(&argc, &argv);
    ClutterColor stage_color = { 0, 0, 0, 255 };
    ClutterActor *stage = clutter_stage_new();
    clutter_actor_set_size(stage, 512, 512);
    clutter_actor_set_background_color(stage, &stage_color);
    ClutterColor actor_color = { 0, 255, 0, 128 }; 
    ClutterActor *rect = clutter_actor_new();
    clutter_actor_set_background_color(rect, &actor_color);
    clutter_actor_set_size(rect, 100, 100);
    clutter_actor_set_position(rect, 100, 100);
    clutter_actor_add_child(stage, rect);
    clutter_actor_show(stage);
    g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); 
    clutter_main();
    return error;
}

Qui si conclude la prima parte del nostro tutorial, mi rendo conto che abbiamo visto poco fino a questo momento, ma ci siamo fatti un'idea della semplicità di utilizzo di Clutter.

Per ulteriori informazioni riguardanti la sintassi delle funzioni vi consiglio di consultare la guida di riferimento utilizzando DevHelp o sul sito.

Se avete dubbi, domande o suggerimenti vi invito a lasciare dei commenti, a presto!