Concevoir la prochaine génération d'interface utilisateur multiplateforme avec Qt

| Johan Thelin | Traduction : Guillaume Belz | Qt Quarterly |

Le framework Graphics de Qt apporte différents outils permettant d'améliorer l'interface utilisateur de vos applications en modifiant l'apparence des objets graphiques et en les animant.

Cet article est une traduction autorisée de Building Next Generation UIs Across Platforms with Qt, par Johan Thelin.

Commentez

- Titre : Concevoir la prochaine génération d'interface utilisateur multiplateforme avec Qt

- Auteurs : Johan Thelin, Traduction : Guillaume Belz, Qt Quarterly

- Parution : 14 avril 2010

- Mise à jour : 1 août 2011

- Licence : Copyright ® 2009 Johan Thelin. 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. L'article original

- II. Introduction

- III. L'apparence de l'application

- III-A. Les effets graphiques

- III-B. Les classes d'animations

- III-C. Ajouter des états

- IV. Qu'en est-il des widgets ?

- V. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Building Next Generation UIs Across Platforms with Qt de Johan Thelin paru dans la Qt Quarterly Issue 32.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) incluse dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Introduction

Avec Qt 4.6, une multitude de nouvelles classes ont été ajoutées. Toutes ces classes vont dans la même direction : des interfaces modernes, animées, et réactives.

En regardant les interfaces utilisateur d'un point de vue utilisateur, les appareils mobiles actuels ont commencé à employer des interfaces utilisateur imitant le monde réel. Cela a permis d'ajouter des effets graphiques avancés tels que les transformations en trois dimensions, le flou, l'opacité - mais aussi les transitions fluides et la simulation physique. Dans ce type d'application, utiliser des blocs de construction de l'interface utilisateur standard n'est pas toujours désiré. Au contraire, l'interface utilisateur doit s'intégrer dans l'appareil physique qui a sa propre interface.

Qt permet d'aborder cette situation selon deux approches complémentaires. La première est constituée des méthodes d'interaction avec l'utilisateur. Qt supporte le multi-touch et la gestuelle en complément du clavier et de la souris. L'autre approche correspond aux graphiques, aux transitions et aux effets. Nous allons nous concentrer sur cette partie dans cet article.

Les nouvelles classes de graphiques et d'animations de Qt 4.6 peuvent être divisées en trois groupes : les effets graphiques, les animations de propriétés et le framework de machine à état. Ces trois composants forment ensemble la base d'une interface utilisateur moderne et animée. Avant de pouvoir utiliser ces outils, nous avons besoin de donner à l'application une apparence correcte.

III - L'apparence de l'application

Traditionnellement, les applications de bureau sont fabriquées à partir de widgets rectangulaires. Les widgets gèrent leur propre contenu ainsi que le contenu de leurs enfants dans un rectangle qui leur est alloué. Ils sont également conçus pour être positionnés les uns par rapport aux autres et pour rester à peu près à la même place la plupart du temps. Le résultat est une interface plus ou moins rigide construite sur des rectangles. Les environnements de bureau jouent parfois avec les coins arrondis des boutons et ajoutent un peu de couleur. Aujourd'hui, les utilisateurs veulent plus.

Classique

Moderne

Pour contourner les limitations des widgets, il est possible d'utiliser le framework Graphics View comme base de votre interface utilisateur. Ce framework a remplacé l'ancienne API mais l'a aussi étendu. Avec Graphics View, il est possible de faire pivoter, de modifier l'échelle, de détacher et de déplacer des éléments, mais également d'appliquer des pseudo-transformations tridimensionnelles comme la rotation d'un objet autour de l'axe x ou y.

Au cours de l'évolution de Qt, de plus en plus de fonctionnalités ont été ajoutées, y compris la capacité à intégrer des widgets dans des scènes graphiques. Parmi les apports de Qt 4.6, on trouve un framework de classes pour l'animation, des effets graphiques, les états et des transitions pour l'interface utilisateur. En étant capable de mélanger des éléments graphiques avec des widgets et des animations, le résultat permet de créer des interfaces utilisateurs modernes.

