Débuter avec les Enlightenment Foundation Libraries (EFL)

Louis du Verdier

Les EFL (Enlightenment Foundation Libraries) sont des bibliothèques graphiques C (des bindings sont également disponibles pour C++, Python, Perl, JavaScript, Vala et Ruby) développées à côté du projet Enlightenment lui-même. Comparables à Qt ou GTK, elles ont été créées pour réaliser des IU (Interface Utilisateur) fluides, du fait de leur base asynchrone. Ce tutoriel montre par étapes comment prendre en main aisément et rapidement ces bibliothèques.

N'hésitez pas à commenter cet article !
Commentez

- Titre : Débuter avec les Enlightenment Foundation Libraries (EFL)

- Auteur : Louis du Verdier

- Parution : 24 mai 2011

- Mise à jour : 24 mai 2011

- Licence : Copyright ® 2011 Louis du Verdier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Enlightenment et les EFL, qu'est-ce ?

- I-B. Les bibliothèques composants les EFL

- I-B-1. Eina

- I-B-2. Eet

- I-B-3. Evas

- I-B-4. Ecore

- I-B-5. Edje

- I-B-6. Elementary

- I-C. Différences avec des frameworks tels que Qt et GTK

- II. Installation

- II-A. Windows

- II-B. Mac OS

- II-C. Linux

- III. Les forces et les problèmes des EFL

- III-A. Les forces

- III-B. Les faiblesses

- IV. Introduction à Edje

- IV-A. Les fichiers EDC et EDJ

- IV-B. Compilation et vision d'un fichier Edje

- V. Écriture d'un fichier de description Edje

- V-A. Généralités sur les fichiers EDC

- V-B. Points particuliers

- V-B-1. Disposition des éléments

- V-B-2. Les images

- V-B-3. Effets et animations avec Edje

- V-B-4. Aperçu des scripts avec Embryo

- VI. Un minijeu en Edje combiné avec Elementary

- VII. Introduction

- VII-A. Objectifs

- VII-B. Compilation

- VIII. Premiers pas avec Elementary

- VIII-A. Aperçu des widgets d'Elementary

- VIII-B. Code minimal

- VIII-C. Edje et le code C

- VIII-C-1. Utiliser un fichier EDJ dans du code C

- VIII-C-2. Un exemple

- VIII-C-3. Interactions entre C et EDC

- VIII-D. La gestion des évènements

- IX. Quelques widgets

- IX-A. Les listes

- IX-B. Les genlist

- X. Avant-propos

- XI. Installation de Webkit-EFL

- XII. Création du navigateur

- XII-A. Utilisation de Webkit-EFL avec Elementary

- XII-B. Une interface graphique plus esthétique

- XII-C. Les callbacks

- XIII. Ressources externes

- XIII-A. Sites internet

- XIII-B. Articles

- XIII-C. Divers

- XIV. Remerciements

I - Introduction

I-A - Enlightenment et les EFL, qu'est-ce ?

Enlightenment, également connu sous le nom de E (actuellement E17), est un gestionnaire de fenêtrage pour Linux/X11 comprenant une suite complète de bibliothèques pour la réalisation d'interfaces utilisateur aussi fluides qu'esthétiques. Gestionnaire de fenêtrage rapide et léger en publication roulante (rolling release) depuis dix ans, E permet notamment d'effectuer un « thémage » complet.

Les EFL (Enlightenment Foundation Libraries) sont des bibliothèques graphiques C (des bindings sont également disponibles pour C++, Python, Perl, JavaScript et Ruby) développées à côté du projet Enlightenment lui-même. Elles ont été créées pour réaliser des IU (Interface Utilisateur) fluides, du fait de leur base asynchrone et peuvent être utilisées avec des systèmes de bureau tels que GNOME ou KDE. Jugeant que les plateformes fixes commencent à perdre de leur prestige, cédant peu à peu leur place aux plateformes mobiles, les EFL s'orientent vers un support de ces dernières, ayant pour objectif une meilleure portabilité des sources. Ainsi, elles s'orientent principalement vers une utilisation tactile, sans pour autant délaisser l'utilisation bureautique. Elles restent parfaitement adaptées au développement bureautique. Les EFL sont sponsorisées par un certain nombre d'entreprises, dont Samsung et Free. Pour finir, elles constituent le sujet de ce cours ; nous ne nous intéresserons pas au gestionnaire de fenêtrage lui-même.

Exemple « Buttons » d'Elementary_test

Note : les produits d'Enlightenment ne sont pas uniquement conçus pour les plateformes mobiles et pour Linux/X11. En effet, ils sont également portés sous Windows et sous Mac OS.

I-B - Les bibliothèques composants les EFL

Comme leur nom l'indique, les EFL sont constituées de plusieurs bibliothèques ayant chacune leurs spécificités :

- Evas ;

- Eina ;

- Edje ;

- Eet ;

- Ecore ;

- Efreet ;

- E_Dbus ;

- Embryo ;

- Eeze ;

- Elementary ;

- et bien d'autres.

À la suite de ce tutoriel seront brièvement présentées certaines de ces bibliothèques.

I-B-1 - Eina

La bibliothèque Eina correspond à la trousse à outils des EFL, permettant des opérations core, soit la manipulation de types de données tels que les strings buffers, les stringshares (optimisation sur les chaînes de caractères par un moyen de stockage et d'utilisation à travers toute l'application, réduisant considérablement les doublons de la chaîne en mémoire), listes, itérateurs, fichiers, etc.

Consulter la documentation d'Eina.

I-B-2 - Eet

La bibliothèque Eet est un moyen simple et rapide de stockage et de chargement de tout type de données. C'est notamment un moyen de sérialisation. Un exemple d'utilisation peut être trouvé sur le SVN.

Consulter la documentation d'Eet.

I-B-3 - Evas

La bibliothèque Evas est une bibliothèque de gestion d'objets graphiques simples (rectangles, images, textes, polygones et lignes) dont la particularité est d'optimiser le rendu en n'en effectuant un que lorsque des objets ont été modifiés, par souci de consommation de ressources. On n'a donc jamais de rendu partiel, ce qui correspond à un gain de temps et d'énergie important.

Attention à ne pas confondre cette bibliothèque avec la bibliothèque Elementary : Evas n'est pas conçue pour la création de widgets mais pour la création et gestion d'éléments simples dont la liste exhaustive a été donnée plus haut.

À noter qu'Evas est la base de tous les éléments graphiques fournis par les EFL.

Consulter la documentation d'Evas.

I-B-4 - Ecore

La bibliothèque Ecore est une bibliothèque de boucles d'évènements permettant l'utilisation de timers, d'animations, de fonctionnalités réseaux (tcp, udp, http, ftp, etc.) et de bien d'autres choses. L'intérêt fondamental que cette bibliothèque fournit est que rien de ce qu'elle met à disposition n'est bloquant, ce qui simplifie la réalisation d'applications fluides. Par exemple, quand Ecore n'a plus aucune tâche à effectuer, il rentre en mode Idle, ce qui évite une consommation inutile de ressources.

Consulter la documentation d'Ecore.

I-B-5 - Edje

La bibliothèque Edje est sans doute la bibliothèque la plus importante des EFL, dans le sens où toute application utilisant les EFL va y recourir à un moment ou à un autre. Elle correspond à un moyen de concevoir des applications en séparant la partie design de la partie concernant la logique et le code. En effet, la création d'une interface graphique avec Edje ne nécessite pas la moindre ligne de code C, ce qui permet notamment aux développeurs et aux designers de travailler ensemble sur un projet. Le designer mettrait en place la partie Edje de l'application tandis que le développeur pourrait se concentrer exclusivement sur le code C, ce qui correspondrait à un gain non négligeable de temps. Quel développeur n'a jamais eu de problème avec son chef d'équipe comme quoi le design de l'application ne collerait pas avec le travail initial des designers ? Grâce à la bibliothèque Edje, c'en est fini de ce type de problèmes.

Le codage avec la bibliothèque Edje se fait un peu comme en CSS avec une association propriété: valeur. Exemple du traditionnel Hello world :

collections {
group {
name: "interface";
parts {
part {
name: "text";
type: TEXT;
description {
state: "default" 0.0;
color: 255 255 255 255;
text {
font: "Sans-serif";
text: "Hello world !";
size: 18;
}
}
}
}
}
}

Et le rendu :

La bibliothèque Edje utilise plusieurs bibliothèques fournies par les EFL :

- Eet pour la partie concernant le stockage de données ;

- Evas pour la partie graphique des thèmes écrits avec Edje ;

- Ecore pour gérer la boucle d'évènements.

Cette bibliothèque sera bien plus amplement détaillée par la suite ; cette partie consiste uniquement en un aperçu de la chose.

Consulter la documentation d'Edje.

I-B-6 - Elementary

La bibliothèque Elementary met à disposition un grand nombre de widgets hautement personnalisables. Certains widgets d'Elementary, nommés « widgets performants », ont initialement été conçus pour être utilisés sur des plateformes mobiles, nécessitant une grande fluidité.

Pour illustrer, on peut prendre un exemple qui a poussé les développeurs à mettre en place les genlists : on dispose d'une liste de plusieurs centaines d'éléments graphiques. Parmi ces éléments, on souhaite uniquement en afficher une vingtaine en même temps à l'écran. Dans ce cas de figure, n'est-ce pas être inconscient que de vouloir prendre de la mémoire pour un tel nombre ? Le principe est de stocker les items dans un arbre pour éviter d'avoir à parcourir trop d'éléments. Les widgets performants vont être créés lors de l'affichage, et détruits lorsqu'ils ne seront plus affichés (à noter que derrière cela, on a tout de même un principe de cache).

Voici une capture d'écran de l'exemple « Genlist Group » d'Elementary_test qui permet de visualiser une genlist ordinaire :

Consulter la documentation d'Elementary.

I-C - Différences avec des frameworks tels que Qt et GTK

Il est fort possible que vous vous interrogiez sur la différence qu'ont les EFL avec les gros frameworks d'aujourd'hui tels que Qt et GTK. Certes, ces trois frameworks permettent plus ou moins d'effectuer la même chose, mais EFL ne tente pas de suivre ces deux monstres. Il préfère se démarquer d'eux sur plusieurs points détaillés ci-dessous de manière non exhaustive.

Comme Qt et GTK, les EFL fournissent avec leur bibliothèque Elementary des widgets (comme des boutons, cases à cocher, etc.). Il faut voir les widgets des EFL comme des outils et non comme de simples éléments de fenêtre, à la différence de ce que proposent Qt et GTK. Avec Qt, on voit couramment des widgets conteneurs présents dans d'autres conteneurs (exemple : un QPushButton présent dans un QWidget, lui-même englobé dans un QMainWindow), soit des arbres de widgets s'englobant les uns les autres. Avec EFL, les widgets sont utilisés comme des « briques » combinées avec la bibliothèque Edje. Les widgets d'Elementary sont hautement personnalisables, donnant la possibilité de modifier l'échelle, de changer diverses propriétés. Une autre différence notable avec les frameworks concurrents est sans doute que les EFL présentent pour leurs widgets une zone tactile différenciée de la zone d'affichage, peu importe le fait que le tactile soit utilisé ou non dans l'application.

