In this tutorial we will study the FRP binding to the
processing-for-haskell library.
The processing-for-haskell
is suitable to create animations and games.
It embeds Processing language into Haskell.
The application is run with state-machine like approach.
We have a function that renders the state of application
on the screen and we have a function that updates the state
on events.
With FRP we use the same functions for drawing as they are defined in the gloss (see the module Graphics.Proc).
Only to run the application we use FRP approach. The main function is:
type Draw = Pio ()
runApp :: Spec -> Run (Dyn Draw) -> IO ()
The Draw
is a set of drawing instructions to the renderer.
Where Spec
defines the initial settings of the gloss application:
data Spec = Spec
{ spec'setup :: Pio () -- ^ setup instructions }
The spec'setup
is a procedure that is run once at startup.
So to run the application we need to provide the spec and dynamic picture:
runApp :: Spec -> Run (Dyn Draw) -> IO ()
The Draw
is a set of instructions to the OpenGL renderer.
It’s a bit more higher-level but still imperative.
It run inside Pio
-monad which stands for Processing IO
.
The Run
is a monad that has instance of Frp
class, i.e. we can
use MonadIO
and MonadBaseControl
methods with it.
For writing internal mutable variables we use IORef
.
The library dyna-processing
re-exports all drawing primitives
from the processing-for-haskell
library.
It re-exports the core FRP functions of dyna
.
Also it defines special wrappers for Evt
and Dyn
to avoid
typing Evt Run a
since the Run
monad is fixed for gloss applications.
It’s very simple newtype
-wrappers:
newtype Evt a = Evt { unEvt :: Dyna.Evt Run a }
newtype Dyn a = Evt { unDyn :: Dyna.Dyn Run a }
So we can use all the functions from the Dyna
module.
The first program
Let’s define a simple program. It will draw a circle at the position of the mouse:
module Main where
import Dyna.Proc
main :: IO ()
main = runApp spec $ pure pic
where
spec = Spec $ do
size (P2 500 500)
drawCircle :: P2 -> Draw
drawCircle pos = do
background white
translate pos
fill green
stroke green
circle 30 0
pic :: Dyn Draw
pic = drawCircle <$> mouse
Let’s save that to the file HelloProcessing.hs
.
To run the programs we can compile it and run executable
or execute right away with runhaskell
:
stack exec -- runhaskell HelloGloss.hs
We can close the window to stop the application. So let’s study the code. What happens:
pic = drawCircle <$> mouse
We take mouse positions:
mouse :: Dyn P2
The P2
is 2D vector. And we map the positions to the pictures:
drawCircle :: P2 -> Draw
drawCircle pos = do
background white -- clear background to white
translate pos -- translate center to position pos
fill green -- set fill color to green
stroke green -- set stroke color to green
circle 30 0 -- draw circle with radious 30 at the point (P2 0 0)
Note that to draw animations in processing we need to clear the screen first to draw. Otherwise the next frame will be drawn on top of the previous one.
The P2
is a type for 2D points. Also there is a type for 3D points.
As processing can work in 3D.
User IO
Instead of getting events over event loop we have event streams and dynamics that read the user input.
Here is most useful of them:
-- mouse input
mouse :: Dyn P2
mouseRight :: Evt P2
mouseLeft :: Evt P2
mouseWheel :: Evt Float
-- generic gloss events
data Click = Click (Either Key MouseButton) KeyState Modifiers P2
getClicks :: Evt Click
-- key input
keyUp :: Key -> Evt Modifiers
keyDown :: Key -> Evt Modifiers
charUp :: Char -> Evt Modifiers
charDown :: Char -> Evt Modifiers
See the module Dyna.Proc.Run
for complete list of the user input functions.
Conclusion
That’s it. We have described the FRP binding to processing-fro-haskell
.
Peculiarities comparing to processing-for-haskell
:
- special type for game initialisation
Spec
which contains processingsetup
function - main function
runApp
expectsRun (Dyn Draw)
to evaluate - the
Run
is aFrp
-monad (MonadIO
andMonadBaseControl
)
FRP peculiarities:
-
special wrappers for
Evt
andDyn
that hide generic monad which is fixed to theRun
monad -
re-exports core FRP module
Dyna
with all functions specialized for wrappedEvt/Dyn
types. -
internal mutable updates are done with
IORef
=>
Example- Up: Table of contents