Lors de la création d'une interface utilisateur moderne, le travail des designers est d'obtenir la bonne combinaison d'effets. Le développeur qui implémente l'interface doit décider quel outil utiliser pour chaque partie du design. L'outil tout prêt est le framework Animation. Ces classes définissent, regroupent et ordonnent les animations. En plus des animations, un ensemble d'effets, tels que le flou et l'ombrage, sont également disponibles.

Pour conduire une telle expérience de l'utilisateur, une sorte de machine à état est nécessaire. Cela est permis par le framework de machine à état de Qt. Ici, plusieurs états peuvent être définis ainsi que des transitions entre eux avec des animations et des effets. Il n'est pas nécessaire de créer une liste de booléens et une variable globale représentant l'état courant associée à des minuteries et des slots utilisateurs. Une configuration unique d'une machine à états encapsule l'interface utilisateur complète avec des transitions et des animations couplées. Avec ce problème administratif réglé, les développeurs peuvent se concentrer sur l'ajout de fonctionnalités moderne à l'application.

III-A - Les effets graphiques

L'une des caractéristiques nouvelles de Qt 4.6 est l'ensemble des nouvelles classes d'effets graphiques. La classe de base, QGraphicsEffect, forme la base d'un framework ajoutant des effets tels que le flou et les ombres projetées. Ces effets peuvent être appliqués aux éléments d'interface utilisateur comme les widgets et les éléments graphiques. Il est possible de créer des effets personnalisés qui peuvent être utilisés dans les mêmes circonstances, ce qui signifie que vous pouvez faire ce que vous voulez.

Tous les effets graphiques sont contrôlés par un certain nombre de propriétés. Cela signifie non seulement que les effets peuvent être adaptés pour chaque application, mais aussi qu'ils peuvent être modifiés par le système d'animation que nous examinerons plus tard.

Flou

Ombre projetée

L'un des effets disponibles est l'effet de flou par la classe QGraphicsBlurEffect. Elle applique un flou sur son élément source en fonction de la propriété blurRadius. Un petit rayon donne peu de flou tandis qu'un grand rayon conduit à un flou plus important. Pour appliquer l'effet graphique à un élément graphique, utilisez la méthode QGraphicsItem::setGraphicsEffect() comme indiqué ci-dessous.

QGraphicsBlurEffect *effect = new QGraphicsBlurEffect(this);
effect->setBlurRadius(0);

QGraphicsPixmapItem *item = new QGraphicsPixmapItem();
// ...
item->setGraphicsEffect(effect);

Pour obtenir plus d'effets, il est possible de les combiner. Ceci est obtenu en ajoutant une hiérarchie aux objets graphiques et en appliquant un effet sur chaque niveau. Par exemple, des effets de flou et d'opacité sont appliqués ensemble dans l'exemple suivant. La classe QGraphicsRectItem est l'élément que lequel nous voulons appliquer les effets tandis que la classe QGraphicsItemGroup est utilisé pour le second effet.

Des rectangles avec des effets de flou et d'opacité

III-B - Les classes d'animations

Un des éléments clés d'une interface utilisateur moderne est d'avoir des transitions animées et fluides au lieu des changements immédiats et soudains. Cela peut aider l'utilisateur à comprendre comment les éléments sont reliés entre eux et rendre l'interface utilisateur plus réaliste. Dernier point mais non des moindres, il contribue également à améliorer l'attrait visuel de l'ensemble du système.

Qt 4.6 est livré avec un tout nouveau framework d'animation. L'animation aurait pu être implémentée en utilisant des versions antérieures de Qt mais celle-ci aurait été limitée à la fourniture de time lines pour les animations et les classes animant des transformations d'objets graphiques. Le nouveau framework rend possible d'animer n'importe quelle propriété d'un QObject.

Le framework d'animation se compose de nombreuses classes basées sur la classe QAbstractAnimation. Fondamentalement, les classes utilisaient soit des outils facilitant la synchronisations des animations, soit des animations de propriétés. La synchronisation permet aux animations d'être exécutées en série ou en parallèle. La classe QPropertyAnimation est probablement celle que vous utiliserez le plus souvent. Elle permet d'animer individuellement les propriétés des QObject.

