vendredi 16 décembre 2011

MoSync : Introduction à MAUI

Ce tutoriel donne une introduction à l’API d’interface utilisateur de MoSync (MAUI). Il décrit comment utiliser les classes de la librairie afin de construire des écrans et widgets.

C’est quoi MAUI ?

MAUI est une collection de composantes de l’interface utilisateur que vous pouvez utiliser dans vos propres applications. Il est possible de créer toute une application mobile sans eux, mais ils fournissent des fonctionnalités très utiles pour gagner du temps. Le modèle MAUI est extensible. Vous pouvez créer vos propres composantes avec.
MAUI s’inspire des interfaces graphique modernes telles que Java Swing, Windows forms et QT. Il a par contre été conçu avec les particularités des appareils mobiles prises en compte.

Les Screens (écrans)

Les blocs de construction principaux de Mosync sont les Screens et les Widgets.
Les Screens représentent un affichage d’écran complet. Un screen est généralement associé à une activité telle que le login ou la navigation sur une liste d’albums par exemple. Les Screens sont des emplacements logiques différents dans votre application. L’utilisateur peut ainsi naviguer entre les Screens.
Pour utiliser la classe Screen vous devez inclure l’entête
#include
Afin d’afficher un Screen en particulier (et renvoyer tous les autres Screens en arrière plan), vous utilisez la fonction show. La fonction hide renvoie le Screen en arrière plan.
Les Screens sont des conteneurs de Widgets. Il y’a toujours une Widget principale qui est redimensionnée afin de correspondre à la taille de l’écran. Pour attribuer la Widget principale, utilisez la fonction setMain et pour la récupérer utilisez getMain.
La classe Screen implémente certains listeners d’événements intéressants afin de lui permettre de détecter les événements UI tels que l’appuie sur clavier par exemple.

Widgets

Chaque élément de l’interface graphique est une Widget. Chaque Widget effectue des tâches GUI communes. Exemples :
·         Boutons
·         List boxes
·         Labels
·         Images
Toutes les widgets sont des dérivées directes ou indirectes de MAUI ::Widget. Pour utiliser les Widgets dans votre application vous devez inclure le header correspondant. Par exemple pour inclure un Label :
#include
Une Widget peut être un conteneur pour d’autres Widgets. Par exemple, si vous voulez une image avec du texte au dessus, vous pouvez créer une Image, puis mettre Image comme parent d’une Widget Label. Quand une Widget est détruite, tous ses enfants sont détruits.

Disposition des Widget

L’UI d’un Screen est construit sous forme d’hiérarchie de Widgets.
Il existe une Widget nommée Layout qui peut être utilisée pour définir une grille pour y disposer d’autres Widgets. Une autre Widget nommée ListBox permet de créer des listes horizontales ou verticales de Widgets.
Vous pouvez voir ici une hiérarchie de Widget Layout et ListBox utilisée pour arranger la disposition dans Screen.
Quand vous créez une Widget, vous spécifiez sa taille, sa position et son parent :
Widget* widget = new Widget(0, 0, 240, 320, NULL);
Dans un mobile, les positions x et y sont souvent moins importantes que l’on pourrait croire. Vous verrez qu’en réalité, vous ferez la grande partie de votre positionnement grâce à Layout et ListBox et en attribuant l’espacement avec des fonctions comme w->setPaddingBottom(5); qui va ajouter une marge en bas de la Widget.
Certaines fonctions standards des Widgets changent la couleur de fond :
// Change la couleur de fond de la Widget à un vert sombre
widget->setBackgroundColor(0x30A030);
// S’assurer que la Widget affiche une couleur de fond
widget ->setDrawBackground(true);
Parfois vous voudriez afficher des arrières plans, parfois pas. Dans la Widget ci-dessus, un arrière plan vert sombre est utilisé au lieu d’un thème. Par contre, si vous ajoutez un Layout au dessus d’une image que vous voudriez utiliser comme arrière plan, alors vous devrez ajouter setDrawBackground(false) pour vous assurer que le Layout ne cache pas l’image en affichant au dessus.

Taille et orientation du Screen (écran)

