packages feed

Mondrian-0.1.0.0: lib/Graphics/Rendering/Rect/Types.hs

{-# LANGUAGE RecordWildCards #-}
-- So getters can implement typeclasses
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
module Graphics.Rendering.Rect.Types(Rect(..), size, Rects(..), BoxSelector,
        Uniform, u, us, c, cs, renderRectWith, liftGL) where

import Linear (M44, V2(..), V4(..))
import qualified Data.ByteString.Char8 as B8
import Data.ByteString (ByteString)
import qualified Data.Vector.Unboxed as UV

import Typograffiti.GL
import Graphics.GL.Core32
import Graphics.GL.Types
import Foreign.Marshal.Array (withArray, withArrayLen)
import Foreign.Ptr (castPtr)

import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad (forM)
import System.Exit (die)

import Data.Colour (AlphaColour, over, alphaChannel)
import Data.Colour.SRGB (RGB(..), toSRGB)
import Data.Colour.Names (black)
import Graphics.Rendering.Rect.Image (Texture(..))

data Rect = Rect {
    left :: Float, top :: Float,
    right :: Float, bottom :: Float
} deriving (Read, Show, Eq, Ord)
rect2geom :: Rect -> UV.Vector (V2 Float)
rect2geom Rect{..} = UV.fromList
    [V2 left top, V2 right top, V2 left bottom, V2 right bottom]
size :: Rect -> (Float, Float)
size Rect {..} = (right - left, bottom - top)

data Rects = Rects {
    contentBox :: Rect,
    paddingBox :: Rect,
    borderBox :: Rect,
    marginBox :: Rect
} deriving (Read, Show, Eq, Ord)
rect :: Float -> Rect
rect x = Rect x x x x

type BoxSelector = Rects -> Rect
instance Eq BoxSelector where
    a == b = a rects == b rects
      where rects = Rects (rect 0) (rect 1) (rect 2) (rect 3)
instance Show BoxSelector where
    show a | a rects == rect 0 = "contentBox"
        | a rects == rect 1 = "paddingBox"
        | a rects == rect 2 = "borderBox"
        | a rects == rect 3 = "marginBox"
        | otherwise = "?"
      where rects = Rects (rect 0) (rect 1) (rect 2) (rect 3)
instance Read BoxSelector where
    readsPrec _ ('c':'o':'n':'t':'e':'n':'t':'B':'o':'x':t) = [(contentBox, t)]
    readsPrec _ ('p':'a':'d':'d':'i':'n':'g':'B':'o':'x':t) = [(paddingBox, t)]
    readsPrec _ ('b':'o':'r':'d':'e':'r':'B':'o':'x':t) = [(borderBox, t)]
    readsPrec _ ('m':'a':'r':'g':'i':'n':'B':'o':'x':t) = [(marginBox, t)]
    readsPrec _ _ = []

vertexShader :: ByteString
vertexShader = B8.pack $ unlines [
    "#version 330 core",
    "uniform mat4 transform;",
    "uniform vec2 origin;",
    "in vec2 pos;",
    "out vec2 coord;",
    "void main() {",
    "   gl_Position = vec4(pos, 0, 1) * transform;",
    "   coord = pos - origin;",
    "}"
  ]

type Uniform m = GLuint -> GLint -> m ()
u :: (MonadIO m, UniformValue a) => a -> Uniform m
u val prog slot = liftIO $ updateUniform prog slot val
us :: MonadIO m => [Float] -> Uniform m
us vals prog slot = do
    liftIO $ withArrayLen vals $ \len -> glUniform1fv slot (toEnum len)
    clearUniformUpdateError prog slot vals

c :: MonadIO m => AlphaColour Float -> Uniform m
c rgba = u $ c' rgba
c' :: AlphaColour Float -> V4 Float
c' rgba = V4 r g b a
  where
    a = alphaChannel rgba
    -- Workaround for missing APIs in "colour" hackage.
    RGB r g b = toSRGB $ over rgba black
cs :: MonadIO m => Int -> [AlphaColour Float] -> Uniform m
cs mlen rgba prog slot = do
    let val = map c' $ take mlen rgba
    liftIO $ withArrayLen val $ \len -> glUniform4fv slot (toEnum len) . castPtr
    clearUniformUpdateError prog slot val

renderRectWith :: (MonadIO m, MonadIO n) => ByteString -> [String] ->
        n ([Texture] -> [Uniform m] -> (a -> Rect) -> (a -> Rect) -> a
            -> M44 Float -> m ())
renderRectWith fragmentShader uniformNames = do
    vs <- liftGL $ compileOGLShader vertexShader GL_VERTEX_SHADER
    fs <- liftGL $ compileOGLShader fragmentShader GL_FRAGMENT_SHADER
    prog <- liftGL $ compileOGLProgram [("pos", 0)] [vs, fs]
    uniformIDs <- forM uniformNames $ getUniformLocation prog
    matID <- getUniformLocation prog "transform"
    originID <- getUniformLocation prog "origin"
    szID <- getUniformLocation prog "boxSize"
    glUseProgram prog
    glEnable GL_BLEND
    glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
    return $ \textures uniforms clip' origin' rects mat -> do
        vao <- liftIO $ newBoundVAO
        pbuf <- newBuffer
        bufferGeometry 0 pbuf $ rect2geom $ clip' rects

        glUseProgram prog
        liftIO $ updateUniform prog matID $ mflip mat
        let r = origin' rects
        liftIO $ updateUniform prog originID $ V2 (left r) (top r)
        liftIO $ updateUniform prog szID $ V2 (right r - left r) (bottom r - top r)
        _ <- forM (zip uniformIDs uniforms) $ \(slot, cb) -> cb prog slot

        withBoundTextures (map unTexture textures) $ do
            glBindVertexArray vao
            drawVAO prog vao GL_TRIANGLE_STRIP 4
            glBindVertexArray 0

        liftIO $ withArray [pbuf] $ glDeleteBuffers 1
        liftIO $ withArray [vao] $ glDeleteVertexArrays 1

liftGL :: MonadIO m => IO (Either String a) -> m a
liftGL n = do
    ret <- liftIO n
    case ret of
        Left err -> liftIO $ die err
        Right x -> return x

mflip :: V4 (V4 a) -> V4 (V4 a)
mflip (V4 (V4 a b cc d) (V4 e f g h) (V4 i j k l) (V4 m n o p)) =
    V4 (V4 a e i m) (V4 b f j n) (V4 cc g k o) (V4 d h l p)