170d44591ec4506b894d81bc2369fed3d593a8f1
[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_repeatAnim = ANIM_REPEAT_NONE;
364   m_currentState = ANIM_STATE_NONE;
365   m_currentProcess = ANIM_PROCESS_NONE;
366   m_queuedProcess = ANIM_PROCESS_NONE;
367   m_lastCondition = false;
368   m_length = 0;
369   m_delay = 0;
370   m_start = 0;
371   m_amount = 0;
372 }
373
374 CAnimation::CAnimation(const CAnimation &src)
375 {
376   *this = src;
377 }
378
379 CAnimation::~CAnimation()
380 {
381   for (unsigned int i = 0; i < m_effects.size(); i++)
382     delete m_effects[i];
383   m_effects.clear();
384 }
385
386 CAnimation &CAnimation::operator =(const CAnimation &src)
387 {
388   if (this == &src) return *this; // same
389   m_type = src.m_type;
390   m_reversible = src.m_reversible;
391   m_condition = src.m_condition;
392   m_repeatAnim = src.m_repeatAnim;
393   m_lastCondition = src.m_lastCondition;
394   m_queuedProcess = src.m_queuedProcess;
395   m_currentProcess = src.m_currentProcess;
396   m_currentState = src.m_currentState;
397   m_start = src.m_start;
398   m_length = src.m_length;
399   m_delay = src.m_delay;
400   m_amount = src.m_amount;
401   // clear all our effects
402   for (unsigned int i = 0; i < m_effects.size(); i++)
403     delete m_effects[i];
404   m_effects.clear();
405   // and assign the others across
406   for (unsigned int i = 0; i < src.m_effects.size(); i++)
407   {
408     CAnimEffect *newEffect = NULL;
409     if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
410       newEffect = new CFadeEffect(*(CFadeEffect *)src.m_effects[i]);
411     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
412       newEffect = new CZoomEffect(*(CZoomEffect *)src.m_effects[i]);
413     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
414       newEffect = new CSlideEffect(*(CSlideEffect *)src.m_effects[i]);
415     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
416              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
417              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
418       newEffect = new CRotateEffect(*(CRotateEffect *)src.m_effects[i]);
419     if (newEffect)
420       m_effects.push_back(newEffect);
421   }
422   return *this;
423 }
424
425 void CAnimation::Animate(unsigned int time, bool startAnim)
426 {
427   // First start any queued animations
428   if (m_queuedProcess == ANIM_PROCESS_NORMAL)
429   {
430     if (m_currentProcess == ANIM_PROCESS_REVERSE)
431       m_start = time - m_amount;  // reverse direction of animation
432     else
433       m_start = time;
434     m_currentProcess = ANIM_PROCESS_NORMAL;
435   }
436   else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
437   {
438     if (m_currentProcess == ANIM_PROCESS_NORMAL)
439       m_start = time - (m_length - m_amount); // reverse direction of animation
440     else if (m_currentProcess == ANIM_PROCESS_NONE)
441       m_start = time;
442     m_currentProcess = ANIM_PROCESS_REVERSE;
443   }
444   // reset the queued state once we've rendered to ensure allocation has occured
445   if (startAnim || m_queuedProcess == ANIM_PROCESS_REVERSE)
446     m_queuedProcess = ANIM_PROCESS_NONE;
447
448   // Update our animation process
449   if (m_currentProcess == ANIM_PROCESS_NORMAL)
450   {
451     if (time - m_start < m_delay)
452     {
453       m_amount = 0;
454       m_currentState = ANIM_STATE_DELAYED;
455     }
456     else if (time - m_start < m_length + m_delay)
457     {
458       m_amount = time - m_start - m_delay;
459       m_currentState = ANIM_STATE_IN_PROCESS;
460     }
461     else
462     {
463       m_amount = m_length;
464       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
465       { // pulsed anims auto-reverse
466         m_currentProcess = ANIM_PROCESS_REVERSE;
467         m_start = time;
468       }
469       else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
470       { // looped anims start over
471         m_amount = 0;
472         m_start = time;
473       }
474       else
475         m_currentState = ANIM_STATE_APPLIED;
476     }
477   }
478   else if (m_currentProcess == ANIM_PROCESS_REVERSE)
479   {
480     if (time - m_start < m_length)
481     {
482       m_amount = m_length - (time - m_start);
483       m_currentState = ANIM_STATE_IN_PROCESS;
484     }
485     else
486     {
487       m_amount = 0;
488       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
489       { // pulsed anims auto-reverse
490         m_currentProcess = ANIM_PROCESS_NORMAL;
491         m_start = time;
492       }
493       else
494         m_currentState = ANIM_STATE_APPLIED;
495     }
496   }
497 }
498
499 void CAnimation::ResetAnimation()
500 {
501   m_queuedProcess = ANIM_PROCESS_NONE;
502   m_currentProcess = ANIM_PROCESS_NONE;
503   m_currentState = ANIM_STATE_NONE;
504 }
505
506 void CAnimation::ApplyAnimation()
507 {
508   m_queuedProcess = ANIM_PROCESS_NONE;
509   if (m_repeatAnim == ANIM_REPEAT_PULSE)
510   { // pulsed anims auto-reverse
511     m_amount = m_length;
512     m_currentProcess = ANIM_PROCESS_REVERSE;
513     m_currentState = ANIM_STATE_IN_PROCESS;
514   }
515   else if (m_repeatAnim == ANIM_REPEAT_LOOP)
516   { // looped anims start over
517     m_amount = 0;
518     m_currentProcess = ANIM_PROCESS_NORMAL;
519     m_currentState = ANIM_STATE_IN_PROCESS;
520   }
521   else
522   { // set normal process, so that Calculate() knows that we're finishing for zero length effects
523     // it will be reset in RenderAnimation()
524     m_currentProcess = ANIM_PROCESS_NORMAL;
525     m_currentState = ANIM_STATE_APPLIED;
526     m_amount = m_length;
527   }
528   Calculate(CPoint());
529 }
530
531 void CAnimation::Calculate(const CPoint &center)
532 {
533   for (unsigned int i = 0; i < m_effects.size(); i++)
534   {
535     CAnimEffect *effect = m_effects[i];
536     if (effect->GetLength())
537       effect->Calculate(m_delay + m_amount, center);
538     else
539     { // effect has length zero, so either apply complete
540       if (m_currentProcess == ANIM_PROCESS_NORMAL)
541         effect->ApplyState(ANIM_STATE_APPLIED, center);
542       else
543         effect->ApplyState(ANIM_STATE_NONE, center);
544     }
545   }
546 }
547
548 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint &center)
549 {
550   if (m_currentProcess != ANIM_PROCESS_NONE)
551     Calculate(center);
552   // If we have finished an animation, reset the animation state
553   // We do this here (rather than in Animate()) as we need the
554   // currentProcess information in the UpdateStates() function of the
555   // window and control classes.
556   if (m_currentState == ANIM_STATE_APPLIED)
557   {
558     m_currentProcess = ANIM_PROCESS_NONE;
559     m_queuedProcess = ANIM_PROCESS_NONE;
560   }
561   if (m_currentState != ANIM_STATE_NONE)
562   {
563     for (unsigned int i = 0; i < m_effects.size(); i++)
564       matrix *= m_effects[i]->GetTransform();
565   }
566 }
567
568 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
569 {
570   m_queuedProcess = process;
571 }
572
573 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
574 {
575   CAnimation anim;
576   anim.m_type = type;
577   anim.AddEffect(new CFadeEffect(start, end, delay, length));
578   return anim;
579 }
580
581 bool CAnimation::CheckCondition()
582 {
583   return !m_condition || m_condition->Get();
584 }
585
586 void CAnimation::UpdateCondition(const CGUIListItem *item)
587 {
588   if (!m_condition)
589     return;
590   bool condition = m_condition->Get(item);
591   if (condition && !m_lastCondition)
592     QueueAnimation(ANIM_PROCESS_NORMAL);
593   else if (!condition && m_lastCondition)
594   {
595     if (m_reversible)
596       QueueAnimation(ANIM_PROCESS_REVERSE);
597     else
598       ResetAnimation();
599   }
600   m_lastCondition = condition;
601 }
602
603 void CAnimation::SetInitialCondition()
604 {
605   m_lastCondition = m_condition ? m_condition->Get() : false;
606   if (m_lastCondition)
607     ApplyAnimation();
608   else
609     ResetAnimation();
610 }
611
612 void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context)
613 {
614   if (!node || !node->FirstChild())
615     return;
616
617   // conditions and reversibility
618   const char *condition = node->Attribute("condition");
619   if (condition)
620     m_condition = g_infoManager.Register(condition, context);
621   const char *reverse = node->Attribute("reversible");
622   if (reverse && strcmpi(reverse, "false") == 0)
623     m_reversible = false;
624
625   const TiXmlElement *effect = node->FirstChildElement("effect");
626
627   CStdString type = node->FirstChild()->Value();
628   m_type = ANIM_TYPE_CONDITIONAL;
629   if (effect) // new layout
630     type = node->Attribute("type");
631
632   if (StringUtils::StartsWithNoCase(type, "visible")) m_type = ANIM_TYPE_VISIBLE;
633   else if (type.Equals("hidden")) m_type = ANIM_TYPE_HIDDEN;
634   else if (type.Equals("focus"))  m_type = ANIM_TYPE_FOCUS;
635   else if (type.Equals("unfocus"))  m_type = ANIM_TYPE_UNFOCUS;
636   else if (type.Equals("windowopen"))  m_type = ANIM_TYPE_WINDOW_OPEN;
637   else if (type.Equals("windowclose"))  m_type = ANIM_TYPE_WINDOW_CLOSE;
638   // sanity check
639   if (m_type == ANIM_TYPE_CONDITIONAL)
640   {
641     if (!m_condition)
642     {
643       CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)");
644       return;
645     }
646
647     // pulsed or loop animations
648     const char *pulse = node->Attribute("pulse");
649     if (pulse && strcmpi(pulse, "true") == 0)
650       m_repeatAnim = ANIM_REPEAT_PULSE;
651     const char *loop = node->Attribute("loop");
652     if (loop && strcmpi(loop, "true") == 0)
653       m_repeatAnim = ANIM_REPEAT_LOOP;
654   }
655
656   m_delay = 0xffffffff;
657   if (!effect)
658   { // old layout:
659     // <animation effect="fade" start="0" end="100" delay="10" time="2000" condition="blahdiblah" reversible="false">focus</animation>
660     CStdString type = node->Attribute("effect");
661     AddEffect(type, node, rect);
662   }
663   while (effect)
664   { // new layout:
665     // <animation type="focus" condition="blahdiblah" reversible="false">
666     //   <effect type="fade" start="0" end="100" delay="10" time="2000" />
667     //   ...
668     // </animation>
669     CStdString type = effect->Attribute("type");
670     AddEffect(type, effect, rect);
671     effect = effect->NextSiblingElement("effect");
672   }
673 }
674
675 void CAnimation::AddEffect(const CStdString &type, const TiXmlElement *node, const CRect &rect)
676 {
677   CAnimEffect *effect = NULL;
678   if (type.Equals("fade"))
679     effect = new CFadeEffect(node, m_type < 0);
680   else if (type.Equals("slide"))
681     effect = new CSlideEffect(node);
682   else if (type.Equals("rotate"))
683     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Z);
684   else if (type.Equals("rotatey"))
685     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Y);
686   else if (type.Equals("rotatex"))
687     effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_X);
688   else if (type.Equals("zoom"))
689     effect = new CZoomEffect(node, rect);
690
691   if (effect)
692     AddEffect(effect);
693 }
694
695 void CAnimation::AddEffect(CAnimEffect *effect)
696 {
697   m_effects.push_back(effect);
698   // our delay is the minimum of all the effect delays
699   if (effect->GetDelay() < m_delay)
700     m_delay = effect->GetDelay();
701   // our length is the maximum of all the effect lengths
702   if (effect->GetLength() > m_delay + m_length)
703     m_length = effect->GetLength() - m_delay;
704 }
705
706 CScroller::CScroller(unsigned int duration /* = 200 */, boost::shared_ptr<Tweener> tweener /* = NULL */)
707 {
708   m_scrollValue = 0;
709   m_delta = 0;
710   m_startTime = 0;
711   m_startPosition = 0;
712   m_hasResumePoint = false;
713   m_lastTime = 0;
714   m_duration = duration > 0 ? duration : 1;
715   m_pTweener = tweener;
716 }
717
718 CScroller::CScroller(const CScroller& right)
719 {
720   m_pTweener.reset();
721   *this = right;
722 }
723
724 CScroller& CScroller::operator=(const CScroller &right)
725 {
726   if (&right == this) return *this;
727
728   m_scrollValue = right.m_scrollValue;
729   m_delta = right.m_delta;
730   m_startTime = right.m_startTime;
731   m_startPosition = right.m_startPosition;
732   m_hasResumePoint = right.m_hasResumePoint;
733   m_lastTime = right.m_lastTime;
734   m_duration = right.m_duration;
735   m_pTweener = right.m_pTweener;
736   return *this;
737 }
738
739 CScroller::~CScroller()
740 {
741 }
742
743 void CScroller::ScrollTo(float endPos)
744 {
745   float delta = endPos - m_scrollValue;
746     // if there is scrolling running in same direction - set resume point
747   m_hasResumePoint = m_delta != 0 && delta * m_delta > 0 && m_pTweener ? m_pTweener->HasResumePoint() : 0;
748
749   m_delta = delta;
750   m_startPosition = m_scrollValue;
751   m_startTime = m_lastTime;
752 }
753
754 float CScroller::Tween(float progress)
755 {
756   if (m_pTweener)
757   {
758     if (m_hasResumePoint) // tweener with in_and_out easing
759     {
760       // time linear transformation (y = a*x + b): 0 -> resumePoint and 1 -> 1
761       // resumePoint = a * 0 + b and 1 = a * 1 + b
762       // a = 1 - resumePoint , b = resumePoint
763       // our resume point is 0.5
764       // a = 0.5 , b = 0.5
765       progress = 0.5f * progress + 0.5f;
766       // tweener value linear transformation (y = a*x + b): resumePointValue -> 0 and 1 -> 1
767       // 0 = a * resumePointValue and 1 = a * 1 + b
768       // a = 1 / ( 1 - resumePointValue) , b = -resumePointValue / (1 - resumePointValue)
769       // we assume resumePointValue = Tween(resumePoint) = Tween(0.5) = 0.5
770       // (this is true for tweener with in_and_out easing - it's rotationally symmetric about (0.5,0.5) continuous function)
771       // a = 2 , b = -1
772       return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1);
773     }
774     else 
775       return m_pTweener->Tween(progress, 0, 1, 1);
776   }
777   else
778     return progress;
779 }
780
781 bool CScroller::Update(unsigned int time)
782 {
783   m_lastTime = time;
784   if (m_delta != 0)
785   {
786     if (time - m_startTime >= m_duration) // we are finished
787     {
788       m_scrollValue = m_startPosition + m_delta;
789       m_startTime = 0;
790       m_hasResumePoint = false;
791       m_delta = 0;
792       m_startPosition = 0;
793     }
794     else
795       m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta;
796     return true;
797   }
798   else
799     return false;
800 }