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>12 then Graphics.moveto 0 (y-12)
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() -12) )
(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=120; maxy=120; x=60; y=60;
scale=4; bc=Graphics.rgb 130 130 130;
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.