module Reanimate.Monad where
import Control.Arrow ()
import qualified Control.Category as C
import Control.Monad.State
import Data.Fixed
import Data.Fixed (mod')
import Text.Printf
import qualified Data.Map as M
import Data.Monoid ((<>))
import Data.Text (Text, pack)
import Graphics.SvgTree (Document (..), Number (..), Text (..),
TextSpan (..), TextSpanContent (..),
Tree, Tree (..), xmlOfDocument, xmlOfTree)
import Graphics.SvgTree.Printer
import Reanimate.Svg
import Text.XML.Light (elContent)
import Text.XML.Light.Output
import Reanimate.Combinators (approxFnData, morphPath)
type Duration = Double
type Time = Double
data Frame a = Frame {unFrame :: Duration -> Time -> State ([Tree] -> [Tree]) a}
instance Functor Frame where
fmap fn f = Frame $ \d t -> fmap fn (unFrame f d t)
instance Applicative Frame where
pure a = Frame $ \_ _ -> pure a
fn <*> fa = Frame $ \d t -> do
fn <- unFrame fn d t
a <- unFrame fa d t
pure (fn a)
instance Monad Frame where
return a = Frame $ \_ _ -> pure a
f >>= g = Frame $ \d t -> do
a <- unFrame f d t
unFrame (g a) d t
data Animation = Animation Duration (Frame ())
mkAnimation :: Duration -> Frame () -> Animation
mkAnimation = Animation
duration :: Animation -> Duration
duration (Animation d _) = d
emit :: Tree -> Frame ()
emit svg = Frame $ \_ _ -> modify (.(svg:))
before :: Animation -> Animation -> Animation
before (Animation d1 (Frame f1)) (Animation d2 (Frame f2)) =
Animation (d1+d2) (Frame $ \_ t -> if t < d1 then f1 d1 t else f2 d2 (t-d1))
sim :: Animation -> Animation -> Animation
sim (Animation d1 (Frame f1)) (Animation d2 (Frame f2)) =
Animation (max d1 d2) $ Frame $ \d t -> do
f1 d1 (min d1 t)
f2 d2 (min d2 t)
simLoop :: Animation -> Animation -> Animation
simLoop (Animation d1 (Frame f1)) (Animation d2 (Frame f2)) =
Animation (max d1 d2) $ Frame $ \d t -> do
f1 d1 (t `mod'` d1)
f2 d2 (t `mod'` d2)
simDrop :: Animation -> Animation -> Animation
simDrop (Animation d1 (Frame f1)) (Animation d2 (Frame f2)) =
Animation (max d1 d2) $ Frame $ \d t -> do
when (t < d1) (f1 d1 t)
when (t < d2) (f2 d2 t)
pause :: Double -> Animation
pause d = Animation d (pure ())
andThen :: Animation -> Animation -> Animation
andThen a b = a `sim` (pause (duration a) `before` b)
signal :: Double -> Double -> Frame Double
signal from to = Frame $ \d t -> pure $
from + (to-from)*(t/d)
signalSCurve :: Double -> Double -> Double -> Frame Double
signalSCurve steepness from to = do
s <- signal 0 1
let s' = if s < 0.5
then 0.5 * (2*s)**steepness
else 1-0.5 * (2 - 2*s)**steepness
pure $ from + (to-from)*s'
frameAt :: Double -> Animation -> Tree
frameAt t (Animation d (Frame f)) = mkGroup $ execState (f d (min d t)) id []
renderTree :: Tree -> String
renderTree t = maybe "" ppElement $ xmlOfTree t
renderSvg :: Maybe Number -> Maybe Number -> Tree -> String
renderSvg w h t = ppDocument doc
where
width = 320
height = width / (16/9)
doc = Document
{ _viewBox = Just (-width/2, -height/2, width, height)
, _width = w
, _height = h
, _elements = [t]
, _definitions = M.empty
, _description = ""
, _documentLocation = ""
}
mapA :: (Tree -> Tree) -> Animation -> Animation
mapA fn (Animation d f) = Animation d (mapF fn f)
mapF :: (Tree -> Tree) -> Frame a -> Frame a
mapF fn frame = Frame $ \d t -> do
case runState (unFrame frame d t) id of
(a, children) -> modify (. (fn (mkGroup (children [])):)) >> pure a
pauseAtEnd :: Double -> Animation -> Animation
pauseAtEnd p a = a `andThen` pause p
adjustSpeed :: Double -> Animation -> Animation
adjustSpeed factor (Animation d fn) =
Animation (d/factor) $ Frame $ \_dur t -> unFrame fn d (t*factor)
reverseAnimation :: Animation -> Animation
reverseAnimation (Animation d fn) = Animation d $ Frame $ \_dur t ->
unFrame fn d (d-t)
autoReverse :: Animation -> Animation
autoReverse a = a `before` reverseAnimation a
oscillate :: Frame a -> Frame a
oscillate f = Frame $ \d t -> do
if t < d/2
then unFrame f d (t*2)
else unFrame f d (d*2-t*2)
repeatAnimation :: Double -> Animation -> Animation
repeatAnimation n (Animation d f) = Animation (d*n) $ Frame $ \_ t ->
unFrame f d (t `mod'` d)