Lors de l'animation d'une propriété, les valeurs de début et de fin doivent être précisées. Cela est obtenu en spécifiant les QPropertyAnimation::setStartValue() et QPropertyAnimation::setEndValue() pour cette propriété. Il est également possible de fixer des valeurs intermédiaires en utilisant la fonction QPropertyAnimation::setKeyValueAt(). Lorsque vous définissez la valeur de propriété, l'animation est supposée fonctionner entre 0 et 1. Ce n'est pas tout à fait vrai puisse que les animations peuvent utiliser les différentes courbes de relaxation. Ces courbes peuvent se déplacer légèrement en dehors de la gamme de 0 à 1 comme illustré ci-dessous.

Pour illustrer toutes ces techniques, le code ci-dessous montre une animation simple qui permet à un QPushButton d'apparaître dans un widget conteneur. Comme cette animation ne sera utilisée qu'une seule fois, l'argument QAbstractAnimation::DeleteWhenStopped est passé à la méthode de démarrage. Cela garantit que l'objet animation soit supprimé quand il est arrêté.

QPropertyAnimation *animation = new QPropertyAnimation(button, "geometry");
animation->setStartValue(startRect);
animation->setEndValue(endRect);
animation->setEasingCurve(QEasingCurve::OutBounce);
animation->start(QAbstractAnimation::DeleteWhenStopped);

La même technique, montrée ici pour des widgets, peut être utilisée pour n'importe quel QObject. Cependant, la classe couramment utilisée pour cela, QGraphicsItem, n'hérite pas de QObject. Pour résoudre ce problème, un élément graphique personnalisé doit être créé dans ce cas. Ce n'est pas aussi lourd qu'il n'y paraît. Tout ce que la nouvelle classe doit faire est d'hériter de QObject et d'une des classes dérivant de QGraphicsItem puis de rendre accessible la propriété voulue. Par exemple, la classe CustomRectItem ci-dessous rend accessible la propriété de rotation d'un QGraphicsRectItem pour le système d'animation. Aucun code supplémentaire n'est nécessaire, à l'exception d'un constructeur court et de la ligne Q_PROPERTY.

class CustomRectItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(qreal rotation READ rotation WRITE setRotation)

public:
CustomRectItem(const QRectF &rect)
: QObject(0),QGraphicsRectItem(rect)
{ }
};

Compte tenu de la définition de la classe CustomRectItem, la rotation de l'objet est aussi simple que de créer un objet QPropertyAnimation pour cette classe et de le démarrer.

QPropertyAnimation *animation = new QPropertyAnimation(item, "rotation", this);
animation->setStartValue(0);
animation->setEndValue(90);
animation->start();

III-C - Ajouter des états

Un cas habituel d'utilisation des animations est de changer entre différents états de l'interface utilisateur comme une vue en liste, une vue détaillée et un mode d'édition. La plupart des développeurs ont entendu parler, et même peut-être déjà utilisé, des machines à états. Celles-ci sont parfaites pour définir des états et les transitions entre ces états.

Qt 4.6 est livré avec un tout nouveau framework de machine à états qui vous permet de faire cela. Grâce à ce framework, la valeur des propriétés des objets peut être définie pour un ensemble d'états. Une transition peut ensuite être ajoutée entre les différents états et l'ensemble peut alors être démarré.

Un exemple simple montrant cela peut être construit à partir de nos exemples précédents. Nous prenons une scène graphique avec deux rectangles inclus dans des groupes (pour utiliser deux effets). Nous définissons ensuite deux états, le premier dans lequel le rectangle vert apparaît opaque à 100% sans flou tandis que le rectangle jaune est opaque à 50% et flou, le second état où ces effets sont appliqués au rectangle vert au lieu du jaune.

