{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
module Reanimate.Scene
(
Scene
, ZIndex
, scene
, sceneAnimation
, play
, fork
, queryNow
, wait
, waitUntil
, waitOn
, adjustZ
, withSceneDuration
, Var
, newVar
, readVar
, writeVar
, modifyVar
, tweenVar
, tweenVarUnclamped
, simpleVar
, findVar
, Sprite
, Frame
, unVar
, spriteT
, spriteDuration
, newSprite
, newSprite_
, newSpriteA
, newSpriteA'
, newSpriteSVG
, newSpriteSVG_
, destroySprite
, applyVar
, spriteModify
, spriteMap
, spriteTween
, spriteVar
, spriteE
, spriteZ
, spriteScope
, Object
, ObjectData
, oNew
, newObject
, oModify
, oModifyS
, oRead
, oTween
, oTweenS
, oTweenV
, oTweenVS
, Renderable(..)
, oTranslate
, oSVG
, oContext
, oMargin
, oMarginTop
, oMarginRight
, oMarginBottom
, oMarginLeft
, oBB
, oBBMinX
, oBBMinY
, oBBWidth
, oBBHeight
, oOpacity
, oShown
, oZIndex
, oEasing
, oScale
, oScaleOrigin
, oTopY
, oBottomY
, oLeftX
, oRightX
, oCenterXY
, oValue
, oShow
, oHide
, oFadeIn
, oFadeOut
, oGrow
, oShrink
, oTransform
, Circle(..)
, circleRadius
, Rectangle(..)
, rectWidth
, rectHeight
, Morph(..)
, morphDelta
, morphSrc
, morphDst
, Camera(..)
, cameraAttach
, cameraFocus
, cameraSetZoom
, cameraZoom
, cameraSetPan
, cameraPan
, liftST
, transitionO
, evalScene
)
where
import Control.Lens
import Control.Monad (void)
import Control.Monad.Fix
import Control.Monad.ST
import Control.Monad.State (execState, State)
import Data.List
import Data.STRef
import Graphics.SvgTree (Tree (None))
import Reanimate.Animation
import Reanimate.Ease (Signal, curveS, fromToS)
import Reanimate.Effect
import Reanimate.Svg.Constructors
import Reanimate.Svg.BoundingBox
import Reanimate.Transition
import Reanimate.Morph.Common (morph)
import Reanimate.Morph.Linear (linear)
type ZIndex = Int
type Gen s = ST s (Duration -> Time -> (SVG, ZIndex))
newtype Scene s a = M { unM :: Time -> ST s (a, Duration, Duration, [Gen s]) }
instance Functor (Scene s) where
fmap f action = M $ \t -> do
(a, d1, d2, gens) <- unM action t
return (f a, d1, d2, gens)
instance Applicative (Scene s) where
pure a = M $ \_ -> return (a, 0, 0, [])
f <*> g = M $ \t -> do
(f', s1, p1, gen1) <- unM f t
(g', s2, p2, gen2) <- unM g (t + s1)
return (f' g', s1 + s2, max p1 (s1 + p2), gen1 ++ gen2)
instance Monad (Scene s) where
return = pure
f >>= g = M $ \t -> do
(a, s1, p1, gen1) <- unM f t
(b, s2, p2, gen2) <- unM (g a) (t + s1)
return (b, s1 + s2, max p1 (s1 + p2), gen1 ++ gen2)
instance MonadFix (Scene s) where
mfix fn = M $ \t -> mfix (\v -> let (a, _s, _p, _gens) = v in unM (fn a) t)
liftST :: ST s a -> Scene s a
liftST action = M $ \_ -> action >>= \a -> return (a, 0, 0, [])
evalScene :: (forall s . Scene s a) -> a
evalScene action = runST $ do
(val, _, _ , _) <- unM action 0
return val
scene :: (forall s . Scene s a) -> Animation
scene = sceneAnimation
sceneAnimation :: (forall s . Scene s a) -> Animation
sceneAnimation action = runST
(do
(_, s, p, gens) <- unM action 0
let dur = max s p
genFns <- sequence gens
return $ mkAnimation
dur
(\t -> mkGroup $ map fst $ sortOn
snd
[ spriteRender dur (t * dur) | spriteRender <- genFns ]
)
)
fork :: Scene s a -> Scene s a
fork (M action) = M $ \t -> do
(a, s, p, gens) <- action t
return (a, 0, max s p, gens)
play :: Animation -> Scene s ()
play ani = newSpriteA ani >>= destroySprite
queryNow :: Scene s Time
queryNow = M $ \t -> return (t, 0, 0, [])
wait :: Duration -> Scene s ()
wait d = M $ \_ -> return ((), d, 0, [])
waitUntil :: Time -> Scene s ()
waitUntil tNew = do
now <- queryNow
wait (max 0 (tNew - now))
waitOn :: Scene s a -> Scene s a
waitOn (M action) = M $ \t -> do
(a, s, p, gens) <- action t
return (a, max s p, 0, gens)
adjustZ :: (ZIndex -> ZIndex) -> Scene s a -> Scene s a
adjustZ fn (M action) = M $ \t -> do
(a, s, p, gens) <- action t
return (a, s, p, map genFn gens)
where
genFn gen = do
frameGen <- gen
return $ \d t -> let (svg, z) = frameGen d t in (svg, fn z)
withSceneDuration :: Scene s () -> Scene s Duration
withSceneDuration s = do
t1 <- queryNow
s
t2 <- queryNow
return (t2 - t1)
addGen :: Gen s -> Scene s ()
addGen gen = M $ \_ -> return ((), 0, 0, [gen])
newtype Var s a = Var (STRef s (Time -> a))
newVar :: a -> Scene s (Var s a)
newVar def = Var <$> liftST (newSTRef (const def))
readVar :: Var s a -> Scene s a
readVar (Var ref) = liftST (readSTRef ref) <*> queryNow
writeVar :: Var s a -> a -> Scene s ()
writeVar var val = modifyVar var (const val)
modifyVar :: Var s a -> (a -> a) -> Scene s ()
modifyVar (Var ref) fn = do
now <- queryNow
liftST $ modifySTRef ref $ \prev t -> if t < now then prev t else fn (prev t)
tweenVar :: Var s a -> Duration -> (a -> Time -> a) -> Scene s ()
tweenVar (Var ref) dur fn = do
now <- queryNow
liftST $ modifySTRef ref $ \prev t ->
if t < now
then prev t
else fn (prev t) (max 0 (min dur $ t - now) / dur)
wait dur
tweenVarUnclamped :: Var s a -> Duration -> (a -> Time -> a) -> Scene s ()
tweenVarUnclamped (Var ref) dur fn = do
now <- queryNow
liftST $ modifySTRef ref $ \prev t -> fn (prev t) ((t - now) / dur)
wait dur
simpleVar :: (a -> SVG) -> a -> Scene s (Var s a)
simpleVar render def = do
v <- newVar def
_ <- newSprite $ render <$> unVar v
return v
findVar :: (a -> Bool) -> [Var s a] -> Scene s (Var s a)
findVar _cond [] = error "Variable not found."
findVar cond (v : vs) = do
val <- readVar v
if cond val then return v else findVar cond vs
data Sprite s = Sprite Time (STRef s (Duration, ST s (Duration -> Time -> SVG -> (SVG, ZIndex))))
newtype Frame s a = Frame { unFrame :: ST s (Time -> Duration -> Time -> a) }
instance Functor (Frame s) where
fmap fn (Frame gen) = Frame $ do
m <- gen
return (\real_t d t -> fn $ m real_t d t)
instance Applicative (Frame s) where
pure v = Frame $ return (\_ _ _ -> v)
Frame f <*> Frame g = Frame $ do
m1 <- f
m2 <- g
return $ \real_t d t -> m1 real_t d t (m2 real_t d t)
unVar :: Var s a -> Frame s a
unVar (Var ref) = Frame $ do
fn <- readSTRef ref
return $ \real_t _d _t -> fn real_t
spriteT :: Frame s Time
spriteT = Frame $ return (\_real_t _d t -> t)
spriteDuration :: Frame s Duration
spriteDuration = Frame $ return (\_real_t d _t -> d)
newSprite :: Frame s SVG -> Scene s (Sprite s)
newSprite render = do
now <- queryNow
ref <- liftST $ newSTRef (-1, return $ \_d _t svg -> (svg, 0))
addGen $ do
fn <- unFrame render
(spriteDur, spriteEffectGen) <- readSTRef ref
spriteEffect <- spriteEffectGen
return $ \d absT ->
let relD = (if spriteDur < 0 then d else spriteDur) - now
relT = absT - now
inTimeSlice = relT >= 0 && relT < relD
isLastFrame = d==absT && relT == relD
in if inTimeSlice || isLastFrame
then spriteEffect relD relT (fn absT relD relT)
else (None, 0)
return $ Sprite now ref
newSprite_ :: Frame s SVG -> Scene s ()
newSprite_ = void . newSprite
newSpriteA :: Animation -> Scene s (Sprite s)
newSpriteA = newSpriteA' SyncStretch
newSpriteA' :: Sync -> Animation -> Scene s (Sprite s)
newSpriteA' sync animation =
newSprite (getAnimationFrame sync animation <$> spriteT <*> spriteDuration)
<* wait (duration animation)
newSpriteSVG :: SVG -> Scene s (Sprite s)
newSpriteSVG = newSprite . pure
newSpriteSVG_ :: SVG -> Scene s ()
newSpriteSVG_ = void . newSpriteSVG
applyVar :: Var s a -> Sprite s -> (a -> SVG -> SVG) -> Scene s ()
applyVar var sprite fn = spriteModify sprite $ do
varFn <- unVar var
return $ \(svg, zindex) -> (fn varFn svg, zindex)
destroySprite :: Sprite s -> Scene s ()
destroySprite (Sprite _ ref) = do
now <- queryNow
liftST $ modifySTRef ref $ \(ttl, render) ->
(if ttl < 0 then now else min ttl now, render)
spriteModify :: Sprite s -> Frame s ((SVG, ZIndex) -> (SVG, ZIndex)) -> Scene s ()
spriteModify (Sprite born ref) modFn = liftST $ modifySTRef ref $ \(ttl, renderGen) ->
( ttl
, do
render <- renderGen
modRender <- unFrame modFn
return $ \relD relT ->
let absT = relT + born in modRender absT relD relT . render relD relT
)
spriteMap :: Sprite s -> (SVG -> SVG) -> Scene s ()
spriteMap sprite@(Sprite born _) fn = do
now <- queryNow
let tDelta = now - born
spriteModify sprite $ do
t <- spriteT
return $ \(svg, zindex) -> (if (t - tDelta) < 0 then svg else fn svg, zindex)
spriteTween :: Sprite s -> Duration -> (Double -> SVG -> SVG) -> Scene s ()
spriteTween sprite@(Sprite born _) dur fn = do
now <- queryNow
let tDelta = now - born
spriteModify sprite $ do
t <- spriteT
return $ \(svg, zindex) -> (fn (clamp 0 1 $ (t - tDelta) / dur) svg, zindex)
wait dur
where
clamp a b v | v < a = a
| v > b = b
| otherwise = v
spriteVar :: Sprite s -> a -> (a -> SVG -> SVG) -> Scene s (Var s a)
spriteVar sprite def fn = do
v <- newVar def
applyVar v sprite fn
return v
spriteE :: Sprite s -> Effect -> Scene s ()
spriteE (Sprite born ref) effect = do
now <- queryNow
liftST $ modifySTRef ref $ \(ttl, renderGen) ->
( ttl
, do
render <- renderGen
return $ \d t svg ->
let (svg', z) = render d t svg
in (delayE (max 0 $ now - born) effect d t svg', z)
)
spriteZ :: Sprite s -> ZIndex -> Scene s ()
spriteZ (Sprite born ref) zindex = do
now <- queryNow
liftST $ modifySTRef ref $ \(ttl, renderGen) ->
( ttl
, do
render <- renderGen
return $ \d t svg ->
let (svg', z) = render d t svg in (svg', if t < now - born then z else zindex)
)
spriteScope :: Scene s a -> Scene s a
spriteScope (M action) = M $ \t -> do
(a, s, p, gens) <- action t
return (a, s, p, map (genFn (t+max s p)) gens)
where
genFn maxT gen = do
frameGen <- gen
return $ \_ t ->
if t < maxT
then frameGen maxT t
else (None, 0)
asAnimation :: (forall s'. Scene s' a) -> Scene s Animation
asAnimation s = do
now <- queryNow
return $ dropA now (sceneAnimation (wait now >> s))
transitionO :: Transition -> Double -> (forall s'. Scene s' a) -> (forall s'. Scene s' b) -> Scene s ()
transitionO t o a b = do
aA <- asAnimation a
bA <- fork $ do
wait (duration aA - o)
asAnimation b
play $ overlapT o t aA bA
class Renderable a where
toSVG :: a -> SVG
instance Renderable Tree where
toSVG = id
data Object s a = Object
{ objectSprite :: Sprite s
, objectData :: Var s (ObjectData a)
}
data ObjectData a = ObjectData
{ _oTranslate :: (Double, Double)
, _oValueRef :: a
, _oSVG :: SVG
, _oContext :: SVG -> SVG
, _oMargin :: (Double, Double, Double, Double)
, _oBB :: (Double,Double,Double,Double)
, _oOpacity :: Double
, _oShown :: Bool
, _oZIndex :: Int
, _oEasing :: Signal
, _oScale :: Double
, _oScaleOrigin :: (Double, Double)
}
oTranslate :: Lens' (ObjectData a) (Double, Double)
oTranslate = lens _oTranslate $ \obj val -> obj { _oTranslate = val }
oSVG :: Getter (ObjectData a) SVG
oSVG = to _oSVG
oContext :: Lens' (ObjectData a) (SVG -> SVG)
oContext = lens _oContext $ \obj val -> obj { _oContext = val }
oMargin :: Lens' (ObjectData a) (Double, Double, Double, Double)
oMargin = lens _oMargin $ \obj val -> obj { _oMargin = val }
oBB :: Getter (ObjectData a) (Double, Double, Double, Double)
oBB = to _oBB
oOpacity :: Lens' (ObjectData a) Double
oOpacity = lens _oOpacity $ \obj val -> obj { _oOpacity = val }
oShown :: Lens' (ObjectData a) Bool
oShown = lens _oShown $ \obj val -> obj { _oShown = val }
oZIndex :: Lens' (ObjectData a) Int
oZIndex = lens _oZIndex $ \obj val -> obj { _oZIndex = val }
oEasing :: Lens' (ObjectData a) Signal
oEasing = lens _oEasing $ \obj val -> obj { _oEasing = val }
oScale :: Lens' (ObjectData a) Double
oScale = lens _oScale $ \obj val -> obj { _oScale = val }
oScaleOrigin :: Lens' (ObjectData a) (Double, Double)
oScaleOrigin = lens _oScaleOrigin $ \obj val -> obj { _oScaleOrigin = val }
oValue :: Renderable a => Lens' (ObjectData a) a
oValue = lens _oValueRef $ \obj newVal ->
let svg = toSVG newVal
in obj
{ _oValueRef = newVal
, _oSVG = svg
, _oBB = boundingBox svg }
oTopY :: Lens' (ObjectData a) Double
oTopY = lens getter setter
where
getter obj =
let top = obj ^. oMarginTop
miny = obj ^. oBBMinY
h = obj ^. oBBHeight
dy = obj ^. oTranslate . _2
in dy+miny+h+top
setter obj val =
obj & (oTranslate . _2) +~ val-getter obj
oBottomY :: Lens' (ObjectData a) Double
oBottomY = lens getter setter
where
getter obj =
let bot = obj ^. oMarginBottom
miny = obj ^. oBBMinY
dy = obj ^. oTranslate . _2
in dy+miny-bot
setter obj val =
obj & (oTranslate . _2) +~ val-getter obj
oLeftX :: Lens' (ObjectData a) Double
oLeftX = lens getter setter
where
getter obj =
let left = obj ^. oMarginLeft
minx = obj ^. oBBMinX
dx = obj ^. oTranslate . _1
in dx+minx-left
setter obj val =
obj & (oTranslate . _1) +~ val-getter obj
oRightX :: Lens' (ObjectData a) Double
oRightX = lens getter setter
where
getter obj =
let right = obj ^. oMarginRight
minx = obj ^. oBBMinX
w = obj ^. oBBWidth
dx = obj ^. oTranslate . _1
in dx+minx+w+right
setter obj val =
obj & (oTranslate . _1) +~ val-getter obj
oCenterXY :: Lens' (ObjectData a) (Double, Double)
oCenterXY = lens getter setter
where
getter obj =
let minx = obj ^. oBBMinX
miny = obj ^. oBBMinY
w = obj ^. oBBWidth
h = obj ^. oBBHeight
(dx,dy) = obj ^. oTranslate
in (dx+minx+w/2, dy+miny+h/2)
setter obj (dx, dy) =
let (x,y) = getter obj in
obj & (oTranslate . _1) +~ dx-x
& (oTranslate . _2) +~ dy-y
oMarginTop :: Lens' (ObjectData a) Double
oMarginTop = oMargin . _1
oMarginRight :: Lens' (ObjectData a) Double
oMarginRight = oMargin . _2
oMarginBottom :: Lens' (ObjectData a) Double
oMarginBottom = oMargin . _3
oMarginLeft :: Lens' (ObjectData a) Double
oMarginLeft = oMargin . _4
oBBMinX :: Getter (ObjectData a) Double
oBBMinX = oBB . _1
oBBMinY :: Getter (ObjectData a) Double
oBBMinY = oBB . _2
oBBWidth :: Getter (ObjectData a) Double
oBBWidth = oBB . _3
oBBHeight :: Getter (ObjectData a) Double
oBBHeight = oBB . _4
oModify :: Object s a -> (ObjectData a -> ObjectData a) -> Scene s ()
oModify o fn = modifyVar (objectData o) fn
oModifyS :: Object s a -> (State (ObjectData a) b) -> Scene s ()
oModifyS o fn = oModify o (execState fn)
oRead :: Object s a -> Getting b (ObjectData a) b -> Scene s b
oRead o l = view l <$> readVar (objectData o)
oTween :: Object s a -> Duration -> (Double -> ObjectData a -> ObjectData a) -> Scene s ()
oTween o d fn = do
ease <- oRead o oEasing
tweenVar (objectData o) d (\v t -> fn (ease t) v)
oTweenS :: Object s a -> Duration -> (Double -> State (ObjectData a) b) -> Scene s ()
oTweenS o d fn = oTween o d (\t -> execState (fn t))
oTweenV :: Renderable a => Object s a -> Duration -> (Double -> a -> a) -> Scene s ()
oTweenV o d fn = oTween o d (\t -> oValue %~ fn t)
oTweenVS :: Renderable a => Object s a -> Duration -> (Double -> State a b) -> Scene s ()
oTweenVS o d fn = oTween o d (\t -> oValue %~ execState (fn t))
oNew :: Renderable a => a -> Scene s (Object s a)
oNew = newObject
newObject :: Renderable a => a -> Scene s (Object s a)
newObject val = do
ref <- newVar ObjectData
{ _oTranslate = (0,0)
, _oValueRef = val
, _oSVG = svg
, _oContext = id
, _oMargin = (0.5,0.5,0.5,0.5)
, _oBB = boundingBox svg
, _oOpacity = 1
, _oShown = False
, _oZIndex = 1
, _oEasing = curveS 2
, _oScale = 1
, _oScaleOrigin = (0,0)
}
sprite <- newSprite $ do
~ObjectData{..} <- unVar ref
pure $
if _oShown
then
uncurry translate _oTranslate $
uncurry translate (_oScaleOrigin & both %~ negate) $
scale _oScale $
uncurry translate _oScaleOrigin $
withGroupOpacity _oOpacity $
_oContext _oSVG
else None
spriteModify sprite $ do
~ObjectData{_oZIndex=z} <- unVar ref
pure $ \(img,_) -> (img,z)
return Object
{ objectSprite = sprite
, objectData = ref }
where
svg = toSVG val
oShow :: Object s a -> Scene s ()
oShow o = oModify o $ oShown .~ True
oHide :: Object s a -> Scene s ()
oHide o = oModify o $ oShown .~ False
oFadeIn :: Object s a -> Duration -> Scene s ()
oFadeIn o d = do
oModify o $
oShown .~ True
oTweenS o d $ \t ->
oOpacity *= t
oFadeOut :: Object s a -> Duration -> Scene s ()
oFadeOut o d = do
oModify o $
oShown .~ True
oTweenS o d $ \t ->
oOpacity *= 1-t
oGrow :: Object s a -> Duration -> Scene s ()
oGrow o d = do
oModify o $
oShown .~ True
oTweenS o d $ \t ->
oScale *= t
oShrink :: Object s a -> Duration -> Scene s ()
oShrink o d =
oTweenS o d $ \t ->
oScale *= 1-t
oTransform :: Object s a -> Object s b -> Duration -> Scene s ()
oTransform src dst d = do
srcSvg <- oRead src oSVG
srcCtx <- oRead src oContext
srcEase <- oRead src oEasing
srcLoc <- oRead src oTranslate
oModify src $ oShown .~ False
dstSvg <- oRead dst oSVG
dstCtx <- oRead dst oContext
dstLoc <- oRead dst oTranslate
m <- newObject $ Morph 0 (srcCtx srcSvg) (dstCtx dstSvg)
oModifyS m $ do
oShown .= True
oEasing .= srcEase
oTranslate .= srcLoc
fork $ oTween m d $ \t -> oTranslate %~ moveTo t dstLoc
oTweenV m d $ \t -> morphDelta .~ t
oModify m $ oShown .~ False
oModify dst $ oShown .~ True
where
moveTo t (dstX, dstY) (srcX, srcY) =
(fromToS srcX dstX t, fromToS srcY dstY t)
newtype Circle = Circle {_circleRadius :: Double}
circleRadius :: Lens' Circle Double
circleRadius = iso _circleRadius Circle
instance Renderable Circle where
toSVG (Circle r) = mkCircle r
data Rectangle = Rectangle { _rectWidth :: Double, _rectHeight :: Double }
rectWidth :: Lens' Rectangle Double
rectWidth = lens _rectWidth $ \obj val -> obj{_rectWidth=val}
rectHeight :: Lens' Rectangle Double
rectHeight = lens _rectHeight $ \obj val -> obj{_rectHeight=val}
instance Renderable Rectangle where
toSVG (Rectangle w h) = mkRect w h
data Morph = Morph { _morphDelta :: Double, _morphSrc :: SVG, _morphDst :: SVG }
morphDelta :: Lens' Morph Double
morphDelta = lens _morphDelta $ \obj val -> obj{_morphDelta = val}
morphSrc :: Lens' Morph SVG
morphSrc = lens _morphSrc $ \obj val -> obj{_morphSrc = val}
morphDst :: Lens' Morph SVG
morphDst = lens _morphDst $ \obj val -> obj{_morphDst = val}
instance Renderable Morph where
toSVG (Morph t src dst) = morph linear src dst t
data Camera = Camera
instance Renderable Camera where
toSVG Camera = None
cameraAttach :: Object s Camera -> Object s a -> Scene s ()
cameraAttach cam obj =
spriteModify (objectSprite obj) $ do
camData <- unVar (objectData cam)
return $ \(svg,zindex) ->
let (x,y) = camData^.oTranslate
ctx =
translate (-x) (-y) .
uncurry translate (camData^.oScaleOrigin) .
scale (camData^.oScale) .
uncurry translate (camData^.oScaleOrigin & both %~ negate)
in (ctx svg, zindex)
cameraFocus :: Object s Camera -> (Double, Double) -> Scene s ()
cameraFocus cam (x,y) = do
(ox, oy) <- oRead cam oScaleOrigin
(tx, ty) <- oRead cam oTranslate
s <- oRead cam oScale
let newLocation = (x-((x-ox)*s+ox-tx), y-((y-oy)*s+oy-ty))
oModifyS cam $ do
oTranslate .= newLocation
oScaleOrigin .= (x,y)
cameraSetZoom :: Object s Camera -> Double -> Scene s ()
cameraSetZoom cam s =
oModifyS cam $
oScale .= s
cameraZoom :: Object s Camera -> Duration -> Double -> Scene s ()
cameraZoom cam d s =
oTweenS cam d $ \t ->
oScale %= \v -> fromToS v s t
cameraSetPan :: Object s Camera -> (Double, Double) -> Scene s ()
cameraSetPan cam location =
oModifyS cam $ do
oTranslate .= location
cameraPan :: Object s Camera -> Duration -> (Double, Double) -> Scene s ()
cameraPan cam d (x,y) =
oTweenS cam d $ \t -> do
oTranslate._1 %= \v -> fromToS v x t
oTranslate._2 %= \v -> fromToS v y t