• Aucun résultat trouvé

Transformation des appels de fonctions en animations

Essentiel d'Interaction n°

2.4 Transformation des appels de fonctions en animations

Motivés par la prédominance des frameworks observée durant les premières interviews, nous avons entrepris la réalisation d'un projet les améliorant, sans pour autant investir de l'énergie pour en modifier un explicitement. De plus, à partir des Essentiels d'Interaction, nous souhaitions démontrer notre vision d'une syntaxe intégrée à un langage de programmation. Nous avons donc développé, à partir du concept d'animer une fonction, une extension du langage Smalltalk qui convertisse une transition instantanée par appel de méthode, en une transition animée dans le temps. Avec ce travail, nous nous sommes également attachés à inclure le support des traitements en durée dans l'environnement d'interaction des applications (c'est-à-dire les traitements qui se déroulent sur une période de temps plutôt qu'instantanément). Conformément au premier Essentiel d'Interaction, l'orchestration des animations est rendue explicite par une règle simple  : toute fonction animée est exécutée à fréquence fixe pendant la durée d'animation.

Ce travail a pu être réalisé grâce à la collaboration avec une équipe de recherche en Génie Logiciel, RMoD, qui développe son propre interpréteur et environnement de développement pour Smalltalk, Pharo [Duc17]. Avec l'aide des chercheurs et ingénieurs de cette équipe, nous avons pu réaliser une extension du langage Smalltalk dédiée à l'expression des animations dans les interfaces graphiques. Ce travail a été présenté en Late Breaking Result lors de la conférence EICS'17 [Raf17], et démontré en direct lors de la conférence Pharo Days 2017 [Raf17]. Nous détaillons ici ce travail.

2.4.1 Introduction

Depuis le développement des ordinateurs, les animations ont été utilisées dans une gamme de plus en plus large de scénarios tels que  : l'enseignement de la programmation avec des environnements visuels [Sta93, Res09, Dan11], les transitions dans les interfaces graphiques et visualisations pour redimensionner les fenêtres ou alterner entre vues sur des données [Sha07, Kle05,  Dra11], ou l'animation de personnages virtuels dans les jeux vidéo par interpolation entre images-clés [Bro88,

Wil97]. Les animations sont considérées comme utiles dans les interfaces utilisateur pour aider à

suivre les changements  [Sch07], et dans les visualisations pour construire une carte mentale des informations spatiales  [Bed99]. Elles peuvent aussi donner un sens à la visualisation des données  [Gon96], à la narration  [Ken02], ainsi que de nombreux autres usages dans les interfaces utilisateur [Che16].

Les frameworks d'interaction ont évolué au fil des ans pour supporter une plus grande variété d'usages, en proposant des manières plus flexibles d'animer les éléments des interfaces utilisateur. Alors que les systèmes d'autrefois animaient quelques propriétés (position, couleur) avec des fonctions dédiées pour chacune, les systèmes modernes en contiennent trop pour poursuivre de cette façon. Par exemple, CSS a 44  propriétés  animables  [Bar18], et Core Animation a 29 propriétés animables [App06]. Pour gérer ce nombre croissant de propriétés animables, la plupart des frameworks définissent des types génériques qui peuvent être animés (comme IntProperty, ou DoubleProperty), au lieu d'avoir une fonction spécifique pour chacune. Ils améliorent ainsi la flexibilité du choix des propriétés à animer à l'exécution, et réduisent la taille de leur API. Ils indiquent aussi implicitement que toute propriété peut être animée, ou au moins celles qui auraient du sens pour le programmeur.

Pourtant cette flexibilité a un prix. L'animation de types définis par l'utilisateur nécessite de fournir une API avancée, qui expose les détails de bas niveau des systèmes d'animation des frameworks, en particulier les timers et threads. Il en résulte des APIs d'animation plus larges et des syntaxes lourdes en raison des indirections supplémentaires pour accéder aux types animables. Il se crée aussi une courbe d'apprentissage abrupte entre l'API de base et l'API avancée, qui est susceptible de forcer les programmeurs à s'en tenir autant que possible aux propriétés animables existantes.

Dans cette section nous introduisons un opérateur de durée, pour exprimer les animations par transformation d'appels de mutateurs (setters) en transitions animées. Nous l'illustrons avec le pseudo- code suivant :

object.setProperty(target) during 2s

Cette syntaxe s'étend très simplement aux appels de fonctions (hors méthodes d'objets), cependant dans le cadre de ce travail nous l'avons implémentée dans un langage à objets. Dans le reste de cette section, nous parlerons exclusivement de méthodes, et spécifions les traitements des fonctions lorsque ceux-ci diffèrent. Nous commençons par énumérer les attributs caractérisant une animation, ainsi que les étapes nécessaires pour la construire à partir d'un appel de méthode avec durée. Ensuite, nous

décrivons l'implémentation d'un prototype fonctionnel pour la plateforme Pharo. Pour finir, nous comparons six frameworks d'interaction modernes, et discutons des limites de notre système et l'implication de ce travail pour le reste de ce travail de thèse.

2.4.2 Caractérisation d'une animation

Dans les systèmes interactifs modernes, la transition d'un état initial à un état final est instantanée. Changer la position d'un objet à l'écran le fait disparaître de sa position actuelle, et en même temps apparaître à sa nouvelle position. Ce changement brusque peut casser notre perception d'un seul et même objet s'est déplacé, plutôt qu'un nouvel objet soit apparu à une autre position. Lorsqu'on souhaite souligner le déplacement d'un tel objet, ou maintenir la perception de son unicité, on a recours aux animations pour adoucir la transition dans le temps, afin qu'elle semble continue. La définition qu'en donnent Betrancourt et Tversky est : « any application which generates a series of frames,

