Merge pull request #2957 from jmbreuer/upstream-libdvdnav-seek
[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 const 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)
252 {
253   // effect defaults
254   m_startX = m_startY = 100;
255   m_endX = m_endY = 100;
256   m_center = CPoint(0,0);
257   m_autoCenter = false;
258
259   float startPosX = rect.x1;
260   float startPosY = rect.y1;
261   float endPosX = rect.x1;
262   float endPosY = rect.y1;
263
264   float width = max(rect.Width(), 0.001f);
265   float height = max(rect.Height(),0.001f);
266
267   const char *start = node->Attribute("start");
268   if (start)
269   {
270     CStdStringArray params;
271     StringUtils::SplitString(start, ",", params);
272     if (params.size() == 1)
273     {
274       m_startX = (float)atof(params[0].c_str());
275       m_startY = m_startX;
276     }
277     else if (params.size() == 2)
278     {
279       m_startX = (float)atof(params[0].c_str());
280       m_startY = (float)atof(params[1].c_str());
281     }
282     else if (params.size() == 4)
283     { // format is start="x,y,width,height"
284       // use width and height from our rect to calculate our sizing
285       startPosX = (float)atof(params[0].c_str());
286       startPosY = (float)atof(params[1].c_str());
287       m_startX = (float)atof(params[2].c_str());
288       m_startY = (float)atof(params[3].c_str());
289       m_startX *= 100.0f / width;
290       m_startY *= 100.0f / height;
291     }
292   }
293   const char *end = node->Attribute("end");
294   if (end)
295   {
296     CStdStringArray params;
297     StringUtils::SplitString(end, ",", params);
298     if (params.size() == 1)
299     {
300       m_endX = (float)atof(params[0].c_str());
301       m_endY = m_endX;
302     }
303     else if (params.size() == 2)
304     {
305       m_endX = (float)atof(params[0].c_str());
306       m_endY = (float)atof(params[1].c_str());
307     }
308     else if (params.size() == 4)
309     { // format is start="x,y,width,height"
310       // use width and height from our rect to calculate our sizing
311       endPosX = (float)atof(params[0].c_str());
312       endPosY = (float)atof(params[1].c_str());
313       m_endX = (float)atof(params[2].c_str());
314       m_endY = (float)atof(params[3].c_str());
315       m_endX *= 100.0f / width;
316       m_endY *= 100.0f / height;
317     }
318   }
319   const char *centerPos = node->Attribute("center");
320   if (centerPos)
321   {
322     if (strcmpi(centerPos, "auto") == 0)
323       m_autoCenter = true;
324     else
325     {
326       vector<CStdString> commaSeparated;
327       StringUtils::SplitString(centerPos, ",", commaSeparated);
328       if (commaSeparated.size() > 1)
329         m_center.y = (float)atof(commaSeparated[1].c_str());
330       m_center.x = (float)atof(commaSeparated[0].c_str());
331     }
332   }
333   else
334   { // no center specified
335     // calculate the center position...
336     if (m_startX)
337     {
338       float scale = m_endX / m_startX;
339       if (scale != 1)
340         m_center.x = (endPosX - scale*startPosX) / (1 - scale);
341     }
342     if (m_startY)
343     {
344       float scale = m_endY / m_startY;
345       if (scale != 1)
346         m_center.y = (endPosY - scale*startPosY) / (1 - scale);
347     }
348   }
349 }
350
351 void CZoomEffect::ApplyEffect(float offset, const CPoint &center)
352 {
353   if (m_autoCenter)
354     m_center = center;
355   float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f;
356   float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f;
357   m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y);
358 }
359
360 CAnimation::CAnimation()
361 {
362   m_type = ANIM_TYPE_NONE;
363   m_reversible = true;
364   m_condition = 0;
365   m_repeatAnim = ANIM_REPEAT_NONE;
366   m_currentState = ANIM_STATE_NONE;
367   m_currentProcess = ANIM_PROCESS_NONE;
368   m_queuedProcess = ANIM_PROCESS_NONE;
369   m_lastCondition = false;
370   m_length = 0;
371   m_delay = 0;
372   m_start = 0;
373   m_amount = 0;
374 }
375
376 CAnimation::CAnimation(const CAnimation &src)
377 {
378   *this = src;
379 }
380
381 CAnimation::~CAnimation()
382 {
383   for (unsigned int i = 0; i < m_effects.size(); i++)
384     delete m_effects[i];
385   m_effects.clear();
386 }
387
388 const CAnimation &CAnimation::operator =(const CAnimation &src)
389 {
390   if (this == &src) return *this; // same
391   m_type = src.m_type;
392   m_reversible = src.m_reversible;
393   m_condition = src.m_condition; // TODO: register/unregister
394   m_repeatAnim = src.m_repeatAnim;
395   m_lastCondition = src.m_lastCondition;
396   m_queuedProcess = src.m_queuedProcess;
397   m_currentProcess = src.m_currentProcess;
398   m_currentState = src.m_currentState;
399   m_start = src.m_start;
400   m_length = src.m_length;
401   m_delay = src.m_delay;
402   m_amount = src.m_amount;
403   // clear all our effects
404   for (unsigned int i = 0; i < m_effects.size(); i++)
405     delete m_effects[i];
406   m_effects.clear();
407   // and assign the others across
408   for (unsigned int i = 0; i < src.m_effects.size(); i++)
409   {
410     CAnimEffect *newEffect = NULL;
411     if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
412       newEffect = new CFadeEffect(*(CFadeEffect *)src.m_effects[i]);
413     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
414       newEffect = new CZoomEffect(*(CZoomEffect *)src.m_effects[i]);
415     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
416       newEffect = new CSlideEffect(*(CSlideEffect *)src.m_effects[i]);
417     else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
418              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
419              src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
420       newEffect = new CRotateEffect(*(CRotateEffect *)src.m_effects[i]);
421     if (newEffect)
422       m_effects.push_back(newEffect);
423   }
424   return *this;
425 }
426
427 void CAnimation::Animate(unsigned int time, bool startAnim)
428 {
429   // First start any queued animations
430   if (m_queuedProcess == ANIM_PROCESS_NORMAL)
431   {
432     if (m_currentProcess == ANIM_PROCESS_REVERSE)
433       m_start = time - m_amount;  // reverse direction of animation
434     else
435       m_start = time;
436     m_currentProcess = ANIM_PROCESS_NORMAL;
437   }
438   else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
439   {
440     if (m_currentProcess == ANIM_PROCESS_NORMAL)
441       m_start = time - (m_length - m_amount); // reverse direction of animation
442     else if (m_currentProcess == ANIM_PROCESS_NONE)
443       m_start = time;
444     m_currentProcess = ANIM_PROCESS_REVERSE;
445   }
446   // reset the queued state once we've rendered to ensure allocation has occured
447   if (startAnim || m_queuedProcess == ANIM_PROCESS_REVERSE)
448     m_queuedProcess = ANIM_PROCESS_NONE;
449
450   // Update our animation process
451   if (m_currentProcess == ANIM_PROCESS_NORMAL)
452   {
453     if (time - m_start < m_delay)
454     {
455       m_amount = 0;
456       m_currentState = ANIM_STATE_DELAYED;
457     }
458     else if (time - m_start < m_length + m_delay)
459     {
460       m_amount = time - m_start - m_delay;
461       m_currentState = ANIM_STATE_IN_PROCESS;
462     }
463     else
464     {
465       m_amount = m_length;
466       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
467       { // pulsed anims auto-reverse
468         m_currentProcess = ANIM_PROCESS_REVERSE;
469         m_start = time;
470       }
471       else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
472       { // looped anims start over
473         m_amount = 0;
474         m_start = time;
475       }
476       else
477         m_currentState = ANIM_STATE_APPLIED;
478     }
479   }
480   else if (m_currentProcess == ANIM_PROCESS_REVERSE)
481   {
482     if (time - m_start < m_length)
483     {
484       m_amount = m_length - (time - m_start);
485       m_currentState = ANIM_STATE_IN_PROCESS;
486     }
487     else
488     {
489       m_amount = 0;
490       if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
491       { // pulsed anims auto-reverse
492         m_currentProcess = ANIM_PROCESS_NORMAL;
493         m_start = time;
494       }
495       else
496         m_currentState = ANIM_STATE_APPLIED;
497     }
498   }
499 }
500
501 void CAnimation::ResetAnimation()
502 {
503   m_queuedProcess = ANIM_PROCESS_NONE;
504   m_currentProcess = ANIM_PROCESS_NONE;
505   m_currentState = ANIM_STATE_NONE;
506 }
507
508 void CAnimation::ApplyAnimation()
509 {
510   m_queuedProcess = ANIM_PROCESS_NONE;
511   if (m_repeatAnim == ANIM_REPEAT_PULSE)
512   { // pulsed anims auto-reverse
513     m_amount = m_length;
514     m_currentProcess = ANIM_PROCESS_REVERSE;
515     m_currentState = ANIM_STATE_IN_PROCESS;
516   }
517   else if (m_repeatAnim == ANIM_REPEAT_LOOP)
518   { // looped anims start over
519     m_amount = 0;
520     m_currentProcess = ANIM_PROCESS_NORMAL;
521     m_currentState = ANIM_STATE_IN_PROCESS;
522   }
523   else
524   { // set normal process, so that Calculate() knows that we're finishing for zero length effects
525     // it will be reset in RenderAnimation()
526     m_currentProcess = ANIM_PROCESS_NORMAL;
527     m_currentState = ANIM_STATE_APPLIED;
528     m_amount = m_length;
529   }
530   Calculate(CPoint());
531 }
532
533 void CAnimation::Calculate(const CPoint &center)
534 {
535   for (unsigned int i = 0; i < m_effects.size(); i++)
536   {
537     CAnimEffect *effect = m_effects[i];
538     if (effect->GetLength())
539       effect->Calculate(m_delay + m_amount, center);
540     else
541     { // effect has length zero, so either apply complete
542       if (m_currentProcess == ANIM_PROCESS_NORMAL)
543         effect->ApplyState(ANIM_STATE_APPLIED, center);
544       else
545         effect->ApplyState(ANIM_STATE_NONE, center);
546     }
547   }
548 }
549
550 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint &center)
551 {
552   if (m_currentProcess != ANIM_PROCESS_NONE)
553     Calculate(center);
554   // If we have finished an animation, reset the animation state
555   // We do this here (rather than in Animate()) as we need the
556   // currentProcess information in the UpdateStates() function of the
557   // window and control classes.
558   if (m_currentState == ANIM_STATE_APPLIED)
559   {
560     m_currentProcess = ANIM_PROCESS_NONE;
561     m_queuedProcess = ANIM_PROCESS_NONE;
562   }
563   if (m_currentState != ANIM_STATE_NONE)
564   {
565     for (unsigned int i = 0; i < m_effects.size(); i++)
566       matrix *= m_effects[i]->GetTransform();
567   }
568 }
569
570 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
571 {
572   m_queuedProcess = process;
573 }
574
575 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
576 {
577   CAnimation anim;
578   anim.m_type = type;
579   anim.AddEffect(new CFadeEffect(start, end, delay, length));
580   return anim;
581 }
582
583 bool CAnimation::CheckCondition()
584 {
585   return !m_condition || g_infoManager.GetBoolValue(m_condition);
586 }
587
588 void CAnimation::UpdateCondition(const CGUIListItem *item)
589 {
590   bool condition = g_infoManager.GetBoolValue(m_condition, 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 = g_infoManager.GetBoolValue(m_condition);
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 (type.Left(7).Equals("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 const 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 }