Il existe beaucoup de tailles d’écrans mobiles. Il est recommandé de demander au mobile de vous donner sa taille d’écran avant de commencer l’affichage avec la fonction maGetScrSize() :
MAExtent screenSize = maGetScrSize();
int scrWidth = EXTENT_X(screenSize);
int scrHeight = EXTENT_Y(screenSize);
Pour savoir si l’écran est en portrait ou paysage, vous devez faire un test :
if(scrWidth > scrHeight)
{
// Mode paysage
}
else
{
      // Mode portrait

}

Moblets

Les moblets sont le cœur de la programmation événementielle sur MoSync. Lorsque l’application démarre, c’est la Moblet qui est lancée en premier. La Moblet va créer (au moins) le premier écran de votre application et l’afficher. La Moblet est également responsable de la fermeture de l’application, et de la gestion du système des évenements. Certains événements sont passés à l’écran automatiquement, par exemple les appuis sur touches ou sur écran. D’autres événements doivent être récupérés manuellement tels que la récupération d’une nouvelle position GPS.
Lorsque vous créez un nouveau projet Moblet, vous avez un code comme celui-ci (avec quelques modifications) :
#include    //MAUI utilise Moblet comme base
#include      //Permet la création des écrans (ou screens)
#include //Permet la création des textes Labels
#include "MAHeaders.h"

using namespace MAUtil; //Pour des raisons pratiques d'appel des classes de MAUtil
using namespace MAUI;   //Pour des raisons pratiques d'appel des classes de MAUI

class MyScreen : public Screen {   //Héritage de la classe Screen
ublic:
      MyScreen() {      //Constructeur de l'écran
      }

      void keyPressEvent(int keyCode, int nativeCode) {    //Fermeture de l'application si une touche est appuyée
            maExit(0);
      }

      void keyReleaseEvent(int keyCode, int nativeCode) {
      }
     
      ~MyScreen() {     //Destructeur de l'écran
      }
private:
};

class MAUIMoblet : public Moblet { //Héritage de la classe Moblet
public:
      MAUIMoblet() {    //Construction de la Moblet

            screen = new MyScreen();     //Création de l'écran
            screen->show();   //Affichage de l'écran screen
      }

      void keyPressEvent(int keyCode, int nativeCode) {    //Cette fonction n'est appelée que s'il n'y a aucun écran d'affiché
      }

      void keyReleaseEvent(int keyCode, int nativeCode) {  //Cette fonction n'est appelée que s'il n'y a aucun écran d'affiché
      }
      MyScreen* screen; //Déclaration de l'écran screen

      ~MAUIMoblet() {   //Destruction du Moblet
            delete screen;    //Destruction de l'écran
      }
     
};

extern "C" int MAMain() {    //Fonction princpale avec le même rôle
      Moblet::run(new MAUIMoblet());
      return 0;
};
Vous devriez faire des changements à ce code quand vous commencez un nouveau projet. Tout d’abord, vous devez séparer les classes dans leurs propres fichiers .h et .cpp. En second lieu, la Moblet fait passer les événements appuis sur le clavier et l’écran directement à l’écran. Donc ce n’est pas la peine de définir les ces fonctions de callback dans Moblet, la déclaration dans Screen suffit. Elles peuvent par contre être utiles si vous voulez capturer des événements lorsqu’aucun écran n’est affiché ou pour capturer des événements communs sur tous les écrans.

Screen (écran)

Les écrans sont les principales unités de l’application. Un écran est un conteneur de Widgets et du code qui permet de traiter les interactions avec l’utilisateur. Les différents types de Widgets sont expliqués plus bas. Aussi, le projet MAUI donne un bon exemple de l’utilisation des écrans et Widgets.

Labels

C’est probablement la Widget la plus basique. Un label va ajouter du texte au Screen :
Label* myLabel = new Label(0, 0, scrWidth, scrHeight, NULL, "My Label", 0xFFC0C0, myFont);

Un autre exemple de construction d’un label:
Label* myLabel = new Label(0, 0, scrWidth, scrHeight, NULL);
myLabel->setCaption("My label");


