packages feed

helm 0.4 → 0.5.0

raw patch · 11 files changed

+639/−238 lines, 11 filesdep +mtldep +pangodep +random

Dependencies added: mtl, pango, random

Files

README.md view
@@ -18,6 +18,9 @@ Documentation of the Helm API is available on [Hackage](http://hackage.haskell.org/package/helm). There is currently a heavily work-in-progress guide on [Helm's website](http://helm-engine.org/guide), which is a resource aiming to give thorough explanations of the way Helm and its API work through examples.+You can [ask on the mailing list](https://groups.google.com/d/forum/helm-dev) if you're having any trouble+with using the engine for games or working on the engine itself, or if you just want to chit-chat about+Helm.  ## Features @@ -29,9 +32,9 @@ * Straightforward API heavily inspired by the Elm programming language. The API   is broken up into the following areas:   * `FRP.Helm` contains the main code for interfacing with the game engine but-    also includes some utility functions and the modules `FRP.Helm.Color` and `FRP.Helm.Graphics`-    in the style of a sort of prelude library, allowing it to be included and readily-    make the most basic of games.+    also includes some utility functions and the modules `FRP.Helm.Color`, `FRP.Helm.Utilities`+    and `FRP.Helm.Graphics` in the style of a sort of prelude library, allowing it to be included+    and readily make the most basic of games.   * `FRP.Helm.Animation` contains a simple implementation of animations. Each     animation is made up of a list of frames which render a form at a specific time.   * `FRP.Helm.Automaton` contains the `Automaton` data structure and functions@@ -49,7 +52,10 @@   * `FRP.Helm.Mouse` contains signals for working with mouse state.   * `FRP.Helm.Text` contains functions for composing text, formatting it     and then turning it into an element.+  * `FRP.Helm.Utilities` contains a few useful functions, such as lifting/folding signal generators+    containing signals.   * `FRP.Helm.Time` contains functions for composing units of time and signals that sample from the game clock.+  * `FRP.Helm.Transition` contains functions for composing transitions allowing you to animate between interpolable types, e.g. colors.   * `FRP.Helm.Window` contains signals for working with the game window state.  ## Example@@ -64,18 +70,16 @@ render (w, h) = collage w h [move (100, 100) $ filled red $ square 64]  main :: IO ()-main = run $ render <~ Window.dimensions+main = run defaultConfig $ render <~ Window.dimensions ``` -It renders a red square at the position `(100, 100)` with a side length of 64px.  +It renders a red square at the position `(100, 100)` with a side length of `64`.      The next example is the barebones of a game that depends on input. It shows how to create an accumulated state that depends on the values sampled from signals (e.g. mouse input). You should see a white square on the screen and pressing the arrow keys allows you to move it.  ```haskell-import Control.Applicative-import FRP.Elerea.Simple import FRP.Helm import qualified FRP.Helm.Keyboard as Keyboard import qualified FRP.Helm.Window as Window@@ -91,7 +95,7 @@   centeredCollage w h [move (mx, my) $ filled white $ square 100]  main :: IO ()-main = run $ render <~ Window.dimensions ~~ stepper+main = run defaultConfig $ render <~ Window.dimensions ~~ stepper   where     state = State { mx = 0, my = 0 }     stepper = foldp step state Keyboard.arrows@@ -144,7 +148,7 @@   See [issue #1](https://github.com/z0w0/helm/issues/1). * Optimizations and testing. This is a early release of the engine so   obviously little testing or optimizations have been done.-  See [issue #2](https://github.com/z0w0/helm/issues/2).+  See [issue #2](https://github.com/z0w0/helm/issues/2). Preferably, upgrade to SDL2. * Port and support multiple platforms. I've only been testing it on   Linux, but there's really no reason that it wouldn't work out of the box   on Windows or OSX after setting up the dependencies.
helm.cabal view
@@ -1,5 +1,5 @@ name: helm-version: 0.4+version: 0.5.0 synopsis: A functionally reactive game engine. description: A functionally reactive game engine, with headgear to protect you              from the headache of game development provided.@@ -22,6 +22,10 @@  library   hs-source-dirs: src+  default-language: Haskell2010+  default-extensions: RecordWildCards, NamedFieldPuns+  ghc-options: -threaded -Wall -fno-warn-unused-do-bind -O2+   exposed-modules:     FRP.Helm     FRP.Helm.Automaton@@ -31,27 +35,31 @@     FRP.Helm.Joystick     FRP.Helm.Keyboard     FRP.Helm.Mouse-    FRP.Helm.Signal     FRP.Helm.Text     FRP.Helm.Time+    FRP.Helm.Utilities     FRP.Helm.Window+    FRP.Helm.Transition+       build-depends:     base >= 4 && < 5,     cairo >= 0.12 && < 1,+    pango >= 0.12 && < 1,     containers >= 0.5 && < 1,     elerea >= 2.7 && < 3,     filepath >= 1.3 && < 2,-    SDL >= 0.6 && < 1-  default-language: Haskell2010-  default-extensions: RecordWildCards, NamedFieldPuns-  ghc-options: -Wall -fno-warn-unused-do-bind+    SDL >= 0.6 && < 1,+    random >= 1.0.1.1 && < 1.2,+    mtl >= 2.1 && < 2.2  test-suite helm-tests   type: exitcode-stdio-1.0   x-uses-tf: true-  ghc-options: -Wall -rtsopts+  ghc-options: -threaded -Wall -rtsopts -O   hs-source-dirs: tests, src   default-language: Haskell2010+  main-is: Main.hs+     build-depends:     base >= 4 && < 5,     HUnit >= 1.2 && < 2,@@ -60,4 +68,3 @@     test-framework-quickcheck2 >= 0.3 && < 1,     elerea >= 2.7 && < 3,     SDL >= 0.6 && < 1-  main-is: Main.hs
src/FRP/Helm.hs view
@@ -3,16 +3,14 @@ module FRP.Helm (   -- * Types   Time,+  EngineConfig(..),   -- * Engine   run,-  -- * Utilities-  radians,-  degrees,-  turns,+  defaultConfig,   -- * Prelude   module Color,   module Graphics,-  module Signal,+  module Utilities, ) where  import Control.Exception@@ -23,44 +21,52 @@ import FRP.Elerea.Simple import FRP.Helm.Color as Color import FRP.Helm.Graphics as Graphics-import FRP.Helm.Signal as Signal+import FRP.Helm.Utilities as Utilities import FRP.Helm.Time (Time) import System.FilePath import qualified Data.Map as Map import qualified Graphics.UI.SDL as SDL import qualified Graphics.Rendering.Cairo as Cairo+import qualified Graphics.Rendering.Pango as Pango  {-| Attempt to change the window dimensions (and initialize the video mode if not already).     Will try to get a hardware accelerated window and then fallback to a software one.     Throws an exception if the software mode can't be used as a fallback. -}-requestDimensions :: Int -> Int -> IO SDL.Surface-requestDimensions w h =	do-  mayhaps <- SDL.trySetVideoMode w h 32 [SDL.HWSurface, SDL.DoubleBuf, SDL.Resizable]+requestDimensions :: Int -> Int -> Bool -> IO SDL.Surface+requestDimensions w h resizable =	do+    mayhaps <- SDL.trySetVideoMode w h 32 $ [SDL.HWSurface, SDL.DoubleBuf] ++ flags -  case mayhaps of-    Just screen -> return screen-    Nothing -> SDL.setVideoMode w h 32 [SDL.SWSurface, SDL.Resizable]+    case mayhaps of+      Just screen -> return screen+      Nothing -> SDL.setVideoMode w h 32 $ SDL.SWSurface : flags -{-| Converts radians into the standard angle measurement (radians). -}-radians :: Double -> Double-radians n = n+  where+    flags = [SDL.Resizable | resizable] -{-| Converts degrees into the standard angle measurement (radians). -}-degrees :: Double -> Double-degrees n = n * pi / 180+{-| A data structure describing miscellaneous initial configurations of the game window and engine. -}+data EngineConfig = EngineConfig {+  windowDimensions :: (Int, Int),+  windowIsFullscreen :: Bool,+  windowIsResizable :: Bool,+  windowTitle :: String+} -{-| Converts turns into the standard angle measurement (radians).-    Turns are essentially full revolutions of the unit circle. -}-turns :: Double -> Double-turns n = 2 * pi * n+{-| Creates the default configuration for the engine. You should change the fields where necessary before passing it to 'run'. -}+defaultConfig :: EngineConfig+defaultConfig = EngineConfig {+  windowDimensions = (800, 600),+  windowIsFullscreen = False,+  windowIsResizable = True,+  windowTitle = ""+}  {-| A data structure describing the current engine state.     This may be in userland in the future, for setting     window dimensions, title, etc. -} data EngineState = EngineState {   smp :: IO Element,-  {- FIXME: we need this mutable state (unfortunately) -     because Cairo forces us to liftIO and can't return anything +  {- FIXME: we need this mutable state (unfortunately)+     because Cairo forces us to liftIO and can't return anything      in the render function, where the lazy image loading takes place.      There may be a way to do this nicely, I'm just not experienced      enough with Haskell to know how. -}@@ -81,15 +87,17 @@     > import qualified FRP.Helm.Window as Window     >     > render :: (Int, Int) -> Element-    > render (w, h) = collage w h [filled red $ rect (fromIntegral w) (fromIntegral h)]+    > render (w, h) = collage w h [rect (fromIntegral w) (fromIntegral h) |> filled red]     >     > main :: IO ()-    > main = run $ fmap (fmap render) Window.dimensions+    > main = run defaultConfig $ lift render Window.dimensions  -}-run :: SignalGen (Signal Element) -> IO ()-run gen = finally SDL.quit $ do+run :: EngineConfig -> SignalGen (Signal Element) -> IO ()+run (EngineConfig { .. }) gen = finally SDL.quit $ do   SDL.init [SDL.InitVideo, SDL.InitJoystick]-  requestDimensions 800 600+  SDL.rawSetCaption (Just windowTitle) Nothing+  when windowIsFullscreen $ SDL.getVideoSurface >>= SDL.toggleFullscreen+  uncurry requestDimensions windowDimensions windowIsResizable   start gen >>= newEngineState >>= run'  {-| A utility function called by 'run' that samples the element@@ -110,28 +118,35 @@   case event of     SDL.NoEvent -> return True     SDL.Quit -> return False-    SDL.VideoResize w h -> requestDimensions w h >> run''+    SDL.VideoResize w h -> requestDimensions w h True >> run''     _ -> run''  {-| A utility function that renders a previously sampled element     using an engine state. -} render :: EngineState -> Element -> IO ()-render state element = SDL.getVideoSurface >>= render' state element+render state element = do+    videoSurface <- SDL.getVideoSurface+    let+      w = SDL.surfaceGetWidth videoSurface+      h = SDL.surfaceGetHeight videoSurface+    cairoSurface <- SDL.createRGBSurface [SDL.HWSurface] w h 32 0x00ff0000 0x0000ff00 0x000000ff 0xff000000+    render' state element videoSurface cairoSurface  {-| A utility function called by 'render\'' that does     the actual heavy lifting. -}-render' :: EngineState -> Element -> SDL.Surface -> IO ()-render' state element screen = do-    pixels <- SDL.surfaceGetPixels screen+render' :: EngineState -> Element -> SDL.Surface -> SDL.Surface -> IO ()+render' state element videoSurface cairoSurface = do+    pixels <- SDL.surfaceGetPixels cairoSurface -    Cairo.withImageSurfaceForData (castPtr pixels) Cairo.FormatRGB24 w h (w * 4) $ \surface ->+    Cairo.withImageSurfaceForData (castPtr pixels) Cairo.FormatARGB32 w h (w * 4) $ \surface ->       Cairo.renderWith surface (render'' w h state element) -    SDL.flip screen+    SDL.blitSurface cairoSurface Nothing videoSurface Nothing+    SDL.flip videoSurface    where-    w = SDL.surfaceGetWidth screen-    h = SDL.surfaceGetHeight screen+    w = SDL.surfaceGetWidth videoSurface+    h = SDL.surfaceGetHeight videoSurface  {-| A utility function called by 'render\'\'' that is called by Cairo     when it's ready to do rendering. -}@@ -167,11 +182,11 @@  {-| A utility function for rendering a specific element. -} renderElement :: EngineState -> Element -> Cairo.Render ()-renderElement state (CollageElement w h centered forms) = do+renderElement state (CollageElement w h center forms) = do   Cairo.save   Cairo.rectangle 0 0 (fromIntegral w) (fromIntegral h)   Cairo.clip-  when centered $ Cairo.translate (fromIntegral w / 2) (fromIntegral h / 2)+  forM_ center $ uncurry Cairo.translate   mapM_ (renderForm state) forms   Cairo.restore @@ -193,11 +208,40 @@   Cairo.restore  renderElement _ (TextElement (Text { textColor = (Color r g b a), .. })) = do-  Cairo.setSourceRGBA r g b a-  Cairo.selectFontFace textTypeface textSlant textWeight-  Cairo.setFontSize textHeight-  Cairo.showText textUTF8+    Cairo.save +    layout <- Pango.createLayout textUTF8++    Cairo.liftIO $ Pango.layoutSetAttributes layout [Pango.AttrFamily { paStart = i, paEnd = j, paFamily = textTypeface },+                                                     Pango.AttrWeight { paStart = i, paEnd = j, paWeight = mapFontWeight textWeight },+                                                     Pango.AttrStyle { paStart = i, paEnd = j, paStyle = mapFontStyle textStyle },+                                                     Pango.AttrSize { paStart = i, paEnd = j, paSize = textHeight }]++    Pango.PangoRectangle x y w h <- fmap snd $ Cairo.liftIO $ Pango.layoutGetExtents layout++    Cairo.translate ((-w / 2) -x) ((-h / 2) - y)+    Cairo.setSourceRGBA r g b a+    Pango.showLayout layout+    Cairo.restore++  where+    i = 0+    j = length textUTF8++{-| A utility function that maps to a Pango font weight based off our variant. -}+mapFontWeight :: FontWeight -> Pango.Weight+mapFontWeight weight = case weight of+  LightWeight  -> Pango.WeightLight+  NormalWeight -> Pango.WeightNormal+  BoldWeight   -> Pango.WeightBold++{-| A utility function that maps to a Pango font style based off our variant. -}+mapFontStyle :: FontStyle -> Pango.FontStyle+mapFontStyle style = case style of+  NormalStyle  -> Pango.StyleNormal+  ObliqueStyle -> Pango.StyleOblique+  ItalicStyle  -> Pango.StyleItalic+ {-| A utility function that goes into a state of transformation and then pops it when finished. -} withTransform :: Double -> Double -> Double -> Double -> Cairo.Render () -> Cairo.Render () withTransform s t x y f = Cairo.save >> Cairo.scale s s >> Cairo.translate x y >> Cairo.rotate t >> f >> Cairo.restore@@ -205,16 +249,16 @@ {-| A utility function that sets the Cairo line cap based off of our version. -} setLineCap :: LineCap -> Cairo.Render () setLineCap cap = case cap of-  Flat   -> Cairo.setLineCap Cairo.LineCapButt-  Round  -> Cairo.setLineCap Cairo.LineCapRound-  Padded -> Cairo.setLineCap Cairo.LineCapSquare+  FlatCap   -> Cairo.setLineCap Cairo.LineCapButt+  RoundCap  -> Cairo.setLineCap Cairo.LineCapRound+  PaddedCap -> Cairo.setLineCap Cairo.LineCapSquare  {-| A utility function that sets the Cairo line style based off of our version. -} setLineJoin :: LineJoin -> Cairo.Render () setLineJoin join = case join of-  Smooth    -> Cairo.setLineJoin Cairo.LineJoinRound-  Sharp lim -> Cairo.setLineJoin Cairo.LineJoinMiter >> Cairo.setMiterLimit lim-  Clipped   -> Cairo.setLineJoin Cairo.LineJoinBevel+  SmoothJoin    -> Cairo.setLineJoin Cairo.LineJoinRound+  SharpJoin lim -> Cairo.setLineJoin Cairo.LineJoinMiter >> Cairo.setMiterLimit lim+  ClippedJoin   -> Cairo.setLineJoin Cairo.LineJoinBevel  {-| A utility function that sets up all the necessary settings with Cairo     to render with a line style and then strokes afterwards. Assumes@@ -260,17 +304,18 @@ renderForm state Form { .. } = withTransform formScale formTheta formX formY $   case formStyle of     PathForm style ~ps @ ((hx, hy) : _) -> do-      setLineStyle style+      Cairo.newPath       Cairo.moveTo hx hy       mapM_ (uncurry Cairo.lineTo) ps+      setLineStyle style      ShapeForm style shape -> do+      Cairo.newPath+       case shape of         PolygonShape ~ps @ ((hx, hy) : _) -> do-          Cairo.newPath           Cairo.moveTo hx hy           mapM_ (uncurry Cairo.lineTo) ps-          Cairo.closePath          RectangleShape (w, h) -> Cairo.rectangle (-w / 2) (-h / 2) w h 
src/FRP/Helm/Animation.hs view
@@ -2,7 +2,8 @@ module FRP.Helm.Animation (   -- * Types   Frame,-  Animation(..),+  Animation,+  Status(..),   -- * Creating   absolute,   relative,@@ -16,70 +17,107 @@  import FRP.Elerea.Simple import Control.Applicative-import FRP.Helm.Graphics (Form)+import FRP.Helm.Graphics (Form,blank) import FRP.Helm.Time (Time) import Data.Maybe (fromJust) import Data.List (find)+import qualified Data.List as List (length)  {-| A type describing a single frame in an animation. A frame consists of a time at     which the frame takes place in an animation and the form which is how the frame     actually looks when rendered. -} type Frame = (Time, Form) + {-| A type describing an animation consisting of a list of frames. -}-newtype Animation = Animation [Frame] deriving (Show, Eq)+type Animation = [Frame] +{-| This type tells of the state an animation is in.+    Continue: A continued animation plays through its frames as specified in the Animation.+    Pause: A paused animation does not change its current frame and time.+    Stop: A stopped animation is set to its first frame and time 0.+    Frame: The Frame constructor can be used to choose a specific frame of the animation+           where time is set to the first millisecond of that chosen frame. (Indexing starts at 1. 'first frame', not 'zero frame')+    Time: The Time constructor sets the current time (in milliseconds) in the animation to the specified value.   -}+data Status = Continue | Pause | Stop | Frame Int | Time Time+ {-| Creates an animation from a list of frames. The time value in each frame     is absolute to the entire animation, i.e. each time value is the time     at which the frame takes place relative to the starting time of the animation.-    The list of frames should never be empty.  -} absolute :: [Frame] -> Animation-absolute = Animation+absolute = id  {-| Creates an animation from a list of frames. The time value in each frame     is relative to other frames, i.e. each time value is the difference-    in time from the last frame. The list of frames should never be empty.+    in time from the last frame.      > relative [(100, picture1), (100, picture2), (300, picture3)] == absolute [(100, picture1), (200, picture2), (500, picture3)]  -} relative :: [Frame] -> Animation-relative frames = Animation $ scanl1 (\acc x -> (fst acc + fst x, snd x)) frames+relative = scanl1 (\acc x -> (fst acc + fst x, snd x))  {-| Creates a signal contained in a generator that returns the current form in the animation when sampled from     a specific animation. The second argument is a signal generator containing a signal that     returns the time to setup the animation forward when sampled. The third argument is a     signal generator containing a signal that returns true to continue animating     or false to stop animating when sampled. -}-animate :: Animation -> SignalGen (Signal Time) -> SignalGen (Signal Bool) -> SignalGen (Signal Form)-animate anim dt cont = do+animate :: Animation -> SignalGen (Signal Time) -> SignalGen (Signal Status) -> SignalGen (Signal Form)+animate [] _ _ = return $ return blank+animate anim dt status = do   dt1 <- dt-  cont1 <- cont-  progress <- transfer2 0 (\t r animT -> if r then t else resetThisAnim (animT + t)) dt1 cont1+  status1 <- status+  progress <- transfer2 0 (timestep anim) status1 dt1 -  return $ (formAt anim) <$> progress-    where-      resetThisAnim = resetOnEnd anim+  return $ fromJust <$> formAt anim <$> progress +{-| Makes a step in the Animation of size dt, but also takes care to:+    start over if the end was reached.+    behave in accordance to the possible statuses,+    handle erroneous input gently (this may do more bad than good since it may be unexpected, please dispute!) -}+timestep :: Animation -> Status -> Time -> Time -> Time+timestep anim Continue  dt t = cycleTime anim (dt + t)+timestep _    Pause     _  t = t+timestep _    Stop      _  _ = 0+timestep anim (Time sT) _  _ = cycleTime anim sT+timestep anim (Frame f) _  _ = gentleIndex anim f+  where+    gentleIndex [] _ = 0+    gentleIndex xs n = fst $ xs !! (cycleFrames anim n -1)+ {-| The form that will be rendered for a specific time in an animation. -}-formAt :: Animation -> Time -> Form-formAt (Animation anim) t = snd $ fromJust $ find (\frame -> t < (fst frame)) anim+formAt :: Animation -> Time -> Maybe Form+formAt anim t = snd <$> find (\frame -> t <= fst frame) anim  {-| The amount of time one cycle of the animation takes. -} length :: Animation -> Time-length = maximum . times+length [] = 0+length anim = maximum $ times anim  {-| A list of all the time values of each frame in the animation. -} times :: Animation -> [Time]-times (Animation anim) = map fst anim+times = map fst -{-| Given an animation, a function is created which resets the time of the animation-    if the animation was finished. -}-resetOnEnd :: Animation -> (Time -> Time)-resetOnEnd anim = resetOnEnd' (length anim)+{-| Given an animation, a function is created which loops the time of the animation+    to always be in the animations length boundary. -}+cycleTime :: Animation -> Time -> Time+cycleTime anim = cycleTime' (length anim) -{-| Helper function which resets a timer if the timer got bigger than a given number. -}-resetOnEnd' :: Time -> Time -> Time-resetOnEnd' l t-  | t >= l = 0+{-| Helper function which makes a timer loop through an time interval. -}+cycleTime' :: Time -> Time -> Time+cycleTime' l t+  | t > l = cycleTime' l (t-l)+  | t < 0 = cycleTime' l (l+t)   | otherwise = t++{-| Given an animation, a function is created which loops the frame indices of the animation+    to always be in the animations frame length boundary. -}+cycleFrames :: Animation -> Int -> Int+cycleFrames anim = cycleFrames' (List.length anim)++{-| Helper function which makes a frame index loop through an interval starting at 1. -}+cycleFrames' :: Int -> Int -> Int+cycleFrames' l f+  | f > l = cycleFrames' l (f-l)+  | f < 1 = cycleFrames' l (l+f)+  | otherwise = f
src/FRP/Helm/Color.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric #-} {-| Contains all data structures and functions for composing colors. -} module FRP.Helm.Color (   -- * Types@@ -8,6 +9,7 @@   rgb,   hsva,   hsv,+  blend,   complement,   linear,   radial,@@ -31,10 +33,12 @@   forestGreen ) where +import GHC.Generics+ {-| A data structure describing a color. It is represented interally as an RGBA     color, but the utility functions 'hsva', 'hsv', etc. can be used to convert     from other popular formats to this structure. -}-data Color = Color Double Double Double Double deriving (Show, Eq, Ord, Read)+data Color = Color Double Double Double Double deriving (Show, Eq, Ord, Read, Generic)  {-| Creates an RGB color. -} rgb :: Double -> Double -> Double -> Color@@ -111,6 +115,17 @@ {-| A dark green color. -} forestGreen :: Color forestGreen = rgb 0.133 0.543 0.133++{-| Takes a list of colors and turns it into a single color by+    averaging the color components. -}+blend :: [Color] -> Color+blend colors = (\(Color r g b a) -> Color (r / denom) (g / denom) (b / denom) (a / denom)) $ foldl blend' black colors+  where+    denom = fromIntegral $ length colors++{-| A utility function that adds colors together. -}+blend' :: Color -> Color -> Color+blend' (Color r1 g1 b1 a1) (Color r2 g2 b2 a2) = Color (r1 + r2) (g1 + g2) (b1 + b2) (a1 + a2)  {-| Calculate a complementary color for a provided color. Useful for outlining     a filled shape in a color clearly distinguishable from the fill color. -}
src/FRP/Helm/Graphics.hs view
@@ -3,6 +3,8 @@ module FRP.Helm.Graphics (   -- * Types   Element(..),+  FontWeight(..),+  FontStyle(..),   Text(..),   Form(..),   FormStyle(..),@@ -18,6 +20,7 @@   croppedImage,   collage,   centeredCollage,+  fixedCollage,   -- * Styles & Forms   defaultLine,   solid,@@ -30,6 +33,7 @@   traced,   sprite,   toForm,+  blank,   -- * Grouping   group,   groupTransform,@@ -53,8 +57,27 @@  import FRP.Helm.Color (Color, black, Gradient) import Graphics.Rendering.Cairo.Matrix (Matrix)-import qualified Graphics.Rendering.Cairo as Cairo +{-| A data structure describing the weight of a piece of font. -}+data FontWeight = LightWeight |+                  NormalWeight |+                  BoldWeight deriving (Show, Eq, Ord, Enum, Read)++{-| A data structure describing the style of of a piece of font. -}+data FontStyle = NormalStyle |+                 ObliqueStyle |+                 ItalicStyle deriving (Show, Eq, Ord, Enum, Read)++{-| A data structure describing a piece of formatted text. -}+data Text = Text {+  textUTF8 :: String,+  textColor :: Color,+  textTypeface :: String,+  textHeight :: Double,+  textWeight :: FontWeight,+  textStyle :: FontStyle+} deriving (Show, Eq)+ {-| A data structure describing something that can be rendered     to the screen. Elements are the most important structure     in Helm. Games essentially feed the engine a stream@@ -62,20 +85,10 @@     The usual way to render art in a Helm game is to call     off to the 'collage' function, which essentially     renders a collection of forms together. -}-data Element = CollageElement Int Int Bool [Form] |+data Element = CollageElement Int Int (Maybe (Double, Double)) [Form] |                ImageElement (Int, Int) Int Int FilePath Bool |                TextElement Text deriving (Show, Eq) -{-| A data structure describing a piece of formatted text. -}-data Text = Text {-  textUTF8 :: String,-  textColor :: Color,-  textTypeface :: String,-  textHeight :: Double,-  textWeight :: Cairo.FontWeight,-  textSlant :: Cairo.FontSlant-} deriving (Show, Eq)- {-| Create an element from an image with a given width, height and image file path.     If the image dimensions are not the same as given, then it will stretch/shrink to fit.     Only PNG files are supported currently. -}@@ -109,12 +122,12 @@ data FillStyle = Solid Color | Texture String | Gradient Gradient deriving (Show, Eq, Ord, Read)  {-| A data structure describing the shape of the ends of a line. -}-data LineCap = Flat | Round | Padded deriving (Show, Eq, Enum, Ord, Read)+data LineCap = FlatCap | RoundCap | PaddedCap deriving (Show, Eq, Enum, Ord, Read)  {-| A data structure describing the shape of the join of a line, i.e.     where separate line segments join. The 'Sharp' variant takes     an argument to limit the length of the join. -}-data LineJoin = Smooth | Sharp Double | Clipped deriving (Show, Eq, Ord, Read)+data LineJoin = SmoothJoin | SharpJoin Double | ClippedJoin deriving (Show, Eq, Ord, Read)  {-| A data structure describing how a shape or path looks when stroked. -} data LineStyle = LineStyle {@@ -132,8 +145,8 @@ defaultLine = LineStyle {   lineColor = black,   lineWidth = 1,-  lineCap = Flat,-  lineJoin = Sharp 10,+  lineCap = FlatCap,+  lineJoin = SharpJoin 10,   lineDashing = [],   lineDashOffset = 0 }@@ -194,6 +207,10 @@ toForm :: Element -> Form toForm element = form (ElementForm element) +{-| Creates a empty form, useful for having forms rendered only at some state. -}+blank :: Form+blank = group []+ {-| Groups a collection of forms into a single one. -} group :: [Form] -> Form group forms = form (GroupForm Nothing forms)@@ -230,11 +247,15 @@     >                  move (100, 100) $ outlined (solid white) $ circle 50]  -} collage :: Int -> Int -> [Form] -> Element-collage w h = CollageElement w h False+collage w h = CollageElement w h Nothing  {-| Like 'collage', but it centers the forms within the supplied dimensions. -} centeredCollage :: Int -> Int -> [Form] -> Element-centeredCollage w h = CollageElement w h True+centeredCollage w h = CollageElement w h (Just (realToFrac w / 2, realToFrac h / 2))++{-| Like 'centeredCollage', but it centers the forms around a specific point. -}+fixedCollage :: Int -> Int -> (Double, Double) -> [Form] -> Element+fixedCollage w h (x, y) = CollageElement w h (Just (realToFrac w / 2 - x, realToFrac h / 2 - y))  {-| A data type made up a collection of points that form a path when joined. -} type Path = [(Double, Double)]
− src/FRP/Helm/Signal.hs
@@ -1,112 +0,0 @@-{-| Contains utility functions for working with signals and signal generators. -}-module FRP.Helm.Signal (-  -- * Composing-  constant,-  lift,-  lift2,-  lift3,-  (<~),-  (~~),-  -- * Accumulating-  foldp,-  count,-  countIf,-  -- * DYEL?-  lift4,-  lift5,-  lift6,-  lift7,-  lift8-) where--import Control.Applicative ((<*>))-import FRP.Elerea.Simple--{-| Creates a signal that never changes. -}-constant :: a -> SignalGen (Signal a)-constant value = return $ return value--{- TODO:-combine :: [SignalGen (Signal a)] -> SignalGen (Signal [a])--}--{-| Applies a function to a signal producing a new signal. This is a wrapper around the builtin-    'fmap' function that automatically binds the input signal out of the signal generator.--    > render <~ Window.dimensions- -}-lift :: (a -> b) -> SignalGen (Signal a) -> SignalGen (Signal b)-lift f = fmap (fmap f)--{-| Applies a function to two signals. -}-lift2 :: (a -> b -> c) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c)-lift2 f a b = f <~ a ~~ b--{-| Applies a function to three signals. -}-lift3 :: (a -> b -> c -> d) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-lift3 f a b c = (f <~ a ~~ b) ~~ c--{-| Applies a function to four signals. -}-lift4 :: (a -> b -> c -> d -> e) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-                                 -> SignalGen (Signal e)-lift4 f a b c d = ((f <~ a ~~ b) ~~ c) ~~ d--{-| Applies a function to five signals. -}-lift5 :: (a -> b -> c -> d -> e -> f) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-                                      -> SignalGen (Signal e) -> SignalGen (Signal f)-lift5 f a b c d e = (((f <~ a ~~ b) ~~ c) ~~ d) ~~ e--{-| Applies a function to six signals. -}-lift6 :: (a -> b -> c -> d -> e -> f -> g) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-                                           -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g)-lift6 f a b c d e f1 = ((((f <~ a ~~ b) ~~ c) ~~ d) ~~ e) ~~ f1--{-| Applies a function to seven signals. -}-lift7 :: (a -> b -> c -> d -> e -> f -> g -> h) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-                                                -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g) -> SignalGen (Signal h)-lift7 f a b c d e f1 g = (((((f <~ a ~~ b) ~~ c) ~~ d) ~~ e) ~~ f1) ~~ g--{-| Applies a function to eight signals. -}-lift8 :: (a -> b -> c -> d -> e -> f -> g -> h -> i) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)-                                                     -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g) -> SignalGen (Signal h)-                                                     -> SignalGen (Signal i)-lift8 f a b c d e f1 g h = ((((((f <~ a ~~ b) ~~ c) ~~ d) ~~ e) ~~ f1) ~~ g) ~~ h--{-| An alias for 'lift'. -}-(<~) :: (a -> b) -> SignalGen (Signal a) -> SignalGen (Signal b)-(<~) = lift--infix 4 <~--{-| Applies a function within a signal to a signal. This is a wrapper around the builtin '<*>' operator-    that automatically binds the input signal out of the signal generator.--    > render <~ Window.dimensions ~~ Window.position- -}-(~~) :: SignalGen (Signal (a -> b)) -> SignalGen (Signal a) -> SignalGen (Signal b)-(~~) f input = do-	f1 <- f-	input1 <- input--	return $ f1 <*> input1--infix 3 ~~--{-| Creates a past-dependent signal that depends on another signal. This is a-    wrapper around the 'transfer' function that automatically binds the input-    signal out of the signal generator. This function is useful for making a render-    function that depends on some accumulated state. -}-foldp :: (a -> b -> b) -> b -> SignalGen (Signal a) -> SignalGen (Signal b)-foldp f ini input = do-	input1 <- input--	transfer ini f input1--{-| Creates a signal that counts the amount of times it has been sampled. -}-count :: SignalGen (Signal Int)-count = stateful 0 (+ 1)--{-| Creates a signal that counts the amount of times an input signal has passed-    a predicate when sampled. -}-countIf :: (a -> Bool) -> SignalGen (Signal a) -> SignalGen (Signal Int)-countIf f = foldp (\v c -> c + fromEnum (f v)) 0
src/FRP/Helm/Text.hs view
@@ -9,8 +9,10 @@   defaultText,   toText,   -- * Formatting+  light,   bold,   italic,+  oblique,   color,   monospace,   typeface,@@ -19,19 +21,18 @@ ) where  import FRP.Helm.Color (Color, black)-import FRP.Helm.Graphics (Element(TextElement), Text(..))-import qualified Graphics.Rendering.Cairo as Cairo+import FRP.Helm.Graphics (Element(TextElement), Text(..), FontWeight(..), FontStyle(..))  {-| Creates the default text. By default the text is black sans-serif-    with a height of 14px. -}+    with a height of 14pt. -} defaultText :: Text defaultText = Text {   textUTF8 = "",   textColor = black,   textTypeface = "sans-serif",   textHeight = 14,-  textWeight = Cairo.FontWeightNormal,-  textSlant = Cairo.FontSlantNormal+  textWeight = NormalWeight,+  textStyle = NormalStyle }  {-| Creates a text from a string. -}@@ -62,12 +63,20 @@  {-| Sets the weight of a piece of text to bold. -} bold :: Text -> Text-bold txt = txt { textWeight = Cairo.FontWeightBold }+bold txt = txt { textWeight = BoldWeight } +{-| Sets the weight of a piece of text to light. -}+light :: Text -> Text+light txt = txt { textWeight = LightWeight }+ {-| Sets the slant of a piece of text to italic. -} italic :: Text -> Text-italic txt = txt { textSlant = Cairo.FontSlantItalic }+italic txt = txt { textStyle = ItalicStyle } +{-| Sets the slant of a piece of text to oblique. -}+oblique :: Text -> Text+oblique txt = txt { textStyle = ObliqueStyle }+ {-| Sets the color of a piece of text. -} color :: Color -> Text -> Text color col txt = txt { textColor = col }@@ -76,9 +85,7 @@ monospace :: Text -> Text monospace txt = txt { textTypeface = "monospace" } -{-| Sets the typeface of the text. Only fonts-    supported by Cairo's toy font API are currently-    supported. -}+{-| Sets the typeface of the text. -} typeface :: String -> Text -> Text typeface face txt = txt { textTypeface = face } 
src/FRP/Helm/Time.hs view
@@ -78,6 +78,11 @@     amount of time it blocked for. Please note that delaying by values smaller than 1 millisecond can have     platform-specific results. -} delay :: Time -> SignalGen (Signal Time)-delay t = effectful $ SDL.delay fixed >> return (realToFrac fixed)+delay t = effectful $ do+    before <- SDL.getTicks++    SDL.delay fixed+    realToFrac <$> flip (-) before <$> SDL.getTicks+   where     fixed = max 0 $ round t
+ src/FRP/Helm/Transition.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE DefaultSignatures, TypeOperators, FlexibleContexts, FlexibleInstances #-}+{-| Contains all data structures for describing transitions, composing and animating them. -}+module FRP.Helm.Transition (+  -- * Types+  Transition,+  TransitionStatus(..),+  Interpolate(..),+  -- * Creating+  waypoint,+  startWith,+  fromList,+  -- * Transitions+  transition,+  length+) where++import Control.Applicative+import FRP.Elerea.Simple+import FRP.Helm.Color (Color)+import FRP.Helm.Time (Time)+import Data.List (find)+import Prelude hiding (length)+import Data.Maybe (fromJust)+import GHC.Float+import Data.Word+import Data.Int+import GHC.Generics+import Control.Monad.Writer.Lazy+import Control.Monad.State.Lazy++{-| A type describing a combosable transition. The writer keeps record of all the frames in the transition.+    The state holds the current value of the transition. This allows you to easily compose transitions using do notation. -}+type Transition a = StateT a (Writer [(a, Time)])++{-| This is used only for easier search of frames when transitioning is in progress. -}+data InternalFrame a =+  InternalFrame { -- | The initial value in the transition.+                  s :: a,+                  -- | The final value in the transition.+                  e :: a,+                  -- | The time that the transition will take.+                  t :: Double,+                  -- | The transition-relative time of the beginning of this frame.+                  tend :: Double,+                  -- | The transition-relative time of the end of this frame.+                  tstart :: Double+  } deriving Show++type InternalTransition a = [InternalFrame a]++{-| A variety of statuses that can be used to control a transition. -}+data TransitionStatus+  -- | The transition will repeat forever.+  = Cycle+  -- | The transition will be paused and won't changed until resumed.+  | Pause+  -- | The transition is cycled once and then stops.+  | Once+  -- | The transition will reset to a certain point in time.+  | Set Time++{-| Adds a value to the transition monad that will be the next point in the transition. -}+waypoint :: Interpolate a => a -> Time -> Transition a a+waypoint a t = do+  tell [(a, t)]+  put a+  return a++{-| Interpolates between the beginning and the end of the given frame. -}+transFrame :: Interpolate a => InternalFrame a -> Time -> a+transFrame InternalFrame{..} time = interpolate progress s e+  where+    progress = time / (tend - tstart)++{-| Searches the frame active at the given time and gives back the value of the frame at that time. -}+transitionAt :: Interpolate a => InternalTransition a -> Time -> a+transitionAt pks timeUnsafe = transFrame currentTransition currentTime+  where+    currentTime = time - tstart currentTransition+    currentTransition = fromJust $ find (\InternalFrame{..}-> tend >= time) pks+    time = cycleTime pks timeUnsafe++{-| Turns the internal representation of a transition into a signal.+    The provided time signal acts as the inner clock of the transition.+    The status signal can be used to control the transition, deciding whether+    the transition should cycle, go to a specific time, pause, stop or run once. -}+transition :: Interpolate a => SignalGen (Signal Time) -> SignalGen (Signal TransitionStatus) -> InternalTransition a -> SignalGen (Signal a)+transition _ _ [] = error "empty transitions don't have any default value"+transition dtGen statusGen trans = do+  dt <- dtGen+  status <- statusGen+  time <- transfer2 0 step' status dt+  return $ transitionAt trans <$> time+  where+      step' Cycle  dt t = cycleTime trans (dt/1000+t)+      step' Pause _  t = t+      step' Once dt t = if newT < length trans then newT+                        else length trans+                        where newT = dt / 1000 + t+      step' (Set t) _ _  = t++{-| Converts a list of tuples describing a waypoint value and time into a transition.+    The first element in the list is the starting value and time of the transition.++    > color = transition (constant $ Time.fps 60) (constant Cycle) $ fromList [(white, 0), (green, 2), (red, 5), (black, 1), (yellow, 2)] -}+fromList :: Interpolate a => [(a,Time)] -> InternalTransition a+fromList [] = error "empty transitions don't have any default value"+fromList ((v1, d1) : xs) = scanl (\InternalFrame { .. } (v, d) -> InternalFrame e v d (tend + d) tend) first xs+  where+    first = InternalFrame v1 v1 d1 d1 0++{-| Starts a transition with an initial value. ++    > color = transition (constant $ Time.fps 60) (constant Cycle) $ startWith white $ do+    >   waypoint green 2+    >   waypoint red 5+    >   waypoint black 1+    >   waypoint yellow 2+-}+startWith :: Interpolate a => a -> Transition a b -> InternalTransition a+startWith beginning transitionMonad = fromList $ snd $ runWriter $ evalStateT (tell [(beginning, 0)] >> transitionMonad) beginning++{-| Given an animation, a function is created which loops the time of the animation+    to always be in the animations length boundary. -}+cycleTime :: InternalTransition a -> Time -> Time+cycleTime [] = const 0+cycleTime anim = cycleTime' (length anim)++{-| Helper function which makes a timer loop through an time interval. -}+cycleTime' :: Time -> Time -> Time+cycleTime' l t+  | t > l = cycleTime' l (t-l)+  | t < 0 = cycleTime' l (l+t)+  | otherwise = t++{-| How long it takes for the provided transition to end.  -}+length :: InternalTransition a -> Double+length = tend . last++{-| Defines a value that can be interpolated. An example instance of this class follows:++   > data YourDataType = YourDataConstructor SomeInterpolableType SomeOtherInterpolableType deriving Generic+   >+   > instance Interpolate YourDataType+   >   interpolate 0.5 (YourDataConstructor 3 5) (YourDataConstructor 5 7) == YourDataConstructor 4 6+ -}+class Interpolate a where+  interpolate :: Double -> a -> a -> a+  default interpolate :: (Generic a, GInterpolate (Rep a)) => Double -> a -> a -> a+  interpolate d a b = to $ ginterpolate d (from a) (from b)++class GInterpolate a where+  ginterpolate :: Double -> a b -> a b -> a b++instance GInterpolate V1 where+  ginterpolate _ _ b = b++instance GInterpolate U1 where+  ginterpolate _ _ b = b++instance (GInterpolate a, GInterpolate b) => GInterpolate (a :*: b) where+  ginterpolate d (a1 :*: b1) (a2 :*: b2) = ginterpolate d a1 a2 :*: ginterpolate d b1 b2++instance (GInterpolate a, GInterpolate b) => GInterpolate (a :+: b) where+  ginterpolate d (L1 a) (L1 b) = L1 $ ginterpolate d a b+  ginterpolate d (R1 a) (R1 b) = R1 $ ginterpolate d a b+  ginterpolate _ (L1 _) (R1 b) = R1 b+  ginterpolate _ (R1 _) (L1 b) = L1 b++instance (GInterpolate a) => GInterpolate (M1 i c a) where+  ginterpolate d (M1 a) (M1 b) = M1 $ ginterpolate d a b++instance (Interpolate a) => GInterpolate (K1 i a) where+  ginterpolate d (K1 a) (K1 b) = K1 $ interpolate d a b++instance Interpolate Double where+  interpolate p a b = b * p + a * (1-p)++instance Interpolate Float where+  interpolate p a b = b * double2Float p + a * double2Float (1 - p)++instance Interpolate Char where+  interpolate _ _ b = b++integralInterpolate :: Integral a => Double -> a -> a -> a+integralInterpolate d a b = ceiling $ interpolate d (fromIntegral a :: Double) (fromIntegral b :: Double)++instance Interpolate Word where+  interpolate = integralInterpolate++instance Interpolate Word8 where+  interpolate = integralInterpolate++instance Interpolate Word16 where+  interpolate = integralInterpolate++instance Interpolate Word32 where+  interpolate = integralInterpolate++instance Interpolate Word64 where+  interpolate = integralInterpolate++instance Interpolate Int where+  interpolate = integralInterpolate++instance Interpolate Int8 where+  interpolate = integralInterpolate++instance Interpolate Int16 where+  interpolate = integralInterpolate++instance Interpolate Int32 where+  interpolate = integralInterpolate++instance Interpolate Int64 where+  interpolate = integralInterpolate++instance Interpolate Integer where+  interpolate = integralInterpolate++instance Interpolate Bool+instance Interpolate (Double, Double)+instance Interpolate Color
+ src/FRP/Helm/Utilities.hs view
@@ -0,0 +1,148 @@+{-| Contains miscellaneous utility functions such as functions for working with signals and signal generators. -}+module FRP.Helm.Utilities (+  -- * Angles+  radians,+  degrees,+  turns,+  -- * Applying+  (<|),+  (|>),+  -- * Random numbers+  random,+  randomR,+  -- * Composing+  constant,+  combine,+  lift,+  lift2,+  lift3,+  (<~),+  (~~),+  -- * Accumulating+  foldp,+  count,+  countIf,+  -- * DYEL?+  lift4,+  lift5,+  lift6,+  lift7,+  lift8+) where++import Control.Applicative ((<*>))+import Control.Monad ((>=>))+import FRP.Elerea.Simple+import System.Random (Random, randomIO, randomRIO)++{-| Converts radians into the standard angle measurement (radians). -}+radians :: Double -> Double+radians n = n++{-| Converts degrees into the standard angle measurement (radians). -}+degrees :: Double -> Double+degrees n = n * pi / 180++{-| Converts turns into the standard angle measurement (radians).+    Turns are essentially full revolutions of the unit circle. -}+turns :: Double -> Double+turns n = 2 * pi * n++{-| Forward function application, think of it as a inverted '($)'. Provided for easy porting from Elm. -}+(|>) :: a -> (a -> b) -> b+(|>) = flip ($)++{-| Exactly the same as '($)', only there to make code using '(|>)'+    more consistent. -}+(<|) :: (a -> b) -> a -> b+(<|) = ($)++{-| Creates a signal that never changes. -}+constant :: a -> SignalGen (Signal a)+constant = return . return++{-| Combines a list of signals into a signal of lists. -}+combine :: [SignalGen (Signal a)] -> SignalGen (Signal [a])+combine = sequence >=> return . sequence++{-| Applies a function to a signal producing a new signal. This is a wrapper around the builtin+    'fmap' function that automatically binds the input signal out of the signal generator.++    > render <~ Window.dimensions+ -}+lift :: (a -> b) -> SignalGen (Signal a) -> SignalGen (Signal b)+lift = fmap . fmap++{-| Applies a function to two signals. -}+lift2 :: (a -> b -> c) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c)+lift2 f a b = f <~ a ~~ b++{-| Applies a function to three signals. -}+lift3 :: (a -> b -> c -> d) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+lift3 f a b c = f <~ a ~~ b ~~ c++{-| Applies a function to four signals. -}+lift4 :: (a -> b -> c -> d -> e) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+                                 -> SignalGen (Signal e)+lift4 f a b c d = f <~ a ~~ b ~~ c ~~ d++{-| Applies a function to five signals. -}+lift5 :: (a -> b -> c -> d -> e -> f) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+                                      -> SignalGen (Signal e) -> SignalGen (Signal f)+lift5 f a b c d e = f <~ a ~~ b ~~ c ~~ d ~~ e++{-| Applies a function to six signals. -}+lift6 :: (a -> b -> c -> d -> e -> f -> g) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+                                           -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g)+lift6 f a b c d e f1 = f <~ a ~~ b ~~ c ~~ d ~~ e ~~ f1++{-| Applies a function to seven signals. -}+lift7 :: (a -> b -> c -> d -> e -> f -> g -> h) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+                                                -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g) -> SignalGen (Signal h)+lift7 f a b c d e f1 g = f <~ a ~~ b ~~ c ~~ d ~~ e ~~ f1 ~~ g++{-| Applies a function to eight signals. -}+lift8 :: (a -> b -> c -> d -> e -> f -> g -> h -> i) -> SignalGen (Signal a) -> SignalGen (Signal b) -> SignalGen (Signal c) -> SignalGen (Signal d)+                                                     -> SignalGen (Signal e) -> SignalGen (Signal f) -> SignalGen (Signal g) -> SignalGen (Signal h)+                                                     -> SignalGen (Signal i)+lift8 f a b c d e f1 g h = f <~ a ~~ b ~~ c ~~ d ~~ e ~~ f1 ~~ g ~~ h++{-| An alias for 'lift'. -}+(<~) :: (a -> b) -> SignalGen (Signal a) -> SignalGen (Signal b)+(<~) = lift++infixl 4 <~++{-| Applies a function within a signal to a signal. This is a wrapper around the builtin '<*>' operator+    that automatically binds the input signal out of the signal generator.++    > render <~ Window.dimensions ~~ Window.position+ -}+(~~) :: SignalGen (Signal (a -> b)) -> SignalGen (Signal a) -> SignalGen (Signal b)+(~~) = (<*>) . fmap (<*>)++infixl 4 ~~++{-| Creates a past-dependent signal that depends on another signal. This is a+    wrapper around the 'transfer' function that automatically binds the input+    signal out of the signal generator. This function is useful for making a render+    function that depends on some accumulated state. -}+foldp :: (a -> b -> b) -> b -> SignalGen (Signal a) -> SignalGen (Signal b)+foldp f ini = (>>= transfer ini f)++{-| Creates a signal that counts the amount of times it has been sampled. -}+count :: SignalGen (Signal Int)+count = stateful 0 (+ 1)++{-| Creates a signal that counts the amount of times an input signal has passed+    a predicate when sampled. -}+countIf :: (a -> Bool) -> SignalGen (Signal a) -> SignalGen (Signal Int)+countIf f = foldp (\v c -> c + fromEnum (f v)) 0++{-| Creates a signal of a random number. -}+random :: Random a => SignalGen (Signal a)+random = effectful randomIO++{-| Creates a signal of a random number based on the given range. -}+randomR :: Random a => (a, a) -> SignalGen (Signal a)+randomR = effectful . randomRIO