1. Tutorial

La façon la plus simple de travailler est de programmer directement dans le répertoire de EZ-Draw en partant d’un exemple, et de compléter le Makefile fourni, voir section Compilation.

1.1. Premier programme avec une fenêtre

Écrivons un premier programme qui ouvre une fenêtre, appelé demo-01.c. Il faut commencer par inclure ez-draw.h (ligne 1, ci-dessous) : ce fichier décrit les types et prototypes du module principal de EZ-Draw, et il inclut aussi les fichiers .h standards tels que <stdio.h>, <stdlib.h>, <string.h> ; vous n’avez donc pas à vous en occuper.

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 le terminal, puis renvoie -1. Dans ce cas, il faut sortir du programme en faisant exit(1) .

Ensuite on crée une (ou plusieurs) fenêtre(s) 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. Elle s’arrête lorsqu’on appelle ez_quit(), ou lorsque toutes les fenêtres sont détruites.

Voici le fichier demo-01.c :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "ez-draw.h"


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 300, "Demo 01: Hello World", NULL);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-01

1.2. Compilation

Pour compiler demo-01.c dans un terminal sous Unix, taper

gcc -Wall demo-01.c ez-draw.c -o demo-01 -lX11 -lXext

ou sous Windows, taper

gcc -Wall demo-01.c ez-draw.c -o demo-01.exe -lgdi32

Pour exécuter le programme sous Unix, taper

./demo-01

ou sous Windows, taper

demo-01

bullet À l’usage, il est beaucoup plus pratique d’utiliser la commande make au lieu de retaper chaque fois la ligne de commande de gcc. La commande make utilise le même fichier Makefile sous Unix et Windows.

Pour tout compiler la première fois, ou pour recompiler les fichiers C modifiés, il suffit de taper dans le terminal, quelque soit votre système :

make all

En cas d’erreur, voir le chapitre Installation. Si tout est à jour, make affiche : make: Rien à faire pour « all ». Vous pouvez toujours forcer une recompilation générale en tapant :

make clean all

Enfin si vous souhaitez faire le ménage, par exemple avant de faire une sauvegarde, tapez :

make distclean

Cela effacera tous les exécutables et fichiers temporaires, et ne conservera que les fichiers sources.

Exercice :
Éditez le fichier demo-01.c, et modifiez le titre par "Mon premier programme". Compilez en utilisant make puis exécutez le programme.

bullet Voyons maintenant comment rajouter un nouveau programme. Il suffit d’éditer le fichier Makefile, et de rajouter section C le nom de l’exécutable dans la liste EXECS, EXECS_M ou EXECS_IM, puis d’enregistrer. On peut alors taper make all pour compiler le nouveau programme. Il y a plusieurs listes pour distinguer les cas :

  • EXECS : pour compiler uniquement avec ez-draw.c ;
  • EXECS_M : pour compiler avec ez-draw.c et -lm (la librairie math) ;
  • EXECS_IM : pour compiler avec ez-draw.c, -lm et ez-image.c (pour afficher des images).

Voici les listes actuelles dans le Makefile :

EXECS = demo-01 demo-02 demo-03 demo-04 demo-05 demo-06 demo-07 demo-08 \
        demo-09 demo-11 jeu-sudoku jeu-nim jeu-vie jeu-taquin jeu-2048 \
        jeu-tetris

EXECS_M = demo-10 jeu-laby jeu-ezen jeu-heziom jeu-tangram

EXECS_IM = demo-12 demo-13 demo-14 demo-15 demo-16 demo-17 \

Comme vous pouvez le constater, lorsqu’une liste est sur plusieurs lignes, les lignes intermédiaires sont terminées par un \.

Exercice :
Recopiez le fichier demo-01.c en essai1.c, et rajoutez essai1 dans le Makefile. Compilez en utilisant make puis exécutez le programme.

bullet Vous pouvez utiliser le Makefile fourni pour des projets plus élaborés découpés en plusieurs modules. Supposons que votre exécutable s’appelle monappli, que votre projet utilise les modules ez-draw.c, ez-image.c, monprog.c et divers.c, et que vous avez besoin des librairies -lm et -lgomp. Il vous suffit de renseigner les champs EXECS_PRO, OBJS_PRO et LIBS_PRO dans le Makefile section D comme ceci :

