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