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 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), m_center(CPoint(0,0))
254 m_startX = m_startY = 100;
255 m_endX = m_endY = 100;
256 m_autoCenter = false;
258 float startPosX = rect.x1;
259 float startPosY = rect.y1;
260 float endPosX = rect.x1;
261 float endPosY = rect.y1;
263 float width = max(rect.Width(), 0.001f);
264 float height = max(rect.Height(),0.001f);
266 const char *start = node->Attribute("start");
269 CStdStringArray params;
270 StringUtils::SplitString(start, ",", params);
271 if (params.size() == 1)
273 m_startX = (float)atof(params[0].c_str());
276 else if (params.size() == 2)
278 m_startX = (float)atof(params[0].c_str());
279 m_startY = (float)atof(params[1].c_str());
281 else if (params.size() == 4)
282 { // format is start="x,y,width,height"
283 // use width and height from our rect to calculate our sizing
284 startPosX = (float)atof(params[0].c_str());
285 startPosY = (float)atof(params[1].c_str());
286 m_startX = (float)atof(params[2].c_str());
287 m_startY = (float)atof(params[3].c_str());
288 m_startX *= 100.0f / width;
289 m_startY *= 100.0f / height;
292 const char *end = node->Attribute("end");
295 CStdStringArray params;
296 StringUtils::SplitString(end, ",", params);
297 if (params.size() == 1)
299 m_endX = (float)atof(params[0].c_str());
302 else if (params.size() == 2)
304 m_endX = (float)atof(params[0].c_str());
305 m_endY = (float)atof(params[1].c_str());
307 else if (params.size() == 4)
308 { // format is start="x,y,width,height"
309 // use width and height from our rect to calculate our sizing
310 endPosX = (float)atof(params[0].c_str());
311 endPosY = (float)atof(params[1].c_str());
312 m_endX = (float)atof(params[2].c_str());
313 m_endY = (float)atof(params[3].c_str());
314 m_endX *= 100.0f / width;
315 m_endY *= 100.0f / height;
318 const char *centerPos = node->Attribute("center");
321 if (strcmpi(centerPos, "auto") == 0)
325 vector<CStdString> commaSeparated;
326 StringUtils::SplitString(centerPos, ",", commaSeparated);
327 if (commaSeparated.size() > 1)
328 m_center.y = (float)atof(commaSeparated[1].c_str());
329 m_center.x = (float)atof(commaSeparated[0].c_str());
333 { // no center specified
334 // calculate the center position...
337 float scale = m_endX / m_startX;
339 m_center.x = (endPosX - scale*startPosX) / (1 - scale);
343 float scale = m_endY / m_startY;
345 m_center.y = (endPosY - scale*startPosY) / (1 - scale);
350 void CZoomEffect::ApplyEffect(float offset, const CPoint ¢er)
354 float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f;
355 float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f;
356 m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y);
359 CAnimation::CAnimation()
361 m_type = ANIM_TYPE_NONE;
363 m_repeatAnim = ANIM_REPEAT_NONE;
364 m_currentState = ANIM_STATE_NONE;
365 m_currentProcess = ANIM_PROCESS_NONE;
366 m_queuedProcess = ANIM_PROCESS_NONE;
367 m_lastCondition = false;
374 CAnimation::CAnimation(const CAnimation &src)
379 CAnimation::~CAnimation()
381 for (unsigned int i = 0; i < m_effects.size(); i++)
386 CAnimation &CAnimation::operator =(const CAnimation &src)
388 if (this == &src) return *this; // same
390 m_reversible = src.m_reversible;
391 m_condition = src.m_condition;
392 m_repeatAnim = src.m_repeatAnim;
393 m_lastCondition = src.m_lastCondition;
394 m_queuedProcess = src.m_queuedProcess;
395 m_currentProcess = src.m_currentProcess;
396 m_currentState = src.m_currentState;
397 m_start = src.m_start;
398 m_length = src.m_length;
399 m_delay = src.m_delay;
400 m_amount = src.m_amount;
401 // clear all our effects
402 for (unsigned int i = 0; i < m_effects.size(); i++)
405 // and assign the others across
406 for (unsigned int i = 0; i < src.m_effects.size(); i++)
408 CAnimEffect *newEffect = NULL;
409 if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
410 newEffect = new CFadeEffect(*(CFadeEffect *)src.m_effects[i]);
411 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
412 newEffect = new CZoomEffect(*(CZoomEffect *)src.m_effects[i]);
413 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
414 newEffect = new CSlideEffect(*(CSlideEffect *)src.m_effects[i]);
415 else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
416 src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
417 src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
418 newEffect = new CRotateEffect(*(CRotateEffect *)src.m_effects[i]);
420 m_effects.push_back(newEffect);
425 void CAnimation::Animate(unsigned int time, bool startAnim)
427 // First start any queued animations
428 if (m_queuedProcess == ANIM_PROCESS_NORMAL)
430 if (m_currentProcess == ANIM_PROCESS_REVERSE)
431 m_start = time - m_amount; // reverse direction of animation
434 m_currentProcess = ANIM_PROCESS_NORMAL;
436 else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
438 if (m_currentProcess == ANIM_PROCESS_NORMAL)
439 m_start = time - (m_length - m_amount); // reverse direction of animation
440 else if (m_currentProcess == ANIM_PROCESS_NONE)
442 m_currentProcess = ANIM_PROCESS_REVERSE;
444 // reset the queued state once we've rendered to ensure allocation has occured
445 if (startAnim || m_queuedProcess == ANIM_PROCESS_REVERSE)
446 m_queuedProcess = ANIM_PROCESS_NONE;
448 // Update our animation process
449 if (m_currentProcess == ANIM_PROCESS_NORMAL)
451 if (time - m_start < m_delay)
454 m_currentState = ANIM_STATE_DELAYED;
456 else if (time - m_start < m_length + m_delay)
458 m_amount = time - m_start - m_delay;
459 m_currentState = ANIM_STATE_IN_PROCESS;
464 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
465 { // pulsed anims auto-reverse
466 m_currentProcess = ANIM_PROCESS_REVERSE;
469 else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
470 { // looped anims start over
475 m_currentState = ANIM_STATE_APPLIED;
478 else if (m_currentProcess == ANIM_PROCESS_REVERSE)
480 if (time - m_start < m_length)
482 m_amount = m_length - (time - m_start);
483 m_currentState = ANIM_STATE_IN_PROCESS;
488 if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
489 { // pulsed anims auto-reverse
490 m_currentProcess = ANIM_PROCESS_NORMAL;
494 m_currentState = ANIM_STATE_APPLIED;
499 void CAnimation::ResetAnimation()
501 m_queuedProcess = ANIM_PROCESS_NONE;
502 m_currentProcess = ANIM_PROCESS_NONE;
503 m_currentState = ANIM_STATE_NONE;
506 void CAnimation::ApplyAnimation()
508 m_queuedProcess = ANIM_PROCESS_NONE;
509 if (m_repeatAnim == ANIM_REPEAT_PULSE)
510 { // pulsed anims auto-reverse
512 m_currentProcess = ANIM_PROCESS_REVERSE;
513 m_currentState = ANIM_STATE_IN_PROCESS;
515 else if (m_repeatAnim == ANIM_REPEAT_LOOP)
516 { // looped anims start over
518 m_currentProcess = ANIM_PROCESS_NORMAL;
519 m_currentState = ANIM_STATE_IN_PROCESS;
522 { // set normal process, so that Calculate() knows that we're finishing for zero length effects
523 // it will be reset in RenderAnimation()
524 m_currentProcess = ANIM_PROCESS_NORMAL;
525 m_currentState = ANIM_STATE_APPLIED;
531 void CAnimation::Calculate(const CPoint ¢er)
533 for (unsigned int i = 0; i < m_effects.size(); i++)
535 CAnimEffect *effect = m_effects[i];
536 if (effect->GetLength())
537 effect->Calculate(m_delay + m_amount, center);
539 { // effect has length zero, so either apply complete
540 if (m_currentProcess == ANIM_PROCESS_NORMAL)
541 effect->ApplyState(ANIM_STATE_APPLIED, center);
543 effect->ApplyState(ANIM_STATE_NONE, center);
548 void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint ¢er)
550 if (m_currentProcess != ANIM_PROCESS_NONE)
552 // If we have finished an animation, reset the animation state
553 // We do this here (rather than in Animate()) as we need the
554 // currentProcess information in the UpdateStates() function of the
555 // window and control classes.
556 if (m_currentState == ANIM_STATE_APPLIED)
558 m_currentProcess = ANIM_PROCESS_NONE;
559 m_queuedProcess = ANIM_PROCESS_NONE;
561 if (m_currentState != ANIM_STATE_NONE)
563 for (unsigned int i = 0; i < m_effects.size(); i++)
564 matrix *= m_effects[i]->GetTransform();
568 void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
570 m_queuedProcess = process;
573 CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
577 anim.AddEffect(new CFadeEffect(start, end, delay, length));
581 bool CAnimation::CheckCondition()
583 return !m_condition || m_condition->Get();
586 void CAnimation::UpdateCondition(const CGUIListItem *item)
590 bool condition = m_condition->Get(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 = m_condition ? m_condition->Get() : false;
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 (StringUtils::StartsWithNoCase(type, "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 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;