packages feed

aztecs-glfw-0.2.0: src/Aztecs/GLFW.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}

module Aztecs.GLFW
  ( Key (..),
    Keys (..),
    keyPressed,
    keyJustPressed,
    keyJustUnpressed,
    Cursor (..),
    Window (..),
    RawWindow (..),
    runAccessGLFW,
  )
where

import Aztecs
import Control.Monad
import Control.Monad.IO.Class
import Data.IORef
import Data.Set (Set)
import qualified Data.Set as Set
import Graphics.UI.GLFW (Key (..))
import qualified Graphics.UI.GLFW as GLFW
import Prelude hiding (lookup)

type KeySet = Set Key

data KeyStates = KeyStates
  { keysJustPressed :: KeySet,
    keysJustUnpressed :: KeySet
  }
  deriving (Show, Eq)

instance Monoid KeyStates where
  mempty = KeyStates Set.empty Set.empty

instance Semigroup KeyStates where
  (KeyStates p1 u1) <> (KeyStates p2 u2) = KeyStates (p1 <> p2) (u1 <> u2)

-- | Raw window component
data RawWindow = RawWindow
  { unRawWindow :: GLFW.Window,
    rawWindowKeyStates :: IORef KeyStates
  }

instance (Monad m) => Component m RawWindow

-- | Keys component
data Keys = Keys
  { keysPressed :: KeySet,
    keyStates :: KeyStates
  }
  deriving (Show, Eq)

instance Monoid Keys where
  mempty = Keys Set.empty mempty

instance Semigroup Keys where
  (Keys p1 ks1) <> (Keys p2 ks2) = Keys (p1 <> p2) (ks1 <> ks2)

instance (Monad m) => Component m Keys

keyPressed :: Key -> Keys -> Bool
keyPressed k ks = k `Set.member` keysPressed ks

keyJustPressed :: Key -> Keys -> Bool
keyJustPressed k ks = k `Set.member` (keysJustPressed . keyStates $ ks)

keyJustUnpressed :: Key -> Keys -> Bool
keyJustUnpressed k ks = k `Set.member` (keysJustUnpressed . keyStates $ ks)

-- | Cursor component
data Cursor = Cursor
  { cursorX :: Double,
    cursorY :: Double
  }
  deriving (Show, Eq)

instance (Monad m) => Component m Cursor

-- | Window component
data Window = Window
  { windowTitle :: String,
    windowWidth :: Int,
    windowHeight :: Int
  }
  deriving (Show, Eq)

instance (MonadIO m) => Component m Window where
  componentOnInsert e w = do
    insert e $ bundle (mempty @Keys) <> bundle (Cursor 0 0)
    res <- liftIO $ do
      _ <- GLFW.init
      res <- GLFW.createWindow (windowWidth w) (windowHeight w) (windowTitle w) Nothing Nothing
      case res of
        Nothing -> return Nothing
        Just raw -> do
          keyStatesRef <- newIORef $ KeyStates Set.empty Set.empty
          GLFW.setKeyCallback raw $ Just (go keyStatesRef)
          return . Just $ RawWindow raw keyStatesRef
    case res of
      Just raw -> insert e . bundle $ raw
      Nothing -> error "TODO"
    where
      go keyStatesRef _win key _scancode action _mods = do
        when (action == GLFW.KeyState'Pressed) $ do
          modifyIORef keyStatesRef (\ks -> ks {keysJustPressed = Set.insert key (keysJustPressed ks)})
        when (action == GLFW.KeyState'Released) $ do
          modifyIORef keyStatesRef (\ks -> ks {keysJustUnpressed = Set.insert key (keysJustUnpressed ks)})
  componentOnChange e w w' = do
    when (windowTitle w /= windowTitle w') $ do
      mws <- lookup e
      case mws of
        Just (RawWindow raw _) -> liftIO $ GLFW.setWindowTitle raw (windowTitle w')
        Nothing -> return ()
    when ((windowWidth w, windowHeight w) /= (windowWidth w', windowHeight w')) $ do
      mws <- lookup e
      case mws of
        Just (RawWindow raw _) -> liftIO $ GLFW.setWindowSize raw (windowWidth w') (windowHeight w')
        Nothing -> return ()
  componentOnRemove e _ = do
    mws <- lookup e
    case mws of
      Just (RawWindow raw _) -> liftIO $ GLFW.destroyWindow raw
      Nothing -> return ()

runAccessGLFW :: (MonadIO m) => Access m Bool -> Access m ()
runAccessGLFW a = do
  liftIO $ GLFW.pollEvents
  shouldClose <- a
  if shouldClose
    then return ()
    else do
      res <- system . runQuery . queryUntracked . queryMapWithAccumM go' $ queryMapWithAccumM go query
      unless (or $ fmap fst res) $ runAccessGLFW a
  where
    go (RawWindow rw keyStatesRef) cursor = liftIO $ do
      (x, y) <- GLFW.getCursorPos rw
      let cursor' = cursor {cursorX = x, cursorY = y}
      return (RawWindow rw keyStatesRef, cursor')
    go' (RawWindow rw keyStatesRef, _) keys = liftIO $ do
      ks <- readIORef keyStatesRef
      writeIORef keyStatesRef mempty

      let newPressed = (keysPressed keys `Set.union` keysJustPressed ks) `Set.difference` keysJustUnpressed ks
      let keys' =
            Keys
              { keysPressed = newPressed,
                keyStates = ks
              }

      shouldClose <- GLFW.windowShouldClose rw
      GLFW.swapBuffers rw
      return (shouldClose, keys')