{-|
Module      : Reanimate.Animation
Copyright   : Written by David Himmelstrup
License     : Unlicense
Maintainer  : lemmih@gmail.com
Stability   : experimental
Portability : POSIX

Declarative animation API based on combinators. For a higher-level interface,
see 'Reanimate.Scene'.

-}
module Reanimate.Animation
  ( Duration
  , Time
  , SVG
  , Animation
  -- * Creating animations
  , mkAnimation
  , animate
  , staticFrame
  , pause
  -- * Querying animations
  , duration
  , frameAt
  -- * Composing animations
  , seqA
  , andThen
  , parA
  , parLoopA
  , parDropA
  -- * Modifying animations
  , setDuration
  , adjustDuration
  , mapA
  , takeA
  , dropA
  , lastA
  , pauseAtEnd
  , pauseAtBeginning
  , pauseAround
  , repeatA
  , reverseA
  , playThenReverseA
  , signalA
  , freezeAtPercentage
  , addStatic
  -- * Misc
  , getAnimationFrame
  , Sync(..)
  -- * Rendering
  , renderTree
  , renderSvg
  ) where

import           Data.Fixed                 (mod')
import           Graphics.SvgTree
import           Graphics.SvgTree.Printer   (ppDocument)
import           Reanimate.Constants        (defaultStrokeWidth)
import           Reanimate.Ease             (Signal, reverseS)
import           Reanimate.Svg.Constructors (mkGroup, scaleXY, withStrokeWidth)
import           Text.XML.Light.Output      (ppElement)

-- | Duration of an animation or effect. Usually measured in seconds.
type Duration = Double
-- | Time signal. Goes from 0 to 1, inclusive.
type Time = Double

-- | SVG node.
type SVG = Tree

-- | Animations are SVGs over a finite time.
data Animation = Animation Duration (Time -> SVG)

-- | Construct an animation with a given duration.
mkAnimation :: Duration -> (Time -> SVG) -> Animation
mkAnimation :: Duration -> (Duration -> SVG) -> Animation
mkAnimation = Duration -> (Duration -> SVG) -> Animation
Animation

-- | Construct an animation with a duration of @1@.
animate :: (Time -> SVG) -> Animation
animate :: (Duration -> SVG) -> Animation
animate = Duration -> (Duration -> SVG) -> Animation
Animation Duration
1

-- | Create an animation with provided @duration@, which consists of stationary frame displayed for its entire duration.
staticFrame :: Duration -> SVG -> Animation
staticFrame :: Duration -> SVG -> Animation
staticFrame Duration
d SVG
svg = Duration -> (Duration -> SVG) -> Animation
Animation Duration
d (SVG -> Duration -> SVG
forall a b. a -> b -> a
const SVG
svg)

-- | Query the duration of an animation.
duration :: Animation -> Duration
duration :: Animation -> Duration
duration (Animation Duration
d Duration -> SVG
_) = Duration
d

-- | Play animations in sequence. The @lhs@ animation is removed after it has
--   completed. New animation duration is '@duration lhs + duration rhs@'.
--
--   Example:
--
--   @'Reanimate.Builtin.Documentation.drawBox' `'seqA'` 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_seqA.gif>>
seqA :: Animation -> Animation -> Animation
seqA :: Animation -> Animation -> Animation
seqA (Animation Duration
d1 Duration -> SVG
f1) (Animation Duration
d2 Duration -> SVG
f2) =
  Duration -> (Duration -> SVG) -> Animation
Animation Duration
totalD ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    if Duration
t Duration -> Duration -> Bool
forall a. Ord a => a -> a -> Bool
< Duration
d1Duration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
totalD
      then Duration -> SVG
f1 (Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d1)
      else Duration -> SVG
f2 ((Duration
tDuration -> Duration -> Duration
forall a. Num a => a -> a -> a
-Duration
d1Duration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
totalD) Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d2)
  where
    totalD :: Duration
totalD = Duration
d1Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
+Duration
d2

-- | Play two animation concurrently. Shortest animation freezes on last frame.
--   New animation duration is '@max (duration lhs) (duration rhs)@'.
--
--   Example:
--
--   @'Reanimate.Builtin.Documentation.drawBox' `'parA'` 'adjustDuration' (*2) 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_parA.gif>>
parA :: Animation -> Animation -> Animation
parA :: Animation -> Animation -> Animation
parA (Animation Duration
d1 Duration -> SVG
f1) (Animation Duration
d2 Duration -> SVG
f2) =
  Duration -> (Duration -> SVG) -> Animation
Animation (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
d1 Duration
d2) ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    let t1 :: Duration
t1 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d1
        t2 :: Duration
t2 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d2 in
    [SVG] -> SVG
mkGroup
    [ Duration -> SVG
f1 (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
min Duration
1 Duration
t1)
    , Duration -> SVG
f2 (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
min Duration
1 Duration
t2) ]
  where
    totalD :: Duration
totalD = Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
d1 Duration
d2

-- | Play two animation concurrently. Shortest animation loops.
--   New animation duration is '@max (duration lhs) (duration rhs)@'.
--
--   Example:
--
--   @'Reanimate.Builtin.Documentation.drawBox' `'parLoopA'` 'adjustDuration' (*2) 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_parLoopA.gif>>
parLoopA :: Animation -> Animation -> Animation
parLoopA :: Animation -> Animation -> Animation
parLoopA (Animation Duration
d1 Duration -> SVG
f1) (Animation Duration
d2 Duration -> SVG
f2) =
  Duration -> (Duration -> SVG) -> Animation
Animation Duration
totalD ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    let t1 :: Duration
t1 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d1
        t2 :: Duration
t2 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d2 in
    [SVG] -> SVG
mkGroup
    [ Duration -> SVG
f1 (Duration
t1 Duration -> Duration -> Duration
forall a. Real a => a -> a -> a
`mod'` Duration
1)
    , Duration -> SVG
f2 (Duration
t2 Duration -> Duration -> Duration
forall a. Real a => a -> a -> a
`mod'` Duration
1) ]
  where
    totalD :: Duration
totalD = Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
d1 Duration
d2

-- | Play two animation concurrently. Animations disappear after playing once.
--   New animation duration is '@max (duration lhs) (duration rhs)@'.
--
--   Example:
--
--   @'Reanimate.Builtin.Documentation.drawBox' `'parDropA'` 'adjustDuration' (*2) 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_parDropA.gif>>
parDropA :: Animation -> Animation -> Animation
parDropA :: Animation -> Animation -> Animation
parDropA (Animation Duration
d1 Duration -> SVG
f1) (Animation Duration
d2 Duration -> SVG
f2) =
  Duration -> (Duration -> SVG) -> Animation
Animation Duration
totalD ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    let t1 :: Duration
t1 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d1
        t2 :: Duration
t2 = Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
totalDDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d2 in
    [SVG] -> SVG
mkGroup
    [ if Duration
t1Duration -> Duration -> Bool
forall a. Ord a => a -> a -> Bool
>Duration
1 then SVG
None else Duration -> SVG
f1 Duration
t1
    , if Duration
t2Duration -> Duration -> Bool
forall a. Ord a => a -> a -> Bool
>Duration
1 then SVG
None else Duration -> SVG
f2 Duration
t2 ]
  where
    totalD :: Duration
totalD = Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
d1 Duration
d2

-- | Empty animation (no SVG output) with a fixed duration.
--
--   Example:
--
--   @'pause' 1 `'seqA'` 'Reanimate.Builtin.Documentation.drawProgress'@
--
--   <<docs/gifs/doc_pause.gif>>
pause :: Duration -> Animation
pause :: Duration -> Animation
pause Duration
d = Duration -> (Duration -> SVG) -> Animation
Animation Duration
d (SVG -> Duration -> SVG
forall a b. a -> b -> a
const SVG
None)

-- | Play left animation and freeze on the last frame, then play the right
--   animation. New duration is '@duration lhs + duration rhs@'.
--
--   Example:
--
--   @'Reanimate.Builtin.Documentation.drawBox' `'andThen'` 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_andThen.gif>>
andThen :: Animation -> Animation -> Animation
andThen :: Animation -> Animation -> Animation
andThen Animation
a Animation
b = Animation
a Animation -> Animation -> Animation
`parA` (Duration -> Animation
pause (Animation -> Duration
duration Animation
a) Animation -> Animation -> Animation
`seqA` Animation
b)

-- | Calculate the frame that would be displayed at given point in @time@ of running @animation@.
--
-- The provided time parameter is clamped between 0 and animation duration.
frameAt :: Time -> Animation -> SVG
frameAt :: Duration -> Animation -> SVG
frameAt Duration
t (Animation Duration
d Duration -> SVG
f) = Duration -> SVG
f Duration
t'
  where
    t' :: Duration
t' = Duration -> Duration -> Duration -> Duration
clamp Duration
0 Duration
1 (Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d)

-- | Helper function for pretty-printing SVG nodes.
renderTree :: SVG -> String
renderTree :: SVG -> String
renderTree SVG
t = String -> (Element -> String) -> Maybe Element -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" Element -> String
ppElement (Maybe Element -> String) -> Maybe Element -> String
forall a b. (a -> b) -> a -> b
$ SVG -> Maybe Element
xmlOfTree SVG
t

-- | Helper function for pretty-printing SVG nodes as SVG documents.
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.
          -> Maybe Number -- ^ Similar to previous argument, but for @height@ attribute.
          -> SVG          -- ^ SVG to render
          -> String       -- ^ String representation of SVG XML markup
renderSvg :: Maybe Number -> Maybe Number -> SVG -> String
renderSvg Maybe Number
w Maybe Number
h SVG
t = Document -> String
ppDocument Document
doc
-- renderSvg w h t = ppFastElement (xmlOfDocument doc)
  where
    width :: Duration
width = Duration
16
    height :: Duration
height = Duration
9
    doc :: Document
doc = Document :: Maybe (Duration, Duration, Duration, Duration)
-> Maybe Number
-> Maybe Number
-> [SVG]
-> String
-> String
-> PreserveAspectRatio
-> Document
Document
      { _documentViewBox :: Maybe (Duration, Duration, Duration, Duration)
_documentViewBox = (Duration, Duration, Duration, Duration)
-> Maybe (Duration, Duration, Duration, Duration)
forall a. a -> Maybe a
Just (-Duration
widthDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
2, -Duration
heightDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
2, Duration
width, Duration
height)
      , _documentWidth :: Maybe Number
_documentWidth = Maybe Number
w
      , _documentHeight :: Maybe Number
_documentHeight = Maybe Number
h
      , _documentElements :: [SVG]
_documentElements = [Duration -> SVG -> SVG
withStrokeWidth Duration
defaultStrokeWidth (SVG -> SVG) -> SVG -> SVG
forall a b. (a -> b) -> a -> b
$ Duration -> Duration -> SVG -> SVG
scaleXY Duration
1 (-Duration
1) SVG
t]
      , _documentDescription :: String
_documentDescription = String
""
      , _documentLocation :: String
_documentLocation = String
""
      , _documentAspectRatio :: PreserveAspectRatio
_documentAspectRatio = Bool -> Alignment -> Maybe MeetSlice -> PreserveAspectRatio
PreserveAspectRatio Bool
False Alignment
AlignNone Maybe MeetSlice
forall a. Maybe a
Nothing
      }

-- | Map over the SVG produced by an animation at every frame.
--
--   Example:
--
--   @'mapA' ('scale' 0.5) 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_mapA.gif>>

mapA :: (SVG -> SVG) -> Animation -> Animation
mapA :: (SVG -> SVG) -> Animation -> Animation
mapA SVG -> SVG
fn (Animation Duration
d Duration -> SVG
f) = Duration -> (Duration -> SVG) -> Animation
Animation Duration
d (SVG -> SVG
fn (SVG -> SVG) -> (Duration -> SVG) -> Duration -> SVG
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Duration -> SVG
f)

-- | Freeze the last frame for @t@ seconds at the end of the animation.
--
--   Example:
--
--   @'pauseAtEnd' 1 'Reanimate.Builtin.Documentation.drawProgress'@
--
--   <<docs/gifs/doc_pauseAtEnd.gif>>
pauseAtEnd :: Duration -> Animation -> Animation
pauseAtEnd :: Duration -> Animation -> Animation
pauseAtEnd Duration
t Animation
a = Animation
a Animation -> Animation -> Animation
`andThen` Duration -> Animation
pause Duration
t

-- | Freeze the first frame for @t@ seconds at the beginning of the animation.
--
--   Example:
--
--   @'pauseAtBeginning' 1 'Reanimate.Builtin.Documentation.drawProgress'@
--
--   <<docs/gifs/doc_pauseAtBeginning.gif>>
pauseAtBeginning :: Duration -> Animation -> Animation
pauseAtBeginning :: Duration -> Animation -> Animation
pauseAtBeginning Duration
t Animation
a =
    Duration -> (Duration -> SVG) -> Animation
Animation Duration
t (Duration -> Animation -> Duration -> SVG
freezeFrame Duration
0 Animation
a) Animation -> Animation -> Animation
`seqA` Animation
a

-- | Freeze the first and the last frame of the animation for a specified duration.
--
--   Example:
--
--   @'pauseAround' 1 1 'Reanimate.Builtin.Documentation.drawProgress'@
--
--   <<docs/gifs/doc_pauseAround.gif>>
pauseAround :: Duration -> Duration -> Animation -> Animation
pauseAround :: Duration -> Duration -> Animation -> Animation
pauseAround Duration
start Duration
end = Duration -> Animation -> Animation
pauseAtEnd Duration
end (Animation -> Animation)
-> (Animation -> Animation) -> Animation -> Animation
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Duration -> Animation -> Animation
pauseAtBeginning Duration
start

-- Freeze frame at time @t@.
freezeFrame :: Time -> Animation -> (Time -> SVG)
freezeFrame :: Duration -> Animation -> Duration -> SVG
freezeFrame Duration
t (Animation Duration
d Duration -> SVG
f) = SVG -> Duration -> SVG
forall a b. a -> b -> a
const (SVG -> Duration -> SVG) -> SVG -> Duration -> SVG
forall a b. (a -> b) -> a -> b
$ Duration -> SVG
f (Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d)

-- | Change the duration of an animation. Animates are stretched or squished
--   (rather than truncated) to fit the new duration.
adjustDuration :: (Duration -> Duration) -> Animation -> Animation
adjustDuration :: (Duration -> Duration) -> Animation -> Animation
adjustDuration Duration -> Duration
fn (Animation Duration
d Duration -> SVG
gen) =
  Duration -> (Duration -> SVG) -> Animation
Animation (Duration -> Duration
fn Duration
d) Duration -> SVG
gen

-- | Set the duration of an animation by adjusting its playback rate. The
--   animation is still played from start to finish without being cropped.
setDuration :: Duration -> Animation -> Animation
setDuration :: Duration -> Animation -> Animation
setDuration Duration
newD = (Duration -> Duration) -> Animation -> Animation
adjustDuration (Duration -> Duration -> Duration
forall a b. a -> b -> a
const Duration
newD)

-- | Play an animation in reverse. Duration remains unchanged. Shorthand for:
--   @'signalA' 'reverseS'@.
--
--   Example:
--
--   @'reverseA' 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_reverseA.gif>>
reverseA :: Animation -> Animation
reverseA :: Animation -> Animation
reverseA = (Duration -> Duration) -> Animation -> Animation
signalA Duration -> Duration
reverseS

-- | Play animation before playing it again in reverse. Duration is twice
--   the duration of the input.
--
--   Example:
--
--   @'playThenReverseA' 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_playThenReverseA.gif>>
playThenReverseA :: Animation -> Animation
playThenReverseA :: Animation -> Animation
playThenReverseA Animation
a = Animation
a Animation -> Animation -> Animation
`seqA` Animation -> Animation
reverseA Animation
a

-- | Loop animation @n@ number of times. This number may be fractional and it
--   may be less than 1. It must be greater than or equal to 0, though.
--   New duration is @n*duration input@.
--
--   Example:
--
--   @'repeatA' 1.5 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_repeatA.gif>>
repeatA :: Double -> Animation -> Animation
repeatA :: Duration -> Animation -> Animation
repeatA Duration
n (Animation Duration
d Duration -> SVG
f) = Duration -> (Duration -> SVG) -> Animation
Animation (Duration
dDuration -> Duration -> Duration
forall a. Num a => a -> a -> a
*Duration
n) ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
  Duration -> SVG
f ((Duration
tDuration -> Duration -> Duration
forall a. Num a => a -> a -> a
*Duration
n) Duration -> Duration -> Duration
forall a. Real a => a -> a -> a
`mod'` Duration
1)


-- | @freezeAtPercentage time animation@ creates an animation consisting of stationary frame,
-- that would be displayed in the provided @animation@ at given @time@.
-- The duration of the new animation is the same as the duration of provided @animation@.
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
                   -> Animation -- ^ original animation, from which the frame will be taken
                   -> Animation -- ^ new animation consisting of static frame displayed for the duration of the original animation
freezeAtPercentage :: Duration -> Animation -> Animation
freezeAtPercentage Duration
frac (Animation Duration
d Duration -> SVG
genFrame) =
  Duration -> (Duration -> SVG) -> Animation
Animation Duration
d ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ SVG -> Duration -> SVG
forall a b. a -> b -> a
const (SVG -> Duration -> SVG) -> SVG -> Duration -> SVG
forall a b. (a -> b) -> a -> b
$ Duration -> SVG
genFrame Duration
frac

-- | Overlay animation on top of static SVG image.
--
--  Example:
--
--  @'addStatic' ('mkBackground' "lightblue") 'Reanimate.Builtin.Documentation.drawCircle'@
--
--  <<docs/gifs/doc_addStatic.gif>>
addStatic :: SVG -> Animation -> Animation
addStatic :: SVG -> Animation -> Animation
addStatic SVG
static = (SVG -> SVG) -> Animation -> Animation
mapA (\SVG
frame -> [SVG] -> SVG
mkGroup [SVG
static, SVG
frame])

-- | Modify the time component of an animation. Animation duration is unchanged.
--
--   Example:
--
--   @'signalA' ('fromToS' 0.25 0.75) 'Reanimate.Builtin.Documentation.drawCircle'@
--
--   <<docs/gifs/doc_signalA.gif>>
signalA :: Signal -> Animation -> Animation
signalA :: (Duration -> Duration) -> Animation -> Animation
signalA Duration -> Duration
fn (Animation Duration
d Duration -> SVG
gen) = Duration -> (Duration -> SVG) -> Animation
Animation Duration
d ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ Duration -> SVG
gen (Duration -> SVG) -> (Duration -> Duration) -> Duration -> SVG
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Duration -> Duration
fn

-- | @takeA duration animation@ creates a new animation consisting of initial segment of
--   @animation@ of given @duration@, played at the same rate as the original animation.
--
--  The @duration@ parameter is clamped to be between 0 and @animation@'s duration.
--  New animation duration is equal to (eventually clamped) @duration@.
takeA :: Duration -> Animation -> Animation
takeA :: Duration -> Animation -> Animation
takeA Duration
len (Animation Duration
d Duration -> SVG
gen) = Duration -> (Duration -> SVG) -> Animation
Animation Duration
len' ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    Duration -> SVG
gen (Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
len'Duration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d)
  where
    len' :: Duration
len' = Duration -> Duration -> Duration -> Duration
clamp Duration
0 Duration
d Duration
len

-- | @dropA duration animation@ creates a new animation by dropping initial segment
--   of length @duration@ from the provided @animation@, played at the same rate as the original animation.
--
--  The @duration@ parameter is clamped to be between 0 and @animation@'s duration.
--  The duration of the resulting animation is duration of provided @animation@ minus (eventually clamped) @duration@.
dropA :: Duration -> Animation -> Animation
dropA :: Duration -> Animation -> Animation
dropA Duration
len (Animation Duration
d Duration -> SVG
gen) = Duration -> (Duration -> SVG) -> Animation
Animation Duration
len' ((Duration -> SVG) -> Animation) -> (Duration -> SVG) -> Animation
forall a b. (a -> b) -> a -> b
$ \Duration
t ->
    Duration -> SVG
gen (Duration
t Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
* Duration
len'Duration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
+ Duration
lenDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d)
  where
    len' :: Duration
len' = Duration
d Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
- Duration -> Duration -> Duration -> Duration
clamp Duration
0 Duration
d Duration
len

-- | @lastA duration animation@ return the last @duration@ seconds of the animation.
lastA :: Duration -> Animation -> Animation
lastA :: Duration -> Animation -> Animation
lastA Duration
len Animation
a = Duration -> Animation -> Animation
dropA (Animation -> Duration
duration Animation
a Duration -> Duration -> Duration
forall a. Num a => a -> a -> a
- Duration
len) Animation
a

clamp :: Double -> Double -> Double -> Double
clamp :: Duration -> Duration -> Duration -> Duration
clamp Duration
a Duration
b Duration
number
  | Duration
a Duration -> Duration -> Bool
forall a. Ord a => a -> a -> Bool
< Duration
b     = Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
a (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
min Duration
b Duration
number)
  | Bool
otherwise = Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
max Duration
b (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
min Duration
a Duration
number)

-- (#) :: a -> (a -> b) -> b
-- o # f = f o

-- | Ask for an animation frame using a given synchronization policy.
getAnimationFrame :: Sync -> Animation -> Time -> Duration -> SVG
getAnimationFrame :: Sync -> Animation -> Duration -> Duration -> SVG
getAnimationFrame Sync
sync (Animation Duration
aDur Duration -> SVG
aGen) Duration
t Duration
d =
  case Sync
sync of
    Sync
SyncStretch -> Duration -> SVG
aGen (Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
d)
    Sync
SyncLoop    -> Duration -> SVG
aGen (Duration -> Duration
takeFrac (Duration -> Duration) -> Duration -> Duration
forall a b. (a -> b) -> a -> b
$ Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
aDur)
    Sync
SyncDrop    -> if Duration
t Duration -> Duration -> Bool
forall a. Ord a => a -> a -> Bool
> Duration
aDur then SVG
None else Duration -> SVG
aGen (Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
aDur)
    Sync
SyncFreeze  -> Duration -> SVG
aGen (Duration -> Duration -> Duration
forall a. Ord a => a -> a -> a
min Duration
1 (Duration -> Duration) -> Duration -> Duration
forall a b. (a -> b) -> a -> b
$ Duration
tDuration -> Duration -> Duration
forall a. Fractional a => a -> a -> a
/Duration
aDur)
  where
    takeFrac :: Duration -> Duration
takeFrac Duration
f = (Int, Duration) -> Duration
forall a b. (a, b) -> b
snd (Duration -> (Int, Duration)
forall a b. (RealFrac a, Integral b) => a -> (b, a)
properFraction Duration
f :: (Int, Double))

-- | Animation synchronization policies.
data Sync
  = SyncStretch
  | SyncLoop
  | SyncDrop
  | SyncFreeze