EXECS_PRO = monappli
OBJS_PRO  = ez-draw.o ez-image.o monprog.o divers.o
LIBS_PRO  = -lm -lgomp

bullet Enfin, si vous voulez créer votre projet dans un nouveau répertoire, voici les fichiers qu’il faudra recopier dedans :

Il suffira de vider les listes EXECS dans le Makefile et de compléter les champs nécessaires.

1.3. Gestion des évènements

Nous allons maintenant nous intéresser à la gestion des évènements dans les fenêtres.

Les paramètres de la fonction ez_window_create() responsable de la création d’une fenêtre sont :

Ez_window ez_window_create (int w, int h, const char *name, Ez_func on_event);

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 ; on_event est la fonction d’évènement de la fenêtre, voir ci-dessous.

Le résultat de ez_window_create() est de type Ez_window ; il sert à identifier la fenêtre, et on peut l’afficher dans le terminal sous une forme hexadécimale :

Ez_window win1;
win1 = ez_window_create (400, 300, "Demo 0: Hello World", NULL);
printf ("win1 = 0x%x\n", ez_window_get_id(win1));

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 ; c’est pourquoi on donne la fonction on_event en 4e argument de ez_window_create().

La fonction d’évènement on_event, encore appelée callback, est une fonction de votre programme (ou NULL comme dans l’exemple demo-01). Cette fonction sera automatiquement appelée par ez_main_loop() lors de chaque évènement concernant la fenêtre.

La fonction on_event doit obligatoirement avoir le prototype suivant :

void on_event (Ez_event *ev);

La variable ev pointe sur un struct dont les champs décrivent l’évènement ; en particulier, ev->win indique la fenêtre concernée par l’évènement. On détaille les autres champs de ev dans la section Tracer les évènements.

Découvrons deux évènements dans l’exemple suivant demo-02.c :

  • Le gestionnaire de fenêtre indique à votre programme si vos fenêtres doivent être redessinées (la première fois qu’elles apparaissent, si une autre fenêtre est passée devant, etc). Lorsque cela arrive, l’évènement Expose est déclenché. Vous devez alors redessiner l’ensemble du contenu de la fenêtre ev->win.
  • Lorsque l’utilisateur enfonce une touche, l’évènement KeyPress est déclenché. Le code de la touche est disponible dans ev->key_sym (pour key symbol). Chaque code de touche correspond à une constante préfixée par XK_, par exemple ici XK_q pour la touche “q”.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "ez-draw.h"


void win1_on_event (Ez_event *ev)                /* Called by ez_main_loop() */
{                                                /* for each event on win1   */
    switch (ev->type) {

        case Expose :                           /* We must redraw everything */
            ez_set_color (ez_red);
            ez_draw_text (ev->win, EZ_MC, 200, 150, 
                "To quit, press the key 'q', or click\n"
                "on the Close button of the window");
            break;

        case KeyPress :                                 /* A key was pressed */
            switch (ev->key_sym) {
                case XK_q : ez_quit (); break;
            }
            break;
    }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 300, "Demo 02: Window and events", win1_on_event);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-02

Cet exemple demo-02.c est une première façon de gérer les événements, avec un gros switch dans win1_on_event. L’inconvénient de cette méthode est que le switch peut rapidement grossir lorsqu’on augmente le programme, au risque de rendre le programme illisible. C’est pourquoi il vaut mieux éclater win1_on_event en fonctions spécialisées par événement (toujours avec un switch), ce que l’on fait dans la section suivante.

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_on_expose. À 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 la section 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 faits dans l’épaisseur courante (par défaut 1 pixel). On peut changer l’épaisseur courante avec ez_set_thick(), voir Dessins.

Les dessins sont faits 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 Couleurs.

Voici le fichier demo-03.c :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include "ez-draw.h"


