zu www.bildungsgueter.de Table of Section Contents previous page

Use of Cairo

Drawing Lines


You can now try different commands of the cairo graphics system. To do that, you will modify the draw instructions in function drawEventHander.

Here are a few examples:

--  This function is the event handler for the "draw" event
--  of the DrawingArea.
--  When it is called, it draws three differently coloured
--  quadrangles of equal size.
drawEventHandler ::  Render ()
drawEventHandler =
   do
    setSourceRGBA 0.5 0.0 0.0 0.5
    rectangle 10 10 120 120
    fill
    setSourceRGBA 0.0 0.5 0.0 0.5
    rectangle 60 60 120 120
    fill
    setSourceRGBA 0.0 0.0 0.5 0.5
    rectangle 110 110 120 120
    fill

Three rectangles filled with translucent colours

Drawing a Background - Using the Widget Size

....

import "gtk3" Graphics.UI.Gtk
import Graphics.Rendering.Cairo

--  This function is the event handler for the "draw" event
--  of the DrawingArea.
--  When it is called, it draws a rectangle that adapts itself to the
--  window size when then window is resized.
drawEventHandler ::  DrawingArea -> Render ()
drawEventHandler drawingArea =
  do
    w <- liftIO $ widgetGetAllocatedWidth drawingArea
    h <- liftIO $ widgetGetAllocatedHeight drawingArea
    setSourceRGB 1.0 1.0 1.0
    Cairo.rectangle 0.0 0.0 (fromIntegral w) (fromIntegral h)
    fill                          --  draw a white background
    setSourceRGB 0.5 0.0 0.0
    Cairo.rectangle 10 10 (fromIntegral w - 20) (fromIntegral h - 20)
    fill
     

--  action to be performed after the window was closed  
destroyEventHandler :: IO ()
destroyEventHandler =
  do mainQuit

  
main :: IO ()
main = do
    initGUI
    --  all needed widgets are created and configured
    window <- windowNew
    paintArea <- drawingAreaNew
    --  the properties of the main window are set
    set window [windowDefaultWidth := 250,
                windowDefaultHeight := 250,
                windowWindowPosition := WinPosCenter,
                windowTitle := "DrawingArea Demo",
                containerChild := paintArea]
    --  all needed callback functions are provided
        on paintArea draw $ (drawEventHandler paintArea)
    on window objectDestroy destroyEventHandler
    --  all widgets are made visible
    widgetShow paintArea
    widgetShow window
    --  the event processing is started
    mainGUI

The event handler for the draw event is now the function

(drawEventHandler paintArea)

This is a frequently used technique in Haskell and it demonstrates the power of currying.

A rectangle that adapts itself to the widget size.


The framework for most of the following examples:

drawDiagram :: Double -> Double -> Render()
drawDiagram width height =
  do
    setSourceRGB 0.5 0.0 0.0
    Cairo.rectangle 10.0 10.0 (width - 20.0) (height - 20.0)
    fill


--  This function handles the draw event of the drawing area.
--  It draws three differently coloured quadrangles.
drawEventHandler ::  DrawingArea -> Render()
drawEventHandler drawingArea =
   do
    w <- liftIO $ widgetGetAllocatedWidth drawingArea
    h <- liftIO $ widgetGetAllocatedHeight drawingArea
    setSourceRGB 1.0 1.0 1.0
    Cairo.rectangle 0.0 0.0 (fromIntegral w) (fromIntegral h)
    fill                          --  draw a white background
    drawDiagram (fromIntegral w) (fromIntegral h)

For most of the following examples, only the function drawDiagram will be shown.

drawDiagram :: Double -> Double -> Render()
drawDiagram width height =
  do
    setSourceRGB  1.0 0.0 0.0
    setLineWidth 12.0
    setLineCap LineCapRound
    moveTo 10.0 10.0
    lineTo 290.0 290.0
    stroke

    setSourceRGBA 0.2 0.2 1.0 0.4
    Cairo.rectangle 30.0 30.0 150.0 150.0
    fill
    Cairo.rectangle 80.0 80.0 150.0 150.0
    fill
    Cairo.rectangle 130.0 130.0 150.0 150.0
    fill

Three partly overlapping quadrangles filled with a translucent colour.

Compare with:

drawDiagram :: Double -> Double -> Render()
drawDiagram width height =
  do
    setSourceRGB  1.0 0.0 0.0
    setLineWidth 12.0
    setLineCap LineCapRound
    moveTo 10.0 10.0
    lineTo 290.0 290.0
    stroke

    setSourceRGBA 0.2 0.2 1.0 0.4
    Cairo.rectangle 30.0 30.0 150.0 150.0
    Cairo.rectangle 80.0 80.0 150.0 150.0
    Cairo.rectangle 130.0 130.0 150.0 150.0
    fill

A contour of three partly overlapping rectangles filled with a translucent colour.

Here the entire contour is filled only once; the effect of repeated filling of some parts of teh quadrangles with a translucent colour connot occur.

When you stroke the same contour, you obtain the following images:

drawDiagram :: Double -> Double -> Render()
drawDiagram width height =
  do
    setSourceRGB  1.0 0.0 0.0
    setLineWidth 12.0
    setLineCap LineCapRound
    moveTo 10.0 10.0
    lineTo 290.0 290.0
    stroke

    setLineJoin LineJoinRound
    setSourceRGBA 0.2 0.2 1.0 0.4
    Cairo.rectangle 30.0 30.0 150.0 150.0
    stroke
    Cairo.rectangle 80.0 80.0 150.0 150.0
    stroke
    Cairo.rectangle 130.0 130.0 150.0 150.0
    stroke

stroke example 1

drawDiagram :: Double -> Double -> Render()
drawDiagram width height =
  do
    setSourceRGB  1.0 0.0 0.0
    setLineWidth 12.0
    setLineCap LineCapRound
    moveTo 10.0 10.0
    lineTo 290.0 290.0
    stroke

    setLineJoin LineJoinRound
    setSourceRGBA 0.2 0.2 1.0 0.4
    Cairo.rectangle 30.0 30.0 150.0 150.0
    Cairo.rectangle 80.0 80.0 150.0 150.0
    Cairo.rectangle 130.0 130.0 150.0 150.0
    stroke

stroking example 2

Drawing Lines

A seemingly simple task like the drawing of some parallel lines is complicated by the fact that the repetitive execution of a sequence of statements is handled in a quite uncommon way.

Suppose you want to translate this piece of C code into Haskell:

    int i;
    for (i = 0; i <= 6; i++) {  /* horizontal lines */
        cairo_move_to(context, 0, 50*i);
        cairo_line_to(context, 300, 50*i);
        cairo_stroke(context);
    }

As you easily see, this piece of code contains a loop statement that executes a sequence of cairo statements seven times. So, this piece of code draws seven horizontal lines. The vertical distance between two lines is 50 pixel, this distance is accieved by using 50 times the current value of the loop counter as y-coordinate of both the start point and the and point of a line.

The core language of Haskell does not provide language elements like loops - such language elements have to be programmed in Haskell itself. While it is a good exercise for an advanced Haskell programmer to do just that, for our purpose it is better to use a facility of module Control.Monad: The function forM_.

The function forM_ takes two arguments:

The function forM_ does not construct any result value which is what we need here: We will use forM_ to repeatedly execute a sequence of Cairo instructions.

This is how a typical use of forM_ looks like:

import Control.Monad                --  to import  forM_

--  further statements

forM_ [0, 50 .. 300] (drawHorizontalLine 300)

The call of forM_ is meant to draw seven equidistant horizontal lines of length 300 pixel. It does so by sending the seven elements of a list one by one to a function that draws a line of specified length.

The list expression [0, 50 .. 300] can be read as: For values from 0 to 300 (included) with increment 50. A more explicit writing form of that list is thus:

[0, 50, 100, 150, 200, 250, 300]

The repeatedly executed function drawHorizontalLine is defined as a function that takes two arguments and yields Render():

drawHorizontalLine :: Double -> Double -> Render ()
drawHorizontalLine length y0 =
  do
    moveTo 0 y0
    relLineTo length 0
    stroke

The first function argument specifies the length of the line to be drawn, the second function argument specifies the y-coordinate of its initial point.

Note that by virtue of Currying, the call

(drawHorizontalLine 300)

yields a function that takes one argument and yields Render(). The call of drawHorizontalLine assigns a value to the first formal function argument.


Drawing a grid and three filled quadrangles

Archive file with this example: Cairo-Coordinates001.tar.gz. Be sure to read the file README.txt for further instructions.

Adding scales:

Archive file with this example: Cairo-Coordinates002.tar.gz. Be sure to read the file README.txt for further instructions.

Adding a slider to control colour opacity

The slider is used to change the opacity of the colour.

Archive file with this example: Cairo-Coordinates004.tar.gz. Be sure to read the file README.txt for further instructions.


previous page Table of Section Contents