/dev/random

Vala e Clutter

Interfacce in 3D in modo semplice e veloce.

Da un po' di tempo seguo lo sviluppo di Clutter, una libreria che permette di scrivere interfacce 3D basate su OpenGL, e quindi automaticamente accelerate se avete i driver 3D per la vostra scheda, in modo molto semplice.

Non andando molto d'accordo con il C (mi si ingarbugliano spesso i puntatori e i cast ;) ), aspettavo che fossero stabili e utilizzabili i binding per Vala, e dopo il recente rilascio della versione 0.8.0 di Clutter, finalmente anche i binding sembrano a buon punto, quindi mi sono messo a smanettarci. L'unica “fregatura” è che per ora bisogna scaricarli via SVN e installarli a mano in /usr/share/vala/vapi/.

Purtroppo non si trovano molti esempi in Vala, e quelli che si trovano sono basati sulla versione 0.6.x di Clutter, quindi ho cercato di fare un mix di questi e degli esempi in C sul sito ufficiale, seguendo il manuale ufficiale di Clutter che, nella migliore tradizione Gtk/Gnome, è utilissimo se sapete già usarlo, ma molto rognoso se state iniziando, e me ne sono uscito col primo programmino:

using Clutter;
using GLib;
using Gdk;

public class Scena : GLib.Object
{
    public Clutter.Label label;
    public Clutter.Timeline timeline;
    public Clutter.Color label_white;
    public Clutter.Color label_red;

    construct {

        this.timeline = new Clutter.Timeline(720,15);
        this.timeline.loop = true;
        this.timeline.new_frame += this.frame_cb;

        Clutter.Color label_white = {0xff, 0xff, 0xff, 0xff };
        this.label_white = label_white;

        Clutter.Color label_red = {0xff, 0x00, 0x00, 0xff };
        this.label_red = label_red;

        this.label = new Clutter.Label();
        this.label.set_font_name("Sans 24");
        this.label.set_text("Prova");
        this.label.set_color(ref label_white);
        this.label.set_position(10,10);
        this.label.set_reactive(true);
        this.label.button_press_event += this.button_press;
        this.label.button_release_event += this.button_release;
        this.label.enter_event += this.enter_event;
        this.label.leave_event += this.leave_event;
    }

    public void frame_cb(Clutter.Timeline tl, int frame_num)
    {
        if (frame_num <=360) {
            this.label.set_position(10+frame_num, 10+frame_num);
        } else {
            this.label.set_position(730-frame_num, 730-frame_num);
        }

        this.label.set_rotation(Clutter.RotateAxis.Y_AXIS, frame_num, 0,0,0);
        this.label.set_opacity((uchar)(frame_num%255));
    }

    public bool button_press(Clutter.Label label, Clutter.ButtonEvent event)
    {
        stdout.printf("Button press\n");
        return false;
    }

    public bool button_release(Clutter.Label label, Clutter.ButtonEvent event)
    {
        stdout.printf("Button release\n");
        return false;
    }

    public bool enter_event(Clutter.Label label, Clutter.CrossingEvent event)
    {
        label.set_color(ref this.label_red);
        return false;
    }

    public bool leave_event(Clutter.Label label, Clutter.CrossingEvent event)
    {
        label.set_color(ref this.label_white);
        return false;
    }

}
public class Test : GLib.Object
{
    static void main (string[] args) {
        Clutter.init (ref args);
        Clutter.Stage stage = (Clutter.Stage) Clutter.Stage.get_default ();
        Clutter.Color background;

        stage.get_color(out background);
        background.red = (char) 0x61;
        background.green = (char) 0x64;
        background.blue = (char) 0x8c;
        background.alpha = (char) 0xff;
        stage.set_color (ref background);

        stage.set_size(640, 480);

        var scena = new Scena();

        stage.add_actor(scena.label);
        scena.timeline.start();

        stage.show_all ();

        Clutter.main ();
    }
}

Il programma, se mi passate il francesismo, è di una stupidità disarmante.

Si limita a creare un “attore” (nel gergo di Clutter) rappresentato da una label (un testo) con font Sans 24 punti, di colore bianco, e a metterlo sulla scena (stage), data da una finestra da 640×480 pixel con sfondo azzurrognolo.

Tutto il resto è magia! Viene reso sensibile l'attore, e vengono intercettati i segnali di base: il puntatore si muove sopra la label, il puntatore esce dalla label, viene premuto il tasto sinistro del mouse, viene rilasciato il tasto sinistro del mouse. Poi il tutto viene animato tramite una timeline, formata da 720 frame a 15 frame al secondo (è già abbastanza fluido. Ho provato anche con 30 e 60, ma la differenza quasi non si nota, tranne per la velocità di movimento della scritta). I 720 frame li ho scelti per non complicarmi la vita, visto che le rotazioni sono in gradi, ma potete usare quello che volete.

La label si muove dall'angolo in alto a sinistra a quello in basso a destra, e nel frattempo ruota su se stessa e appare piano piano, per poi sparire di nuovo. Il tutto avviene nel callback frame_cb() che, in base al frame in cui si trova, applica le variazioni all'attore.

Se vi muovete col mouse sopra la label, questa diventa rossa, se ne uscite, torna bianca (enter_event e leave_event). Se ci cliccate vi appare un messaggio nella console (button_press e button_release). Il tutto anche mentre la label si muove!

Le possibilità sono veramente infinite.

Si possono muovere gli attori (che però sono limitati ad essere 2D) in uno spazio completamente 3D. Li si vede rimpicciolire quando si allontanano lungo l'asse z, e ingrandire quando si avvicinano, e si deformano quando si allontanano dal centro (il punto di fuga della scena). Si possono caricare immagini in diversi formati, compreso l'SVG (vettoriale).Si possono raggruppare diversi elementi (immagini, testi, video ed altro) in un gruppo che è a sua volta un attore, e può essere spostato, scalato, ruotato, ecc.

Grazie a Pango, il testo non ha limiti di nazionalità e di caratteri strani. Si possono inserire filmati supportati da GStreamer, renderizzare su superfici Cairo, embeddare il tutto in finestre Gtk+ e, tramite librerie esterne, utilizzare un motore basato su leggi fisiche o embeddare il motore WebKit per visualizzare pagine HTML.

Inoltre sono supportati diversi dispositivi di input e si possono definire le animazioni tramite scripting in JSON.

Io ho appena iniziato a scalfire la superficie di questo enorme iceberg, ma già intravedo sviluppi spaventosi. D'altronde non serve nemmeno fantasticare tanto. Se avete presente l'interfaccia dell'iPhone/iPod Touch o quelle che ho linkato qualche giorno fa per Android, ecco, avete un'idea di cosa ci si possa fare. Anche Intel, per Moblin, probabilmente userà clutter per la GUI.

Clutter è anche multipiattaforma, e Vala, dopo aver installato le GLib ed eventualmente le GDK e le Gtk+ sotto Windows/Mac, può essere compilato anche su questi sistemi, quindi i software gireranno senza problemi su tutti e tre i sistemi.