• Aucun résultat trouvé

Des alternatives à la programmation évènementielle

Essentiel d'Interaction n°

2.3 L'environnement d'interaction de toute application

2.3.2 Des alternatives à la programmation évènementielle

La programmation évènementielle est un concept populaire en IHM, mais qui est aussi très peu défini. Elle se caractérise principalement par la manipulation d'évènements (events), qui peuvent représenter des actions de l'utilisateur (ex. mouvement de souris), des messages entre processus ou machines, ou tous types de changements d'état. Par exemple, pour modéliser les mouvements de la souris en programmation évènementielle, on définirait une structure MouseMoveEvent, contenant le déplacement relatif de la souris, ainsi que d'éventuelles informations comme le nom du périphérique ou sa précision. struct MouseMoveEvent { int dx; int dy; String productID; int dpi; ... }

La programmation évènementielle est souvent utilisée pour qualifier des systèmes qui écoutent leur environnement, cependant la manière de gérer les évènements varie. Lorsqu'elle est utilisée pour propager des évènements avec attente passive, on l'associe avec le patron observateur (listener) pour enregistrer des fonctions de rappel (event handler) traitant chaque type d'évènement. Avec ce mécanisme, chaque widget stocke une fonction de rappel pour chaque type d'évènement qu'il peut recevoir (clic de souris dans sa zone, appui de touche lorsqu'il est actif, etc.). Lorsque le framework détecte la survenue d'un évènement, il remplit les champs d'une structure d'évènement, trouve le widget sur lequel l'évènement s'est produit, et exécute la ou les fonctions de rappel qui y sont enregistrées pour le type d'évènement en question. Les fonctions reçoivent en argument la structure initialisée.

Lorsque la programmation évènementielle est utilisée en propagation avec attente active, on lui associe une file d'évènements (event queue), qui les accumule afin de les traiter périodiquement par paquets. La file est alors parcourue dans l'ordre d'arrivée des évènements, ceux-ci sont traités un par un, puis la file est vidée pour accueillir les prochains évènements. Ce type de stockage des évènements est courant dans les jeux vidéo, lorsqu'ils sont synchronisés avec un tickrate particulier — celui d'un

La modélisation des évènements dans une application interactive est un compromis entre la taille des structures d'évènements, et la spécialisation des traitements, qui forme un continuum de granularité des solutions allant de monolithique (avec un type d'évènement complexe et unique) à fine (avec une grande variété de types d'évènements à la structure simple). Ce continuum est représenté en

figure 14. À une extrémité, tous les évènements sont représentés par une même structure, et chaque

gestionnaire doit filtrer les évènements qui ne l'intéressent pas. En contrepartie, on ne conserve qu'une seule liste de gestionnaires d'évènements, ou une seule file d'évènements. Des exemples d'application de ce principe sont le framework SDL [Lan98] ainsi que le protocole TUIO [Kal05]. À l'autre extrémité, chaque type d'évènement est représenté par sa propre structure, et un gestionnaire d'évènements n'a jamais à filtrer d'évènement qui ne l'intéresse pas. En contrepartie, il faut maintenir autant de listes de gestionnaires, de files d'évènements, et de canaux de transmission d'évènements que le nombre d'évènements possibles. Des exemples d'application de ce principe sont les frameworks AWT et Swing, ainsi que le framework GLFW  [GLF02] (celui-ci ne déclare aucune structure mais passe les variables directement en arguments des fonctions de rappel).

Évènements monolithiques Évènements fins

struct Event {     int type     int keyCode     int keyChar     int motionX     int motionY     int scrollX     int scrollY } struct MouseEvent {     int type     int motionX     int motionY     int scrollX     int scrollY } struct KeyboardEvent {     int type     int keyCode     int keyChar } struct MouseMotionEvent {     int motionX     int motionY } struct MouseScrollEvent {     int scrollX     int scrollY } struct KeyPressEvent {     int keyCode     int keyChar } struct KeyReleaseEvent {     int keyCode     int keyChar } Évènements hybrides

Figure 14 : Le continuum de granularité des types d'évènements, illustré par un exemple de code supportant les mouvements de souris et les appuis/relâchements de touches du clavier. Dans le cas des évènements fins, le type de chaque évènement n'est pas stocké explicitement mais correspond au nom de la structure. Il épargne au programme l'utilisation de structures conditionnelles (if/else et switch) pour filtrer les évènements, et laisse cette responsabilité au framework et au langage. En pratique la plupart des frameworks orientés objets définissent des hiérarchies d'évènements (figure  15), qui leur permettent de proposer des gestionnaires à tous les niveaux du continuum, au prix d'un code plus complexe.

Cependant, en pratique l'utilisation d'évènements fins favorise la fragmentation de la logique discutée en section 2.2.4. En effet, les différents types de traitements sont séparés en différentes fonctions (ex. onMouseMotion(...), onMouseScroll, onKeyPress, onKeyRelease). Ces fonctions contribuent à séparer tous les comportements correspondant aux actions possibles dans l'interface, en une multitude de fonctions individuelles qui compliquent la maintenance globale de

l'application. En outre, la programmation évènementielle abstrait les caractéristiques de chaque périphérique, en le réduisant en une modalité d'interaction. Bien qu'elle ait ainsi permis, par exemple, d'adapter facilement des interfaces de bureau à l'utilisation du doigt (en générant des évènements de souris), ce type d'abstraction réduit la richesse de l'interaction au doigt (précision, intensité de pression, orientation) en le réduisant à un pointeur. Enfin, les structures d'évènements communiquent par définition les données relatives à l'action observée, et omettent souvent les données relatives aux périphériques d'entrée. Elles rendent ainsi plus difficile l'adaptation de l'interaction aux types de périphériques qui génèrent des évènements.

Event KeyboardEvent MouseEvent KeyPressEvent KeyReleaseEvent MouseMotionEvent MouseScrollEvent class Event {}

class MouseEvent extends Event {}

class MouseMotionEvent extends MouseEvent {     int motionX

    int motionY }

class MouseScrollEvent extends MouseEvent {     int scrollX

    int scrollY }

class KeyboardEvent extends Event {     int keyCode

    int charCode }

class KeyPressEvent extends KeyboardEvent {} class KeyReleaseEvent extends KeyboardEvent {} Figure 15 : Exemple de hiérarchie d'évènements supportant les mouvements de souris et les

appuis/relâchements de touches du clavier, et le code correspondant

Nous considérons donc comme essentiel de propager les données d'entrée en conservant les caractéristiques des périphériques qui les ont générées, et avons cherché des alternatives à la programmation évènementielle au cours de ce travail de thèse. Dans un système interactif, tout changement d'état s'accompagne de données décrivant ce changement (quel bouton est pressé, de combien d'unités la souris se déplace, etc.). Ces données doivent être mises à disposition du programme. Des alternatives à la programmation évènementielle consistent donc en leur stockage autre part que des structures d'évènements :

sur l'objet recevant l'interaction (bouton cliqué, champ de texte actif) sur le périphérique à l'origine de l'interaction (souris, clavier) comme arguments aux gestionnaires d'évènements

dans des variables globales, accessibles depuis tout gestionnaire d'évènement

Dans le chapitre 3, nous présentons une alternative dans laquelle ces données sont stockées dans des objets représentant les périphériques à l'origine de l'interaction.