Merge pull request #22816 from CastagnaIT/fix_tx3g
[xbmc.git] / xbmc / guilib / VisibleEffect.cpp
blob17b2daa755ff592befd7325d1c1725b607bb80ce
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "VisibleEffect.h"
11 #include "GUIColorManager.h"
12 #include "GUIControlFactory.h"
13 #include "GUIInfoManager.h"
14 #include "Tween.h"
15 #include "addons/Skin.h" // for the effect time adjustments
16 #include "guilib/GUIComponent.h"
17 #include "utils/ColorUtils.h"
18 #include "utils/StringUtils.h"
19 #include "utils/XBMCTinyXML.h"
20 #include "utils/XMLUtils.h"
21 #include "utils/log.h"
23 #include <utility>
25 CAnimEffect::CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect)
27 m_effect = effect;
28 // defaults
29 m_delay = m_length = 0;
30 m_pTweener.reset();
31 // time and delay
33 float temp;
34 if (TIXML_SUCCESS == node->QueryFloatAttribute("time", &temp)) m_length = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
35 if (TIXML_SUCCESS == node->QueryFloatAttribute("delay", &temp)) m_delay = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
37 m_pTweener = GetTweener(node);
40 CAnimEffect::CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect)
42 m_delay = delay;
43 m_length = length;
44 m_effect = effect;
45 m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
48 CAnimEffect::~CAnimEffect() = default;
50 CAnimEffect::CAnimEffect(const CAnimEffect &src)
52 m_pTweener.reset();
53 *this = src;
56 CAnimEffect& CAnimEffect::operator=(const CAnimEffect &src)
58 if (&src == this) return *this;
60 m_matrix = src.m_matrix;
61 m_effect = src.m_effect;
62 m_length = src.m_length;
63 m_delay = src.m_delay;
65 m_pTweener = src.m_pTweener;
66 return *this;
69 void CAnimEffect::Calculate(unsigned int time, const CPoint &center)
71 assert(m_delay + m_length);
72 // calculate offset and tweening
73 float offset = 0.0f; // delayed forward, or finished reverse
74 if (time >= m_delay && time < m_delay + m_length)
75 offset = (float)(time - m_delay) / m_length;
76 else if (time >= m_delay + m_length)
77 offset = 1.0f;
78 if (m_pTweener)
79 offset = m_pTweener->Tween(offset, 0.0f, 1.0f, 1.0f);
80 // and apply the effect
81 ApplyEffect(offset, center);
84 void CAnimEffect::ApplyState(ANIMATION_STATE state, const CPoint &center)
86 float offset = (state == ANIM_STATE_APPLIED) ? 1.0f : 0.0f;
87 ApplyEffect(offset, center);
90 std::shared_ptr<Tweener> CAnimEffect::GetTweener(const TiXmlElement *pAnimationNode)
92 std::shared_ptr<Tweener> m_pTweener;
93 const char *tween = pAnimationNode->Attribute("tween");
94 if (tween)
96 if (StringUtils::CompareNoCase(tween, "linear") == 0)
97 m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
98 else if (StringUtils::CompareNoCase(tween, "quadratic") == 0)
99 m_pTweener = std::shared_ptr<Tweener>(new QuadTweener());
100 else if (StringUtils::CompareNoCase(tween, "cubic") == 0)
101 m_pTweener = std::shared_ptr<Tweener>(new CubicTweener());
102 else if (StringUtils::CompareNoCase(tween, "sine") == 0)
103 m_pTweener = std::shared_ptr<Tweener>(new SineTweener());
104 else if (StringUtils::CompareNoCase(tween, "back") == 0)
105 m_pTweener = std::shared_ptr<Tweener>(new BackTweener());
106 else if (StringUtils::CompareNoCase(tween, "circle") == 0)
107 m_pTweener = std::shared_ptr<Tweener>(new CircleTweener());
108 else if (StringUtils::CompareNoCase(tween, "bounce") == 0)
109 m_pTweener = std::shared_ptr<Tweener>(new BounceTweener());
110 else if (StringUtils::CompareNoCase(tween, "elastic") == 0)
111 m_pTweener = std::shared_ptr<Tweener>(new ElasticTweener());
113 const char *easing = pAnimationNode->Attribute("easing");
114 if (m_pTweener && easing)
116 if (StringUtils::CompareNoCase(easing, "in") == 0)
117 m_pTweener->SetEasing(EASE_IN);
118 else if (StringUtils::CompareNoCase(easing, "out") == 0)
119 m_pTweener->SetEasing(EASE_OUT);
120 else if (StringUtils::CompareNoCase(easing, "inout") == 0)
121 m_pTweener->SetEasing(EASE_INOUT);
125 float accel = 0;
126 pAnimationNode->QueryFloatAttribute("acceleration", &accel);
128 if (!m_pTweener)
129 { // no tweener is specified - use a linear tweener
130 // or quadratic if we have acceleration
131 if (accel)
133 m_pTweener = std::shared_ptr<Tweener>(new QuadTweener(accel));
134 m_pTweener->SetEasing(EASE_IN);
136 else
137 m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
140 return m_pTweener;
143 CFadeEffect::CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_TYPE effect)
144 : CAnimEffect(node, effect)
146 if (reverseDefaults)
147 { // out effect defaults
148 m_startAlpha = 100.0f;
149 m_endAlpha = 0;
151 else
152 { // in effect defaults
153 m_startAlpha = 0;
154 m_endAlpha = 100.0f;
157 m_startColor.alpha = m_startColor.red = m_startColor.green = m_startColor.blue = 1.0f;
158 m_endColor.alpha = m_endColor.red = m_endColor.green = m_endColor.blue = 1.0f;
160 if (effect == EFFECT_TYPE_FADE)
162 node->QueryFloatAttribute("start", &m_startAlpha);
163 node->QueryFloatAttribute("end", &m_endAlpha);
164 if (m_startAlpha > 100.0f)
165 m_startAlpha = 100.0f;
166 if (m_endAlpha > 100.0f)
167 m_endAlpha = 100.0f;
168 if (m_startAlpha < 0)
169 m_startAlpha = 0;
170 if (m_endAlpha < 0)
171 m_endAlpha = 0;
172 m_startColor.alpha = m_startAlpha * 0.01f;
173 m_endColor.alpha = m_endAlpha * 0.01f;
175 else if (effect == EFFECT_TYPE_FADE_DIFFUSE)
177 const char* start = node->Attribute("start");
178 const char* end = node->Attribute("end");
179 if (start)
180 m_startColor = UTILS::COLOR::ConvertToFloats(
181 CServiceBroker::GetGUI()->GetColorManager().GetColor(start));
182 if (end)
183 m_endColor =
184 UTILS::COLOR::ConvertToFloats(CServiceBroker::GetGUI()->GetColorManager().GetColor(end));
188 CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE)
190 m_startAlpha = start;
191 m_endAlpha = end;
194 CFadeEffect::CFadeEffect(UTILS::COLOR::Color start,
195 UTILS::COLOR::Color end,
196 unsigned int delay,
197 unsigned int length)
198 : CAnimEffect(delay, length, EFFECT_TYPE_FADE_DIFFUSE)
200 m_startAlpha = m_endAlpha = 1.0f;
201 m_startColor = UTILS::COLOR::ConvertToFloats(start);
202 m_endColor = UTILS::COLOR::ConvertToFloats(end);
205 void CFadeEffect::ApplyEffect(float offset, const CPoint &center)
207 if (m_effect == EFFECT_TYPE_FADE)
209 m_matrix.SetFader(((m_endAlpha - m_startAlpha) * offset + m_startAlpha) * 0.01f);
211 else if (m_effect == EFFECT_TYPE_FADE_DIFFUSE)
213 m_matrix.SetFader(((m_endColor.alpha - m_startColor.alpha) * offset + m_startColor.alpha),
214 ((m_endColor.red - m_startColor.red) * offset + m_startColor.red),
215 ((m_endColor.green - m_startColor.green) * offset + m_startColor.green),
216 ((m_endColor.blue - m_startColor.blue) * offset + m_startColor.blue));
220 CSlideEffect::CSlideEffect(const TiXmlElement *node) : CAnimEffect(node, EFFECT_TYPE_SLIDE)
222 m_startX = m_endX = 0;
223 m_startY = m_endY = 0;
224 const char *startPos = node->Attribute("start");
225 if (startPos)
227 std::vector<std::string> commaSeparated = StringUtils::Split(startPos, ",");
228 if (commaSeparated.size() > 1)
229 m_startY = (float)atof(commaSeparated[1].c_str());
230 if (!commaSeparated.empty())
231 m_startX = (float)atof(commaSeparated[0].c_str());
233 const char *endPos = node->Attribute("end");
234 if (endPos)
236 std::vector<std::string> commaSeparated = StringUtils::Split(endPos, ",");
237 if (commaSeparated.size() > 1)
238 m_endY = (float)atof(commaSeparated[1].c_str());
239 if (!commaSeparated.empty())
240 m_endX = (float)atof(commaSeparated[0].c_str());
244 void CSlideEffect::ApplyEffect(float offset, const CPoint &center)
246 m_matrix.SetTranslation((m_endX - m_startX)*offset + m_startX, (m_endY - m_startY)*offset + m_startY, 0);
249 CRotateEffect::CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect) : CAnimEffect(node, effect)
251 m_startAngle = m_endAngle = 0;
252 m_autoCenter = false;
253 node->QueryFloatAttribute("start", &m_startAngle);
254 node->QueryFloatAttribute("end", &m_endAngle);
256 // convert to a negative to account for our reversed Y axis (Needed for X and Z ???)
257 m_startAngle *= -1;
258 m_endAngle *= -1;
260 const char *centerPos = node->Attribute("center");
261 if (centerPos)
263 if (StringUtils::CompareNoCase(centerPos, "auto") == 0)
264 m_autoCenter = true;
265 else
267 std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ",");
268 if (commaSeparated.size() > 1)
269 m_center.y = (float)atof(commaSeparated[1].c_str());
270 if (!commaSeparated.empty())
271 m_center.x = (float)atof(commaSeparated[0].c_str());
276 void CRotateEffect::ApplyEffect(float offset, const CPoint &center)
278 static const float degree_to_radian = 0.01745329252f;
279 if (m_autoCenter)
280 m_center = center;
281 if (m_effect == EFFECT_TYPE_ROTATE_X)
282 m_matrix.SetXRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
283 else if (m_effect == EFFECT_TYPE_ROTATE_Y)
284 m_matrix.SetYRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
285 else if (m_effect == EFFECT_TYPE_ROTATE_Z) // note coordinate aspect ratio is not generally square in the XY plane, so correct for it.
286 m_matrix.SetZRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio());
289 CZoomEffect::CZoomEffect(const TiXmlElement *node, const CRect &rect) : CAnimEffect(node, EFFECT_TYPE_ZOOM), m_center(CPoint(0,0))
291 // effect defaults
292 m_startX = m_startY = 100;
293 m_endX = m_endY = 100;
294 m_autoCenter = false;
296 float startPosX = rect.x1;
297 float startPosY = rect.y1;
298 float endPosX = rect.x1;
299 float endPosY = rect.y1;
301 float width = std::max(rect.Width(), 0.001f);
302 float height = std::max(rect.Height(),0.001f);
304 const char *start = node->Attribute("start");
305 if (start)
307 std::vector<std::string> params = StringUtils::Split(start, ",");
308 if (params.size() == 1)
310 m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
311 m_startY = m_startX;
313 else if (params.size() == 2)
315 m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
316 m_startY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
318 else if (params.size() == 4)
319 { // format is start="x,y,width,height"
320 // use width and height from our rect to calculate our sizing
321 startPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
322 startPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
323 m_startX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width());
324 m_startY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height());
325 m_startX *= 100.0f / width;
326 m_startY *= 100.0f / height;
329 const char *end = node->Attribute("end");
330 if (end)
332 std::vector<std::string> params = StringUtils::Split(end, ",");
333 if (params.size() == 1)
335 m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
336 m_endY = m_endX;
338 else if (params.size() == 2)
340 m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
341 m_endY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
343 else if (params.size() == 4)
344 { // format is start="x,y,width,height"
345 // use width and height from our rect to calculate our sizing
346 endPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
347 endPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
348 m_endX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width());
349 m_endY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height());
350 m_endX *= 100.0f / width;
351 m_endY *= 100.0f / height;
354 const char *centerPos = node->Attribute("center");
355 if (centerPos)
357 if (StringUtils::CompareNoCase(centerPos, "auto") == 0)
358 m_autoCenter = true;
359 else
361 std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ",");
362 if (commaSeparated.size() > 1)
363 m_center.y = CGUIControlFactory::ParsePosition(commaSeparated[1].c_str(), rect.Height());
364 if (!commaSeparated.empty())
365 m_center.x = CGUIControlFactory::ParsePosition(commaSeparated[0].c_str(), rect.Width());
368 else
369 { // no center specified
370 // calculate the center position...
371 if (m_startX)
373 float scale = m_endX / m_startX;
374 if (scale != 1)
375 m_center.x = (endPosX - scale*startPosX) / (1 - scale);
377 if (m_startY)
379 float scale = m_endY / m_startY;
380 if (scale != 1)
381 m_center.y = (endPosY - scale*startPosY) / (1 - scale);
386 void CZoomEffect::ApplyEffect(float offset, const CPoint &center)
388 if (m_autoCenter)
389 m_center = center;
390 float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f;
391 float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f;
392 m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y);
395 CAnimation::CAnimation()
397 m_type = ANIM_TYPE_NONE;
398 m_reversible = true;
399 m_repeatAnim = ANIM_REPEAT_NONE;
400 m_currentState = ANIM_STATE_NONE;
401 m_currentProcess = ANIM_PROCESS_NONE;
402 m_queuedProcess = ANIM_PROCESS_NONE;
403 m_lastCondition = false;
404 m_length = 0;
405 m_delay = 0;
406 m_start = 0;
407 m_amount = 0;
410 CAnimation::CAnimation(const CAnimation &src)
412 *this = src;
415 CAnimation::~CAnimation()
417 for (unsigned int i = 0; i < m_effects.size(); i++)
418 delete m_effects[i];
419 m_effects.clear();
422 CAnimation &CAnimation::operator =(const CAnimation &src)
424 if (this == &src) return *this; // same
425 m_type = src.m_type;
426 m_reversible = src.m_reversible;
427 m_condition = src.m_condition;
428 m_repeatAnim = src.m_repeatAnim;
429 m_lastCondition = src.m_lastCondition;
430 m_queuedProcess = src.m_queuedProcess;
431 m_currentProcess = src.m_currentProcess;
432 m_currentState = src.m_currentState;
433 m_start = src.m_start;
434 m_length = src.m_length;
435 m_delay = src.m_delay;
436 m_amount = src.m_amount;
437 // clear all our effects
438 for (unsigned int i = 0; i < m_effects.size(); i++)
439 delete m_effects[i];
440 m_effects.clear();
441 // and assign the others across
442 for (unsigned int i = 0; i < src.m_effects.size(); i++)
444 CAnimEffect *newEffect = NULL;
445 if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
446 newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i]));
447 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE)
448 newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i]));
449 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
450 newEffect = new CZoomEffect(*static_cast<CZoomEffect*>(src.m_effects[i]));
451 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
452 newEffect = new CSlideEffect(*static_cast<CSlideEffect*>(src.m_effects[i]));
453 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
454 src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
455 src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
456 newEffect = new CRotateEffect(*static_cast<CRotateEffect*>(src.m_effects[i]));
457 if (newEffect)
458 m_effects.push_back(newEffect);
460 return *this;
463 void CAnimation::Animate(unsigned int time, bool startAnim)
465 // First start any queued animations
466 if (m_queuedProcess == ANIM_PROCESS_NORMAL)
468 if (m_currentProcess == ANIM_PROCESS_REVERSE)
469 m_start = time - m_amount; // reverse direction of animation
470 else
471 m_start = time;
472 m_currentProcess = ANIM_PROCESS_NORMAL;
474 else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
476 if (m_currentProcess == ANIM_PROCESS_NORMAL)
477 m_start = time - (m_length - m_amount); // reverse direction of animation
478 else if (m_currentProcess == ANIM_PROCESS_NONE)
479 m_start = time;
480 m_currentProcess = ANIM_PROCESS_REVERSE;
482 // reset the queued state once we've rendered to ensure allocation has occurred
483 m_queuedProcess = ANIM_PROCESS_NONE;
485 // Update our animation process
486 if (m_currentProcess == ANIM_PROCESS_NORMAL)
488 if (time - m_start < m_delay)
490 m_amount = 0;
491 m_currentState = ANIM_STATE_DELAYED;
493 else if (time - m_start < m_length + m_delay)
495 m_amount = time - m_start - m_delay;
496 m_currentState = ANIM_STATE_IN_PROCESS;
498 else
500 m_amount = m_length;
501 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
502 { // pulsed anims auto-reverse
503 m_currentProcess = ANIM_PROCESS_REVERSE;
504 m_start = time;
506 else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
507 { // looped anims start over
508 m_amount = 0;
509 m_start = time;
511 else
512 m_currentState = ANIM_STATE_APPLIED;
515 else if (m_currentProcess == ANIM_PROCESS_REVERSE)
517 if (time - m_start < m_length)
519 m_amount = m_length - (time - m_start);
520 m_currentState = ANIM_STATE_IN_PROCESS;
522 else
524 m_amount = 0;
525 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
526 { // pulsed anims auto-reverse
527 m_currentProcess = ANIM_PROCESS_NORMAL;
528 m_start = time;
530 else
531 m_currentState = ANIM_STATE_APPLIED;
536 void CAnimation::ResetAnimation()
538 m_queuedProcess = ANIM_PROCESS_NONE;
539 m_currentProcess = ANIM_PROCESS_NONE;
540 m_currentState = ANIM_STATE_NONE;
543 void CAnimation::ApplyAnimation()
545 m_queuedProcess = ANIM_PROCESS_NONE;
546 if (m_repeatAnim == ANIM_REPEAT_PULSE)
547 { // pulsed anims auto-reverse
548 m_amount = m_length;
549 m_currentProcess = ANIM_PROCESS_REVERSE;
550 m_currentState = ANIM_STATE_IN_PROCESS;
552 else if (m_repeatAnim == ANIM_REPEAT_LOOP)
553 { // looped anims start over
554 m_amount = 0;
555 m_currentProcess = ANIM_PROCESS_NORMAL;
556 m_currentState = ANIM_STATE_IN_PROCESS;
558 else
559 { // set normal process, so that Calculate() knows that we're finishing for zero length effects
560 // it will be reset in RenderAnimation()
561 m_currentProcess = ANIM_PROCESS_NORMAL;
562 m_currentState = ANIM_STATE_APPLIED;
563 m_amount = m_length;
565 Calculate(CPoint());
568 void CAnimation::Calculate(const CPoint &center)
570 for (unsigned int i = 0; i < m_effects.size(); i++)
572 CAnimEffect *effect = m_effects[i];
573 if (effect->GetLength())
574 effect->Calculate(m_delay + m_amount, center);
575 else
576 { // effect has length zero, so either apply complete
577 if (m_currentProcess == ANIM_PROCESS_NORMAL)
578 effect->ApplyState(ANIM_STATE_APPLIED, center);
579 else
580 effect->ApplyState(ANIM_STATE_NONE, center);
585 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint &center)
587 if (m_currentProcess != ANIM_PROCESS_NONE)
588 Calculate(center);
589 // If we have finished an animation, reset the animation state
590 // We do this here (rather than in Animate()) as we need the
591 // currentProcess information in the UpdateStates() function of the
592 // window and control classes.
593 if (m_currentState == ANIM_STATE_APPLIED)
595 m_currentProcess = ANIM_PROCESS_NONE;
596 m_queuedProcess = ANIM_PROCESS_NONE;
598 if (m_currentState != ANIM_STATE_NONE)
600 for (unsigned int i = 0; i < m_effects.size(); i++)
601 matrix *= m_effects[i]->GetTransform();
605 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
607 m_queuedProcess = process;
610 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
612 CAnimation anim;
613 anim.m_type = type;
614 anim.m_delay = delay;
615 anim.m_length = length;
616 anim.m_effects.push_back(new CFadeEffect(start, end, delay, length));
617 return anim;
620 bool CAnimation::CheckCondition()
622 return !m_condition || m_condition->Get(INFO::DEFAULT_CONTEXT);
625 void CAnimation::UpdateCondition(const CGUIListItem *item)
627 if (!m_condition)
628 return;
629 bool condition = m_condition->Get(INFO::DEFAULT_CONTEXT, item);
630 if (condition && !m_lastCondition)
631 QueueAnimation(ANIM_PROCESS_NORMAL);
632 else if (!condition && m_lastCondition)
634 if (m_reversible)
635 QueueAnimation(ANIM_PROCESS_REVERSE);
636 else
637 ResetAnimation();
639 m_lastCondition = condition;
642 void CAnimation::SetInitialCondition()
644 m_lastCondition = m_condition ? m_condition->Get(INFO::DEFAULT_CONTEXT) : false;
645 if (m_lastCondition)
646 ApplyAnimation();
647 else
648 ResetAnimation();
651 void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context)
653 if (!node || !node->FirstChild())
654 return;
656 // conditions and reversibility
657 const char *condition = node->Attribute("condition");
658 if (condition)
659 m_condition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context);
660 const char *reverse = node->Attribute("reversible");
661 if (reverse && StringUtils::CompareNoCase(reverse, "false") == 0)
662 m_reversible = false;
664 const TiXmlElement *effect = node->FirstChildElement("effect");
666 std::string type = node->FirstChild()->Value();
667 m_type = ANIM_TYPE_CONDITIONAL;
668 if (effect) // new layout
669 type = XMLUtils::GetAttribute(node, "type");
671 if (StringUtils::StartsWithNoCase(type, "visible")) m_type = ANIM_TYPE_VISIBLE;
672 else if (StringUtils::EqualsNoCase(type, "hidden")) m_type = ANIM_TYPE_HIDDEN;
673 else if (StringUtils::EqualsNoCase(type, "focus")) m_type = ANIM_TYPE_FOCUS;
674 else if (StringUtils::EqualsNoCase(type, "unfocus")) m_type = ANIM_TYPE_UNFOCUS;
675 else if (StringUtils::EqualsNoCase(type, "windowopen")) m_type = ANIM_TYPE_WINDOW_OPEN;
676 else if (StringUtils::EqualsNoCase(type, "windowclose")) m_type = ANIM_TYPE_WINDOW_CLOSE;
677 // sanity check
678 if (m_type == ANIM_TYPE_CONDITIONAL)
680 if (!m_condition)
682 CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)");
683 return;
686 // pulsed or loop animations
687 const char *pulse = node->Attribute("pulse");
688 if (pulse && StringUtils::CompareNoCase(pulse, "true") == 0)
689 m_repeatAnim = ANIM_REPEAT_PULSE;
690 const char *loop = node->Attribute("loop");
691 if (loop && StringUtils::CompareNoCase(loop, "true") == 0)
692 m_repeatAnim = ANIM_REPEAT_LOOP;
695 if (!effect)
696 { // old layout:
697 // <animation effect="fade" start="0" end="100" delay="10" time="2000" condition="blahdiblah" reversible="false">focus</animation>
698 std::string type = XMLUtils::GetAttribute(node, "effect");
699 AddEffect(type, node, rect);
701 while (effect)
702 { // new layout:
703 // <animation type="focus" condition="blahdiblah" reversible="false">
704 // <effect type="fade" start="0" end="100" delay="10" time="2000" />
705 // ...
706 // </animation>
707 std::string type = XMLUtils::GetAttribute(effect, "type");
708 AddEffect(type, effect, rect);
709 effect = effect->NextSiblingElement("effect");
711 // compute the minimum delay and maximum length
712 m_delay = 0xffffffff;
713 unsigned int total = 0;
714 for (const auto& i : m_effects)
716 m_delay = std::min(m_delay, i->GetDelay());
717 total = std::max(total, i->GetLength());
719 m_length = total - m_delay;
722 void CAnimation::AddEffect(const std::string &type, const TiXmlElement *node, const CRect &rect)
724 CAnimEffect *effect = NULL;
725 if (StringUtils::EqualsNoCase(type, "fade"))
726 effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE);
727 else if (StringUtils::EqualsNoCase(type, "fadediffuse"))
728 effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE);
729 else if (StringUtils::EqualsNoCase(type, "slide"))
730 effect = new CSlideEffect(node);
731 else if (StringUtils::EqualsNoCase(type, "rotate"))
732 effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Z);
733 else if (StringUtils::EqualsNoCase(type, "rotatey"))
734 effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Y);
735 else if (StringUtils::EqualsNoCase(type, "rotatex"))
736 effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_X);
737 else if (StringUtils::EqualsNoCase(type, "zoom"))
738 effect = new CZoomEffect(node, rect);
740 if (effect)
741 m_effects.push_back(effect);
744 CScroller::CScroller(unsigned int duration /* = 200 */, std::shared_ptr<Tweener> tweener /* = NULL */)
746 m_scrollValue = 0;
747 m_delta = 0;
748 m_startTime = 0;
749 m_startPosition = 0;
750 m_hasResumePoint = false;
751 m_duration = duration > 0 ? duration : 1;
752 m_pTweener = std::move(tweener);
755 CScroller::CScroller(const CScroller& right)
757 m_pTweener.reset();
758 *this = right;
761 CScroller& CScroller::operator=(const CScroller &right)
763 if (&right == this) return *this;
765 m_scrollValue = right.m_scrollValue;
766 m_delta = right.m_delta;
767 m_startTime = right.m_startTime;
768 m_startPosition = right.m_startPosition;
769 m_hasResumePoint = right.m_hasResumePoint;
770 m_duration = right.m_duration;
771 m_pTweener = right.m_pTweener;
772 return *this;
775 CScroller::~CScroller() = default;
777 void CScroller::ScrollTo(float endPos)
779 float delta = endPos - m_scrollValue;
780 // if there is scrolling running in same direction - set resume point
781 m_hasResumePoint = m_delta != 0 && delta * m_delta > 0 && m_pTweener ? m_pTweener->HasResumePoint() : false;
783 m_delta = delta;
784 m_startPosition = m_scrollValue;
785 m_startTime = 0;
788 float CScroller::Tween(float progress)
790 if (m_pTweener)
792 if (m_hasResumePoint) // tweener with in_and_out easing
794 // time linear transformation (y = a*x + b): 0 -> resumePoint and 1 -> 1
795 // resumePoint = a * 0 + b and 1 = a * 1 + b
796 // a = 1 - resumePoint , b = resumePoint
797 // our resume point is 0.5
798 // a = 0.5 , b = 0.5
799 progress = 0.5f * progress + 0.5f;
800 // tweener value linear transformation (y = a*x + b): resumePointValue -> 0 and 1 -> 1
801 // 0 = a * resumePointValue and 1 = a * 1 + b
802 // a = 1 / ( 1 - resumePointValue) , b = -resumePointValue / (1 - resumePointValue)
803 // we assume resumePointValue = Tween(resumePoint) = Tween(0.5) = 0.5
804 // (this is true for tweener with in_and_out easing - it's rotationally symmetric about (0.5,0.5) continuous function)
805 // a = 2 , b = -1
806 return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1);
808 else
809 return m_pTweener->Tween(progress, 0, 1, 1);
811 else
812 return progress;
815 bool CScroller::Update(unsigned int time)
817 if (!m_startTime)
818 m_startTime = time;
819 if (m_delta != 0)
821 if (time - m_startTime >= m_duration) // we are finished
823 m_scrollValue = m_startPosition + m_delta;
824 m_startTime = 0;
825 m_hasResumePoint = false;
826 m_delta = 0;
827 m_startPosition = 0;
829 else
830 m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta;
831 return true;
833 else
834 return false;