Par défaut, les Labels vont afficher uniquement la première ligne. Pour passer à la 2° ligne automatiquement, vous devez appeler :
myLabel->setMultiLine(true);
Vous pouvez également demander au Label de prendre l’espace minimale requis pour son affichage :
// Autoriser le label à changer de taille verticalement
label->setAutoSizeY(true);
// Interdire au Label de changer de taille horizontalement.
label->setAutoSizeX(false);

Images

Les images sont souvent utilisées comme boutons ou pour l’affichage d’images tout simplement. La création d’image est très simple :
// Crée un nouvel objet image basé sur le Handler IMAGE dans le fichier ressources
Image* image = new Image(0, 0, 100, 100, listbox, true, true, IMAGE);
Les 3 derniers paramètres sont le redimensionnement automatique horizontal, vertical et le MAHandle (identifiant) de la ressource Image. Si le redimensionnement n’est pas automatique, la Widget ne prendra pas automatiquement la taille de l’image.

Si vous voulez ajouter une bordure à l’image, vous pouvez utiliser le padding. Si vous avez utilisé le redimensionnement automatique, l’image sera coupée aux bords. Parce que la Widget aura exactement la même taille de l’image est le padding va consommer l’espace dans la bordure intérieur de la Widget.
// Crée une nouvelle Image basée sur un fichier PNG dans les ressources.
Image* image = new Image(0, 0, 150, 150, listbox, true, true, IMAGE);
image->setPaddingTop(10);
image->setPaddingLeft(10);
Vous pouvez animer des images en écoutant des événements de Timer par exemple, et en changeant le MAHandle de la ressource que vous utilisez pour l’image. Nous verrons ces procédés plus tard.

Layouts

La widget Layout n’affiche rien en elle-même, mais elle organise les widgets dans Screen pour vous. Vous pouvez spécifier une grille dans laquelle les widgets seront placées. Elles seront insérées du haut à gaucher vers le bas à droite. Ne confondez pas cela avec les tables HTML. Il y’a des ressemblances, mais les Layouts ne vous donnent pas toutes les options d’une table HTML.
Pour créer une une Layout, vous spécifiez le nombre de lignes et de colonnes :
Layout *mainLayout = new Layout(0, 0, scrWidth, scrHeight, NULL, 1, 2);
Dans cet exemple, nous avons une colonne et 2 lignes. Nous aurons donc 2 widgets alignées verticalement. Vous ne pouvez pas insérer plus de Widgets que vous avez de cases. Utilisez une ListBox si vous voulez ajouter dynamiquement des Widgets sans vous soucier de leur nombre.
Si vous avez plus d’une colonne, l’espace horizontal est distribué équitablement entre elles. Vous ne pouvez pas avoir une colonne plus large qu’une autre. Chaque widget peut avoir l’espace vertical dont elle a besoin si vous avez une seule colonne. Si vous en avez plus, toutes les Widgets d’une même ligne doivent avoir la même hauteur.
Vous ne pouvez pas positionner une Widget au sein de sa cellule. Les 2 premiers arguments sont donc ignorés. Vous devez augmenter la taille de la Widget pour qu’elle s’affiche avec une bordure.

ListBox

La ListBox est probablement la Widget la plus utile dans la collection standard. Elle ressemble à Layout mais avec 2 différences majeurs. D’abord, elle utilise une seule dimension (horizontale ou verticale mais pas une grille). Puis, elle peut s’étendre au fur et à mesure que vous y insérez des Widgets.
Ne soyez pas tentés d’utiliser le constructeur basique de ListBox. Elle n’aura pas la taille à laquelle vous vous attendez.
Si vous créez une ListBox avec les paramètres par défaut, elle ne va pas glisser.
infobox = new ListBox(0, 0, scrWidth, scrHeight, layout);
Vous devez ajouter tous les arguments spécifiques à la ListBox:
infobox = new ListBox(0, 0, scrWidth, scrHeight, layout, ListBox::LBO_VERTICAL, ListBox::LBA_LINEAR, true);
Vous pouvez décider si elle va glisser horizontalement ou verticalement, si elle va être animée et si elle va rebondir au début quand elle atteindra la fin.
En général, un Screen contient une ListBox insérée dans une Layout. Le schéma en bas est un exemple de disposition à l’écran :
 Vous pouvez faire glisser une ListBox en utilisant les fonctions selectNextItem() ou selectPreviousItem(). Dans cet exemple, les entrées clavier sont capturées et l’application fait glisser la ListBox :
