Visualizzazione post con etichetta programmazione. Mostra tutti i post
Visualizzazione post con etichetta programmazione. Mostra tutti i post

domenica 26 marzo 2017

Multi-threading e interfaccia grafica GTK+

Il multi-threading è la suddivisione di un processo in diversi sotto processi eseguiti in parallelo (o concorrentemente), questo ci permette di eseguire il nostro programma sfruttando la potenza di calcolo di tutti i core presenti nei moderni processori dotati di tecnologia multi-core.

Questa tecnica è molto utile soprattutto per migliorare le prestazioni in presenza di un carico di lavoro pesante, ma è anche utilizzata per preservare la reattività dell'interfaccia grafica in qualsiasi situazione.
Mentre negli anni ottanta era accettabile il fatto di dover attendere davanti al monitor mentre il computer finiva di elaborare l'operazione assegnata, al giorno d'oggi, trovarsi davanti ad un'interfaccia grafica che non risponde a nessun comando ci farebbe subito pensare che il software in esecuzione sia andato in crash o che comunque ci sia un problema.

Quando non è possibile velocizzare un'operazione al punto da renderla quasi istantanea, allora bisogna intrattenere l'utente per ingannare l'attesa. Ad esempio, se l'interfaccia grafica fornisce informazioni sullo stato dell'esecuzione di un'operazione complessa, l'utente attenderà sapendo che il software sta lavorando correttamente.

Iniziamo con il creare una classe che sia in grado di svolgere un compito su un thread separato dal programma principale.