Il est logique de constater une grande similitude entre Qt Quick (de Qt) et du code de thèmes avec Edje (à noter qu'Edje existait avant que Qt Quick ne fasse son entrée - c'est par ignorance que bien des gens ont annoncé avoir attendu le concept utilisé par QML). Il existe toutefois de nombreux points les dissociant. Tout d'abord, les fichiers de description d'Edje sont compilés avant de pouvoir être observés, à la différence des fichiers QML. Avec Edje, il est possible de modifier entièrement le thème d'une application sans avoir besoin de modifier le code C. On peut passer d'une architecture en liste à une architecture tout autre d'une simple modification du fichier Edje. Quant à lui, Qt Quick va plutôt se baser sur un moteur JavaScript. Glade, l'équivalent de Qt Designer, écrit ses fichiers en XML. Sur ce point, Edje et QML sont donc plus avancés dans le sens où un fichier Edje permet de réaliser le thème de l'application en plus de disposer les éléments dans l'interface utilisateur.

Tel que vous avez dû l'avoir compris, les EFL s'associent au mot « optimisation » par les moyens dont sont réalisés leurs composants et par leur base asynchrone. Même si certaines optimisations peuvent vous paraître peu intéressantes, voire même complètement inutiles, les EFL ont prouvé par cela leur fluidité. Généralement, une application développée avec les EFL, sauf problème de conception de son coeur, ne devrait jamais geler. C'est une grande différence par rapport aux frameworks du type de Qt et de GTK.

II - Installation

II-A - Windows

Si vous souhaitez utiliser les EFL sous Windows, téléchargez cet installeur, lancez-le et laissez-vous guider. Vous pouvez également vous référer à cette page pour choisir l'installeur adapté à vos besoins.

II-B - Mac OS

Si vous préférez utiliser les EFL sous Mac OS, vous devez être en possession des programmes Fink et XCode2. L'installation des EFL sous Mac OS nécessite des dépendances, ce qui explique l'intérêt de Fink :

fink install xorg
fink install m4
fink install autoconf2.5
fink install automake1.9
fink install libtool14
fink install gettext-tools
fink install pkgconfig
fink install libjpeg
fink install libpng3
fink install dbus-dev

Liste en provenance de http://trac.enlightenment.org/e/wiki/Installation_MacOSX. Vous pouvez alors télécharger les sources :

svn co http://svn.enlightenment.org/svn/e/trunk/eina eina-svn
svn co http://svn.enlightenment.org/svn/e/trunk/eet eet-svn
svn co http://svn.enlightenment.org/svn/e/trunk/evas evas-svn
svn co http://svn.enlightenment.org/svn/e/trunk/ecore ecore-svn
svn co http://svn.enlightenment.org/svn/e/trunk/efreet efreet-svn
svn co http://svn.enlightenment.org/svn/e/trunk/embryo embryo-svn
svn co http://svn.enlightenment.org/svn/e/trunk/edje edje-svn
svn co http://svn.enlightenment.org/svn/e/trunk/e_dbus e_dbus-svn
svn co http://svn.enlightenment.org/svn/e/trunk/elementary elementary-svn

Cela peut prendre un certain temps. La dernière étape est la compilation et l'installation. Pour cela, il s'agit d'entrer dans chaque répertoire (dans l'ordre donné ci-dessus) et d'exécuter les commandes :

./autogen.sh
make
make install && ldconfig

Par exemple, pour eina :

cd /chemin/vers/eina/
./autogen.sh
make
make install && ldconfig

Note : la page mentionnée ci-dessus parle de la nécessité d'un hack dans e17/libs/eet/src/lib/eet_lib.c pour les versions de Max OS X précédant la version Leopard (10.5), donc les versions Tiger (10.4) et précédentes. Ce hack correspond à ajouter les deux lignes suivantes aux alentours de la ligne 21.

typedef unsigned int uint8_t;
typedef unsigned char uint32_t;

II-C - Linux

Pour les distributions telles qu'Ubuntu, une solution est d'utiliser le script easy_e17.sh pour récupérer les sources depuis le SVN puis les compiler. La méthode ne marche pas toujours du fait que des changements dans l'organisation du dépôt cassent de temps en temps le script. Quelques modifications sont donc parfois requises.

sudo apt-get install xterm make gcc bison flex subversion automake autoconf autotools-dev autoconf-archive libtool \
gettext libpam0g-dev libfreetype6-dev libpng12-dev zlib1g-dev libjpeg62-dev libtiff4-dev libungif4-dev librsvg2-dev \
libx11-dev libxcursor-dev libxrender-dev libxrandr-dev libxfixes-dev libxdamage-dev libxcomposite-dev libxss-dev \
libxp-dev libxext-dev libxinerama-dev libxft-dev libxfont-dev libxi-dev libxv-dev libxkbfile-dev libxres-dev \
libxtst-dev libltdl7-dev libglu1-xorg-dev libglut3-dev xserver-xephyr libdbus-1-dev cvs subversion mercurial \
liblua5.1-dev libavformat-dev mplayer libxine-dev libxml2-dev libcurl4-openssl-dev wget libexif-dev libsqlite3-dev \
libxine1-all-plugins libxine1-ffmpeg libudev-dev liblua5.1-dev doxygen

Liste initialement en provenance du site http://dev.enlightenment.fr/~captainigloo/ ; doxygen est optionnel. Une fois ces paquets installés, on récupère easy_e17.sh :

wget http://omicron.homeip.net/projects/easy_e17/easy_e17.sh

À l'heure actuelle, Elementary n'est pas sortie en version stable. Ainsi, cette bibliothèque n'est pas installée par défaut. Remédions-y : éditez le fichier easy_e17.sh et ajoutez en dernière position « elementary » dans efl_basic :

efl_basic="eina eet evas ecore efreet eio eeze e_dbus embryo edje elementary"

Une fois cela fait, on peut lancer le script :

chmod +x easy_e17.sh
./easy_e17.sh -i

Cela peut prendre un certain temps. L'installation du module exchange est susceptible de bloquer easy_e17.sh. Si c'est le cas chez vous, retirez-le de la liste des modules à installer puis relancez le script. Vous pourrez toujours l'ajouter manuellement par la suite.

Par défaut, le script va récupérer les sources depuis le SVN et installer les EFL dans /opt/e17. Les sources seront disponibles dans $HOME/e17_src (par exemple, /home/username/e17_src).

Une fois l'exécution du script terminée, le script va suggérer l'ajout de plusieurs variables d'environnement. Pour une installation par défaut, les variables suivantes sont nécessaires :

export PATH=/opt/e17/bin:$PATH
export LD_LIBRARY_PATH=/opt/e17/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=/opt/e17/lib/pkgconfig:$PKG_CONFIG_PATH

Les utilisateurs d'Archlinux, la commande suivante suffit à faire l'installation :

pacman -S e-svn

Toutefois, il faudra également installer Elementary qui n'est pas inclus par défaut.

Pour les distributions ne permettant pas ce type d'installation, se référer à cette page du site officiel.

III - Les forces et les problèmes des EFL

III-A - Les forces

Les EFL sont extrêmement pratiques pour le développement d'applications fluides et esthétiques. Le tactile est très bien géré, d'où le moyen de faire tourner des logiciels faits avec les EFL sous de multiples types de plateformes, fixes ou mobiles. Un logiciel, peu importe la plateforme sur laquelle il doit s'exécuter, n'aura généralement jamais de souci de ralentissement, sauf souci de conception. En effet, les solutions d'optimisation mises à disposition sont nombreuses car initialement non négligées.

Comme autre force, on peut noter que le thémage entièrement personnalisable d'une application développée avec les EFL est extrêmement simple et offre de nombreuses possibilités, comme la réalisation d'animations et d'effets sans avoir besoin de toucher à une seule ligne de C, ce qui permet aux développeurs et aux designers de travailler ensemble.

Enfin, les EFL disposent de bien des modules conçus pour la réalisation de tout type d'application, ce qui permet à leurs utilisateurs de ne pas avoir besoin d'utiliser des bibliothèques externes pour combler des manques. Multimédia, réseau, bureautique et bien d'autres choses sont déjà englobés par les EFL, et la liste ne fait que s'agrandir !

III-B - Les faiblesses

D'un point de vue de la communication, Enlightenment souffre de quelques problèmes. La documentation est assez limitée, les moteurs de recherche ne trouvent pas grand-chose sur les EFL (il faut souvent passer par le mot-clé Enlightenment pour avoir des résultats pertinents). Ces problèmes de communication engendrent le fait qu'il y ait un manque de tutoriels et par exemple que l'installation des EFL ne soit pas toujours évidente.

La bibliothèque Elementary, bien que « bien garnie », manque de widgets, et le non tactile n'est pas tellement une priorité dans le développement.

Pour finir, on peut ajouter qu'il arrive que les développeurs réécrivent des éléments afin de les améliorer, mais retirent occasionnellement des possibilités pour les remplacer par d'autres. L'adaptation n'est pas toujours évidente. Quoi qu'il en soit, il faut noter que les changements majeurs prévus pour E17 ont été repoussés à E18. Ce problème d'adaptation ne devrait ainsi pas se poser avant un certain temps.

IV - Introduction à Edje

La description faite dans la partie précédente est suffisante pour comprendre le rôle qu'a Edje dans le développement d'une application avec les EFL. Pour résumer, le principe des EFL est de séparer le design et la partie applicative. Grossièrement dit, on réalise son interface avec Edje sans avoir à écrire la moindre ligne de C puis on conçoit le reste avec Elementary et les autres bibliothèques.

IV-A - Les fichiers EDC et EDJ

La plupart des tutoriels disponibles sur la Toile entendent par Edje deux choses et ce cours n'échappera pas à la règle :

- les fichiers de description EDC (Edje Data Collection) où est écrite la description du thème de l'application ;

- les fichiers EDJ obtenus lors de la compilation des fichiers EDC.

Note : c'est la bibliothèque libedje.so qui relie les fichiers EDJ et le code C.

IV-B - Compilation et vision d'un fichier Edje

Dans cette partie, je vais partir du principe que vous avez un fichier description.edc dans un répertoire donné contenant une description de thème quelconque. Si vous souhaitez suivre les étapes de la compilation mais que vous n'avez pas de fichier .edc en attente de compilation, vous pouvez par exemple utiliser l'exemple Hello world donné précédemment :

collections {
group {
name: "interface";
parts {
part {
name: "text";
type: TEXT;
description {
state: "default" 0.0;
color: 255 255 255 255;
text {
font: "Sans-serif";
text: "Hello world !";
size: 18;
}
}
}
}
}
}

