{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RecordWildCards #-} {-| Module : Reanimate.Scene Description : Imperative animation API Copyright : Written by David Himmelstrup License : Unlicense Maintainer : lemmih@gmail.com Stability : experimental Portability : POSIX Scenes are an imperative way of defining animations. -} module Reanimate.Scene ( -- * Scenes Scene , ZIndex , sceneAnimation -- :: (forall s. Scene s a) -> Animation , play -- :: Animation -> Scene s () , fork -- :: Scene s a -> Scene s a , queryNow -- :: Scene s Time , wait -- :: Duration -> Scene s () , waitUntil -- :: Time -> Scene s () , waitOn -- :: Scene s a -> Scene s a , adjustZ -- :: (ZIndex -> ZIndex) -> Scene s a -> Scene s a , withSceneDuration -- :: Scene s () -> Scene s Duration -- * Variables , Var , newVar -- :: a -> Scene s (Var s a) , readVar -- :: Var s a -> Scene s a , writeVar -- :: Var s a -> a -> Scene s () , modifyVar -- :: Var s a -> (a -> a) -> Scene s () , tweenVar -- :: Var s a -> Duration -> (a -> Time -> a) -> Scene s () , tweenVarUnclamped -- :: Var s a -> Duration -> (a -> Time -> a) -> Scene s () , simpleVar -- :: (a -> SVG) -> a -> Scene s (Var s a) , findVar -- :: (a -> Bool) -> [Var s a] -> Scene s (Var s a) -- * Sprites , Sprite , Frame , unVar -- :: Var s a -> Frame s a , spriteT -- :: Frame s Time , spriteDuration -- :: Frame s Duration , newSprite -- :: Frame s SVG -> Scene s (Sprite s) , newSprite_ -- :: Frame s SVG -> Scene s () , newSpriteA -- :: Animation -> Scene s (Sprite s) , newSpriteA' -- :: Sync -> Animation -> Scene s (Sprite s) , newSpriteSVG -- :: SVG -> Scene s (Sprite s) , newSpriteSVG_ -- :: SVG -> Scene s () , destroySprite -- :: Sprite s -> Scene s () , applyVar -- :: Var s a -> Sprite s -> (a -> SVG -> SVG) -> Scene s () , spriteModify -- :: Sprite s -> Frame s ((SVG,ZIndex) -> (SVG, ZIndex)) -> Scene s () , spriteMap -- :: Sprite s -> (SVG -> SVG) -> Scene s () , spriteTween -- :: Sprite s -> Duration -> (Double -> SVG -> SVG) -> Scene s () , spriteVar -- :: Sprite s -> a -> (a -> SVG -> SVG) -> Scene s (Var s a) , spriteE -- :: Sprite s -> Effect -> Scene s () , spriteZ -- :: Sprite s -> ZIndex -> Scene s () , spriteScope -- :: Scene s a -> Scene s a -- * Object API , Renderable(..) , Object , ObjectData , oTranslate , oSVG , oContext , oMargin , oMarginTop , oMarginRight , oMarginBottom , oMarginLeft , oBB , oBBMinX , oBBMinY , oBBWidth , oBBHeight , oOpacity , oShown , oZIndex , oEasing , oScale , oScaleOrigin , oTopY , oBottomY , oLeftX , oRightX , oCenterXY , newObject , oValue , oModify , oModifyS , oRead , oTween , oTweenS , oTweenV , oTweenVS -- ** Graphics object methods , oShow , oHide , oFadeIn , oFadeOut , oGrow , oShrink , oTransform -- ** Pre-defined objects , Circle(..) , circleRadius , Rectangle(..) , rectWidth , rectHeight , Morph(..) , morphDelta , morphSrc , morphDst , Camera(..) , cameraAttach , cameraFocus , cameraSetZoom , cameraZoom , cameraSetPan , cameraPan -- * ST internals , liftST , asAnimation -- :: (forall s. Scene s a) -> Scene s Animation , 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) -- | The ZIndex property specifies the stack order of sprites and animations. Elements -- with a higher ZIndex will be drawn on top of elements with a lower index. type ZIndex = Int -- (seq duration, par duration) -- [(Time, Animation, ZIndex)] -- Map Time [(Animation, ZIndex)] 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 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 ] ) ) -- | Execute actions in a scene without advancing the clock. Note that scenes do not end before -- all forked actions have completed. -- -- Example: -- -- > do fork $ play drawBox -- > play drawCircle -- -- <> 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 an animation once and then remove it. This advances the clock by the duration of the -- animation. -- -- Example: -- -- > do play drawBox -- > play drawCircle -- -- <> play :: Animation -> Scene s () play ani = newSpriteA ani >>= destroySprite -- | Query the current clock timestamp. -- -- Example: -- -- > do now <- play drawCircle *> queryNow -- > play $ staticFrame 1 $ scale 2 $ withStrokeWidth 0.05 $ -- > mkText $ "Now=" <> T.pack (show now) -- -- <> queryNow :: Scene s Time queryNow = M $ \t -> return (t, 0, 0, []) -- | Advance the clock by a given number of seconds. -- -- Example: -- -- > do fork $ play drawBox -- > wait 1 -- > play drawCircle -- -- <> wait :: Duration -> Scene s () wait d = M $ \_ -> return ((), d, 0, []) -- | Wait until the clock is equal to the given timestamp. waitUntil :: Time -> Scene s () waitUntil tNew = do now <- queryNow wait (max 0 (tNew - now)) -- | Wait until all forked and sequential animations have finished. -- -- Example: -- -- > do waitOn $ fork $ play drawBox -- > play drawCircle -- -- <> 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) -- | Change the ZIndex of a scene. 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) -- | Query the duration of a scene. 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]) -- | Time dependent variable. newtype Var s a = Var (STRef s (Time -> a)) -- | Create a new variable with a default value. -- Variables always have a defined value even if they are read at a timestamp that is -- earlier than when the variable was created. For example: -- -- > do v <- fork (wait 10 >> newVar 0) -- Create a variable at timestamp '10'. -- > readVar v -- Read the variable at timestamp '0'. -- > -- The value of the variable will be '0'. newVar :: a -> Scene s (Var s a) newVar def = Var <$> liftST (newSTRef (const def)) -- | Read the value of a variable at the current timestamp. readVar :: Var s a -> Scene s a readVar (Var ref) = liftST (readSTRef ref) <*> queryNow -- | Write the value of a variable at the current timestamp. -- -- Example: -- -- > do v <- newVar 0 -- > newSprite $ mkCircle <$> unVar v -- > writeVar v 1; wait 1 -- > writeVar v 2; wait 1 -- > writeVar v 3; wait 1 -- -- <> writeVar :: Var s a -> a -> Scene s () writeVar var val = modifyVar var (const val) -- | Modify the value of a variable at the current timestamp and all future timestamps. 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) -- | Modify a variable between @now@ and @now+duration@. -- Note: The modification function is invoked for past timestamps (with a time value of 0) and -- for timestamps after @now+duration@ (with a time value of 1). See 'tweenVarUnclamped'. 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 -- | Modify a variable between @now@ and @now+duration@. -- Note: The modification function is invoked for past timestamps (with a negative time value) and -- for timestamps after @now+duration@ (with a time value greater than 1). 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 -- | Create and render a variable. The rendering will be born at the current timestamp -- and will persist until the end of the scene. -- -- Example: -- -- > do var <- simpleVar mkCircle 0 -- > tweenVar var 2 $ \val -> fromToS val (screenHeight/2) -- -- <> simpleVar :: (a -> SVG) -> a -> Scene s (Var s a) simpleVar render def = do v <- newVar def _ <- newSprite $ render <$> unVar v return v -- | Helper function for filtering variables. 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 -- | Sprites are animations with a given time of birth as well as a time of death. -- They can be controlled using variables, tweening, and effects. data Sprite s = Sprite Time (STRef s (Duration, ST s (Duration -> Time -> SVG -> (SVG, ZIndex)))) -- | Sprite frame generator. Generates frames over time in a stateful environment. 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) -- | Dereference a variable as a Sprite frame. -- -- Example: -- -- > do v <- newVar 0 -- > newSprite $ mkCircle <$> unVar v -- > tweenVar v 1 $ \val -> fromToS val 3 -- > tweenVar v 1 $ \val -> fromToS val 0 -- -- <> unVar :: Var s a -> Frame s a unVar (Var ref) = Frame $ do fn <- readSTRef ref return $ \real_t _d _t -> fn real_t -- | Dereference seconds since sprite birth. spriteT :: Frame s Time spriteT = Frame $ return (\_real_t _d t -> t) -- | Dereference duration of the current sprite. spriteDuration :: Frame s Duration spriteDuration = Frame $ return (\_real_t d _t -> d) -- | Create new sprite defined by a frame generator. Unless otherwise specified using -- 'destroySprite', the sprite will die at the end of the scene. -- -- Example: -- -- > do newSprite $ mkCircle <$> spriteT -- Circle sprite where radius=time. -- > wait 2 -- -- <> 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 -- Sprite is live [now;duration[ -- If we're at the end of a scene, sprites -- are live: [now;duration] -- This behavior is difficult to get right. See the 'bug_*' examples for -- automated tests. 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 -- | Create new sprite defined by a frame generator. The sprite will die at -- the end of the scene. newSprite_ :: Frame s SVG -> Scene s () newSprite_ = void . newSprite -- | Create a new sprite from an animation. This advances the clock by the -- duration of the animation. Unless otherwise specified using -- 'destroySprite', the sprite will die at the end of the scene. -- -- Note: If the scene doesn't end immediately after the duration of the -- animation, the animation will be stretched to match the lifetime of the -- sprite. See 'newSpriteA'' and 'play'. -- -- Example: -- -- > do fork $ newSpriteA drawCircle -- > play drawBox -- > play $ reverseA drawBox -- -- <> newSpriteA :: Animation -> Scene s (Sprite s) newSpriteA = newSpriteA' SyncStretch -- | Create a new sprite from an animation and specify the synchronization policy. This advances -- the clock by the duration of the animation. -- -- Example: -- -- > do fork $ newSpriteA' SyncFreeze drawCircle -- > play drawBox -- > play $ reverseA drawBox -- -- <> newSpriteA' :: Sync -> Animation -> Scene s (Sprite s) newSpriteA' sync animation = newSprite (getAnimationFrame sync animation <$> spriteT <*> spriteDuration) <* wait (duration animation) -- | Create a sprite from a static SVG image. -- -- Example: -- -- > do newSpriteSVG $ mkBackground "lightblue" -- > play drawCircle -- -- <> newSpriteSVG :: SVG -> Scene s (Sprite s) newSpriteSVG = newSprite . pure -- | Create a permanent sprite from a static SVG image. Same as `newSpriteSVG` -- but the sprite isn't returned and thus cannot be destroyed. newSpriteSVG_ :: SVG -> Scene s () newSpriteSVG_ = void . newSpriteSVG -- | Change the rendering of a sprite using data from a variable. If data from several variables -- is needed, use a frame generator instead. -- -- Example: -- -- > do s <- fork $ newSpriteA drawBox -- > v <- newVar 0 -- > applyVar v s rotate -- > tweenVar v 2 $ \val -> fromToS val 90 -- -- <> 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) -- | Destroy a sprite, preventing it from being rendered in the future of the scene. -- If 'destroySprite' is invoked multiple times, the earliest time-of-death is used. -- -- Example: -- -- > do s <- newSpriteSVG $ withFillOpacity 1 $ mkCircle 1 -- > fork $ wait 1 >> destroySprite s -- > play drawBox -- -- <> 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) -- | Low-level frame modifier. 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 ) -- | Map the SVG output of a sprite. -- -- Example: -- -- > do s <- fork $ newSpriteA drawCircle -- > wait 1 -- > spriteMap s flipYAxis -- -- <> 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) -- | Modify the output of a sprite between @now@ and @now+duration@. -- -- Example: -- -- > do s <- fork $ newSpriteA drawCircle -- > spriteTween s 1 $ \val -> translate (screenWidth*0.3*val) 0 -- -- <> 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 -- | Create a new variable and apply it to a sprite. -- -- Example: -- -- > do s <- fork $ newSpriteA drawBox -- > v <- spriteVar s 0 rotate -- > tweenVar v 2 $ \val -> fromToS val 90 -- -- <> 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 -- | Apply an effect to a sprite. -- -- Example: -- -- > do s <- fork $ newSpriteA drawCircle -- > spriteE s $ overBeginning 1 fadeInE -- > spriteE s $ overEnding 0.5 fadeOutE -- -- <> 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) ) -- | Set new ZIndex of a sprite. -- -- Example: -- -- > do s1 <- newSpriteSVG $ withFillOpacity 1 $ withFillColor "blue" $ mkCircle 3 -- > newSpriteSVG $ withFillOpacity 1 $ withFillColor "red" $ mkRect 8 3 -- > wait 1 -- > spriteZ s1 1 -- > wait 1 -- -- <> 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) ) -- Destroy all local sprites at the end of a scene. 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 scene = do now <- queryNow return $ dropA now (sceneAnimation (wait now >> scene)) 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 ------------------------------------------------------- -- Objects 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) -- ^ Top, right, bottom, left , _oBB :: (Double,Double,Double,Double) , _oOpacity :: Double , _oShown :: Bool , _oZIndex :: Int , _oEasing :: Signal , _oScale :: Double , _oScaleOrigin :: (Double, Double) } -- Basic lenses 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 } -- Smart lenses 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 = do v <- readVar (objectData o) return $ view l v oTween :: Object s a -> Duration -> (Double -> ObjectData a -> ObjectData a) -> Scene s () oTween o d fn = do -- Read 'easing' var here instead of taking it from 'v'. -- This allows different easing functions even at the same timestamp. ease <- oRead o oEasing tweenVar (objectData o) d (\v t -> fn (ease t) v) -- tweenVar ref d (\v t -> fn t v) -- oTweenS :: Object s a -> Duration -> (Double -> ObjectData a -> ObjectData a) -> Scene s () 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)) 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 return Object { objectSprite = sprite , objectData = ref } where svg = toSVG val newtype Circle = Circle {_circleRadius :: Double} instance Renderable Circle where toSVG (Circle r) = mkCircle r data Rectangle = Rectangle { _rectWidth :: Double, _rectHeight :: Double } instance Renderable Rectangle where toSVG (Rectangle w h) = mkRect w h data Morph = Morph { _morphDelta :: Double, _morphSrc :: SVG, _morphDst :: SVG } 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 makeLenses ''Circle makeLenses ''Rectangle makeLenses ''Morph 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 -- FIXME: Also transform attributes: 'opacity', 'scale', 'scaleOrigin'. 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)