E' una classe molto semplice: ha una variabile membro di tipo double chiamata fraction_done, che ci servirà a simulare l'esecuzione di un lavoro, ed una denominata wrk_mutex, che è di tipo mutex (dall'inglese mutual exclusion, mutua esclusione) fornito dalla standard library per impedire che più thread accedano contemporaneamente agli stessi dati.

Le funzioni membro sono solo due: start_work() che esegue il lavoro e get_fraction_done() che comunica lo stato dell'esecuzione.

All'interno della funzione start_work() troviamo un primo blocco, delimitato dalle parentesi graffe, all'interno del quale si invoca la classe lock_guard, che serve ad acquisire il mutex ed impedire a qualsiasi altro thread in esecuzione di interferire fino alla fine delle parentesi. All'interno di questo blocco protetto, fraction_done è inizializzato con il valore di 0.0.
Successivamente troviamo un loop infinito, all'interno del quale il thread in esecuzione attende 250 millisecondi con la chiamata di funzione sleep_for(), per simulare lo svolgimento di un'operazione complessa.
Quindi c'è un nuovo blocco di codice con una nuova acquisizione del mutex e si incrementa il valore di fraction_done, infine verifica se il lavoro è concluso (al raggiungimento del valore di 1.0) ed esce dal loop.

Ora scriviamo un piccolo programma che utilizza la nostra classe Worker.


Nella funzione main() viene dapprima creata un'istanza della classe Worker, quindi viene creato un nuovo thread che esegue la funzione membro start_work() di Worker. Da questo punto in poi il programma principale è eseguito in modo concorrenziale a start_work(), nel loop infinito all'interno di main() viene richiesto all'istanza di Worker il valore attuale di fraction_done.

Nella funzione get_fraction_done() viene invocata la classe lock_guard per impedire che fraction_done sia modificato mentre se ne comunica il valore.

Quindi viene stampato sul video il valore ottenuto (solo se diverso dall'ultimo ottenuto).
Se il valore ha raggiunto 1.0 allora esce dal loop perché il lavoro è terminato, infine viene invocata la funzione join() per riunire il thread al programma principale prima di rilasciare le risorse allocate e terminare.

Proviamo a compilare ed eseguire


Funziona come volevamo, il thread svolge il suo compito ed il programma principale ci tiene aggiornati sullo stato del lavoro.
Ora proviamo ad applicare la stessa tecnica ad un programma con interfaccia grafica realizzata con la libreria GTK+.

Utilizzerò l'IDE Anjuta per realizzare un nuovo programma GTK+.

Creiamo un nuovo progetto di tipo C++ GTKmm (semplice).
Nelle opzioni del progetto spuntiamo la voce "configura pacchetti esterni"
Spuntiamo il pacchetto pthread-stubs
Dopo aver creato il progetto andiamo a creare l'interfaccia grafica con Glade


Come potete vedere dall'immagine è una finestra con una barra progressiva ed un'etichetta per visualizzare lo stato del lavoro. Subito sotto ci sono i pulsanti per avviare e fermare il thread.

Ora modifichiamo la classe Worker per farla lavorare in un programma dotato di interfaccia grafica.


Analizzando l'header potete vedere l'aggiunta di alcune variabili membro:
- has_stopped - ci servirà per capire se il thread è fermo;
- shall_stop - utilizzato per comunicare al thread l'intenzione di fermarlo;
- caller_notification - questo è un signal che utilizzeremo per comunicare con il programma principale.

Inoltre sono stati aggiunte tre funzioni:
- has_stopped_working() - per sapere se il thread è fermo;
- stop_work() - per fermare il thread;
- signal_caller_notification() - per connettere il signal al nostro programma.

Per finire è stata modificata la funzione start_work(), con l'aggiunta dell'emissione del signal e la possibilità di fermare il lavoro tramite shall_stop.

Ora vediamo il programma principale.


Oltre al file main.cc ho creato una classe Controller che si occupa di gestire l'intera applicazione.
Di solito si trovano esempi o tutorial in C++ che utilizzano GTKmm dove si crea una classe derivata di Gtk::Window e si costruisce l'interfaccia da codice, il mio approccio è differente.
Per prima cosa preferisco creare l'interfaccia grafica con Glade, non vedo il vantaggio di costruirla tramite codice, per quanto riguarda questo pattern, è probabilmente frutto dalle precedenti esperienze di programmazione su OS X con Objective-C.

L'istanza di Controller creata in main() si occupa di recuperare gli elementi dell'interfaccia grafica dal file .ui, quindi in connect_signals() collega i signal dei pulsanti e di due altri oggetti: ctrl_worker e ctrl_dispatcher.
Il primo è un'istanza della classe Worker e collega il suo signal alla funzione on_worker_notification().
Il secondo è un oggetto della classe Gtk::Dispatcher ed è l'elemento fondamentale per garantire il corretto funzionamento del thread di Worker con l'interfaccia del programma principale.
Alla pressione del pulsante start viene creato un thread al quale viene affidata l'esecuzione della funzione start_work() di Worker, il puntatore denominato wrk_thread ne terrà traccia.
Ogni qualvolta il thread emette un signal questo verrà ricevuto dal programma ed eseguirà la funzione on_worker_notification() che, a sua volta, emetterà il signal del nostro ctrl_dispatcher.
Quest'ultimo verrà ricevuto dalla funzione on_dispatcher_notification() che si occuperà di verificare lo stato del thread, aggiornare l'interfaccia grafica ed eliminarlo se fermo.

Questo è il programma perfettamente funzionante.


Provando a non utilizzare l'istanza di Gtk::Dispatcher, collegando il signal di Worker direttamente alla funzione che gestisce il thread ed aggiorna l'interfaccia grafica, finiremmo con un crash dell'applicazione in un momento non precisato dell'esecuzione.


giovedì 22 dicembre 2016

Iniziare a programmare con SDL 2 su Linux - parte 2


Nella prima parte di questo tutorial abbiamo visto come installare tutti gli strumenti di sviluppo e abbiamo creato il nostro primo programma sfruttando la libreria SDL 2. In questa seconda parte approfondiremo meglio l'utilizzo di g++ e valgrind, inoltre miglioreremo il nostro SDL Hello World.

Per tutti coloro che sono nuovi utenti del mondo Linux o che comunque non hanno esperienza nell'invocazione del compilatore g++ ho pensato di fare un po' di chiarezza sulla sintassi di questo comando.

La sintassi più semplice è
$ g++ [file sorgente]

L'eseguibile avrà il nome di default a.out.
Per specificare il nome dell'eseguibile basta aggiungere l'opzione -o [nome eseguibile], quindi
$ g++ [file sorgente] -o [nome eseguibile]

Se ci sono più file sorgenti basta elencarli
$ g++ [file sorgente 1] [file sorgente 2] -o [nome eseguibile]

Per finire, se dobbiamo utilizzare una libreria, come SDL 2 nel nostro caso, dobbiamo comunicarlo al linker tramite l'opzione -l (elle minuscola) seguita dal nome della libreria senza spazi.
$ g++ [file sorgenti] -o [nome eseguibile] -l[nome libreria]

Per la libreria SDL 2 scriveremo -lSDL2

Bene, questo è quanto abbiamo visto finora nella prima parte del tutorial, ma avrete notato che, quando la lista dei file sorgente aumenta, invocare g++ diventa decisamente scomodo.
Per semplificare il tutto possiamo utilizzare il comando make.

Il funzionamento di make è molto semplice, dobbiamo creare un file di testo dove forniremo tutte le informazioni necessarie per la compilazione, quali il compilatore da invocare, la lista dei file sorgente, le opzioni da passare al compilatore, ecc...
Una volta preparato il file chiamato Makefile, basterà invocare make e questo invocherà g++ per noi.
Questo è un esempio di Makefile per compilare il nostro SDL Hello World


Il contenuto del Makefile è abbastanza semplice da capire, man mano che aggiungiamo un file sorgente o un file header basterà aggiungerlo alla lista nel Makefile. Possiamo apportare tutte le modifiche che vogliamo nel Makefile e basterà invocare il comando make per compilare ed ottenere il file eseguibile.
Oltre a questo make si occupa di verificare se dall'ultima compilazione è cambiato qualcosa nei file sorgente, se nulla è cambiato ci verrà notificato.
Copiate ed incollate il testo in un file e salvatelo con il nome Makefile nella stessa directory dei file sorgente. Ora provate ad eseguire il comando
$ make

Decisamente più comodo, non è vero?



Passiamo ad un altro argomento: valgrind.
La scorsa volta avevamo installato valgrind in quanto ottimo strumento di debugging, in particolare per gli errori nella gestione delle allocazioni di memoria. La sintassi è semplice
$ valgrind ./[nome eseguibile]

L'eseguibile verrà avviato all'interno dell'ambiente di debugging di valgrind, il quale analizzerà tutte le allocazioni di memoria e le relative de-allocazioni delle stesse. Al termine del programma verrà stampato sul terminale un resoconto completo con un elenco degli eventuali errori rilevati.

Proviamolo con il nostro SDL Hello World
$ valgrind ./sdl_helloworld

L'esecuzione risulterà più lenta e alla fine avremo sul terminale un resoconto dettagliato.
Senza entrare troppo nei particolari, quello che ci interessa più di tutto il resto è l'ultima riga:
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 2)

Nella sezione LEAK SUMMARY potete trovare altri dettagli sulle allocazioni di memoria.
E' ovvio che non è solo il vostro codice che viene analizzato, ma anche quello delle librerie utilizzate, quindi alcuni memory leaks possono essere causati da quest'ultime.

Bene, torniamo all'argomento principale di questo tutorial, SDL 2.
Nello scorso esempio abbiamo utilizzato la funzione SDL_loadBMP(), che carica un'immagine di tipo BMP (Windows bitmap) in un oggetto SDL_Surface.
Per quanto questa funzione sia molto comoda e semplice da utilizzare, il formato BMP è piuttosto limitato e scarsamente utilizzato al giorno d'oggi.
Nella libreria principale di SDL 2 non ci sono altre funzioni per caricare immagini di tipo diverse, quali ad esempio PNG o JPG, ma fortunatamente c'è un'altra libreria dedicata a questo scopo: SDL_image.

Prima cosa da fare è installare sul nostro sistema questa libreria.
$ su
(verrà richiesta la password di root)
# apt install libsdl2-image-dev



Ora che abbiamo installato SDL_image possiamo modificare il programma per aprire un'immagine di tipo PNG.

Aggiungiamo l'include con il file header di questa libreria in helloengine.h
#include <SDL2/SDL_image.h>

Come la libreria SDL 2 anche questa va inizializzata, quindi nella funzione init() della classe HelloEngine (helloengine.cc) aggiungiamo queste righe di codice per inizializzare SDL_image per il caricamento di immagini PNG, subito sotto l'inizializzazione di SDL 2.

// inizializzazione SDL_image per i file di tipo PNG
if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) != IMG_INIT_PNG)
{
    on_SDL_error(std::cout, "IMG_Init");
    return false;
}

