EZ-Draw, pour dessiner en C

Copyright (C) 2008 Edouard Thiel <Edouard.Thiel@lif.univ-mrs.fr>

21 juin 2011


EZ-Draw ("Easy Draw") est un module en langage C (*) qui facilite l'écriture de programmes graphiques, avec une programmation évènementielle simplifiée (sur le modèle de Xlib). Il permet de créer des fenêtres, y dessiner, réagir à la souris et au clavier, faire des animations, etc.

EZ-Draw fonctionne à la fois sous X11 (Linux, MacOS, et autres Unix) et Windows (Seven, Vista, XP, etc) ; en interne il est basé respectivement sur la librairie Xlib et sur l'API win32, et il masque tous leurs aspects techniques.

(*) Normes C ANSI et C99.


Table des matières

Page principale  Table des matières   Prev  Next

1. Manuel de programmation

La façon la plus simple de travailler est de récupérer dans son répertoire les fichiers ez-draw.h et ez-draw.c, puis de créer vos programmes dans ce même répertoire.

Vous pouvez aussi programmer directement dans le répertoire de EZ-Draw et rajouter les noms des exécutables dans le fichier Makefile (sous Unix) ou Makefile.win (sous Windows).

Page principale  Table des matières   Prev   Next

1.1. Premier programme avec une fenêtre

Écrivons un premier programme qui ouvre une fenêtre, appelé "demo0.c". Il faut commencer par inclure "ez-draw.h" : ce fichier décrit les types et prototypes du module, et il inclut aussi les fichiers .h standards tels que <stdio.h>, <stdlib.h>, <string.h>.

Dans le main, on initialise le module et le mode graphique en appelant ez_init() ; si l'initialisation du mode graphique échoue, la fonction affiche un message d'erreur dans la console, puis renvoie -1. Dans ce cas, il faut sortir du programme en faisant exit(1) .

Ensuite on crée une (ou plusieurs) fenêtres avec la fonction ez_window_create. Ces fenêtres sont affichées quand le programme atteint ez_main_loop(). C'est cette fonction qui fait "vivre" les fenêtres par une boucle sans fin, qui peut être interrompue par un appel à ez_quit(), ou qui s'arrête lorsque toutes les fenêtres sont détruites.

demo0.c
    #include "ez-draw.h"
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 0 : Hello World", NULL);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo0

Page principale  Table des matières   Prev   Next

1.2. Compilation

Pour compiler le précédent exemple demo0.c, sous Unix taper :
    gcc -Wall demo0.c ez-draw.c -o demo0 -L/usr/X11R6/lib -lX11 -lXext
ou sous Windows taper :
    gcc -Wall demo0.c ez-draw.c -o demo0.exe -lgdi32
On peut aussi rajouter le nom de l'exécutable à la fin de EXECS = dans le Makefile sous Unix (ou Makefile.win pour Windows), puis taper make all pour compiler.

Page principale  Table des matières   Prev   Next

1.3. Fonction traitant les évènements

Pour être en mesure de réagir aux actions de l'utilisateur (touches clavier, mouvement de souris, clics de souris, etc) il faut réaliser ce qu'on nomme une gestion d'évènements. On met en place ce mécanisme au moment de la création d'une fenêtre avec la fonction ez_window_create. Ses paramètres sont :
    Window ez_window_create (int w, int h, const char *name, Ez_func func);

w est la largeur (width) de l'intérieur de la fenêtre en pixels, h est la hauteur (height), name est le titre de la fenêtre ; func est la fonction d'évènement de la fenêtre, voir ci-dessous.

Le résultat de ez_window_create est le numéro qui identifie la fenêtre, de type Window ; on peut afficher une variable Window win dans la console par printf ("win = 0x%x\n", (int) win);

La fonction d'évènement func, encore appelée callback, est une fonction de votre programme (ou NULL comme dans l'exemple demo0). Cette fonction sera automatiquement appelée par ez_main_loop() lors de chaque évènement concernant la fenêtre. La fonction func doit obligatoirement avoir le prototype suivant :

    void func (Ez_event *ev);
La variable ev pointe sur un struct dont les champs décrivent l'évènement. On détaille ces champs dans la section 1.6. Tous les évènements.

Découvrons deux évènements dans l'exemple suivant :

