packages feed

glapp-0.1.0.0: src/Data/Glapp.hs

module Data.Glapp (
    module T,
    iterateApp
) where

import           Graphics.UI.GLFW
import           Graphics.Rendering.OpenGL
import           Control.Monad
import           Control.Monad.State
import           Control.Concurrent
import           Control.Lens
import           Data.Maybe
import           Data.Glapp.Types as T
import           Data.Glapp.Internal
import qualified Data.Set as S



input :: MVar (UserWindow a) -> InputEvent -> IO ()
input mvar e = do
    uApp <- takeMVar mvar
    let datum = (uApp ^. windowInput) (uApp ^. windowData) e
    putMVar mvar $ uApp & windowData .~ datum


render :: MVar (UserWindow a) -> Window -> IO ()
render mvar win = do
    makeContextCurrent $ Just win
    uWin <- readMVar mvar
    clear [ColorBuffer, DepthBuffer]
    uWin ^. windowRender $ uWin ^. windowData
    swapBuffers win
    makeContextCurrent Nothing


iterateUntilM :: Monad m => (a -> Bool) -> (a -> m a) -> a -> m a
iterateUntilM p f v
    | p v = return v
    | otherwise = f v >>= iterateUntilM p f


getUserAppShouldQuit :: App a b -> Bool
getUserAppShouldQuit app = shouldQuit getUserApp
    where shouldQuit = app ^. userApp . userAppShouldQuit
          getUserApp = app ^. userApp . userAppData


getUserWindowIds :: App a b -> IO [UserWindowId b]
getUserWindowIds app = do
    uWins <- forM (map _appUserWindowMVar $ S.toList $ app ^. windows) readMVar
    let ids = S.map _appWindowId (app ^. windows)
    return $ zip (S.toList ids) uWins


stepWindows :: App a b -> IO (App a b)
stepWindows app = do
    forM_ (S.toList $ app ^. windows) $ \(AppWindow i win mvar) -> do
        uWin  <- takeMVar mvar
        udata <- uWin ^. windowStep $ uWin ^. windowData
        putMVar mvar $ uWin & windowData .~ udata
    return app


makeAppWindow :: Id -> NewWindow a -> IO (Maybe (AppWindow a))
makeAppWindow id' (UserWindowConfig s p t, uWin) = do
    mWin <- uncurry createWindow s t Nothing Nothing
    case mWin of
       Nothing  -> return Nothing
       Just win -> do
            (uncurry $ setWindowPos win) p

            mvar <- newMVar uWin

            setCharCallback win $ Just $ \_ c ->
                input mvar $ CharEvent c

            setWindowSizeCallback win $ Just $ \win' w h -> do
                input mvar $ WindowSizeEvent w h
                render mvar win'

            setKeyCallback win $ Just $ \win' k i ks mod ->
                input mvar $ KeyEvent k i ks mod

            setMouseButtonCallback win $ Just $ \win' mb mbs mod ->
                input mvar $ MouseButtonEvent mb mbs mod

            setCursorPosCallback win $ Just $ \win' x y ->
                input mvar $ CursorMoveEvent x y

            setCursorEnterCallback win $ Just $ \win' cs ->
                input mvar $ CursorEnterEvent cs

            setScrollCallback win $ Just $ \win' x y ->
                input mvar $ ScrollEvent x y

            return $ Just $ AppWindow id' win mvar


createWindows :: Show a => App a b -> IO (App a b)
createWindows app = do
    -- Get our user windows.
    uWinIds <- getUserWindowIds app

    -- Get the config for our windows.
    let uWins      = map snd uWinIds
        news       = app ^. userApp.userAppNewWindows $ app ^. userApp.userAppData
        ids        = take (length news) $ iterate succ $ app ^. nextWindowId
        zipped     = zip ids news

    -- Create the windows.
    appWins <- fmap catMaybes $ forM zipped (uncurry makeAppWindow)

    return $ flip execState app $
        unless (null appWins) $ do
            nextWindowId .= succ (last ids)
            windows %= S.union (S.fromList appWins)


destroyWindows :: App a b -> IO (App a b)
destroyWindows app = do
    -- Run through all the windows and find which ones should
    -- close.
    closedAppWins <- fmap catMaybes $ forM (S.toList $ app ^. windows) $
        \a@(AppWindow _ win _) -> do
            shouldClose <- windowShouldClose win
            if shouldClose then do
                -- Close them and return them.
                destroyWindow win
                return $ Just a
              else return Nothing

    -- Remove them from our list.
    return $ app & windows %~ (`S.difference` S.fromList closedAppWins)


renderWindows :: App a b -> IO (App a b)
renderWindows app = do
    -- Run through all the windows and step/render them.
    forM_ (S.toList $ app ^. windows) $ \(AppWindow _ win mvar) ->
        render mvar win
    return app


stepUserApp :: App a b -> IO (App a b)
stepUserApp app = do
    let uApp  = app ^. userApp
    uWins <- fmap (map snd) $ getUserWindowIds app
    uad <- (uApp ^. userAppStep) (uApp ^. userAppData) uWins
    return $ app & userApp.userAppData .~ uad


stepApp :: Show a => App a b -> IO (App a b)
stepApp a = do
    pollEvents
    createWindows a >>= stepWindows >>= stepUserApp >>= destroyWindows >>= renderWindows


iterateApp :: Show a => UserApp a b -> IO ()
iterateApp u = do
    True <- Graphics.UI.GLFW.init
    let app = App { _userApp = u
                  , _windows = S.empty
                  , _nextWindowId = Id 0
                  }

    _ <- stepApp app >>= iterateUntilM getUserAppShouldQuit stepApp
    return ()