Ora andiamo a modificare la parte di codice che carica l'immagine, è sempre nella funzione init() poco più in basso. Utilizzeremo la funzione IMG_LoadTexture() che ci restituisce direttamente un puntatore ad un oggetto di tipo SDL_Texture, quindi non avremo bisogno di crearla passando per un SDL_Surface.

// carica l'immagine in texture
std::string name = "helloworld.png";
if ((texture = IMG_LoadTexture(renderer, name.c_str())) == nullptr)
    on_SDL_error(std::cout, "IMG_LoadTexture");

Potete convertire l'immagine BMP in una di tipo PNG utilizzando Gimp e salvatela con il nome helloworld.png.

Prima di compilare ricordatevi che stiamo utilizzando un'altra libreria, quindi dovremo modificare il nostro Makefile aggiungendo -lSDL2_image alla lista delle librerie da linkare, subito dopo -lSDL2.

Terminate le modifiche questo è quello che dovreste ottenere
Compilate con
$ make

Ed ora avviate il programma
$ -/sdl_helloworld

Se non avete fatto errori il programma funzionerà come prima.
Bene, in questa seconda parte del tutorial dedicato ad SDL 2 abbiamo preso maggior confidenza con gli strumenti di sviluppo e debugging, inoltre abbiamo visto come usare la libreria SDL_image.
Nel prossimo tutorial faremo ulteriori modifiche e vedremo come funziona la gestione degli eventi.