La compilation d'un fichier Edje est relativement simple. En réalité, cela se limite à une seule commande, qui est la suivante :

edje_cc description.edc

Avec cela, si aucune erreur n'est détectée, un fichier description.edj a dû faire son apparition dans le même répertoire que le fichier description.edc. Pour visionner un fichier EDJ, on utilise généralement edje_player :

edje_player description.edj

Cette ligne exécutée, votre interface devrait s'afficher sous vos yeux.

Il est bon de savoir que divers arguments peuvent être passés à l'edje_player et à l'edje_cc pour altérer des choses et d'autres. Pour en connaître la liste, entrez :

edje_cc --help

Pour edje_cc. Et pour edje_player :

edje_player --help

V - Écriture d'un fichier de description Edje

V-A - Généralités sur les fichiers EDC

Les fichiers de description Edje, donc les fichiers EDC, font parfois penser à une structure en C ou bien à du code CSS dans le sens où ils correspondent à des définitions de propriétés. On obtient des arbres de définitions de la sorte :

block
{
property: value;

block
{
...
}

...
}

Étudions notre fichier Hello_world.edc contenant le code de l'exemple Hello world.

collections { ... }

Le mot-clé « collections » correspond à la définition d'un ensemble de groupes définissant le thème de l'application.

group {
name: "group_name";
...
}

Ce mot-clé-ci correspond quant à lui à la définition d'un groupe, ou objet, composant le thème. Chaque groupe a un nom unique, défini par le mot-clé « name », et peut contenir divers éléments, comme des « parts » ou des scripts (cette notion sera abordée plus loin). Si un groupe du même nom qu'un autre est défini, il va écraser complètement son prédécesseur, d'où l'unicité de la valeur du mot-clé « name ».

parts {
part {
}
...
}

Les « parts » sont un ensemble de « part », chacune d'elles représentant les éléments graphiques présents dans un thème, donc un bouton, label, etc.

part {
name: "text";
type: TEXT;
...
}

Chaque « part » possède au minimum un nom (name) et un type. Ce dernier peut correspondre à l'un des types suivants :

- RECT : les rectangles ;

- TEXT : les textes ;

- IMAGE : les images ;

- SWALLOW : les conteneurs ;

- TEXTBLOCK : les blocs de texte ;

- GROUP : les groupes ;

- BOX : les boîtes ;

- TABLE : les tableaux ;

- EXTERNAL : les éléments externes.

Dans le cas de l'exemple Hello world, on a un TEXT. Selon le type, la description de chaque part va être menée à varier. Quoi qu'il en soit, les données des propriétés du thème ne sont pas condamnées à garder la valeur qu'on leur a assignée dans le fichier EDC. En effet, le code C généralement situé en parallèle - il est rare de trouver des applications réalisées entièrement avec Edje - fournit bien des moyens de changer leur valeur lors de l'exécution.

Pour plus d'informations concernant les propriétés mises à disposition, vous pouvez consulter cette page de la documentation qui en donne la liste exhaustive. Nous allons tout de même nous pencher par la suite sur plusieurs de ces éléments afin d'illustrer.

Notez que l'on peut commenter les fichiers EDC avec des commentaires tels qu'on les écrit en C :

// Ceci est un commentaire
/* Ceci en est un autre */

Pour ne pas se perdre dans son travail, tout développeur a l'habitude de créer de multiples fichiers pour aérer son code. Pour cela, Edje permet l'utilisation de directives telles que la directive #include, dont voici un exemple :

collections {
#include "interface.edc"
...
}

V-B - Points particuliers

Maintenant que nous avons passé en revue une description Edje, nous allons pouvoir entrer dans les détails.

V-B-1 - Disposition des éléments

Tout d'abord, un point sur l'affichage à l'écran d'un thème : les éléments sont affichés dans l'ordre de leur écriture dans le fichier Edje. Cela signifie que s'il y a lieu de superposition d'éléments, l'élément défini en dernier sera situé « au-dessus » des autres.

Puisque nous parlons de superposition, parlons de disposition : comment faire pour afficher un élément à une position donnée ? Comment faire pour que notre élément s'agrandisse et se rétrécisse selon la taille de la fenêtre ? Comment disposer un élément relativement à un autre ? Le positionnement et le comportement face aux redimensionnements forment sans aucun doute la partie la plus importante d'une interface utilisateur. Edje nous permet par le biais des propriétés des descriptions de la gérer, par de divers moyens.

Dans un premier temps, voyons l'alignement avec les balises « rel1 » et « rel2 ». La balise rel1 correspond au point situé en haut à gauche de l'élément à placer, et rel2 au point en bas à droite. Pour un placement relatif d'un élément à 50 % de l'axe des abscisses et à 0 % de l'axe des ordonnées par rapport à rel1, on va utiliser :

rel1 {
relative: 0.5 0.0;
}

Comme vous avez dû le comprendre, 0 % d'un axe va correspondre à la valeur 0 tandis que 100 % va correspondre à 1. Pour un placement absolu à 200x300px de rel1, on va utiliser :

rel1 {
offset: 200 300;
}

Qui est l'équivalent logique de :

rel1 {
relative: 0 0;
offset: 200 300;
}

Tout en gardant en tête que les valeurs d'offset peuvent être négatives. Il est possible de combiner les propriétés « relative » et « offset ». Cela correspond tout simplement à un positionnement absolu à partir d'un point relatif.

On peut d'ailleurs aussi faire un placement relatif à un autre élément avec l'aide de la propriété « to » :

rel1 {
...
to: "other part's name";
}

Qui est l'équivalent de :

rel1 {
...
to_x: "other part's name";
to_y: "other part's name";
}

Les propriétés « to_x » et « to_y » veulent respectivement dire que l'on souhaite placer notre élément relativement à la position des abscisses (to_x) ou bien des ordonnées (to_y). Utiliser les deux n'est bien entendu pas obligatoire, on peut utiliser l'un et/ou l'autre, ou tout simplement ne pas les utiliser du tout.

Edje laisse au développeur la possibilité de positionner un élément par rapport à la place qui lui est attribuée avec align. Par défaut, lorsqu'un élément est plus petit que l'espace dont il dispose, il va s'y placer au centre (align: 0.5 0.5). Exemple pour placer un élément en haut à gauche de l'espace lui étant alloué :

align: 0 0; // 0 pour 0 % et 1 pour 100 %

En ce qui concerne les propriétés liées au dimensionnement, on a le choix entre plusieurs éléments. Ci-dessous se trouvent trois des plus importants, en provenance de la documentation :

- fixed [longueur, 0 ou 1] [hauteur, 0 ou 1] : fixe ou non la longueur et la largeur de l'élément ;

- min [longueur, px] [hauteur, px] : fixe une taille minimale à l'élément (astuce : -1 pour ne pas fixer, 1 pour laisser Edje calculer la taille) ;

- max [longueur, px] [hauteur, px] : fixe une taille maximale à l'élément.

Pour le comportement face à un redimensionnement, on va plutôt utiliser aspect, le ratio entre la longueur et la hauteur, et aspect_preference, le comportement de redimensionnement (HORIZONTAL, VERTICAL ou BOTH). Ces deux propriétés sont liées. Exemple d'un rectangle se redimensionnant sur les deux axes tout en conservant ses proportions initiales :

collections {
group {
name: "interface";
parts {
part {
name: "button";
type: RECT;
description {
state: "default" 0.0;
aspect: 1.0 1.0;
aspect_preference: BOTH;
}
}
}
}
}

V-B-2 - Les images

La documentation est suffisante pour comprendre comment définir un certain nombre d'éléments. Il peut tout de même s'avérer intéressant de s'arrêter sur les images afin de parler de certaines fonctionnalités en plus du type lui-même.

collections {
group {
name: "interface" ;
images {
image: "image_normal.png" COMP;
}
parts {
part {
name: "image";
type: IMAGE;
description {
state: "default" 0.0;
aspect: 1 1;
aspect_preference: BOTH;
image {
normal: "image_normal.png";
}
}
}
}
}
}

Cela affiche l'image « image_normal.png » s'adaptant à l'écran tout en conservant son ratio d'origine.

#define IMAGE_NORMAL "image_normal.png"
collections {
group {
name: "interface";
images {
image: IMAGE_NORMAL COMP;
}
parts {
part {
name: "image";
type: IMAGE;
description {
state: "default" 0.0;
image {
normal: IMAGE_NORMAL;
}
}
}
}
}
}

Le code identique avec l'utilisation d'une macro IMAGE_NORMAL, dans l'unique but d'informer que les macros existent et s'écrivent tout comme en C.

Afin d'utiliser une image, il est nécessaire de passer au préalable par images pour la définir :

images {
image: "image_normal.png" COMP;
}

Un certain nombre de formats sont supportés par Edje (JPEG, PNG, etc.), qui permet également une compression de l'objet image. COMP, utilisé dans l'exemple, correspond à une compression sans perte, RAW à aucune compression, LOSSY[x] à une compression de qualité x (x variant entre 0 et 100), comparable à la qualité JPEG, et USER à une image non intégrée au Edje, lue depuis le disque. Il est souvent préférable d'utiliser COMP plutôt que USER dans le sens où il est plus pratique de ne pas avoir à la charger depuis le disque.

Attention : dans le cas de la qualité RAW, il reste nécessaire de faire attention à la taille finale que peut avoir le fichier binaire.

Puisqu'il est question de compression, et donc de qualité, une question se pose : nous avons une image donnée que l'utilisateur peut redimensionner. Comment faire pour qu'elle ne soit pas floue pour les grands écrans ? Une réponse possible serait de joindre une grande image plutôt qu'une image de qualité moyenne. Dans ce cas, la question inverse se poserait : comment faire pour qu'il n'y ait pas de baisse de la qualité pour les petits écrans ? Edje propose une solution : set.

collections {
group {
name: "interface";
images {
set {
name: "my_image";
image {
image: "image-32.png" COMP;
size: 0 0 32 32;
}
image {
image: "image-64.png" COMP;
size: 33 33 64 64;
}
image {
image: "image-128.png" COMP;
size: 65 65 128 128;
}
}
}
parts {
part {
name: "image";
type: IMAGE;
description {
state: "default" 0.0;
aspect: 1 1;
aspect_preference: BOTH;
image {
normal: "my_image";
}
}
}
}
}
}

Dans ce code, on définit un set d'images de différentes tailles, avec « my_image » comme valeur de la propriété « name ». Au moment de la définition de l'élément IMAGE, on va utiliser la valeur de la propriété « name » de notre set :

image {
normal: "my_image";
}

De là, Edje va déterminer quelle image choisir selon la taille de la fenêtre à l'exécution à l'aide des propriétés « size » de chaque image du set. Dans le cas où, lors de l'exécution, la taille de la fenêtre ne serait pas incluse dans l'intervalle des propriétés « size », l'image ayant un min-size à 0 sera utilisée jusqu'à la nécessité d'un changement menant à l'utilisation d'une autre image.

