Events
The handling of events produced in the graphical window allows interaction
between the user and the program. Graphics supports the treating
of events like keystrokes, mouse clicks and movements of the mouse.
The programming style therefore changes the organization of the program.
It becomes an infinite loop waiting for events. After handling each
newly triggered event, the program returns to the infinite loop except
for events that indicate program termination.
Types and functions for events
The main function for waiting for events is
wait_next_event of type event list -> status.
The different events are given by the sum type
event.
type
event
=
Button_down
|
Button_up
|
Key_pressed
|
Mouse_motion
|
Poll;;
The four main values correspond to pressing and to releasing a mouse
button, to movement of the mouse and to keystrokes. Waiting for an
event is a blocking operation except if the constructor Poll
is passed in the event list. This function returns a value of type
status:
type
status
=
{
mouse_x
:
int;
mouse_y
:
int;
button
:
bool;
keypressed
:
bool;
key
:
char};;
This is a record containing the position of the mouse, a Boolean which
indicates whether a mouse button is being pressed, another Boolean for
the keyboard and a character which corresponds to the pressed key. The
following functions exploit the data contained in the event record:
-
mouse_pos: unit -> int * int: returns the
position of the mouse with respect to the window. If the mouse is placed
elsewhere, the coordinates are outside the borders of the window.
- button_down: unit -> bool: indicates pressing
of a mouse button.
- read_key: unit -> char: fetches a character
typed on the keyboard; this operation blocks.
- key_pressed: unit -> bool: indicates whether
a key is being pressed on the keyboard; this operation does not block.
The handling of events supported by Graphics is indeed
minimal for developing interactive interfaces. Nevertheless, the code
is portable across various graphical systems like Windows, MacOS
or X-Windows. This is the reason why this library does not take into
account different mouse buttons. In fact, the Mac does not even possess
more than one. Other events, such as exposing a window or changing its
size are not accessible and are left to the control of the library.
Program skeleton
All programs implementing a graphical user interface make use
of a potentially infinite loop waiting for user interaction. As
soon as an action arrives, the program executes the job associated
with this action. The following function possesses five parameters
of functionals. The first two serve for starting and closing the
application. The next two arguments handle keyboard and mouse events. The
last one permits handling of exceptions that escape out of the different
functions of the application. We assume that the events associated with
terminating the application raise the exception End.
# exception
End;;
exception End
# let
skel
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
End
->
raise
End
|
e
->
f_except
e
done
with
End
->
f_end
();;
val skel :
(unit -> 'a) ->
(unit -> unit) ->
(char -> unit) -> (int -> int -> unit) -> (exn -> unit) -> unit = <fun>
Here, we use the skeleton to implement a mini-editor. Touching a key
displays the typed character. A mouse click changes the current point. The
character '&' exits the program. The only difficulty in this program is
line breaking. We assume as simplification that the height of characters
does not exceed twelve 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
handle_char
c
=
match
c
with
'&'
->
raise
End
|
'\n'
->
next_line
()
|
'\r'
->
next_line
()
|
_
->
Graphics.draw_char
c;;
val handle_char : char -> unit = <fun>
# let
go
()
=
skel
(fun
()
->
Graphics.clear_graph
();
Graphics.moveto
0
(Graphics.size_y()
-
1
2
)
)
(fun
()
->
Graphics.clear_graph())
handle_char
(fun
x
y
->
Graphics.moveto
x
y)
(fun
e
->
());;
val go : unit -> unit = <fun>
This program does not handle deletion of characters by pressing the
key DEL
.
Example: telecran
Telecran is a little drawing game for training coordination of movements.
A point appears on a slate. This point can be moved in directions X and
Y by using two control buttons for these axes without ever releasing the
pencil. We try to simulate this behavior to illustrate the interaction
between a program and a user. To do this we reuse the previously described
skeleton. We will use certain keys of the keyboard to indicate movement
along the axes.
We first define the type state, which is a record describing
the size of the slate in terms of the number of positions in X and Y, the
current position of the point and the scaling factor for visualization,
the color of the trace, the background color and the color of the
current point.
# type
state
=
{maxx:
int;
maxy:
int;
mutable
x
:
int;
mutable
y
:
int;
scale:
int;
bc
:
Graphics.color;
fc:
Graphics.color;
pc
:
Graphics.color};;
The function draw_point displays a point given its coordinates,
the scaling factor and its color.
# 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>
All these functions for initialization, handling of user interaction and
exiting the program receive a parameter corresponding to the state. The
first four functions are defined as follows:
# let
t_init
s
()
=
Graphics.open_graph
(" "
^
(string_of_int
(s.
scale*
s.
maxx))
^
"x"
^
(string_of_int
(s.
scale*
s.
maxy)));
Graphics.set_color
s.
bc;
Graphics.fill_rect
0
0
(s.
scale*
s.
maxx+
1
)
(s.
scale*
s.
maxy+
1
);
draw_point
s.
x
s.
y
s.
scale
s.
pc;;
val t_init : state -> unit -> unit = <fun>
# let
t_end
s
()
=
Graphics.close_graph();
print_string
"Good bye..."
;
print_newline();;
val t_end : 'a -> unit -> unit = <fun>
# let
t_mouse
s
x
y
=
();;
val t_mouse : 'a -> 'b -> 'c -> unit = <fun>
# let
t_except
s
ex
=
();;
val t_except : 'a -> 'b -> unit = <fun>
The function t_init opens the graphical window and displays the
current point, t_end closes this window and displays a message,
t_mouse and t_except do not do anything. The program
handles neither mouse events nor exceptions which may accidentally arise
during program execution. The important function is the one for handling
the keyboard t_key:
# let
t_key
s
c
=
draw_point
s.
x
s.
y
s.
scale
s.
fc;
(match
c
with
'8'
->
if
s.
y
<
s.
maxy
then
s.
y
<-
s.
y
+
1
;
|
'2'
->
if
s.
y
>
0
then
s.
y
<-
s.
y
-
1
|
'4'
->
if
s.
x
>
0
then
s.
x
<-
s.
x
-
1
|
'6'
->
if
s.
x
<
s.
maxx
then
s.
x
<-
s.
x
+
1
|
'c'
->
Graphics.set_color
s.
bc;
Graphics.fill_rect
0
0
(s.
scale*
s.
maxx+
1
)
(s.
scale*
s.
maxy+
1
);
Graphics.clear_graph()
|
'e'
->
raise
End
|
_
->
());
draw_point
s.
x
s.
y
s.
scale
s.
pc;;
val t_key : state -> char -> unit = <fun>
It displays the current point in the color of the trace. Depending
on the character passed, it modifies, if possible, the coordinates of
the current point (characters: '2', '4', '6', '8'), clears the screen
(character: 'c') or raises the exception End (character: 'e'),
then it displays the new current point. Other characters are ignored. The
choice of characters for moving the cursor comes from the layout of the
numeric keyboard: the chosen keys correspond to the indicated digits and
to the direction arrows. It is therefore useful to activate the numeric
keyboard for the ergonomics of the program.
We finally define a state and apply the skeleton function in the
following way:
# let
stel
=
{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 stel : state =
{maxx=120; maxy=120; x=60; y=60; scale=4; bc=8553090; fc=0; pc=16711680}
# let
slate
()
=
skel
(t_init
stel)
(t_end
stel)
(t_key
stel)
(t_mouse
stel)
(t_except
stel);;
val slate : unit -> unit = <fun>
Calling function slate displays the graphical window, then it
waits for user interaction on the keyboard.
Figure 5.8 shows a drawing created with this program.
Figure 5.8: Telecran.