1. Enlightenment et les EFL
Les EFL, Enlightenment Foundation Libraries, sont les bases d'un toolkit développé par l'équipe du projet Enlightenment depuis un peu plus de 10 ans, principalement pour le gestionnaire de fenêtres E17. Le projet E17 visant à fournir un gestionnaire de fenêtres à la fois joli, léger et rapide a donc mis de très gros efforts dans la réalisation d'une base saine, légère et rapide. Les EFL ont évolué dans le temps et sont maintenant destinées à la réalisation de tout type d'application ; à tel point qu'aujourd'hui, l'objectif est d'en faire un concurrent dans l'embarqué face aux poids lourds tels que QT ou GTK.
C'est ainsi que les EFL viennent de passer en version Alpha, ce qui garantira une compatibilité API/ABI la plus longue possible dans le temps. La version 1.0 devrait être disponible d'ici peu, et l'objectif est que la version 2.0 (permettant éventuellement de « casser » l'ABI) ne sorte pas avant plusieurs années. Avec cette sortie, les EFL entrent définitivement dans la cour des grands et cassent un peu plus ce mythe de vaporware. D'ailleurs, pour le clin d'œil, Free a inclus Duke Nukem 3D dans le même firmware mettant à disposition Elixir, ce framework JavaScript qui utilise les EFL.
Les EFL ont été découpées en morceaux logiques pour en faciliter la compréhension et la maintenance :
Eina est la base de toutes les EFL. Une bibliothèque de types de données qui essayent de rendre le plus simple possible la manipulation efficace de ces types. Dans le cadre du projet Elixir et sur la Freebox, celle-ci n'est pas du tout exposée aux utilisateurs car le JavaScript a déjà, contrairement au C, ces fonctionnalités dans le langage.
Eet fournit une mécanique robuste et efficace pour sérialiser et enregistrer des données dans un fichier ou les envoyer sur le réseau. Cela inclut n'importe quelle structure mémoire, des images ou encore les scripts JavaScript d'Elixir.
Evas est un canvas graphique stateful. C'est réellement le cœur des EFL car c'est Evas qui est en charge de la gestion et de l'affichage des objets sur le canvas graphique. C'est cette bibliothèque qui optimise toutes les requêtes au système graphique pour tirer au mieux parti des performances du matériel.
Ecore est chargé de gérer tous les événements, qu'ils soient liés aux fenêtres graphiques (redimensionnement, déplacement, ...) ou au réseau.
Edje permet quant à elle de définir indépendamment du code de l'application son look & feel. Cela permet, par exemple, d'aussi bien définir le layout des menus d'une application que les animations d'un sprite. L'intérêt étant de pouvoir avoir plusieurs versions d'un thème s'adaptant le mieux possible au terminal cible, une véritable abstraction entre le code et l'interface
Sur ces bases vient se poser Elixir, qui expose directement l'API C des EFL en JavaScript grâce à la bibliothèque SpiderMonkey du projet Mozilla. Ce qui fait de cette solution un framework entièrement libre et ouvert, portable et intégrable facilement sur des plates-formes même très limitées. Pour pouvoir bénéficier d'un maximum d'exemples, de documentation et d'aide, il a été préféré de ne pas faire une API orientée objet : cela permet de réutiliser quasi directement les exemples en C.
2. Elixir
Il existe plusieurs manières de récupérer les EFL et SpiderMonkey. Ainsi, il existe un PPA pour Ubuntu, de Cédric Schieli, ppa:cschieli/freebox-elixir, des paquets sont aussi disponibles dans AUR pour Arch et il est bien entendu possible de tout compiler à la main en partant des sources. Pour ce dernier cas, nous vous laisserons consulter la documentation disponible sur http://code.google.com/p/freebox-elixir/wiki/InstallerElixir. Free met aussi à disposition une image pour machine virtuelle VirtualBox à l'adresse : ftp://ftp.free.fr/pub/elixir/Elixir_virtualbox.tar.gz. Ceci doit permettre de démarrer à développer sans trop perdre de temps sur des détails de compilation et d'installation.
Essayons donc, pour commencer, de réaliser un classique Hello World!. Tout d'abord, il faut charger les bindings nécessaires à notre exemple, c'est-à-dire Evas, Ecore et Ecore_Evas. Chaque module pouvant exister de manière optionnelle, il est considéré comme étant une bonne pratique de vérifier la valeur de retour de la fonction elx.load.
var test = true;
test &= elx.load("evas");
test &= elx.load("ecore");
test &= elx.load("ecore-evas");
Maintenant que les bindings sont activés, il faut initialiser les EFL, créer une fenêtre, ajouter le texte au canvas, puis enfin, attendre que l'utilisateur presse sur une touche avant de quitter. Il va donc falloir utiliser Ecore puis Evas. Regardons ce que cela donne :
var background;
var obj;
ecore_init();
ecore_evas_init();
// Création de la fenêtre
var ee = ecore_evas_new(null, 0, 0, 720, 576, "name=Hello World;");
// Obtention du canvas lié à la fenêtre
var evas = ecore_evas_get(ee);
// Définition des contraintes sur le canvas
evas_image_cache_set(evas, 10 * 1024 * 1024);
evas_font_path_prepend(evas, "/.fonts/");
evas_font_cache_set(evas, 512 * 1024);
// Création d'un background noir opaque
obj = evas_object_rectangle_add(evas);
evas_object_resize(obj, 720, 576);
evas_object_color_set(obj, 0, 0, 0, 255);
evas_object_show(obj);
background = obj;
// Création de l'objet texte
obj = evas_object_text_add(evas);
evas_object_text_text_set(obj, "Hello World!");
evas_object_text_font_set(obj, "Vera", 30);
evas_object_text_style_set(obj, EVAS_TEXT_STYLE_SOFT_SHADOW);
evas_object_text_shadow_color_set(obj, 128, 128, 128, 255);
evas_object_color_set(obj, 128, 64, 0, 180);
evas_object_resize(obj, 200, 50);
evas_object_move(obj, 100, 100);
evas_object_show(obj);
Comme Evas est un canvas stateful, nous créons les objets qui seront affichés à l'écran, puis on en change les propriétés pour obtenir le résultat voulu. Par contre, vous noterez la présence d'un rectangle noir explicitement défini pour le fond. Il est nécessaire, car Evas n'a pas de notion d'initialisation du fond d'écran avec une valeur quelconque. Donc, sans cet objet, Evas ne saurait pas quoi mettre comme contenu dans les pixels composant le fond. Vous pouvez tester en désactivant juste l'appel à evas_object_show, qui correspond au fond d'écran (par défaut, les objets ne sont pas visibles). Ce bout de code a aussi introduit deux types d'objets gérés par Evas : les rectangles et le texte. Evas gère aussi des images, des blocs de texte, des tables, des listes fixes (appelées box) et des objets composés de toutes ces primitives (appelés smart objects).
Ajoutons maintenant la gestion du clavier pour quitter l'application :
evas_object_event_callback_add(background, EVAS_CALLBACK_KEY_UP, key_up_cb, null);
evas_object_focus_set(background, 1);
ecore_evas_show(ee);
ecore_main_loop_begin();
Voilà ! Nous avons maintenant attaché une callback sur l'objet background et défini qu'il recevrait les événements clavier puisqu'il possède le focus (evas_object_focus_set). Enfin, la fenêtre a été affichée juste avant de démarrer la boucle principale du programme. C'est cette boucle qui déclenchera au moment opportun la mise à jour du rendu à l'écran et transmettra les événements clavier à Evas sans bloquer l'interactivité de l'application. Détaillons donc le contenu de la fonction key_up_cb :
function key_up_cb(data, e, obj, event)
{
switch (event.keyname)
{
case "b":
case "Red":
case "equal":
case "Stop":
case "Home":
case "Escape":
case "Start":
ecore_main_loop_quit();
break;
}
}
On notera qu'ici, le langage JavaScript nous aide en permettant de directement faire un switch sur des chaînes de caractères, ce qui simplifie grandement le code. On précisera que ecore_main_loop_quit va demander à ecore de quitter la boucle principale, mais ne quittera pas brutalement le programme. On va donc revenir à la fonction d'initialisation, juste après l'appel à ecore_main_loop_begin, et on en profitera pour tout désinitialiser proprement en détruisant les objets du canvas ainsi que la fenêtre et en « coupant » les EFL utilisées dans l'exemple.
evas_object_del(obj);
evas_object_del(background);
ecore_evas_free(ee);
ecore_evas_shutdown();
ecore_shutdown();
Voici donc un exemple simple mais peu animé (ou plutôt très statique !). Nous allons maintenant ajouter du tonus à ce texte en le faisant rebondir d'un bout à l'autre de l'écran. Pour cela, nous allons utiliser un timer un peu spécial : un animator. Utiliser un timer classique dans cet exemple ne comportant une seule animation n'aurait pas vraiment montré de problème, mais lorsqu'on a beaucoup d'objets en mouvement, on veut que leurs déplacements soient synchronisés. Pour cela, il faut synchroniser les timers entre eux, ce qui n'est pas vraiment pratique. C'est ainsi que l'on fait appel à un type spécial de timers, qui vont toujours s'exécuter ensemble au même rythme, et ainsi, permettre à l'animation d'une frame d'être toujours complète. Commençons donc par l'initialiser juste après avoir créé le texte :
obj.dx = +20;
obj.dy = +10;
obj.x = 100;
obj.y = 100;
obj.w = 200;
obj.h = 50;
obj.constrain = { x: 0, y: 0, w: 720, h: 576 };
ecore_animator_add(anim_cb, obj);
La première chose que l'on remarque ici, c'est que nous définissons des propriétés supplémentaires à l'objet texte. Le problème que l'on tente de résoudre par cette démarche est que faire des allers-retours entre le monde en C et le JavaScript est coûteux. Pour éviter ceci, on utilise aussi longtemps que possible les informations connues par le JavaScript, puis on copie la dimension de l'objet texte et on lui ajoute également une propriété de vitesse, dx et dy. Avec cela, on est prêt à lancer l'animation, il ne reste plus qu'à coder anim_cb.
function anim_cb(obj)
{
var x;
var y;
x = obj.x + obj.dx;
y = obj.y + obj.dy;
if (x + obj.w > obj.constrain.x + obj.constrain.w || x < obj.constrain.x)
{
obj.dx = - obj.dx;
x += 2 * obj.dx;
}
if (y + obj.h > obj.constrain.y + obj.constrain.h || y < obj.constrain.y)
{
obj.dy = - obj.dy;
y += 2 * obj.dy;
}
evas_object_move(obj, x, y);
obj.x = x;
obj.y = y;
return true;
}
Le code de l'animation est plutôt simple : on déplace l'objet à la vitesse dx/dy en gérant juste un rebond/changement de direction lorsqu'on atteint un des bords de l'écran. Vous pouvez assez facilement ajouter d'autres objets et (par exemple) changer la couleur avec evas_object_color_set.
Avec cet exemple, vous avez vu comment fonctionne un canvas stateful et comment on peut réaliser des animations. C'est la base de toute interface, mais cela deviendra vite laborieux de devoir déclarer chaque objet à la main dans le JavaScript. Cela ne permet pas non plus de séparer le design du code et ralentit l'adaptation de l'interface aux plates-formes cibles. C'est pour cela que le projet Enlightenment a développé la technologie Edje qui est là pour adresser tous ces problèmes. Edje mériterait bien plus qu'un article, mais pour cette fois-ci, nous allons juste l'utiliser pour changer le rectangle du fond d'écran et en faire un layout simple. Avant d'utiliser Edje, il ne faut pas oublier de charger son binding à l'aide d'un elx.load("edje"). Ensuite, nous allons créer un fichier elixir.edc qui va contenir notre layout :
collections {
group {
name: "main";
parts {
part {
name: "background";
type: RECT;
mouse_events: 0;
description {
state: "default" 0.0;
rel1.relative: 0.0 0.0;
rel2.relative: 1.0 1.0;
color: 0 0 100 255;
}
}
}
}
}
Le principe d'un fichier Edje est de décrire différents groupes d'objets qui pourront s'instancier dans un unique objet Edje dans le JavaScript. Ici, on ne crée qu'un seul groupe, ne contenant qu'un seul objet, background, qui prend toute la surface de l'objet Edje et aura du bleu foncé comme couleur. Il est à noter qu'il est préférable d'exprimer les coordonnées d'un objet de manière relative dans un fichier Edje. Maintenant que nous avons notre fichier de descriptions, il faut le compiler dans sa forme finale grâce à la commande edje_cc -v elixir.edc elixir.edj. À partir de ce fichier Edje, nous pouvons instancier un objet Edje pour le fond d'écran en remplaçant les appels à evas_object_rectangle_add et evas_object_color_set par :
obj = edje_object_add(evas);
edje_object_file_set(obj, "elixir.edj", "main");
Et voilà un superbe fond bleu ! Nous pouvons un peu amélioré ceci en limitant la zone d'animation avec un deuxième rectangle, un peu à la manière d'une zone de jeu. Pour cela, ajoutons une deuxième part à notre fichier Edje :
part {
name: "constrain";
type: RECT;
mouse_events: 0;
description {
state: "default" 0.0;
rel1.relative: 0.1 0.1;
rel2.relative: 0.7 0.9;
color: 0 0 180 255;
}
}
Il reste à prendre en compte les caractéristiques de cet objet dans notre JavaScript en changeant juste la ligne obj.constrain = { x: 0, y: 0, w: 720, h: 576 } par obj.constrain = edje_object_part_geometry_get(background, "constrain"). Après recompilation du fichier Edje, l'animation se fait maintenant dans un cadre plus limité. On peut facilement modifier les coordonnées de cette zone et voir les résultats du JavaScript varier. Mais il y a quand même un défaut : le point de départ du texte n'est pas forcément dans la zone d'animation, ce qui peut provoquer de petits inconvénients. Pour cela, une légère modification résoudra le problème :
obj.x = obj.constrain.x + 10;
obj.y = obj.constrain.y + 10;
evas_object_move(obj, obj.x, obj.y);
Il reste une dernière notion à aborder dans Edje : les animations. Dans les objets que nous avons décrits jusqu'à présent, nous n'avons spécifié qu'une description par défaut, qui est l'état initial d'une part lors du chargement d'un Edje. En en créant plusieurs, nous pouvons définir les bases d'une animation :
part {
name: "animation";
type: RECT;
mouse_events: 0;
description {
state: "default" 0.0;
rel1.relative: 0.75 0.1;
rel2.relative: 0.85 0.2;
color: 0 0 255 255;
}
description {
state: "right" 0.0;
rel1.relative: 0.85 0.1;
rel2.relative: 0.95 0.2;
color: 255 0 0 255;
}
description {
state: "bottom" 0.0;
rel1.relative: 0.75 0.8;
rel2.relative: 0.85 0.9;
color: 0 255 0 255;
}
}
Il faut maintenant décrire la logique de l'animation grâce à une série de programmes qui seront déclenchés lors de l'affichage de l'objet après une période d'attente aléatoire :
programs {
program {
name: "beginning";
source: "";
signal: "show";
in: 0.1 1.0;
action: STATE_SET "right" 0.0;
transition: LINEAR 0.5;
target: "animation";
after: "nextstep";
}
program {
name: "nextstep";
action: STATE_SET "bottom" 0.0;
transition: SINUSOIDAL 0.8;
target: "animation";
after: "laststep";
}
program {
name: "laststep";
action: STATE_SET "default" 0.0;
transition: ACCELERATE 0.8;
target: "animation";
after: "beginning";
}
}
Le premier programme (beginning) démarrera lorsqu'il recevra le signal show après un temps d'attente entre 0,1 et 1,1 seconde. show est un signal envoyé automatiquement par Edje dès l'affichage de la première frame, mais il est possible d'envoyer des signaux depuis le JavaScript avec edje_object_signal_emit, ce qui permet d'intégrer complètement l'Edje au JavaScript. Nous allons donc ajouter une part dont l'animation sera contrôlée directement par le JavaScript et qui changera d'état entre visible et invisible en fonction de l'appui sur une touche du clavier.
program {
source: "js";
signal: "toggle";
filter: "feedback" "default";
action: STATE_SET "transparent" 0.0;
transition: LINEAR 0.5;
target: "feedback";
after: "back";
}
program {
source: "js";
signal: "toggle";
filter: "feedback" "transparent";
action: STATE_SET "default" 0.0;
transition: LINEAR 0.5;
target: "feedback";
after: "back";
}
program {
name: "back";
action: SIGNAL_EMIT "toggle" "end";
}
On utilise ici deux nouvelles possibilités de Edje. Tout d'abord, chaque programme interceptant les signaux ne sera déclenché que si la part feedback est dans l'état spécifié dans le second paramètre. Cela évite de devoir coder tous les états possibles dans le JavaScript et permet d'ajouter facilement de nouveaux états intermédiaires. Enfin, pour permettre des animations de temps variable, il faut notifier au JavaScript quand elles se terminent. C'est à cela que sert le SIGNAL_EMIT. Les signaux envoyés par Edje peuvent être captés de manière identique dans un programme et en JavaScript. Pour cela, il suffit d'enregistrer une callback pour un signal particulier à la création de l'objet Edje : edje_object_signal_callback_add(obj, "toggle", "end", toggle_end_cb, null) dans notre exemple. La fonction gérant le signal va être très simple dans notre cas :
function toggle_end_cb(data, obj, emission, source)
{
obj.toggling = false;
}
Il manque juste un petit morceau de code pour envoyer le signal depuis le JavaScript dans le handler de clavier :
case "x":
if (!obj.toggling)
{
edje_object_signal_emit(obj, "toggle", "js");
obj.toggling = true;
}
break;
Et voilà, ça fonctionne ! Rien de bien compliqué pour un exemple qui commence à bouger. Il y a encore beaucoup d'autres fonctionnalités à voir dans Edje ; on peut toutes les retrouver dans la documentation à l'adresse http://docs.enlightenment.org/auto/edje/edcref.html. Notre première application est presque finie, il ne reste plus qu'à en faire un seul fichier pour être déployé plus facilement. Pour cela, nous nous appuyons sur le format de fichier de Edje qui utilise Eet et qui peut intégrer plusieurs sections différentes et indépendantes. Vous pouvez voir les sections composant un fichier Edje de la manière suivante :
$ eet -l elixir.edj
edje_source_fontmap
edje_sources
edje/file
edje/collections/0
$
Nous allons donc simplement insérer le fichier JavaScript dans une section utilisée par Elixir à l'aide de la commande eet -i elixir.edj elixir/main elixir.js. Il suffit maintenant d'exécuter elixir elixir.edj ou de déposer le fichier sur le disque dur de la Freebox via son serveur FTP, ou encore de l'envoyer via http://factory.free.fr sur le Freestore. Bien entendu, tout ce code d'exemple et bien plus sont disponibles à l'adresse : http://code.google.com/p/freebox-elixir/source/browse/#svn/trunk/exemples/articles/introduction.