void win1_on_expose (Ez_event *ev)
{
    ez_set_color (ez_magenta);
    ez_draw_text      (ev->win, EZ_BL, 10, 20, "Thickness 1");
    ez_set_thick (1);
    ez_draw_point     (ev->win,  30,  50);
    ez_draw_line      (ev->win,  60,  35, 130,  70);
    ez_draw_rectangle (ev->win, 160,  30, 220,  70);
    ez_draw_circle    (ev->win, 240,  30, 300,  70);
    ez_draw_triangle  (ev->win, 320,  30, 380,  40, 350,  70);

    ez_set_color (ez_black);
    ez_draw_text      (ev->win, EZ_BL, 10, 100, "Thickness 2");
    ez_set_color (ez_cyan);
    ez_set_thick (2);
    ez_draw_point     (ev->win,  30, 130);
    ez_draw_line      (ev->win,  60, 115, 130, 150);
    ez_draw_rectangle (ev->win, 160, 110, 220, 150);
    ez_draw_circle    (ev->win, 240, 110, 300, 150);
    ez_draw_triangle  (ev->win, 320, 110, 380, 120, 350, 150);

    ez_set_color (ez_blue);
    ez_draw_text      (ev->win, EZ_BL, 10, 180, "Thickness 9");
    ez_set_color (ez_green);
    ez_set_thick (9);
    ez_draw_point     (ev->win,  30, 210);
    ez_draw_line      (ev->win,  60, 195, 130, 230);
    ez_draw_rectangle (ev->win, 160, 190, 220, 230);
    ez_draw_circle    (ev->win, 240, 190, 300, 230);
    ez_draw_triangle  (ev->win, 320, 190, 380, 200, 350, 230);

    ez_set_color (ez_red);
    ez_draw_text      (ev->win, EZ_BL, 10, 260, "Fill");
    ez_set_color (ez_yellow);
    ez_fill_rectangle (ev->win, 160, 270, 220, 310);
    ez_fill_circle    (ev->win, 240, 270, 300, 310);
    ez_fill_triangle  (ev->win, 320, 270, 380, 280, 350, 310);
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;
    }

}


void win1_on_event (Ez_event *ev)                /* Called by ez_main_loop() */
{                                                /* for each event on win1   */
    switch (ev->type) {
        case Expose   : win1_on_expose    (ev); break;
        case KeyPress : win1_on_key_press (ev); break;
    }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 320, "Demo 03: All drawings", win1_on_event);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-03

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 argument 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 paramètres. Tout est détaillé dans Texte et fontes.

La chaîne de caractères peut comporter des \n, ceci provoquera des saut de lignes dans l’affichage. L’affichage de texte se fait dans la couleur courante, modifiable par ez_set_color().

Dans l’exemple suivant demo-04.c on illustre l’affichage de texte, ainsi que l’usage de ez_window_get_size() pour faire un dessin qui s’adapte aux changements de taille de la fenêtre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "ez-draw.h"


void win1_on_expose (Ez_event *ev)
{
    int i, w, h;

    ez_window_get_size (ev->win, &w, &h);

    ez_set_color (ez_black);
    for (i = 0; i <= 3; i++) {
        ez_set_nfont (i);
        ez_draw_text (ev->win, EZ_TC, w/2, h/2 + 25*(i-2), 
            "Font number %d", i);                           /* like a printf */
    }

    ez_set_nfont (0);
    ez_set_color (ez_red);

    ez_draw_text (ev->win, EZ_TL,   2,   1, "Top\nLeft");
    ez_draw_text (ev->win, EZ_TC, w/2,   1, "Top\nCenter");
    ez_draw_text (ev->win, EZ_TR, w-2,   1, "Top\nRight");
    ez_draw_text (ev->win, EZ_ML,   2, h/2, "Middle\nLeft");
    ez_draw_text (ev->win, EZ_MR, w-2, h/2, "Middle\nRight");
    ez_draw_text (ev->win, EZ_BL,   2, h-2, "Bottom\nLeft");
    ez_draw_text (ev->win, EZ_BC, w/2, h-2, "Bottom\nCenter");
    ez_draw_text (ev->win, EZ_BR, w-2, h-2, "Bottom\nRight");
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;
    }

}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose   : win1_on_expose    (ev); break;
        case KeyPress : win1_on_key_press (ev); break;
    }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 300, "Demo 04: Displaying text", win1_on_event);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-04

1.6. Tracer les évènements

Dans l’exemple suivant on recense tous les évènements possibles et on affiche dans le terminal les champs utilisables de la variable ev (les autres champs sont réinitialisés à 0). Voir aussi la section Évènements.

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 :