void Menu::keyPressEvent(int keycode)
{
      // Une liste complète des constantes keycode sont disponibles sur
      // http://www.mosync.com/docs/2.0b1/html/maapi_8h.html
      switch(keycode)
      {
      case MAK_SOFTLEFT:
            // Quitte l'application
            maExit(0); // Appelle la moblet pour quitter gracieusement
            break;
      case MAK_8:
      case MAK_DOWN:
            contentBox->selectNextItem(true); // Sélectionne l'élément suivant dans le menu
            break;
      case MAK_2:
      case MAK_UP:
            contentBox->selectPreviousItem(true); // Sélectionne l'élément précédent dans le menu
            break;
      case MAK_5:
      case MAK_FIRE:
      // Décidez quelle action vous souhaiteriez effectuer une fois l'élément sélectionné.
      // Vous pouvez récupérer l'élément sélectionné avec contentBox->getSelectedIndex();
            break;
      }
}
Vous pouvez également décider s’il faut revenir en haut une fois que le défilement passe la dernière Widget avec l’option setWrapping().

EditBox

Une EditBox vous permet de capturer du texte.
EditBox* editBox = new EditBox(
      0, 0, scrWidth, 60, NULL, “”,
      0xFFFFFF, aFont, true, true, 256,
      EditBox::IM_STANDARD
      );
EditBox est la Widget la plus compliquée vu qu’il y’a beaucoup d’options pour capturer les entrées clavier. Vous ne voulez pas par exemple que les boutons de navigation changent de Widget alors que l’utilisateur essaie juste d’entrer son nom. Vous pouvez donc paramétrer EditBox pour gérer sa propre navigation. Quand c’est le cas, EditBox devient un listener des entrées clavier et récupérera celles-ci tout comme Screen. Cela peut poser problème pour distinguer entre la navigation au sein d’EditBox et la navigation dans Screen.
EditBox a un curseur avec la couleur blanche par défaut. Si vous avec un arrière plan blanc, vous devez changer sa couleur vers le noir par exemple :
editBox->setCursorColor(0x000000);
Il y’a 2 méthodes pour entrer le texte. Vous pouvez l’utiliser pour récupérer les caractères alphanumériques ou uniquement numériques. Le symbole # agit comme échangeur temporaire (shift ou majuscule). Si l’utilisateur appuie par exemple sur # puis 4, il aura un G majuscule.
WidgetSkins (thèmes)

La plupart des Widgets ont des thèmes qu’ils appliquent. C’est une image standard qui va être appliquée aux bordures et à l’arrière plan des Widgets pour leur donner un Look&Feel uniforme. Voici un thème qui est utilisé dans l’exemple MAUI. Le thème est composé d’une grille 3x3, et des lignes ont été tracées afin de vous montrer où est définie la grille. Quand il est appliqué à une Widget, les coins restent comme ils sont et les 5 autres sections sont étirées pour occuper l’espace requis. La section 5 est étirée pour remplir l’arrière plan de la Widget. Vous devez faire attention à ça, car un joli dégradé ne sera pas si joli une fois étiré.
Pour définir le thème il suffit de définir les positions horizontales des coupes verticales, puis les positions verticales des coupes horizontales.
gSkin = new WidgetSkin(
                    RES_SELECTED, RES_UNSELECTED,
                    16, 32, 16, 32, true, true);
Les 2 premiers arguments sont les MAHandles des images qui sont utilisées pour définir le thème. Le premier sera appliqué lorsque le Widget est sélectionné, le deuxième lorsqu’il ne l’est pas. Vous spécifiez ensuite les décalages des coupes verticales et les décalages des coupes horizontales. Puis vous précisez si les images que vous utilisez contiennent des zones transparentes (ce qui généralement le cas).
Il est très important que les 2 images utilisées (sélection / pas de sélection) soient exactement de la même taille. Vous pouvez voir ici les thèmes appliqués à des EditBoxes.

