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