Voici le fichier demo-05.c :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include "ez-draw.h"


void win1_on_expose (Ez_event *ev)              /* We must redraw everything */
{
    ez_draw_text (ev->win, EZ_MC, 200, 150, 
        "Events are traced\nin the terminal.\n\n"
        "Type 'q' to quit.");
    printf ("Expose           win = 0x%x\n", ez_window_get_id(ev->win));
}


void win1_on_button_press (Ez_event *ev)             /* Mouse button pressed */
{
    printf ("ButtonPress      win = 0x%x  mx = %d  my = %d  mb = %d\n",
        ez_window_get_id(ev->win), ev->mx, ev->my, ev->mb);
}


void win1_on_button_release (Ez_event *ev)          /* Mouse button released */
{
    printf ("ButtonRelease    win = 0x%x  mx = %d  my = %d  mb = %d\n",
        ez_window_get_id(ev->win), ev->mx, ev->my, ev->mb);
}


void win1_on_motion_notify (Ez_event *ev)                     /* Mouse moved */
{
    printf ("MotionNotify     win = 0x%x  mx = %d  my = %d  mb = %d\n",
        ez_window_get_id(ev->win), ev->mx, ev->my, ev->mb);
}


void win1_on_key_press (Ez_event *ev)                         /* Key pressed */
{
    printf ("KeyPress         win = 0x%x  mx = %d  my = %d  "
            "key_sym = 0x%x  key_name = %s  key_count = %d  key_string = \"%s\"\n",
        ez_window_get_id(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);
}


void win1_on_key_release (Ez_event *ev)                      /* Key released */
{
    printf ("KeyRelease       win = 0x%x  mx = %d  my = %d  "
            "key_sym = 0x%x  key_name = %s  key_count = %d  key_string = \"%s\"\n",
        ez_window_get_id(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;
    }
}


void win1_on_configure_notify (Ez_event *ev)          /* Window size changed */
{
    printf ("ConfigureNotify  win = 0x%x  width = %d  height = %d\n",
        ez_window_get_id(ev->win), ev->width, ev->height);
}


void win1_on_window_close (Ez_event *ev)             /* Close button pressed */
{
    printf ("WindowClose      win = 0x%x\n", ez_window_get_id(ev->win));
}


void win1_on_event (Ez_event *ev)                /* Called by ez_main_loop() */
{                                                /* for each event on win1   */
    switch (ev->type) {
        case Expose          : win1_on_expose           (ev); break;
        case ButtonPress     : win1_on_button_press     (ev); break;
        case ButtonRelease   : win1_on_button_release   (ev); break;
        case MotionNotify    : win1_on_motion_notify    (ev); break;
        case KeyPress        : win1_on_key_press        (ev); break;
        case KeyRelease      : win1_on_key_release      (ev); break;
        case ConfigureNotify : win1_on_configure_notify (ev); break;
        case WindowClose     : win1_on_window_close     (ev); break;
        default :
             printf ("Unknown event: %d\n", ev->type);
   }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 300, "Demo 05: Tracing events", win1_on_event);

    ez_auto_quit (0);  /* to get WindowClose event */

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-05

Remarque : les évènements TimerNotify ne sont pas traités ici, voir Timers.

1.7. Dessiner à la souris

L’exemple présenté dans cette section permet de dessiner une ligne polygonale à la souris. Les coordonnées des sommets sont mémorisées dans des variables globales (on pourrait aussi éviter d’avoir des variables globales, voir Client-data). 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.

Voici le fichier demo-06.c :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include "ez-draw.h"

#define VER_MAX 100
int ver_nb = 0, ver_x[VER_MAX], ver_y[VER_MAX];


void vertex_clear ()
{
    ver_nb = 0;
}


void vertex_insert (int x, int y)
{
    if (ver_nb >= VER_MAX) return;
    ver_x[ver_nb] = x;
    ver_y[ver_nb] = y;
    ver_nb++;
}


void vertex_move (int x, int y)
{
    if (ver_nb <= 0 || ver_nb >= VER_MAX) return;
    ver_x[ver_nb-1] = x;
    ver_y[ver_nb-1] = y;
}


void draw_vertices (Ez_window win)
{
    int i;

    ez_set_color (ez_blue);
    for (i = 0; i < ver_nb; i++)
        ez_draw_rectangle (win, ver_x[i]-2, ver_y[i]-2, ver_x[i]+2, ver_y[i]+2);
}

void draw_segments (Ez_window win)
{
    int i;

    ez_set_color (ez_grey);
    for (i = 1; i < ver_nb; i++)
        ez_draw_line (win, ver_x[i-1], ver_y[i-1], ver_x[i], ver_y[i]);
}


void win1_on_expose (Ez_event *ev)
{
    ez_set_color (ez_black);
    ez_draw_text (ev->win, EZ_TL, 10, 10,
        "Click and drag the mouse in the window to draw.\n"
        "Type Space to clear the window, 'q' to quit.");
    draw_segments (ev->win);
    draw_vertices (ev->win);
}


void win1_on_button_press (Ez_event *ev)
{
    vertex_insert (ev->mx, ev->my);
    ez_send_expose (ev->win);
}


void win1_on_motion_notify (Ez_event *ev)
{
    if (ev->mb == 0) return;                            /* No button pressed */
    vertex_move (ev->mx, ev->my);
    ez_send_expose (ev->win);
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : 
            ez_quit (); 
            break;
        case XK_space : 
            vertex_clear ();
            ez_send_expose (ev->win); 
            break;
    }
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose       : win1_on_expose        (ev); break;
        case ButtonPress  : win1_on_button_press  (ev); break;
        case MotionNotify : win1_on_motion_notify (ev); break;
        case KeyPress     : win1_on_key_press     (ev); break;
    }
}


