Événements
La gestion des événements produits dans la fenêtre graphique permet de
construire une interaction conviviale entre l'utilisateur et le
programme. Les événements traités par Graphics sont l'appui
d'une touche au clavier, le clic de la souris et le mouvement de
celle-ci.
Le style de programmation change ainsi que l'organisation du
programme. Celui-ci devient une boucle sans fin d'attente
d'événements. À chaque nouveau déclenchement d'un événement, celui-ci
est traité, puis le programme revient dans la boucle d'attente sauf
lorsqu'un événement est prévu pour provoquer la sortie du programme.
Types et fonctions sur les événements
La fonction principale d'attente d'événements est
wait_next_event de type event list -> status.
Les différents événements sont donnés par le type somme
event.
type
event
=
Button_down
|
Button_up
|
Key_pressed
|
Mouse_motion
|
Poll
;;
Les quatre premières valeurs correspondent, respectivement, à
l'appui ou au relâchement d'un bouton de souris, au déplacement de la
souris et à l'appui d'une touche. L'attente est bloquante, sauf si le
constructeur Poll est ajouté à la liste des événements.
Cette fonction retourne une
valeur de type status :
type
status
=
{
mouse_x
:
int;
mouse_y
:
int;
button
:
bool;
keypressed
:
bool;
key
:
char};;
C'est un enregistrement contenant la position de la souris, un booléen
indiquant si un bouton est pressé, un autre booléen pour le clavier et
un caractère correspondant au caractère de la touche utilisée. Les
fonctions suivantes exploitent les données de l'enregistrement des
événements :
-
mouse_pos : unit -> int * int : retourne
la position de la souris par rapport à la fenêtre, si la souris est
ailleurs, les coordonnées sont en dehors des bornes de la fenêtre.
- button_down : unit -> bool : indique la
pression sur un bouton de la souris.
- read_key : unit -> char : renvoie un
caractère tapé au clavier; l'attente est bloquante.
- key_pressed : unit -> bool : indique si
une touche du clavier est pressée; l'attente est non bloquante.
La gestion d'événements proposée par Graphics est vraiment
minimale pour la construction d'interfaces interactives. Néanmoins, le
code est portable sur différents systèmes graphiques comme Windows,
MacOS ou X-Windows. C'est d'ailleurs pour cela que cette
bibliothèque ne tient pas compte des différents boutons de la
souris. En effet les Mac n'en possèdent qu'un seul. Les autres
événements : exposition et changement de taille de la fenêtre ne sont
pas accessibles et sont laissés à la charge de la bibliothèque.
Squelette de programme
Tous les programmes d'interface utilisateur comprennent une boucle
potentiellement infinie d'attente d'action de l'utilisateur. Une fois
celle-ci transmise, le programme effectue le travail lié à cette
action. La fonction suivante possède cinq paramètres fonctionnels.
Les deux premiers servent à démarrer et à fermer l'application. Les
deux suivants sont les fonctions de traitement des événements clavier
et souris. Le dernier permet la gestion des exceptions lors des
différents calculs de l'application. On suppose que les
événements associés à la sortie de l'application déclenchent
l'exception Fin.
# exception
Fin;;
exception Fin
# let
squel
f_init
f_end
f_key
f_mouse
f_except
=
f_init
()
;
try
while
true
do
try
let
s
=
Graphics.wait_next_event
[
Graphics.
Button_down;
Graphics.
Key_pressed]
in
if
s.
Graphics.keypressed
then
f_key
s.
Graphics.key
else
if
s.
Graphics.button
then
f_mouse
s.
Graphics.mouse_x
s.
Graphics.mouse_y
with
Fin
->
raise
Fin
|
e
->
f_except
e
done
with
Fin
->
f_end
()
;;
val squel :
(unit -> 'a) ->
(unit -> unit) ->
(char -> unit) -> (int -> int -> unit) -> (exn -> unit) -> unit = <fun>
Le squelette est utilisé pour programmer une mini-machine à
écrire. L'appui d'une touche affiche le caractère tapé. Le clic d'une
souris change le point courant. Le caractère '§' fait sortir du
programme. La seule difficulté de ce programme est le saut de
ligne. On suppose, pour simplifier, que la hauteur des caractères
n'excède pas douze pixels.
# let
next_line
()
=
let
(x,
y)
=
Graphics.current_point()
in
if
y>
1
2
then
Graphics.moveto
0
(y-
1
2
)
else
Graphics.moveto
0
y
;;
val next_line : unit -> unit = <fun>
# let
trait_char
c
=
match
c
with
'§'
->
raise
Fin
|
'\n'
->
next_line
()
|
'\r'
->
next_line
()
|
_
->
Graphics.draw_char
c
;;
val trait_char : char -> unit = <fun>
# let
go
()
=
squel
(fun
()
->
Graphics.clear_graph
()
;
Graphics.moveto
0
(Graphics.size_y()
-
1
2
)
)
(fun
()
->
Graphics.clear_graph())
trait_char
(fun
x
y
->
Graphics.moveto
x
y)
(fun
e
->
())
;;
val go : unit -> unit = <fun>
Ce programme ne gère pas l'effacement d'un caractère par l'appui de la
touche DEL
.
Exemple : le télécran
Le télécran est ce petit jeu de dessin pour la coordination des
mouvements. Un point apparaît sur une ardoise. Ce point peut se
déplacer en X et en Y en utilisant deux boutons de contrôle de ces
axes, sans jamais relever le crayon. On cherche à simuler ce
comportement pour illustrer l'interaction entre un programme et un
utilisateur. Pour cela on reprendra le squelette décrit
précédemment. On utilisera certaines touches du clavier pour indiquer
un mouvement sur un des axes.
On définit tout d'abord le type etat qui est un
enregistrement décrivant la taille de l'ardoise en nombre de positions
en X et en Y, la position courante du point, le facteur d'échelle pour
la visualisation, la couleur du trait, la couleur du fond et
la couleur pour repérer le
point courant.
# type
etat
=
{maxx:
int;
maxy:
int;
mutable
x
:
int;
mutable
y
:
int;
scale:
int;
bc
:
Graphics.color;
fc:
Graphics.color;
pc
:
Graphics.color}
;;
La fonction draw_point affiche un point en lui donnant
comme paramètres les coordonnées, le facteur d'échelle et la couleur.
# let
draw_point
x
y
s
c=
Graphics.set_color
c;
Graphics.fill_rect
(s*
x)
(s*
y)
s
s
;;
val draw_point : int -> int -> int -> Graphics.color -> unit = <fun>
Toutes les fonctions d'initialisation, de gestion de l'interaction et
de sortie du programme recevront un paramètre correspondant à
l'état. Les quatre premières fonctions sont définies ci-après :
# let
t_init
e
()
=
Graphics.open_graph
(":0 "
^
(string_of_int
(e.
scale*
e.
maxx))
^
"x"
^
(string_of_int
(e.
scale*
e.
maxy)))
;
Graphics.set_color
e.
bc
;
Graphics.fill_rect
0
0
(e.
scale*
e.
maxx+
1
)
(e.
scale*
e.
maxy+
1
)
;
draw_point
e.
x
e.
y
e.
scale
e.
pc
;;
val t_init : etat -> unit -> unit = <fun>
# let
t_end
e
()
=
Graphics.close_graph()
;
print_string
"A bientôt..."
;
print_newline()
;;
val t_end : 'a -> unit -> unit = <fun>
# let
t_mouse
e
x
y
=
()
;;
val t_mouse : 'a -> 'b -> 'c -> unit = <fun>
# let
t_except
e
ex
=
()
;;
val t_except : 'a -> 'b -> unit = <fun>
La fonction t_init ouvre la fenêtre graphique et affiche le
point courant, t_end ferme cette fenêtre et affiche un
message, t_mouse et t_except ne font rien. Le
programme ne gérera pas les événements souris, ni les exceptions qui
risquent de se déclencher à l'exécution. La fonction importante est
celle de la gestion du clavier t_key :
# let
t_key
e
c
=
draw_point
e.
x
e.
y
e.
scale
e.
fc;
(match
c
with
'8'
->
if
e.
y
<
e.
maxy
then
e.
y
<-
e.
y
+
1
;
|
'2'
->
if
e.
y
>
0
then
e.
y
<-
e.
y
-
1
|
'4'
->
if
e.
x
>
0
then
e.
x
<-
e.
x
-
1
|
'6'
->
if
e.
x
<
e.
maxx
then
e.
x
<-
e.
x
+
1
|
'c'
->
Graphics.set_color
e.
bc;
Graphics.fill_rect
0
0
(e.
scale*
e.
maxx+
1
)
(e.
scale*
e.
maxy+
1
);
Graphics.clear_graph()
|
'f'
->
raise
Fin
|
_
->
());
draw_point
e.
x
e.
y
e.
scale
e.
pc;;
val t_key : etat -> char -> unit = <fun>
Elle affiche le point courant de la couleur du trait, et selon le
caractère passé modifie, si possible, les coordonnées du point courant
(caractères : '2', '4', '6', '8'), efface l'écran (caractère : 'c')
ou déclenche l'exception Fin (caractère : 'f'), puis
affiche le nouveau point courant. Les caractères autres que ceux
mentionnés sont ignorés. Le choix des caractères de déplacement
du curseur provient de la disposition du pavé numérique : les touches
choisies correspondent aux chiffres indiqués et aux flèches de
direction. Il est utile alors d'activer le pavé numérique
pour l'ergonomie du programme.
On définit enfin un état et on applique la fonction squelette de la
manière suivante :
# let
etel
=
{maxx=
1
2
0
;maxy=
1
2
0
;
x=
6
0
;
y=
6
0
;
scale=
4
;
bc=
Graphics.rgb
1
3
0
1
3
0
1
3
0
;
fc=
Graphics.black;
pc
=
Graphics.red};;
val etel : etat =
{maxx=120; maxy=120; x=60; y=60; scale=4; bc=8553090; fc=0; pc=16711680}
# let
ardoise
()
=
squel
(t_init
etel)
(t_end
etel)
(t_key
etel)
(t_mouse
etel)
(t_except
etel);;
val ardoise : unit -> unit = <fun>
L'appel à ardoise affiche la fenêtre graphique puis attend
une interaction clavier.
La figure 5.8 montre un
dessin réalisé par ce programme.
Figure 5.8 : Le télécran