Draw something on the screen... and interact with it!

Summary of the previous episodes: 10 days ago Richard Jones complained about the difficulties to achieve simple tasks (drawing a function graph on the screen) on modern computers with modern programming languages; the day after Erik de Castro Lopo replied with a post in which he used GTK and Cairo (better: the OCaml bindings) to achieve the result to draw a simple function on the screen. Yesterday Matias Giovannini added some pepper to this argument using SDL to draw the Newton fractal.

So, what can be added to all this? With a perfect graphic toy you can draw on a window with simple commands, of course, but you also want to interact with the objects you drew. So I elaborated Erik example to add some keyboard and mouse interaction with the graphics on the screen.

Downloading and compiling

First of all, download the source code or, if you want the latest version, clone my GIT repository:

$ git clone https://www.ex-nunc.org/projects/pdonadeo/cairo_toy.git cairo_toy.git

To compile the program you need:

  • OCaml (I have version 3.10.2, but probably 3.10.0 or 3.10.1 are ok);
  • Lablgtk2, the OCaml binding to GTK2;
  • the OCaml binding to libcairo.

All these packages are available in any recent Linux distribution; on Debian/Ubuntu:

$ aptitude install ocaml liblablgtk2-ocaml-dev libcairo-ocaml-dev

To compile instruct this command inside the program directory:

$ ocamlbuild demo_toy.native

The code

The program is very simple and is essentially derived from Erik's code: the core is the functor Toy_maker.Make which accepts, as input, a module with the following signature INTERACTOR:

module type INTERACTOR =
sig
  type state
  val init_state : state
  val win_title : string
  val init_width : int
  val init_height : int
  val cmd_line_handler : state -> string array -> state
  val keyboard_callback : state -> GdkEvent.Key.t -> state * bool
  val pointer_buttons_callback : state -> GdkEvent.Button.t -> state * bool
  val pointer_motion_callback : state -> GdkEvent.Motion.t -> state * bool
  val pointer_scroll_callback : state -> GdkEvent.Scroll.t -> state * bool
  val repaint : state -> Cairo.t -> int -> int -> state * bool
end

In this module the user must provide a type state, which contains the application state, some initialization values, a command line handler (in case you need) and 4 event handlers for the following events:

  • keyboard;
  • mouse motion;
  • mouse buttons;
  • mouse wheel event (scroll events in GTK).

The user also provides a repaint function, which takes care of repainting the Cairo context.

As a demo I wrote a simple My_interactor module implementing the following simple features:

  • left click on the gray background creates a new circle;
  • left click inside an existing circle moves it around;
  • right click inside a circle deletes it;
  • the mouse wheel zooms (in and out);
  • middle click is used to pan;

Here is the result.

Yes, it's somewhat dull, but it does its job. Have fun!