2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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"
27 #include "utils/XBMCTinyXML.h"
31 CAnimEffect::CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect)
35 m_delay = m_length = 0;
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());
43 m_pTweener = GetTweener(node);
46 CAnimEffect::CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect)
51 m_pTweener = boost::shared_ptr<Tweener>(new LinearTweener());
54 CAnimEffect::~CAnimEffect()
58 CAnimEffect::CAnimEffect(const CAnimEffect &src)
64 const CAnimEffect &CAnimEffect::operator=(const CAnimEffect &src)
66 if (&src == this) return *this;
68 m_matrix = src.m_matrix;
69 m_effect = src.m_effect;
70 m_length = src.m_length;
71 m_delay = src.m_delay;
73 m_pTweener = src.m_pTweener;
77 void CAnimEffect::Calculate(unsigned int time, const CPoint ¢er)
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)
87 offset = m_pTweener->Tween(offset, 0.0f, 1.0f, 1.0f);
88 // and apply the effect
89 ApplyEffect(offset, center);
92 void CAnimEffect::ApplyState(ANIMATION_STATE state, const CPoint ¢er)
94 float offset = (state == ANIM_STATE_APPLIED) ? 1.0f : 0.0f;
95 ApplyEffect(offset, center);
98 boost::shared_ptr<Tweener> CAnimEffect::GetTweener(const TiXmlElement *pAnimationNode)
100 boost::shared_ptr<Tweener> m_pTweener;
101 const char *tween = pAnimationNode->Attribute("tween");
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());
121 const char *easing = pAnimationNode->Attribute("easing");
122 if (m_pTweener && easing)
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);
134 pAnimationNode->QueryFloatAttribute("acceleration", &accel);
137 { // no tweener is specified - use a linear tweener
138 // or quadratic if we have acceleration
141 m_pTweener = boost::shared_ptr<Tweener>(new QuadTweener(accel));
142 m_pTweener->SetEasing(EASE_IN);
145 m_pTweener = boost::shared_ptr<Tweener>(new LinearTweener());
151 CFadeEffect::CFadeEffect(const TiXmlElement *node, bool reverseDefaults) : CAnimEffect(node, EFFECT_TYPE_FADE)
154 { // out effect defaults
155 m_startAlpha = 100.0f;
159 { // in effect defaults
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;
171 CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE)
173 m_startAlpha = start;
177 void CFadeEffect::ApplyEffect(float offset, const CPoint ¢er)
179 m_matrix.SetFader(((m_endAlpha - m_startAlpha) * offset + m_startAlpha) * 0.01f);
182 CSlideEffect::CSlideEffect(const TiXmlElement *node) : CAnimEffect(node, EFFECT_TYPE_SLIDE)
184 m_startX = m_endX = 0;
185 m_startY = m_endY = 0;
186 const char *startPos = node->Attribute("start");
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());
195 const char *endPos = node->Attribute("end");
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());
206 void CSlideEffect::ApplyEffect(float offset, const CPoint ¢er)
208 m_matrix.SetTranslation((m_endX - m_startX)*offset + m_startX, (m_endY - m_startY)*offset + m_startY, 0);
211 CRotateEffect::CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect) : CAnimEffect(node, effect)
213 m_startAngle = m_endAngle = 0;
214 m_autoCenter = false;
215 node->QueryFloatAttribute("start", &m_startAngle);
216 node->QueryFloatAttribute("end", &m_endAngle);
218 // convert to a negative to account for our reversed Y axis (Needed for X and Z ???)
222 const char *centerPos = node->Attribute("center");
225 if (strcmpi(centerPos, "auto") == 0)
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());
238 void CRotateEffect::ApplyEffect(float offset, const CPoint ¢er)
240 static const float degree_to_radian = 0.01745329252f;
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());
251 CZoomEffect::CZoomEffect(const TiXmlElement *node, const CRect &rect) : CAnimEffect(node, EFFECT_TYPE_ZOOM)
254 m_startX = m_startY = 100;
255 m_endX = m_endY = 100;
256 m_center = CPoint(0,0);
257 m_autoCenter = false;
259 float startPosX = rect.x1;
260 float startPosY = rect.y1;
261 float endPosX = rect.x1;
262 float endPosY = rect.y1;
264 float width = max(rect.Width(), 0.001f);
265 float height = max(rect.Height(),0.001f);
267 const char *start = node->Attribute("start");
270 CStdStringArray params;
271 StringUtils::SplitString(start, ",", params);
272 if (params.size() == 1)
274 m_startX = (float)atof(params[0].c_str());
277 else if (params.size() == 2)
279 m_startX = (float)atof(params[0].c_str());
280 m_startY = (float)atof(params[1].c_str());
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;
293 const char *end = node->Attribute("end");
296 CStdStringArray params;
297 StringUtils::SplitString(end, ",", params);
298 if (params.size() == 1)
300 m_endX = (float)atof(params[0].c_str());
303 else if (params.size() == 2)
305 m_endX = (float)atof(params[0].c_str());
306 m_endY = (float)atof(params[1].c_str());
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;
319 const char *centerPos = node->Attribute("center");
322 if (strcmpi(centerPos, "auto") == 0)
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());
334 { // no center specified
335 // calculate the center position...
338 float scale = m_endX / m_startX;
340 m_center.x = (endPosX - scale*startPosX) / (1 - scale);
344 float scale = m_endY / m_startY;
346 m_center.y = (endPosY - scale*startPosY) / (1 - scale);
351 void CZoomEffect::ApplyEffect(float offset, const CPoint ¢er)
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);
360 CAnimation::CAnimation()
362 m_type = ANIM_TYPE_NONE;
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;
376 CAnimation::CAnimation(const CAnimation &src)
381 CAnimation::~CAnimation()
383 for (unsigned int i = 0; i < m_effects.size(); i++)
388 const CAnimation &CAnimation::operator =(const CAnimation &src)
390 if (this == &src) return *this; // same
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++)
407 // and assign the others across
408 for (unsigned int i = 0; i < src.m_effects.size(); i++)
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]);
422 m_effects.push_back(newEffect);
427 void CAnimation::Animate(unsigned int time, bool startAnim)
429 // First start any queued animations
430 if (m_queuedProcess == ANIM_PROCESS_NORMAL)
432 if (m_currentProcess == ANIM_PROCESS_REVERSE)
433 m_start = time - m_amount; // reverse direction of animation
436 m_currentProcess = ANIM_PROCESS_NORMAL;
438 else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
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)
444 m_currentProcess = ANIM_PROCESS_REVERSE;
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;
450 // Update our animation process
451 if (m_currentProcess == ANIM_PROCESS_NORMAL)
453 if (time - m_start < m_delay)
456 m_currentState = ANIM_STATE_DELAYED;
458 else if (time - m_start < m_length + m_delay)
460 m_amount = time - m_start - m_delay;
461 m_currentState = ANIM_STATE_IN_PROCESS;
466 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
467 { // pulsed anims auto-reverse
468 m_currentProcess = ANIM_PROCESS_REVERSE;
471 else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
472 { // looped anims start over
477 m_currentState = ANIM_STATE_APPLIED;
480 else if (m_currentProcess == ANIM_PROCESS_REVERSE)
482 if (time - m_start < m_length)
484 m_amount = m_length - (time - m_start);
485 m_currentState = ANIM_STATE_IN_PROCESS;
490 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
491 { // pulsed anims auto-reverse
492 m_currentProcess = ANIM_PROCESS_NORMAL;
496 m_currentState = ANIM_STATE_APPLIED;
501 void CAnimation::ResetAnimation()
503 m_queuedProcess = ANIM_PROCESS_NONE;
504 m_currentProcess = ANIM_PROCESS_NONE;
505 m_currentState = ANIM_STATE_NONE;
508 void CAnimation::ApplyAnimation()
510 m_queuedProcess = ANIM_PROCESS_NONE;
511 if (m_repeatAnim == ANIM_REPEAT_PULSE)
512 { // pulsed anims auto-reverse
514 m_currentProcess = ANIM_PROCESS_REVERSE;
515 m_currentState = ANIM_STATE_IN_PROCESS;
517 else if (m_repeatAnim == ANIM_REPEAT_LOOP)
518 { // looped anims start over
520 m_currentProcess = ANIM_PROCESS_NORMAL;
521 m_currentState = ANIM_STATE_IN_PROCESS;
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;
533 void CAnimation::Calculate(const CPoint ¢er)
535 for (unsigned int i = 0; i < m_effects.size(); i++)
537 CAnimEffect *effect = m_effects[i];
538 if (effect->GetLength())
539 effect->Calculate(m_delay + m_amount, center);
541 { // effect has length zero, so either apply complete
542 if (m_currentProcess == ANIM_PROCESS_NORMAL)
543 effect->ApplyState(ANIM_STATE_APPLIED, center);
545 effect->ApplyState(ANIM_STATE_NONE, center);
550 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint ¢er)
552 if (m_currentProcess != ANIM_PROCESS_NONE)
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)
560 m_currentProcess = ANIM_PROCESS_NONE;
561 m_queuedProcess = ANIM_PROCESS_NONE;
563 if (m_currentState != ANIM_STATE_NONE)
565 for (unsigned int i = 0; i < m_effects.size(); i++)
566 matrix *= m_effects[i]->GetTransform();
570 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
572 m_queuedProcess = process;
575 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
579 anim.AddEffect(new CFadeEffect(start, end, delay, length));
583 bool CAnimation::CheckCondition()
585 return !m_condition || g_infoManager.GetBoolValue(m_condition);
588 void CAnimation::UpdateCondition(const CGUIListItem *item)
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)
596 QueueAnimation(ANIM_PROCESS_REVERSE);
600 m_lastCondition = condition;
603 void CAnimation::SetInitialCondition()
605 m_lastCondition = g_infoManager.GetBoolValue(m_condition);
612 void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context)
614 if (!node || !node->FirstChild())
617 // conditions and reversibility
618 const char *condition = node->Attribute("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;
625 const TiXmlElement *effect = node->FirstChildElement("effect");
627 CStdString type = node->FirstChild()->Value();
628 m_type = ANIM_TYPE_CONDITIONAL;
629 if (effect) // new layout
630 type = node->Attribute("type");
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;
639 if (m_type == ANIM_TYPE_CONDITIONAL)
643 CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)");
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;
656 m_delay = 0xffffffff;
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);
665 // <animation type="focus" condition="blahdiblah" reversible="false">
666 // <effect type="fade" start="0" end="100" delay="10" time="2000" />
669 CStdString type = effect->Attribute("type");
670 AddEffect(type, effect, rect);
671 effect = effect->NextSiblingElement("effect");
675 void CAnimation::AddEffect(const CStdString &type, const TiXmlElement *node, const CRect &rect)
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);
695 void CAnimation::AddEffect(CAnimEffect *effect)
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;
706 CScroller::CScroller(unsigned int duration /* = 200 */, boost::shared_ptr<Tweener> tweener /* = NULL */)
712 m_hasResumePoint = false;
714 m_duration = duration > 0 ? duration : 1;
715 m_pTweener = tweener;
718 CScroller::CScroller(const CScroller& right)
724 const CScroller &CScroller::operator=(const CScroller &right)
726 if (&right == this) return *this;
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;
739 CScroller::~CScroller()
743 void CScroller::ScrollTo(float endPos)
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;
750 m_startPosition = m_scrollValue;
751 m_startTime = m_lastTime;
754 float CScroller::Tween(float progress)
758 if (m_hasResumePoint) // tweener with in_and_out easing
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
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)
772 return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1);
775 return m_pTweener->Tween(progress, 0, 1, 1);
781 bool CScroller::Update(unsigned int time)
786 if (time - m_startTime >= m_duration) // we are finished
788 m_scrollValue = m_startPosition + m_delta;
790 m_hasResumePoint = false;
795 m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta;