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