Author: Christian.Queinnec@lip6.fr

## 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.