int main ()
{
    Ez_window win1;

    if (ez_init() < 0) exit(1);

    win1 = ez_window_create (400, 300, "Demo 06: Drawing wih the mouse", win1_on_event);

    /* Enable double buffer to prevent window flashes */
    ez_window_dbuf (win1, 1);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-06

1.8. Gérer 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 l’exemple qui suit, 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.

Voici le fichier demo-07.c :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "ez-draw.h"

/* Global variables */
Ez_window win1, win2, win3 = None; int show2 = 0;


void win3_on_expose (Ez_event *ev)
{
    ez_draw_text (ev->win, EZ_TL, 10, 10,
        "If you close this window, it will be destroyed.");
}


/* The user has clicked on the Close button of the window */

void win3_on_window_close (Ez_event *ev)
{
    (void) ev;  /* Tell the compiler that ev is unused */
    ez_window_destroy (win3); win3 = None;
}


void win3_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose      : win3_on_expose       (ev); break;
        case WindowClose : win3_on_window_close (ev); break;
    }
}


void win2_on_expose (Ez_event *ev)
{
    ez_draw_text (ev->win, EZ_TL, 10, 10,
        "If you close this window, it will be hidden.");
}


void win2_on_window_close (Ez_event *ev)
{
    (void) ev;
    ez_window_show (win2, 0); show2 = 0;
}


void win2_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose      : win2_on_expose       (ev); break;
        case WindowClose : win2_on_window_close (ev); break;
    }
}


void win1_on_expose (Ez_event *ev)
{
    ez_draw_text (ev->win, EZ_TL, 10, 10,
        "Click in this window (to get the keyboard focus),\n"
        "then type :\n"
        "    - on 'm' to show or hide window 2;\n"
        "    - on 'c' to create or destroy window 3;\n"
        "    - on 'q' to quit.\n"
        "\n"
        "If you close this window, the program will end.");
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;

        case XK_m :
            show2 = !show2;                       /* show or hide the window */
            ez_window_show (win2, show2);
        break;

        case XK_c :
            if (win3 == None)      /* if the window doesn't exist, create it */
                win3 = ez_window_create (380, 220, "Window 3", win3_on_event);
            else { ez_window_destroy (win3); win3 = None; }
        break;
    }
}


void win1_on_window_close (Ez_event *ev)
{
    (void) ev;
    ez_quit ();
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose      : win1_on_expose       (ev); break;
        case KeyPress    : win1_on_key_press    (ev); break;
        case WindowClose : win1_on_window_close (ev); break;
    }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    win1 = ez_window_create (400, 300, "Demo 07: Several windows", win1_on_event);
    win2 = ez_window_create (400, 200, "Window 2", win2_on_event);
    ez_window_show (win2, show2);

    /* By default, closing any window will cause the end of the program.
       We change this behaviour: for now on, closing any window will send 
       a WindowClose event for this window. */
    ez_auto_quit (0);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-07