demo1.c
    #include "ez-draw.h"
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {                                     /* par ez_main_loop()                   */
        switch (ev->type) {
    
            case Expose :                              /* Il faut tout redessiner */
                ez_set_color (ez_red);
                ez_draw_text (ev->win, EZ_MC, 200, 150, 
                    "Pour quitter, tapez sur la touche 'q', ou\n"
                    "cliquez sur l'icone fermeture de la fenetre");
                break;
    
            case KeyPress :                           /* Une touche a ete pressee */
                switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
                }
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 1 : fenetre et evenements", win1_event);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo1

Page principale  Table des matières   Prev   Next

1.4. Les dessins et les couleurs

Comme expliqué dans la section précédente, l'évènement Expose signifie qu'il faut redessiner le contenu de la fenêtre, ce qu'on fait dans l'exemple suivant en appelant win1_redessiner. À noter, pour chaque Expose, EZ-Draw vide entièrement la fenêtre (avec un fond blanc) avant de passer l'évènement à votre programme.

La liste des dessins est donné dans 2.4. Dessins. Les coordonnées sont relatives à l'origine, qui est le coin en haut à gauche de l'intérieur de la fenêtre, avec x vers la droite et y vers le bas.

Les dessins sont automatiquements coupés par le bord de la fenêtre, il n'y a donc pas à se préoccuper de savoir si un dessin risque de dépasser ou pas.

Les dessins sont fait dans l'épaisseur courante (par défaut 1 pixel). On peut changer l'épaisseur courante avec ez_set_thick, voir 2.4..

Les dessins sont fait dans la couleur courante (par défaut en noir). Pour changer la couleur courante, on appelle ez_set_color en lui donnant un numéro de couleur. Quelques couleurs sont prédéfinies : ez_black, ez_white, ez_grey, ez_red, ez_green, ez_blue, ez_yellow, ez_cyan, ez_magenta. On peut créer d'autres couleurs, voir 2.3. Couleurs.

demo2.c
    #include "ez-draw.h"
    
    void win1_redessiner (Window win)
    {
    
        ez_set_color (ez_magenta);
        ez_draw_text      (win, EZ_BL, 10, 20, "draw epaisseur 1 :");
        ez_set_thick (1);
        ez_draw_point     (win,  30,  50);
        ez_draw_line      (win,  60,  35, 130,  70);
        ez_draw_rectangle (win, 160,  30, 220,  70);
        ez_draw_circle    (win, 240,  30, 300,  70);
        ez_draw_triangle  (win, 320,  30, 380,  40, 350,  70);
    
        ez_set_color (ez_black);
        ez_draw_text      (win, EZ_BL, 10, 100, "draw epaisseur 2 :");
        ez_set_color (ez_cyan);
        ez_set_thick (2);
        ez_draw_point     (win,  30, 130);
        ez_draw_line      (win,  60, 115, 130, 150);
        ez_draw_rectangle (win, 160, 110, 220, 150);
        ez_draw_circle    (win, 240, 110, 300, 150);
        ez_draw_triangle  (win, 320, 110, 380, 120, 350, 150);
    
        ez_set_color (ez_blue);
        ez_draw_text      (win, EZ_BL, 10, 180, "draw epaisseur 9 :");
        ez_set_color (ez_green);
        ez_set_thick (9);
        ez_draw_point     (win,  30, 210);
        ez_draw_line      (win,  60, 195, 130, 230);
        ez_draw_rectangle (win, 160, 190, 220, 230);
        ez_draw_circle    (win, 240, 190, 300, 230);
        ez_draw_triangle  (win, 320, 190, 380, 200, 350, 230);
    
        ez_set_color (ez_red);
        ez_draw_text      (win, EZ_BL, 10, 260, "fill :");
        ez_set_color (ez_yellow);
        ez_fill_rectangle (win, 160, 270, 220, 310);
        ez_fill_circle    (win, 240, 270, 300, 310);
        ez_fill_triangle  (win, 320, 270, 380, 280, 350, 310);
    
    }
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {
        switch (ev->type) {
    
            case Expose :                              /* Il faut tout redessiner */
                win1_redessiner (ev->win);
                break;
    
            case KeyPress :                           /* Une touche a ete pressee */
                switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
                }
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 320, "Demo 2 : tous les dessins", win1_event);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo2

Page principale  Table des matières   Prev   Next

1.5. Affichage de texte

On peut afficher du texte n'importe où dans la fenêtre à l'aide de la fonction ez_draw_text. Elle prend en paramètre la fenêtre, le type d'alignement align, puis des coordonnées x1,y1, enfin une chaîne de caractères à imprimer, ou comme dans printf, un format et des arguments. Tout est détaillé dans 2.5. Texte et fontes.

Le résultat peut comporter des \n, provoquant des saut de lignes dans l'affichage. L'affichage de texte se fait dans la couleur courante, modifiable par ez_set_color.

On profite de l'exemple suivant pour montrer aussi l'usage de ez_window_get_size pour faire un dessin qui s'adapte aux changements de taille de la fenêtre.

demo3.c
    #include "ez-draw.h"
    
    void win1_redessiner (Window win)
    {
        int i, w, h;
    
        ez_window_get_size (win, &w, &h);
    
        ez_set_color (ez_black);
        for (i = 0; i <= 3; i++) {
            ez_set_nfont (i);
            ez_draw_text (win, EZ_TC, w/2, h/2 + 25*(i-2), 
                "Fonte numero %d", i);                         /* comme un printf */
        }
    
        ez_set_nfont (0);
        ez_set_color (ez_red);
    
        ez_draw_text (win, EZ_TL,   2,   1, "Top\nLeft");
        ez_draw_text (win, EZ_TC, w/2,   1, "Top\nCenter");
        ez_draw_text (win, EZ_TR, w-2,   1, "Top\nRight");
        ez_draw_text (win, EZ_ML,   2, h/2, "Middle\nLeft");
        ez_draw_text (win, EZ_MR, w-2, h/2, "Middle\nRight");
        ez_draw_text (win, EZ_BL,   2, h-2, "Bottom\nLeft");
        ez_draw_text (win, EZ_BC, w/2, h-2, "Bottom\nCenter");
        ez_draw_text (win, EZ_BR, w-2, h-2, "Bottom\nRight");
    }
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {
        switch (ev->type) {
    
            case Expose :                              /* Il faut tout redessiner */
                win1_redessiner (ev->win);
                break;
    
            case KeyPress :                           /* Une touche a ete pressee */
                switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
                }
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 3 : affichage de texte", win1_event);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo3

Page principale  Table des matières   Prev   Next

1.6. Tous les évènements

Dans l'exemple suivant on recense tous les évènements possibles et on affiche dans la console les champs utilisables de la variable ev (les autres champs sont réinitialisés à 0).

Par défaut, le bouton "Fermer" dans la barre de titre d'une des fenêtres de l'application provoque la fin du programme. On peut changer ce réglage d'origine en faisant ez_auto_quit(0) : le bouton "Fermer" provoquera l'évènement WindowClose, comme dans l'exemple suivant :

demo4.c
    #include "ez-draw.h"
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {
        switch (ev->type) {
    
            /* Il faut tout redessiner */
            case Expose :
                ez_draw_text (ev->win, EZ_MC, 200, 150, 
                    "L'affichage des evenements\nest fait dans la console.\n\n"
                    "Tapez 'q' pour quitter.");
                printf ("Expose           win = 0x%x\n", (int) ev->win);
                break;
    
            /* Un bouton de la souris a ete enfonce ou relache */
            case ButtonPress :
                printf ("ButtonPress      win = 0x%x  mx = %d  my = %d  mb = %d\n",
                    (int) ev->win, ev->mx, ev->my, ev->mb);
                break;
            case ButtonRelease :
                printf ("ButtonRelease    win = 0x%x  mx = %d  my = %d  mb = %d\n",
                    (int) ev->win, ev->mx, ev->my, ev->mb);
                break;
    
            /* La souris a ete deplacee dans la fenetre */
            case MotionNotify :
                printf ("MotionNotify     win = 0x%x  mx = %d  my = %d  mb = %d\n",
                    (int) ev->win, ev->mx, ev->my, ev->mb);
                break;
    
            /* Une touche du clavier a ete enfoncee ou relachee */
            case KeyPress :
                printf ("KeyPress         win = 0x%x  mx = %d  my = %d  "
                        "key_sym = 0x%x  key_name = %s  key_count = %d  key_string = \"%s\"\n",
                    (int) ev->win, ev->mx, ev->my,
                    (int) ev->key_sym, ev->key_name, ev->key_count,
                    ev->key_sym == XK_Return || ev->key_sym == XK_KP_Enter ? "" : ev->key_string);
                break;
            case KeyRelease :
                printf ("KeyRelease       win = 0x%x  mx = %d  my = %d  "
                        "key_sym = 0x%x  key_name = %s  key_count = %d  key_string = \"%s\"\n",
                    (int) ev->win, ev->mx, ev->my,
                    (int) ev->key_sym, ev->key_name, ev->key_count,
                    ev->key_sym == XK_Return || ev->key_sym == XK_KP_Enter ? "" : ev->key_string);
                 switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
                }
               break;
    
            /* La geometrie de la fenetre a change */
            case ConfigureNotify :
                printf ("ConfigureNotify  win = 0x%x  width = %d  height = %d\n",
                    (int) ev->win, ev->width, ev->height);
                break;
    
            /* La fermeture de la fenetre est interceptee */
            case WindowClose : 
                printf ("WindowClose      win = 0x%x\n", (int) ev->win);
                break;
    
             default :
                 printf ("Evenement inconnu : %d\n", ev->type);
       }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 4 : trace les evenements", win1_event);
    
        ez_auto_quit (0);  /* pour capter l'evenement WindowClose */
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo4

Seul les évènements TimerNotify ne sont pas traités ici, voir 2.7..

Page principale  Table des matières   Prev   Next

1.7. Dessiner à la souris

Voici un petit exemple dans lequel on peut dessiner une ligne polygonale à la souris. Les coordonnées des sommets sont mémorisées dans des variables globales. Chaque fois que le bouton de la souris est cliqué, un sommet est inséré ; à chaque déplacement de la souris avec bouton enfoncé, le dernier sommet est déplacé.

Par principe (et pour des raisons techniques), les dessins ne peuvent être faits que lors de l'évènement Expose. Si l'on veut mettre à jour le dessin dans la fenêtre lors d'un autre évènement, il suffit d'envoyer l'évènement Expose avec la fonction ez_send_expose. C'est ce que l'on fait ici pour les évènements ButtonPress, MotionNotify et KeyPress.

demo5.c
    #include "ez-draw.h"
    
    #define SOM_MAX 100
    int som_nb = 0, som_x[SOM_MAX], som_y[SOM_MAX];
    
    void sommet_vider ()
    {
        som_nb = 0;
    }
    
    void sommet_ajouter (int x, int y)
    {
        if (som_nb >= SOM_MAX) return;
        som_x[som_nb] = x;
        som_y[som_nb] = y;
        som_nb++;
    }
    
    void sommet_deplacer (int x, int y)
    {
        if (som_nb <= 0 || som_nb >= SOM_MAX) return;
        som_x[som_nb-1] = x;
        som_y[som_nb-1] = y;
    }
    
    void sommet_dessiner (Window win)
    {
        int i;
    
        /* On dessine les sommets */
        ez_set_color (ez_blue);
        for (i = 0; i < som_nb; i++)
            ez_draw_rectangle (win, som_x[i]-2, som_y[i]-2, som_x[i]+2, som_y[i]+2);
    
        /* On relie les sommets par des segments */
        ez_set_color (ez_grey);
        for (i = 1; i < som_nb; i++)
            ez_draw_line (win, som_x[i-1], som_y[i-1], som_x[i], som_y[i]);
    }
    
    /* Fonction appelee a chaque evenement sur win1 */
    void win1_event (Ez_event *ev)
    {
        switch (ev->type) {
    
            case Expose :
                ez_set_color (ez_black);
                ez_draw_text (ev->win, EZ_TL, 10, 10,
                    "Cliquez et tirez la souris dans la fenetre pour dessiner.\n"
                    "Tapez sur espace pour vider la fenetre, 'q' pour quitter.");
                sommet_dessiner (ev->win);
                break;
    
            case ButtonPress :
                sommet_ajouter (ev->mx, ev->my);
                ez_send_expose (ev->win);
                break;
    
            case MotionNotify :
                if (ev->mb == 0) break;                /* pas de bouton enfonce' */
                sommet_deplacer (ev->mx, ev->my);
                ez_send_expose (ev->win);
                break;
    
            case ButtonRelease :
                break;
    
            case KeyPress :
                switch (ev->key_sym) {
                    case XK_q : 
                        ez_quit (); 
                        break;
                    case XK_space : 
                        sommet_vider ();
                        ez_send_expose (ev->win); 
                        break;
                }
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 5 : dessin a la souris", win1_event);
    
        /* On associe un double-buffer d'affichage pour eviter tout clignotement */
        ez_window_dbuf (win1, 1);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo5

Page principale  Table des matières   Prev   Next

1.8. Ouvrir plusieurs fenêtres

On peut créer autant de fenêtre que l'on veut, par des appels à ez_window_create. Chaque fenêtre créée est immédiatement affichée par dessus les autres fenêtres. Pour faire détruire une fenêtre win (et donc la faire disparaître de l'écran), utiliser ez_window_destroy (win).

On peut cacher une fenêtre win (c'est-à-dire la rendre invisible) avec ez_window_show (win, 0) puis la rendre visible (par dessus les autres fenêtres) avec ez_window_show (win, 1).

Dans cet exemple, on paramètre la boucle principale en faisant ez_auto_quit(0) : le bouton "Fermer" de la barre de titre d'une fenêtre ne provoquera plus la fin du programme, mais provoquera l'évènement WindowClose. Selon la fenêtre incriminée, on cache la fenêtre, détruit la fenêtre ou quitte le programme.

demo6.c
    #include "ez-draw.h"
    
    /* En global */
    Window win1, win2, win3 = None; int show2 = 0;
    
    void win3_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win3 */
    {
        switch (ev->type) {
    
            case Expose :
                ez_draw_text (ev->win, EZ_TL, 10, 10,
                    "Si vous fermez cette fenetre, elle sera detruite.");
                break;
    
            case WindowClose : 
                ez_window_destroy (win3); win3 = None;
                break;
        }
    }
    
    void win2_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win2 */
    {
        switch (ev->type) {
    
            case Expose :
                ez_draw_text (ev->win, EZ_TL, 10, 10,
                    "Si vous fermez cette fenetre, elle sera simplement cachee.");
                break;
    
            case WindowClose : 
                ez_window_show (win2, 0); show2 = 0;
                break;
        }
    }
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {
        switch (ev->type) {
    
            case Expose :
                ez_draw_text (ev->win, EZ_TL, 10, 10,
                    "Cliquez dans cette fenetre (pour donner le focus clavier),\n"
                    "puis tapez :\n"
                    "    - sur 'm' pour montrer ou cacher la fenetre 2 ;\n"
                    "    - sur 'c' pour creer ou detruire la fenetre 3 ;\n"
                    "    - sur 'q' pour quitter.\n"
                    "\n"
                    "Si vous fermez cette fenetre, le programme se terminera.");
                break;
    
            case KeyPress :
                switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
    
                    case XK_m :
                        show2 = !show2;         /* on affiche ou on cache la fenetre */
                        ez_window_show (win2, show2);
                    break;
    
                    case XK_c :
                        if (win3 == None)  /* si la fenetre n'existe pas, on la cree */
                            win3 = ez_window_create (380, 220, "Fenetre 3", win3_event);
                        else { ez_window_destroy (win3); win3 = None; }
                    break;
                }
                break;
    
            /* L'utilisateur a clique' sur le bouton de fermeture de la fenetre */
            case WindowClose : ez_quit (); break;
        }
    }
    
    int main ()
    {
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 300, "Demo 6 : plusieurs fenetres", win1_event);
        win2 = ez_window_create (400, 200, "Fenetre 2", win2_event);
        ez_window_show (win2, show2);
    
        /* Par defaut, fermer n'importe quelle fenetre provoque la fin du programme.
           On desactive cette fin automatique ; fermer une fenetre provoquera alors
           l'evenement WindowClose pour ce window. */
        ez_auto_quit (0);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo6-1

Page principale  Table des matières   Prev   Next

1.9. Saisie de texte

L'exemple suivant montre comment lire au clavier une chaîne de caractères, supprimer des caractères avec la touche Backspace, et détecter la frappe de la touche Entrée pour déclencher une action.

demo7.c
    #include "ez-draw.h"
    
    #define BUF_MAX 80
    char buf1[BUF_MAX] = "", buf2[BUF_MAX] = "";
    
    /* Renvoie 1 si l'affichage doit etre refait, 2 si texte valide', 0 sinon */
    int saisie_texte (Ez_event *ev, char *s)
    {
        int i;
    
        switch (ev->key_sym) {
    
            case XK_BackSpace :                               /* Touche backspace */
                i = strlen (s);
                if (i == 0) break;
                s[i-1] = 0;
                return 1;
    
            case XK_Return :                                     /* Touche Entree */
                return 2;
    
            default :                                 /* Insertion d'un caractere */
                if (ev->key_count != 1) break;
                i = strlen (s);
                if (i >= BUF_MAX-1) break;
                s[i] = ev->key_string[0]; s[i+1] = 0;
                return 1;
        }
        return 0;
    }
    
    void affi_texte (Window win, int x, int y, char *s1, char *s2)
    {
        ez_set_color (ez_black);
        ez_draw_text (win, EZ_TL, x, y, "Texte : %s_", s1);
    
        if (strcmp (buf2, "") != 0) {
            ez_set_color (ez_red);
            ez_draw_text (win, EZ_TC, 200, 70,
                "Vous avez valide le texte :\n%s", s2);
        }
    }
    
    void win1_event (Ez_event *ev)        /* Appele'e a chaque evenement sur win1 */
    {
        int k;
    
        switch (ev->type) {
    
            case Expose :
                affi_texte (ev->win, 10, 10, buf1, buf2);
                break;
    
            case KeyPress :
                k = saisie_texte (ev, buf1);
                if (k == 2) strncpy (buf2, buf1, BUF_MAX);
                if (k > 0) ez_send_expose (ev->win);
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (400, 200, "Demo 7 : saisie de texte", win1_event);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo7

Page principale  Table des matières   Prev   Next

1.10. Animations

Pour réaliser une animation il faut deux ingrédients supplémentaires : un timer (voir 2.6.) pour entretenir la séquence temporelle, et un double buffer d'affichage (voir 2.7.) pour éviter que l'affichage ne clignote.

Attention, il ne faut surtout pas employer sleep ou usleep car ces fonction empêchent les callbacks de "rendre la main" à ez_main_loop, et donc elles figent l'affichage et l'interface (ou les perturbent fortement).

Le principe d'une animation est le suivant :

Voici un premier exemple simple, où l'animation consiste à faire grossir un cercle au milieu de la fenêtre (que l'on peut agrandir) :

demo8a.c
    #include "ez-draw.h"
    
    #define MAX_CPT1 100
    
    int cpt1 = 0, win1_w = 300, win1_h = 200, delay1 = 30;
    
    void win1_redessiner (Window win)
    {
        /* On fait le dessin en fonction de cpt1 */
        int xc = win1_w/2, rx = xc * cpt1 / MAX_CPT1,
            yc = win1_h/2, ry = yc * cpt1 / MAX_CPT1;
    
        ez_set_color (ez_magenta); 
        ez_set_thick (3);
        ez_draw_circle (win, xc-rx, yc-ry, xc+rx, yc+ry);
    
        ez_set_color (ez_black); ez_set_nfont (0);
        ez_draw_text (win, EZ_BL, 8, win1_h-8, "q : quitter");
    }
    
    void win1_event (Ez_event *ev)   /* Callback appele'e a chaque evenement sur win1 */
    {
        switch (ev->type) {
    
            /* Lorsqu'on recoit cet evenement, il faut tout redessiner */
            case Expose :
                win1_redessiner (ev->win);
                break;
    
            /* Le timer est arrive' a echance */
            case TimerNotify :
                /* On incremente le compteur cpt1 pour modifier la position de l'objet
                   que l'on veut animer */
                cpt1 = (cpt1 + 1) % MAX_CPT1;
                /* On envoie l'evenement Expose pour que la fenetre soit redessinee */
                ez_send_expose (ev->win);
                /* On re'arme le timer, pour entretenir une "boucle" de TimerNotify 
                   qui est iteree tous les delay1 millisecondes */
                ez_start_timer (ev->win, delay1);
                break;
    
            /* Une touche a ete pressee */
            case KeyPress : 
                switch (ev->key_sym) {
                    case XK_q : ez_quit (); break;
                }
                break;
    
            /* On memorise la nouvelle taille de la fenetre */
            case ConfigureNotify :
                win1_w = ev->width; win1_h = ev->height;
                break;
        }
    }
    
    int main ()
    {
        Window win1;
    
        if (ez_init() < 0) exit(1);
    
        win1 = ez_window_create (win1_w, win1_h, "Demo 8a : hypnose", win1_event);
    
        /* On associe un double-buffer d'affichage pour eviter tout clignotement */
        ez_window_dbuf (win1, 1);
    
        /* Provoque l'envoie d'un evenement TimerNotify dans delay1 millisecondes :
           c'est le point de depart de la "boucle" de TimerNotify */
        ez_start_timer (win1, delay1);
    
        ez_main_loop ();
        exit(0);
    }
    

On obtient cette fenêtre :

demo8a


L'exemple suivant demo8b.c illustre les animations multiples : on fait tourner les aiguilles d'une montre dans une fenêtre (on peut faire pause avec la touche espace), tandis qu'une balle rebondit sur une raquette dans une seconde fenêtre (que l'on peut agrandir).

On obtient ces fenêtres :

demo8b-1 demo8b-2

Page principale  Table des matières   Prev   Next

2. Manuel de référence

Pour les sources, voir ez-draw.h , ez-draw.c .

Page principale  Table des matières   Prev   Next

2.1. Boucle principale

Initialisation générale. Renvoie 0 succès, -1 échec.
    int ez_init ();

Boucle principale.

    void ez_main_loop ();
Cette fonction affiche les fenêtres créées, puis attend les évènements et appelle au fur et à mesure la fonction d'évènements correspondante (callback) de la fenêtre concernée.
Pour interrompre la boucle principale, appeler ez_quit() dans une callback. Une fois revenu de cette fonction, il ne faut plus faire aucun graphisme.

Cette fonction fait sortir de ez_main_loop().

    void ez_quit ();

Modifie l'effet du bouton "Fermer" de la barre de titre d'une fenêtre, pour toutes les fenêtres du programme.

    void ez_auto_quit (int val);
Par défaut (cas où val = 1), si on clique sur le bouton "Fermer" de n'importe quelle fenêtre d'un programme, il se termine immédiatement.
Vous pouvez changer ce comportement en invoquant ez_auto_quit avec val = 0 : si ensuite l'utilisateur clique sur le bouton "Fermer" d'une fenêtre, le programme (au lieu de se terminer) recevra l'évènement WindowClose pour cette fenêtre ; libre alors à votre programme de décider ce qu'il veut faire : Remarque : lorsque toutes les fenêtres sont détruites, le programme s'arrête.

Page principale  Table des matières   Prev   Next

2.2. Fenêtres

Crée et affiche une fenêtre, de largeur w et hauteur h, avec un titre name, et une fonction func appelée pour chaque évènement. Renvoie l'identifiant entier de la fenêtre, de type Window.
    Window ez_window_create (int w, int h, const char *name, Ez_func func);

Détruit une fenêtre win.

    void ez_window_destroy (Window win);

Rend visible (val = 1) ou cache (val = 0) une fenêtre win.

    void ez_window_show (Window win, int val);

Change ou récupère la taille d'une fenêtre.

    void ez_window_set_size (Window win, int w, int h);

    void ez_window_get_size (Window win, int *w, int *h);

Vide une fenêtre (fond blanc ) et réinitialise aux valeurs par défaut les paramètres de dessin { couleur, épaisseur, fonte }.

    ez_window_clear (Window win);

Envoie un évènement Expose a la fenêtre, pour la vider et la forcer à se redessiner.

    void ez_send_expose (Window win);

Page principale  Table des matières   Prev   Next

2.3. Couleurs

Affecte la couleur des prochains dessins, y compris l'affichage de texte.
    void ez_set_color (unsigned long color);
Les couleurs suivantes sont prédéfinies : ez_black, ez_white, ez_grey, ez_red, ez_green, ez_blue, ez_yellow, ez_cyan, ez_magenta.

Calcule une couleur à partir des niveaux R,G,B entre 0 et 255.

    unsigned long ez_get_RGB (unsigned char R, unsigned char G, unsigned char B);

Calcule une couleur grise à partir de son niveau g entre 0 et 255.

    unsigned long ez_get_grey (unsigned long g);

Page principale  Table des matières   Prev   Next

2.4. Dessins

Dessins de base. Les coordonnées sont relatives à l'origine, qui est le point en haut à gauche de l'intérieur de la fenêtre ; x va vers la droite et y va vers le bas.
    void ez_draw_point (Window win, int x1, int y1);

    void ez_draw_line (Window win, int x1, int y1, int x2, int y2);

    void ez_draw_rectangle (Window win, int x1, int y1, int x2, int y2);

    void ez_fill_rectangle (Window win, int x1, int y1, int x2, int y2);

    void ez_draw_triangle (Window win, int x1, int y1, int x2, int y2, int x3, int y3);

    void ez_fill_triangle (Window win, int x1, int y1, int x2, int y2, int x3, int y3);

    void ez_draw_circle (Window win, int x1, int y1, int x2, int y2);

    void ez_fill_circle (Window win, int x1, int y1, int x2, int y2);
Pour les rectangles et les cercles, x1,y1 et y2,y2 sont les coordonnées en haut à gauche et en bas à droite de la boîte englobante.
Pour les points, les segments de droite et les triangles, on passe les coordonnées des sommets.

Par défaut, la couleur est le noir ; se change avec ez_set_color.

Change l'épaisseur courante des traits. thick est la nouvelle épaisseur en pixels.

    void ez_set_thick (int thick);
Par défaut, les traits ont une épaisseur de 1 pixel. On peut fixer une épaisseur plus grande pour les dessins réalisés par : ez_draw_point, ez_draw_line, ez_draw_rectangle, ez_draw_triangle, ez_draw_circle.

Page principale  Table des matières   Prev   Next

2.5. Texte et fontes

Charge une fonte à partir de son nom (exemple "6x13") et la stocke pour le numéro num. Renvoie 0 succès, -1 erreur.
    int ez_font_load (int num, const char *name);
Le numéro de fonte doit être inférieur à EZ_FONT_MAX. Fontes préchargées : Remarque : Sous X11, le nom peut avoir une forme quelconque mais doit correspondre à une fonte existante. Sous Windows, le nom doit être sous la forme "largeurxhauteur" (une fonte approchante de taille fixe est obtenue).

Affecte le numéro de fonte pour les prochains affichages de texte.

    void ez_set_nfont (int num);

Affiche du texte, s'utilise comme printf.

    void ez_draw_text (Window win, Ez_Align align, int x1, int y1, 
        const char *format, ...);
Exemple :
    ez_draw_text (win, EZ_TL, 10, 10, "Largeur = %d\nHauteur = %d", w, h);
Les coordonnées x1,y1 sont données par rapport à align, qui prend pour valeurs : EZ_TL (Top Left), EZ_TC (Top Center), EZ_TR (Top Right), EZ_ML (Middle Left), EZ_MC (Middle Center), EZ_MR (Middle Right), EZ_BL (Bottom Left), EZ_BC (Bottom Center), EZ_BR (Bottom Right).

Le texte est tracé par dessus le dessin actuel ; on peut aussi faire effacer le fond en même temps (avec du blanc) en utilisant pour align les constantes EZ_TLF (Top Left Filled), EZ_TCF (Top Center Filled), EZ_TRF (Top Right Filled), EZ_MLF (Middle Left Filled), EZ_MCF (Middle Center Filled), EZ_MRF (Middle Right Filled), EZ_BLF (Bottom Left Filled), EZ_BCF (Bottom Center Filled), EZ_BRF (Bottom Right Filled).

Par défaut :

Page principale  Table des matières   Prev   Next

2.6. Double-buffer d'affichage

L'affichage avec double-buffer permet d'éviter que la fenêtre ne clignote pendant qu'elle est rafraichie. Le principe est de dessiner dans le double-buffer, puis d'échanger celui-ci avec le contenu de la fenêtre quand tous les dessins sont finis. Tout est automatiquement géré par EZ-Draw.

Active ou inactive l'affichage double-buffer pour le window win.

    void ez_window_dbuf (Window win, int val);
Par défaut, l'affichage double-buffer est désactivé (val = 0).

Si l'affichage double-buffer est activé (val = 1) pour un window, les dessins dans ce window doivent obligatoirement être faits uniquement lors des évènements Expose de ce window. Si le double-buffer est inactivé, ce n'est plus une obligation, mais cela reste fortement conseillé.

Comme exemple, voir dans jeu-nim.c les fonctions gui_init(), win1_onKeyPress(), win1_onExpose().

Dans ce jeu, on peut tester l'affichage avec et sans le double-buffer (presser la lettre 'd' pour basculer entre l'un et l'autre) :

jeu-nim-2

Page principale  Table des matières   Prev   Next

2.7. Timers

Armer un timer signifie mémoriser une date dans le futur, qui est la date actuelle plus un certain délai. Lorsqu'on arrive à cette date future, on dit que le timer est arrivé à échéance.

Chaque fenêtre peut être associée à (au plus) un timer. À l'échéance du timer, l'application reçoit un évènement unique TimerNotify pour le window concerné, puis le timer est supprimé.

Arme un timer pour le window win avec un délai delay en millisecondes.

    void ez_start_timer (Window win, int delay);
Tout rappel de cette fonction avant l'échéance du timer annule et remplace le timer. Si de plus delay vaut -1, alors le timer est supprimé (remarque : ce n'est pas une erreur de supprimer un timer déjà supprimé ou inexistant).

Comme exemple, voir demo8a.c.

Page principale  Table des matières   Prev   Next

2.8. Client-data

Chaque fenêtre peut mémoriser une donnée arbitraire du programme, par exemple une chaîne de caractères ou l'adresse d'un struct. On peut ensuite récupérer cette donnée à tout moment dans le programme. Ce mécanisme permet ainsi d'éviter l'emploi de variables globales.

Mémorise la donnée data dans la fenêtre win.

    void ez_set_data (Window win, void *data);

Retrouve la donnée à partir de la fenêtre win.

    void *ez_get_data (Window win);
La fonction renvoie la donnée.

Voici un exemple de programme qui affiche un cercle, dont les coordonnées sont placées dans une variable globale md :

    #include "ez-draw.h"
    
    typedef struct {
        int x, y, r;
    } Mes_donnees;
    
    Mes_donnees md;  /* 1. Variable globale */
    
    void win1_event (Ez_event *ev)
    {
       switch (ev->type) {
            case Expose :
                /* 3. Utilisation */
                ez_draw_circle (ev->win, md.x-md.r, md.y-md.r, md.x+md.r, md.y+md.r);
                break;
        }
    }
    
    int main ()
    {
        Window win1;
        if (ez_init() < 0) exit(1);
    
        /* 2. Initialisation */
        md.x = 200; md.y = 100; md.r = 50;
    
        win1 = ez_window_create (400, 300, "Demo client-data 1", win1_event);
    
        ez_main_loop ();
        exit(0);
    }
Voici maintenant le même programme sans variable globale, en mémorisant la donnée dans la fenêtre :
    #include "ez-draw.h"
    
    typedef struct {
        int x, y, r;
    } Mes_donnees;
    
    void win1_event (Ez_event *ev)
    {
        /* 4. On retrouve les données attachées à la fenêtre */
        Mes_donnees *md = ez_get_data (ev->win);
    
        switch (ev->type) {
            case Expose :
                /* 5. Utilisation */
                ez_draw_circle (ev->win, md->x-md->r, md->y-md->r, md->x+md->r, md->y+md->r);
                break;
        }
    }
    
    int main ()
    {
        Window win1;
        Mes_donnees md;  /* 1. Variable locale à main() */
    
        if (ez_init() < 0) exit(1);
    
        /* 2. Initialisation */
        md.x = 200; md.y = 100; md.r = 50;
    
        win1 = ez_window_create (400, 300, "Demo client-data 2", win1_event);

        /* 3. On mémorise la donnée dans la fenêtre */
        ez_set_data (win1, &md);
    
        ez_main_loop ();
        exit(0);
    }
Comme autre exemple, voir jeu-taquin.c.

Page principale  Table des matières   Prev   Next

2.9. Divers

Renvoie un entier aléatoire entre 0 et n-1.
    int ez_random (int n);
Remarque : le générateur de nombres aléatoires est initialisé par ez_init.


Document généré par Bocal