La solution des set d'images est très pratique pour éviter d'avoir à passer par une solution bien plus lourde telle que l'utilisation de SVG.

V-B-3 - Effets et animations avec Edje

Tout ce qu'on a vu jusque-là est certes assez instructif, mais n'est pas vraiment impressionnant. Cette partie va donc donner l'occasion d'aborder un point que vous attendiez : les animations, les effets, et donc la gestion des évènements, tout cela dans un fichier de description Edje sans la moindre ligne de C.

On appelle « programme » une action effectuée par Edje, généralement en réponse à un signal (un clic de la souris, par exemple). Ces programs peuvent être intégrés à un group et effectuer diverses choses sur divers éléments, comme changer l'état d'un ou plusieurs éléments.

Voyons un exemple simple. On souhaite faire en sorte qu'un élément TEXT affiché à l'écran laisse sa place à un élément IMAGE lorsqu'on clique dessus, et inversement. Ainsi, nous allons devoir définir deux « programmes » : le premier correspondra à la réaction à un clic sur l'élément TEXT et le second à un clic sur l'élément IMAGE.

La méthode n'est pas très compliquée. Chacun des deux éléments, TEXT et IMAGE, va être doté de deux descriptions : une pour l'état par défaut et une autre pour l'état après un clic. Lorsque l'utilisateur va cliquer sur l'élément TEXT, le programme associé à cette interaction va changer l'état des deux éléments à l'état « après un clic » afin de repositionner l'élément TEXT hors de la fenêtre et de repositionner l'élément IMAGE à l'intérieur. Pour un clic sur l'élément IMAGE, l'action est réciproque.

collections {
group {
name: "interface";
images {
image: "image.png" COMP;
}
parts {
part { // Notre élément TEXT
name: "text";
type: TEXT;
mouse_events: 1;
description { // État par défaut
state: "default" 0.0;
text {
text: "Cliquez ici !";
size: 12;
font: "Sans";
}
}
description { // État clicked
state: "clicked" 0.0;
inherit: "default" 0.0;
rel1.offset: -1000 0;
}
}
part { // Notre élément IMAGE
name: "image";
type: IMAGE;
mouse_events: 1;
description { // État par défaut
state: "default" 0.0;
max: 128 128;
aspect: 1 1;
aspect_preference: BOTH;
image.normal: "image.png";
rel1.offset: -1000 0;
}
description { // État clicked
state: "clicked" 0.0;
inherit: "default" 0.0;
rel1.offset: 0 0;
}
}
}
programs {
program { // Programme déclenché par le TEXT
name: "text_clicked";
signal: "mouse,clicked,1";
source: "text";
action: STATE_SET "clicked" 0.0;
target: "text";
target: "image";
}
program { // Programme déclenché par l'IMAGE
name: "image_clicked";
signal: "mouse,clicked,1";
source: "image";
action: STATE_SET "default" 0.0;
target: "text";
target: "image";
}
}
}
}

Avec la propriété mouse_events mise à 0, on dit qu'on n'accepte pas les événements de la souris, tandis qu'avec cette même propriété mise à 1, on dit qu'on les accepte. C'est pour cela que nos deux éléments ont la propriété définie à 1 :

mouse_events: 1; // On accepte les évènements de la souris

Note : par défaut, la propriété mouse_events est déjà à 1. L'intérêt de la définir à 1 dans le code n'a donc qu'une utilité, vous avertir que cela existe.

L'utilisation de la propriété inherit sur les descriptions clicked de nos éléments avec pour argument default est un moyen d'éviter de devoir écrire deux fois la même chose. Cela permet de dire à Edje « cette description a les mêmes propriétés et valeurs que la description default ». À partir de là, il ne nous reste plus qu'à placer les propriétés divergeant de l'état par défaut :

description { // Description de
state: "clicked" 0.0; // État clicked
inherit: "default" 0.0; // Hérite de default
rel1.offset: 0 0; // On change uniquement le placement
}

Dans chacun de nos programmes, nous définissons :

- name, le nom du programme ;

- signal, le signal qui va déclencher le programme ;

- source, le nom de l'élément source du signal (au plus, une seule source par programme) ;

- action, l'action à effectuer lors du déclenchement du programme ;

- target, la cible de l'action (il peut y avoir plusieurs cibles).

program { // Programme déclenché par le TEXT
name: "text_clicked";
signal: "mouse,clicked,1"; // Appelé par un clic de la souris
source: "text"; // En provenance de l'élément TEXT
action: STATE_SET "clicked" 0.0; // Change l'état à clicked
target: "text"; // ... pour le TEXT
target: "image"; // ... et pour l'IMAGE
}

Si vous testez ce code, vous vous rendez compte que l'image apparaît immédiatement après un clic sur l'élément TEXT, qui va quant à lui disparaître. Avec la propriété transition de chaque programme, on va pouvoir passer d'un simple effet à une petite animation, et cela en une seule ligne.

La propriété transition a la syntaxe suivante :

transition: TYPE paramètre;

Avec TYPE faisant partie des types de transition suivants :

- NONE ;

- LINEAR ;

- SINUSOIDAL ;

- ACCELERATE ;

- DECELERATE ;

Et paramètre correspondant au temps de transition en secondes. Plus la valeur passée sera élevée, plus l'animation sera longue.

Par exemple, si on met pour chacun de nos programmes :

transition: ACCELERATE 1;

On pourra voir une animation lors d'un clic sur l'image ou le texte. L'élément présent va sortir de l'écran en l'espace d'une seconde avec une vitesse s'accélérant, puis l'élément non présent va entrer dans l'écran par un effet identique.

Note : les actions des programmes ne se limitent pas aux simples changements d'état. À l'heure où j'écris cet article, un programme peut également envoyer un signal pouvant par exemple être capté et géré par le code C (nous verrons cela par la suite), ainsi que stopper l'exécution d'un autre programme.

Consulter la documentation des programmes Edje.

V-B-4 - Aperçu des scripts avec Embryo

Notez avant tout que l'on peut aussi bien écrire des scripts Edje avec Embryo qu'avec du code LUA.

Edje est basé sur Evas pour la gestion des éléments graphiques, sur Ecore pour la gestion de tout ce qui concerne la boucle évènementielle et Eet pour tout ce qui concerne les opérations core. Il peut également utiliser Embryo pour utiliser les scripts dans une description de thème. Les scripts Edje fonctionnent en continuité avec les programmes. En réalité, ce sont les programmes qui les appellent par le biais de leur propriété script.

Afin de donner un aperçu des scripts avec Embryo dans un fichier de description Edje, on va remodeler l'exemple précédent pour appeler les programmes non pas avec la méthode énoncée plus haut, mais plutôt avec un script.

collections {
group {
images {
image: "image.png" COMP;
}
name: "interface";
script { // Notre script
public callPrograms() {
new buf[16];
new Float:val;
get_state(PART:"text", buf, sizeof(buf), val); // Récupération de l'état dans buf
if(!strcmp(buf, "default")) { // Si l'état de notre TEXT est default
run_program(PROGRAM:"hide_text"); // Appel du programme hide_text
run_program(PROGRAM:"show_image"); // Appel du programme show_image
}
// Sinon :
else {
run_program(PROGRAM:"show_text"); // Appel du programme show_text
run_program(PROGRAM:"hide_image"); // Appel du programme hide_image
}
}
}
parts {
part {
name: "text";
type: TEXT;
mouse_events: 0;
description {
state: "default" 0.0;
text {
text: "Cliquez ici !";
size: 12;
font: "Sans";
}
}
description {
state: "hidden" 0.0;
inherit: "default" 0.0;
rel1.offset: -1000 0;
}
}
part {
name: "image";
type: IMAGE;
mouse_events: 0;
description {
state: "default" 0.0;
max: 128 128;
aspect: 1 1;
aspect_preference: BOTH;
image.normal: "image.png";
rel1.offset: -1000 0;
}
description {
state: "shown" 0.0;
inherit: "default" 0.0;
rel1.offset: 0 0;
}
}
part {
name: "event_handler";
type: RECT;
mouse_events: 1;
description {
state: "default" 0.0;
color: 0 0 0 0;
}
}
}
programs {
program {
name: "show_text";
action: STATE_SET "default" 0.0;
transition: ACCELERATE 1;
target: "text";
}
program {
name: "hide_text";
action: STATE_SET "hidden" 0.0;
transition: ACCELERATE 1;
target: "text";
}
program {
name: "hide_image";
action: STATE_SET "default" 0.0;
transition: ACCELERATE 1;
target: "image";
}
program {
name: "show_image";
action: STATE_SET "shown" 0.0;
transition: ACCELERATE 1;
target: "image";
}
program {
name: "event_clicked";
signal: "mouse,clicked,1";
source: "event_handler";
script {
callPrograms();
}
}
}
}
}

Pour simplifier la gestion des signaux, on a utilisé un élément RECT invisible dont le rôle est de récupérer les signaux qui nous intéressent - ici, le signal clicked en provenance d'un clic de souris. À savoir que cette manière de récupérer les signaux est très utilisée. Par conséquent, nos éléments TEXT et IMAGE n'ont pas besoin de récupérer les évènements de la souris :

mouse_events: 0;

On peut noter que le code des scripts ressemble furieusement à du code C tel qu'on a l'habitude de l'écrire. Toutefois, la liste des fonctions pouvant être utilisées est assez restreinte. La documentation d'Embryo étant également assez limitée au moment où l'article est en train d'être écrit, la solution pour accéder à une liste exhaustive de ces fonctions est d'aller directement consulter les sources, donc le fichier edje_embryo.c.

Pour ce type d'usage, il n'est pas nécessaire d'utiliser un script. Par contre, dès qu'il s'agit de vérifier un état avec get_state, de définir le texte d'un élément avec set_text, etc. sans pourtant vouloir passer par du code C, les scripts forment une solution idéale.

VI - Un minijeu en Edje combiné avec Elementary

Afin de valider les connaissances acquises, d'utiliser les scripts Embryo de manière plus concrète et d'introduire le type d'élément EXTERNAL, la réalisation d'un minijeu est une idée judicieuse. Le jeu qui sera codé est un jeu de « Plus ou Moins », qui consiste à générer un nombre aléatoire et de le faire deviner à l'utilisateur, en lui donnant les indications plus ou moins.

Pour annoncer que l'on va utiliser Elementary et utiliser certains de ses widgets directement dans un fichier de description Edje, on doit passer par une déclaration d'externals.

externals { // On précise qu'on va utiliser Elementary :
external: "elm";
}

À partir de là, on peut créer des parts de type EXTERNAL, comme un bouton :

