Merge pull request #3144 from MartijnKaijser/win32_min_version
[vuplus_xbmc] / xbmc / guilib / VisibleEffect.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "VisibleEffect.h"
22 #include "GUIInfoManager.h"
23 #include "utils/log.h"
24 #include "addons/Skin.h" // for the effect time adjustments
25 #include "utils/StringUtils.h"
26 #include "Tween.h"
27 #include "utils/XBMCTinyXML.h"
28
29 using namespace std;
30
31 CAnimEffect::CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect)
32 {
33   m_effect = effect;
34   // defaults
35   m_delay = m_length = 0;
36   m_pTweener.reset();
37   // time and delay
38
39   float temp;
40   if (TIXML_SUCCESS == node->QueryFloatAttribute("time", &temp)) m_length = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
41   if (TIXML_SUCCESS == node->QueryFloatAttribute("delay", &temp)) m_delay = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
42
43   m_pTweener = GetTweener(node);
44 }
45
46 CAnimEffect::CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect)
47 {
48   m_delay = delay;
49   m_length = length;
50   m_effect = effect;
51   m_pTweener = boost::shared_ptr<Tweener>(new LinearTweener());
52 }
53
54 CAnimEffect::~CAnimEffect()
55 {
56 }
57
58 CAnimEffect::CAnimEffect(const CAnimEffect &src)
59 {
60   m_pTweener.reset();
61   *this = src;
62 }
63
64 CAnimEffect& CAnimEffect::operator=(const CAnimEffect &src)
65 {
66   if (&src == this) return *this;
67
68   m_matrix = src.m_matrix;
69   m_effect = src.m_effect;
70   m_length = src.m_length;
71   m_delay = src.m_delay;
72
73   m_pTweener = src.m_pTweener;
74   return *this;
75 }
76
77 void CAnimEffect::Calculate(unsigned int time, const CPoint &center)
78 {
79   assert(m_delay + m_length);
80   // calculate offset and tweening
81   float offset = 0.0f;  // delayed forward, or finished reverse
82   if (time >= m_delay && time < m_delay + m_length)
83     offset = (float)(time - m_delay) / m_length;
84   else if (time >= m_delay + m_length)
85     offset = 1.0f;
86   if (m_pTweener)
87     offset = m_pTweener->Tween(offset, 0.0f, 1.0f, 1.0f);
88   // and apply the effect
89   ApplyEffect(offset, center);
90 }
91
92 void CAnimEffect::ApplyState(ANIMATION_STATE state, const CPoint &center)
93 {
94   float offset = (state == ANIM_STATE_APPLIED) ? 1.0f : 0.0f;
95   ApplyEffect(offset, center);
96 }
97
98 boost::shared_ptr<Tweener> CAnimEffect::GetTweener(const TiXmlElement *pAnimationNode)
99 {
100   boost::shared_ptr<Tweener> m_pTweener;
101   const char *tween = pAnimationNode->Attribute("tween");
102   if (tween)
103   {
104     if (strcmpi(tween, "linear")==0)
105       m_pTweener = boost::shared_ptr<Tweener>(new LinearTweener());
106     else if (strcmpi(tween, "quadratic")==0)
107       m_pTweener = boost::shared_ptr<Tweener>(new QuadTweener());
108     else if (strcmpi(tween, "cubic")==0)
109       m_pTweener = boost::shared_ptr<Tweener>(new CubicTweener());
110     else if (strcmpi(tween, "sine")==0)
111       m_pTweener = boost::shared_ptr<Tweener>(new SineTweener());
112     else if (strcmpi(tween, "back")==0)
113       m_pTweener = boost::shared_ptr<Tweener>(new BackTweener());
114     else if (strcmpi(tween, "circle")==0)
115       m_pTweener = boost::shared_ptr<Tweener>(new CircleTweener());
116     else if (strcmpi(tween, "bounce")==0)
117       m_pTweener = boost::shared_ptr<Tweener>(new BounceTweener());
118     else if (strcmpi(tween, "elastic")==0)
119       m_pTweener = boost::shared_ptr<Tweener>(new ElasticTweener());
120
121     const char *easing = pAnimationNode->Attribute("easing");
122     if (m_pTweener && easing)
123     {
124       if (strcmpi(easing, "in")==0)
125         m_pTweener->SetEasing(EASE_IN);
126       else if (strcmpi(easing, "out")==0)
127         m_pTweener->SetEasing(EASE_OUT);
128       else if (strcmpi(easing, "inout")==0)
129         m_pTweener->SetEasing(EASE_INOUT);
130     }
131   }
132
133   float accel = 0;
134   pAnimationNode->QueryFloatAttribute("acceleration", &accel);
135
136   if (!m_pTweener)
137   { // no tweener is specified - use a linear tweener
138     // or quadratic if we have acceleration
139     if (accel)
140     {
141       m_pTweener = boost::shared_ptr<Tweener>(new QuadTweener(accel));
142       m_pTweener->SetEasing(EASE_IN);
143     }
144     else
145       m_pTweener = boost::shared_ptr<Tweener>(new LinearTweener());
146   }
147
148   return m_pTweener;
149 }
150
151 CFadeEffect::CFadeEffect(const TiXmlElement *node, bool reverseDefaults) : CAnimEffect(node, EFFECT_TYPE_FADE)
152 {
153   if (reverseDefaults)
154   { // out effect defaults
155     m_startAlpha = 100.0f;
156     m_endAlpha = 0;
157   }
158   else
159   { // in effect defaults
160     m_startAlpha = 0;
161     m_endAlpha = 100.0f;
162   }
163   node->QueryFloatAttribute("start", &m_startAlpha);
164   node->QueryFloatAttribute("end", &m_endAlpha);
165   if (m_startAlpha > 100.0f) m_startAlpha = 100.0f;
166   if (m_endAlpha > 100.0f) m_endAlpha = 100.0f;
167   if (m_startAlpha < 0) m_startAlpha = 0;
168   if (m_endAlpha < 0) m_endAlpha = 0;
169 }
170
171 CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE)
172 {
173   m_startAlpha = start;
174   m_endAlpha = end;
175 }
176
177 void CFadeEffect::ApplyEffect(float offset, const CPoint &center)
178 {
179   m_matrix.SetFader(((m_endAlpha - m_startAlpha) * offset + m_startAlpha) * 0.01f);
180 }
181
182 CSlideEffect::CSlideEffect(const TiXmlElement *node) : CAnimEffect(node, EFFECT_TYPE_SLIDE)
183 {
184   m_startX = m_endX = 0;
185   m_startY = m_endY = 0;
186   const char *startPos = node->Attribute("start");
187   if (startPos)
188   {
189     vector<CStdString> commaSeparated;
190     StringUtils::SplitString(startPos, ",", commaSeparated);
191     if (commaSeparated.size() > 1)
192       m_startY = (float)atof(commaSeparated[1].c_str());
193     m_startX = (float)atof(commaSeparated[0].c_str());
194   }
195   const char *endPos = node->Attribute("end");
196   if (endPos)
197   {
198     vector<CStdString> commaSeparated;
199     StringUtils::SplitString(endPos, ",", commaSeparated);
200     if (commaSeparated.size() > 1)
201       m_endY = (float)atof(commaSeparated[1].c_str());
202     m_endX = (float)atof(commaSeparated[0].c_str());
203   }
204 }
205
206 void CSlideEffect::ApplyEffect(float offset, const CPoint &center)
207 {
208   m_matrix.SetTranslation((m_endX - m_startX)*offset + m_startX, (m_endY - m_startY)*offset + m_startY, 0);
209 }
210
211 CRotateEffect::CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect) : CAnimEffect(node, effect)
212 {
213   m_startAngle = m_endAngle = 0;
214   m_autoCenter = false;
215   node->QueryFloatAttribute("start", &m_startAngle);
216   node->QueryFloatAttribute("end", &m_endAngle);
217
218   // convert to a negative to account for our reversed Y axis (Needed for X and Z ???)
219   m_startAngle *= -1;
220   m_endAngle *= -1;
221
222   const char *centerPos = node->Attribute("center");
223   if (centerPos)
224   {
225     if (strcmpi(centerPos, "auto") == 0)
226       m_autoCenter = true;
227     else
228     {
229       vector<CStdString> commaSeparated;
230       StringUtils::SplitString(centerPos, ",", commaSeparated);
231       if (commaSeparated.size() > 1)
232         m_center.y = (float)atof(commaSeparated[1].c_str());
233       m_center.x = (float)atof(commaSeparated[0].c_str());
234     }
235   }
236 }
237
238 void CRotateEffect::ApplyEffect(float offset, const CPoint &center)
239 {
240   static const float degree_to_radian = 0.01745329252f;
241   if (m_autoCenter)
242     m_center = center;
243   if (m_effect == EFFECT_TYPE_ROTATE_X)
244     m_matrix.SetXRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
245   else if (m_effect == EFFECT_TYPE_ROTATE_Y)
246     m_matrix.SetYRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
247   else if (m_effect == EFFECT_TYPE_ROTATE_Z) // note coordinate aspect ratio is not generally square in the XY plane, so correct for it.
248     m_matrix.SetZRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, g_graphicsContext.GetScalingPixelRatio());
249 }
250
251 CZoomEffect::CZoomEffect(const TiXmlElement *node, const CRect &rect) : CAnimEffect(node, EFFECT_TYPE_ZOOM), m_center(CPoint(0,0))
252 {
253   // effect defaults
254   m_startX = m_startY = 100;
255   m_endX = m_endY = 100;
256   m_autoCenter = false;
257
258   float startPosX = rect.x1;
259   float startPosY = rect.y1;
260   float endPosX = rect.x1;
261   float endPosY = rect.y1;
262
263   float width = max(rect.Width(), 0.001f);
264   float height = max(rect.Height(),0.001f);
265
266   const char *start = node->Attribute("start");
267   if (start)
268   {
269     CStdStringArray params;
270     StringUtils::SplitString(start, ",", params);
271     if (params.size() == 1)
272     {
273       m_startX = (float)atof(params[0].c_str());
274       m_startY = m_startX;
275     }
276     else if (params.size() == 2)
277     {
278       m_startX = (float)atof(params[0].c_str());
279       m_startY = (float)atof(params[1].c_str());
280     }
281     else if (params.size() == 4)
282     { // format is start="x,y,width,height"
283       // use width and height from our rect to calculate our sizing
284       startPosX = (float)atof(params[0].c_str());
285       startPosY = (float)atof(params[1].c_str());
286       m_startX = (float)atof(params[2].c_str());
287       m_startY = (float)atof(params[3].c_str());
288       m_startX *= 100.0f / width;
289       m_startY *= 100.0f / height;
290     }
291   }
292   const char *end = node->Attribute("end");
293   if (end)
294   {
295     CStdStringArray params;
296     StringUtils::SplitString(end, ",", params);
297     if (params.size() == 1)
298     {
299       m_endX = (float)atof(params[0].c_str());
300       m_endY = m_endX;
301     }
302     else if (params.size() == 2)
303     {
304       m_endX = (float)atof(params[0].c_str());
305       m_endY = (float)atof(params[1].c_str());
306     }
307     else if (params.size() == 4)
308     { // format is start="x,y,width,height"
309       // use width and height from our rect to calculate our sizing
310       endPosX = (float)atof(params[0].c_str());
311       endPosY = (float)atof(params[1].c_str());
312       m_endX = (float)atof(params[2].c_str());
313       m_endY = (float)atof(params[3].c_str());
314       m_endX *= 100.0f / width;
315       m_endY *= 100.0f / height;
316     }
317   }
318   const char *centerPos = node->Attribute("center");
319   if (centerPos)
320   {
321     if (strcmpi(centerPos, "auto") == 0)
322       m_autoCenter = true;
323     else
324     {
325       vector<CStdString> commaSeparated;
326       StringUtils::SplitString(centerPos, ",", commaSeparated);
327       if (commaSeparated.size() > 1)
328         m_center.y = (float)atof(commaSeparated[1].c_str());
329       m_center.x = (float)atof(commaSeparated[0].c_str());
330     }
331   }
332   else
333   { // no center specified
334     // calculate the center position...
335     if (m_startX)
336     {
337       float scale = m_endX / m_startX;
338       if (scale != 1)
339         m_center.x = (endPosX - scale*startPosX) / (1 - scale);
340     }
341     if (m_startY)
342     {
343       float scale = m_endY / m_startY;
344       if (scale != 1)
345         m_center.y = (endPosY - scale*startPosY) / (1 - scale);
346     }
347   }
348 }
349
350 void CZoomEffect::ApplyEffect(float offset, const CPoint &center)
351 {
352   if (m_autoCenter)
353     m_center = center;
354   float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f;
355   float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f;
356   m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y);
357 }
358
359 CAnimation::CAnimation()
360 {
361   m_type = ANIM_TYPE_NONE;
362   m_reversible = true;
363   m_condition = 0;
364   m_repeatAnim = ANIM_REPEAT_NONE;
365   m_currentState = ANIM_STATE_NONE;
366   m_currentProcess = ANIM_PROCESS_NONE;
367   m_queuedProcess = ANIM_PROCESS_NONE;
368   m_lastCondition = false;
369   m_length = 0;
370   m_delay = 0;
371   m_start = 0;
372   m_amount = 0;
373 }
374
375 CAnimation::CAnimation(const CAnimation &src)
376 {
377   *this = src;
378 }
379
380 CAnimation::~CAnimation()
381 {
382   for (unsigned int i = 0; i < m_effects.size(); i++)
383     delete m_effects[i];
384   m_effects.clear();
385 }
386
387 CAnimation &CAnimation::operator =(const CAnimation &src)
388 {
389   if (this == &src) return *this; // same
390   m_type = src.m_type;
391   m_reversible = src.m_reversible;
392   m_condition = src.m_condition; // TODO: register/unregister
393   m_repeatAnim = src.m_repeatAnim;
394   m_lastCondition = src.m_lastCondition;
395   m_queuedProcess = src.m_queuedProcess;
396   m_currentProcess = src.m_currentProcess;
397   m_currentState = src.m_currentState;
398   m_start = src.m_start;
399   m_length = src.m_length;
400   m_delay = src.m_delay;
401   m_amount = src.m_amount;
402   // clear all our effects
403   for (unsigned int i = 0; i < m_effects.size(); i++)
404     delete m_effects[i];
405   m_effects.clear();
406   // and assign the others across
407   for (unsigned int i = 0; i < src.m_effects.size(); i++)
408   {
409     CAnimEffect *newEffect = NULL;
410     if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
411       newEffect = new CFadeEffect(*(CFadeEffect *)src.m_effects[i]);
412     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
413       newEffect = new CZoomEffect(*(CZoomEffect *)src.m_effects[i]);
414     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
415       newEffect = new CSlideEffect(*(CSlideEffect *)src.m_effects[i]);
416     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
417              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
418              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
419       newEffect = new CRotateEffect(*(CRotateEffect *)src.m_effects[i]);
420     if (newEffect)
421       m_effects.push_back(newEffect);
422   }
423   return *this;
424 }
425
426 void CAnimation::Animate(unsigned int time, bool startAnim)
427 {
428   // First start any queued animations
429   if (m_queuedProcess == ANIM_PROCESS_NORMAL)
430   {
431     if (m_currentProcess == ANIM_PROCESS_REVERSE)
432       m_start = time - m_amount;  // reverse direction of animation
433     else
434       m_start = time;
435     m_currentProcess = ANIM_PROCESS_NORMAL;
436   }
437   else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
438   {
439     if (m_currentProcess == ANIM_PROCESS_NORMAL)
440       m_start = time - (m_length - m_amount); // reverse direction of animation
441     else if (m_currentProcess == ANIM_PROCESS_NONE)
442       m_start = time;
443     m_currentProcess = ANIM_PROCESS_REVERSE;
444   }
445   // reset the queued state once we've rendered to ensure allocation has occured
446   if (startAnim || m_queuedProcess == ANIM_PROCESS_REVERSE)
447     m_queuedProcess = ANIM_PROCESS_NONE;
448
449   // Update our animation process
450   if (m_currentProcess == ANIM_PROCESS_NORMAL)
451   {
452     if (time - m_start < m_delay)
453     {
454       m_amount = 0;
455       m_currentState = ANIM_STATE_DELAYED;
456     }
457     else if (time - m_start < m_length + m_delay)
458     {
459       m_amount = time - m_start - m_delay;
460       m_currentState = ANIM_STATE_IN_PROCESS;
461     }
462     else
463     {
464       m_amount = m_length;
465       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
466       { // pulsed anims auto-reverse
467         m_currentProcess = ANIM_PROCESS_REVERSE;
468         m_start = time;
469       }
470       else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
471       { // looped anims start over
472         m_amount = 0;
473         m_start = time;
474       }
475       else
476         m_currentState = ANIM_STATE_APPLIED;
477     }
478   }
479   else if (m_currentProcess == ANIM_PROCESS_REVERSE)
480   {
481     if (time - m_start < m_length)
482     {
483       m_amount = m_length - (time - m_start);
484       m_currentState = ANIM_STATE_IN_PROCESS;
485     }
486     else
487     {
488       m_amount = 0;
489       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
490       { // pulsed anims auto-reverse
491         m_currentProcess = ANIM_PROCESS_NORMAL;
492         m_start = time;
493       }
494       else
495         m_currentState = ANIM_STATE_APPLIED;
496     }
497   }
498 }
499
500 void CAnimation::ResetAnimation()
501 {
502   m_queuedProcess = ANIM_PROCESS_NONE;
503   m_currentProcess = ANIM_PROCESS_NONE;
504   m_currentState = ANIM_STATE_NONE;
505 }
506
507 void CAnimation::ApplyAnimation()
508 {
509   m_queuedProcess = ANIM_PROCESS_NONE;
510   if (m_repeatAnim == ANIM_REPEAT_PULSE)
511   { // pulsed anims auto-reverse
512     m_amount = m_length;
513     m_currentProcess = ANIM_PROCESS_REVERSE;
514     m_currentState = ANIM_STATE_IN_PROCESS;
515   }
516   else if (m_repeatAnim == ANIM_REPEAT_LOOP)
517   { // looped anims start over
518     m_amount = 0;
519     m_currentProcess = ANIM_PROCESS_NORMAL;
520     m_currentState = ANIM_STATE_IN_PROCESS;
521   }
522   else
523   { // set normal process, so that Calculate() knows that we're finishing for zero length effects
524     // it will be reset in RenderAnimation()
525     m_currentProcess = ANIM_PROCESS_NORMAL;
526     m_currentState = ANIM_STATE_APPLIED;
527     m_amount = m_length;
528   }
529   Calculate(CPoint());
530 }
531
532 void CAnimation::Calculate(const CPoint &center)
533 {
534   for (unsigned int i = 0; i < m_effects.size(); i++)
535   {
536     CAnimEffect *effect = m_effects[i];
537     if (effect->GetLength())
538       effect->Calculate(m_delay + m_amount, center);
539     else
540     { // effect has length zero, so either apply complete
541       if (m_currentProcess == ANIM_PROCESS_NORMAL)
542         effect->ApplyState(ANIM_STATE_APPLIED, center);
543       else
544         effect->ApplyState(ANIM_STATE_NONE, center);
545     }
546   }
547 }
548
549 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint &center)
550 {
551   if (m_currentProcess != ANIM_PROCESS_NONE)
552     Calculate(center);
553   // If we have finished an animation, reset the animation state
554   // We do this here (rather than in Animate()) as we need the
555   // currentProcess information in the UpdateStates() function of the
556   // window and control classes.
557   if (m_currentState == ANIM_STATE_APPLIED)
558   {
559     m_currentProcess = ANIM_PROCESS_NONE;
560     m_queuedProcess = ANIM_PROCESS_NONE;
561   }
562   if (m_currentState != ANIM_STATE_NONE)
563   {
564     for (unsigned int i = 0; i < m_effects.size(); i++)
565       matrix *= m_effects[i]->GetTransform();
566   }
567 }
568
569 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
570 {
571   m_queuedProcess = process;
572 }
573
574 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
575 {
576   CAnimation anim;
577   anim.m_type = type;
578   anim.AddEffect(new CFadeEffect(start, end, delay, length));
579   return anim;
580 }
581
582 bool CAnimation::CheckCondition()
583 {
584   return !m_condition || g_infoManager.GetBoolValue(m_condition);
585 }
586
587 void CAnimation::UpdateCondition(const CGUIListItem *item)
588 {
589   bool condition = g_infoManager.GetBoolValue(m_condition, item);
590   if (condition && !m_lastCondition)
591     QueueAnimation(ANIM_PROCESS_NORMAL);
592   else if (!condition && m_lastCondition)
593   {
594     if (m_reversible)
595       QueueAnimation(ANIM_PROCESS_REVERSE);
596     else
597       ResetAnimation();
598   }
599   m_lastCondition = condition;
600 }
601
602 void CAnimation::SetInitialCondition()
603 {
604   m_lastCondition = g_infoManager.GetBoolValue(m_condition);
605   if (m_lastCondition)
606     ApplyAnimation();
607   else
608     ResetAnimation();
609 }
610
611 void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context)
612 {
613   if (!node || !node->FirstChild())
614     return;
615
616   // conditions and reversibility
617   const char *condition = node->Attribute("condition");
618   if (condition)
619     m_condition = g_infoManager.Register(condition, context);
620   const char *reverse = node->Attribute("reversible");
621   if (reverse && strcmpi(reverse, "false") == 0)
622     m_reversible = false;
623
624   const TiXmlElement *effect = node->FirstChildElement("effect");
625
626   CStdString type = node->FirstChild()->Value();
627   m_type = ANIM_TYPE_CONDITIONAL;
628   if (effect) // new layout
629     type = node->Attribute("type");
630
631   if (type.Left(7).Equals("visible")) m_type = ANIM_TYPE_VISIBLE;
632   else if (type.Equals("hidden")) m_type = ANIM_TYPE_HIDDEN;
633   else if (type.Equals("focus"))  m_type = ANIM_TYPE_FOCUS;
634   else if (type.Equals("unfocus"))  m_type = ANIM_TYPE_UNFOCUS;
635   else if (type.Equals("windowopen"))  m_type = ANIM_TYPE_WINDOW_OPEN;
636   else if (type.Equals("windowclose"))  m_type = ANIM_TYPE_WINDOW_CLOSE;
637   // sanity check
638   if (m_type == ANIM_TYPE_CONDITIONAL)
639   {
640     if (!m_condition)
641     {
642       CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)");
643       return;
644     }
645
646     // pulsed or loop animations
647     const char *pulse = node->Attribute("pulse");
648     if (pulse && strcmpi(pulse, "true") == 0)
649       m_repeatAnim = ANIM_REPEAT_PULSE;
650     const char *loop = node->Attribute("loop");
651     if (loop && strcmpi(loop, "true") == 0)
652       m_repeatAnim = ANIM_REPEAT_LOOP;
653   }
654
655   m_delay = 0xffffffff;
656   if (!effect)
657   { // old layout:
658     // <animation effect="fade" start="0" end="100" delay="10" time="2000" condition="blahdiblah" reversible="false">focus</animation>
659     CStdString type = node->Attribute("effect");
660     AddEffect(type, node, rect);
661   }
662   while (effect)
663   { // new layout:
664     // <animation type="focus" condition="blahdiblah" reversible="false">
665     //   <effect type="fade" start="0" end="100" delay="10" time="2000" />
666     //   ...
667     // </animation>
668     CStdString type = effect->Attribute("type");
669     AddEffect(type, effect, rect);
670     effect = effect->NextSiblingElement("effect");
671   }
672 }
673
674 void CAnimation::AddEffect(const CStdString &type, const TiXmlElement *node, const CRect &rect)
675 {
676   CAnimEffect *effect = NULL;
677   if (type.Equals("fade"))
678     effect = new CFadeEffect(node, m_type < 0);
679   else if (type.Equals("slide"))
680     effect = new CSlideEffect(node);
681   else if (type.Equals("rotate"))
682     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Z);
683   else if (type.Equals("rotatey"))
684     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Y);
685   else if (type.Equals("rotatex"))
686     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_X);
687   else if (type.Equals("zoom"))
688     effect = new CZoomEffect(node, rect);
689
690   if (effect)
691     AddEffect(effect);
692 }
693
694 void CAnimation::AddEffect(CAnimEffect *effect)
695 {
696   m_effects.push_back(effect);
697   // our delay is the minimum of all the effect delays
698   if (effect->GetDelay() < m_delay)
699     m_delay = effect->GetDelay();
700   // our length is the maximum of all the effect lengths
701   if (effect->GetLength() > m_delay + m_length)
702     m_length = effect->GetLength() - m_delay;
703 }
704
705 CScroller::CScroller(unsigned int duration /* = 200 */, boost::shared_ptr<Tweener> tweener /* = NULL */)
706 {
707   m_scrollValue = 0;
708   m_delta = 0;
709   m_startTime = 0;
710   m_startPosition = 0;
711   m_hasResumePoint = false;
712   m_lastTime = 0;
713   m_duration = duration > 0 ? duration : 1;
714   m_pTweener = tweener;
715 }
716
717 CScroller::CScroller(const CScroller& right)
718 {
719   m_pTweener.reset();
720   *this = right;
721 }
722
723 CScroller& CScroller::operator=(const CScroller &right)
724 {
725   if (&right == this) return *this;
726
727   m_scrollValue = right.m_scrollValue;
728   m_delta = right.m_delta;
729   m_startTime = right.m_startTime;
730   m_startPosition = right.m_startPosition;
731   m_hasResumePoint = right.m_hasResumePoint;
732   m_lastTime = right.m_lastTime;
733   m_duration = right.m_duration;
734   m_pTweener = right.m_pTweener;
735   return *this;
736 }
737
738 CScroller::~CScroller()
739 {
740 }
741
742 void CScroller::ScrollTo(float endPos)
743 {
744   float delta = endPos - m_scrollValue;
745     // if there is scrolling running in same direction - set resume point
746   m_hasResumePoint = m_delta != 0 && delta * m_delta > 0 && m_pTweener ? m_pTweener->HasResumePoint() : 0;
747
748   m_delta = delta;
749   m_startPosition = m_scrollValue;
750   m_startTime = m_lastTime;
751 }
752
753 float CScroller::Tween(float progress)
754 {
755   if (m_pTweener)
756   {
757     if (m_hasResumePoint) // tweener with in_and_out easing
758     {
759       // time linear transformation (y = a*x + b): 0 -> resumePoint and 1 -> 1
760       // resumePoint = a * 0 + b and 1 = a * 1 + b
761       // a = 1 - resumePoint , b = resumePoint
762       // our resume point is 0.5
763       // a = 0.5 , b = 0.5
764       progress = 0.5f * progress + 0.5f;
765       // tweener value linear transformation (y = a*x + b): resumePointValue -> 0 and 1 -> 1
766       // 0 = a * resumePointValue and 1 = a * 1 + b
767       // a = 1 / ( 1 - resumePointValue) , b = -resumePointValue / (1 - resumePointValue)
768       // we assume resumePointValue = Tween(resumePoint) = Tween(0.5) = 0.5
769       // (this is true for tweener with in_and_out easing - it's rotationally symmetric about (0.5,0.5) continuous function)
770       // a = 2 , b = -1
771       return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1);
772     }
773     else 
774       return m_pTweener->Tween(progress, 0, 1, 1);
775   }
776   else
777     return progress;
778 }
779
780 bool CScroller::Update(unsigned int time)
781 {
782   m_lastTime = time;
783   if (m_delta != 0)
784   {
785     if (time - m_startTime >= m_duration) // we are finished
786     {
787       m_scrollValue = m_startPosition + m_delta;
788       m_startTime = 0;
789       m_hasResumePoint = false;
790       m_delta = 0;
791       m_startPosition = 0;
792     }
793     else
794       m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta;
795     return true;
796   }
797   else
798     return false;
799 }