scenegraph-0.1: src/Graphics/SceneGraph/Reactive.hs
----------------------------------------------------------------------
-- |
-- Module : Graphics.SceneGraph.Reactive
-- Copyright : (c) Mark Wassell 2008
-- License : LGPL
--
-- Maintainer : mwassell@bigpond.net.au
-- Stability : experimental
-- Portability : portable
--
-- This module provides the ability to 'enliven' a scene with widgets
-- which act as sources of events and are sinks. For example button widgets
-- and text string display areas.
--
-- runSceneReactive takes a scene and an instance of SGM monad. The latter
-- would have been built up to provide a series of actions to enliven
-- the scene. The monad is run prior to passing the scene to the viewport
-- engine.
----------------------------------------------------------------------
module Graphics.SceneGraph.Reactive where
import Data.Reactive
import Graphics.SceneGraph.Basic
import Control.Monad.State
import Control.Concurrent.MVar
import Graphics.SceneGraph.MySTM
import Graphics.SceneGraph.SimpleViewport
import Data.IORef
import Graphics.Rendering.OpenGL.GL.BasicTypes (GLdouble)
import Data.Graph.Inductive (Node)
import Graphics.SceneGraph.SimpleViewport (GSRef,GraphicsState(..))
data Widget = Button Scene | Slider Scene | Wheel Scene | WText Scene
{--
The widget building functions below assume the following structure in the
scene graph for the following widgets:
Simple Button: Handler -> Switch -> Material -> Sphere
-> Material -> Sphere
Button -> Group -> Group -> Transform -> Material -> Transform -> Torus
Transform -> Material -> Cube
-> Transform -> Simple Button
Slider == Same as button in structure BUT has a different output and the button can slide.
Text = Text
--}
-- | This monad allows us to compose widget building actions on the scene.
type SGM = StateT (Scene,MVar Bool ,GSRef) IO
getNodeByLabel :: String -> SGM Node
getNodeByLabel lbl = do
((sg,_),_,_) <- get
return $ getByLabel sg lbl
-- | Turn the supplied node into an button style widget
-- Int needs to reference a handler
mkButton :: Int -> SGM (Event Bool)
mkButton nde = do
((sg,start),sem,ref) <- get
let nde' = findHandlerDown sg nde
-- lift $ putStrLn $ "handler " ++ show nde'
(ev,snk') <- lift $ mkEvent
let snk _ = snk' True
let (SceneNode ide (Handler (Just (f,_)) dh)) = llab sg nde'
sg' = replaceNode'' sg (SceneNode ide (Handler (Just (f,snk)) dh ))
put ((sg',start),sem,ref)
return $ ev
-- | Turn the referenced node into a slider style widget
mkSlider :: Int -> SGM (Event GLdouble)
mkSlider nde = do
((sg,start),sem,ref) <- get
(ev,snk) <- lift $ mkEvent
let (SceneNode ide (Handler ch (Just (f,_)))) = llab sg nde
sg' = replaceNode'' sg (SceneNode ide (Handler ch (Just (f,snk)) ))
put ((sg',start),sem,ref)
return $ ev
doText :: Int -> MVar Bool -> GSRef -> Sink String
doText nde sem ref a = updateNode nde sem ref (Text $ a)
updateNode :: Node -> MVar Bool -> IORef GraphicsState -> SceneData -> IO ()
updateNode nde sem ref dte = atomically sem $ do
gs <- readIORef ref
case gsScene gs of
Just (gr,start) -> do
let (SceneNode lab _) = llab gr nde
gr' = replaceNode'' gr (SceneNode lab dte)
writeIORef ref (gs { gsDisplayList=Nothing,gsScene = Just (gr',start)})
return ()
_ -> return ()
-- | Turn the referenced node into a text sink widget
mkText :: Int -> SGM (Sink String)
mkText nde = do
((sg,_),sem,ref) <- get
let nde' = findTextDown sg nde
return $ doText nde' sem ref
-- | Run a scene with supplied Event Action builder
runSceneReactive :: Scene -> SGM (Event Action) -> IO ()
runSceneReactive scene sr = do
sem <- newMVar True
ref <- newTVar newState
-- We have pass sem and ref as these are need when the widgets are
-- doing something when we run the scene.
(x, (scene',sem,ref)) <- runStateT sr (scene,sem,ref)
modifyIORef ref (\s -> s { gsScene = Just scene' })
forkE x
setupGUI sem ref