part { // Déclaration d'un bouton Elementary
name: "button";
type: EXTERNAL;
source: "elm/button";
description {
state: "default" 0.0;
params {
string: "label" "Cliquez sur moi !";
}
}
}

Les scripts Embryo peuvent alors gérer ces éléments par le biais des fonctions spécialement conçues pour les éléments du type étudié. Ces fonctions sont de la forme external_param_set_**** et external_param_get_****. Les arguments qu'elles prennent et les valeurs de retour peuvent être trouvées dans les sources, donc dans le fichier edje_embryo.c, comme le reste des fonctions d'Embryo disponibles dans Edje. Par exemple, pour récupérer le nombre affiché dans un widget spinner (le widget affichant 16 dans la capture d'écran, et possédant des boutons pour altérer cette valeur), on va utiliser le code suivant :

new Float:num = external_param_get_float(PART:"form", "value");

Avec l'élément form, un widget spinner d'Elementary, et value le paramètre du spinner qui contient la valeur indiquée dans le widget. Pour savoir quels paramètres on peut récupérer, l'idéal est d'aller dans les sources, donc dans {Chemin vers les sources des EFL}/elementary/src/edje_externals/. Dans ce dossier, vous trouverez des fichiers nommés elm_{nom du widget}.c qui contiennent la liste des paramètres utilisables ainsi que les types. Si on ouvre elm_spinner.c, on peut trouver cette liste :

static Edje_External_Param_Info external_spinner_params[] = {
DEFINE_EXTERNAL_COMMON_PARAMS,
EDJE_EXTERNAL_PARAM_INFO_STRING_DEFAULT("label format", "%1.2f"),
EDJE_EXTERNAL_PARAM_INFO_DOUBLE("min"),
EDJE_EXTERNAL_PARAM_INFO_DOUBLE_DEFAULT("max", 100.0),
EDJE_EXTERNAL_PARAM_INFO_DOUBLE_DEFAULT("step", 1.0),
EDJE_EXTERNAL_PARAM_INFO_DOUBLE("value"),
EDJE_EXTERNAL_PARAM_INFO_BOOL("wrap"),
EDJE_EXTERNAL_PARAM_INFO_SENTINEL
};

Pour pouvoir réaliser le minijeu, une dernière question se pose : comment générer le nombre aléatoire ? Embryo propose deux fonctions de génération, rand() et randf() qui sont tout à fait adaptées au besoin et que l'on peut utiliser un peu comme en C. Par exemple :

script {
public rand_num; // Nombre aléatoire
public generate() { // Génère un nombre aléatoire
set_int(rand_num, rand()%25); // On utilise set_int pour stocker le nombre généré
}
}

Désormais, vous avez tous les éléments nécessaires à la réalisation du minijeu ; tenter de le faire serait un bon moyen de vous entrainer. Quoi qu'il en soit, voici une proposition parmi bien d'autres de code, commenté pour simplifier la compréhension :

collections {
group {
name: "interface";
min: 200 175;

externals { // On précise qu'on va utiliser Elementary :
external: "elm";
}
script { // Le script de notre minijeu
public rand_num; // Nombre à trouver
public startGame() { // Génère le nombre à trouver
set_int(rand_num, rand()%25);
}
public checkNum() { // Plus ? Moins ? Gagné ?
new Float:num = external_param_get_float(PART:"form", "value");
if (num > get_int(rand_num)) // Moins !
external_param_set_str(PART:"info", "label", "C'est moins !");
else if (num < get_int(rand_num)) // Plus !
external_param_set_str(PART:"info", "label", "C'est plus !");
else // Gagné !
external_param_set_str(PART:"info", "label", "Vous avez gagné !");
}
}
parts {
part { // Un fond blanc
name: "background";
type: RECT;
mouse_events: 0;
description {
state: "default" 0.0;
color: 255 255 255 255;
}
}
part { // Label de titre
name: "text";
type: EXTERNAL;
source: "elm/label";
description {
state: "default" 0.0;
align: 0 0;
min: 0 0;
rel1 {
offset: 15 5;
}
params {
string: "label" "Insérez un nombre entre 0 et 25 :";
}
}
}
part { // Formulaire
name: "form";
type: EXTERNAL;
source: "elm/spinner";
description {
state: "default" 0.0;
align: 0 0;
max: 100 1;
rel1 {
to_y: "text";
relative: 0.0 1.0;
offset: 25 15;
}
params {
double: "min" 0;
double: "max" "25";
double: "value" "0";
}
}
}
part { // Bouton de validation du formulaire
name: "button";
type: EXTERNAL;
source: "elm/button";
description {
state: "default" 0.0;
align: 0 0;
max: 0 1;
rel1 {
to_y: "form";
relative: 0.0 1.0;
offset: 25 5;
}
params {
string: "label" "Tester le nombre !";
}
}
}
part { // Label indiquant plus, moins, etc.
name: "info";
type: EXTERNAL;
source: "elm/label";
description {
state: "default" 0.0;
align: 0 0;
min: 0 0;
rel1 {
to_y: "button";
relative: 0.0 1.0;
offset: 15 15;
}
params {
string: "label" "";
}
}
}
}
programs {
program { // On a cliqué sur le bouton
name: "click_event";
signal: "clicked";
source: "button";
script {
checkNum();
}
}
program { // Début de la partie
name: "load_event";
signal: "load";
script {
startGame();
}
}
}
}
}

Ce code peut bien entendu être amélioré, par exemple en ajoutant une gestion du nombre de coups, d'une difficulté, etc. N'hésitez surtout pas à vous attarder sur ce minijeu pour y apporter vos retouches, c'est essentiel pour prendre la main efficacement.

VII - Introduction

Comme dit dans la première partie de l'article, Elementary est une bibliothèque permettant l'exploitation d'une gamme de widgets hautement personnalisables. À l'heure actuelle, Elementary n'est toujours pas sortie en version stable du fait de ses quelques bogues et manques de widgets. Malgré cela, cette bibliothèque reste tout à fait appropriée pour la réalisation d'interfaces graphiques ordinaires.

VII-A - Objectifs

Cette partie traitera au mieux l'aspect « C » du développement d'une application avec les EFL. Bien entendu, du code Edje sera introduit afin de montrer comment lier un fichier de description Edje avec du code C, par exemple, ainsi que pour ne pas ignorer la particularité des EFL de séparer la partie graphique de la partie logique d'une application.

Dans le cadre du développement en C, Elementary aura une place centrale mais ne sera logiquement pas la seule bibliothèque utilisée. En effet, Elementary utilisée seule n'est pas suffisante pour écarter les possibilités offertes par d'autres bibliothèques telles qu'Evas, indispensable à la réalisation d'une application, ou Ecore.

VII-B - Compilation

La compilation d'un fichier source utilisant Elementary nommé source.c se fait généralement avec la commande suivante :

gcc -o source source.c `pkg-config elementary --cflags --libs`

Si vous obtenez une erreur comme quoi elementary.pc serait introuvable (« Package elementary was not found in the pkg-config search path ») malgré le fait que vous ayez déjà installé correctement Elementary, ajoutez le chemin /.../e17/lib/pkgconfig dans la variable d'environnement PKG_CONFIG_PATH :

export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/.../e17/lib/pkgconfig

Tout en remplaçant /.../ par le chemin correct. Cette variable d'environnement a déjà été introduite plus tôt dans l'article, précisée nécessaire dans la partie traitant l'installation des EFL.

Une fois la compilation terminée, on peut trouver l'exécutable à l'emplacement spécifié lors de la compilation.

VIII - Premiers pas avec Elementary

VIII-A - Aperçu des widgets d'Elementary

Dans le cas où l'on rechercherait un widget ou une personnalisation, Elementary vient avec une application, elementary_test, qui recense un grand nombre de widgets afin de donner un aperçu des différents widgets mis à disposition par la bibliothèque.

L'application elementary_test et l'exemple « Laucher »

L'application est intéressante quand il s'agit de rechercher un widget, mais le code qu'elle met à disposition n'est pas un bon exemple d'écriture de GUI avec les EFL.

VIII-B - Code minimal

Quoi de mieux pour commencer l'étude d'une bibliothèque étroitement liée au GUI que de présenter le code minimal d'ouverture d'une fenêtre afin de pouvoir le détailler progressivement par la suite ?

#include <Elementary.h>

static void
win_del(void *data, Evas_Object *obj, void *event_info)
{
elm_exit();
}

EAPI int
elm_main(int argc, char **argv)
{
Evas_Object *win;

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);
evas_object_show(win);

elm_run();
elm_shutdown();

return EXIT_SUCCESS;
}
ELM_MAIN()

Par le biais de l'inclusion du fichier Elementary.h, nous obtenons la possibilité d'utiliser toutes les fonctions que la bibliothèque met à disposition. Elementary inclut par défaut Eina, Eet, Evas, Ecore et Edje (et d'autres, selon la configuration), ce qui signifie qu'inclure Elementary.h permet d'utiliser toutes ces bibliothèques sans avoir besoin d'inclure des fichiers d'en-tête supplémentaires.

EAPI int
elm_main(int argc, char **argv)
{
...
}
ELM_MAIN()

La macro ELM_MAIN() définissant la fonction main() et appelant la fonction elm_main(), cette dernière est considérée comme la fonction principale du programme.

Evas_Object *win;

Cela définit un pointeur win de type Evas_Object, structure qui correspond à la base de tous les éléments graphiques.

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);
evas_object_show(win);

L'utilisation de la fonction elm_win_add mène à la création d'une fenêtre de nom unique main_window et de type ELM_WIN_BASIC (une fenêtre classique). L'utilisation - qui sera bien plus amplement détaillée par la suite - de la fonction evas_object_smart_callback_add() fait en sorte que la fonction win_del() soit appelée lorsque l'utilisateur demande à fermer la fenêtre avec le bouton associé. Cette fonction appelle elm_exit() qui permet de quitter la boucle principale, lancée par elm_run(). Enfin, evas_object_show() affiche la fenêtre.

Malgré tout, cette fenêtre n'a pas de titre, contient uniquement un fond noir, et n'est donc au final pas tellement attirante. Remédions-y en changeant le contenu de elm_main() :

EAPI int
elm_main(int argc, char **argv)
{
Evas_Object *win, *background;

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
elm_win_title_set(win, "Elementary");
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);

background = elm_bg_add(win);
evas_object_size_hint_min_set(background, 200, 100);
evas_object_size_hint_weight_set(background, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
elm_win_resize_object_add(win, background);
evas_object_show(background);

evas_object_show(win);

elm_run();
elm_shutdown();

return EXIT_SUCCESS;
}
ELM_MAIN()

Par le biais de la fonction elm_win_title_set(), on peut définir le titre d'une fenêtre :

elm_win_title_set(win, "Elementary");

De même, la bibliothèque permet de créer un arrière-plan à un objet Elementary par le biais de la fonction elm_bg_add() :

