packages feed

yampa-sdl2 (empty) → 0.0.1.1

raw patch · 17 files changed

+719/−0 lines, 17 filesdep +StateVardep +Yampadep +base

Dependencies added: StateVar, Yampa, base, colour, linear, sdl2, sdl2-gfx, text, yampa-sdl2

Files

+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2018 Simre++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,52 @@+# YampaSDL2++YampaSDL2 provides SDL2 bindings to use with the FRP library [Yampa](https://github.com/ivanperez-keera/Yampa).++![Screenshot](./screenshot.png)+A screenshot of some drawn shapes.++## Roadmap++- [ ] Display Shapes+  - [x] Rectangle+  - [x] Circle+  - [x] Triangle+  - [ ] Polygon+- [ ] Display Images+- [x] Animations+- [x] Handle Input+- [ ] Play sound++**This library is still work in progress**+++## Getting started++### Prerequisites++yampa-sdl2 uses the C-libraries+- _sdl2_+- _sdl2-gfx_++Consequently, you need to have these two libraries installed on your computer.++### Installation++1. Download the library:+```bash+cd example-project+git clone https://github.com/Simre1/yampa-sdl2.git+```+2. Add it to your dependencies:+Edit your stack.yaml file and add yampa-sdl2 to packages.+Add the yampa-sdl2 dependency to your cabal file (or package.yaml)++3. Import the library with: `import YampaSDL2`++## How to use++Head over to the [wiki](https://github.com/Simre1/yampa-sdl2/wiki).++## License++This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+ src/YampaSDL2.hs view
@@ -0,0 +1,48 @@+module YampaSDL2+  ( module YampaSDL2.MainLoop+  , module YampaSDL2.Backend+  , module YampaSDL2.Backend.SDL+  , module YampaSDL2.AppInput+  , module YampaSDL2.AppOutput+  , module YampaSDL2.Geometry+  , module YampaSDL2.Animation+  , module Data.Colour.Names+  , module Data.Colour.SRGB+  , module Linear.V2+  , module SDL.Input.Keyboard.Codes+  ) where++import Linear.V2+import Data.Colour.Names+import Data.Colour.SRGB+import SDL.Input.Keyboard.Codes++import YampaSDL2.AppInput+  ( AppInput+  , quit+  , anyKeyActive+  , anyKeyPress+  , mouseLeftActive+  , mouseLeftPress+  , mouseRightActive+  , mouseRightPress+  , mousePosition+  )+import YampaSDL2.AppOutput+  ( AppOutput(..)+  , Graphics(..)+  , Sound(..)+  , RenderShape(..)+  , Camera(..)+  , ShapeColour(..)+  )+import YampaSDL2.Backend (defaultBackendConfiguration, BackendConfiguration(..))+import YampaSDL2.Geometry (Shape(..))+import YampaSDL2.MainLoop (mainLoop)+import YampaSDL2.Animation+  ( Animation+  , AnimationType(..)+  , animate+  , newAnimation+  )+import YampaSDL2.Backend.SDL (sdlBackend)
+ src/YampaSDL2/Animation.hs view
@@ -0,0 +1,45 @@+{-|+Module      : Animation+Description : Provide an easy way to do animations+-}+{-# Language Arrows #-}++module YampaSDL2.Animation+  ( -- * Animation+    animate+  , Animation(..)+  , AnimationType(..)+  , newAnimation+  ) where++import FRP.Yampa++import YampaSDL2.AppOutput (RenderShape)++data Animation = Animation+  { frames :: [(Time, RenderShape)] -- ^ Time specifies how long the RenderShape is displayed, for example [(0.5, renderShape1), (0.5, renderShape2)]+  , type_ :: AnimationType+  }++newAnimation = Animation++data AnimationType = Once | Endless | Repeat Int | Alternate | AlternateEndless++animate :: Animation -> SF a (Maybe RenderShape)+animate animation+  | (null $ frames animation) = constant Nothing+  | otherwise = proc _ -> do+      frameEvent <- afterEach (getFrames animation) -< ()+      frame <- hold initialFrame -< frameEvent+      returnA -< return frame+        where initialFrame = let (_,frame) = head $ frames animation+                       in frame++getFrames :: Animation -> [(Time, RenderShape)]+getFrames (Animation{frames=frames,type_=type_}) =+  case type_ of+    Once -> frames+    Endless -> cycle frames+    Repeat times -> concat $ replicate times frames+    Alternate -> frames ++ reverse frames+    AlternateEndless -> cycle (frames ++ reverse frames)
+ src/YampaSDL2/AppInput.hs view
@@ -0,0 +1,69 @@+{-|+Module      : Input+Description : Contains all input SF functions+-}++{-# Language Arrows #-}++module YampaSDL2.AppInput+  ( -- * Input+    AppInput(..)+  , initAppInput+    -- ** SFs+  , quit+  , anyKeyActive+  , anyKeyPress+  , mouseLeftActive+  , mouseLeftPress+  , mouseRightActive+  , mouseRightPress+  , mousePosition+  ) where++import FRP.Yampa+import FRP.Yampa.Event (maybeToEvent)+import Data.Maybe (isJust)+import SDL.Input.Keyboard.Codes+import Linear.V2++-- | Your main SF receives AppInput as input+data AppInput = AppInput+  { inpQuit :: Bool+  , inpKey :: Maybe Scancode+  , inpMousePos :: V2 Double+  , inpMouseLeft :: Maybe (V2 Double)+  , inpMouseRight :: Maybe (V2 Double)+  }++initAppInput :: AppInput+initAppInput = AppInput+  { inpQuit = False+  , inpKey = Nothing+  , inpMousePos = V2 0 0+  , inpMouseLeft = Nothing+  , inpMouseRight = Nothing+  }++quit :: SF AppInput (Event ())+quit = inpQuit ^>> edge++anyKeyActive :: SF AppInput (Event Scancode)+anyKeyActive = inpKey ^>> arr maybeToEvent++anyKeyPress :: SF AppInput (Event Scancode)+anyKeyPress = inpKey ^>> edgeJust++mouseLeftActive :: SF AppInput (Event (V2 Double))+mouseLeftActive = inpMouseLeft ^>> arr maybeToEvent++mouseLeftPress :: SF AppInput (Event (V2 Double))+mouseLeftPress = inpMouseLeft ^>> edgeJust++mouseRightActive :: SF AppInput (Event (V2 Double))+mouseRightActive = inpMouseRight ^>> arr maybeToEvent++mouseRightPress :: SF AppInput (Event (V2 Double))+mouseRightPress = inpMouseRight ^>> edgeJust++mousePosition :: SF AppInput (V2 Double)+mousePosition = arr inpMousePos
+ src/YampaSDL2/AppOutput.hs view
@@ -0,0 +1,55 @@+{-|+Module      : Output+Description : Datatypes to describe the output+-}++module YampaSDL2.AppOutput+  ( -- * Output+    AppOutput(..)+  , Graphics(..)+  , Camera(..)+  , RenderShape(..)+  , ShapeColour(..)+  , Sound(..)+  ) where++import Data.Colour+import Linear.V2++import YampaSDL2.Geometry (Shape)++++-- | Your main SF needs to create an AppOutput+data AppOutput = AppOutput+  { graphics :: Graphics+  , sound :: [Sound]+  , shouldExit :: Bool+  }++data Graphics = Graphics+  { camera :: Camera+  , objects :: [RenderShape]+  }++data Camera = Camera+  { cPos :: V2 Double -- ^Moves the view point+  , cSize :: V2 Double -- ^Set the size of the view, for example to zoom.+  }++data RenderShape+  = RS { shape :: Shape+       , colour :: ShapeColour+       , zIndex :: Int -- ^ Higher zIndex means the RenderShape is in front of the others.+       }++  |  -- | Allows you to move multiple RenderShapes at once with the same vector+    Container { containerCentre :: V2 Double+              , children :: [RenderShape] }++data ShapeColour+  = Filled (Colour Double)+  | Unfilled (Colour Double)++data Sound =+  NotImplementedYet
+ src/YampaSDL2/Backend.hs view
@@ -0,0 +1,36 @@+{-|+Module      : Backend+-}+module YampaSDL2.Backend+  ( -- * Backend+    Backend(..)+  , BackendConfiguration(..)+  , defaultBackendConfiguration+  ) where++import FRP.Yampa++import YampaSDL2.AppInput (AppInput)+++data Backend a b = Backend+  { initAction :: IO a+  , inputAction :: Bool -> IO (DTime, Maybe a)+  , outputAction :: Bool -> b -> IO Bool+  , parseInput :: SF a AppInput+  , closeAction :: IO ()+  }++data BackendConfiguration = BackendConfiguration+  { windowWidth :: Int+  , windowHeight :: Int+  , windowName :: String+  , windowResizable :: Bool+  }++defaultBackendConfiguration = BackendConfiguration+  { windowWidth = 800+  , windowHeight = 600+  , windowName = "App"+  , windowResizable = True+  }
+ src/YampaSDL2/Backend/Close.hs view
@@ -0,0 +1,10 @@+module YampaSDL2.Backend.Close+  (closeAction) where++import qualified SDL++closeAction :: SDL.Renderer -> SDL.Window -> IO ()+closeAction r w = do+  SDL.destroyRenderer r+  SDL.destroyWindow w+  SDL.quit
+ src/YampaSDL2/Backend/Init.hs view
@@ -0,0 +1,9 @@+module YampaSDL2.Backend.Init+  (initAction) where++import FRP.Yampa+import qualified SDL++initAction :: IO (Event SDL.EventPayload)+initAction = return NoEvent+
+ src/YampaSDL2/Backend/Input.hs view
@@ -0,0 +1,15 @@+module YampaSDL2.Backend.Input+  (inputAction) where++import qualified SDL+import Control.Concurrent+import FRP.Yampa++inputAction :: MVar DTime -> Bool -> IO (DTime, Maybe (Event SDL.EventPayload))+inputAction lastInteraction _canBlock = do+  currentTime <- SDL.time+  dt <- (currentTime -) <$> swapMVar lastInteraction currentTime+  threadDelay $ round (10000 - dt) -- 10ms pause for roughly 100 FPS+  mEvent <- SDL.pollEvent+  return (dt, Event . SDL.eventPayload <$> mEvent)+
+ src/YampaSDL2/Backend/Output.hs view
@@ -0,0 +1,97 @@+module YampaSDL2.Backend.Output+  (outputAction) where++import qualified SDL+import qualified SDL.Primitive as GFX+import SDL.Vect+import Data.Colour.SRGB+import Data.List (sortBy)+import Data.StateVar (($=))+import Control.Monad++import YampaSDL2.AppOutput ( AppOutput(..)+                           , Graphics (..)+                           , Camera (..)+                           , RenderShape (..)+                           , ShapeColour (..)+                           )+import YampaSDL2.Geometry++outputAction :: SDL.Renderer -> Bool -> AppOutput -> IO Bool+outputAction renderer changed ao =+  when changed ( do+    let os =+          sortBy (\r1 r2 -> zIndex r1 `compare` zIndex r2) $ objects (graphics ao)+        c = camera (graphics ao)++    SDL.rendererDrawColor renderer $= V4 maxBound maxBound maxBound maxBound+    texture <-+      SDL.createTexture+        renderer+        SDL.RGB24+        SDL.TextureAccessTarget+        ((fmap round . cSize) c)+    SDL.rendererRenderTarget renderer $= return texture+    SDL.clear renderer+    mapM_ (renderView renderer c) os+    SDL.rendererRenderTarget renderer $= Nothing+    SDL.clear renderer+    SDL.copy renderer texture Nothing Nothing+    SDL.present renderer+    SDL.destroyTexture texture+    )+  >> return (shouldExit ao)++renderView :: SDL.Renderer -> Camera -> RenderShape -> IO ()+renderView renderer cam rs = do+  let cameraPoint = (cPos cam)+      cameraView = (cSize cam)++  case rs of+    Container {containerCentre=centre, children=cs } -> do+      mapM_ (renderView renderer (Camera (cameraPoint - centre) cameraView)) (sortBy (\r1 r2 -> zIndex r1 `compare` zIndex r2) cs)+    RS {shape=s} -> do+      let shapePoint = shapeCentre (shape rs)+          adjustedShape =+            s { shapeCentre =+              (shapePoint - cameraPoint) + cameraView/2+            }+      case adjustedShape of+        Rectangle {shapeCentre = V2 x y, rectSize = V2 w h} -> do+          let (RGB r g b) = toSRGB24 (sColour rs)+          let draw = if sFilled rs then GFX.fillRectangle else GFX.rectangle+          draw renderer+            (round <$> V2 (x - w / 2) (getY cameraView - (y + h / 2)))+            (round <$> V2 (x + w / 2) (getY cameraView - (y - h / 2)))+            (V4 r g b maxBound)+        Circle {shapeCentre = V2 x y, radius=rad} -> do+          let (RGB r g b) = toSRGB24 (sColour rs)+          let draw = if sFilled rs then GFX.fillCircle else GFX.circle+          draw renderer+            (round <$> V2 x y)+            (round rad)+            (V4 r g b maxBound)+        Triangle {shapeCentre=V2 x y, pointA=V2 pax pay, pointB=V2 pbx pby, pointC=V2 pcx pcy} -> do+          let (RGB r g b) = toSRGB24 (sColour rs)+          let draw = if sFilled rs then GFX.fillTriangle else GFX.smoothTriangle+          draw renderer+            (round <$> V2 (x + pax) (getY cameraView - (y + pay)))+            (round <$> V2 (x + pbx) (getY cameraView - (y + pby)))+            (round <$> V2 (x + pcx) (getY cameraView - (y + pcy)))+            (V4 r g b maxBound)+  return ()++sColour :: RenderShape -> Colour Double+sColour rs =+  case colour rs of+    (Filled a) -> a+    (Unfilled a) -> a++sFilled :: RenderShape -> Bool+sFilled rs =+  case colour rs of+    (Filled _) -> True+    (Unfilled _) -> False++getY :: V2 a -> a+getY (V2 _ y) = y
+ src/YampaSDL2/Backend/Parse.hs view
@@ -0,0 +1,46 @@+module YampaSDL2.Backend.Parse+  (parseInput) where++import qualified SDL+import SDL.Vect+import FRP.Yampa++import YampaSDL2.AppInput (AppInput(..), initAppInput)++parseInput :: SF (Event SDL.EventPayload) AppInput+parseInput = accumHoldBy onSDLInput initAppInput++onSDLInput :: AppInput -> SDL.EventPayload -> AppInput+onSDLInput ai SDL.QuitEvent = ai {inpQuit = True}+onSDLInput ai (SDL.KeyboardEvent ev)+  | SDL.keyboardEventKeyMotion ev == SDL.Pressed =+    ai {inpKey = Just $ getKeyCode ev}+  | SDL.keyboardEventKeyMotion ev == SDL.Released =+    ai+    { inpKey =+        if inpKey ai == return (getKeyCode ev)+          then Nothing+          else inpKey ai+    }+  where+    getKeyCode = SDL.keysymScancode . SDL.keyboardEventKeysym+onSDLInput ai (SDL.MouseMotionEvent ev) =+  ai {inpMousePos = fromIntegral <$> V2 x y}+  where+    P (V2 x y) = SDL.mouseMotionEventPos ev+onSDLInput ai (SDL.MouseButtonEvent ev) =+  ai {inpMouseLeft = lmb, inpMouseRight = rmb}+  where+    motion = SDL.mouseButtonEventMotion ev+    button = SDL.mouseButtonEventButton ev+    pos = inpMousePos ai+    inpMod =+      case (motion, button) of+        (SDL.Released, SDL.ButtonLeft) -> first (const Nothing)+        (SDL.Pressed, SDL.ButtonLeft) -> first (const (Just pos))+        (SDL.Released, SDL.ButtonRight) -> second (const Nothing)+        (SDL.Pressed, SDL.ButtonRight) -> second (const (Just pos))+        _ -> id+    (lmb, rmb) = inpMod $ (inpMouseLeft &&& inpMouseRight) ai+onSDLInput ai _ = ai+
+ src/YampaSDL2/Backend/SDL.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++module YampaSDL2.Backend.SDL+  (sdlBackend) where++import qualified SDL+import SDL.Vect+import Control.Concurrent.MVar+import FRP.Yampa+import Data.Text (pack)++import qualified YampaSDL2.Backend.Init as Init+import qualified YampaSDL2.Backend.Input as Input+import qualified YampaSDL2.Backend.Output as Output+import qualified YampaSDL2.Backend.Parse as Parse+import qualified YampaSDL2.Backend.Close as Close++import YampaSDL2.AppOutput (AppOutput)+import YampaSDL2.Backend++-- | Set up SDL+sdlBackend ::+     BackendConfiguration -> IO (Backend (Event SDL.EventPayload) AppOutput)+sdlBackend bc = do+  SDL.initialize [SDL.InitVideo]+  window <- SDL.createWindow (pack $ windowName bc) windowConf+  SDL.showWindow window+  renderer <- SDL.createRenderer window (-1) SDL.defaultRenderer+  lastInteraction <- newMVar =<< SDL.time+  return $+    Backend+    { initAction = Init.initAction+    , inputAction = Input.inputAction lastInteraction+    , outputAction = Output.outputAction renderer+    , parseInput = Parse.parseInput+    , closeAction = Close.closeAction renderer window+    }+  where+    windowConf =+      SDL.defaultWindow+      { SDL.windowInitialSize =+          V2 (fromIntegral (windowWidth bc)) (fromIntegral (windowHeight bc))+        , SDL.windowResizable = windowResizable (bc)+      }+
+ src/YampaSDL2/Geometry.hs view
@@ -0,0 +1,31 @@+{-|+Module      : Geometry+Description : Declares all shapes+-}++module YampaSDL2.Geometry+  ( -- ** Shapes+    Shape(..)+  ) where++import Linear.V2+++++data Shape =+    Rectangle+      { shapeCentre :: V2 Double+      , rectSize :: V2 Double+      }+  | Circle+     { shapeCentre :: V2 Double+     , radius :: Double+     }+  | Triangle+     { shapeCentre :: V2 Double+     , pointA :: V2 Double+     , pointB :: V2 Double+     , pointC :: V2 Double+     }+     -- ^ Think of shapeCentre as a vector which is applied to all 3 points of the triangle
+ src/YampaSDL2/MainLoop.hs view
@@ -0,0 +1,25 @@+{-|+Module      : MainLoop+-}+++module YampaSDL2.MainLoop+  ( -- * MainLoop+    mainLoop+  ) where++import FRP.Yampa++import YampaSDL2.AppInput+import YampaSDL2.AppOutput+import YampaSDL2.Backend++-- | Starts the Yampa loop+mainLoop :: Backend a AppOutput -> SF AppInput AppOutput -> IO ()+mainLoop backend sf = do+  reactimate+    (initAction backend)+    (inputAction backend)+    (outputAction backend)+    (parseInput backend >>> sf)+  closeAction backend
+ test/Main.hs view
@@ -0,0 +1,44 @@+{-# Language Arrows #-}++import FRP.Yampa+import YampaSDL2++import Data.Maybe++main :: IO ()+main = do+  backend <- sdlBackend defaultBackendConfiguration+  mainLoop backend sf++sf :: SF AppInput AppOutput+sf = proc input -> do+  anyKeyE <- anyKeyActive -< input+  point <- accumHoldBy+    (\p int -> p + direction int)+    (V2 0 0) -< anyKeyE+  shouldQuit <- quit -< input++  objAnimated <- animate animation -< ()+  returnA -< AppOutput+    { graphics = Graphics+      { camera = camera+      , objects = [Container (V2 200 200) [obj1 point, fromMaybe obj5 objAnimated]]+      }+    , sound = []+    , shouldExit = isEvent shouldQuit+    }+  where camera = Camera (V2 0 0) (V2 800 600)+        shape1 = Triangle (V2 0 0) (V2 0 50) (V2 50 (-50)) (V2 (-50) (-50))+        shape2 point = Rectangle point (V2 50 50)+        obj1 point = RS (shape2 point) (Filled blue) 2+        obj2 = RS shape1 (Filled yellow) 1+        obj3 = RS shape1 (Filled violet) 1+        obj4 = RS shape1 (Filled green) 1+        obj5 = RS shape1 (Filled orange) 1+        animation = newAnimation [(0.5,obj2), (0.5,obj3), (0.5,obj4), (0.5, obj5)] Endless+        direction ScancodeRight = V2 1 0+        direction ScancodeLeft = V2 (-1) 0+        direction ScancodeDown = V2 0 (-1)+        direction ScancodeUp = V2 0 1+        direction _ = V2 0 0+
+ yampa-sdl2.cabal view
@@ -0,0 +1,71 @@+-- This file has been generated from package.yaml by hpack version 0.21.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 03e817a47b063a73dd635929d74cec6d550986109c6361e048e24ba1ffe9b4be++name:           yampa-sdl2+version:        0.0.1.1+synopsis:       Yampa and SDL2 made easy+description:    yampa-sdl2 lets you start coding your app right away instead of dealing with SDL2 first.+category:       Graphics+homepage:       https://github.com/Simre1/YampaSDL2#readme+author:         Simre+maintainer:     simre4775@gmail.com+copyright:      2018 Simre+license:        MIT+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10++extra-source-files:+    README.md++library+  exposed-modules:+      YampaSDL2+  other-modules:+      YampaSDL2.Animation+      YampaSDL2.AppInput+      YampaSDL2.AppOutput+      YampaSDL2.Backend+      YampaSDL2.Backend.Close+      YampaSDL2.Backend.Init+      YampaSDL2.Backend.Input+      YampaSDL2.Backend.Output+      YampaSDL2.Backend.Parse+      YampaSDL2.Backend.SDL+      YampaSDL2.Geometry+      YampaSDL2.MainLoop+      Paths_yampa_sdl2+  hs-source-dirs:+      src+  build-depends:+      StateVar+    , Yampa+    , base >=4.7 && <5+    , colour+    , linear+    , sdl2+    , sdl2-gfx+    , text+  default-language: Haskell2010++test-suite YampaSDL2-test+  type: exitcode-stdio-1.0+  main-is: Main.hs+  other-modules:+      Paths_yampa_sdl2+  hs-source-dirs:+      test+  build-depends:+      StateVar+    , Yampa+    , base+    , colour+    , linear+    , sdl2+    , sdl2-gfx+    , text+    , yampa-sdl2+  default-language: Haskell2010