Module OcamlCanvas

The OCaml-Canvas library

The OCaml-Canvas library provides a portable Canvas framework for OCaml. It features an interface similar to HTML5 Canvas, and can render to native X11, MS Windows or macOS windows. It also features a small wrapper around HTML5 Canvas, allowing to use the library in any web browser. In addition, it allows to handle keyboard and mouse events.

Quick start

Before using any function in the library (and assuming the OcamlCanvas.V1 module has been opened), the user should call Backend.init so that the library makes any internal initialization it needs for the current backend.

Once the backend is initialized, one can create Canvas objects using the Canvas.createOnscreen and Canvas.createOffscreen functions. The first one creates canvases contained in regular windows (which are simulated in the Javascript backend), while the second one creates canvases that are not rendered on screen, which can be useful to save complex images that can then simply be copied to a visible canvas. Onscreen canvases are hidden by default, and Canvas.show should be called on them to make them visible.

Drawing on a canvas can be perfomed using various drawing primitives, the most ubiquitous being Canvas.clearPath, Canvas.moveTo, Canvas.lineTo, Canvas.arc, Canvas.bezierCurveTo, Canvas.fill and Canvas.stroke. These functions allow to build a path step by step and either fill it completely or draw its outline. It is also possible to directly render some text with the Canvas.fillText and Canvas.strokeText functions.

The canvas drawing style can be customized using functions such as Canvas.setFillColor, Canvas.setStrokeColor or Canvas.setLineWidth. The font used to draw text can be specified with the Canvas.setFont function. It is also possible to apply various transformations to a canvas, such as translation, rotation and scaling, with the functions Canvas.transform, Canvas.translate, Canvas.scale, Canvas.rotate and Canvas.shear. All these styling elements can be saved and restored to/from a state stack using the functions Canvas.save and Canvas.restore.

Once the canvases are ready, we may start handling events for these canvases. To do so, we use the Backend.run function, which runs an event loop. This function MUST be the last instruction of the program. It takes a single argument, which is a function to be executed when the event loop has finished running. The event loop may be stopped by calling Backend.stop from any update function.

Each event reports at least the canvas on which it occured, and its timestamp. It may also report mouse coordinates for mouse events, or keyboard status for keyboard events.

An actual example

The following program creates a windowed canvas with an orange background, a cyan border, and the "Hello world !" text drawn rotated in the middle. The user may press the "Escape" key or close the window to exit the program. It will show the number of frames displayed when quitting.

open OcamlCanvas.V1

let () =

  Backend.init ();

  let c = Canvas.createOnscreen ~title:"Hello world"
            ~pos:(300, 200) ~size:(300, 200) () in

  Canvas.setFillColor c Color.orange;
  Canvas.fillRect c ~pos:(0.0, 0.0) ~size:(300.0, 200.0);

  Canvas.setStrokeColor c Color.cyan;
  Canvas.setLineWidth c 10.0;
  Canvas.clearPath c;
  Canvas.moveTo c (5.0, 5.0);
  Canvas.lineTo c (295.0, 5.0);
  Canvas.lineTo c (295.0, 195.0);
  Canvas.lineTo c (5.0, 195.0);
  Canvas.closePath c;
  Canvas.stroke c;

  Canvas.setFont c "Liberation Sans" ~size:36.0
    ~slant:Font.Roman ~weight:Font.bold;

  Canvas.setFillColor c (Color.of_rgb 0 64 255);
  Canvas.setLineWidth c 1.0;
  Canvas.save c;
  Canvas.translate c (150.0, 100.0);
  Canvas.rotate c (-. Const.pi_8);
  Canvas.fillText c "Hello world !" (-130.0, 20.0);
  Canvas.restore c;

  Canvas.show c;

  let e1 =
    React.E.map (fun { Event.canvas = _; timestamp = _; data = () } ->
        Backend.stop ()
      ) Event.close
  in

  let e2 =
    React.E.map (fun { Event.canvas = _; timestamp = _;
                       data = { Event.key; char = _; flags = _ }; _ } ->
        if key = KeyEscape then
          Backend.stop ()
      ) Event.key_down
  in

  let e3 =
    React.E.map (fun { Event.canvas = _; timestamp = _;
                       data = { Event.position = (x, y); button } } ->
        Canvas.setFillColor c Color.red;
        Canvas.clearPath c;
        Canvas.arc c ~center:(float_of_int x, float_of_int y)
          ~radius:5.0 ~theta1:0.0 ~theta2:(2.0 *. Const.pi) ~ccw:false;
        Canvas.fill c ~nonzero:false
      ) Event.button_down
  in

  let frames = ref 0L in

  let e4 =
    React.E.map (fun { Event.canvas = _; timestamp = _ } ->
        frames := Int64.add !frames Int64.one
      ) Event.frame
  in

  Backend.run (fun () ->
      ignore e1; ignore e2; ignore e3; ignore e4;
      Printf.printf "Displayed %Ld frames. Goodbye !\n" !frames)
module V1 : sig ... end

The OCaml-Canvas module is versioned. This is version 1. It is guaranteed that this interface will always remain compatible with existing programs, provided that the modules defined here ARE NEVER included in other modules nor opened globally. Local opens should be performed very carefully, as new identifiers may be introduced in modules and thus shadow any identifier defined before the open directive. An effort will be made to avoid introducing new identifiers that are of length 3 of less, or starting with a single character followed by an underscore. Hence such identifiers should be safe to use without risking to be shadowed.