// Crée un arrière-plan pour win :
background = elm_bg_add(win);
// Définit des dimensions minimales à background :
evas_object_size_hint_min_set(background, 200, 100);
// Permet un redimensionnement horizontal et vertical :
evas_object_size_hint_weight_set(background, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
// Ajoute l'objet dans win et fait en sorte qu'il s'adapte à sa taille :
elm_win_resize_object_add(win, background);
evas_object_show(background);

La création d'un objet Elementary retourne toujours un Evas_Object*. Il est donc possible de manipuler un tel objet par le biais des méthodes fournies à la fois par la bibliothèque Evas et par la bibliothèque Elementary. Les deux bibliothèques peuvent parfois fournir les mêmes fonctionnalités. Dans ce cas, par convention, il est recommandé d'utiliser les méthodes d'Elementary.

VIII-C - Edje et le code C

VIII-C-1 - Utiliser un fichier EDJ dans du code C

Il est extrêmement rare que des applications complètes soient exclusivement développées en Edje. Comme dit dans la partie précédente, l'intérêt d'Edje est surtout de fournir l'aspect graphique de l'application afin de laisser le C gérer la partie logique. C'est pour cette raison que la méthode de lien d'un fichier Edje avec le code C est abordée ci-dessous, venant avec le concept de layouts :

Evas_Object *win, *layout;
...
layout = elm_layout_add(win);
elm_layout_file_set(layout, "interface.edj", "background_group");
elm_win_resize_object_add(win, layout);
evas_object_show(layout);

La fonction d'Elementary elm_layout_add() permet de créer un layout à un objet Elementary. Sa syntaxe est identique à celle de elm_bg_add(), vue précédemment. Le reste du code a déjà été vu, à l'exception d'une ligne :

elm_layout_file_set(layout, "interface.edj", "background_group");

La fonction elm_layout_file_set() permet de définir le fichier EDJ qui sera utilisé en tant que layout. Ici, « background_group » correspond au groupe à charger.

On peut alors manipuler le layout ainsi créé par le biais des fonctions dédiées d'Elementary ainsi qu'avec les fonctions d'Evas.

VIII-C-2 - Un exemple

Afin d'illustrer l'utilisation d'un fichier EDJ dans du C, prenons un exemple de fichier Edje qui pourra éventuellement servir de base par la suite. C'est d'ailleurs un bel exemple de l'utilisation des macros avec Edje et des éléments de type TABLE. Voici le code complet de chargement avec Elementary :

#include <Elementary.h>

static void
win_del(void *data, Evas_Object *obj, void *event_info)
{
elm_exit();
}

EAPI int
elm_main(int argc, char **argv)
{
Evas_Object *win, *layout;

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
elm_win_title_set(win, "Elementary");
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);

layout = elm_layout_add(win);
elm_layout_file_set(layout, "interface.edj", "background_group");
elm_win_resize_object_add(win, layout);
evas_object_show(layout);

evas_object_resize(win, 500, 300);
evas_object_show(win);

elm_run();
elm_shutdown();

return EXIT_SUCCESS;
}
ELM_MAIN()

Selon les images utilisées, cela ressemblera plus ou moins à ceci :

Icônes de l'application en provenance du site everaldo.com

VIII-C-3 - Interactions entre C et EDC

L'intérêt premier des fichiers de description Edje est de pouvoir séparer la partie graphique de la partie logique d'une application. Il est ainsi nécessaire de pouvoir gérer les signaux envoyés par Edje avec la partie logique de l'application.

Un programme Edje peut, comme dit dans la partie introduisant Edje, envoyer un signal par le biais de SIGNAL_EMIT :

program {
name: "mouse_click";
signal: "mouse,clicked,1";
source: "element";
action: SIGNAL_EMIT "element,clicked" "";
}

Le signal pourra alors être récupéré par un callback dans le C. Cela sera traité plus en détail dans la partie suivante, concernant les évènements.

Note : comme montré dans la partie précédente de l'article, les fichiers de description Edje peuvent comprendre des widgets d'Elementary avec le type EXTERNAL. C'est pour cette raison qu'il est hautement préférable de définir les widgets Elementary par le biais d'Edje, puis de les gérer dans le code C en récupérant un Evas_Object de l'élément Edje (méthode utilisée dans la partie suivante) puis en l'exploitant afin d'insérer des lignes, du texte ou autre, selon le widget.

VIII-D - La gestion des évènements

Un framework ne permettant pas une gestion des évènements est statique et donc peu intéressant. Ainsi, les EFL permettent une gestion des évènements par le biais des callbacks, c'est-à-dire des fonctions qui sont appelées lorsqu'un événement particulier s'est produit. De multiples fonctions sont utilisables selon le contexte pour créer un callback. Parmi elles :

- evas_object_smart_callback_add, utilisée avec des smart objets (ou objets intelligents) ;

- evas_object_event_callback_add, utilisée avec des objets Evas ;

- edje_object_signal_callback_add, quant à elle utilisée pour gérer les signaux en provenance d'une interface Edje.

Le premier moyen a déjà brièvement été présenté dans les exemples. Son prototype est le suivant :

EAPI void evas_object_smart_callback_add(Evas_Object * obj,
const char *    event,
Evas_Smart_Cb   func,
const void *    data
)

Avec obj, le smart objet sur lequel le callback va s'appliquer, event, l'évènement sur lequel réagir, func, la fonction callback et data, les paramètres à passer à la fonction callback. Cette dernière sera forcément de cette forme :

void callback(void *data, Evas_Object *obj, void *event_info) ;

Avec data, la valeur de l'argument data passée lors de l'ajout du callback, obj, l'objet sur lequel l'évènement s'est produit et event_info, le pointeur vers une structure de données pouvant ou non être passé.

Par exemple, pour un bouton Elementary nommé button, on pourrait avoir ceci :

void button_clicked(void *data, Evas_Object *obj, void *event_info)
{
printf("Click event.\n");
}
...
evas_object_smart_callback_add(button, "clicked", button_clicked, NULL);

La deuxième méthode est généralement utilisée sur les objets Evas simples :

EAPI void evas_object_event_callback_add(Evas_Object * obj,
Evas_Callback_Type      type,
Evas_Object_Event_Cb    func,
const void *            data
)

Avec obj, l'objet sur lequel le callback va s'appliquer, type, le type d'évènement appelant le callback (par exemple EVAS_CALLBACK_KEY_DOWN et EVAS_CALLBACK_MOUSE_UP), func, la fonction callback et data, les paramètres à passer à la fonction callback. Cette dernière sera de la forme suivante :

void callback(void *data, Evas *e, Evas_Object *obj, void *event_info);

Avec e, le canvas Evas qui a appelé l'évènement. Les autres arguments sont les mêmes que ceux de la première méthode détaillée. Toutefois, la partie traite d'Elementary et non d'Evas, donc aucune information supplémentaire ne sera donnée concernant cette méthode.

La troisième méthode permet de créer une interaction entre le code C et Edje. Son prototype est le suivant :

EAPI void edje_object_signal_callback_add(Evas_Object * obj,
const char * emission,
const char * source,
void(*)(void*,Evas_Object*,const char*,const char*) func,
void *       data
)

Avec obj, l'objet qui va émettre le signal, emission, le nom du signal, source, le nom de la part Edje source, func, la fonction callback et data, les paramètres à passer à la fonction callback, de la forme donnée dans le prototype :

void callback(void *data, Evas_Object *obj, const char *emission, const char *source)

Par exemple, pour récupérer le signal émis dans le programme présenté plus tôt :

void
edje_event(void *data, Evas_Object *obj, const char *emission, const char *source)
{
printf("Click event.\n");
}

layout = elm_layout_add(win);
elm_layout_file_set(layout, "interface.edj", "group_name");
elm_win_resize_object_add(win, layout);
evas_object_show(layout);

edje = elm_layout_edje_get(layout);
edje_object_signal_callback_add(edje, "element,clicked", "*", edje_event, NULL);

Il est nécessaire de passer par elm_layout_edje_get() pour récupérer l'objet Edje depuis l'objet Elementary afin d'ajouter par la suite un callback.

Note : on peut également écrire une fonction permettant de récupérer la totalité des évènements se produisant dans l'Edje afin de déboguer :

void
edje_event(void *data, Evas_Object *obj, const char *emission, const char *source)
{
printf("Signal %s from %s\n", emission, source);
}
...
edje_object_signal_callback_add(edje, "*", "*", edje_event, NULL);

Utilisé avec le code d'exemple donné plus tôt dans la partie II.3.2, voici les logs d'un simple clic sur une icône :

Signal mouse,down,1 from table
Signal mouse,down,1 from table[3]:event_handler
Signal mouse,up,1 from table
Signal mouse,clicked,1 from table
Signal mouse,up,1 from table[3]:event_handler
Signal mouse,clicked,1 from table[3]:event_handler

Et dans le cas où l'on aurait ajouté ce programme dans la macro TABLE_IMAGE :

program {                                       \
name: "mouse_click";                        \
signal: "mouse,clicked,1";                  \
source: "event_handler";                    \
action: SIGNAL_EMIT _name",clicked" "";     \
}                                               \

Une ligne supplémentaire s'ajouterait aux logs :

Signal graphics,clicked from table[3]:

L'avantage d'un tel système de logs et que l'on voit l'intégralité de ce qu'il se passe sur l'interface utilisateur, ce qui simplifie grandement le débogage.

IX - Quelques widgets

Maintenant que l'on sait comment créer une interface avec Elementary combinée avec Edje ainsi qu'agir en réaction à un événement, il est temps de partir plus en profondeur dans Elementary, et ainsi aller à la rencontre de quelques widgets intéressants.

IX-A - Les listes

Les listes sont des éléments extrêmement utilisés dans les diverses applications que l'on utilise quotidiennement, que ce soit pour afficher une liste de fichiers, une playlist ou bien d'autres choses encore. Elementary fournit le widget List pour créer de tels objets dont voici un exemple basique d'utilisation :

Evas_Object *win, *list;
char buf[32];
int i;

...

list = elm_list_add(win);
elm_win_resize_object_add(win, list);
elm_list_mode_set(list, ELM_LIST_LIMIT);
evas_object_size_hint_weight_set(list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);

for (i = 0; i < 10; ++i)
{
snprintf(buf, sizeof(buf), "Élément n°%d", i);
elm_list_item_append(list, buf, NULL, NULL, NULL, NULL);
}

elm_list_go(list);
evas_object_show(list);

À ce stade de l'article, seules trois fonctions sont à détailler :

- elm_list_mode_set() : permet de changer le mode de la liste ;

- elm_list_item_append() : permet d'ajouter un élément dans la liste ;

- elm_list_go() : à appeler avant d'afficher la liste.

Toutes étant quoi qu'il en soit détaillées dans la documentation.

Cette liste est aussi triste qu'inutile. Il serait intéressant de lui ajouter des icônes sur le côté et de lui assigner une fonction callback afin de gérer les sélections d'éléments.

