packages feed

pine-0.1.0.0: src/Pine/Internal/Renderer.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Pine.Internal.Renderer
  ( defaultApp
  , pine
  ) where

import Pine.Internal.Types

import qualified SDL
import qualified SDL.Image as SDLI
import Control.Concurrent.STM

import Data.Text (Text)
import Data.Map (Map)
import qualified Data.Map as M
import Control.Monad
import Data.Word (Word32)

type TextureCache = Map FilePath SDL.Texture

pine :: (Stateful s, Drawable s)
     => Text
     -> SDL.WindowConfig
     -> s
     -> IO ()
pine title windowConfig state_ = do
  SDL.initializeAll
  window <- SDL.createWindow title windowConfig
  renderer <- SDL.createRenderer window (-1) $ SDL.RendererConfig
    { SDL.rendererType = SDL.AcceleratedRenderer
    , SDL.rendererTargetTexture = False
    }
  let
    appLoop :: TextureCache -> IO ()
    appLoop cache = do
      updateQueue <- newTChanIO
      timer <- SDL.addTimer 16 (fpsTimer updateQueue)
      time <- SDL.ticks
      SDL.pollEvent >>= go time updateQueue cache state_
      _ <- SDL.removeTimer timer
      pure ()

    go :: (Stateful s, Drawable s) => Word32 -> TChan () -> TextureCache -> s -> Maybe SDL.Event -> IO ()
    go time updateQueue cache state mevent = do
      time' <- SDL.ticks
      let dt = fromIntegral (time' - time) :: Double
      atomically $ readTChan updateQueue -- not ideal, events could get backed up
      cache' <- drawCanvas cache $ draw state
      case mevent of
        Nothing -> SDL.pollEvent >>= go time' updateQueue cache' (update (DeltaTime dt) state)
        Just ev -> case SDL.eventPayload ev of
          SDL.WindowClosedEvent _ -> pure ()
          _ -> SDL.pollEvent >>= go time' updateQueue cache' (update (SDLEvent ev) state)

    fpsTimer :: TChan () -> Word32 -> IO SDL.RetriggerTimer
    fpsTimer updateQueue _ = do
      atomically $ writeTChan updateQueue ()
      pure $ SDL.Reschedule 16

    drawCanvas :: TextureCache -> Canvas -> IO TextureCache
    drawCanvas cache canvas = do
      SDL.clear renderer
      cache' <- drawCanvas' cache canvas
      SDL.present renderer
      pure cache'

    drawCanvas' :: TextureCache -> Canvas -> IO TextureCache
    drawCanvas' cache canvas = do
      case canvas of
        SingleImage img -> drawImages cache [img]
        Images imgs -> drawImages cache imgs
        EmptyCanvas -> pure cache

    drawImages :: TextureCache -> [Image] -> IO TextureCache
    drawImages cache [] = pure cache
    drawImages cache (img:imgs) =
      case cache M.!? (imageSrc img) of
        Nothing -> do
          tex <- SDLI.loadTexture renderer (imageSrc img)
          SDL.copy renderer tex Nothing Nothing
          drawImages (M.insert (imageSrc img) tex cache) imgs
        Just tex -> do
          SDL.copy renderer tex Nothing Nothing
          drawImages cache imgs

   in appLoop mempty

data DefaultState = Logo Image

instance Stateful DefaultState where
  initial = Logo $ newImage "src/Media/logo.png"
  update = const id

instance Drawable DefaultState where
  draw (Logo img) = fromImage img

defaultApp :: IO ()
defaultApp = pine "Pine" defaultConfig (initial :: DefaultState)
  where
    defaultConfig = SDL.WindowConfig
      { SDL.windowBorder        = True
      , SDL.windowHighDPI       = False
      , SDL.windowInputGrabbed  = False
      , SDL.windowMode          = SDL.Windowed
      , SDL.windowOpenGL        = Nothing
      , SDL.windowPosition      = SDL.Wherever
      , SDL.windowResizable     = True
      , SDL.windowInitialSize   = SDL.V2 800 800
      , SDL.windowVisible       = True
      }

{-
  addEventWatch $ \ev ->
    case eventPayload ev of
      WindowSizeChangedEvent sizeChangeData ->
        putStrLn $ "eventWatch windowSizeChanged: " ++ show sizeChangeData
      KeyboardEvent kev ->
        putStrLn "key event"
      _ -> return ()
  appLoop mempty
    where
      appLoop :: TextureCache -> IO ()
      appLoop tc = pollEvent >>= go tc

      go :: TextureCache -> Maybe Event -> IO ()
      go tc = \case
        Nothing -> pollEvent >>= go tc
        Just ev -> case eventPayload ev of
          KeyboardEvent keyboardEvent
            |  keyboardEventKeyMotion keyboardEvent == Pressed &&
               keysymKeycode (keyboardEventKeysym keyboardEvent) == KeycodeQ
            -> return ()
          _ -> pollEvent >>= go tc



-}