packages feed

gore-and-ash-glfw (empty) → 1.1.0.0

raw patch · 7 files changed

+828/−0 lines, 7 filesdep +GLFW-bdep +basedep +deepseqsetup-changed

Dependencies added: GLFW-b, base, deepseq, exceptions, extra, gore-and-ash, hashable, mtl, transformers, unordered-containers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Gushcha Anton (c) 2015-2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of  nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ gore-and-ash-glfw.cabal view
@@ -0,0 +1,43 @@+name:                gore-and-ash-glfw+version:             1.1.0.0+synopsis:            Core module for Gore&Ash engine for GLFW input events+description:         Please see README.md+homepage:            https://github.com/Teaspot-Studio/gore-and-ash-glfw+license:             BSD3+license-file:        LICENSE+author:              Anton Gushcha+maintainer:          ncrashed@gmail.com+copyright:           2015-2016 Anton Gushcha+category:            Game+build-type:          Simple+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Game.GoreAndAsh.GLFW+                       Game.GoreAndAsh.GLFW.API+                       Game.GoreAndAsh.GLFW.Module+                       Game.GoreAndAsh.GLFW.State++  default-language:    Haskell2010+  build-depends:       base >= 4.7 && < 5+                     , deepseq >= 1.4+                     , exceptions >= 0.8.0.2+                     , extra >= 1.4.2+                     , GLFW-b >= 1.4.7+                     , gore-and-ash >= 1.1.0.1+                     , hashable >= 1.2.3+                     , mtl >= 2.2+                     , transformers >= 0.4.2+                     , unordered-containers >= 0.2.5+                     +  default-extensions:  +                       BangPatterns+                       DeriveGeneric+                       FlexibleInstances+                       GeneralizedNewtypeDeriving+                       MultiParamTypeClasses+                       RecordWildCards+                       TypeFamilies+                       UndecidableInstances+                       ScopedTypeVariables
+ src/Game/GoreAndAsh/GLFW.hs view
@@ -0,0 +1,99 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+{-|+Module      : Game.GoreAndAsh.GLFW+Description : Module that contains GLFW integration for Gore&Ash+Copyright   : (c) Anton Gushcha, 2015-2016+License     : BSD3+Maintainer  : ncrashed@gmail.com+Stability   : experimental+Portability : POSIX++The core module contains API for GLFW library integration. +The module doesn't depends on others core modules and could be place in any place in +game monad stack.++The module is NOT pure within first phase (see 'ModuleStack' docs), therefore currently only 'IO' end monad can handler the module.++Example of embedding:++@+-- | Application monad is monad stack build from given list of modules over base monad (IO)+type AppStack = ModuleStack [GLFWT, ... other modules ... ] IO+newtype AppState = AppState (ModuleState AppStack)+  deriving (Generic)++instance NFData AppState ++-- | Wrapper around type family+newtype AppMonad a = AppMonad (AppStack a)+  deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadThrow, MonadCatch, MonadGLFW, ... other modules monads ... )+  +instance GameModule AppMonad AppState where +  type ModuleState AppMonad = AppState+  runModule (AppMonad m) (AppState s) = do +    (a, s') <- runModule m s +    return (a, AppState s')+  newModuleState = AppState <$> newModuleState+  withModule _ = withModule (Proxy :: Proxy AppStack)+  cleanupModule (AppState s) = cleanupModule s ++-- | Arrow that is build over the monad stack+type AppWire a b = GameWire AppMonad a b+-- | Action that makes indexed app wire+type AppActor i a b = GameActor AppMonad i a b+@++-}+module Game.GoreAndAsh.GLFW(+  -- * Low-level API +    GLFWState+  , GLFWT+  , MonadGLFW(..)+  -- * Arrow API+  -- ** Keyboard API+  , keyStatus+  , keyStatusDyn+  , keyPressed+  , keyPressedDyn+  , keyReleased+  , keyReleasedDyn+  , keyRepeating+  , keyRepeatingDyn+  , keyPressing+  , keyPressingDyn+  -- ** Mouse buttons API+  , mouseButton+  , mouseButtonDyn+  , mouseButtonPressed+  , mouseButtonPressedDyn+  , mouseButtonReleased+  , mouseButtonReleasedDyn+  -- ** Cursor position+  , mousePosition+  , mousePositionChange+  , mouseXChange+  , mouseYChange+  , mouseDelta+  , mouseDeltaChange+  , mouseDeltaXChange+  , mouseDeltaYChange+  -- ** Mouse scroll+  , mouseScroll+  , mouseScrollX+  , mouseScrollY+  -- ** Window API+  , windowSize+  , windowClosing+  -- ** Reexports+  , Key(..)+  , KeyState(..)+  , MouseButton(..)+  , MouseButtonState(..)+  , ModifierKeys(..)+  ) where++import Graphics.UI.GLFW++import Game.GoreAndAsh.GLFW.API +import Game.GoreAndAsh.GLFW.Module+import Game.GoreAndAsh.GLFW.State 
+ src/Game/GoreAndAsh/GLFW/API.hs view
@@ -0,0 +1,350 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Module      : Game.GoreAndAsh.GLFW.API+Description : Monadic and arrow API for GLFW core module+Copyright   : (c) Anton Gushcha, 2015-2016+License     : BSD3+Maintainer  : ncrashed@gmail.com+Stability   : experimental+Portability : POSIX++The module contains monadic and arrow API of the core module.+-}+module Game.GoreAndAsh.GLFW.API(+    MonadGLFW(..)+  -- * Keyboard API+  , keyStatus+  , keyStatusDyn+  , keyPressed+  , keyPressedDyn+  , keyReleased+  , keyReleasedDyn+  , keyRepeating+  , keyRepeatingDyn+  , keyPressing+  , keyPressingDyn+  -- * Mouse buttons API+  , mouseButton+  , mouseButtonDyn+  , mouseButtonPressed+  , mouseButtonPressedDyn+  , mouseButtonReleased+  , mouseButtonReleasedDyn+  -- * Cursor position+  , mousePosition+  , mousePositionChange+  , mouseXChange+  , mouseYChange+  , mouseDelta+  , mouseDeltaChange+  , mouseDeltaXChange+  , mouseDeltaYChange+  -- * Mouse scroll+  , mouseScroll+  , mouseScrollX+  , mouseScrollY+  -- * Window API+  , windowSize+  , windowClosing+  -- * Reexports+  , Key(..)+  , KeyState(..)+  , MouseButton(..)+  , MouseButtonState(..)+  , ModifierKeys(..)+  ) where++import Prelude hiding (id, (.))+import Control.Wire ++import Control.Monad.State.Strict +import Control.Wire.Unsafe.Event+import Graphics.UI.GLFW+import qualified Data.HashMap.Strict as M ++import Game.GoreAndAsh+import Game.GoreAndAsh.GLFW.State+import Game.GoreAndAsh.GLFW.Module ++-- | Module low-level API+class Monad m => MonadGLFW m where +  -- | Returns state of given keyboard's key+  keyStatusM :: Key -> m (Maybe (KeyState, ModifierKeys))+  -- | Returns state of given mouse button+  mouseButtonM :: MouseButton -> m (Maybe (MouseButtonState, ModifierKeys))+  -- | Returns current position of mouse cursor+  mousePosM :: m (Double, Double)+  -- | Returns current scroll values of mouse+  mouseScrollM :: m [(Double, Double)]+  -- | Returns current size of window+  windowSizeM :: m (Maybe (Double, Double))+  -- | Returns True when close button is pushed+  windowClosingM :: m Bool +  -- | Setups current window for input catch+  setCurrentWindowM :: Maybe Window -> m ()+  -- | Returns current window +  getCurrentWindowM :: m (Maybe Window)+  -- | Setup maximum size of inner buffers for keys, mouse buttons+  setBufferSizeM :: Int -> m ()++instance {-# OVERLAPPING #-} Monad m => MonadGLFW (GLFWT s m) where +  keyStatusM k = do +    GLFWState{..} <- GLFWT get+    return $ M.lookup k glfwKeys++  mouseButtonM b = do +    GLFWState{..} <- GLFWT get +    return $ M.lookup b glfwMouseButtons++  mousePosM = GLFWT $ glfwMousePos <$> get +  mouseScrollM = GLFWT $ glfwScroll <$> get +  windowSizeM = GLFWT $ glfwWindowSize <$> get +  windowClosingM = GLFWT $ glfwClose <$> get ++  setCurrentWindowM w = GLFWT $ do +    s <- get +    put $ s { +        glfwWindow = w +      , glfwPrevWindow = glfwWindow s +      }++  getCurrentWindowM = GLFWT $ do +    s <- get +    return . glfwWindow $! s ++  setBufferSizeM i = GLFWT $ do +    s <- get +    put $ s {+        glfwBufferSize = i +      }++instance {-# OVERLAPPABLE #-} (Monad (mt m), MonadGLFW m, MonadTrans mt) => MonadGLFW (mt m) where +  keyStatusM = lift . keyStatusM+  mouseButtonM = lift . mouseButtonM+  mousePosM = lift mousePosM+  mouseScrollM = lift mouseScrollM+  windowSizeM = lift windowSizeM+  windowClosingM = lift windowClosingM+  setCurrentWindowM = lift . setCurrentWindowM+  getCurrentWindowM = lift getCurrentWindowM+  setBufferSizeM = lift . setBufferSizeM+  +-- | Produces event when key state changes+keyStatus :: MonadGLFW m => Key -> GameWire m a (Event (KeyState, ModifierKeys))+keyStatus k = liftGameMonad (maybe2Event <$> keyStatusM k)++-- | Produces event when key state changes, get key as arrow argument+keyStatusDyn :: MonadGLFW m => GameWire m Key (Event (KeyState, ModifierKeys))+keyStatusDyn = liftGameMonad1 $ \k -> do +  ms <- keyStatusM k +  return $ maybe2Event ms ++-- | Simple transform from maybe to event+maybe2Event :: Maybe a -> Event a +maybe2Event Nothing = NoEvent +maybe2Event (Just a) = Event a++keyStated :: MonadGLFW m => KeyState -> Key -> GameWire m a (Event ModifierKeys)+keyStated ks k = mapE snd . filterE (\(ks', _) -> ks' == ks) . keyStatus k++keyStatedDyn :: MonadGLFW m => KeyState -> GameWire m Key (Event ModifierKeys)+keyStatedDyn ks = mapE snd . filterE (\(ks', _) -> ks' == ks) . keyStatusDyn++-- | Fires when keyboard key is pressed+keyPressed :: MonadGLFW m => Key -> GameWire m a (Event ModifierKeys)+keyPressed = keyStated KeyState'Pressed++-- | Version of keyPressed that takes key at runtime+keyPressedDyn :: MonadGLFW m => GameWire m Key (Event ModifierKeys)+keyPressedDyn = keyStatedDyn KeyState'Pressed++-- | Fires when keyboard key is released+keyReleased :: MonadGLFW m => Key -> GameWire m a (Event ModifierKeys)+keyReleased = keyStated KeyState'Released++-- | Version of keyReleased that takes key at runtime+keyReleasedDyn :: MonadGLFW m => GameWire m Key (Event ModifierKeys)+keyReleasedDyn = keyStatedDyn KeyState'Released++-- | Fires when keyboard key is entered into repeating mode+keyRepeating :: MonadGLFW m => Key -> GameWire m a (Event ModifierKeys)+keyRepeating = keyStated KeyState'Repeating++-- | Version of keyRepeating that takes key at runtime+keyRepeatingDyn :: MonadGLFW m => GameWire m Key (Event ModifierKeys)+keyRepeatingDyn = keyStatedDyn KeyState'Repeating++-- | Fires event from moment of press until release of given key+keyPressing :: MonadGLFW m => Key -> GameWire m a (Event ModifierKeys)+keyPressing k = go NoEvent +  where+    go !e = mkGen $ \_ _ -> do +      !mks <- keyStatusM k+      return $! case mks of +        Nothing -> (Right e, go e)+        Just (!ks, !mds) -> case ks of +          KeyState'Pressed -> (Right $! Event mds, go $! Event mds)+          KeyState'Released -> (Right NoEvent, go NoEvent)+          _ -> (Right e, go e)++-- | Version of keyPressing that takes key at runtime+keyPressingDyn :: MonadGLFW m => GameWire m Key (Event ModifierKeys)+keyPressingDyn = go NoEvent +  where+    go !e = mkGen $ \_ k -> do +      !mks <- keyStatusM k+      return $! case mks of +        Nothing -> (Right e, go e)+        Just (!ks, !mds) -> case ks of +          KeyState'Pressed -> (Right $! Event mds, go $! Event mds)+          KeyState'Released -> (Right NoEvent, go NoEvent)+          _ -> (Right e, go e)++-- | Produces event when mouse button state changes+mouseButton :: MonadGLFW m => MouseButton -> GameWire m a (Event (MouseButtonState, ModifierKeys))+mouseButton k = liftGameMonad (maybe2Event <$> mouseButtonM k)++-- | Produces event when key state changes, get key as arrow argument+mouseButtonDyn :: MonadGLFW m => GameWire m MouseButton (Event (MouseButtonState, ModifierKeys))+mouseButtonDyn = liftGameMonad1 $ \k -> do +  ms <- mouseButtonM k +  return $ maybe2Event ms ++mouseButtonStated :: MonadGLFW m => MouseButtonState -> MouseButton -> GameWire m a (Event ModifierKeys)+mouseButtonStated bs b = mapE snd . filterE (\(bs', _) -> bs == bs') . mouseButton b++mouseButtonStatedDyn :: MonadGLFW m => MouseButtonState -> GameWire m MouseButton (Event ModifierKeys)+mouseButtonStatedDyn bs = mapE snd . filterE (\(bs', _) -> bs == bs') . mouseButtonDyn++-- | Fires when mouse button is pressed+mouseButtonPressed :: MonadGLFW m => MouseButton -> GameWire m a (Event ModifierKeys)+mouseButtonPressed = mouseButtonStated MouseButtonState'Pressed ++-- | Version of mouseButtonPressed that takes button at runtime+mouseButtonPressedDyn :: MonadGLFW m => GameWire m MouseButton (Event ModifierKeys)+mouseButtonPressedDyn = mouseButtonStatedDyn MouseButtonState'Pressed++-- | Fires when mouse button is released+mouseButtonReleased :: MonadGLFW m => MouseButton -> GameWire m a (Event ModifierKeys)+mouseButtonReleased = mouseButtonStated MouseButtonState'Released ++-- | Version of mouseButtonReleased that takes button at runtime+mouseButtonReleasedDyn :: MonadGLFW m => GameWire m MouseButton (Event ModifierKeys)+mouseButtonReleasedDyn = mouseButtonStatedDyn MouseButtonState'Released++-- | Returns current position of mouse+mousePosition :: MonadGLFW m => GameWire m a (Double, Double)+mousePosition = liftGameMonad mousePosM++-- | Fires event when mouse position changes+mousePositionChange :: MonadGLFW m => GameWire m a (Event (Double, Double))+mousePositionChange = go 0 0+  where+    go !x !y = mkGen $ \_ _-> do +      (!x', !y') <- mousePosM+      return $ if x /= x' || y /= y' +        then (Right $! Event (x', y'), go x' y')+        else (Right NoEvent, go x y)++-- | Fires event when mouse X axis changes+mouseXChange :: MonadGLFW m => GameWire m a (Event Double)+mouseXChange = go 0 +  where+    go !x = mkGen $ \_ _-> do +      (!x', _) <- mousePosM+      return $ if x /= x'+        then (Right $! Event x', go x')+        else (Right NoEvent, go x)++-- | Fires event when mouse Y axis changes+mouseYChange :: MonadGLFW m => GameWire m a (Event Double)+mouseYChange = go 0 +  where+    go !y = mkGen $ \_ _-> do +      (_, !y') <- mousePosM+      return $ if y /= y'+        then (Right $! Event y', go y')+        else (Right NoEvent, go y)++-- | Returns mouse delta moves+mouseDelta :: MonadGLFW m => GameWire m a (Double, Double)+mouseDelta = go 0 0+  where +    go !x !y = mkGen $ \_ _ -> do +      (!x', !y') <- mousePosM+      let dx = x' - x +          dy = y' - y+          res = Right (dx, dy)+      return $ dx `seq` dy `seq` (res, go x' y')++-- | Fires when mouse moves, holds delta move+mouseDeltaChange :: MonadGLFW m => GameWire m a (Event (Double, Double))+mouseDeltaChange = go 0 0+  where +    go !x !y = mkGen $ \_ _ -> do +      (!x', !y') <- mousePosM+      let dx = x' - x +          dy = y' - y+          res = Right $! Event (dx, dy)+      return $ if x /= x' || y /= y' +        then dx `seq` dy `seq` (res, go x' y')+        else (Right NoEvent, go x y)++-- | Fires when mouse X axis changes, holds delta move+mouseDeltaXChange :: MonadGLFW m => GameWire m a (Event Double)+mouseDeltaXChange = go 0 +  where +    go !x = mkGen $ \_ _ -> do +      (!x', _) <- mousePosM+      let dx = x' - x +          res = Right $! Event dx+      return $ if x /= x' +        then dx `seq` (res, go x')+        else (Right NoEvent, go x)++-- | Fires when mouse Y axis changes, holds delta move+mouseDeltaYChange :: MonadGLFW m => GameWire m a (Event Double)+mouseDeltaYChange = go 0 +  where +    go !y = mkGen $ \_ _ -> do +      (_, !y') <- mousePosM+      let dy = y' - y +          res = Right $! Event dy+      return $ if y /= y'+        then dy `seq` (res, go y')+        else (Right NoEvent, go y)++-- | Fires when windows size is changed+windowSize :: MonadGLFW m => GameWire m a (Event (Double, Double))+windowSize = go 0 0 +  where+    go !x !y = mkGen $ \_ _ -> do +      ms <- windowSizeM+      return $! case ms of +        Nothing -> (Right NoEvent, go x y)+        Just (!x', !y') -> if x /= x' || y /= y' +          then x' `seq` y' `seq` (Right $! Event (x', y'), go x' y')+          else (Right NoEvent, go x y)++-- | Fires when user scrolls+mouseScroll :: MonadGLFW m => GameWire m a (Event (Double, Double))+mouseScroll = mkGen_ $ \_ -> do +  ss <- mouseScrollM+  return . Right $! case ss of +    [] -> NoEvent+    ((!x', !y'):_) -> Event (x', y')++-- | Fires when user scrolls X axis+mouseScrollX :: MonadGLFW m => GameWire m a (Event Double)+mouseScrollX = mapE fst . mouseScroll++-- | Fires when user scrolls Y axis+mouseScrollY :: MonadGLFW m => GameWire m a (Event Double)+mouseScrollY = mapE snd . mouseScroll ++-- | Fires when user hits close button of window +windowClosing :: MonadGLFW m => GameWire m a (Event ())+windowClosing = liftGameMonad $ do +  f <- windowClosingM +  return $! if f then Event ()+    else NoEvent 
+ src/Game/GoreAndAsh/GLFW/Module.hs view
@@ -0,0 +1,216 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Module      : Game.GoreAndAsh.GLFW.Module+Description : Monad transformer of the module+Copyright   : (c) Anton Gushcha, 2015-2016+License     : BSD3+Maintainer  : ncrashed@gmail.com+Stability   : experimental+Portability : POSIX++The module contains declaration of monad transformer of the core module and+instance for 'GameModule' class.+-}+module Game.GoreAndAsh.GLFW.Module(+    GLFWT(..)+  ) where++import Control.Monad.Catch+import Control.Monad.Extra+import Control.Monad.Fix +import Control.Monad.IO.Class+import Control.Monad.State.Strict +import Data.IORef+import Data.Proxy +import Graphics.UI.GLFW+import qualified Data.HashMap.Strict as M ++import Game.GoreAndAsh+import Game.GoreAndAsh.GLFW.State++-- | Monad transformer that handles GLFW specific API+--+-- [@s@] - State of next core module in modules chain;+--+-- [@m@] - Next monad in modules monad stack;+--+-- [@a@] - Type of result value;+--+-- How to embed module:+-- +-- @+-- type AppStack = ModuleStack [GLFWT, ... other modules ... ] IO+--+-- newtype AppMonad a = AppMonad (AppStack a)+--   deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadThrow, MonadCatch, MonadSDL)+-- @+--+-- The module is NOT pure within first phase (see 'ModuleStack' docs), therefore currently only 'IO' end monad can handler the module.+newtype GLFWT s m a = GLFWT { runGLFWT :: StateT (GLFWState s) m a }+  deriving (Functor, Applicative, Monad, MonadState (GLFWState s), MonadFix, MonadThrow, MonadCatch, MonadMask)++instance GameModule m s => GameModule (GLFWT s m) (GLFWState s) where +  type ModuleState (GLFWT s m) = GLFWState s+  +  runModule (GLFWT m) s_ = do+    liftIO $ pollEvents+    close <- readCloseEvent s_+    let s = s_ { glfwClose = close }++    ((a, s'@GLFWState{..}), nextState) <- runModule (runStateT m s) (glfwNextState s)+    bindWindow glfwPrevWindow glfwWindow glfwKeyChannel glfwMouseButtonChannel +      glfwMousePosChannel glfwWindowSizeChannel glfwScrollChannel glfwCloseChannel+    keys <- readAllKeys s'+    buttons <- readAllButtons s'+    mpos <- readMousePos s'+    wsize <- readWindowSize s'+    scroll <- readMouseScroll s'+    return (a, s' { +        glfwKeys = keys+      , glfwMouseButtons = buttons+      , glfwMousePos = mpos+      , glfwNextState = nextState +      , glfwWindowSize = wsize+      , glfwScroll = scroll+      , glfwClose = False+      })+    where +      readAllKeys GLFWState{..} = liftIO $ do+        keys <- readAllChan glfwBufferSize glfwKeyChannel+        return $ M.fromList $ (\(k, ks, mds) -> (k, (ks, mds))) <$> keys++      readAllButtons GLFWState{..} = liftIO $ do +        btns <- readAllChan glfwBufferSize glfwMouseButtonChannel+        return $ M.fromList $ (\(b, bs, mds) -> (b, (bs, mds))) <$> btns ++      readMousePos GLFWState{..} = liftIO $+        readIORef glfwMousePosChannel++      readWindowSize GLFWState{..} = liftIO $ +        readIORef glfwWindowSizeChannel ++      readMouseScroll GLFWState{..} = liftIO $ +        readAllChan glfwBufferSize glfwScrollChannel++      readCloseEvent GLFWState{..} = liftIO $ +        readIORef glfwCloseChannel ++  newModuleState = do+    s <- newModuleState +    kc <- liftIO $ newIORef []+    mbc <- liftIO $ newIORef []+    mpc <- liftIO $ newIORef (0, 0)+    wsc <- liftIO $ newIORef Nothing+    sch <- liftIO $ newIORef []+    cch <- liftIO $ newIORef False+    return $ GLFWState {+        glfwNextState = s+      , glfwKeyChannel = kc+      , glfwKeys = M.empty+      , glfwMouseButtonChannel = mbc +      , glfwMouseButtons = M.empty+      , glfwMousePos = (0, 0)+      , glfwMousePosChannel = mpc+      , glfwWindow = Nothing+      , glfwPrevWindow = Nothing+      , glfwWindowSize = Nothing+      , glfwWindowSizeChannel = wsc+      , glfwScroll = []+      , glfwScrollChannel = sch+      , glfwClose = False+      , glfwCloseChannel = cch+      , glfwBufferSize = 100+      }++  withModule _ = withModule (Proxy :: Proxy m)+  cleanupModule _ = return ()+  +instance MonadTrans (GLFWT s) where+  lift = GLFWT . lift ++instance MonadIO m => MonadIO (GLFWT s m) where +  liftIO = GLFWT . liftIO ++-- | Updates handlers when current window changes+bindWindow :: MonadIO m => Maybe Window -> Maybe Window +  -> KeyChannel -> ButtonChannel -> MouseChannel -> WindowSizeChannel +  -> ScrollChannel -> CloseChannel -> m ()+bindWindow prev cur kch mbch mpch wsch sch cch = unless (prev == cur) $ liftIO $ do +  whenJust prev $ \w -> do+    setKeyCallback w Nothing+    setMouseButtonCallback w Nothing+    setCursorPosCallback w Nothing+    setWindowSizeCallback w Nothing >> atomicWriteIORef wsch Nothing+    setScrollCallback w Nothing+    setWindowCloseCallback w Nothing+  whenJust cur $ \w -> do+    bindKeyListener kch w+    bindMouseButtonListener mbch w+    bindMousePosListener mpch w++    bindWindowSizeListener wsch w+    -- update window size+    (!sx, !sy) <- getWindowSize w +    atomicWriteIORef wsch $! Just (fromIntegral sx, fromIntegral sy)++    bindScrollListener sch w +    bindCloseCallback cch w++atomicAppendIORef :: IORef [a] -> a -> IO ()+atomicAppendIORef ref a = atomicModifyIORef ref $ \as -> (a : as, ()) ++-- | Bind callback that passes keyboard info to channel+bindKeyListener :: KeyChannel -> Window -> IO ()+bindKeyListener kch w = setKeyCallback w (Just f)+  where+    f :: Window -> Key -> Int -> KeyState -> ModifierKeys -> IO ()+    f _ k _ ks mds = atomicAppendIORef kch (k, ks, mds)++-- | Bind callback that passes mouse button info to channel+bindMouseButtonListener :: ButtonChannel -> Window -> IO ()+bindMouseButtonListener mbch w = setMouseButtonCallback w (Just f)+  where +    f :: Window -> MouseButton -> MouseButtonState -> ModifierKeys -> IO ()+    f _ b bs mds = atomicAppendIORef mbch (b, bs, mds)++-- | Bind callback that passes mouse position info to channel+bindMousePosListener :: MouseChannel -> Window -> IO ()+bindMousePosListener mpch w = setCursorPosCallback w (Just f)+  where +    f :: Window -> Double -> Double -> IO ()+    f w' x y = do+      (sx, sy) <- getWindowSize w'+      let x' = 2 * (x / fromIntegral sx - 0.5)+          y' = 2 * (0.5 - y / fromIntegral sy)+      atomicWriteIORef mpch $! x' `seq` y' `seq` (x', y')++-- | Bind callback that passes window size info to channel+bindWindowSizeListener :: WindowSizeChannel -> Window -> IO ()+bindWindowSizeListener wsch w = setWindowSizeCallback w (Just f)+  where+    f :: Window -> Int -> Int -> IO ()+    f _ sx sy = do +      let sx' = fromIntegral sx +          sy' = fromIntegral sy+      atomicWriteIORef wsch . Just $! sx' `seq` sy' `seq` (sx', sy')++-- | Bind callback that passes scoll info to channel+bindScrollListener :: ScrollChannel -> Window -> IO ()+bindScrollListener sch w = setScrollCallback w (Just f)+  where +    f :: Window -> Double -> Double -> IO ()+    f _ !sx !sy = atomicAppendIORef sch $! (sx, sy)++-- | Bind callback that passes close event to channel+bindCloseCallback :: CloseChannel -> Window -> IO ()+bindCloseCallback cch w = setWindowCloseCallback w (Just f)+  where +    f :: Window -> IO ()+    f _ = atomicWriteIORef cch True ++-- | Helper function to read all elements from channel+readAllChan :: Int -> IORef [a] -> IO [a]+readAllChan mi chan = do +  xs <- readIORef chan +  atomicWriteIORef chan []+  return $ take mi xs
+ src/Game/GoreAndAsh/GLFW/State.hs view
@@ -0,0 +1,88 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Module      : Game.GoreAndAsh.GLFW.State+Description : Internal core module state+Copyright   : (c) Anton Gushcha, 2015-2016+License     : BSD3+Maintainer  : ncrashed@gmail.com+Stability   : experimental+Portability : POSIX+-}+module Game.GoreAndAsh.GLFW.State(+    KeyChannel+  , ButtonChannel+  , MouseChannel+  , WindowSizeChannel+  , ScrollChannel+  , CloseChannel+  , GLFWState(..)+  ) where++import Control.DeepSeq+import Data.IORef+import Data.Hashable+import GHC.Generics (Generic)+import Graphics.UI.GLFW+import qualified Data.HashMap.Strict as M ++-- | Channel to connect core and callback with key states+type KeyChannel = IORef [(Key, KeyState, ModifierKeys)]+-- | Channel to connect core and callback with mouse button states+type ButtonChannel = IORef [(MouseButton, MouseButtonState, ModifierKeys)]+-- | Channel to connect core and callback with mouse position+type MouseChannel = IORef (Double, Double)+-- | Channel to connect core and callback with window resizing +type WindowSizeChannel = IORef (Maybe (Double, Double))+-- | Channel to connect core and callback with mouse scrolling+type ScrollChannel = IORef [(Double, Double)]+-- | Channel to connect core and callback for window closing+type CloseChannel = IORef Bool ++-- | Module inner state+--+-- [@s@] - State of next module, the states are chained via nesting.+data GLFWState s = GLFWState {+  glfwNextState :: !s+, glfwKeys :: !(M.HashMap Key (KeyState, ModifierKeys))+, glfwKeyChannel :: !KeyChannel+, glfwMouseButtons :: !(M.HashMap MouseButton (MouseButtonState, ModifierKeys))+, glfwMouseButtonChannel :: !ButtonChannel+, glfwMousePos :: !(Double, Double)+, glfwMousePosChannel :: !MouseChannel+, glfwWindow :: !(Maybe Window)+, glfwPrevWindow :: !(Maybe Window)+, glfwWindowSize :: !(Maybe (Double, Double))+, glfwWindowSizeChannel :: !WindowSizeChannel+, glfwScroll :: ![(Double, Double)]+, glfwScrollChannel :: !ScrollChannel+, glfwClose :: !Bool+, glfwCloseChannel :: !CloseChannel +, glfwBufferSize :: !Int+} deriving (Generic)++instance NFData s => NFData (GLFWState s) where +  rnf GLFWState {..} = +    glfwNextState `deepseq` +    glfwKeys `deepseq` +    glfwKeyChannel `seq` +    glfwMouseButtons `deepseq` +    glfwMouseButtonChannel `seq` +    glfwMousePos `deepseq`+    glfwMousePosChannel `seq`+    glfwWindow `seq`+    glfwPrevWindow `seq` +    glfwWindowSize `deepseq`+    glfwWindowSizeChannel `seq`+    glfwScroll `deepseq`+    glfwScrollChannel `seq` +    glfwClose `seq` +    glfwCloseChannel `seq`+    glfwBufferSize `seq` ()++instance Hashable Key +instance Hashable MouseButton+instance NFData Key +instance NFData KeyState +instance NFData ModifierKeys+instance NFData MouseButton+instance NFData MouseButtonState