1.9. Saisie de texte

L’exemple suivant demo-08.c 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "ez-draw.h"

#define BUF_MAX 80
char buf1[BUF_MAX] = "", buf2[BUF_MAX] = "";


/* Return 1 if text must be displayed again, 2 if text is validated, else 0 */

int text_input (Ez_event *ev, char *s)
{
    int i;

    switch (ev->key_sym) {

        case XK_BackSpace :                                 /* Backspace key */
            i = strlen (s);
            if (i == 0) break;
            s[i-1] = 0;
            return 1;

        case XK_Return :                                        /* Enter key */
            return 2;

        default :                                      /* Insert a character */
            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 text_display (Ez_window win, int x, int y, char *s1, char *s2)
{
    ez_set_color (ez_black);
    ez_draw_text (win, EZ_TL, x, y, "Text: %s_", s1);

    if (strcmp (buf2, "") != 0) {
        ez_set_color (ez_blue);
        ez_draw_text (win, EZ_TC, 200, 70,
            "You have validated this text:\n%s", s2);
    }
}


void win1_on_expose (Ez_event *ev)
{
    text_display (ev->win, 10, 10, buf1, buf2);
}


void win1_on_key_press (Ez_event *ev)
{
    int k = text_input (ev, buf1);
    if (k == 2) strncpy (buf2, buf1, BUF_MAX);
    if (k > 0) ez_send_expose (ev->win);
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose   : win1_on_expose    (ev); break;
        case KeyPress : win1_on_key_press (ev); break;
    }
}


int main ()
{
    if (ez_init() < 0) exit(1);

    ez_window_create (400, 200, "Demo 08: Text input", win1_on_event);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-08

1.10. Animations

Pour réaliser une animation il faut deux ingrédients supplémentaires : un timer (voir Timers) pour entretenir la séquence temporelle, et un double buffer d’affichage (voir Double-buffer d’affichage) 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 :

  • on démarre un timer dans main, qui provoque un évènement TimerNotify quelques millisecondes plus tard ;
  • à réception de cet évènement TimerNotify dans la callback de la fenêtre, on fait trois choses :
    • on incrémente un compteur (ou des coordonnées) permettant de modifier la position de l’objet à animer ;
    • on émet un évènement Expose pour que l’affichage soit refait ;
    • enfin, on réarme le timer pour qu’il y ait un prochain évènement TimerNotify (on obtient une espèce de “boucle” de TimerNotify temporisée) ;
  • chaque fois qu’un évènement Expose est reçu, on redessine la fenêtre en tenant compte du compteur (ou des coordonnées) pour dessiner l’objet animé dans sa position courante. Il ne faut pas dessiner pour un autre évènement, car cela perturberait le double-buffer d’affichage. De plus, EZ-Draw optimise l’affichage en éliminant les évènements Expose inutiles.

Voici un premier exemple simple avec le fichier demo-09.c affiché ci-dessous. L’animation consiste à faire grossir un cercle au milieu de la fenêtre ; elle s’adapte également à la taille de la fenêtre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "ez-draw.h"

#define MAX_CPT1 100

/* We can avoid global variables by using ez_set_data(), see demo-10.c
   and the next examples */
int count1 = 0, win1_w = 300, win1_h = 200, delay1 = 30;


void win1_on_expose (Ez_event *ev)
{
    /* We draw based on count1 */
    int xc = win1_w/2, rx = xc * count1 / MAX_CPT1,
        yc = win1_h/2, ry = yc * count1 / MAX_CPT1;

    ez_set_color (ez_magenta); 
    ez_set_thick (3);
    ez_draw_circle (ev->win, xc-rx, yc-ry, xc+rx, yc+ry);

    ez_set_color (ez_black); ez_set_nfont (0);
    ez_draw_text (ev->win, EZ_BL, 8, win1_h-8, "q: quit");
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;
    }
}


void win1_on_configure_notify (Ez_event *ev)
{
    win1_w = ev->width; win1_h = ev->height;
}


void win1_on_timer_notify (Ez_event *ev)            /* The timer has expired */
{
    /* We increment the counter count1 so as to modify the position of the
       object to animate */
    count1 = (count1 + 1) % MAX_CPT1;
    /* We send an Expose event so that the window will be displayed again */
    ez_send_expose (ev->win);
    /* We restart the timer to maintain a "loop" or TimerNotify events,
       which is iterated each delay1 milliseconds */
    ez_start_timer (ev->win, delay1);
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose          : win1_on_expose           (ev); break;
        case KeyPress        : win1_on_key_press        (ev); break;
        case ConfigureNotify : win1_on_configure_notify (ev); break;
        case TimerNotify     : win1_on_timer_notify     (ev); break;
    }
}


