{-# 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