La première chose pour créer cela est de mettre en place la scène : vous pouvez voir comment faire dans le code source disponible en téléchargement à la fin de cet article. Les résultats produits sont des rectangles et des groupes appelés greenRect, yellowRect, greenGroup et yellowGroup. Nous pouvons ensuite leur appliquer des effets puis ajouter ces éléments à la scène.

QGraphicsOpacityEffect *greenOpacity = new QGraphicsOpacityEffect();
QGraphicsOpacityEffect *yellowOpacity = new QGraphicsOpacityEffect();
QGraphicsBlurEffect *greenBlur = new QGraphicsBlurEffect();
QGraphicsBlurEffect *yellowBlur = new QGraphicsBlurEffect();

greenRect->setGraphicsEffect(greenOpacity);
yellowRect->setGraphicsEffect(yellowOpacity);
greenGroup->setGraphicsEffect(greenBlur);
yellowGroup->setGraphicsEffect(yellowBlur);

Quand tout cela est en place, le framework de machine à états entre en scène. Nous créons deux états, nommés greenState et yellowState. L'initialisation de l'état greenState est indiquée ci-dessous. Notez que les propriétés modifiées par cet état sont définies à l'aide de la méthode assignProperty() qui prend un pointeur d'objet, un nom de propriété et une valeur. La valeur est traitée comme un QVariant, donc vous pouvez utiliser ce que vous voulez dedans. Vous pouvez même ajouter le support de vos propres types en utilisant le système de type de Qt, mais je m'égare.

QState *greenState = new QState();
greenState->assignProperty(greenOpacity, "opacity", 1);
greenState->assignProperty(greenBlur, "blurRadius", 0);
greenState->assignProperty(yellowOpacity, "opacity", 0.5);
greenState->assignProperty(yellowBlur, "blurRadius", 5);

Pour chaque état, une série de transitions doit être ajoutée. Ces transitions permettent de passer de l'état actuel vers un autre état. Comme nous n'avons que deux états, ça nous laisse avec une transition par état.

Avant de regarder le code, nous allons discuter des transitions. Toutes les transitions héritent de la classe QAbstractTransition. À ce niveau, les états d'origine et de fin peuvent être spécifiés. Il y a donc des spécialisations de cette classe abstraite représentant une transition en fonction de la façon dont la transition est déclenchée. Par exemple, il y a des transitions déclenchées par un événement (QEventTransition) qui peut intercepter différents événements : l'appui sur une touche spécifique du clavier (QKeyEventTransition) ou une action de la souris (QMouseEventTransition). Il y a également la classe QSignalTransition qui implémente une transition déclenchée par un signal spécifique. C'est la classe que nous utilisons dans le code ci-dessous.

QSignalTransition *t = new QSignalTransition(this, SIGNAL(yellowClicked()));
t->setTargetState(yellowState);
greenState->addTransition(t);

Maintenant que tous les états sont créés, ils doivent être ajoutés à l'objet QStateMachine qui gère l'ensemble. Comme vous pouvez le voir, nous ajoutons tous les états dans la machine à états mais un seul comme étant l'état initial. Cela est nécessaire pour donner à la machine à états de point de départ (n'oubliez pas que toutes les transitions ont un état d'origine).

QStateMachine *machine = new QStateMachine(this);
machine->addState(greenState);
machine->addState(yellowState);
machine->setInitialState(greenState);

Maintenant, il est temps de dire à la machine à états comment changer d'un état à un autre. C'est là que les classes d'animation que vous avez vu plus tôt entrent en jeu. Il est possible de préciser exactement quelle animation utiliser pour chaque objet, propriété et transition. Mais pour faire avancer les choses, vous pouvez également spécifier une animation par défaut pour chaque objet et propriété à un niveau de la machine à états.

machine->addDefaultAnimation(new QPropertyAnimation(greenBlur, "blurRadius"));
machine->addDefaultAnimation(new QPropertyAnimation(yellowBlur, "blurRadius"));
machine->addDefaultAnimation(new QPropertyAnimation(greenOpacity, "opacity"));
machine->addDefaultAnimation(new QPropertyAnimation(yellowOpacity, "opacity"));