int main ()
{
    Ez_window win1;

    if (ez_init() < 0) exit(1);

    win1 = ez_window_create (win1_w, win1_h, "Demo 09: Hypnosis", win1_on_event);

    /* Enable double buffer to prevent window flashes */
    ez_window_dbuf (win1, 1);

    /* Start a timer to get a TimerNotify event in delay1 milliseconds:
       this is the starting point of the "loop" of TimerNotify events. */
    ez_start_timer (win1, delay1);

    ez_main_loop ();
    exit(0);
}

On obtient cette fenêtre :

demo-09

L’exemple suivant demo-10.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 :

demo-10-1 demo-10-2

Cet exemple demo-10.c illustre également comment attacher une information (par exemple un struct) à une fenêtre, pour éviter les variables globales. Pour plus d’explications voir Client-data. On se sert beaucoup de cette possibilité dans la partie suivante.

1.11. Images

Dans les sections précédentes on a vu tout ce que l’on pouvait faire avec le module de base.

EZ-Draw contient un second module, ez-image.c, qui permet de charger et d’afficher des images en couleur au format PNG, JPEG, GIF ou BMP, ou encore de créer une image en mémoire et de dessiner dedans. Tout est détaillé dans les sections Le type image et suivantes.

Pour utiliser ce module il faut inclure ez-image.h. Voici l’exemple demo-13.c dans lequel on récupère un nom de fichier image en argument de la ligne de commande, puis on charge l’image et enfin on l’affiche :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "ez-draw.h"
#include "ez-image.h"


typedef struct {
    Ez_image *image1;
    Ez_window win1;
} App_data;


void app_data_init (App_data *a, char *filename)
{
    a->image1 = ez_image_load (filename);                   /* Load an image */
    if (a->image1 == NULL) exit (1);
}


void app_data_destroy (App_data *a)
{
    ez_image_destroy (a->image1);                           /* Destroy image */
}


void win1_on_expose (Ez_event *ev)
{
    App_data *a = ez_get_data (ev->win);

    ez_image_paint (a->win1, a->image1, 0, 0);              /* Display image */
}


void win1_on_key_press (Ez_event *ev)
{
    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;
    }
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose   : win1_on_expose    (ev); break;
        case KeyPress : win1_on_key_press (ev); break;
    }
}


int main (int argc, char *argv[])
{
    char *filename = "images/tux2.gif";
    App_data a;

    if (argc-1 != 1)
         fprintf (stderr, "Usage: %s image\n", argv[0]);
    else filename = argv[1];

    if (ez_init() < 0) exit (1);
    app_data_init (&a, filename);

    a.win1 = ez_window_create (                   /* Resize window for image */
        a.image1->width, a.image1->height, 
        filename, win1_on_event);
    ez_set_data (a.win1, &a);
    ez_window_dbuf(a.win1, 1);

    ez_main_loop ();

    app_data_destroy (&a);
    exit(0);
}

On obtient par exemple cette fenêtre :

demo-13

Pour compiler cet exemple demo-13.c sous Unix, taper :

gcc -Wall demo-13.c ez-draw.c ez-image.c -o demo-13 -lX11 -lXext -lm

ou sous Windows, taper :

gcc -Wall demo-13.c ez-draw.c ez-image.c -o demo-13.exe -lgdi32 -lmsimg32 -lm

On peut également rajouter le nom de l’exécutable à la fin de EXECS_IM = dans le Makefile, puis taper make all pour compiler.

