never executed always true always false
    1 module Reanimate.Animation
    2   ( Duration
    3   , Time
    4   , SVG
    5   , Animation
    6   -- * Creating animations
    7   , mkAnimation
    8   , animate
    9   , staticFrame
   10   , pause
   11   -- * Querying animations
   12   , duration
   13   , frameAt
   14   -- * Composing animations
   15   , seqA
   16   , andThen
   17   , parA
   18   , parLoopA
   19   , parDropA
   20   -- * Modifying animations
   21   , setDuration
   22   , adjustDuration
   23   , mapA
   24   , takeA
   25   , dropA
   26   , lastA
   27   , pauseAtEnd
   28   , pauseAtBeginning
   29   , pauseAround
   30   , pauseUntil
   31   , repeatA
   32   , reverseA
   33   , playThenReverseA
   34   , signalA
   35   , freezeAtPercentage
   36   , addStatic
   37   -- * Misc
   38   , getAnimationFrame
   39   , Sync(..)
   40   -- * Rendering
   41   , renderTree
   42   , renderSvg
   43   ) where
   44 
   45 import           Control.Arrow              ()
   46 import           Data.Fixed                 (mod')
   47 import           Graphics.SvgTree           (Alignment (..), Document (..),
   48                                              Number (..),
   49                                              PreserveAspectRatio (..),
   50                                              Tree (..), xmlOfTree)
   51 import           Graphics.SvgTree.Printer
   52 import           Reanimate.Constants
   53 import           Reanimate.Ease
   54 import           Reanimate.Svg.Constructors
   55 import           Text.XML.Light.Output
   56 
   57 -- | Duration of an animation or effect. Usually measured in seconds.
   58 type Duration = Double
   59 -- | Time signal. Goes from 0 to 1, inclusive.
   60 type Time = Double
   61 
   62 -- | SVG node.
   63 type SVG = Tree
   64 
   65 -- | Animations are SVGs over a finite time.
   66 data Animation = Animation Duration (Time -> SVG)
   67 
   68 -- | Construct an animation with a given duration.
   69 mkAnimation :: Duration -> (Time -> SVG) -> Animation
   70 mkAnimation = Animation
   71 
   72 -- | Construct an animation with a duration of @1@.
   73 animate :: (Time -> SVG) -> Animation
   74 animate = Animation 1
   75 
   76 -- | Create an animation with provided @duration@, which consists of stationary frame displayed for its entire duration.
   77 staticFrame :: Duration -> SVG -> Animation
   78 staticFrame d svg = Animation d (const svg)
   79 
   80 -- | Query the duration of an animation.
   81 duration :: Animation -> Duration
   82 duration (Animation d _) = d
   83 
   84 -- | Play animations in sequence. The @lhs@ animation is removed after it has
   85 --   completed. New animation duration is '@duration lhs + duration rhs@'.
   86 --
   87 --   Example:
   88 --
   89 --   > drawBox `seqA` drawCircle
   90 --
   91 --   <<docs/gifs/doc_seqA.gif>>
   92 seqA :: Animation -> Animation -> Animation
   93 seqA (Animation d1 f1) (Animation d2 f2) =
   94   Animation totalD $ \t ->
   95     if t < d1/totalD
   96       then f1 (t * totalD/d1)
   97       else f2 ((t-d1/totalD) * totalD/d2)
   98   where
   99     totalD = d1+d2
  100 
  101 -- | Play two animation concurrently. Shortest animation freezes on last frame.
  102 --   New animation duration is '@max (duration lhs) (duration rhs)@'.
  103 --
  104 --   Example:
  105 --
  106 --   > drawBox `parA` adjustDuration (*2) drawCircle
  107 --
  108 --   <<docs/gifs/doc_parA.gif>>
  109 parA :: Animation -> Animation -> Animation
  110 parA (Animation d1 f1) (Animation d2 f2) =
  111   Animation (max d1 d2) $ \t ->
  112     let t1 = t * totalD/d1
  113         t2 = t * totalD/d2 in
  114     mkGroup
  115     [ f1 (min 1 t1)
  116     , f2 (min 1 t2) ]
  117   where
  118     totalD = max d1 d2
  119 
  120 -- | Play two animation concurrently. Shortest animation loops.
  121 --   New animation duration is '@max (duration lhs) (duration rhs)@'.
  122 --
  123 --   Example:
  124 --
  125 --   > drawBox `parLoopA` adjustDuration (*2) drawCircle
  126 --
  127 --   <<docs/gifs/doc_parLoopA.gif>>
  128 parLoopA :: Animation -> Animation -> Animation
  129 parLoopA (Animation d1 f1) (Animation d2 f2) =
  130   Animation totalD $ \t ->
  131     let t1 = t * totalD/d1
  132         t2 = t * totalD/d2 in
  133     mkGroup
  134     [ f1 (t1 `mod'` 1)
  135     , f2 (t2 `mod'` 1) ]
  136   where
  137     totalD = max d1 d2
  138 
  139 -- | Play two animation concurrently. Animations disappear after playing once.
  140 --   New animation duration is '@max (duration lhs) (duration rhs)@'.
  141 --
  142 --   Example:
  143 --
  144 --   > drawBox `parLoopA` adjustDuration (*2) drawCircle
  145 --
  146 --   <<docs/gifs/doc_parDropA.gif>>
  147 parDropA :: Animation -> Animation -> Animation
  148 parDropA (Animation d1 f1) (Animation d2 f2) =
  149   Animation totalD $ \t ->
  150     let t1 = t * totalD/d1
  151         t2 = t * totalD/d2 in
  152     mkGroup
  153     [ if t1>1 then None else f1 t1
  154     , if t2>1 then None else f2 t2 ]
  155   where
  156     totalD = max d1 d2
  157 
  158 -- | Empty animation (no SVG output) with a fixed duration.
  159 --
  160 --   Example:
  161 --
  162 --   > pause 1 `seqA` drawProgress
  163 --
  164 --   <<docs/gifs/doc_pause.gif>>
  165 pause :: Duration -> Animation
  166 pause d = Animation d (const None)
  167 
  168 -- | Play left animation and freeze on the last frame, then play the right
  169 --   animation. New duration is '@duration lhs + duration rhs@'.
  170 --
  171 --   Example:
  172 --
  173 --   > drawBox `andThen` drawCircle
  174 --
  175 --   <<docs/gifs/doc_andThen.gif>>
  176 andThen :: Animation -> Animation -> Animation
  177 andThen a b = a `parA` (pause (duration a) `seqA` b)
  178 
  179 -- | Calculate the frame that would be displayed at given point in @time@ of running @animation@.
  180 --
  181 -- The provided time parameter is clamped between 0 and animation duration.
  182 frameAt :: Time -> Animation -> SVG
  183 frameAt t (Animation d f) = f t'
  184   where
  185     t' = clamp 0 1 (t/d)
  186 
  187 -- | Helper function for pretty-printing SVG nodes.
  188 renderTree :: SVG -> String
  189 renderTree t = maybe "" ppElement $ xmlOfTree t
  190 
  191 -- | Helper function for pretty-printing SVG nodes as SVG documents.
  192 renderSvg :: Maybe Number -- ^ The number to use as value of the @width@ attribute of the resulting top-level svg element. If @Nothing@, the width attribute won't be rendered.
  193           -> Maybe Number -- ^ Similar to previous argument, but for @height@ attribute.
  194           -> SVG          -- ^ SVG to render
  195           -> String       -- ^ String representation of SVG XML markup
  196 renderSvg w h t = ppDocument doc
  197 -- renderSvg w h t = ppFastElement (xmlOfDocument doc)
  198   where
  199     width = 16
  200     height = 9
  201     doc = Document
  202       { _viewBox = Just (-width/2, -height/2, width, height)
  203       , _width = w
  204       , _height = h
  205       , _elements = [withStrokeWidth defaultStrokeWidth $ scaleXY 1 (-1) t]
  206       , _description = ""
  207       , _documentLocation = ""
  208       , _documentAspectRatio = PreserveAspectRatio False AlignNone Nothing
  209       }
  210 
  211 -- | Map over the SVG produced by an animation at every frame.
  212 --
  213 --   Example:
  214 --
  215 --   > mapA (scale 0.5) drawCircle
  216 --
  217 --   <<docs/gifs/doc_mapA.gif>>
  218 
  219 mapA :: (SVG -> SVG) -> Animation -> Animation
  220 mapA fn (Animation d f) = Animation d (fn . f)
  221 
  222 -- | Freeze the last frame for @t@ seconds at the end of the animation.
  223 --
  224 --   Example:
  225 --
  226 --   > pauseAtEnd 1 drawProgress
  227 --
  228 --   <<docs/gifs/doc_pauseAtEnd.gif>>
  229 pauseAtEnd :: Duration -> Animation -> Animation
  230 pauseAtEnd t a = a `andThen` pause t
  231 
  232 -- | Freeze the first frame for @t@ seconds at the beginning of the animation.
  233 --
  234 --   Example:
  235 --
  236 --   > pauseAtBeginning 1 drawProgress
  237 --
  238 --   <<docs/gifs/doc_pauseAtBeginning.gif>>
  239 pauseAtBeginning :: Duration -> Animation -> Animation
  240 pauseAtBeginning t a =
  241     Animation t (freezeFrame 0 a) `seqA` a
  242 
  243 -- | Freeze the first and the last frame of the animation for a specified duration.
  244 --
  245 --   Example:
  246 --
  247 --   > pauseAround 1 1 drawProgress
  248 --
  249 --   <<docs/gifs/doc_pauseAround.gif>>
  250 pauseAround :: Duration -> Duration -> Animation -> Animation
  251 pauseAround start end = pauseAtEnd end . pauseAtBeginning start
  252 
  253 -- XXX: Rename to 'setDurationFreeze'. Add 'setDurationDrop' and
  254 --      'setDurationLoop'.
  255 pauseUntil :: Duration -> Animation -> Animation
  256 pauseUntil d a = pauseAtEnd (d-duration a) a
  257 
  258 -- Freeze frame at time @t@.
  259 freezeFrame :: Time -> Animation -> (Time -> SVG)
  260 freezeFrame t (Animation d f) = const $ f (t/d)
  261 
  262 -- | Change the duration of an animation. Animates are stretched or squished
  263 --   (rather than truncated) to fit the new duration.
  264 adjustDuration :: (Duration -> Duration) -> Animation -> Animation
  265 adjustDuration fn (Animation d gen) =
  266   Animation (fn d) gen
  267 
  268 -- | Set the duration of an animation by adjusting its playback rate. The
  269 --   animation is still played from start to finish without being cropped.
  270 setDuration :: Duration -> Animation -> Animation
  271 setDuration newD = adjustDuration (const newD)
  272 
  273 -- | Play an animation in reverse. Duration remains unchanged. Shorthand for:
  274 --   @'signalA' 'reverseS'@.
  275 --
  276 --   Example:
  277 --
  278 --   > reverseA drawCircle
  279 --
  280 --   <<docs/gifs/doc_reverseA.gif>>
  281 reverseA :: Animation -> Animation
  282 reverseA = signalA reverseS
  283 
  284 -- | Play animation before playing it again in reverse. Duration is twice
  285 --   the duration of the input.
  286 --
  287 --   Example:
  288 --
  289 --   > playThenReverseA drawCircle
  290 --
  291 --   <<docs/gifs/doc_playThenReverseA.gif>>
  292 playThenReverseA :: Animation -> Animation
  293 playThenReverseA a = a `seqA` reverseA a
  294 
  295 -- | Loop animation @n@ number of times. This number may be fractional and it
  296 --   may be less than 1. It must be greater than or equal to 0, though.
  297 --   New duration is @n*duration input@.
  298 --
  299 --   Example:
  300 --
  301 --   > repeatA 1.5 drawCircle
  302 --
  303 --   <<docs/gifs/doc_repeatA.gif>>
  304 repeatA :: Double -> Animation -> Animation
  305 repeatA n (Animation d f) = Animation (d*n) $ \t ->
  306   f ((t*n) `mod'` 1)
  307 
  308 
  309 -- | @freezeAtPercentage time animation@ creates an animation consisting of stationary frame,
  310 -- that would be displayed in the provided @animation@ at given @time@.
  311 -- The duration of the new animation is the same as the duration of provided @animation@.
  312 freezeAtPercentage :: Time  -- ^ value between 0 and 1. The frame displayed at this point in the original animation will be displayed for the duration of the new animation
  313                    -> Animation -- ^ original animation, from which the frame will be taken
  314                    -> Animation -- ^ new animation consisting of static frame displayed for the duration of the original animation
  315 freezeAtPercentage frac (Animation d genFrame) =
  316   Animation d $ const $ genFrame frac
  317 
  318 -- | Overlay animation on top of static SVG image.
  319 --
  320 --  Example:
  321 --
  322 --  > addStatic (mkBackground "lightblue") drawCircle
  323 --
  324 --  <<docs/gifs/doc_addStatic.gif>>
  325 addStatic :: SVG -> Animation -> Animation
  326 addStatic static = mapA (\frame -> mkGroup [static, frame])
  327 
  328 -- | Modify the time component of an animation. Animation duration is unchanged.
  329 --
  330 --   Example:
  331 --
  332 --   > signalA (fromToS 0.25 0.75) drawCircle
  333 --
  334 --   <<docs/gifs/doc_signalA.gif>>
  335 signalA :: Signal -> Animation -> Animation
  336 signalA fn (Animation d gen) = Animation d $ gen . fn
  337 
  338 -- | @takeA duration animation@ creates a new animation consisting of initial segment of
  339 --   @animation@ of given @duration@, played at the same rate as the original animation.
  340 --
  341 --  The @duration@ parameter is clamped to be between 0 and @animation@'s duration.
  342 --  New animation duration is equal to (eventually clamped) @duration@.
  343 takeA :: Duration -> Animation -> Animation
  344 takeA len (Animation d gen) = Animation len' $ \t ->
  345     gen (t * len'/d)
  346   where
  347     len' = clamp 0 d len
  348 
  349 -- | @dropA duration animation@ creates a new animation by dropping initial segment
  350 --   of length @duration@ from the provided @animation@, played at the same rate as the original animation.
  351 --
  352 --  The @duration@ parameter is clamped to be between 0 and @animation@'s duration.
  353 --  The duration of the resulting animation is duration of provided @animation@ minus (eventually clamped) @duration@.
  354 dropA :: Duration -> Animation -> Animation
  355 dropA len (Animation d gen) = Animation len' $ \t ->
  356     gen (t * len'/d + len/d)
  357   where
  358     len' = d - clamp 0 d len
  359 
  360 -- | @lastA duration animation@ return the last @duration@ seconds of the animation.
  361 lastA :: Duration -> Animation -> Animation
  362 lastA len a = dropA (duration a - len) a
  363 
  364 clamp :: Double -> Double -> Double -> Double
  365 clamp a b number
  366   | a < b     = max a (min b number)
  367   | otherwise = max b (min a number)
  368 
  369 -- (#) :: a -> (a -> b) -> b
  370 -- o # f = f o
  371 
  372 -- | Ask for an animation frame using a given synchronization policy.
  373 getAnimationFrame :: Sync -> Animation -> Time -> Duration -> SVG
  374 getAnimationFrame sync (Animation aDur aGen) t d =
  375   case sync of
  376     SyncStretch -> aGen (t/d)
  377     SyncLoop    -> aGen (takeFrac $ t/aDur)
  378     SyncDrop    -> if t > aDur then None else aGen (t/aDur)
  379     SyncFreeze  -> aGen (min 1 $ t/aDur)
  380   where
  381     takeFrac f = snd (properFraction f :: (Int, Double))
  382 
  383 -- | Animation synchronization policies.
  384 data Sync
  385   = SyncStretch
  386   | SyncLoop
  387   | SyncDrop
  388   | SyncFreeze