packages feed

reactive-banana-threepenny-0.7.1.3: src/Animation.hs

{-----------------------------------------------------------------------------
    reactive-banana-threepenny
    
    Example: An animated picture follows the mouse pointer.
------------------------------------------------------------------------------}
{-# LANGUAGE ScopedTypeVariables #-} -- allows "forall t. Moment t"

-- import System.Random

import Paths (getDataFile)

import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core hiding (Event)

import Reactive.Banana
import Reactive.Banana.Threepenny

{-----------------------------------------------------------------------------
    Constants
------------------------------------------------------------------------------}
height, width :: Double
height   = 500
width    = 500

dt :: Double
dt = 20 * ms where ms = 1e-3

spriteFile :: IO FilePath
spriteFile = getDataFile "banana.png"

bitmapWidth, bitmapHeight :: Double
bitmapWidth  = 128
bitmapHeight = 128

{-----------------------------------------------------------------------------
    Main
------------------------------------------------------------------------------}
main :: IO ()
main = do
    startGUI Config
        { tpPort       = 10000
        , tpCustomHTML = Nothing
        , tpStatic     = ""
        } setup

setup :: Window -> IO ()
setup window = do
    return window # set title "It's functional programming time!"

    spriteURL <- loadFile window "image/png" =<< spriteFile
    sprite    <- UI.img # set UI.src   spriteURL
    wrap      <- UI.div #. "wrap"
        # set style [("width",double2px width),("height",double2px height)
                    ,("border","solid black 1px")]
        #+ [element sprite]
    getBody window #+ [element wrap]

    timer     <- UI.timer # set UI.interval (ceiling $ dt * 1e3)

    -- event network
    let networkDescription :: forall t. Frameworks t => Moment t ()
        networkDescription = do
            etick  <- event UI.tick timer        -- frame timer
            emouse <- event UI.mousemove wrap    -- mouse events
            
            let
                -- mouse pointer position
                bmouse = stepper (vec 0 0) (readMouse <$> emouse)
            
                -- sprite velocity
                bvelocity :: Behavior t Vector
                bvelocity =
                    (\pos mouse -> speedup $ mouse `vecSub` pos `vecSub` vec 0 45)
                    <$> bposition <*> bmouse
                    where
                    speedup v = v `vecScale` (vecLength v / 20)
                
                -- sprite position
                bposition :: Behavior t Vector
                bposition = accumB (vec 0 0) $
                    (\v pos -> clipToFrame $ (v `vecScale` dt) `vecAdd` pos)
                    <$> bvelocity <@ etick
            
                clipToFrame v = vec
                        (clip 0 x (width  - bitmapWidth ))
                        (clip 0 y (height - bitmapHeight))
                    where
                    x = vecX v; y = vecY v
                    clip a x b = max a (min x b)

            -- animate the sprite
            return sprite # sink position bposition
    
    network <- compile networkDescription    
    actuate network
    UI.start timer


{-----------------------------------------------------------------------------
    2D Geometry
------------------------------------------------------------------------------}
data Vector = Vec !Double !Double deriving (Show)

vecX (Vec x _) = x
vecY (Vec _ y) = y
vec x y = Vec x y
vecScale (Vec x y) s = Vec (s*x) (s*y)
vecAdd   (Vec x1 y1) (Vec x2 y2) = Vec (x1+x2) (y1+y2)
vecSub   (Vec x1 y1) (Vec x2 y2) = Vec (x1-x2) (y1-y2)
vecLength :: Vector -> Double
vecLength (Vec x y) = sqrt (x*x + y*y)

{-----------------------------------------------------------------------------
    Threepenny stuff
------------------------------------------------------------------------------}
position :: WriteAttr Element Vector
position = mkWriteAttr $ \(Vec x y) el ->
    set' style [("position","absolute")
               ,("left", double2px x) ,("top", double2px y)] el

double2px x = show (ceiling x) ++ "px" 

readMouse :: (Int,Int) -> Vector
readMouse (x,y) = vec (fromIntegral x) (fromIntegral y)