Le plus simple, les callbacks :

void
list_event(void *data, Evas_Object *obj, void *event_info)
{
Elm_List_Item *item;
// On récupère l'item sélectionné de la liste :
item = elm_list_selected_item_get(obj);
// On affiche le message ainsi que le texte de son label :
printf("Double click happened. %s.\n", elm_list_item_label_get(item));
}

...

evas_object_smart_callback_add(list, "clicked,double", list_event, NULL);

Lors d'un double-clic sur l'élément n° 5 par exemple, cela affichera ceci dans la console :

Double click happened. Élément n° 5.

La liste exhaustive des signaux utilisables est disponible dans le fichier elm_list.c.

Ajouter des icônes à une liste est une tâche légèrement plus longue dans le sens où il est nécessaire de créer une boîte pour les afficher.

Evas_Object *win, *list, *box, *icon;

...

for (i = 0; i < 10; ++i)
{
// On crée une boîte :
box = elm_box_add(win);
// On crée une icône :
icon = elm_icon_add(win);
// On définit le fichier image de l'icône :
elm_icon_file_set(icon, "images/icon.png", NULL);
// On retire la mise à l'échelle :
elm_icon_scale_set(icon, 0, 0);
// On ajoute l'icône dans la boîte :
elm_box_pack_end(box, icon);
// On affiche l'image :
evas_object_show(icon);
// Le texte de l'item :
snprintf(buf, sizeof(buf), "Élément n°%d", i);
// On utilise la boîte pour afficher l'icône à gauche :
elm_list_item_append(list, buf, box, NULL, NULL, NULL);
}

Avec les commentaires, aucune explication additionnelle n'est requise.

IX-B - Les genlist

Dans la première partie de l'article, lors de la présentation d'Elementary, le concept des genlists était abordé. Pour résumer l'intérêt, si l'on remplace dans le code précédent la ligne :

for (i = 0; i < 10; ++i)

Par la ligne :

for (i = 0; i < 1000; ++i)

Que va-t-il se passer ? Pour donner des chiffres, l'application va « juste » passer de 2 Mo à 17 Mo, en termes de consommation de mémoire. Si on rajoute un zéro, faisant passer le nombre d'éléments à 10 000, sous une machine des plus classiques, l'application va se mettre à laguer et va démarrer en plus de vingt secondes. Ainsi, on peut en déduire que le choix d'une liste classique est furieusement inapproprié à une utilisation à grande échelle. C'est là qu'interviennent les genlists, bien plus appropriées à ce type d'usage.

char *
gl_label_get(void *data, Evas_Object *obj, const char *part)
{
char buf[256];
snprintf(buf, sizeof(buf), "Élément n°%d", (int)data);
return strdup(buf);
}

Evas_Object *
gl_icon_get(void *data, Evas_Object *obj, const char *part)
{
return NULL;
}

Eina_Bool
gl_state_get(void *data, Evas_Object *obj, const char *part)
{
return EINA_FALSE;
}

void
gl_del(void *data, Evas_Object *obj)
{
}

...

Evas_Object *win, *background, *genlist;
Elm_Genlist_Item_Class item;
int i;

