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.
9 #include "VisibleEffect.h"
11 #include "GUIColorManager.h"
12 #include "GUIControlFactory.h"
13 #include "GUIInfoManager.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"
25 CAnimEffect::CAnimEffect(const TiXmlElement
*node
, EFFECT_TYPE effect
)
29 m_delay
= m_length
= 0;
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
)
45 m_pTweener
= std::shared_ptr
<Tweener
>(new LinearTweener());
48 CAnimEffect::~CAnimEffect() = default;
50 CAnimEffect::CAnimEffect(const CAnimEffect
&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
;
69 void CAnimEffect::Calculate(unsigned int time
, const CPoint
¢er
)
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
)
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
¢er
)
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");
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
);
126 pAnimationNode
->QueryFloatAttribute("acceleration", &accel
);
129 { // no tweener is specified - use a linear tweener
130 // or quadratic if we have acceleration
133 m_pTweener
= std::shared_ptr
<Tweener
>(new QuadTweener(accel
));
134 m_pTweener
->SetEasing(EASE_IN
);
137 m_pTweener
= std::shared_ptr
<Tweener
>(new LinearTweener());
143 CFadeEffect::CFadeEffect(const TiXmlElement
* node
, bool reverseDefaults
, EFFECT_TYPE effect
)
144 : CAnimEffect(node
, effect
)
147 { // out effect defaults
148 m_startAlpha
= 100.0f
;
152 { // in effect defaults
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
)
168 if (m_startAlpha
< 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");
180 m_startColor
= UTILS::COLOR::ConvertToFloats(
181 CServiceBroker::GetGUI()->GetColorManager().GetColor(start
));
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
;
194 CFadeEffect::CFadeEffect(UTILS::COLOR::Color start
,
195 UTILS::COLOR::Color end
,
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
¢er
)
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");
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");
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
¢er
)
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 ???)
260 const char *centerPos
= node
->Attribute("center");
263 if (StringUtils::CompareNoCase(centerPos
, "auto") == 0)
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
¢er
)
278 static const float degree_to_radian
= 0.01745329252f
;
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))
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");
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());
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");
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());
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");
357 if (StringUtils::CompareNoCase(centerPos
, "auto") == 0)
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());
369 { // no center specified
370 // calculate the center position...
373 float scale
= m_endX
/ m_startX
;
375 m_center
.x
= (endPosX
- scale
*startPosX
) / (1 - scale
);
379 float scale
= m_endY
/ m_startY
;
381 m_center
.y
= (endPosY
- scale
*startPosY
) / (1 - scale
);
386 void CZoomEffect::ApplyEffect(float offset
, const CPoint
¢er
)
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
;
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;
410 CAnimation::CAnimation(const CAnimation
&src
)
415 CAnimation::~CAnimation()
417 for (unsigned int i
= 0; i
< m_effects
.size(); i
++)
422 CAnimation
&CAnimation::operator =(const CAnimation
&src
)
424 if (this == &src
) return *this; // same
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
++)
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
]));
458 m_effects
.push_back(newEffect
);
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
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
)
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
)
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
;
501 if (m_repeatAnim
== ANIM_REPEAT_PULSE
&& m_lastCondition
)
502 { // pulsed anims auto-reverse
503 m_currentProcess
= ANIM_PROCESS_REVERSE
;
506 else if (m_repeatAnim
== ANIM_REPEAT_LOOP
&& m_lastCondition
)
507 { // looped anims start over
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
;
525 if (m_repeatAnim
== ANIM_REPEAT_PULSE
&& m_lastCondition
)
526 { // pulsed anims auto-reverse
527 m_currentProcess
= ANIM_PROCESS_NORMAL
;
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
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
555 m_currentProcess
= ANIM_PROCESS_NORMAL
;
556 m_currentState
= ANIM_STATE_IN_PROCESS
;
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
;
568 void CAnimation::Calculate(const CPoint
¢er
)
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
);
576 { // effect has length zero, so either apply complete
577 if (m_currentProcess
== ANIM_PROCESS_NORMAL
)
578 effect
->ApplyState(ANIM_STATE_APPLIED
, center
);
580 effect
->ApplyState(ANIM_STATE_NONE
, center
);
585 void CAnimation::RenderAnimation(TransformMatrix
&matrix
, const CPoint
¢er
)
587 if (m_currentProcess
!= ANIM_PROCESS_NONE
)
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
)
614 anim
.m_delay
= delay
;
615 anim
.m_length
= length
;
616 anim
.m_effects
.push_back(new CFadeEffect(start
, end
, delay
, length
));
620 bool CAnimation::CheckCondition()
622 return !m_condition
|| m_condition
->Get(INFO::DEFAULT_CONTEXT
);
625 void CAnimation::UpdateCondition(const CGUIListItem
*item
)
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
)
635 QueueAnimation(ANIM_PROCESS_REVERSE
);
639 m_lastCondition
= condition
;
642 void CAnimation::SetInitialCondition()
644 m_lastCondition
= m_condition
? m_condition
->Get(INFO::DEFAULT_CONTEXT
) : false;
651 void CAnimation::Create(const TiXmlElement
*node
, const CRect
&rect
, int context
)
653 if (!node
|| !node
->FirstChild())
656 // conditions and reversibility
657 const char *condition
= node
->Attribute("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
;
678 if (m_type
== ANIM_TYPE_CONDITIONAL
)
682 CLog::Log(LOGERROR
, "Control has invalid animation type (no condition or no type)");
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
;
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
);
703 // <animation type="focus" condition="blahdiblah" reversible="false">
704 // <effect type="fade" start="0" end="100" delay="10" time="2000" />
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
);
741 m_effects
.push_back(effect
);
744 CScroller::CScroller(unsigned int duration
/* = 200 */, std::shared_ptr
<Tweener
> tweener
/* = NULL */)
750 m_hasResumePoint
= false;
751 m_duration
= duration
> 0 ? duration
: 1;
752 m_pTweener
= std::move(tweener
);
755 CScroller::CScroller(const CScroller
& 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
;
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;
784 m_startPosition
= m_scrollValue
;
788 float CScroller::Tween(float progress
)
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
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)
806 return (2 * m_pTweener
->Tween(progress
, 0, 1, 1) - 1);
809 return m_pTweener
->Tween(progress
, 0, 1, 1);
815 bool CScroller::Update(unsigned int time
)
821 if (time
- m_startTime
>= m_duration
) // we are finished
823 m_scrollValue
= m_startPosition
+ m_delta
;
825 m_hasResumePoint
= false;
830 m_scrollValue
= m_startPosition
+ Tween((float)(time
- m_startTime
) / m_duration
) * m_delta
;