martedì 20 dicembre 2016

Iniziare a programmare con SDL 2 su Linux


SDL (Simple DirectMedia Layer) è una libreria di sviluppo multi-piattaforma, scritta in linguaggio C, progettata per controllare facilmente video, grafica, audio, tastiera, mouse, joystick, ecc...
Queste capacità la rendono ideale per lo sviluppo di videogiochi, applicazioni multimediali ed emulatori.
Un grande vantaggio è il fatto di essere multi-piattaforma, questo infatti semplifica il porting sulle diverse piattaforme supportate (Linux, OS X, Windows, iOS e Android).
SDL 2.0 è rilasciata con licenza Zlib, che è una licenza per software libero ed è compatibile con la licenza GPL.

Vediamo subito come preparare il nostro sistema con tutti gli strumenti necessari per iniziare a programmare.
In questo tutorial utilizzerò Debian GNU/Linux 8.6 con il desktop manager di default GNOME.
Probabilmente già saprete che Debian è una distribuzione GNU/Linux universale ed è la base di molte altre distribuzioni derivate, tra le quali Ubuntu. Utilizzare Debian in questo tutorial mi permette di abbracciare una grossa fetta di utenti Linux.

La prima cosa di cui abbiamo bisogno sono gli strumenti di sviluppo fondamentali, quali il compilatore, il linker ed un debugger.
Il pacchetto build-essential ci fornirà tutta la suite di programmi utili allo sviluppo.

$ su
vi verrà chiesta la password di root.
Controlliamo se ci sono aggiornamenti del sistema disponibili con
# apt update
Se trovati procediamo con l'aggiornamento con
# apt upgrade

Quindi installiamo build-essential
# apt install build-essential


Analizzando la lista dei pacchetti installati, forse avrete notato che gdb (il debugger GNU) è nella lista dei pacchetti consigliati, ma non è stato installato. Possiamo installarlo manualmente con
# apt install gdb
Oppure verrà installato automaticamente quando andremo ad installare Valgrind.
Valgrind è un ottimo software di debug e profiling che, tra le altre cose, permette di scovare gli errori nella gestione della memoria, come ad esempio i memory leaks, ossia le allocazioni di memoria che non vengono liberate al termine del loro utilizzo.
# apt install valgrind