Avant de pouvoir démarrer l'application et profiter des transitions amusantes, il reste une étape finale : démarrer la machine à états.

machine->start();

IV - Qu'en est-il des widgets ?

Jusqu'à présent, nous nous sommes concentrés sur les vues graphiques et les éléments, mais presque tout le monde utilise une interface utilisateur basée sur les widgets. La perspective de devoir remplacer tout le travail déjà accompli semble être une tâche assez lourde. La bonne nouvelle est que presque tous les effets peuvent aussi bien être utilisés dans un environnement basé sur les widgets.

Commençons par un exemple. Regardons les deux états de dialogue ci-dessous. Ici, les widgets inutilisés sont placés en dehors du widget contenant. Une autre option aurait été de modifier la visibilité des éléments.

Ces états sont traduits dans le code ci-dessous par le biais des variables viewState et editState. Les états spécifiques à chaque état sont définis par des appels à assignProperty(). Comme vous pouvez le voir, pour rendre possible l'utilisation de ces effets, vous devez sacrifier les layouts. C'est malheureux, mais en explorant les différentes possibilités, vous trouverez le bon mélange entre le bricolage et la flexibilité des layouts. (NDLT : il est maintenant possible d'utiliser des layouts avec la machines à états en masquant ou affichant les objets à l'aide de la propriété visible des QWidget).

QState *viewState = new QState();
viewState->assignProperty(editButton, "geometry", QRect(180, 5, 70, 30));
viewState->assignProperty(okButton, "geometry", QRect(110, -35, 70, 30));
viewState->assignProperty(cancelButton, "geometry", QRect(180, -35, 70, 30));
viewState->assignProperty(m_textLabel, "geometry", QRect(5, 5, 175, 30));
viewState->assignProperty(m_textEdit, "geometry", QRect(-110,5, 105, 30));

QState *editState = new QState();
editState->assignProperty(editButton, "geometry", QRect(180, 55, 70, 30));
editState->assignProperty(okButton, "geometry", QRect(110, 5, 70, 30));
editState->assignProperty(cancelButton, "geometry", QRect(180, 5, 70, 30));
editState->assignProperty(m_textLabel, "geometry", QRect(-200, 5, 175, 30));
editState->assignProperty(m_textEdit, "geometry", QRect(5,5, 105, 30));

Maintenant que les états ont été définis, il est temps d'ajouter les transitions.

viewState->addTransition(editButton, SIGNAL(clicked()), editState);
editState->addTransition(okButton, SIGNAL(clicked()), viewState);
editState->addTransition(cancelButton, SIGNAL(clicked()), viewState);

Nous ajoutons ensuite les états au gestionnaire avant d'ajouter les animations par défaut. Les widgets m_textLabel et m_textEdit sont déplacés à l'aide de la courbe standard tandis que les boutons se déplacent selon un chemin plus "dynamique" en utilisant la courbe QEasingCurve::InOutBack.

QStateMachine *machine = new QStateMachine(this);
machine->addState(viewState);
machine->addState(editState);
machine->setInitialState(viewState);

QPropertyAnimation *pa;
machine->addDefaultAnimation(pa = new QPropertyAnimation(editButton, "geometry"));
pa->setEasingCurve(QEasingCurve::InOutBack);

// ...

machine->addDefaultAnimation(new QPropertyAnimation(m_textEdit, "geometry"));

Après avoir défini tous les états et les transitions, l'ensemble de l'interface utilisateur prend vie dès que l'instance de QStateMachine est démarrée. Vos utilisateurs devraient avoir une agréable surprise en cliquant sur le bouton Edit pour la première fois.

V - Divers

Le code source de l'exemple présenté dans cet article est disponible au téléchargement à l'adresse suivante : qq32-blurstates.zip.

Johan Thelin est l'auteur du livre Foundations of Qt Development de chez Apress et a un faible pour les systèmes embarqués. Il est également impliqué sur le site QtCentre ainsi que dans le développement de logiciels et la rédaction technique.

Merci à dourouc05 et à Michaël pour leur relecture et leurs conseils.

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé la traduction de cet article !