G2D *** .. include:: ../BoutonGoogleTrad.rst SDL3 — (:cpp:`Simple DirectMedia Layer 3`) est une bibliothèque C++ multiplateforme pour gérer les fenêtres, les entrées, l'audio et le rendu à l'écran. Elle sert de couche d’abstraction entre l’application, le système d’exploitation et le matériel. Ce n’est pas un moteur de jeu, mais une base bas niveau pour créer des applications interactives. Nous allons présenter l'architecture de la librairie G2D. G2D demo ======== Nous avons écrit une surcouche de SDL3 : G2D (:cpp:`Graphics for 2D`) pour faciliter la prise en main de la SDL. La librairie G2D fournit 7 classes et une trentaine de fonctions convrant 95% des besoins d'un projet graphique en 2D. Nous allons voir comment est construite une application G2D. Téléchargez le projet à partir du lien reçu par mail. Décompressez le sur votre disque et ouvrez le répertoire du projet : .. image:: rep.png Vous trouvez plusieurs répertoires et fichiers C++ : - :cpp:`font`: répertoire contenant les fichiers de police texte - :cpp:`G2D` : contient les sources de la librairie :cpp:`G2D`, vous ne devez pas modifier les fichiers dans ce répertoire - :cpp:`BUILD` : contient les fichiers compilés du projet. - :cpp:`SDL3import` : contient les sources de la librairie :cpp:`SDL3`, vous ne devez pas toucher à ce répertoire - :cpp:`ìmages` : répertoire contenant les images et les icônes utilisées dans le projet. **Ajoutez vos nouvelles images dans ce répertoire**. - :cpp:`Color.h`, :cpp:`Event.h`, :cpp:`Font.h`, :cpp:`Graphic.h`, :cpp:`Model.h` et :cpp:`V2.h` les headers principaux des classes fournies par :cpp:`G2D` - :cpp:`student.cpp` le fichier dans lequel vous allez commencer à ajouter du code - :cpp:`PICTOR.sln` et :cpp:`PICTOR.vcxproj` les fichiers de projet :cpp:`Visual Studio` .. note:: Le répertoire :cpp:`BUILD` peut être supprimé avant de transmettre votre projet car il occupe généralement beaucoup de place. Solution explorer ----------------- Double-cliquez sur :cpp:`PICTOR.sln` ou :cpp:`PICTOR.vcxproj` pour ouvrir le projet de base dans Visual. Si le :cpp:`Solution Explorer` de Visual Studio n'est pas ouvert, appuyez sur les touches :cpp:`CTRL ALT L`. Dépliez le projet :cpp:`PICTOR` pour voir la hiérarchie du projet : .. image:: pictor.png Pour éditer un fichier, double-cliquez tout simplement sur les noms des fichiers .h ou .cpp. Remarquez que la hiérarchie du projet correspond à sa structure sur le disque. Student.cpp =========== Appuyez sur la touche :cpp:`F5` pour lancer le projet, une démo affiche le résultat suivant : .. image:: demo.png Plusieurs formes apparaissent à l'écran. Remarquez qu'en déplaçant la souris, la mire rouge bouge en même temps. Editez ce fichier et examinez la fonction :cpp:`drawApp()`, elle contient tous les appels de fonctions pour dessiner cette démo : .. code-block:: void drawApp(Graphics& G, const Model & D) { G.clearWindow(Color::Black); // Monospaced font = all characters have the same width Font F1 = { FontFolder::LiberationMono, 25, false, false, Color::Yellow }; G.drawString(F1, V2(90, 420), "Liberation font"); G.drawString(F1, V2(90, 370), "Bold+Italic"); G.drawString(F1, V2(90, 320), "Italic"); G.drawString(F1, V2(90, 270), "Bold"); G.drawString(F1, V2(90, 220), "Regular"); ... // drawing a button background with a texture G.fillRectangle(V2(530, 300), V2(64, 64), Color::Gray); G.drawRectWithTexture("64_ellipsetool.png", V2(530, 300), V2(64,64),0); // draw geometric lines G.drawLine(V2(670, 500), V2(760, 590), Color::Red, 1); G.drawLine(V2(870, 500), V2(960, 590), Color::Red, 5); G.drawCircle(V2(730, 400), 60, Color::Cyan, 1); G.drawRectangle(V2(670, 220), V2(120, 80), Color::Yellow, 3); G.fillCircle(V2(920, 400), 50, Color::Beige); G.fillRectangle(V2(870, 220), V2(120, 80), Color::Magenta); // draw moving cursor V2 P = D.currentMousePos; int r = 7; Color c = Color::Red; G.drawLine(P + V2( r, 0), P + V2(-r, 0), c); G.drawLine(P + V2( 0,-r), P + V2( 0, r), c); } Cette fonction reçoit un objet :cpp:`Graphics G` permettant d'effectuer différentes opérations de dessin à l'écran. Classes ======= V2 -- Dans :cpp:`V2.h` nous trouvons la classe :cpp:`V2` servant à représenter les points et les vecteurs 2D. L'addition et la soustraction entre V2 sont fournies, ainsi que la multiplication/division par un numérique. On peut remarquer que les membres des :cpp:`V2` sont des valeurs entières nommées :cpp:`x` et :cpp:`y`. .. code-block:: struct V2 { int x, y; ... }; Deux fonctions utilitaires sont fournies : * :cpp:`getPWH()` qui à partir de deux points :math:`P_1` et :math:`P_2` retourne le coin bas-gauche :math:`P` et la largeur hauteur du rectangle associé : .. image:: PLH2.jpg :align: center :scale: 50% * :cpp:`isInside()` qui détermine si un point appartient au rectangle défini par son coin bas-gauche et sa largeur hauteur : .. image:: PLH.jpg :align: center :scale: 30% Color ----- Dans :cpp:`Color.h`, nous trouvons la classe :cpp:`Color` servant à décrire une couleur : .. code-block:: struct Color { float R, G, B, A; // 0<= R,G,B,A <= 1 ... }; Les trois premières composantes (rouge, vert, bleu) décrivent la couleur, ces valeurs sont comprises entre 0 et 1, 0 = absence de lumière et 1 = maximum d'intensité. Ainsi (1,1,1) correspond au blanc, (1,0,0) à un rouge saturé et (0,0,0) au noir. Le 4ème paramètre correspond à la transparence : 1 = opaque, 0 = transparent. A noter que toute une liste de couleurs prédéfinies est disponible : - Transparent : aucune couleur, le pixel est invisible - Black & White - 21 couleurs comportant chacune 2 variantes : :cpp:`Light` plus claire et :cpp:`dark` plus sombre. Voici la liste exhaustive : .. code-block:: const Color Transparent, Black, White, Gray, GrayLight, GrayDark, Red, RedLight, RedDark, Green, GreenLight, GreenDark, Blue, BlueLight, BlueDark, Yellow, YellowLight, YellowDark, Cyan, CyanLight, CyanDark, Magenta, MagentaLight, MagentaDark, Orange, OrangeLight,OrangeDark, Purple, PurpleLight, PurpleDark, Brown, BrownLight, BrownDark, Pink, PinkLight, PinkDark, Lime, LimeLight, LimeDark, Teal, TealLight, TealDark, Navy, NavyLight, NavyDark, Maroon, MaroonLight, MaroonDark, Olive, OliveLight, OliveDark, Silver, SilverLight, SilverDark, Gold, GoldLight, GoldDark, Beige, BeigeLight, BeigeDark, Cream, CreamLight, CreamDark, Violet, VioletLight, VioletDark; Pour utiliser une de ces couleurs prédéfinies, il suffit d'écrire :cpp:`Color::MaroonLight` dans le code. Deux fonctions utilitaires sont fournies pour créer un objet :cpp:`Color` à partir des codes hexadécimaux :cpp:`x12AC44` ou des codes RGB :cpp:`(111,222,45)` que vous trouverez sur le net. .. code-block:: Color ColorFrom255(int r, int g, int b); Color ColorFromHex(int hexCode); Font ---- Dans :cpp:`Font.h`, nous trouvons la classe :cpp:`Font` permettant de décrire une police texte à partir de différents paramètres : .. code-block:: struct FontName { string name; int size; bool bold; bool italic; Color color = Color::Black; }; Le nom doit correspondre aux fichiers de fonts présents dans le répertoire :cpp:`font` du projet. Pour éviter toute erreur de frappe, deux constantes ont été définies pour les deux fonts déjà présentes dans la librairie :cpp:`G2D`: .. code-block:: namespace FontName { const string LiberationMono = "LiberationMono"; const string Tinos = "Tinos"; }; On peut les utiliser en écrivant :cpp:`FontName::Tinos` dans le code. Graphics -------- Dans :cpp:`Graphics.h`, nous trouvons la classe :cpp:`Graphics` permettant d'effectuer plusieurs opérations de dessin à l'écran. On peut les regrouper en plusieurs catégories : - Information : la fonction :cpp:`getWindowSize()` retourne la taille en pixel de la zone de tracé - Initialization : la fonction :cpp:`clearWindow()` efface la zone de tracé - Texte : la fonction :cpp:`drawString()` affiche du texte à l'écran et :cpp:`getTextSize()` retourne la taille d'un rectangle englobant sur ce texte - drawXXX : fonction de tracé des objets non pleins : segment, rectangle, cercle, ligne polygonale - fillXXX : fonction de tracé des objets pleins : rectangle plein, cercle plein, polygone convexe plein - Image : la fonction :cpp:`drawRectWithTexture()` affiche une image à l'écran dans une zone rectangulaire donnée .. note:: Pour afficher une image avec la fonction :cpp:`drawRectWithTexture()`, il faut mettre le fichier png ou jpg dans le répertoire :cpp:`images` du projet. Donnez le nom du fichier avec l'extension sans rien d'autre. Les coordonnées écran correspondent à celles du cours de géométrie dans le plan : - L'origine *(0, 0)* est positionnée en bas à gauche - L'axe des *x* est orienté vers la droite - L'axe des *y* est orienté vers le haut .. image:: reperes.png Todo ---- Dessinez : - Des rectangles imbriqués traversés par les deux mêmes diagonales - Des cercles pleins imbriqués avec des couleurs différentes - Sur un fond gris foncé .. image:: dessin.png Architecture MLR ================ L'architecture de notre application est organisée autour de trois éléments distincts : * Le **Modèle** qui contient l'état (les données) de l'application * La **Logique** qui traite les actions de l'utilisateur et modifie le modèle * Le **Rendu** qui utilise le modèle pour produire l'affichage Cette architecture est une version simplifiée de l'architecture :cpp:`MVU (Model View Update)`. Le Modèle --------- Le **Modèle** contient l'ensemble des données décrivant l'état courant de l'application. Il peut par exemple contenir : * les objets présents dans la scène * l'outil actuellement sélectionné * la couleur courante * la position de certains éléments * l'état d'une sélection .. warning:: Le modèle ne réalise ni traitement graphique ni gestion des événements. On peut le considérer comme la **mémoire de l'application**. Dans G2D, toutes les données sont stockées dans la structure :cpp:`Model` présente dans le fichier :cpp:`Model.h`. .. warning:: Vous ne devez pas instancier cette structure, elle est instanciée par G2D et ensuite l'objet Model est transmis à toutes les fonctions. La Logique ---------- La **Logique** est la partie du programme qui reçoit les événements provenant de l'utilisateur : clavier, souris, déplacement du curseur, clic. Elle interprète ces événements puis modifie le modèle en conséquence. Par exemple : .. code-block:: text clic souris | v fonction de logique | v création d'un rectangle | v modification du modèle La logique peut donc : * ajouter ou supprimer un objet * modifier une position * changer l'outil actif * modifier une couleur * effectuer une sélection .. warning:: La règle importante est la suivante : **la logique modifie le modèle, mais ne réalise aucun affichage.** Le Rendu -------- Le **Rendu** est la partie du programme responsable de l'affichage. Il consulte les données contenues dans le modèle et dessine la représentation graphique correspondante. .. warning:: Le rendu doit uniquement **lire le modèle**, il ne le modifie JAMAIS. .. warning:: Le rendu part de zéro à chaque fois. La zone d'affichage est effacée et les objets sont dessinés un à un. Il n'y a pas réutilisation du tracé précédent. Résumé ------ L'architecture complète peut être représentée ainsi : .. image:: flux.png On parle de **flux de données unidirectionnel**. A retenir : .. panels:: :column: col-lg-10 p-2 La logique écrit dans le modèle, le rendu lit le modèle. La logique ne dessine pas et le rendu ne modifie pas le modèle. .. warning:: Lors du codage de votre projet, il faudra adopter les réflexes suivants : ne pas faire d'affichage lorsque vous traitez un évènement. Cela vous semblera au début illogique. Pourquoi cette séparation ? Pour plusieurs raisons : - Un déplacement de la souris peut générer de nombreux événements alors que le rendu n'est effectué que quelques fois. Les deux traitements ne s'exécutent donc pas nécessairement à la même fréquence. - Si on pouvait faire les affichages n'importe où dans le code, très rapidement, on en verrait fleurir un peu partout, et à la fin, il serait difficile de savoir dans quel ordre ils sont réellement effectués, ce qui produira de nombreux bugs. MLR inside G2D ============== Model ----- Vous remarquez que la classe :cpp:`Model` dans le fichier :cpp:`Model.h` est relativement vide. Ce sera à vous de trouver les données centrales de l'application et de les créer à cet endroit. Comment les reconnaître : - Vous en avez besoin pour effectuer une action : la position de la souris, la couleur du rectangle, la police du texte, l'outil de tracé courant, - Ce ne sont pas des données locales ou des calculs intermédiaires - Le rendu doit effectuer l'affichage, pour cela, il doit connaître les objets à dessiner... Event - Logic ------------- Chaque action de l'utilisateur envoie un évènement :cpp:`Event` à la fonction :cpp:`processEvent(const Event& Ev, Model & Data)` contenue dans :cpp:`student.cpp`. Cette fonction est le point d'entrée de tous vos traitements concernant la LOGIQUE de l'application. Nous aurions pu faire une hiérarchie de classes pour modéliser les différents évènements, mais il en existe plus de 200 et ce n'est donc pas l'approche retenue. Nous avons choisi de faire une structure unique qui inclut - Le type de l'évènement - Deux paramètres entiers qui stockent des informations numériques ayant un sens particulier en fonction du type de l'évènement - Un champ :cpp:`Key` qui indique la touche concernée pour les entrées clavier .. code-block:: EventType Type; int x, y; Key key; Plusieurs fonctions utilitaires sont fournies : - :cpp:`isKeyboardEvent()` : qui indique s'il s'agit d'un évènement clavier - :cpp:`isMouseEvent()` : qui indique s'il s'agit d'un évènement souris - :cpp:`print()` qui affiche toutes les infos de l'évènement ToDo ---- - Lancez l'application - Deux fenêtres s'ouvrent, la fenêtre graphique de l'application et une fenêtre texte de débogage - Mettez les fenêtres côte à côte - Effectuez diverses actions : clic souris, molette, touches du clavier et analysez les informations de chaque évènement .. image:: events.png Rendu ----- Chaque demande d'affichage de l'OS déclenche un appel à la fonction :cpp:`drawApp(Graphics& G, const Model & D)` contenue dans :cpp:`student.cpp`. Cette fonction est le point d'entrée de tous vos affichages graphiques de l'application.