genlist = elm_genlist_add(win);
elm_win_resize_object_add(win, genlist);
evas_object_size_hint_weight_set(genlist, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_show(genlist);

item.item_style = "default";
item.func.label_get = gl_label_get;
item.func.icon_get = gl_icon_get;
item.func.state_get = gl_state_get;
item.func.del = gl_del;

for (i = 0; i < 10000; ++i)
{
elm_genlist_item_append(genlist, &item, (void *)i,
NULL, ELM_GENLIST_ITEM_NONE, NULL, NULL);
}

evas_object_show(genlist);

Le rendu est identique à la capture d'écran présentée plus haut, au détail près qu'il y a bien plus d'éléments.

De manière à avoir un sujet de comparaison avec l'utilisation d'une liste normale, pour dix éléments, l'application va prendre 2.4 Mo et 2.7 Mo pour mille. Avec dix mille éléments, l'application prendra à peine 4 Mo en mémoire (contre environ 150 pour l'utilisation des listes classiques) et ne souffrira d'aucun problème de ralentissement. On peut donc conclure que les genlists sont parfaitement adaptées aux utilisations à grande échelle. Toutefois, comme vous pouvez le voir, le codage d'une genlist est plus complexe que celui d'une simple liste. C'est pour cela qu'il est préférable d'utiliser les listes classiques dans le cas où les éléments à y stocker sont peu nombreux et peu complexes.

Par la notion de complexité, il est entendu que les genlists ne présentent pas uniquement des avantages en matière de consommation en mémoire. Elles sont également pratiques pour réaliser des listes sous forme d'arbre, par exemple. À noter qu'un élément d'une genlist peut être sous plusieurs styles, posséder plusieurs labels, plusieurs états, etc. d'où une réelle divergence des genlists par rapport à leurs cousines.

À première vue, le code donné peut assez logiquement sembler suspect. Pour le comprendre, il est nécessaire d'assimiler précisément la manière dont une genlist fonctionne.

Une genlist crée et détruit les objets des éléments la composant au fur et à mesure que l'utilisateur va la faire défiler, les groupe dans des blocs afin de déterminer aisément lesquels allouer et détruire. Combinée avec un concept de files, une genlist arrive à afficher un nombre incroyable d'éléments avec une consommation ridicule de mémoire. Une question se pose alors : comment fait-elle pour créer et détruire les objets au vol sans stocker les données en mémoire ? C'est là que la structure Elm_Genlist_Item_Class entre en jeu. Son rôle : permettre à une genlist de faire son travail de création/suppression par le biais des différentes fonctions qu'elle fournit.

item.item_style = "default";
item.func.label_get = gl_label_get;
item.func.icon_get = gl_icon_get;
item.func.state_get = gl_state_get;
item.func.del = gl_del;

Il est bien entendu obligatoire de fournir les fonctions, dans le sens où une genlist ne peut pas inventer ce qu'elle doit afficher.

Le concept est assez intéressant. Si vous souhaitez avoir un aperçu de ce qui se fait dans la pratique, soit parce que vous avez du mal à assimiler le concept ou tout simplement par curiosité, créez un int (nommé num, par exemple) et incrémentez-le à chaque fois que la méthode gl_label_get() est appelée. Utilisez alors cet entier pour spécifier le texte de l'élément :

snprintf(buf, sizeof(buf), "Élément n°%d", num);
num++;
return strdup(buf);

Compilez, lancez le programme. Vous pouvez constater que le numéro évolue en permanence, à chaque fois que vous faites défiler la liste.

X - Avant-propos

L'article touche à sa fin. Edje et Elementary ont été suffisamment détaillés pour que tout lecteur puisse se lancer sérieusement dans le développement d'applications utilisant les EFL. Toutefois, abandonner ainsi un lecteur sans donner d'exemple complet ne serait pas forcément judicieux. C'est pour cela que cette partie de l'article développera la conception progressive d'un navigateur Web développé avec Webkit et les EFL grâce au port EFL du projet Webkit.

XI - Installation de Webkit-EFL

Pour les distributions telles qu'Ubuntu, afin d'installer Webkit pour les EFL, soyez sûr d'avoir les paquets suivants :

sudo apt-get install cmake libicu-dev libxslt-dev libsqlite3-dev gperf \
libxt-dev libfontconfig1-dev libcairo2-dev libglib2.0-dev libgtk2.0-dev \
libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libcurl4-dev

Liste initialement en provenance de http://trac.webkit.org/wiki/EFLWebKit.

Cela fait, on récupère les sources, que l'on place par exemple dans /home/username :

cd /home/username/
svn checkout http://svn.webkit.org/repository/webkit/trunk WebKit

L'opération peut prendre un certain temps. La procédure terminée, on suit les informations de la page citée ci-dessus:

cd WebKit/
mkdir build
cd build/

On lance alors CMake puis on compile :

cmake ../Source -DPORT=Efl -DNETWORK_BACKEND=curl -DSHARED_CORE=OFF -DCMAKE_BUILD_TYPE=Release
make && make install

Une fois de plus, cela peut prendre du temps.

XII - Création du navigateur

XII-A - Utilisation de Webkit-EFL avec Elementary

Maintenant que le Webkit est installé avec son port des EFL, il est temps de l'utiliser. Voici donc le code minimal de la partie :

#include <Elementary.h>
#include <EWebKit.h>

static void
win_del(void *data, Evas_Object *obj, void *event_info)
{
elm_exit();
}

EAPI int
elm_main(int argc, char **argv)
{
Evas_Object *win;

ewk_init(); // Initialisation de WebKit.

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
elm_win_title_set(win, "Elementary");
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);

// On créera ici notre navigateur.

evas_object_resize(win, 800, 600);
evas_object_show(win);

elm_run();

ewk_shutdown(); // Fermeture de WebKit.
elm_shutdown();

return EXIT_SUCCESS;
}
ELM_MAIN()

Comme dit dans les commentaires, les fonctions ewk_init() et ewk_shutdown() sont respectivement utilisées pour initialiser et fermer WebKit. Par le biais de EWebKit.h, on dispose de tout ce que WebKit met à disposition pour son déploiement avec les EFL.

Pour compiler le code donné ci-dessus et les suivants, la commande est la suivante :

gcc -o webkit webkit.c \
-DEWK_DATADIR="\"$(pkg-config ewebkit --variable=datadir)\"" \
`pkg-config elementary ewebkit --cflags --libs`

Dans le cas où libewebkit.so.0 serait introuvable, une copie à côté de l'exécutable de ce fichier et de libewebkit.so.0.1.0 depuis /usr/local/lib/ est suffisante.

Pour créer un navigateur, WebKit met à disposition la fonction ewk_view_single_add() qui prend pour argument un objet Evas (et non Elementary) et qui retourne un Evas_Object. Puisque Elementary, et non directement Evas, est utilisée pour créer la fenêtre principale, il est nécessaire de récupérer un objet Evas de l'objet Elementary :

Evas_Object *win;
Evas *e;

...

e = evas_object_evas_get(win);

Dès lors, on peut créer notre navigateur avec ewk_view_single_add() en lui passant l'objet Evas e, définir le thème avec ewk_view_theme_set() et la page à afficher avec ewk_view_uri_set() :

#define EWK_THEME EWK_DATADIR"/themes/default.edj"

...

Evas_Object *win, *browser;
Evas *e;

...

e = evas_object_evas_get(win);

browser = ewk_view_single_add(e);
ewk_view_theme_set(browser, EWK_THEME);
ewk_view_uri_set(browser, "http://www.developpez.com/");
elm_win_resize_object_add(win, browser);
evas_object_show(browser) ;

Après compilation, le résultat est le suivant :

XII-B - Une interface graphique plus esthétique

Il est alors nécessaire d'offrir à l'utilisateur une interface plus jolie ainsi que plus d'outils afin de lui permettre d'interagir avec le navigateur par le biais d'une barre d'adresse ainsi que des boutons « Précédent », « Suivant », etc. Pour cela, un simple fichier Edje va permettre un gain de temps.

#define MIN 24
#define MAX 24

#define TOP_BUTTON(_name, _prev)        \
part {                              \
name: _name;                    \
type: EXTERNAL;                 \
source: "elm/button";           \
description {                   \
state: "default" 0.0;       \
align: 0 0;                 \
min: MIN MIN;               \
max: MAX MAX;               \
params {                    \
string: "icon" _name;   \
}                           \
rel1 {                      \
to_x: _prev;            \
offset: MAX+15 0;       \
}                           \
}                               \
}                                   \

#define TOP_PROGRAM(_name)                          \
program {                                       \
name: _name"_clicked";                      \
signal: "clicked";                          \
source: _name;                              \
action: SIGNAL_EMIT _name",clicked" "";     \
}                                               \

collections {
group {
name: "top_group";
externals {
external: "elm";
}
parts {
part {
name: "back";
type: EXTERNAL;
source: "elm/button";
description {
state: "default" 0.0;
align: 0 0;
min: MIN MIN;
max: MAX MAX;
params {
string: "icon" "back";
}
rel1 {
relative: 0 0;
offset: 0 0;
}
}
}
TOP_BUTTON("forward", "back");
TOP_BUTTON("reload", "forward");
TOP_BUTTON("stop", "reload");
TOP_BUTTON("go-jump", "stop");
part {
name: "url";
type: EXTERNAL;
source: "elm/scrolled_entry";
description {
state: "default" 0.0;
align: 0 0;
rel1 {
to_x: "go-jump";
offset: MAX+15 0;
}
params {
bool: "editable" 1;
bool: "single line" 1;
}
}
}
}
programs {
TOP_PROGRAM("back");
TOP_PROGRAM("forward");
TOP_PROGRAM("reload");
TOP_PROGRAM("stop");
TOP_PROGRAM("go-jump");
}
}
}

On peut constater que l'utilisation de macro a permis de condenser grandement le code. Il s'agit maintenant d'améliorer l'interface graphique. Afin d'aboutir à un rendu plus correct, on va utiliser le fichier Edje et divers widgets, comme des séparateurs, barres de progression ou encore boîtes :

Evas_Object *win, *background, *box, *separator, *top, *browser, *progressbar;

...

ewk_init();

win = elm_win_add(NULL, "main_window", ELM_WIN_BASIC);
elm_win_title_set(win, "Elementary");
evas_object_smart_callback_add(win, "delete,request", win_del, NULL);

background = elm_bg_add(win);
elm_win_resize_object_add(win, background);
evas_object_size_hint_weight_set(background, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_show(background);

box = elm_box_add(win);
elm_win_resize_object_add(win, box);
evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_show(box);

top = elm_layout_add(win);
elm_layout_file_set(top, "top.edj", "top_group");
evas_object_size_hint_weight_set(top, EVAS_HINT_EXPAND, 0);
evas_object_size_hint_align_set(top, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, top);
evas_object_show(top);

separator = elm_separator_add(win);
elm_separator_horizontal_set(separator, 1);
elm_box_pack_end(box, separator);
evas_object_show(separator);

e = evas_object_evas_get(win);

browser = ewk_view_single_add(e);
ewk_view_theme_set(browser, EWK_THEME);
ewk_view_uri_set(browser, "http://www.developpez.com/");
evas_object_size_hint_weight_set(browser, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(browser, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, browser);
evas_object_show(browser);

separator = elm_separator_add(win);
elm_separator_horizontal_set(separator, 1);
elm_box_pack_end(box, separator);
evas_object_show(separator);

progressbar = elm_progressbar_add(win);
evas_object_size_hint_weight_set(progressbar, EVAS_HINT_EXPAND, 0);
evas_object_size_hint_align_set(progressbar, EVAS_HINT_FILL, 0.5);
elm_box_pack_end(box, progressbar);
evas_object_show(progressbar);

...

Jusque-là, rien de nouveau. On a juste utilisé une petite panoplie de widgets afin de réaliser l'interface.

Ce n'est pas encore la dernière version de Google Chrome ou de Firefox, mais ça ressemble un minimum à quelque chose. Du moins, les options de navigation sont là, plus qu'à les relier au navigateur.

XII-C - Les callbacks

L'interface graphique étant faite, la pièce manquante du puzzle est les différents callbacks qui vont la relier au navigateur.

Sur le point de la gestion des signaux Edje, les boutons « Précédent », « Suivant », « Stop » et « Actualiser » devront exercer leur tâche sur le navigateur, le bouton « Go » devra charger la page demandée dans la barre d'adresse. De même, pour la gestion des évènements du navigateur, la barre de progression devra changer de valeur au fur et à mesure que la page sera chargée et la barre d'adresse devra s'adapter à un changement d'adresse.

// Callbacks de top.edj :
edje = elm_layout_edje_get(top);
edje_object_signal_callback_add(edje, "back,clicked", "*", event_handler, (void*)browser);
edje_object_signal_callback_add(edje, "forward,clicked", "*", event_handler, (void*)browser);
edje_object_signal_callback_add(edje, "reload,clicked", "*", event_handler, (void*)browser);
edje_object_signal_callback_add(edje, "stop,clicked", "*", event_handler, (void*)browser);
edje_object_signal_callback_add(edje, "go-jump,clicked", "*", event_handler, (void*)browser);

La seule chose à noter est que nous passons le navigateur en argument pour pouvoir interagir avec lui dans la fonction event_handler(), de prototype :

void
event_handler(void *data, Evas_Object *obj, const char *emission, const char *source)

Ici, data va donner accès à notre pointeur browser, et obj à notre pointeur edje, conformément aux arguments donnés dans edje_object_signal_callback_add().

void
event_handler(void *data, Evas_Object *obj, const char *emission, const char *source)
{
// Précédent :
if (!strcmp(emission, "back,clicked"))
ewk_view_back(data);
// Suivant :
if (!strcmp(emission, "forward,clicked"))
ewk_view_forward(data);
// Actualiser :
if (!strcmp(emission, "reload,clicked"))
ewk_view_reload(data);
// Arrêter :
if (!strcmp(emission, "stop,clicked"))
ewk_view_stop(data);
// Go :
if (!strcmp(emission, "go-jump,clicked"))
{
Edje_External_Param param = {"text", EDJE_EXTERNAL_PARAM_TYPE_STRING, 0, 0, NULL};
// On récupère les paramètres de l'élément de nom url...
if (edje_object_part_external_param_get(obj, "url", &param))
ewk_view_uri_set(data, param.s); // ... dont son texte, dans param.s.
}
}

Toutes les fonctions utilisées dans le code ci-dessus sont documentées dans le fichier ewk_view.cpp, situé par défaut dans /home/username/WebKit/efl/ewk/.

Note : il existe une solution plus simple permettant de gérer le bouton « Go ». Elle consiste en une récupération de l'élément Edje avec edje_object_part_external_object_get() puis de l'utilisation d'une fonction d'Elementary, elm_scrolled_entry_entry_get() sur l'objet ainsi récupéré. Au final, on obtient un const char*, que l'on peut passer à la fonction ewk_view_uri_set() pour effectuer la même chose que dans le code donné. Cette solution sera étudiée plus loin.

Les signaux en provenance d'Edje sont donc gérés. Plus qu'à écrire les callbacks des signaux en provenance du navigateur, dont la liste exhaustive se trouve également dans ewk_view.cpp. Dans le cas présent, les callbacks suivants vont être utilisés :

// Callbacks du browser :
evas_object_smart_callback_add(browser, "uri,changed", b_url_changed, (void*)edje);
evas_object_smart_callback_add(browser, "load,progress", b_load_progress, (void*)progressbar);

Avec le prototype de fonction callback suivant :

void
callback(void *data, Evas_Object *obj, void *event_info)

On a :

- uri,changed est appelé quand le navigateur change d'url (le pointeur event_info contiendra une chaîne de caractères correspondant à la nouvelle adresse) ;

- load,progress est appelé quand le navigateur est en train de charger une page (le pointeur event_info contiendra un nombre entre 0 et 1 correspondant à l'avancement du chargement).

L'implémentation peut donc être faite.
void
b_uri_changed(void *data, Evas_Object *obj, void *event_info)
{
// event_info contient la nouvelle adresse.
Evas_Object *ext_url = edje_object_part_external_object_get(data, "url");
if (!ext_url)
return;
elm_scrolled_entry_entry_set(ext_url, (char*)event_info);
}
void
b_load_progress(void *data, Evas_Object *obj, void *event_info)
{
// event_info contient la progression entre 0.0 et 1.0.
elm_progressbar_value_set(data, *(double*)(event_info));
}

Dans b_uri_changed(), on commence par récupérer un Evas_Object* de l'élément url de l'Edje puis on utilise elm_scrolled_entry_entry_set() pour définir le texte. C'est l'autre méthode par référence à celle présentée pour la gestion du bouton « Go ».

Après compilation, le navigateur fonctionne très bien.

Ainsi se termine cette partie ainsi que l'article. Par le biais de cet exemple, de nombreuses notions ont été approfondies et certains éléments ont été introduits. Toutefois, il n'est jamais inutile d'aller creuser un peu plus dans la documentation et/ou dans les fichiers sources dans le but d'améliorer un exemple ou tout simplement pour se lancer dans un projet, qu'il soit petit ou gros. WebKit est un projet vaste qui ne se limite pas à des actions simples. La gestion des paramètres divers et variés, des menus contextuels, des frames, etc. sont des pistes qui peuvent être explorées. Et l'expérience se gagne essentiellement par la pratique.

XIII - Ressources externes

Ci-dessous se situe la liste des ressources externes ayant été utilisées pour la réalisation de ce cours introductif au développement avec les EFL.

XIII-A - Sites internet

- http://dev.enlightenment.fr/~captainigloo/ ;

- http://www.enlightenment.org/ ;

- http://enlightenment.fr/ ;

- http://trac.webkit.org/wiki/EFLWebKit.

XIII-B - Articles

- http://trac.enlightenment.org/e/browser/trunk/DOCS/tutorials/fr/edje/edje_tutorial.pdf.

XIII-C - Divers

- Journée de présentation des EFL par Enlightenment France et Ordissimo ;

- Conseils sur l'IRC d'Enlightenment France (#e.fr sur freenode).

XIV - Remerciements

Merci à gorgonite pour ses conseils lors de l'écriture de ce cours.

Merci également à Michael Sans pour ses indications et son aide dans la prise en main des EFL ainsi qu'à Guillaume Friloux, Vincent Torri, Jonathan Atton et Cédric Bail pour la relecture technique de l'article.

Pour finir, merci à Claude Leloup, à jpcheck et à dourouc05 pour leur relecture orthographique !