fwgl-glfw-0.1.1.1: FWGL/Backend/GLFW/Common.hs
module FWGL.Backend.GLFW.Common (
loadTextFile,
loadImage,
initBackend,
createCanvas,
setCanvasSize,
setCanvasTitle,
setCanvasResizeCallback,
setCanvasRefreshCallback,
popInput,
getInput,
drawCanvas,
safeFork,
refreshLoop,
FWGL.Backend.GLFW.Common.getTime,
terminateBackend,
Canvas,
BackendState,
ClientAPI(..)
) where
import Codec.Picture
import Codec.Picture.Types (promoteImage)
import Control.Concurrent
import Control.Monad
import Control.Exception.Base (catch)
import qualified Data.HashMap.Strict as H
import Data.Hashable
import Data.IORef
import Data.Vector.Storable (unsafeWith)
import Foreign.Ptr
import FWGL.Backend.GLES hiding (Image)
import FWGL.Input as Input
import Graphics.UI.GLFW as GLFW
loadImage :: (Num sz, Num sz') => String
-> (((sz', sz', Ptr ()), sz, sz) -> IO a) -> IO a
loadImage path c = do eimg <- readImage path
case eimg of
Left err -> error err
Right img ->
case convert img of
Image w h v -> unsafeWith v $
\p -> c ( ( fromIntegral w
, fromIntegral h
, castPtr p)
, fromIntegral w
, fromIntegral h)
where convert :: DynamicImage -> Image PixelRGBA8
convert (ImageRGBA8 img) = img
convert (ImageRGB8 img) = promoteImage img
convert (ImageYA8 img) = promoteImage img
convert (ImageY8 img) = promoteImage img
convert _ = error "Unsupported image format."
loadTextFile :: String -> (Either String String -> IO ()) -> IO ()
loadTextFile fname handler = (>> return ()) . forkIO $
catch (fmap (\s -> s `seq` Right s) $ readFile fname)
(\e -> return (Left $ show (e :: IOError))) >>= handler
data BackendState = BackendState {
eventThread :: ThreadId
}
data Canvas = Canvas GLFW.Window
(IORef (H.HashMap InputEvent [EventData]))
(IORef (Int -> Int -> IO ()))
(IORef (IO ()))
(MVar ())
initBackend :: IO BackendState
initBackend = do GLFW.init
setTime 0
evTid <- forkIO . forever $ waitEvents >> threadDelay 10000
return $ BackendState evTid
createCanvas :: ClientAPI -> Int -> Int
-> String -> Int -> Int -> BackendState -> IO (Canvas, Int, Int)
createCanvas clientAPI maj min title w h _ =
do windowHint $ WindowHint'ClientAPI clientAPI
windowHint $ WindowHint'ContextVersionMajor maj
windowHint $ WindowHint'ContextVersionMinor min
-- TODO: context sharing
Just win <- createWindow w h title Nothing Nothing
bufferSem <- newMVar ()
resizeRef <- newIORef $ \_ _ -> return ()
refreshRef <- newIORef $ return ()
eventsRef <- newIORef $ H.singleton Resize [
emptyEventData {
dataFramebufferSize = Just (w, h)
}]
setKeyCallback win . Just . const $ keyCallback eventsRef
setMouseButtonCallback win . Just $ mouseCallback eventsRef
setCursorPosCallback win . Just . const $ cursorCallback eventsRef
-- XXX: windows that are not using refreshLoop should receive this
{-
setWindowRefreshCallback win . Just . const $
refreshCallback bufferSem refreshRef
-}
setFramebufferSizeCallback win . Just . const $
resizeCallback eventsRef resizeRef
return (Canvas win eventsRef resizeRef refreshRef bufferSem, w, h)
where keyCallback events key _ keyState _ =
do Just t <- GLFW.getTime
modifyIORef' events $
case keyState of
KeyState'Pressed -> insertEvent t KeyDown keyData
KeyState'Released -> insertEvent t KeyUp keyData
_ -> id
where keyData = emptyEventData {
dataKey = Just $ toKey key
}
mouseCallback events win mb mbState _ = do
Just t <- GLFW.getTime
pos <- fmap convertCursorPos $ getCursorPos win
modifyIORef' events $
case mbState of
MouseButtonState'Pressed ->
insertEvent t MouseDown $ keyData pos
MouseButtonState'Released ->
insertEvent t MouseUp $ keyData pos
where keyData p = emptyEventData {
dataButton = Just $ toMouseButton mb,
dataPointer = Just p
}
cursorCallback events x y = GLFW.getTime >>= \(Just t) ->
modifyIORef' events $
insertEvent t MouseMove $ emptyEventData {
dataPointer = Just $ convertCursorPos (x, y)
}
resizeCallback events resizeRef x y =
do Just t <- GLFW.getTime
callback <- readIORef resizeRef
modifyIORef' events $
insertEvent t Resize $ emptyEventData {
dataFramebufferSize = Just $ (x, y)
}
callback x y
refreshCallback bufferSem refreshRef =
do empty <- isEmptyMVar bufferSem
unless empty $
join $ readIORef refreshRef
convertCursorPos (x, y) = (floor x, floor y)
insertEvent t e = H.insertWith (++) e . (: []) . setTime t
where setTime t ed = ed { dataTime = t }
emptyEventData = EventData {
dataFramebufferSize = Nothing,
dataPointer = Nothing,
dataButton = Nothing,
dataKey = Nothing,
dataTime = 0 }
setCanvasSize :: Int -> Int -> Canvas -> BackendState -> IO ()
setCanvasSize w h (Canvas win _ _ _ _) _ = setWindowSize win w h
setCanvasTitle :: String -> Canvas -> BackendState -> IO ()
setCanvasTitle str (Canvas win _ _ _ _) _ = setWindowTitle win str
setCanvasResizeCallback :: (Int -> Int -> IO ()) -> Canvas
-> BackendState -> IO ()
setCanvasResizeCallback callback (Canvas _ _ ref _ _) _ =
writeIORef ref callback
setCanvasRefreshCallback :: IO () -> Canvas -> BackendState -> IO ()
setCanvasRefreshCallback callback (Canvas _ _ _ ref _) _ =
writeIORef ref callback
popInput :: a -> Canvas -> BackendState -> IO (Input a)
popInput c canvas@(Canvas _ events _ _ _) bs = do i <- getInput c canvas bs
writeIORef events H.empty
return i
getInput :: a -> Canvas -> BackendState -> IO (Input a)
getInput c (Canvas _ events _ _ _) _ = flip Input c <$> readIORef events
draw :: IO a -> Bool -> MVar () -> Window -> IO a
draw act shouldSwap bufferSem win =
do () <- takeMVar bufferSem
makeContextCurrent $ Just win
r <- act
when shouldSwap $
swapBuffers win
makeContextCurrent Nothing
putMVar bufferSem ()
return r
drawCanvas :: (MVar () -> IO a) -> Bool -> Canvas -> BackendState -> IO a
drawCanvas act swap (Canvas win _ _ _ sem) _ = draw (act sem) swap sem win
safeFork :: MVar () -> (IO () -> IO ThreadId) -> IO () -> IO ThreadId
safeFork sem fork thread = do mctx <- getCurrentContext
fork $ case mctx of
Just ctx -> draw thread False sem ctx
Nothing -> thread
refreshLoop :: Int -> Canvas -> BackendState -> IO ()
refreshLoop fps c@(Canvas win _ _ refreshCallback _) bs =
do Just t1 <- GLFW.getTime
closed <- windowShouldClose win
join $ readIORef refreshCallback
pollEvents
Just t2 <- GLFW.getTime
let passed = (t2 - t1) * 1000000
if closed
then destroyWindow win
else do when (passed > 0) $
threadDelay . ceiling $ delay - passed
refreshLoop fps c bs
where delay = 1000000 / fromIntegral fps
getTime :: BackendState -> IO Double
getTime _ = do Just t <- GLFW.getTime
return t
terminateBackend :: BackendState -> IO ()
terminateBackend (BackendState tid) = killThread tid >> terminate
toMouseButton :: GLFW.MouseButton -> Input.MouseButton
toMouseButton MouseButton'1 = MouseLeft
toMouseButton MouseButton'3 = MouseMiddle
toMouseButton MouseButton'2 = MouseRight
toKey :: GLFW.Key -> Input.Key
toKey Key'A = KeyA
toKey Key'B = KeyB
toKey Key'C = KeyC
toKey Key'D = KeyD
toKey Key'E = KeyE
toKey Key'F = KeyF
toKey Key'G = KeyG
toKey Key'H = KeyH
toKey Key'I = KeyI
toKey Key'J = KeyJ
toKey Key'K = KeyK
toKey Key'L = KeyL
toKey Key'M = KeyM
toKey Key'N = KeyN
toKey Key'O = KeyO
toKey Key'P = KeyP
toKey Key'Q = KeyQ
toKey Key'R = KeyR
toKey Key'S = KeyS
toKey Key'T = KeyT
toKey Key'U = KeyU
toKey Key'V = KeyV
toKey Key'W = KeyW
toKey Key'X = KeyX
toKey Key'Y = KeyY
toKey Key'Z = KeyZ
toKey Key'0 = Key0
toKey Key'1 = Key1
toKey Key'2 = Key2
toKey Key'3 = Key3
toKey Key'4 = Key4
toKey Key'5 = Key5
toKey Key'6 = Key6
toKey Key'7 = Key7
toKey Key'8 = Key8
toKey Key'9 = Key9
toKey Key'Space = KeySpace
toKey Key'Enter = KeyEnter
toKey Key'Tab = KeyTab
toKey Key'Escape = KeyEsc
toKey Key'Backspace = KeyBackspace
toKey Key'LeftShift = KeyShift -- TODO: different shifts
toKey Key'RightShift = KeyShift
toKey Key'LeftControl = KeyControl -- TODO: //
toKey Key'RightControl = KeyControl
toKey Key'LeftAlt = KeyAlt -- TODO: //
toKey Key'RightAlt = KeyAlt
toKey Key'CapsLock = KeyCapsLock
toKey Key'NumLock = KeyNumLock
toKey Key'Left = KeyArrowLeft
toKey Key'Up = KeyArrowUp
toKey Key'Right = KeyArrowRight
toKey Key'Down = KeyArrowDown
toKey Key'Insert = KeyIns
toKey Key'Delete = KeyDel
toKey Key'Home = KeyHome
toKey Key'End = KeyEnd
toKey Key'PageUp = KeyPgUp
toKey Key'PageDown = KeyPgDown
toKey Key'F1 = KeyF1
toKey Key'F2 = KeyF2
toKey Key'F3 = KeyF3
toKey Key'F4 = KeyF4
toKey Key'F5 = KeyF5
toKey Key'F6 = KeyF6
toKey Key'F7 = KeyF7
toKey Key'F8 = KeyF8
toKey Key'F9 = KeyF9
toKey Key'F10 = KeyF10
toKey Key'F11 = KeyF11
toKey Key'F12 = KeyF12
toKey Key'PadAdd = KeyPadAdd
toKey Key'PadSubtract = KeyPadSub
toKey Key'PadMultiply = KeyPadMul
toKey Key'PadDivide = KeyPadDiv
toKey Key'PadEnter = KeyPadEnter
toKey Key'PadDecimal = KeyPadDot
toKey Key'Pad0 = KeyPad0
toKey Key'Pad1 = KeyPad1
toKey Key'Pad2 = KeyPad2
toKey Key'Pad3 = KeyPad3
toKey Key'Pad4 = KeyPad4
toKey Key'Pad5 = KeyPad5
toKey Key'Pad6 = KeyPad6
toKey Key'Pad7 = KeyPad7
toKey Key'Pad8 = KeyPad8
toKey Key'Pad9 = KeyPad9
toKey _ = KeyUnknown