diff options
Diffstat (limited to 'xbmc/guilib/VisibleEffect.cpp')
-rw-r--r-- | xbmc/guilib/VisibleEffect.cpp | 835 |
1 files changed, 835 insertions, 0 deletions
diff --git a/xbmc/guilib/VisibleEffect.cpp b/xbmc/guilib/VisibleEffect.cpp new file mode 100644 index 0000000..17b2daa --- /dev/null +++ b/xbmc/guilib/VisibleEffect.cpp @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VisibleEffect.h" + +#include "GUIColorManager.h" +#include "GUIControlFactory.h" +#include "GUIInfoManager.h" +#include "Tween.h" +#include "addons/Skin.h" // for the effect time adjustments +#include "guilib/GUIComponent.h" +#include "utils/ColorUtils.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <utility> + +CAnimEffect::CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect) +{ + m_effect = effect; + // defaults + m_delay = m_length = 0; + m_pTweener.reset(); + // time and delay + + float temp; + if (TIXML_SUCCESS == node->QueryFloatAttribute("time", &temp)) m_length = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown()); + if (TIXML_SUCCESS == node->QueryFloatAttribute("delay", &temp)) m_delay = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown()); + + m_pTweener = GetTweener(node); +} + +CAnimEffect::CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect) +{ + m_delay = delay; + m_length = length; + m_effect = effect; + m_pTweener = std::shared_ptr<Tweener>(new LinearTweener()); +} + +CAnimEffect::~CAnimEffect() = default; + +CAnimEffect::CAnimEffect(const CAnimEffect &src) +{ + m_pTweener.reset(); + *this = src; +} + +CAnimEffect& CAnimEffect::operator=(const CAnimEffect &src) +{ + if (&src == this) return *this; + + m_matrix = src.m_matrix; + m_effect = src.m_effect; + m_length = src.m_length; + m_delay = src.m_delay; + + m_pTweener = src.m_pTweener; + return *this; +} + +void CAnimEffect::Calculate(unsigned int time, const CPoint ¢er) +{ + assert(m_delay + m_length); + // calculate offset and tweening + float offset = 0.0f; // delayed forward, or finished reverse + if (time >= m_delay && time < m_delay + m_length) + offset = (float)(time - m_delay) / m_length; + else if (time >= m_delay + m_length) + offset = 1.0f; + if (m_pTweener) + offset = m_pTweener->Tween(offset, 0.0f, 1.0f, 1.0f); + // and apply the effect + ApplyEffect(offset, center); +} + +void CAnimEffect::ApplyState(ANIMATION_STATE state, const CPoint ¢er) +{ + float offset = (state == ANIM_STATE_APPLIED) ? 1.0f : 0.0f; + ApplyEffect(offset, center); +} + +std::shared_ptr<Tweener> CAnimEffect::GetTweener(const TiXmlElement *pAnimationNode) +{ + std::shared_ptr<Tweener> m_pTweener; + const char *tween = pAnimationNode->Attribute("tween"); + if (tween) + { + if (StringUtils::CompareNoCase(tween, "linear") == 0) + m_pTweener = std::shared_ptr<Tweener>(new LinearTweener()); + else if (StringUtils::CompareNoCase(tween, "quadratic") == 0) + m_pTweener = std::shared_ptr<Tweener>(new QuadTweener()); + else if (StringUtils::CompareNoCase(tween, "cubic") == 0) + m_pTweener = std::shared_ptr<Tweener>(new CubicTweener()); + else if (StringUtils::CompareNoCase(tween, "sine") == 0) + m_pTweener = std::shared_ptr<Tweener>(new SineTweener()); + else if (StringUtils::CompareNoCase(tween, "back") == 0) + m_pTweener = std::shared_ptr<Tweener>(new BackTweener()); + else if (StringUtils::CompareNoCase(tween, "circle") == 0) + m_pTweener = std::shared_ptr<Tweener>(new CircleTweener()); + else if (StringUtils::CompareNoCase(tween, "bounce") == 0) + m_pTweener = std::shared_ptr<Tweener>(new BounceTweener()); + else if (StringUtils::CompareNoCase(tween, "elastic") == 0) + m_pTweener = std::shared_ptr<Tweener>(new ElasticTweener()); + + const char *easing = pAnimationNode->Attribute("easing"); + if (m_pTweener && easing) + { + if (StringUtils::CompareNoCase(easing, "in") == 0) + m_pTweener->SetEasing(EASE_IN); + else if (StringUtils::CompareNoCase(easing, "out") == 0) + m_pTweener->SetEasing(EASE_OUT); + else if (StringUtils::CompareNoCase(easing, "inout") == 0) + m_pTweener->SetEasing(EASE_INOUT); + } + } + + float accel = 0; + pAnimationNode->QueryFloatAttribute("acceleration", &accel); + + if (!m_pTweener) + { // no tweener is specified - use a linear tweener + // or quadratic if we have acceleration + if (accel) + { + m_pTweener = std::shared_ptr<Tweener>(new QuadTweener(accel)); + m_pTweener->SetEasing(EASE_IN); + } + else + m_pTweener = std::shared_ptr<Tweener>(new LinearTweener()); + } + + return m_pTweener; +} + +CFadeEffect::CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_TYPE effect) + : CAnimEffect(node, effect) +{ + if (reverseDefaults) + { // out effect defaults + m_startAlpha = 100.0f; + m_endAlpha = 0; + } + else + { // in effect defaults + m_startAlpha = 0; + m_endAlpha = 100.0f; + } + + m_startColor.alpha = m_startColor.red = m_startColor.green = m_startColor.blue = 1.0f; + m_endColor.alpha = m_endColor.red = m_endColor.green = m_endColor.blue = 1.0f; + + if (effect == EFFECT_TYPE_FADE) + { + node->QueryFloatAttribute("start", &m_startAlpha); + node->QueryFloatAttribute("end", &m_endAlpha); + if (m_startAlpha > 100.0f) + m_startAlpha = 100.0f; + if (m_endAlpha > 100.0f) + m_endAlpha = 100.0f; + if (m_startAlpha < 0) + m_startAlpha = 0; + if (m_endAlpha < 0) + m_endAlpha = 0; + m_startColor.alpha = m_startAlpha * 0.01f; + m_endColor.alpha = m_endAlpha * 0.01f; + } + else if (effect == EFFECT_TYPE_FADE_DIFFUSE) + { + const char* start = node->Attribute("start"); + const char* end = node->Attribute("end"); + if (start) + m_startColor = UTILS::COLOR::ConvertToFloats( + CServiceBroker::GetGUI()->GetColorManager().GetColor(start)); + if (end) + m_endColor = + UTILS::COLOR::ConvertToFloats(CServiceBroker::GetGUI()->GetColorManager().GetColor(end)); + } +} + +CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE) +{ + m_startAlpha = start; + m_endAlpha = end; +} + +CFadeEffect::CFadeEffect(UTILS::COLOR::Color start, + UTILS::COLOR::Color end, + unsigned int delay, + unsigned int length) + : CAnimEffect(delay, length, EFFECT_TYPE_FADE_DIFFUSE) +{ + m_startAlpha = m_endAlpha = 1.0f; + m_startColor = UTILS::COLOR::ConvertToFloats(start); + m_endColor = UTILS::COLOR::ConvertToFloats(end); +} + +void CFadeEffect::ApplyEffect(float offset, const CPoint ¢er) +{ + if (m_effect == EFFECT_TYPE_FADE) + { + m_matrix.SetFader(((m_endAlpha - m_startAlpha) * offset + m_startAlpha) * 0.01f); + } + else if (m_effect == EFFECT_TYPE_FADE_DIFFUSE) + { + m_matrix.SetFader(((m_endColor.alpha - m_startColor.alpha) * offset + m_startColor.alpha), + ((m_endColor.red - m_startColor.red) * offset + m_startColor.red), + ((m_endColor.green - m_startColor.green) * offset + m_startColor.green), + ((m_endColor.blue - m_startColor.blue) * offset + m_startColor.blue)); + } +} + +CSlideEffect::CSlideEffect(const TiXmlElement *node) : CAnimEffect(node, EFFECT_TYPE_SLIDE) +{ + m_startX = m_endX = 0; + m_startY = m_endY = 0; + const char *startPos = node->Attribute("start"); + if (startPos) + { + std::vector<std::string> commaSeparated = StringUtils::Split(startPos, ","); + if (commaSeparated.size() > 1) + m_startY = (float)atof(commaSeparated[1].c_str()); + if (!commaSeparated.empty()) + m_startX = (float)atof(commaSeparated[0].c_str()); + } + const char *endPos = node->Attribute("end"); + if (endPos) + { + std::vector<std::string> commaSeparated = StringUtils::Split(endPos, ","); + if (commaSeparated.size() > 1) + m_endY = (float)atof(commaSeparated[1].c_str()); + if (!commaSeparated.empty()) + m_endX = (float)atof(commaSeparated[0].c_str()); + } +} + +void CSlideEffect::ApplyEffect(float offset, const CPoint ¢er) +{ + m_matrix.SetTranslation((m_endX - m_startX)*offset + m_startX, (m_endY - m_startY)*offset + m_startY, 0); +} + +CRotateEffect::CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect) : CAnimEffect(node, effect) +{ + m_startAngle = m_endAngle = 0; + m_autoCenter = false; + node->QueryFloatAttribute("start", &m_startAngle); + node->QueryFloatAttribute("end", &m_endAngle); + + // convert to a negative to account for our reversed Y axis (Needed for X and Z ???) + m_startAngle *= -1; + m_endAngle *= -1; + + const char *centerPos = node->Attribute("center"); + if (centerPos) + { + if (StringUtils::CompareNoCase(centerPos, "auto") == 0) + m_autoCenter = true; + else + { + std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ","); + if (commaSeparated.size() > 1) + m_center.y = (float)atof(commaSeparated[1].c_str()); + if (!commaSeparated.empty()) + m_center.x = (float)atof(commaSeparated[0].c_str()); + } + } +} + +void CRotateEffect::ApplyEffect(float offset, const CPoint ¢er) +{ + static const float degree_to_radian = 0.01745329252f; + if (m_autoCenter) + m_center = center; + if (m_effect == EFFECT_TYPE_ROTATE_X) + m_matrix.SetXRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f); + else if (m_effect == EFFECT_TYPE_ROTATE_Y) + m_matrix.SetYRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f); + else if (m_effect == EFFECT_TYPE_ROTATE_Z) // note coordinate aspect ratio is not generally square in the XY plane, so correct for it. + m_matrix.SetZRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio()); +} + +CZoomEffect::CZoomEffect(const TiXmlElement *node, const CRect &rect) : CAnimEffect(node, EFFECT_TYPE_ZOOM), m_center(CPoint(0,0)) +{ + // effect defaults + m_startX = m_startY = 100; + m_endX = m_endY = 100; + m_autoCenter = false; + + float startPosX = rect.x1; + float startPosY = rect.y1; + float endPosX = rect.x1; + float endPosY = rect.y1; + + float width = std::max(rect.Width(), 0.001f); + float height = std::max(rect.Height(),0.001f); + + const char *start = node->Attribute("start"); + if (start) + { + std::vector<std::string> params = StringUtils::Split(start, ","); + if (params.size() == 1) + { + m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + m_startY = m_startX; + } + else if (params.size() == 2) + { + m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + m_startY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height()); + } + else if (params.size() == 4) + { // format is start="x,y,width,height" + // use width and height from our rect to calculate our sizing + startPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + startPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height()); + m_startX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width()); + m_startY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height()); + m_startX *= 100.0f / width; + m_startY *= 100.0f / height; + } + } + const char *end = node->Attribute("end"); + if (end) + { + std::vector<std::string> params = StringUtils::Split(end, ","); + if (params.size() == 1) + { + m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + m_endY = m_endX; + } + else if (params.size() == 2) + { + m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + m_endY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height()); + } + else if (params.size() == 4) + { // format is start="x,y,width,height" + // use width and height from our rect to calculate our sizing + endPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width()); + endPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height()); + m_endX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width()); + m_endY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height()); + m_endX *= 100.0f / width; + m_endY *= 100.0f / height; + } + } + const char *centerPos = node->Attribute("center"); + if (centerPos) + { + if (StringUtils::CompareNoCase(centerPos, "auto") == 0) + m_autoCenter = true; + else + { + std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ","); + if (commaSeparated.size() > 1) + m_center.y = CGUIControlFactory::ParsePosition(commaSeparated[1].c_str(), rect.Height()); + if (!commaSeparated.empty()) + m_center.x = CGUIControlFactory::ParsePosition(commaSeparated[0].c_str(), rect.Width()); + } + } + else + { // no center specified + // calculate the center position... + if (m_startX) + { + float scale = m_endX / m_startX; + if (scale != 1) + m_center.x = (endPosX - scale*startPosX) / (1 - scale); + } + if (m_startY) + { + float scale = m_endY / m_startY; + if (scale != 1) + m_center.y = (endPosY - scale*startPosY) / (1 - scale); + } + } +} + +void CZoomEffect::ApplyEffect(float offset, const CPoint ¢er) +{ + if (m_autoCenter) + m_center = center; + float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f; + float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f; + m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y); +} + +CAnimation::CAnimation() +{ + m_type = ANIM_TYPE_NONE; + m_reversible = true; + m_repeatAnim = ANIM_REPEAT_NONE; + m_currentState = ANIM_STATE_NONE; + m_currentProcess = ANIM_PROCESS_NONE; + m_queuedProcess = ANIM_PROCESS_NONE; + m_lastCondition = false; + m_length = 0; + m_delay = 0; + m_start = 0; + m_amount = 0; +} + +CAnimation::CAnimation(const CAnimation &src) +{ + *this = src; +} + +CAnimation::~CAnimation() +{ + for (unsigned int i = 0; i < m_effects.size(); i++) + delete m_effects[i]; + m_effects.clear(); +} + +CAnimation &CAnimation::operator =(const CAnimation &src) +{ + if (this == &src) return *this; // same + m_type = src.m_type; + m_reversible = src.m_reversible; + m_condition = src.m_condition; + m_repeatAnim = src.m_repeatAnim; + m_lastCondition = src.m_lastCondition; + m_queuedProcess = src.m_queuedProcess; + m_currentProcess = src.m_currentProcess; + m_currentState = src.m_currentState; + m_start = src.m_start; + m_length = src.m_length; + m_delay = src.m_delay; + m_amount = src.m_amount; + // clear all our effects + for (unsigned int i = 0; i < m_effects.size(); i++) + delete m_effects[i]; + m_effects.clear(); + // and assign the others across + for (unsigned int i = 0; i < src.m_effects.size(); i++) + { + CAnimEffect *newEffect = NULL; + if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE) + newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i])); + else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE) + newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i])); + else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM) + newEffect = new CZoomEffect(*static_cast<CZoomEffect*>(src.m_effects[i])); + else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE) + newEffect = new CSlideEffect(*static_cast<CSlideEffect*>(src.m_effects[i])); + else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X || + src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y || + src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z) + newEffect = new CRotateEffect(*static_cast<CRotateEffect*>(src.m_effects[i])); + if (newEffect) + m_effects.push_back(newEffect); + } + return *this; +} + +void CAnimation::Animate(unsigned int time, bool startAnim) +{ + // First start any queued animations + if (m_queuedProcess == ANIM_PROCESS_NORMAL) + { + if (m_currentProcess == ANIM_PROCESS_REVERSE) + m_start = time - m_amount; // reverse direction of animation + else + m_start = time; + m_currentProcess = ANIM_PROCESS_NORMAL; + } + else if (m_queuedProcess == ANIM_PROCESS_REVERSE) + { + if (m_currentProcess == ANIM_PROCESS_NORMAL) + m_start = time - (m_length - m_amount); // reverse direction of animation + else if (m_currentProcess == ANIM_PROCESS_NONE) + m_start = time; + m_currentProcess = ANIM_PROCESS_REVERSE; + } + // reset the queued state once we've rendered to ensure allocation has occurred + m_queuedProcess = ANIM_PROCESS_NONE; + + // Update our animation process + if (m_currentProcess == ANIM_PROCESS_NORMAL) + { + if (time - m_start < m_delay) + { + m_amount = 0; + m_currentState = ANIM_STATE_DELAYED; + } + else if (time - m_start < m_length + m_delay) + { + m_amount = time - m_start - m_delay; + m_currentState = ANIM_STATE_IN_PROCESS; + } + else + { + m_amount = m_length; + if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition) + { // pulsed anims auto-reverse + m_currentProcess = ANIM_PROCESS_REVERSE; + m_start = time; + } + else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition) + { // looped anims start over + m_amount = 0; + m_start = time; + } + else + m_currentState = ANIM_STATE_APPLIED; + } + } + else if (m_currentProcess == ANIM_PROCESS_REVERSE) + { + if (time - m_start < m_length) + { + m_amount = m_length - (time - m_start); + m_currentState = ANIM_STATE_IN_PROCESS; + } + else + { + m_amount = 0; + if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition) + { // pulsed anims auto-reverse + m_currentProcess = ANIM_PROCESS_NORMAL; + m_start = time; + } + else + m_currentState = ANIM_STATE_APPLIED; + } + } +} + +void CAnimation::ResetAnimation() +{ + m_queuedProcess = ANIM_PROCESS_NONE; + m_currentProcess = ANIM_PROCESS_NONE; + m_currentState = ANIM_STATE_NONE; +} + +void CAnimation::ApplyAnimation() +{ + m_queuedProcess = ANIM_PROCESS_NONE; + if (m_repeatAnim == ANIM_REPEAT_PULSE) + { // pulsed anims auto-reverse + m_amount = m_length; + m_currentProcess = ANIM_PROCESS_REVERSE; + m_currentState = ANIM_STATE_IN_PROCESS; + } + else if (m_repeatAnim == ANIM_REPEAT_LOOP) + { // looped anims start over + m_amount = 0; + m_currentProcess = ANIM_PROCESS_NORMAL; + m_currentState = ANIM_STATE_IN_PROCESS; + } + else + { // set normal process, so that Calculate() knows that we're finishing for zero length effects + // it will be reset in RenderAnimation() + m_currentProcess = ANIM_PROCESS_NORMAL; + m_currentState = ANIM_STATE_APPLIED; + m_amount = m_length; + } + Calculate(CPoint()); +} + +void CAnimation::Calculate(const CPoint ¢er) +{ + for (unsigned int i = 0; i < m_effects.size(); i++) + { + CAnimEffect *effect = m_effects[i]; + if (effect->GetLength()) + effect->Calculate(m_delay + m_amount, center); + else + { // effect has length zero, so either apply complete + if (m_currentProcess == ANIM_PROCESS_NORMAL) + effect->ApplyState(ANIM_STATE_APPLIED, center); + else + effect->ApplyState(ANIM_STATE_NONE, center); + } + } +} + +void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint ¢er) +{ + if (m_currentProcess != ANIM_PROCESS_NONE) + Calculate(center); + // If we have finished an animation, reset the animation state + // We do this here (rather than in Animate()) as we need the + // currentProcess information in the UpdateStates() function of the + // window and control classes. + if (m_currentState == ANIM_STATE_APPLIED) + { + m_currentProcess = ANIM_PROCESS_NONE; + m_queuedProcess = ANIM_PROCESS_NONE; + } + if (m_currentState != ANIM_STATE_NONE) + { + for (unsigned int i = 0; i < m_effects.size(); i++) + matrix *= m_effects[i]->GetTransform(); + } +} + +void CAnimation::QueueAnimation(ANIMATION_PROCESS process) +{ + m_queuedProcess = process; +} + +CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type) +{ + CAnimation anim; + anim.m_type = type; + anim.m_delay = delay; + anim.m_length = length; + anim.m_effects.push_back(new CFadeEffect(start, end, delay, length)); + return anim; +} + +bool CAnimation::CheckCondition() +{ + return !m_condition || m_condition->Get(INFO::DEFAULT_CONTEXT); +} + +void CAnimation::UpdateCondition(const CGUIListItem *item) +{ + if (!m_condition) + return; + bool condition = m_condition->Get(INFO::DEFAULT_CONTEXT, item); + if (condition && !m_lastCondition) + QueueAnimation(ANIM_PROCESS_NORMAL); + else if (!condition && m_lastCondition) + { + if (m_reversible) + QueueAnimation(ANIM_PROCESS_REVERSE); + else + ResetAnimation(); + } + m_lastCondition = condition; +} + +void CAnimation::SetInitialCondition() +{ + m_lastCondition = m_condition ? m_condition->Get(INFO::DEFAULT_CONTEXT) : false; + if (m_lastCondition) + ApplyAnimation(); + else + ResetAnimation(); +} + +void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context) +{ + if (!node || !node->FirstChild()) + return; + + // conditions and reversibility + const char *condition = node->Attribute("condition"); + if (condition) + m_condition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context); + const char *reverse = node->Attribute("reversible"); + if (reverse && StringUtils::CompareNoCase(reverse, "false") == 0) + m_reversible = false; + + const TiXmlElement *effect = node->FirstChildElement("effect"); + + std::string type = node->FirstChild()->Value(); + m_type = ANIM_TYPE_CONDITIONAL; + if (effect) // new layout + type = XMLUtils::GetAttribute(node, "type"); + + if (StringUtils::StartsWithNoCase(type, "visible")) m_type = ANIM_TYPE_VISIBLE; + else if (StringUtils::EqualsNoCase(type, "hidden")) m_type = ANIM_TYPE_HIDDEN; + else if (StringUtils::EqualsNoCase(type, "focus")) m_type = ANIM_TYPE_FOCUS; + else if (StringUtils::EqualsNoCase(type, "unfocus")) m_type = ANIM_TYPE_UNFOCUS; + else if (StringUtils::EqualsNoCase(type, "windowopen")) m_type = ANIM_TYPE_WINDOW_OPEN; + else if (StringUtils::EqualsNoCase(type, "windowclose")) m_type = ANIM_TYPE_WINDOW_CLOSE; + // sanity check + if (m_type == ANIM_TYPE_CONDITIONAL) + { + if (!m_condition) + { + CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)"); + return; + } + + // pulsed or loop animations + const char *pulse = node->Attribute("pulse"); + if (pulse && StringUtils::CompareNoCase(pulse, "true") == 0) + m_repeatAnim = ANIM_REPEAT_PULSE; + const char *loop = node->Attribute("loop"); + if (loop && StringUtils::CompareNoCase(loop, "true") == 0) + m_repeatAnim = ANIM_REPEAT_LOOP; + } + + if (!effect) + { // old layout: + // <animation effect="fade" start="0" end="100" delay="10" time="2000" condition="blahdiblah" reversible="false">focus</animation> + std::string type = XMLUtils::GetAttribute(node, "effect"); + AddEffect(type, node, rect); + } + while (effect) + { // new layout: + // <animation type="focus" condition="blahdiblah" reversible="false"> + // <effect type="fade" start="0" end="100" delay="10" time="2000" /> + // ... + // </animation> + std::string type = XMLUtils::GetAttribute(effect, "type"); + AddEffect(type, effect, rect); + effect = effect->NextSiblingElement("effect"); + } + // compute the minimum delay and maximum length + m_delay = 0xffffffff; + unsigned int total = 0; + for (const auto& i : m_effects) + { + m_delay = std::min(m_delay, i->GetDelay()); + total = std::max(total, i->GetLength()); + } + m_length = total - m_delay; +} + +void CAnimation::AddEffect(const std::string &type, const TiXmlElement *node, const CRect &rect) +{ + CAnimEffect *effect = NULL; + if (StringUtils::EqualsNoCase(type, "fade")) + effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE); + else if (StringUtils::EqualsNoCase(type, "fadediffuse")) + effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE); + else if (StringUtils::EqualsNoCase(type, "slide")) + effect = new CSlideEffect(node); + else if (StringUtils::EqualsNoCase(type, "rotate")) + effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Z); + else if (StringUtils::EqualsNoCase(type, "rotatey")) + effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Y); + else if (StringUtils::EqualsNoCase(type, "rotatex")) + effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_X); + else if (StringUtils::EqualsNoCase(type, "zoom")) + effect = new CZoomEffect(node, rect); + + if (effect) + m_effects.push_back(effect); +} + +CScroller::CScroller(unsigned int duration /* = 200 */, std::shared_ptr<Tweener> tweener /* = NULL */) +{ + m_scrollValue = 0; + m_delta = 0; + m_startTime = 0; + m_startPosition = 0; + m_hasResumePoint = false; + m_duration = duration > 0 ? duration : 1; + m_pTweener = std::move(tweener); +} + +CScroller::CScroller(const CScroller& right) +{ + m_pTweener.reset(); + *this = right; +} + +CScroller& CScroller::operator=(const CScroller &right) +{ + if (&right == this) return *this; + + m_scrollValue = right.m_scrollValue; + m_delta = right.m_delta; + m_startTime = right.m_startTime; + m_startPosition = right.m_startPosition; + m_hasResumePoint = right.m_hasResumePoint; + m_duration = right.m_duration; + m_pTweener = right.m_pTweener; + return *this; +} + +CScroller::~CScroller() = default; + +void CScroller::ScrollTo(float endPos) +{ + float delta = endPos - m_scrollValue; + // if there is scrolling running in same direction - set resume point + m_hasResumePoint = m_delta != 0 && delta * m_delta > 0 && m_pTweener ? m_pTweener->HasResumePoint() : false; + + m_delta = delta; + m_startPosition = m_scrollValue; + m_startTime = 0; +} + +float CScroller::Tween(float progress) +{ + if (m_pTweener) + { + if (m_hasResumePoint) // tweener with in_and_out easing + { + // time linear transformation (y = a*x + b): 0 -> resumePoint and 1 -> 1 + // resumePoint = a * 0 + b and 1 = a * 1 + b + // a = 1 - resumePoint , b = resumePoint + // our resume point is 0.5 + // a = 0.5 , b = 0.5 + progress = 0.5f * progress + 0.5f; + // tweener value linear transformation (y = a*x + b): resumePointValue -> 0 and 1 -> 1 + // 0 = a * resumePointValue and 1 = a * 1 + b + // a = 1 / ( 1 - resumePointValue) , b = -resumePointValue / (1 - resumePointValue) + // we assume resumePointValue = Tween(resumePoint) = Tween(0.5) = 0.5 + // (this is true for tweener with in_and_out easing - it's rotationally symmetric about (0.5,0.5) continuous function) + // a = 2 , b = -1 + return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1); + } + else + return m_pTweener->Tween(progress, 0, 1, 1); + } + else + return progress; +} + +bool CScroller::Update(unsigned int time) +{ + if (!m_startTime) + m_startTime = time; + if (m_delta != 0) + { + if (time - m_startTime >= m_duration) // we are finished + { + m_scrollValue = m_startPosition + m_delta; + m_startTime = 0; + m_hasResumePoint = false; + m_delta = 0; + m_startPosition = 0; + } + else + m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta; + return true; + } + else + return false; +} |