Gtk2Hs Tutorial

Drawing with Cairo: Getting Started

It is possible to draw quite complex figures in Gtk2Hs, on screen and in several file formats,
using the Cairo drawing library. Drawing in Gtk2Hs is not very different from drawing in original Cairo,
though that subject is large enough to merit its own tutorials.

Drawing to the screen or in portable network graphics (png), portable document format (pdf),
postscript (ps) or scaleable vector graphics (svg) formats of any Cairo drawing uses some special syntax in Gtk2Hs.
The goal of this appendix is to explain what basic functions you need.

1. Drawing

First the Cairo Graphics module must be imported with import Graphics.Rendering.Cairo.
Then the following function defines an actual drawing:

The type of this function is Render () and from the do notation you can gather
that Render is a monad. Note that this is not the IO monad, which is
used in Graphics.UI.Gtk . The figure, a triangle, is defined by the moveTo,
lineTo and closePath functions, which do what the names suggest. They do not, however,
draw anything, but rather define a path, which is to be followed. The actual drawing of this path is done by the
stroke function. First, however, the color of the lines and their width must
be specified.

Now this figure can be drawn. For this we need a blank widget, with DrawingArea type.
However, you don't draw on this widget itself, the canvas in the example below,
but on its DrawWindow . To get it you use:

But this drawing must be executed in response to an event. One possibility would be
an onButtonPress event as used in Chapter 6.2 with the Event Box. This actually works.
Each time the window is resized the drawing disappears, and it is drawn again when you click
the button. There is another event, however, the Expose event, which sends a signal
each time the window is resized or redrawn on the screen. This fits what is required, so we use:

A frame has also been included, for visual effect, but this is inessential.
But note that the widgetShowAll function appears before the
widgetGetDrawWindow function in the code below. This is required because only a visible
window can be used to draw on!

Now the drawing will always fit the borders defined by the parameters. We've also
set the background color with the paint function, instead of widgetModify .
The paint function paints the current source everywhere within the current
clip region. Note that setSourceRGB not only takes a Double
between 0 and 1 as its parameters instead of Int values between 0 and 65535,
but also 'lives' within the Render monad instead of the IO monad.

To draw the resizable figure, we need to get the size of the drawing area each time
this changes.

Note that this is just like the previous example, except for the actual drawing. This introduces
setSourceRGBA which sets not just the color but also the transparency, as a
measure between 0 and 1. The example also uses a rectangle and a method
fill which fills closed figures with the specified color and transparancy.

Note: Because of a naming conflict with an older
Gtk2Hs drawing library you must either hide fill in the Graphics.UI.Gtk import
statement or use the full name Graphics.Rendering.Cairo.fill .

2. Saving to Files

It's very easy to save a drawing in png, pdf, ps or svg formats. 'Save' is perhaps not
the correct expression, since each different format involves its own rendering. The function is:

renderWith:: MonadIO m => Surface -> Render a -> m a

The Surface is that on which the drawing appears. In these cases this is not the screen
but something you'll have to provide yourself. There are four different functions, one for each type.

withImageSurface
:: Format -- format of pixels in the surface to create
-> Int -- width of the surface, in pixels
-> Int -- height of the surface, in pixels
-> (Surface -> IO a) -- an action that may use the surface. The surface is only valid within in this action.
-> IO a

This is used for portable network graphics (png) files. The Format data type
has four possible constructors, FormatARGB32, FormatRGB24, FormatA8, FormatA1 .
In the example below we use the first. The action that takes a Surface as its
argument will usually be the renderWith function followed by a function to
write to a file. For the png format this would be the function:

surfaceWriteToPNG
:: Surface -- a Surface
-> FilePath -- the name of a file to write to
-> IO ()

withPDFSurface
:: FilePath -- a filename for the PDF output (must be writable)
-> Double -- width of the surface, in points (1 point == 1/72.0 inch)
-> Double -- height of the surface, in points (1 point == 1/72.0 inch)
-> (Surface -> IO a) -- an action that may use the surface. The surface is only valid within in this action.
-> IO a

This function takes different parameters than the previous on, though it is very similar. The
recipe to save is now:

Note the showPage function. Without it the program will compile, and even produce a .pdf
file, but this cannot be read correctly by a pdf reader. The API documentation states the width and height
are in points (and type Double ), so you'll have to check how this works out in practice.

To save a postscript file:

withPSSurface
:: FilePath -- a filename for the PS output (must be writable)
-> Double -- width of the surface, in points (1 point == 1/72.0 inch)
-> Double -- height of the surface, in points (1 point == 1/72.0 inch)
-> (Surface -> IO a) -- an action that may use the surface. The surface is only valid within in this action.
-> IO a

To save you could use the same 'recipe' as above, or the shorter notation:

Note: Please see the Graphics.Rendering.Cairo API documentation
and the general Cairo tutorials and examples for more advanced uses. The Gtk2Hs distribution also
comes with several interesting demonstration examples.