free-game 0.9 → 0.9.1
raw patch · 9 files changed
+183/−56 lines, 9 files
Files
- Graphics/UI/FreeGame.hs +5/−2
- Graphics/UI/FreeGame/Base.hs +33/−0
- Graphics/UI/FreeGame/Data/Font.hs +21/−39
- Graphics/UI/FreeGame/Data/Text.hs +40/−0
- Graphics/UI/FreeGame/GUI.hs +25/−0
- Graphics/UI/FreeGame/GUI/GLFW.hs +41/−14
- Graphics/UI/FreeGame/Internal/Raindrop.hs +11/−0
- Graphics/UI/FreeGame/Types.hs +4/−0
- free-game.cabal +3/−1
Graphics/UI/FreeGame.hs view
@@ -17,11 +17,12 @@ module Graphics.UI.FreeGame.Base, module Graphics.UI.FreeGame.Data.Bitmap, module Graphics.UI.FreeGame.Data.Font, + module Graphics.UI.FreeGame.Data.Text, module Graphics.UI.FreeGame.Data.Color, module Graphics.UI.FreeGame.GUI, module Graphics.UI.FreeGame.Util, - module Graphics.UI.FreeGame.Types - + module Graphics.UI.FreeGame.Types, + module Linear ) where import Graphics.UI.FreeGame.Base @@ -30,10 +31,12 @@ import Graphics.UI.FreeGame.Types import Graphics.UI.FreeGame.Data.Bitmap import Graphics.UI.FreeGame.Data.Font +import Graphics.UI.FreeGame.Data.Text import Graphics.UI.FreeGame.Data.Color import qualified Graphics.UI.FreeGame.GUI.GLFW as GLFW import Control.Monad.Free.Church import Data.Default +import Linear hiding (rotate) -- | 'Game' is a "free" monad which describes GUIs. -- This monad is an instance of 'Picture2D' so you can create it using 'fromBitmap' and can be transformed with 'translate', 'scale', 'rotate', 'colored'.
Graphics/UI/FreeGame/Base.hs view
@@ -28,6 +28,7 @@ ,_LiftUI -- * Classes ,Picture2D(..)+ ,Figure2D(..) ,Keyboard(..) ,Mouse(..) ,FromFinalizer(..)@@ -127,6 +128,14 @@ translate :: V2 Float -> p a -> p a colored :: Color -> p a -> p a +class Picture2D p => Figure2D p where+ line :: [V2 Float] -> p ()+ polygon :: [V2 Float] -> p ()+ polygonOutline :: [V2 Float] -> p ()+ circle :: Float -> p ()+ circleOutline :: Float -> p ()+ thickness :: Float -> p a -> p a+ -- | The class of types that can handle inputs of the keyboard. class Keyboard t where keyChar :: Char -> t Bool@@ -202,6 +211,14 @@ scale = (t) . scale; \ colored = (t) . colored } +#define MK_FIGURE_2D(cxt, ty, l, t) instance (Figure2D m cxt) => Figure2D (ty) where { \+ line = (l) . line; \+ polygon = (l) . polygon; \+ polygonOutline = (l) . polygonOutline; \+ circle = (l) . circle; \+ circleOutline = (l) . circleOutline; \+ thickness = (t) . thickness }+ #define MK_KEYBOARD(cxt, ty, l) instance (Keyboard m cxt) => Keyboard (ty) where { \ keyChar = (l) . keyChar; \ keySpecial = (l) . keySpecial }@@ -231,6 +248,22 @@ MK_PICTURE_2D(_COMMA_ Monad m, ListT m, lift, mapListT) MK_PICTURE_2D(_COMMA_ Monad m _COMMA_ Error e, ErrorT e m, lift, mapErrorT) MK_PICTURE_2D(_COMMA_ Monad m, ContT r m, lift, mapContT)++MK_FIGURE_2D(_COMMA_ Functor m, F m, liftF, hoistFR)+MK_FIGURE_2D( , UI m, LiftUI, over _LiftUI)+MK_FIGURE_2D(_COMMA_ Functor m, Free.Free m, Free.liftF, hoistFreeR)+MK_FIGURE_2D(_COMMA_ Monad m, ReaderT r m, lift, mapReaderT)+MK_FIGURE_2D(_COMMA_ Monad m, Lazy.StateT s m, lift, Lazy.mapStateT)+MK_FIGURE_2D(_COMMA_ Monad m, Strict.StateT s m, lift, Strict.mapStateT)+MK_FIGURE_2D(_COMMA_ Monad m _COMMA_ Monoid w, Lazy.WriterT w m, lift, Lazy.mapWriterT)+MK_FIGURE_2D(_COMMA_ Monad m _COMMA_ Monoid w, Strict.WriterT w m, lift, Strict.mapWriterT)+MK_FIGURE_2D(_COMMA_ Monad m _COMMA_ Monoid w, Lazy.RWST r w s m, lift, Lazy.mapRWST)+MK_FIGURE_2D(_COMMA_ Monad m _COMMA_ Monoid w, Strict.RWST r w s m, lift, Strict.mapRWST)+MK_FIGURE_2D(_COMMA_ Monad m, IdentityT m, lift, mapIdentityT)+MK_FIGURE_2D(_COMMA_ Monad m, MaybeT m, lift, mapMaybeT)+MK_FIGURE_2D(_COMMA_ Monad m, ListT m, lift, mapListT)+MK_FIGURE_2D(_COMMA_ Monad m _COMMA_ Error e, ErrorT e m, lift, mapErrorT)+MK_FIGURE_2D(_COMMA_ Monad m, ContT r m, lift, mapContT) MK_KEYBOARD(, Ap m, liftAp) MK_KEYBOARD(, UI m, LiftUI)
Graphics/UI/FreeGame/Data/Font.hs view
@@ -13,17 +13,14 @@ module Graphics.UI.FreeGame.Data.Font ( Font , loadFont - , Metrics(..) - , Graphics.UI.FreeGame.Data.Font.metrics , fontBoundingBox + , metricsAscent + , metricsDescent , charToBitmap , RenderedChar(..) - , text - , renderCharacters ) where import Control.Applicative -import Control.Monad import Control.Monad.IO.Class import Data.IORef import Data.Array.Repa as R @@ -31,7 +28,6 @@ import qualified Data.Map as M import Data.Word import Linear -import Graphics.UI.FreeGame.Base import Graphics.UI.FreeGame.Types import Graphics.UI.FreeGame.Data.Bitmap import Graphics.UI.FreeGame.Internal.Finalizer @@ -51,41 +47,41 @@ import Unsafe.Coerce -- | Font object -data Font = Font FT_Face Metrics (BoundingBox Float) (IORef (M.Map (Float, Char) RenderedChar)) +data Font = Font FT_Face (Float, Float) (BoundingBox Float) (IORef (M.Map (Float, Char) RenderedChar)) -- | Create a 'Font' from the given file. loadFont :: FilePath -> IO Font loadFont path = alloca $ \p -> do - e <- withCString path $ \str -> ft_New_Face freeType str 0 p - failFreeType e + runFreeType $ withCString path $ \str -> ft_New_Face freeType str 0 p f <- peek p b <- peek (bbox f) asc <- peek (ascender f) desc <- peek (descender f) u <- fromIntegral <$> peek (units_per_EM f) - let m = Metrics (fromIntegral asc/u) (fromIntegral desc/u) - box = BoundingBox (fromIntegral (xMin b)/u) (fromIntegral (yMin b)/u) + let box = BoundingBox (fromIntegral (xMin b)/u) (fromIntegral (yMin b)/u) (fromIntegral (xMax b)/u) (fromIntegral (yMax b)/u) - Font f m box <$> newIORef M.empty + Font f (fromIntegral asc/u, fromIntegral desc/u) box <$> newIORef M.empty -- | Get the font's metrics. -metrics :: Font -> Metrics -metrics (Font _ m _ _) = m +metricsAscent :: Font -> Float +metricsAscent (Font _ (a, _) _ _) = a +metricsDescent :: Font -> Float +metricsDescent (Font _ (_, d) _ _) = d + fontBoundingBox :: Font -> BoundingBox Float fontBoundingBox (Font _ _ b _) = b --- | Render a text by the specified 'Font'. -text :: (Monad p, Picture2D p, FromFinalizer p) => Font -> Float -> String -> p () -text font siz str = join $ fromFinalizer $ fmap sequence_ (renderCharacters font siz str) - -failFreeType :: Monad m => CInt -> m () -failFreeType 0 = return () -failFreeType e = fail $ "FreeType Error:" Prelude.++ show e +runFreeType :: IO CInt -> IO () +runFreeType m = do + r <- m + case r of + 0 -> return () + e -> fail $ "FreeType Error:" Prelude.++ show e freeType :: FT_Library freeType = unsafePerformIO $ alloca $ \p -> do - failFreeType =<< ft_Init_FreeType p + runFreeType $ ft_Init_FreeType p peek p data RenderedChar = RenderedChar @@ -94,11 +90,6 @@ , charAdvance :: Float } -data Metrics = Metrics - { metricsAscent :: Float - , metricsDescent :: Float - } - -- | The resolution used to render fonts. resolutionDPI :: Int resolutionDPI = 300 @@ -118,13 +109,13 @@ render = do let dpi = fromIntegral resolutionDPI - failFreeType =<< ft_Set_Char_Size face 0 (floor $ siz * 64) dpi dpi + runFreeType $ ft_Set_Char_Size face 0 (floor $ siz * 64) dpi dpi ix <- ft_Get_Char_Index face (fromIntegral $ fromEnum ch) - failFreeType =<< ft_Load_Glyph face ix ft_LOAD_DEFAULT + runFreeType $ ft_Load_Glyph face ix ft_LOAD_DEFAULT slot <- peek $ glyph face - failFreeType =<< ft_Render_Glyph slot ft_RENDER_MODE_NORMAL + runFreeType $ ft_Render_Glyph slot ft_RENDER_MODE_NORMAL bmp <- peek $ GS.bitmap slot left <- fmap fromIntegral $ peek $ GS.bitmap_left slot @@ -148,12 +139,3 @@ result <- computeP (fromFunction (Z:.h:.w:.4) pix) >>= makeStableBitmap return $ RenderedChar result (V2 left (-top)) (fromIntegral (V.x adv) / 64) - -renderCharacters :: (Monad p, Picture2D p) => Font -> Float -> String -> FinalizerT IO [p ()] -renderCharacters font pixel str = render str 0 where - render [] _ = return [] - render (c:cs) pen = do - RenderedChar b (V2 x y) adv <- charToBitmap font pixel c - let (w,h) = bitmapSize b - offset = V2 (pen + x + fromIntegral w / 2) (y + fromIntegral h / 2) - (translate offset (fromBitmap b):) <$> render cs (pen + adv)
+ Graphics/UI/FreeGame/Data/Text.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveFunctor #-} +module Graphics.UI.FreeGame.Data.Text (TextF(..), TextT, runTextT) where + +import Data.String +import Graphics.UI.FreeGame.Base +import Graphics.UI.FreeGame.Types +import Graphics.UI.FreeGame.Internal.Raindrop +import Graphics.UI.FreeGame.Data.Font +import Graphics.UI.FreeGame.Data.Bitmap +import Control.Monad.Trans.Free +import Control.Monad.State +import Linear + +data TextF a = TypeChar Char a deriving Functor + +type TextT = FreeT TextF + +instance Monad m => IsString (TextT m ()) where + fromString str = mapM_ (\c -> liftF (TypeChar c ())) str + +runTextT :: (FromFinalizer m, Monad m, Picture2D m) => Maybe (BoundingBox Float) -> Font -> Float -> TextT m a -> m a +runTextT bbox font size = flip evalStateT (V2 x0 y0) . go where + go m = lift (runFreeT m) >>= \r -> case r of + Pure a -> return a + Free (TypeChar '\n' cont) -> do + modify $ over _x (const x0) . over _y (+advV) + go cont + Free (TypeChar ch cont) -> do + RenderedChar bmp (V2 x y) adv <- fromFinalizer $ charToBitmap font size ch + pen <- get + let (w,h) = bitmapSize bmp + offset = pen ^+^ V2 (x + fromIntegral w / 2) (y + fromIntegral h / 2) + translate offset $ fromBitmap bmp + let pen' = over _x (+adv) pen + put $ if cond pen' + then pen' + else V2 x0 (view _y pen + advV) + go cont + advV = size * (metricsAscent font - metricsDescent font) * 1.1 + (V2 x0 y0, cond) = maybe (zero, const True) (\b -> (view _TopLeft b, flip inBoundingBox b)) bbox
Graphics/UI/FreeGame/GUI.hs view
@@ -35,10 +35,12 @@ data GUIBase a = Input (Ap GUIInput a) | Draw (Picture a) deriving Functor +-- | _Draw :: Traversal' (GUIBase a) (Picture a) _Draw :: Applicative f => (Picture a -> f (Picture a)) -> GUIBase a -> f (GUIBase a) _Draw f (Draw o) = fmap Draw (f o) _Draw _ x = pure x +-- | _Input :: Traversal' (GUIBase a) (Ap GUIInput a) _Input :: Applicative f => (Ap GUIInput a -> f (Ap GUIInput a)) -> GUIBase a -> f (GUIBase a) _Input f (Input o) = fmap Input (f o) _Input _ x = pure x @@ -50,6 +52,14 @@ translate = over _Draw . translate colored = over _Draw . colored +instance Figure2D GUIBase where + line = Draw . line + polygon = Draw . polygon + polygonOutline = Draw . polygonOutline + circle = Draw . circle + circleOutline = Draw . circleOutline + thickness = over _Draw . thickness + instance Keyboard GUIBase where keyChar = Input . keyChar keySpecial = Input . keySpecial @@ -81,6 +91,13 @@ | Scale (V2 Float) (Picture a) | Translate (V2 Float) (Picture a) | Colored Color (Picture a) + + | Line [V2 Float] a + | Polygon [V2 Float] a + | PolygonOutline [V2 Float] a + | Circle Float a + | CircleOutline Float a + | Thickness Float (Picture a) deriving Functor instance Picture2D Picture where @@ -89,6 +106,14 @@ scale = Scale translate = Translate colored = Colored + +instance Figure2D Picture where + line = flip Line () + polygon = flip Polygon () + polygonOutline = flip PolygonOutline () + circle = flip Circle () + circleOutline = flip CircleOutline () + thickness = Thickness instance FromFinalizer Picture where fromFinalizer = PictureWithFinalizer
Graphics/UI/FreeGame/GUI/GLFW.hs view
@@ -41,7 +41,7 @@ -> IORef Int -> GUI (FinalizerT IO (Maybe a)) -> FinalizerT IO (Maybe a) runAction param refTextures refFrame _f = case _f of - LiftUI (Draw pic) -> let ?refTextures = refTextures in join $ runPicture pic + LiftUI (Draw pic) -> let ?refTextures = refTextures in join $ runPicture 1 pic EmbedIO m -> join (liftIO m) Bracket m -> liftIO (runFinalizerT $ runF m (return.Just) (runAction param refTextures refFrame)) >>= maybe (return Nothing) id LiftUI (Input i) -> join $ liftIO $ runInput i @@ -98,7 +98,6 @@ GL.blend $= GL.Enabled GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha) GL.shadeModel $= GL.Smooth - GL.texture GL.Texture2D $= GL.Enabled GL.textureFunction $= GL.Combine let Color r g b a = _clearColor param in GL.clearColor $= GL.Color4 (gf r) (gf g) (gf b) (gf a) @@ -134,8 +133,8 @@ return $ cont $ V2 (fromIntegral x) (fromIntegral y) IMouseWheel cont -> cont <$> GLFW.getMouseWheel -runPicture :: (?refTextures :: IORef (IM.IntMap Texture)) => Picture a -> FinalizerT IO a -runPicture (LiftBitmap bmp@(BitmapData _ (Just h)) r) = do +runPicture :: (?refTextures :: IORef (IM.IntMap Texture)) => Float -> Picture a -> FinalizerT IO a +runPicture _ (LiftBitmap bmp@(BitmapData _ (Just h)) r) = do m <- liftIO $ readIORef ?refTextures case IM.lookup h m of Just t -> liftIO $ drawTexture t @@ -145,26 +144,52 @@ liftIO $ drawTexture t finalizer $ modifyIORef ?refTextures $ IM.delete h return r -runPicture (LiftBitmap bmp@(BitmapData _ Nothing) r) = do +runPicture _ (LiftBitmap bmp@(BitmapData _ Nothing) r) = do liftIO $ runFinalizerT $ installTexture bmp >>= liftIO . drawTexture return r -runPicture (Rotate theta cont) = preservingMatrix' $ do +runPicture sc (Rotate theta cont) = preservingMatrix' $ do liftIO $ GL.rotate (gf (-theta)) (GL.Vector3 0 0 1) - runPicture cont -runPicture (Scale (V2 sx sy) cont) = preservingMatrix' $ do + runPicture sc cont +runPicture sc (Scale (V2 sx sy) cont) = preservingMatrix' $ do liftIO $ GL.scale (gf sx) (gf sy) 1 - runPicture cont -runPicture (Translate (V2 tx ty) cont) = preservingMatrix' $ do + runPicture (sc * max sx sy) cont +runPicture sc (Translate (V2 tx ty) cont) = preservingMatrix' $ do liftIO $ GL.translate (GL.Vector3 (gf tx) (gf ty) 0) - runPicture cont -runPicture (PictureWithFinalizer m) = m -runPicture (Colored (Color r g b a) cont) = do + runPicture sc cont +runPicture _ (PictureWithFinalizer m) = m +runPicture sc (Colored (Color r g b a) cont) = do oldColor <- liftIO $ get GL.currentColor liftIO $ GL.currentColor $= GL.Color4 (gf r) (gf g) (gf b) (gf a) - res <- runPicture cont + res <- runPicture sc cont liftIO $ GL.currentColor $= oldColor return res +runPicture _ (Line path a) = do + liftIO $ GL.renderPrimitive GL.LineStrip $ runVertices path + return a +runPicture _ (Polygon path a) = do + liftIO $ GL.renderPrimitive GL.Polygon $ runVertices path + return a +runPicture _ (PolygonOutline path a) = do + liftIO $ GL.renderPrimitive GL.LineLoop $ runVertices path + return a +runPicture sc (Circle r a) = do + let s = 2 * pi / 64 * sc + liftIO $ GL.renderPrimitive GL.Polygon $ runVertices [V2 (cos t * r) (sin t * r) | t <- [0,s..2 * pi]] + return a +runPicture sc (CircleOutline r a) = do + let s = 2 * pi / 64 * sc + liftIO $ GL.renderPrimitive GL.LineLoop $ runVertices [V2 (cos t * r) (sin t * r) | t <- [0,s..2 * pi]] + return a +runPicture sc (Thickness t cont) = do + oldWidth <- liftIO $ get GL.lineWidth + liftIO $ GL.lineWidth $= gf t + res <- runPicture sc cont + liftIO $ GL.lineWidth $= oldWidth + return res +runVertices :: MonadIO m => [V2 Float] -> m () +runVertices = mapM_ (\(V2 x y) -> liftIO $ GL.vertex $ GL.Vertex2 (gf x) (gf y)) + preservingMatrix' :: MonadIO m => m a -> m a preservingMatrix' m = do liftIO $ glPushMatrix @@ -183,6 +208,7 @@ drawTexture :: Texture -> IO () drawTexture (Texture tex width height) = do let (w, h) = (fromIntegral width / 2, fromIntegral height / 2) + GL.texture GL.Texture2D $= GL.Enabled GL.textureFilter GL.Texture2D $= ((GL.Nearest, Nothing), GL.Nearest) GL.textureBinding GL.Texture2D $= Just tex GL.renderPrimitive GL.Polygon $ zipWithM_ @@ -191,6 +217,7 @@ GL.vertex $ GL.Vertex2 (gf pX) (gf pY)) [(-w, -h), (w, -h), (w, h), (-w, h)] [(0,0), (1.0,0), (1.0,1.0), (0,1.0)] + GL.texture GL.Texture2D $= GL.Disabled mapSpecialKey :: SpecialKey -> GLFW.Key mapSpecialKey KeySpace = GLFW.KeySpace
+ Graphics/UI/FreeGame/Internal/Raindrop.hs view
@@ -0,0 +1,11 @@+module Graphics.UI.FreeGame.Internal.Raindrop (view, over) where++import Data.Functor.Identity+import Control.Monad.Reader+import Control.Applicative++view :: MonadReader s m => ((a -> Const a b) -> (s -> Const a t)) -> m a+view f = ask >>= return . getConst . f Const++over :: ((a -> Identity b) -> (s -> Identity t)) -> (a -> b) -> s -> t+over l f = runIdentity . l (Identity . f)
Graphics/UI/FreeGame/Types.hs view
@@ -12,6 +12,7 @@ ---------------------------------------------------------------------------- module Graphics.UI.FreeGame.Types ( BoundingBox(..), + inBoundingBox, _TopLeft, _TopRight, _BottomLeft, @@ -22,6 +23,9 @@ -- | 2D bounding box. data BoundingBox a = BoundingBox a a a a deriving (Show, Eq, Ord, Functor, Read) + +inBoundingBox :: Ord a => V2 a -> BoundingBox a -> Bool +inBoundingBox (V2 x y) (BoundingBox x0 y0 x1 y1) = x0 <= x && x <= x1 && y0 <= y && y <= y1 -- | @'_TopLeft' :: Lens' ('BoundingBox' a) ('V2' a)@ _TopLeft :: Functor f => (V2 a -> f (V2 a)) -> (BoundingBox a -> f (BoundingBox a))
free-game.cabal view
@@ -1,5 +1,5 @@ name: free-game -version: 0.9 +version: 0.9.1 synopsis: Create graphical applications for free description: Cross-platform GUI library based on free monads homepage: https://github.com/fumieval/free-game @@ -26,9 +26,11 @@ Graphics.UI.FreeGame.Data.Bitmap Graphics.UI.FreeGame.Data.Color Graphics.UI.FreeGame.Data.Font + Graphics.UI.FreeGame.Data.Text Graphics.UI.FreeGame.GUI Graphics.UI.FreeGame.GUI.GLFW Graphics.UI.FreeGame.Internal.Finalizer + Graphics.UI.FreeGame.Internal.Raindrop Graphics.UI.FreeGame.Types Graphics.UI.FreeGame.Util