Ora che abbiamo gli strumenti fondamentali per lo sviluppo, possiamo installare la libreria SDL 2.0.

# apt install libsdl2-dev


Il pacchetto libsdl2-dev contiene la libreria SDL 2.0 e tutti i componenti fondamentali per sviluppare applicazioni con SDL, ma se provate a cercare i pacchetti libsdl2 disponibili troverete altri componenti che ne espandono le funzionalità.
# apt search libsdl2


Analizzeremo ed installeremo gli altri pacchetti nel prossimo tutorial, quando ci serviranno, per il momento iniziamo a fare un piccolo test di compilazione per verificare che tutte le operazioni di preparazione siano andate a buon fine.

Per questo tutorial utilizzerò gedit, l'editor di testo del desktop GNOME, semplice e leggero, sicuramente adatto per scrivere/modificare file di codice sorgente di piccole dimensioni.
Nonostante la sua estrema semplicità, con qualche piccolo accorgimento possiamo renderlo un discreto strumento di sviluppo.
Dopo aver lanciato gedit, clicchiamo sul menù dell'applicazione sulla barra dello schermo in alto accanto ad Attività, e selezioniamo la voce Preferenze. Comparirà una finestra con diverse opzioni divise per categorie, clicchiamo su Editor e modifichiamo l'ampiezza di tabulazione da 8 a 4. Personalmente trovo che 8 spazi siano decisamente troppi, ma siete liberi di impostare il numero di spazi che più vi aggrada.

Preferenze di gedit
Ora cliccate su Plugin e scorrete la lista in basso fino a trovare e spuntare l'opzione Terminale integrato. Questo ci permetterà di avere un Terminale nella parte bassa della finestra di gedit, evitando di dover passare da una finestra ad un'altra quando dobbiamo compilare o effettuare operazioni di debugging.


Adesso clicchiamo sull'icona menù, posta sulla finestra di gedit accanto al tasto x di chiusura, selezioniamo Vista e spuntiamo le voci Riquadro laterale e Riquadro inferiore.



Queste faranno visualizzare un riquadro laterale con la lista dei file aperti ed un riquadro inferiore con il Terminale integrato.
Per finire, nella barra di stato di gedit, cliccate su Testo semplice per far comparire un menù e selezionate C++ per usufruire della colorazione automatica del testo per la sintassi del linguaggio di programmazione che andremo ad utilizzare nel tutorial.


Al termine di queste personalizzazioni, gedit ha un aspetto decisamente più orientato alla programmazione e meno minimalista.

Facciamo un test veloce per verificare che tutto funzioni regolarmente, scriveremo il classico Hello World e proveremo a compilarlo.

E' indispensabile avere una conoscenza minima del linguaggio C++, altrimenti vi consiglio di andare a consultare un manuale o un tutorial specifico.
Salvate il file in Documenti con il nome helloworld.cc, quindi dal Terminale integrato di gedit, andate in Documenti e provate a compilare il vostro programma.

$ g++ helloworld.cc -o helloworld

Se non avete fatto errori la compilazione andrà a buon fine e potrete eseguire helloworld

$ ./helloworld
Hello World!

A questo punto facciamo un altro test, un nuovo Hello World, ma questa volta utilizzando SDL 2.
Andremo a creare una classe chiamata HelloEngine che sarà il motore della nostra applicazione, mentre nel file principale, chiamato sdl_helloworld.cc ci sarà la funzione main() che si occuperà di creare un'istanza di HelloEngine ed avviarne l'esecuzione.
E' una struttura simile a quello che si utilizza per lo sviluppo di un videogioco, in questo caso è molto semplificata in quanto non ha un loop e non gestisce alcun evento. Questo programma si limiterà ad aprire una finestra e visualizzare il messaggio Hello World, dopo 2 secondi d'attesa chiude la finestra e termina.

Questi sono i tre file del nostro SDL Hello World.

