I'm trying to make a toy application, just to get my head around how to write event driven programs in Haskell. What I'm trying to do is draw a line to a canvas which moves forward every time a key is pressed (so it's sort of a primordial cursor in a text editor).
My problem is I can't work out what the best way to count the number of times the user has pressed a key. Obviously I can't use a global variable like I would in an imperative program so presumably I need to pass the state around on the call stack, but in GTK execution descends into the main loop after each event handler returns and since I don't control the main loop I don't see how I can pass the changed global state from one event handler. So how can one event handler ever pass state onto another event handler?
I have a sort of partial solution here, where the keyboard event re-curries myDraw and sets it as a new the event handler. I'm not sure if this solution can be extended, or even if it's a good idea.
What's the best particle solution to this problem?
import Graphics.UI.Gtk
import Graphics.Rendering.Cairo
main :: IO ()
main= do
initGUI
window <- windowNew
set window [windowTitle := "Hello World",
windowDefaultWidth := 300, windowDefaultHeight := 200]
canvas <- drawingAreaNew
containerAdd window canvas
widgetShowAll window
draWin <- widgetGetDrawWindow canvas
canvas `on` exposeEvent $ do liftIO $ renderWithDrawable draWin (myDraw 10)
return False
window `on` keyPressEvent $ onKeyboard canvas
window `on` destroyEvent $ do liftIO mainQuit
return False
mainGUI
onKeyboard :: DrawingArea -> EventM EKey Bool
onKeyboard canvas = do
liftIO $ do drawWin <- widgetGetDrawWindow canvas
canvas `on` exposeEvent $ do liftIO $renderWithDrawable drawWin (myDraw 20)
return False
widgetQueueDraw canvas
return False
myDraw :: Double -> Render ()
myDraw pos = do
setSourceRGB 1 1 1
paint
setSourceRGB 0 0 0
moveTo pos 0
lineTo pos 20
stroke
First off, you could have a global. Ignoring that solution as bad form, this looks like a job for an MVar. In main
you just make a new MVar which you can update in onKeyboard
and check in myDraw
:
...
import Control.Concurrent.MVar
main = do
...
mv <- newMVar 0
....
canvas `on` exposeEvent $ do liftIO $ renderWithDrawable draWin (myDraw mv 10)
...
window `on` keyPressEvent $ onKeyboard canvas mv
onKeyboard canvas mv = do
modifyMVar_ mv (\x -> return (x + 1))
....
myDraw mv pos = do
val <- readMVar mv
...
Note that sharing mutable state is also often useful when passing functions as arguments by first using partial application to provide the MVar
(or TVar
, IORef
, whatever).
Oh, and a warning: MVars aren't strict - if the application has the potential to write lots without forcing the value (i.e. reading and comparing the contained Int
), then you should force the value before writing to avoid building a huge thunk.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With