The Input / Output reference is not a subtopic of HOpenGL, but a Haskell subtopic. Besides this, I decided to include this lesson in the tutorial because of two things:
IORefs
. Perhaps you have never heard about them before.
IORefs
can be very important in the development of applications with HOpenGL.
This subtopic is not going to be largely explored; I just want to make sure you have enough basis to develop you own application/games using them. If you are already used to IORefs
, feel free to start the following lesson.
This lesson is very simple. Think about an IORef
as being a way for your functional program to keep the value of a variable (this surely isn't the best way to explain IORefs
, but it will fit our purposes).
Suppose you would like to move a square around the screen, using the arrow keys. Your program will need to know the previous position of the square before moving it to the new position. The current position of the square, as you may have thought, will be stored in an IORef
of type Float
. The first thing you'll need to do is to create two new IORefs
(one for the X position and the other for the Y position). This is done by the following code:
pX <- newIORef 0.0
pY <- newIORef 0.0
Here, pX
and pY
are the name of our IORefs
and, because we initialized them with the value 0.0
IORef Float
. If we used the code:
pB <- newIORef True
then we would create an IORef Bool
instead.
Now think about this question: if you wish to move the square using the arrow keys of your keyboard, wich kind of callback procedure needs to know the current position of the square? Yes, that's right: it is the specialFunc
. So let's declare our specialFunc
callback procedure with a function named special
, which will receive our two IORef Float
:
specialFunc (Just (special pX pY))
The signature of function special
will be, of course:
special :: IORef Float -> IORef Float -> SpecialAction
The way to directly work with IORefs
is very simple. Two basic things need to be done:
IORef
using readIORef
.
IORef
with the changes applied to the old value, using writeIORef
.
Let's apply this to our example. Suppose the left arrow (KeyRight
) is pressed. In this case, we would need to increase the current X position of the square (by 0.2, for example). Then we would have the following pattern matching:
special pX _ KeyRight _ = do
posX <- readIORef pX
writeIORef pX (posX + 0.2)
The following code is more elegant, and has the same effect:
special pX _ KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2)
Now we are half done. We still need to check for other inputs to update the square position, and create a function to effectively draw the square. The function drawSquare
, used in the mouse managment example of the previous lesson, will also be used for this purpose:
drawSquare :: (GLfloat,GLfloat) -> IO ()
drawSquare (x,y) = do
clear [ColorBufferBit]
color (Color3 1.0 1.0 1.0 :: Color3 GLfloat)
beginEnd Quads $ mapM_ vertex [
Vertex3 x y 0.0,
Vertex3 (x + 0.2) y 0.0,
Vertex3 (x + 0.2) (y + 0.2) 0.0,
Vertex3 x (y + 0.2) (0.0 :: GLfloat)]
flush
We also need to force our program to redraw the window every time we update the square position. This is done by calling the postRedisplay
function just after updating the coordinates. This function automatically calls the displayFunc
callback procedure. The special
function will look like this:
special :: IORef Float -> IORef Float -> SpecialAction
special pX _ KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2) >> postRedisplay
special pX _ KeyLeft _ = readIORef pX >>= \posX -> writeIORef pX (posX - 0.2) >> postRedisplay
special _ pY KeyUp _ = readIORef pY >>= \posY -> writeIORef pY (posY + 0.2) >> postRedisplay
special _ pY KeyDown _ = readIORef pY >>= \posY -> writeIORef pY (posY - 0.2) >> postRedisplay
special _ _ _ _ = return ()
The last thing to be done is to create a function that will be the displayFunc
callback procedure. Once again, let's call it display
. This function will receive the two IORefs
, read them, and finally call the function drawSquare
. Remembering that you'll need to import module IOExts to use all the IORef
stuff, here you have the final code:
import GL import GLUT import IOExts myInit :: IO () myInit = do clearColor (Color4 0.0 0.0 0.0 0.0) clear [ColorBufferBit] matrixMode Projection loadIdentity ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0 special :: IORef Float -> IORef Float -> SpecialAction special pX _ KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2) >> postRedisplay special pX _ KeyLeft _ = readIORef pX >>= \posX -> writeIORef pX (posX - 0.2) >> postRedisplay special _ pY KeyUp _ = readIORef pY >>= \posY -> writeIORef pY (posY + 0.2) >> postRedisplay special _ pY KeyDown _ = readIORef pY >>= \posY -> writeIORef pY (posY - 0.2) >> postRedisplay special _ _ _ _ = return () drawSquare :: (GLfloat,GLfloat) -> IO () drawSquare (x,y) = do clear [ColorBufferBit] color (Color3 1.0 1.0 1.0 :: Color3 GLfloat) beginEnd Quads $ mapM_ vertex [ Vertex3 x y 0.0, Vertex3 (x + 0.2) y 0.0, Vertex3 (x + 0.2) (y + 0.2) 0.0, Vertex3 x (y + 0.2) (0.0 :: GLfloat)] flush display :: IORef Float -> IORef Float -> DisplayAction display pX pY = do clear [ColorBufferBit] posX <- readIORef pX posY <- readIORef pY drawSquare (posX, posY) flush main :: IO () main = do GLUT.init Nothing createWindow "IORef" (return ()) [ Single, GLUT.Rgba ] (Just (WindowPosition 100 100)) (Just (WindowSize 500 500)) myInit pX <- newIORef 0.0 pY <- newIORef 0.0 displayFunc (display pX pY) specialFunc (Just (special pX pY)) mainLoop
Please keep in mind that the main purpose of the IORefs
here is to don't let the value of the square coordinates to be lost. If you understood this example well, you won't have any difficulties to understand the uses of IORefs
in the following lessons.
readIORef/writeIORef
routines every time you want to update an IORef
, use the following updateIORef
function:
updateIORef :: IORef a -> (a -> a) -> IO ()
updateIORef ref f = do
val <- readIORef ref
writeIORef ref (f val)
In other words, this code:
readIORef pX >>= \posX -> writeIORef pX (posX + 0.2)
can be replaced by the following one:
updateIORef pX (\s -> s + 0.2)
IORefs
, do it! They are too much imperative, and you are working with a functional language, aren't you?