1 // This file is part of VSTGUI. It is subject to the license terms
2 // in the LICENSE file found in the top-level directory of this
3 // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
4
5 /**
6 @page page_animation Animations
7
8 VSTGUI version 4 adds simple to use view animation support.
9
10 The source can be found under /lib/animation/
11
12 @section the_animator The Animator
13 Every @link VSTGUI::CFrame::getAnimator CFrame @endlink object can have one @link VSTGUI::Animation::Animator Animator @endlink object which runs animations at 60 Hz.
14
15 The animator is responsible for running animations.
16 You can add and remove animations.
17 Animations are identified by a view and a name.
18
19 To add an animation you just call @link VSTGUI::CView::addAnimation CView::addAnimation (name, target, timing)@endlink.
20 The animation will start immediately and will automatically be removed if it has finished.
21 If you want to stop it before it has finished you can use @link VSTGUI::CView::removeAnimation CView::removeAnimation (name)@endlink.
22 You can also stop all animations for a view with @link VSTGUI::CView::removeAllAnimations CView::removeAllAnimations ()@endlink.
23
24 The animator is the owner of the target and timing function objects and will destroy these objects when the animation has finished.
25 This means that the animator will call delete on these objects or if they are inherited from CBaseObject it will call forget() on them.
26
27 @section the_animation The Animation
28
29 An animation is made up by an @link VSTGUI::Animation::IAnimationTarget IAnimationTarget @endlink and an @link VSTGUI::Animation::ITimingFunction ITimingFunction @endlink object.
30
31 @subsection animation_target The Animation Target
32 The animation target is responsible for changing the view from one state to another state.
33
34 The animation target interface consists of 3 methods:
35 - @link VSTGUI::Animation::IAnimationTarget::animationStart animationStart (view, name) @endlink
36 - @link VSTGUI::Animation::IAnimationTarget::animationTick animationTick (view, name, pos) @endlink
37 - @link VSTGUI::Animation::IAnimationTarget::animationFinished animationFinished (view, name, wasCanceled) @endlink
38
39 All these methods have the view and the animation name as arguments to identify the animation within the target.
40 The animationTick method in addition has the normalized animation position as argument and the animationFinished method has a bool argument indicating if the animation was canceled.
41
42 see @link AnimationTargets included animation target classes @endlink
43
44 @subsection animation_timing The Animation Timing Function
45 the animation timing function maps elapsed time to a normalized position.
46
47 see @link AnimationTimingFunctions included animation timing function classes @endlink
48
49 @section simple_example Simple Usage Example
50 In this example the custom view animates it's alpha value when the mouse moves inside or outside the view.
51
52 @code
53
54 using namespace VSTGUI::Animation;
55
56 class MyView : public CView
57 {
58 public:
59 MyView (const CRect& r) : CView (r) { setAlphaValue (0.5f); }
60
61 CMouseEventResult onMouseEntered (CPoint &where, const CButtonState& buttons)
62 {
63 // this adds an animation which takes 200 ms to make a linear alpha fade from the current value to 1
64 addAnimation ("AlphaValueAnimation", new AlphaValueAnimation (1.f), new LinearTimingFunction (200));
65 return kMouseEventHandled;
66 }
67
68 CMouseEventResult onMouseExited (CPoint &where, const CButtonState& buttons)
69 {
70 // this adds an animation which takes 200 ms to make a linear alpha fade from the current value to 0.5
71 addAnimation ("AlphaValueAnimation", new AlphaValueAnimation (0.5f), new LinearTimingFunction (200));
72 return kMouseEventHandled;
73 }
74
75 void draw (CDrawContext* context)
76 {
77 // ... any drawing code here
78 }
79 };
80
81 @endcode
82
83
84 */
85
86 //------------------------------------------------------------------------
87 /*! @defgroup animation Animation
88 see @ref page_animation
89 */
90 //-----------------------------------------------------------------------------
91
92 #include "animator.h"
93 #include "ianimationtarget.h"
94 #include "itimingfunction.h"
95 #include "../cvstguitimer.h"
96 #include "../cview.h"
97 #include "../dispatchlist.h"
98 #include "../platform/iplatformframe.h"
99 #include <list>
100
101 #define DEBUG_LOG 0 // DEBUG
102
103 namespace VSTGUI {
104 namespace Animation {
105
106 ///@cond ignore
107 namespace Detail {
108
109 //-----------------------------------------------------------------------------
110 class Timer : public NonAtomicReferenceCounted
111 {
112 public:
addAnimator(Animator * animator)113 static void addAnimator (Animator* animator)
114 {
115 getInstance ()->animators.emplace_back (animator);
116 #if DEBUG_LOG
117 DebugPrint ("Animator added: %p\n", animator);
118 #endif
119 }
removeAnimator(Animator * animator)120 static void removeAnimator (Animator* animator)
121 {
122 if (gInstance)
123 {
124 if (getInstance ()->inTimer)
125 {
126 gInstance->toRemove.emplace_back (animator);
127 }
128 else
129 {
130 #if DEBUG_LOG
131 DebugPrint ("Animator removed: %p\n", animator);
132 #endif
133 gInstance->animators.remove (animator);
134 if (gInstance->animators.empty ())
135 {
136 gInstance->forget ();
137 gInstance = nullptr;
138 }
139 }
140 }
141 }
142
143 protected:
getInstance()144 static Timer* getInstance ()
145 {
146 if (gInstance == nullptr)
147 gInstance = new Timer;
148 return gInstance;
149 }
150
Timer()151 Timer ()
152 : inTimer (false)
153 {
154 #if DEBUG_LOG
155 DebugPrint ("Animation timer started\n");
156 #endif
157 timer = new CVSTGUITimer ([this] (CVSTGUITimer*) {
158 onTimer ();
159 }, 1000/60); // 60 Hz
160 }
161
~Timer()162 ~Timer () noexcept override
163 {
164 #if DEBUG_LOG
165 DebugPrint ("Animation timer stopped\n");
166 #endif
167 timer->forget ();
168 gInstance = nullptr;
169 }
170
onTimer()171 void onTimer ()
172 {
173 inTimer = true;
174 auto guard = shared (this);
175 #if DEBUG_LOG
176 DebugPrint ("Current Animators : %d\n", animators.size ());
177 #endif
178 for (auto& animator : animators)
179 animator->onTimer ();
180 inTimer = false;
181 for (auto& animator : toRemove)
182 removeAnimator (animator);
183 toRemove.clear ();
184 }
185
186 CVSTGUITimer* timer;
187
188 using Animators = std::list<Animator*>;
189 Animators animators;
190 Animators toRemove;
191 bool inTimer;
192 static Timer* gInstance;
193 };
194 Timer* Timer::gInstance = nullptr;
195
196 //-----------------------------------------------------------------------------
197 class Animation : public NonAtomicReferenceCounted
198 {
199 public:
200 Animation (CView* view, const std::string& name, IAnimationTarget* at, ITimingFunction* t, DoneFunction&& notification);
201 ~Animation () noexcept override;
202
203 std::string name;
204 SharedPointer<CView> view;
205 IAnimationTarget* animationTarget;
206 ITimingFunction* timingFunction;
207 DoneFunction notification;
208 uint32_t startTime;
209 float lastPos;
210 bool done;
211 };
212
213 //-----------------------------------------------------------------------------
Animation(CView * view,const std::string & name,IAnimationTarget * at,ITimingFunction * t,DoneFunction && notification)214 Animation::Animation (CView* view, const std::string& name, IAnimationTarget* at, ITimingFunction* t, DoneFunction&& notification)
215 : name (name)
216 , view (view)
217 , animationTarget (at)
218 , timingFunction (t)
219 , notification (std::move (notification))
220 , startTime (0)
221 , lastPos (-1)
222 , done (false)
223 {
224 }
225
226 //-----------------------------------------------------------------------------
~Animation()227 Animation::~Animation () noexcept
228 {
229 if (notification)
230 notification (view, name.c_str (), animationTarget);
231 if (auto obj = dynamic_cast<IReference*> (animationTarget))
232 obj->forget ();
233 else
234 delete animationTarget;
235 if (auto obj = dynamic_cast<IReference*> (timingFunction))
236 obj->forget ();
237 else
238 delete timingFunction;
239 }
240
241 } // Detail
242
243 //-----------------------------------------------------------------------------
244 struct Animator::Impl
245 {
246 DispatchList<SharedPointer<Detail::Animation>> animations;
247 };
248 ///@endcond
249
250 //-----------------------------------------------------------------------------
Animator()251 Animator::Animator ()
252 {
253 pImpl = std::unique_ptr<Impl> (new Impl ());
254 }
255
256 //-----------------------------------------------------------------------------
~Animator()257 Animator::~Animator () noexcept
258 {
259 Detail::Timer::removeAnimator (this);
260 }
261
262 //-----------------------------------------------------------------------------
addAnimation(CView * view,IdStringPtr name,IAnimationTarget * target,ITimingFunction * timingFunction,DoneFunction notification)263 void Animator::addAnimation (CView* view, IdStringPtr name, IAnimationTarget* target, ITimingFunction* timingFunction, DoneFunction notification)
264 {
265 if (pImpl->animations.empty ())
266 Detail::Timer::addAnimator (this);
267 removeAnimation (view, name);
268 pImpl->animations.add (makeOwned<Detail::Animation> (view, name, target, timingFunction, std::move (notification)));
269 #if DEBUG_LOG
270 DebugPrint ("new animation added: %p - %s\n", view, name);
271 #endif
272 }
273
274 //-----------------------------------------------------------------------------
addAnimation(CView * view,IdStringPtr name,IAnimationTarget * target,ITimingFunction * timingFunction,CBaseObject * notificationObject)275 void Animator::addAnimation (CView* view, IdStringPtr name, IAnimationTarget* target, ITimingFunction* timingFunction, CBaseObject* notificationObject)
276 {
277 DoneFunction notification;
278 if (notificationObject)
279 {
280 SharedPointer<CBaseObject> nObj (notificationObject);
281 notification = [nObj] (CView* view, const IdStringPtr name, IAnimationTarget* target) {
282 FinishedMessage fmsg (view, name, target);
283 nObj->notify (&fmsg, kMsgAnimationFinished);
284 };
285 }
286 addAnimation (view, name, target, timingFunction, std::move (notification));
287 }
288
289 //-----------------------------------------------------------------------------
removeAnimation(CView * view,IdStringPtr name)290 void Animator::removeAnimation (CView* view, IdStringPtr name)
291 {
292 pImpl->animations.forEach ([&] (const SharedPointer<Detail::Animation>& animation) {
293 if (animation->view == view && animation->name == name)
294 {
295 #if DEBUG_LOG
296 DebugPrint ("animation removed: %p - %s\n", view, name);
297 #endif
298 if (animation->done == false)
299 {
300 animation->done = true;
301 animation->animationTarget->animationFinished (view, name, true);
302 }
303 pImpl->animations.remove (animation);
304 }
305 });
306 }
307
308 //-----------------------------------------------------------------------------
removeAnimations(CView * view)309 void Animator::removeAnimations (CView* view)
310 {
311 pImpl->animations.forEach ([&] (const SharedPointer<Detail::Animation>& animation) {
312 if (animation->view == view)
313 {
314 #if DEBUG_LOG
315 DebugPrint ("animation removed: %p - %s\n", view, animation->name.data ());
316 #endif
317 if (animation->done == false)
318 {
319 animation->done = true;
320 animation->animationTarget->animationFinished (view, animation->name.data (), true);
321 }
322 pImpl->animations.remove (animation);
323 }
324 });
325 }
326
327 //-----------------------------------------------------------------------------
onTimer()328 void Animator::onTimer ()
329 {
330 auto selfGuard = shared (this);
331 uint32_t currentTicks = IPlatformFrame::getTicks ();
332 pImpl->animations.forEach ([&] (SharedPointer<Detail::Animation>& animation) {
333 if (animation->startTime == 0)
334 {
335 #if DEBUG_LOG
336 DebugPrint ("animation start: %p - %s\n", animation->view.cast<CView>(), animation->name.data ());
337 #endif
338 animation->animationTarget->animationStart (animation->view, animation->name.data ());
339 animation->startTime = currentTicks;
340 }
341 uint32_t time = currentTicks - animation->startTime;
342 float pos = animation->timingFunction->getPosition (time);
343 if (pos != animation->lastPos)
344 {
345 animation->animationTarget->animationTick (animation->view, animation->name.data (), pos);
346 animation->lastPos = pos;
347 }
348 if (animation->timingFunction->isDone (time))
349 {
350 animation->done = true;
351 animation->animationTarget->animationFinished (animation->view, animation->name.data (), false);
352 #if DEBUG_LOG
353 DebugPrint ("animation finished: %p - %s\n", animation->view.cast<CView>(), animation->name.data ());
354 #endif
355 pImpl->animations.remove (animation);
356 }
357 });
358 if (pImpl->animations.empty ())
359 Detail::Timer::removeAnimator (this);
360 }
361
362 IdStringPtr kMsgAnimationFinished = "kMsgAnimationFinished";
363
364 }} // namespaces
365