Créer vos propres Widgets

Ce qui est intéressant à propos des Widgets  est que vous pouvez créer vos propres Widgets : barres de progression, boutons, calendrier, sliders… Il suffit d’hériter de Widget et d’implémenter la fonction drawWidget().
Voici une Widget ProgressBar :
#ifndef _PROGRESSBAR_H_
#define _PROGRESSBAR_H_
       
#include
#include

using namespace MAUI;
namespace DatiloUI
{
    class ProgressBar : public Widget
    {
    public:
        ProgressBar(int x, int y, int width, int height,Widget* parent = NULL);
        virtual ~ProgressBar();
        int getPercentage();
        void setPercentage(int percentage);
        void drawWidget();
        void setFullImage(Handle image);
        void setEmptyImage(Handle image);
    private:
        Handle mTileFullImage;
        Handle mTileEmptyImage;
        int mPercent;
        void plotSection(MAPoint2d* offset, int width, Handle image);
    };
};
#endif // _PROGRESSBAR_H_
La fonction drawWidget() est essentielle. Elle sera appelée lorsque la Widget doit être affichée. Elle doit afficher son contenu à l’intérieur des bordures de la Widget.
Voici un extrait de la fonction drawWidget() :
void ProgressBar::drawWidget()
{
      int w = this->getWidth() - this->getPaddingLeft() - this->getPaddingRight();
      int amountRemaining = (w * percent) / 100;
      MAPoint2d* offset = new MAPoint2d();
      Point p = this->getPaddedPosition();
      Rect r = paddedBounds;
      offset->x = r.x;
      offset->y = r.y;
      // Plot the full section
      if(mTileFullImage != NULL)
      {
            plotSection(offset, amountRemaining, mTileFullImage);
      }
      else
      {
            // Plot a progress bar
            plotBar(offset, amountRemaining);
      }
      if(mTileEmptyImage != NULL)
      {
            offset->x += amountRemaining;
            plotSection(offset, w - amountRemaining, mTileFullImage);
      }
      delete offset;
}
Vous pouvez voir que vous pouvez récupérer PaddedPosition de la Widget. Il indique où se trouve la Widget dans l’écran. La fonction plotSection() indique l’espace que vous êtes autorisés à utiliser.
void ProgressBar::plotSection(MAPoint2d* offset, int  width, Handle h)
{
      if(mDrawBorder)
      {
            plotBorder();
      }
      MARect* clippedImage = new MARect();
      Rect r = this->getPaddedBounds();
      Extent imageSize = maGetImageSize(h);
      while(width > 0)
      {
            clippedImage->left = 0;
            clippedImage->width = width > EXTENT_X(imageSize) ? EXTENT_X(imageSize) : width;
            clippedImage->top = 0;
            clippedImage->height = r.height > EXTENT_Y(imageSize) ? EXTENT_Y(imageSize): r.height;
            // lprintfln("Drawing image at x:%d, y:%d", offset->x, offset->y);
            Gfx_drawImageRegion(h, clippedImage, offset, 0);
            offset->x += clippedImage->width;
            width -= clippedImage->width;
      }
      delete clippedImage;
      if(writePercentage)
            plotPercentage();
}
La fonction getPaddedBounds vous indique exactement quelle est l’espace que vous êtes autorisé à utiliser sans dessiner dans une autre Widget.
Le moteur (The engine)
Derrière toutes les Widgets se trouve le moteur MAUI. C’est la partie que vous aurez rarement à manier mais qui s’occupe d’afficher vos Widgets.
Le moteur est un singleton, et vous pouvez récupérer une référence avec
Engine& eng = Engine::getSingleton();
Le moteur peut vous permettre de spécifier la police et le thème par défaut. Alors vous n’avez pas à les spécifier à chaque fois que vous les utilisez.
eng.setDefaultFont(standardFont);
eng.setDefaultSkin(standardSkin);

Aucun commentaire: