Introduction
Qu'ils s'appellent Motif (Unix), Interface Kit (BeOS), AppKit (Cocoa / MacOS X), Swing (Java) ou Appareance Manager (MacOS) tous les moteurs d'interface graphique suivent à peu pres les mêmes règles de conception. Et ceci pour une simple et bonne raison, les codes de ces moteurs d'interface sont basés sur des structures de données équivalentes.
Réaliser un moteur d'interface grahique peut donc revenir
à retrouver cette structure de données et à l'
implémenter.
C'est ce que nous nous proposons de faire dans le présent
document.
L'implantation se fera en C++ sur plateforme MacOS (mais cela peut être facilement porté sous BeOS, Java, Windows, etc...).
Afin de ne pas déroger à la règle, exposons les pré-requis nécessaires à la bonne compréhension de ce document:
Soit la capture d'écran suivante:
|
L'analyse de cette image nous donne les informations
suivantes: Une fenêtre contient 3 objets:
|
Si l'on passe cette image au rayon X (en cachant les bords de la fenêtre), on obtient le cliché suivant:
|
L'analyse devient alors: Un rectangle: A contient 3 rectangles: B, C et D. Le rectangle B contient 2 rectangles: E et F |
class myGraphicObject { public: myGraphicObject(void) { in_=next_=NULL; } |
Le code servant à créer la hiérarchie ressemblerait quant à lui à ceci:
myGraphicObject A,B,C,D,E,F; A.in_=B; B.next_=C; C.next_=D; B.in_=E; E.next_=F; |
class myGraphicObject { protected: Rect rectangle_; // BRect ou NSRect ou Rectangle public: myGraphicObject(Rect); myGraphicObject * in_; myGraphicObject * next_; void DrawEveryObject(void); virtual void DrawObject(void); }; |
myGraphicObject::myGraphicObject(Rect inRectangle_) { rectangle_=inRectangle_; in_=next_=NULL; } void myGraphicObject::DrawEveryObject(void) { DrawObject(); if (in_!=NULL) in_->DrawEveryObject(); if (next_!=NULL) next_->DrawEveryObject(); } void myGraphicObject::DrawObject(void) { ::ForeColor(30); ::PaintRect(&rectangle_); ::ForeColor(33); ::FrameRect(&rectangle_); } |
Il suffit maintenant de modifier le code principal:
myGraphicObject * A,* B,* C,* D,* E, *F; Rect r; ::SetRect(&r,0,0,215,138); A = new myGraphicObject(r); ::SetRect(&r,15,18,196,88); B = new myGraphicObject(r); ::SetRect(&r,63,102,122,121); C = new myGraphicObject(r); ::SetRect(&r,137,102,196,121); D = new myGraphicObject(r); ::SetRect(&r,25,40,101,57); E = new myGraphicObject(r); ::SetRect(&r,25,63,101,80); F = new myGraphicObject(r); A->in_=B; B->next_=C; C->next_=D; B->in_=E; E->next_=F; |
|
|
Maintenant que notre splendide interface se dessine correctement, on aimerait bien pouvoir interagir avec elle. Pour l'instant tout clic dans les cadres ne produit rien.
L'interaction sur clic-souris se produit en 2 étapes:
L'algorithme décrivant cette action pourrait être conçu ainsi:
Le clic se produit-il dans l'objet ?
|
Ce qui nous donnerait la fonction suivante:
myGraphicObject * myGraphicObject::Who(Point inPoint) { if (::PtInRect(pt,&rectangle_)==true) { if (in_!=NULL) { myGraphicObject temp=in_->Who(inPoint); if (temp!=NULL) return temp; } return this; } else { if (next_!=NULL) return next_->Who(inPoint); } return NULL; } |
Cette fonction pourrait sembler parfaite si ne surgissait un problème majeur.
Soit l'interface suivante:
Notre hiérarchie devient la suivante:
class myGraphicObject { protected: Rect rectangle_; public: myGraphicObject(Rect); myGraphicObject * in_; myGraphicObject * next_; myGraphicObject * previous_; void DrawEveryObject(void); virtual void DrawObject(void); myGraphicObject * Who(Point); }; |
L'implantation du code devenant celle-ci:
myGraphicObject::myGraphicObject(Rect inRectangle_) { rectangle_=inRectangle_; previous_=in_=next_=NULL; } myGraphicObject * myGraphicObject::Who(Point inPoint) { if (::PtInRect(inPoint,&rectangle_)==false) { if (previous_!=NULL) return previous_->Who(inPoint); else return NULL; } if (in_!=NULL) { myGraphicObject * tObject; tObject=in_; while (tObject->next_!=NULL) { tObject=tObject->next_; } tObject=tObject->Who(inPoint); if (tObject==NULL) return this; else return tObject; } return this; } // Les autres méthodes sont inchangées // [...] myGraphicObject * A,* B,* C,* D,* E, *F; Rect r; ::SetRect(&r,0,0,215,138); A = new myGraphicObject(r); ::SetRect(&r,15,18,196,88); B = new myGraphicObject(r); ::SetRect(&r,63,102,122,121); C = new myGraphicObject(r); ::SetRect(&r,137,102,196,121); D = new myGraphicObject(r); ::SetRect(&r,25,40,101,57); E = new myGraphicObject(r); ::SetRect(&r,25,63,101,80); F = new myGraphicObject(r); A->in_=B; B->next_=C; C->previous_=B; C->next_=D; D->previous_=C; B->in_=E; E->next_=F; F->previous_=E; |
Réaction de l'objet au clic souris
Pour vérifier que cela fonctionne bien nous allons sur un clic souris alterner la couleur de fond de l'objet cliqué:
void myGraphicObject::ClicInContent(EventRecord * inEvent) { ::ForeColor(205); ::PaintRect(&rectangle_); while (::StillDown()); DrawEveryObject(); // Pour rétablir un dessin harmonieux } |
Notre moteur fonctionne désormais correctement au niveau dessin et réactivité au clic souris. Cependant il estpour l'instant très contraignant de réaliser la partie création de la hiérarchie. Cela peut etre amélioré.
En simplifiant la seule fonction dont nous ayons besoin est celle qui permette d'imbriquer un élément dans un autre. Cela nous donne la fonction suivante:
void myGraphicObject::AddChild(myGraphicObject * inObject) { if (in_==NULL) { in_=inObject; } else { myGraphicObject * temp=in_; while (temp->next_!=NULL) { temp=temp->next_; } temp->next_=inObject; inObject->previous_=temp; } } |
La création de la hiérarchie devient plus simple:
myGraphicObject * A,* B,* C,* D,* E, *F; Rect r; ::SetRect(&r,0,0,215,138); A = new myGraphicObject(r); ::SetRect(&r,15,18,196,88); B = new myGraphicObject(r); ::SetRect(&r,63,102,122,121); C = new myGraphicObject(r); ::SetRect(&r,137,102,196,121); D = new myGraphicObject(r); ::SetRect(&r,25,40,101,57); E = new myGraphicObject(r); ::SetRect(&r,25,63,101,80); F = new myGraphicObject(r); A->AddChild(B); A->AddChild(C); A->AddChild(D); B->AddChild(E); B->AddChild(F); |
Epilogue
Pour l'instant notre moteur d'interface aussi simple soit-il permet de réaliser des interfaces assez complexes. Cependant afin d'avoir quelque chose de tout à fait complet, il faudrait ajouter les éléments suivants:
NSButton * ok,* cancel; NSBox * box; NSCheckBox * check1,* check2; ok=new NSButton(NSRect(137,102,196,121),"Ok"); ok->SetIdentifier("ok"); AddChild(ok); cancel=new NSButton(NSRect(63,102,122,121),"Cancel"); ok->SetIdentifier("cancel"); AddChild(cancel); box=new NSBox(NSRect(15,18,196,88),"Options"); AddChild(box); check1=new NSCheckBox(NSRect(25,40,101,57),"Option 1"); box->AddChild(check1); check2=new NSCheckBox(NSRect(25,63,101,80),"Option 2"); box->AddChild(check2); |