gloss-relative-0.1.2.0: src/Graphics/Gloss/Relative/Interface.hs
-- | Various game modes using frames instead of pictures.
module Graphics.Gloss.Relative.Interface
( module Graphics.Gloss.Data.Display
, module Graphics.Gloss.Data.Picture
, module Graphics.Gloss.Data.Color
, displayRelative, displayRelativeIO, playRelative, playRelativeIO
, Controller(..), Event(..), Mouse(..), Key(..), SpecialKey(..), MouseButton(..), KeyState(..), Modifiers(..))
where
import Graphics.Gloss.Data.Display
import Graphics.Gloss.Data.Picture
import Graphics.Gloss.Data.Color
import Graphics.Gloss.Relative.Frame
import Graphics.Gloss.Relative.Internal.Dimension
import qualified Graphics.Gloss.Relative.Internal.Picture as Picture
import qualified Graphics.Gloss.Relative.Internal.Window as Window
import qualified Graphics.Gloss.Relative.Internal.Frame as Frame
import qualified Graphics.Gloss.Relative.Internal.Raster as Raster
import qualified Graphics.Gloss.Relative.Internal.Cache as Cache
import qualified Graphics.Gloss.Interface.Pure.Display as Gloss
import qualified Graphics.Gloss.Interface.Pure.Game as Gloss
import Graphics.Gloss.Interface.Pure.Game (Key(..), SpecialKey(..), MouseButton(..), KeyState(..), Modifiers(..))
import qualified Graphics.Gloss.Interface.IO.Display as Gloss
import Graphics.Gloss.Interface.IO.Display (Controller(..))
import qualified Graphics.Gloss.Interface.IO.Game as Gloss
import qualified Graphics.Gloss.Rendering as Gloss
import Control.Monad
import qualified Data.Set as Set
import Data.IORef
import System.Exit
-- | A custom Gloss event type that knows about frame regions.
data Event
-- | A key press.
= EventKey { eventKey :: Key, eventKeyState :: KeyState, eventModifiers :: Modifiers, eventMouse :: Mouse }
-- | A mouse movement.
| EventMotion { eventMouse :: Mouse }
deriving (Eq, Show)
-- | Information about the mouse.
data Mouse = Mouse
{ mousePosition :: Point -- ^ Current mouse position.
, mouseInside :: [String] -- ^ List of regions in which the mouse is currently inside. __Note:__ The exact regions correspond to the labels defined in the current 'Frame'.
} deriving (Eq,Show)
fromGlossEvent :: Gloss.Event -> Window.RegionHandler -> IO (Either Event Dimension)
fromGlossEvent (Gloss.EventMotion pos) region = do
rs <- region pos
return $ Left $ EventMotion (Mouse pos $ Set.toList rs)
fromGlossEvent (Gloss.EventKey k st m pos) region = do
rs <- region pos
return $ Left $ EventKey k st m (Mouse pos $ Set.toList rs)
fromGlossEvent (Gloss.EventResize screen) region = do
return $ Right (screenSizeToDimension screen)
-- | A variant of 'Gloss.display' using 'Frame'.
displayRelative
:: Display -- ^ Display mode.
-> Color -- ^ Background color.
-> Frame -- ^ The frame to draw.
-> IO ()
displayRelative dis backColor frame = do
screen <- getDisplayDimension dis
let pic = renderStaticFrame frame screen
Gloss.display dis backColor pic (Picture.flattenPicture pic)
-- | A variant of 'Gloss.displayIO' using 'Frame'.
displayRelativeIO
:: Display -- ^ Display mode.
-> Color -- ^ Background color.
-> IO Frame -- ^ Action to produce the current frame.
-> (Controller -> IO ()) -- ^ Callback to take the display controller.
-> IO ()
displayRelativeIO dis backColor makeFrame eatController = do
screen <- getDisplayDimension dis
let makePicture = do
frame <- makeFrame
let pic = renderStaticFrame frame screen
return (Picture.flattenPicture pic)
Gloss.displayIO dis backColor makePicture eatController
-- | A variant of 'Gloss.play' using 'Frame'. The resulting picture is automatically redimensioned on resize events.
playRelative
:: Display -- ^ Display mode.
-> Color -- ^ Background color.
-> Int -- ^ Number of simulation steps to take for each second of real time.
-> world -- ^ The initial world.
-> (world -> Frame) -- ^ A function to convert the world a picture.
-> (Event -> world -> world)
-- ^ A function to handle input events.
-> (Float -> world -> world)
-- ^ A function to step the world one iteration.
-- It is passed the period of time (in seconds) needing to be advanced.
-> IO ()
playRelative display backColor simResolution worldStart worldToFrame worldHandleEvent worldAdvance = do
let handleEvent (EventKey (Gloss.SpecialKey Gloss.KeyEsc) Gloss.Down _ _) st = exitSuccess
handleEvent ev st = return $ worldHandleEvent ev st
playRelativeIO display backColor simResolution worldStart (return . worldToFrame) handleEvent (\x -> return . worldAdvance x)
-- | A variant of 'Gloss.playIO' using 'Frame'. The resulting picture is automatically redimensioned on resize events.
playRelativeIO
:: Display -- ^ Display mode.
-> Color -- ^ Background color.
-> Int -- ^ Number of simulation steps to take for each second of real time.
-> world -- ^ The initial world.
-> (world -> IO Frame) -- ^ A function to convert the world a picture.
-> (Event -> world -> IO world)
-- ^ A function to handle input events.
-> (Float -> world -> IO world)
-- ^ A function to step the world one iteration.
-- It is passed the period of time (in seconds) needing to be advanced.
-> IO ()
playRelativeIO display backColor simResolution worldStart worldToFrame worldHandleEvent worldAdvance = do
handler :: IORef Window.RegionHandler <- newIORef mempty
screen <- getDisplayScreenSize display
let dim = screenSizeToDimension screen
-- independent gloss state for parallel rendering
glossState <- Gloss.initState
cache <- Cache.newCacheTable
currentFrame :: IORef Int <- newIORef 0 -- wraps around
offscreen :: IORef (IO Raster.Offscreen) <- newIORef (Raster.createOffscreen screen)
let draw (w,s) = do
i <- readIORef currentFrame
f <- worldToFrame w
(Window.WindowOutput pic h) <- Frame.renderDynamicFrame i cache glossState offscreen f s
writeIORef handler $! h
writeIORef currentFrame $! i + 1
Cache.evictOldCacheTable (i+1) simResolution cache
return (Picture.flattenPicture pic)
let handleEvent ev (w,s) = do
h <- readIORef handler
fromGlossEvent ev h >>= \e -> case e of
Left ev' -> liftM (,s) (worldHandleEvent ev' w)
Right s' -> return (w,s')
let advance time (w,s) = liftM (,s) (worldAdvance time w)
Gloss.playIO display backColor simResolution (worldStart,dim) draw handleEvent advance