Iniziamo ad esaminare il file header della classe HelloEngine, all'inizio del file andiamo ad includere iostream e SDL2/SDL.h.
Il secondo è ovviamente indispensabile per utilizzare la libreria SDL2, mentre il primo è necessario quando andiamo a stampare gli eventuali messaggi d'errore tramite std::ostream.

Le funzioni membro sono solo 4:
- init() si occupa di inizializzare HelloEngine;
- start() avvia l'esecuzione;
- render() disegna l'immagine caricata nella finestra;
- on_SDL_error() stampa il messaggio d'errore sullo std::ostream indicato.

Per il resto il file header è piuttosto semplice, andiamo a vedere come queste 4 funzioni sono implementate in helloengine.cc.

Init() è la funzione che inizializza l'ambiente SDL invocando SDL_Init(), prima di poter utilizzare qualsiasi funzione della libreria SDL2 dobbiamo inizializzarla con questa funzione. Successivamente viene creata una finestra con SDL_CreateWindow(), quindi viene creata un'istanza di SDL_Renderer che ci servirà per tutte le operazioni grafiche.
Per finire viene caricata un'immagine BMP ottenendo un oggetto SDL_Surface con il quale viene creato un oggetto SDL_Texture.
Dopo aver ottenuto la nostra texture, viene distrutto l'oggetto SDL_Surface con la funzione SDL_FreeSurface() liberando la memoria allocata.

render() è incaricata di disegnare sulla finestra.
SDL_RenderClear() cancella il contenuto di renderer, SDL_RenderCopy() disegna la texture ottenuta dal caricamento dell'immagine su renderer, infine con SDL_RenderPresent() viene visualizzato il contenuto di renderer sulla finestra.

Start() avvia l'esecuzione vera e proprio di HelloEngine, inizialmente invoca init() per effettuare tutte le inizializzazioni, quindi esegue la funzione render() per disegnare sulla finestra. SDL_Delay() attende per il numero di millesecondi indicato, nel nostro caso attende 2 secondi e quindi esce con valore 0 per indicare che non ci sono stati errori.

on_SDL_error() stampa un messaggio d'errore sullo std::ostream indicato, di fatto è sempre std::cout.

Infine è bene soffermarsi sul distruttore della classe HelloEngine, che si occupa di de-allocare tutta la memoria utilizzata dal programma.
SDL2 ha una funzione specifica per ogni tipo di oggetto da de-allocare, non possiamo utilizzare il semplice delete. Abbiamo visto in init() la funzione SDL_FreeSurface() per distruggere l'SDL_Surface, qui utilizziamo SDL_DestroyTexture per l'SDL_Texture, SDL_DestroyRenderer per l'SDL_Renderer e SDL_Quit per chiudere l'intero ambiente SDL.

Il file sdl_helloworld.cc contiene la funzione main() del programma, il funzionamento è estremamente semplice, crea un'istanza di HelloEngine e ne invoca l'esecuzione con start(). Al termine dell'esecuzione distrugge l'istanza con il delete ed esce con lo stesso codice d'uscita dell'istanza.


Andiamo a compilare SDL Hello World con

$ g++ -std=c++11 helloengine.h helloengine.cc sdl_helloworld.cc -o sdl_helloworld -lSDL2


Prima di eseguire ricordatevi di mettere l'immagine helloworld.bmp nella stessa directory dell'eseguibile, altrimenti verrà visualizzata una finestra completamente nera.


Immagine BMP utilizzata per questo test


L'immagine verrà salvata come JPG, potete esportarla in formato BMP utilizzando Gimp oppure ne potete creare una voi. Ricordatevi che per poter essere caricata correttamente da SDL_LoadBMP() l'immagine deve avere un unico livello.
 
$ ./sdl_helloworld


SDL Hello World in esecuzione

In questo tutorial abbiamo installato gli strumenti di sviluppo e la libreria SDL 2 sul nostro sistema ed abbiamo visto un primo esempio di applicazione sviluppata con questa libreria.
Nel prossimo tutorial vedremo come caricare immagini di altri formati, gestire eventi come la pressione di un tasto o il click del mouse ed altro ancora.

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!