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
....
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.
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
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
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
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
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.
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.
Archive file with this example: Cairo-Coordinates004.tar.gz. Be sure to read the file README.txt for further instructions.