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