{-# LANGUAGE DeriveFunctor, ExistentialQuantification, Rank2Types #-} ----------------------------------------------------------------------------- -- | -- Module : FreeGame.UI -- Copyright : (C) 2013 Fumiaki Kinoshita -- License : BSD-style (see the file LICENSE) -- -- Maintainer : Fumiaki Kinoshita -- Stability : provisional -- Portability : non-portable -- Provides the "free" embodiment. ---------------------------------------------------------------------------- module FreeGame.UI ( UI(..) , reUI , reFrame , reGame , Frame , Game , FreeGame(..) ) where import FreeGame.Class import FreeGame.Internal.Finalizer import FreeGame.Types import Control.Applicative import qualified Data.Map as Map import FreeGame.Data.Bitmap (Bitmap) import Data.Color import Data.BoundingBox import Control.Monad.Free.Church (F, iterM) import Control.Monad.Trans.Iter (IterT, foldM) import Control.Monad (join) data UI a = Draw (forall m. (Applicative m, Monad m, Picture2D m, Local m) => m a) | PreloadBitmap Bitmap a | FromFinalizer (FinalizerT IO a) | KeyStates (Map.Map Key ButtonState -> a) | MouseButtons (Map.Map Int ButtonState -> a) | MousePosition (Vec2 -> a) | MouseInWindow (Bool -> a) | TakeScreenshot (Bitmap -> a) | Bracket (Frame a) | SetFPS Int a | SetTitle String a | ShowCursor a | HideCursor a | ClearColor Color a | GetFPS (Int -> a) | ForkFrame (Frame ()) a | GetBoundingBox (BoundingBox2 -> a) | SetBoundingBox BoundingBox2 a deriving Functor type Game = IterT Frame type Frame = F UI -- | Generalize `Game` to any monad based on `FreeGame`. reGame :: (FreeGame m, Monad m) => Game a -> m a reGame = Control.Monad.Trans.Iter.foldM (join . reFrame) {-# RULES "reGame/sameness" reGame = id #-} {-# INLINE[1] reGame #-} -- | Generalize `Frame` to any monad based on `FreeGame`. reFrame :: (FreeGame m, Monad m) => Frame a -> m a reFrame = iterM (join . reUI) {-# RULES "reFrame/sameness" reFrame = id #-} {-# INLINE[1] reFrame #-} reUI :: FreeGame f => UI a -> f a reUI (Draw m) = draw m reUI (PreloadBitmap bmp cont) = cont <$ preloadBitmap bmp reUI (FromFinalizer m) = fromFinalizer m reUI (KeyStates cont) = cont <$> keyStates_ reUI (MouseButtons cont) = cont <$> mouseButtons_ reUI (MousePosition cont) = cont <$> globalMousePosition reUI (MouseInWindow cont) = cont <$> mouseInWindow reUI (TakeScreenshot cont) = cont <$> takeScreenshot reUI (Bracket m) = bracket m reUI (SetFPS i cont) = cont <$ setFPS i reUI (SetTitle t cont) = cont <$ setTitle t reUI (ShowCursor cont) = cont <$ showCursor reUI (HideCursor cont) = cont <$ hideCursor reUI (ClearColor col cont) = cont <$ clearColor col reUI (GetFPS cont) = cont <$> getFPS reUI (ForkFrame m cont) = cont <$ forkFrame m reUI (GetBoundingBox cont) = cont <$> getBoundingBox reUI (SetBoundingBox bb cont) = cont <$ setBoundingBox bb {-# INLINE[1] reUI #-} {-# RULES "reUI/sameness" reUI = id #-} class (Picture2D m, Local m, Keyboard m, Mouse m, FromFinalizer m) => FreeGame m where -- | Draw an action that consist of 'Picture2D''s methods. draw :: (forall f. (Applicative f, Monad f, Picture2D f, Local f) => f a) -> m a -- | Load a 'Bitmap' to avoid the cost of the first invocation of 'bitmap'. preloadBitmap :: Bitmap -> m () -- | Run a 'Frame', and release all the matter happened. bracket :: Frame a -> m a -- | Run a 'Frame' action concurrently. Do not use this function to draw pictures. forkFrame :: Frame () -> m () -- | Generate a 'Bitmap' from the front buffer. takeScreenshot :: m Bitmap -- | Set the goal FPS. setFPS :: Int -> m () setTitle :: String -> m () showCursor :: m () hideCursor :: m () clearColor :: Color -> m () -- | Get the actual FPS value. getFPS :: m Int getBoundingBox :: m BoundingBox2 setBoundingBox :: BoundingBox2 -> m () instance FreeGame UI where draw = Draw {-# INLINE draw #-} preloadBitmap bmp = PreloadBitmap bmp () {-# INLINE preloadBitmap #-} bracket = Bracket {-# INLINE bracket #-} forkFrame m = ForkFrame m () takeScreenshot = TakeScreenshot id setFPS a = SetFPS a () setTitle t = SetTitle t () showCursor = ShowCursor () hideCursor = HideCursor () clearColor c = ClearColor c () getFPS = GetFPS id getBoundingBox = GetBoundingBox id setBoundingBox s = SetBoundingBox s () overDraw :: (forall m. (Applicative m, Monad m, Picture2D m, Local m) => m a -> m a) -> UI a -> UI a overDraw f (Draw m) = Draw (f m) overDraw _ x = x {-# INLINE overDraw #-} instance Affine UI where translate v = overDraw (translate v) {-# INLINE translate #-} rotateR t = overDraw (rotateR t) {-# INLINE rotateR #-} rotateD t = overDraw (rotateD t) {-# INLINE rotateD #-} scale v = overDraw (scale v) {-# INLINE scale #-} instance Picture2D UI where bitmap x = Draw (bitmap x) {-# INLINE bitmap #-} bitmapOnce x = Draw (bitmapOnce x) {-# INLINE bitmapOnce #-} line vs = Draw (line vs) polygon vs = Draw (polygon vs) polygonOutline vs = Draw (polygonOutline vs) circle r = Draw (circle r) circleOutline r = Draw (circleOutline r) thickness t = overDraw (thickness t) {-# INLINE thickness #-} color c = overDraw (color c) {-# INLINE color #-} blendMode m = overDraw (blendMode m) {-# INLINE blendMode #-} instance Local UI where getLocation = Draw getLocation instance FromFinalizer UI where fromFinalizer = FromFinalizer {-# INLINE fromFinalizer #-} instance Keyboard UI where keyStates_ = KeyStates id instance Mouse UI where globalMousePosition = MousePosition id -- mouseWheel = MouseWheel id mouseButtons_ = MouseButtons id mouseInWindow = MouseInWindow id