so that each frame appears as an alteration of the previous one, and where the sequence of frames is determined either by the designer or the user » [Bet00].

L'animation d'une propriété dans un framework d'interaction consiste à remplacer une transition instantanée par plusieurs modifications mineures de la même propriété, en séquence rapide. La fréquence de ces changements intermédiaires est aussi importante que possible, pour créer une illusion de continuité dans la transition principale. Elle est généralement fixée au maximum de la fréquence de rafraîchissement de l'écran, qui est le nombre de trames générées pouvant être peintes à l'écran chaque seconde. Sur la plupart des écrans, cette fréquence est de 60 Hz, et est parfois ralentie lorsque le rendu d'une trame est trop long.

Chaque modification commence par le calcul d'un temps relatif entre le début et la fin de l'animation  : t = f(now), où t = 0 au début, et t = 1 à la fin. Le temps t ne croît pas nécessairement uniformément dans [0,  1]  : il peut accélérer au démarrage, rebondir avant l'arrêt, et même osciller autour de l'arrivée (voir [Sit19] pour une liste complète). Nous appelons f une fonction de transition (en anglais easing function).

Ensuite, la valeur calculée pour chaque modification est obtenue avec une fonction d'interpolation,

interpolate(start, target, t), qui renvoie start lorsque t vaut 0 et target lorsque t vaut 1. Cette fonction est

spécifique pour chaque type de valeurs, par exemple des couleurs et des positions seraient interpolées avec des algorithmes différents.

Pour décrire le concept d'une propriété animée, nous nous sommes basés sur les 5 aspects de haut

niveau des animations définis par Mirlacher et al. [Mir12], et que nous avons étendus. Nous définissons

donc un objet de transition animée comme contenant :

receveur — l'objet à animer (absent dans le cas d'un appel de fonction)

signature de mutateur — le nom de la méthode modifiant la propriété visée, et les types de ses arguments

valeurs clés — les valeurs de départ et d'arrivée pour chacun des arguments durée — en secondes

fonction d'interpolation — pour chaque argument, spécifique à chaque type

état d'exécution — permet de mettre en pause, de redémarrer, de faire boucler, ou d'inverser la transition animée

2.4.3 L'opérateur de durée

Notre opérateur associe une durée à un appel de fonction, créant un objet de transition animée  <appelFonction> during <durée>. Son interprétation consiste en quatre étapes, comme illustré dans la figure 16.

<appelFonction> during <durée>

extraction des composants de l'appel de fonction

récupération des

valeurs initiales initialisation desinterpolateurs

planification des modifications

futures

objet de transition animée Figure 16 : Les quatre étapes pour transformer un appel de fonction en objet de transition animée

La première étape, extraction des composants de l'appel de fonction, récupère quatre éléments de l'appel de fonction : son receveur (si c'est une méthode d'objet), son nom, les valeurs de ses arguments, et leurs types. Elle annule également l'exécution immédiate de la fonction. Cette étape réifie en fait l'appel, étant donné que nous inspectons ses données, et extrayons ses caractéristiques. Elle nécessite que le langage de programmation supporte l'introspection du code, c'est-à-dire la possibilité d'examiner ses propriétés au lieu de l'exécuter (plus de détails sont donnés dans la partie Implémentation).

Pour la récupération des valeurs initiales, nous avons besoin d'un mécanisme pour obtenir les valeurs courantes d'une propriété ciblée par une méthode ou fonction mutateur. Heureusement, de nombreux frameworks adoptent des conventions de nommage pour les accesseurs (getters) et mutateurs. Qt  [Qt19], par exemple, fait correspondre la plupart des mutateurs setProperty(...) avec des accesseurs getProperty(). Sur la plateforme Smalltalk, la convention est de leur donner le même nom, l'un prenant simplement un argument et l'autre non — comme object property et object property: <value>. Les conventions de nommage sont importantes : sans elles, il faudrait associer explicitement chaque mutateur à l'accesseur correspondant, ce qui nécessiterait d'éditer chaque framework et invaliderait l'intérêt de ce travail. Pour les fonctions à plusieurs arguments, on peut exiger que les accesseurs renvoient plusieurs valeurs si le langage le permet (ex. Python), ou les renvoyer dans des références passées en paramètres. Une fois que l'accesseur est obtenu à partir de son nom, il est appelé dynamiquement pour récupérer les valeurs courantes, qui seront les valeurs initiales de la transition animée. Pour les langages sans appels de fonction dynamiques (dynamic

dispatch) comme le C, une convention de nommage intelligente (ex. property() ⇒

get_property()) permettrait une substitution par le préprocesseur. Autrement, cela nécessiterait un support explicite de la part du compilateur, que nous n'avons pas exploré dans ce travail de thèse.

La troisième étape, initialisation des interpolateurs, attribue une fonction d'interpolation par défaut à chaque argument, en fonction de leur type. Cette fonction pourra être remplacée ultérieurement sur l'objet de transition. Pour les entiers et nombres réels, nous utilisons la formule :

interpolate(start, target, t) = start × (1 − t) + target × t

Les types composites tels que les positions et les couleurs ont leurs champs interpolés séparément sous forme de nombres. Lorsque le langage de programmation supporte le polymorphisme, la formule ci-dessus peut être utilisée pour ces types composites (à condition qu'ils implémentent l'addition et la multiplication à un réel), et ainsi supporter un grand nombre de types par défaut. Néanmoins, il y a des types pour lesquels ce type d'interpolation n'aurait pas de sens, comme les tableaux ou les chaînes de caractères, par exemple en figure  17. Dans ces cas, les programmeurs doivent être en mesure de fournir leur propre fonction d'interpolation.

gentleman