Comme vous le savez peut-être, je travaille actuellement sur Mishra Reader qui est prévu pour être un client Google Reader sympa à utiliser pour Windows.

Je le développe en utilisant WPF avec le design et l’utilisabilité en tête (en fait quand je dis “je” c’était au début car une petite dream team s’est récemment montée autour du projet sur Codeplex).

Or, il se trouve que je ne suis pas un designer (mais alors pas du tout). Toutefois avec quelques règles simples en tête, j’ai réussi à sortir une application qui visuellement tient la route.

Je vais donc vous donner les quelques tuyaux qui m’ont permis d’arriver là.

Note: Vous noterez avec délectation que les captures présentées ici sont celles de la béta 1 qui sortira ce vendredi Clignement d'œil.

Choix des couleurs

Tout d’abord, il va être nécessaire de garder la cohérence de l’application en sélectionnant un nombre restreint de couleurs.

Pour Mishra Reader, j’en ai retenu 4:

  • Couleur de fond : cette couleur sera utilisée pour remplir le fond des contrôles
  • Couleur d’avant-plan : cette couleur sera utilisée pour dessiner le texte et le bord des contrôles non-sélectionnés
  • Couleur d’avant-plan plus sombre : J’ai utilisé cette couleur lorsque je voulais écrire un texte à coté d’une version qui devait le décrire. Ainsi le texte principal est écrit avec la couleur d’avant-plan et le texte d’aide avec la version plus sombre
  • Couleur d’accentuation : Comme pour Windows Phone, j’ai une couleur spéciale pour attirer l’attention de l’utilisateur

Donc avec 4 couleurs (et pas une de plus), nous pouvons produire quelque chose comme cela :

image_thumb[2]

Vous devez donc être concentré sur le fait de garder cette cohérence de couleurs et donc de choisir la bonne au bon endroit.

Prenons par exemple l’écran des options. j’ai refait le modèle des checkbox et j’ai utilisé la couleur d’accentuation. Il est bien évident que cette dernière doit être visible uniquement si la checkbox est active :

image_thumb[6]

Nous pouvons également noter que le message d’aide utilise bien la couleur d’avant-plan sombre pour se distinguer du titre de l’option.

Finalement, comme je voulais permettre à chaque utilisateur de choisir sa propre couleur d’accentuation, je ne pouvais pas laisser le logo de l’application dans sa couleur d’origine. J’ai donc développer un petit bout de code qui se charge de partir du logo en niveaux de gris pour en faire un logo avec la bonne couleur :

image_thumb[12]

Le code en question est le suivant :

  1. public static WriteableBitmap GetColoredLogo(OptionsManager optionsManager)
  2. {
  3.     WriteableBitmap bmp = new WriteableBitmap(new BitmapImage(new Uri("pack://application:,,,/Assets/MishraReader.png")));
  4.     int size = bmp.PixelHeight * bmp.PixelWidth * bmp.Format.BitsPerPixel / 8;
  5.     byte[] pixels = new byte[size];
  6.     Color accentColor = (Color) ColorConverter.ConvertFromString(optionsManager.AccentColorCode);
  7.  
  8.     bmp.CopyPixels(pixels, bmp.PixelWidth * bmp.Format.BitsPerPixel / 8, 0);
  9.  
  10.     for (int index = 0; index < size; index += 4)
  11.     {
  12.         byte b = pixels[index];
  13.         byte g = pixels[index + 1];
  14.         byte r = pixels[index + 2];
  15.  
  16.         if (r > 250 && g > 250 && b > 250)
  17.             continue;
  18.  
  19.         pixels[index + 2] = (byte)Range(0, 255, accentColor.R * Range(0, 1, r / 255.0f + 0.4f));
  20.         pixels[index + 1] = (byte)Range(0, 255, accentColor.G * Range(0, 1, b / 255.0f + 0.4f));
  21.         pixels[index] = (byte)Range(0, 255, accentColor.B * Range(0, 1, b / 255.0f + 0.4f));
  22.     }
  23.  
  24.     bmp.WritePixels(new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight), pixels, bmp.PixelWidth * bmp.Format.BitsPerPixel / 8, 0);
  25.  
  26.     return bmp;
  27. }

Application en mouvement

Le second point à prendre en compte est la mise en place d’animations. Vous ne devriez jamais changer quelque chose dans l’interface sans passer par une animation. Et de plus, vous ne devriez jamais faire une action qui pourrait bloquer le rendu et les animations.

Dans Mishra, j’ai passé beaucoup de temps à vérifier que:

  • Mon code tourne au maximum dans un thread en background sans jamais trop interférer avec le thread principal. Pour ce faire, j’utilise toujours le Dispatcher avec une priorité minimale afin de ne pas perturber les animations :
  1. Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>

 

  • Chaque changement d’état d’un contrôle (ou tout du moins un maximum) est associé à une animation

A propos de ces dernières, il est souvent mieux de rompre leur linéarité en utilisant des fonctions de Easing. Des fonctions telles que BackEase ou CircleEase permettent de donner une impression de performance produisant des animations souples.

Par exemple, voici le code des animations pour afficher un contrôle :

  1. <Storyboard x:Key="showSubscription">
  2.     <DoubleAnimation Duration="0:0:0.300" To="1" Storyboard.TargetName="addSubscription" Storyboard.TargetProperty="Opacity"/>
  3.     <DoubleAnimation Duration="0:0:0.300" To="0" Storyboard.TargetName="addSubscription"
  4.                      Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
  5.         <DoubleAnimation.EasingFunction>
  6.             <BackEase Amplitude="0.6" EasingMode="EaseOut"/>
  7.         </DoubleAnimation.EasingFunction>
  8.     </DoubleAnimation>
  9.     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="addSubscription" Storyboard.TargetProperty="Visibility">
  10.         <DiscreteObjectKeyFrame KeyTime="0">
  11.             <DiscreteObjectKeyFrame.Value>
  12.                 <Visibility>
  13.                     Visible
  14.                 </Visibility>
  15.             </DiscreteObjectKeyFrame.Value>
  16.         </DiscreteObjectKeyFrame>
  17.     </ObjectAnimationUsingKeyFrames>
  18. </Storyboard>

Comme vous pouvez le voir, j’anime l’opacité de manière linéaire mais j’introduis une fonction de type BackEase pour la translation.

Ainsi avec l’intégralité de l’interface qui bouge de manière souple, l’application donne une impression d’efficacité (ressenti de la performance vs. performance) même si vous êtes en train de faire de lourds calculs en tâche de fond.

Aider vos utilisateurs : montrer uniquement ce dont ils ont besoin

Finalement, la dernière astuce est à propos de la simplicité. “Simple is beautiful”. Ce qui en gros veut dire qu’il ne faut pas faire des interfaces surchargées de milliers d’interaction.

Au contraire, vous devez essayer de présenter une interface sobre tout en faisant apparaitre les éléments de contrôles quand ils sont nécessaires. Par exemple, nous pouvons voir ci-dessous que des icones d’action apparaissent uniquement quand la souris survole un élément :

image_thumb[21]

Conclusion

Bien sûr il existe une quantité astronomiques d’autres points à prendre en compte si l’on veut produire une interface efficace. Toutefois en tant que développeur, suivre ces simples recommandations semble être un bon début !