Les formats PNG, GIF et BMP permettent de mémoriser le degré de transparence, dans ce qu’on appelle le canal alpha. Les formats GIF et BMP codent le canal alpha sur 1 bit ; les pixels sont soit transparents (0), soit opaques (255). Le format PNG code le canal alpha sur 8 bits (de 0 pour transparent à 255 pour opaque).

Le module ez-image est capable d’afficher une image en tenant compte de la transparence, en utilisant un seuil d’opacité sur le canal alpha : les pixels sont soit opaques (affichés), soit transparents (non affichés).

L’exemple suivant demo-14.c récupère deux noms de fichiers en argument de la ligne de commande, puis superpose les deux images. On peut ensuite déplacer la seconde image avec les flèches, ou modifier le seuil d’opacité avec les touches + et -.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#include "ez-draw.h"
#include "ez-image.h"


typedef struct {
    int i2_x, i2_y;
    Ez_image *image1, *image2;
    Ez_window win1;
} App_data;


void app_data_init (App_data *a, char *filename1, char *filename2)
{
    a->image1 = ez_image_load (filename1);
    if (a->image1 == NULL) exit (1);

    a->image2 = ez_image_load (filename2);
    if (a->image2 == NULL) exit (1);

    /* Initial position is centered */
    a->i2_x = (a->image1->width  - a->image2->width ) / 2;
    a->i2_y = (a->image1->height - a->image2->height) / 2;
}


void app_data_destroy (App_data *a)
{
    ez_image_destroy (a->image1);
    ez_image_destroy (a->image2);
}


void win1_on_expose (Ez_event *ev)
{
    App_data *a = ez_get_data (ev->win);

    ez_image_paint (a->win1, a->image1, 0, 0);
    ez_image_paint (a->win1, a->image2, a->i2_x, a->i2_y); 
    ez_draw_text (a->win1, EZ_BLF, 10, a->image1->height+15, 
        "[Arrows] to move");
    ez_draw_text (a->win1, EZ_BRF, a->image1->width-10, a->image1->height+15, 
        "Opacity [+-] : %d", a->image2->opacity);
}


void win1_on_key_press (Ez_event *ev)
{
    App_data *a = ez_get_data (ev->win);

    switch (ev->key_sym) {
        case XK_q : ez_quit (); break;
        case XK_Left        : 
        case XK_KP_Left     : a->i2_x-- ; break;
        case XK_Right       :
        case XK_KP_Right    : a->i2_x++ ; break;
        case XK_Up          : 
        case XK_KP_Up       : a->i2_y-- ; break;
        case XK_Down        : 
        case XK_KP_Down     : a->i2_y++ ; break;
        case XK_minus       :
        case XK_KP_Subtract : a->image2->opacity--; break;
        case XK_plus        :
        case XK_KP_Add      : a->image2->opacity++; break;
        default             : return;
    }
    ez_send_expose (a->win1);
}


void win1_on_event (Ez_event *ev)
{
    switch (ev->type) {
        case Expose   : win1_on_expose    (ev); break;
        case KeyPress : win1_on_key_press (ev); break;
    }
}


int main (int argc, char *argv[])
{
    char *file1 = "images/paper1.jpg", *file2 = "images/tux1.png";
    App_data a;

    if (argc-1 != 2)
        fprintf (stderr, "Usage: %s image1 image2\n", argv[0]);
    else { file1 = argv[1]; file2 = argv[2]; }

    if (ez_init() < 0) exit(1);
    app_data_init (&a, file1, file2);

    a.win1 = ez_window_create (a.image1->width, a.image1->height+15, 
        "Demo 14: Images with transparency", win1_on_event);
    ez_set_data (a.win1, &a);
    ez_window_dbuf(a.win1, 1);

    ez_main_loop ();

    app_data_destroy (&a);
    exit(0);
}

On obtient par exemple cette fenêtre :

demo-14

Il est également possible de créer une image en mémoire, puis d’affecter les couleurs des pixels. L’exemple demo-12.c affiche la palette de couleurs HSV à l’aide d’une image calculée en mémoire.

On obtient cette fenêtre :

demo-12

Vous trouverez plus d’informations sur les images dans le Manuel de référence.