{-# Language FlexibleContexts #-}
module Graphics.Proc.Core.Run(
Proc(..), runProc, Draw, Update, TimeInterval
) where
import Control.Monad.IO.Class
import Data.Default
import Data.IORef
import qualified Graphics.Rendering.OpenGL as G
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Graphics.Proc.Core.State
import Graphics.Proc.Core.GLBridge
-- | A alias for value update inside processing IO-monad.
type Update s = s -> Pio s
-- | An alias for processing procedures.
type Draw = Pio ()
-- | It holds all processing standard callbacks.
-- With it we can set the setup, draw, and update functions.
-- Here we can specify how to react on user-input.
--
-- All functions update the program state. They take it in as an argument and produce as result.
-- In Haskell we can not manipulate global variables with such ease as Processing provides.
-- So we have to find out another way to update the state. The natural way for Haskell is to keep
-- the things as explicit as possible. That leads to the following decisions:
--
-- * @setup@ returns the initial state.
--
-- * @draw@ takes the state as an argument and draws it.
--
-- * @update@ should take in the current state and return back the next state.
--
-- * All input processing functions also manipulate the state explicitly by passing arguments.
--
-- Notice that the processing function draw is split on two functions: draw and update.
-- The draw is only for drawing the program state and update is for state update.
--
-- There is a useful function procUpdateTime that provides a time interval that has passed since
-- the previous update of the state. It can be useful for physics engines.
data Proc s = Proc
{ procSetup :: Pio s
, procUpdate :: Update s
, procUpdateTime :: TimeInterval -> Update s
, procDraw :: s -> Draw
-- mouse callbacks
, procMousePressed :: Update s
, procMouseReleased :: Update s
, procMouseClicked :: Update s
, procMouseDragged :: Update s
, procMouseMoved :: Update s
-- keyboard callbacks
, procKeyPressed :: Update s
, procKeyReleased :: Update s
, procKeyTyped :: Update s
}
instance Default (Proc s) where
def = Proc
{ procSetup = return $ error "No setup is defined. Please define the procSetup value."
, procUpdate = return
, procUpdateTime = const return
, procDraw = const (return ())
-- mouse
, procMousePressed = return
, procMouseReleased = return
, procMouseClicked = return
, procMouseDragged = return
, procMouseMoved = return
-- keyboard
, procKeyPressed = return
, procKeyReleased = return
, procKeyTyped = return
}
data St s = St
{ stUser :: s
, stGlobal :: GlobalState }
initSt :: Proc s -> IO (St s)
initSt p = do
(user, global) <- runPio (procSetup p) =<< defGlobalState
return $ St user global
updateSt :: IORef (St s) -> Update s -> IO ()
updateSt ref f = do
st <- get ref
(user, global) <- runPio (f (stUser st)) (stGlobal st)
ref $= St user global
passSt :: IORef (St s) -> Pio () -> IO ()
passSt ref p = updateSt ref $ \s -> p >> return s
-- | The main function for rendering processing actions.
-- It sets the scene and starts the rendering of animation.
runProc :: Proc s -> IO ()
runProc p = do
setupWindow
ref <- newIORef =<< initSt p
nextFrame ref
displayCallback $= display ref
keyboardMouseCallback $= Just (keyMouse ref)
motionCallback $= Just (mouseMotion ref)
passiveMotionCallback $= Just (passiveMouseMotion ref)
mainLoop
where
display ref = updateSt ref $ \s -> do
liftIO $ loadIdentity
procDraw p s
liftIO $ swapBuffers
updateFrameCount
return s
idle ref = do
loopInfo <- getLoopInfo ref
case loopInfo of
Loop -> updateLoopState ref
NoLoop -> return ()
Redraw -> updateLoopState ref >> passSt ref (putLoopMode NoLoop)
nextFrame ref
updateLoopState ref = updateSt ref $ \s -> do
s1 <- procUpdate p s
dt <- getDuration
s2 <- procUpdateTime p dt s1
liftIO $ postRedisplay Nothing
return s2
nextFrame ref = do
timeOut <- getTimeoutInterval ref
addTimerCallback timeOut (idle ref)
keyMouse ref key keyState modifiers pos = updateSt ref $ \s -> do
putPosition pos
case keyState of
Down -> do
case key of
MouseButton mb -> do
putMouseButton (Just mb)
procMousePressed p s
keyPress -> do
putKeyPress keyPress
procKeyPressed p s
Up ->
case key of
Char ch -> procKeyReleased p s
SpecialKey sk -> return s
MouseButton mb -> do
putMouseButton Nothing
procMouseReleased p s
mouseMotion ref pos = passSt ref $ putPosition pos
passiveMouseMotion ref pos = passSt ref $ putPosition pos
getTimeoutInterval ref = readRef getter ref
where getter = fmap (round . (1000 * ) . recip) getFrameRate
getLoopInfo ref = readRef getLoopMode ref
readRef getter ref = do
st <- fmap stGlobal $ get ref
fmap fst $ runPio getter st