From 45ee70aa2639a87793ad7901605957db0993ff04 Mon Sep 17 00:00:00 2001 From: "Aaron J. Seigo" Date: Mon, 25 Jun 2007 06:01:34 +0000 Subject: [PATCH] * startElementAnimation -> animateElement * animate -> animateItem * frames -> framesPerSecond * use the curve shape as provided by the animator * get rid of the use of QTimeLine * this cuts down on the number of objects and timer events dramatically in the case of multiple simultaneous animations * all animations now update on the same tick * simplifies the management code a -lot- though now i need to do a lot more of the math TODO: implement interval updating based on the curve shape; otherwise, works pretty well =) svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=679871 --- phase.cpp | 390 ++++++++++++++++++++++++------------------------------ phase.h | 41 +++--- 2 files changed, 186 insertions(+), 245 deletions(-) diff --git a/phase.cpp b/phase.cpp index b5d6e7496..e7d472b26 100644 --- a/phase.cpp +++ b/phase.cpp @@ -19,7 +19,6 @@ #include "phase.h" #include -#include #include #include @@ -31,18 +30,25 @@ namespace Plasma { + struct AnimationState { QGraphicsItem* item; Phase::Animation animation; + Phase::CurveShape curve; + int interval; + int currentInterval; int frames; + int currentFrame; }; struct ElementAnimationState { QGraphicsItem* item; - QTimeLine* timeline; + Phase::CurveShape curve; Phase::ElementAnimation animation; + int interval; + int currentInterval; int frames; int currentFrame; int id; @@ -55,26 +61,47 @@ class Phase::Private Private() : animator(0), - animId(0) + animId(0), + timerId(0) { } ~Private() { - // delete animator; Animator is a QObject - // TimeLine's are parented to us, and we don't own the items + qDeleteAll(animatedItems); + qDeleteAll(animatedElements); + // Animator is a QObject + // and we don't own the items + } + + void performAnimation(qreal amount, const AnimationState* state) + { + switch (state->animation) { + case Phase::Appear: + animator->appear(amount, state->item); + break; + case Phase::Disappear: + animator->disappear(amount, state->item); + break; + case Phase::Activate: + animator->activate(amount, state->item); + break; + case Phase::FrameAppear: + animator->frameAppear(amount, state->item, QRegion()); //FIXME: what -is- the frame region? + break; + } } Animator* animator; int animId; + int timerId; + QTime time; //TODO: eventually perhaps we should allow multiple animations simulataneously // which would imply changing this to a QMap > // and really making the code fun ;) - QMap theAnimated; - QMap animations; - QMap theAnimatedElements; - QMap elementAnimations; + QMap animatedItems; + QMap animatedElements; }; class PhaseSingleton @@ -111,133 +138,49 @@ void Phase::appletDestroyed(QObject* o) return; } - QMap::iterator it = d->theAnimated.find(item); - - if (it == d->theAnimated.end()) { - return; + QMap::iterator it = d->animatedItems.find(item); + if (it != d->animatedItems.end()) { + delete it.value(); + d->animatedItems.erase(it); } - - d->animations.erase(d->animations.find(it.value())); - d->theAnimated.erase(it); - delete it.value(); } -void Phase::animate(QGraphicsItem* item, Animation animation) +void Phase::animateItem(QGraphicsItem* item, Animation animation) { - QMap::iterator it = d->theAnimated.find(item); - - if (it != d->theAnimated.end()) { + // get rid of any existing animations on this item. + //TODO: shoudl we allow multiple anims per item? + QMap::iterator it = d->animatedItems.find(item); + if (it != d->animatedItems.end()) { delete it.value(); - d->animations.erase(d->animations.find(it.value())); + d->animatedItems.erase(it); } - //TODO: allow the animator to define this? - QTimeLine::CurveShape curveShape = QTimeLine::EaseInOutCurve; int frames = d->animator->framesPerSecond(animation); if (frames < 1) { + // evidently this animator doesn't have an implementation + // for this Animation return; } - AnimationState state; - state.item = item; - state.animation = animation; + AnimationState* state = new AnimationState; + state->item = item; + state->animation = animation; + state->curve = d->animator->curve(animation); //TODO: variance in times based on the value of animation - state.frames = frames / 3; + state->frames = frames / 3; + state->currentFrame = 0; + state->interval = 333 / state->frames; + state->interval = (state->interval / 40) * 40; + state->currentInterval = state->interval; - //TODO: variance in times based on the value of animation - QTimeLine* timeLine = new QTimeLine(333, this); - timeLine->setFrameRange(0, state.frames); - timeLine->setCurveShape(curveShape); + d->animatedItems[item] = state; + d->performAnimation(0, state); - d->animations[timeLine] = state; - d->theAnimated[item] = timeLine; - connect(timeLine, SIGNAL(frameChanged(int)), this, SLOT(advanceFrame(int))); - connect(timeLine, SIGNAL(finished()), this, SLOT(animationComplete())); - timeLine->start(); - advanceFrame(0, timeLine); -} - -void Phase::advanceFrame(int frame) -{ - QTimeLine* timeLine = dynamic_cast(sender()); - - if (!timeLine) { - //kDebug() << "Phase::advanceFrame found no timeLine!" << endl; - return; + if (!d->timerId) { + d->timerId = startTimer(40); + d->time.restart(); } - - advanceFrame(frame, timeLine); -} - -void Phase::advanceFrame(int frame, QTimeLine* timeLine) -{ - QMap::iterator it = d->animations.find(timeLine); - - if (it == d->animations.end()) { - //kDebug() << "Phase::advanceFrame found no entry in animations!" << endl; - return; - } - - AnimationState state = it.value(); - qreal progress = state.frames; - progress = frame / progress; - - switch (state.animation) { - case Appear: - d->animator->appear(progress, state.item); - break; - case Disappear: - d->animator->disappear(progress, state.item); - break; - case Activate: - d->animator->activate(progress, state.item); - break; - case FrameAppear: - d->animator->frameAppear(progress, state.item, QRegion()); //FIXME: what -is- the frame region? - break; - } -} - -void Phase::animationComplete() -{ - QTimeLine* tl = dynamic_cast(sender()); - - if (!tl) { - return; - } - - QMap::iterator it = d->animations.find(tl); - - if (it == d->animations.end()) { - return; - } - - AnimationState state = it.value(); - - switch (state.animation) { - case Appear: - d->animator->appear(1, state.item); - break; - case Disappear: - d->animator->disappear(1, state.item); - break; - case Activate: - d->animator->activate(1, state.item); - break; - case FrameAppear: - d->animator->frameAppear(1, state.item, QRegion()); //FIXME: what -is- the frame region? - break; - } - - QMap::iterator animIt = d->theAnimated.find(state.item); - - if (animIt != d->theAnimated.end()) { - d->theAnimated.erase(animIt); - } - - d->animations.erase(it); - tl->deleteLater(); } void Phase::render(QGraphicsItem* item, QImage& image, RenderOp op) @@ -250,159 +193,166 @@ void Phase::render(QGraphicsItem* item, QImage& image, RenderOp op) } } -Phase::AnimId Phase::startElementAnimation(QGraphicsItem *item, ElementAnimation animation) +Phase::AnimId Phase::animateElement(QGraphicsItem *item, ElementAnimation animation) { //kDebug() << "startElementAnimation(AnimId " << animation << ")" << endl; //TODO: allow the animator to define this? - QTimeLine::CurveShape curveShape = QTimeLine::EaseInOutCurve; - - ElementAnimationState state; - state.item = item; - state.animation = animation; + ElementAnimationState *state = new ElementAnimationState; + state->item = item; + state->curve = d->animator->curve(animation); + state->animation = animation; //TODO: variance in times based on the value of animation - state.frames = d->animator->framesPerSecond(animation) / 3; - state.currentFrame = 0; - state.id = ++d->animId; + state->frames = d->animator->framesPerSecond(animation) / 5; + state->currentFrame = 0; + state->interval = 200 / state->frames; + state->interval = (state->interval / 40) * 40; + state->currentInterval = state->interval; + state->id = ++d->animId; - if (state.frames < 1) { - state.frames = 1; - state.currentFrame = 1; - state.timeline = 0; - } else { - //TODO: variance in times based on the value of animation - state.timeline = new QTimeLine(200, this); - state.timeline->setFrameRange(0, state.frames); - state.timeline->setCurveShape(curveShape); - connect(state.timeline, SIGNAL(frameChanged(int)), this, SLOT(advanceElementFrame(int))); - connect(state.timeline, SIGNAL(finished()), this, SLOT(elementAnimationComplete())); - state.timeline->start(); + //kDebug() << "animateElement " << animation << ", interval: " << state->interval << ", frames: " << state->frames << endl; + bool needTimer = true; + if (state->frames < 1) { + state->frames = 1; + state->currentFrame = 1; + needTimer = false; } - d->elementAnimations[state.timeline] = state.id; - d->theAnimatedElements[state.id] = state; + d->animatedElements[state->id] = state; + state->item->update(); - //TODO: this is a bit wasteful; perhaps we should pass in state itself? - advanceElementFrame(0, state.id); + //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state->id << endl; + if (needTimer && !d->timerId) { + // start a 20fps timer; + //TODO: should be started at the maximum frame rate needed only? + d->timerId = startTimer(40); + d->time.restart(); + } - //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state.id << endl; - return state.id; + return state->id; } void Phase::stopElementAnimation(AnimId id) { - //kDebug() << "stopElementAnimation(AnimId " << id << ")" << endl; - QMap::iterator it = d->theAnimatedElements.find(id); - - if (it == d->theAnimatedElements.end()) { - return; + QMap::iterator it = d->animatedElements.find(id); + if (it != d->animatedElements.end()) { + delete it.value(); + d->animatedElements.erase(it); } - - - if (it.value().timeline) { - d->elementAnimations.erase(d->elementAnimations.find(it.value().timeline)); - //delete it.value().timeline; - it.value().timeline = 0; - } - - d->theAnimatedElements.erase(it); //kDebug() << "stopElementAnimation(AnimId " << id << ") done" << endl; } void Phase::setAnimationPixmap(AnimId id, const QPixmap &pixmap) { - QMap::iterator it = d->theAnimatedElements.find(id); + QMap::iterator it = d->animatedElements.find(id); - if (it == d->theAnimatedElements.end()) { + if (it == d->animatedElements.end()) { kDebug() << "Phase::setAnimationPixmap(" << id << ") found no entry for it!" << endl; return; } - it.value().pixmap = pixmap; + it.value()->pixmap = pixmap; } QPixmap Phase::animationResult(AnimId id) { - QMap::const_iterator it = d->theAnimatedElements.find(id); + QMap::const_iterator it = d->animatedElements.find(id); - if (it == d->theAnimatedElements.constEnd()) { + if (it == d->animatedElements.constEnd()) { kDebug() << "Phase::animationResult(" << id << ") found no entry for it!" << endl; return QPixmap(); } - qreal progress = it.value().frames; + ElementAnimationState* state = it.value(); + qreal progress = state->frames; //kDebug() << "Phase::animationResult(" << id << " at " << progress << endl; - progress = it.value().currentFrame / progress; + progress = state->currentFrame / progress; + progress = qMin(1.0, qMax(0.0, progress)); //kDebug() << "Phase::animationResult(" << id << " at " << progress << endl; - switch (it.value().animation) { + switch (state->animation) { case ElementAppear: - return d->animator->elementAppear(progress, it.value().pixmap); + return d->animator->elementAppear(progress, state->pixmap); break; case ElementDisappear: - return d->animator->elementDisappear(progress, it.value().pixmap); + return d->animator->elementDisappear(progress, state->pixmap); break; } - return it.value().pixmap; + return state->pixmap; } -void Phase::advanceElementFrame(int frame) +void Phase::timerEvent(QTimerEvent *event) { - QTimeLine* timeline = dynamic_cast(sender()); + Q_UNUSED(event) + bool animationsRemain = false; + int elapsed = 40; + if (d->time.elapsed() > elapsed) { + elapsed = d->time.elapsed(); + } + d->time.restart(); +// kDebug() << "timeEvent, elapsed time: " << elapsed << endl; - if (!timeline) { - kDebug() << "Phase::advanceElementFrame found no timeLine!" << endl; - return; + foreach (AnimationState* state, d->animatedItems) { + if (state->currentInterval <= elapsed) { + // we need to step forward! + qreal progress = state->frames; + progress = state->currentFrame / progress; + progress = qMin(1.0, qMax(0.0, progress)); + d->performAnimation(progress, state); + state->currentFrame += qMax(1, elapsed / state->interval); + + if (state->currentFrame < state->frames) { + state->currentInterval = state->interval; + //TODO: calculate a proper interval based on the curve + state->interval *= 1 - progress; + animationsRemain = true; + } else { + d->performAnimation(1, state); + d->animatedItems.erase(d->animatedItems.find(state->item)); + delete state; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } } - QMap::const_iterator it = d->elementAnimations.find(timeline); + foreach (ElementAnimationState* state, d->animatedElements) { + if (state->currentFrame == state->frames) { + // since we keep element animations around until they are + // removed, we will end up with finished animations in the queue; + // just skip them + //TODO: should we move them to a separate QMap? + continue; + } - if (it == d->elementAnimations.constEnd()) { - kDebug() << "Phase::advanceElementFrame found no entry in animations!" << endl; - return; + if (state->currentInterval <= elapsed) { + // we need to step forward! +/* kDebug() << "stepping forwards element anim " << state->id << " from " << state->currentFrame + << " by " << qMax(1, elapsed / state->interval) << " to " + << state->currentFrame + qMax(1, elapsed / state->interval) << endl;*/ + state->currentFrame += qMax(1, elapsed / state->interval); + state->item->update(); + + if (state->currentFrame < state->frames) { + state->currentInterval = state->interval; + //TODO: calculate a proper interval based on the curve + qreal progress = state->frames; + progress = state->currentFrame / progress; + progress = qMin(1.0, qMax(0.0, progress)); + state->interval *= 1 - progress; + animationsRemain = true; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } } - advanceElementFrame(frame, it.value()); -} - -void Phase::advanceElementFrame(int frame, AnimId id) -{ - - QMap::iterator it2 = d->theAnimatedElements.find(id); - - if (it2 == d->theAnimatedElements.end()) { - kDebug() << "Phase::advanceElementFrame found no entry in animations!" << endl; - return; + if (!animationsRemain && d->timerId) { + killTimer(d->timerId); + d->timerId = 0; } - - it2.value().currentFrame = frame; - it2.value().item->update(); -} - -void Phase::elementAnimationComplete() -{ - QTimeLine* tl = dynamic_cast(sender()); - - if (!tl) { - return; - } - - QMap::iterator it = d->elementAnimations.find(tl); - - if (it == d->elementAnimations.end()) { - return; - } - //kDebug() << "elementAnimationComplete() " << it.value() << endl; - - QMap::iterator it2 = d->theAnimatedElements.find(it.value()); - if (it2 != d->theAnimatedElements.end()) { - it2.value().timeline = 0; - it2.value().currentFrame = it2.value().frames; - it2.value().item->update(); - } - - d->elementAnimations.erase(it); - tl->deleteLater(); } void Phase::init() diff --git a/phase.h b/phase.h index 895f41f57..da47f7e97 100644 --- a/phase.h +++ b/phase.h @@ -48,8 +48,8 @@ public: enum ElementAnimation { - ElementAppear = 0, - ElementDisappear + ElementAppear = 0 /*<< Animate the appearance of an element */, + ElementDisappear /*<< Animate the disappearance of an element */ }; enum RenderOp @@ -57,6 +57,14 @@ public: RenderBackground = 0 /*<< Render the background of an item */ }; + enum CurveShape + { + EaseInCurve = 0, + EaseOutCurve, + EaseInOutCurve, + LinearCurve + }; + typedef int AnimId; /** @@ -67,7 +75,10 @@ public: explicit Phase(QObject * parent = 0); ~Phase(); - AnimId startElementAnimation(QGraphicsItem *obj, ElementAnimation); + void animateItem(QGraphicsItem* item, Animation anim); + void render(QGraphicsItem* item, QImage& image, RenderOp op); + + AnimId animateElement(QGraphicsItem *obj, ElementAnimation); void stopElementAnimation(AnimId id); void setAnimationPixmap(AnimId id, const QPixmap &pixmap); QPixmap animationResult(AnimId id); @@ -75,32 +86,12 @@ public: Q_SIGNALS: void animationComplete(QGraphicsItem *item, Animation anim); -public Q_SLOTS: - void animate(QGraphicsItem* item, Animation anim); - void render(QGraphicsItem* item, QImage& image, RenderOp op); +protected: + void timerEvent(QTimerEvent *event); protected Q_SLOTS: void appletDestroyed(QObject*); - void advanceFrame(int frame, QTimeLine* timeLine); - void advanceElementFrame(int frame); - void advanceElementFrame(int frame, AnimId id); - - /** - * NEVER call this method directly, as it relies on sender() - */ - void advanceFrame(int frame); - - /** - * NEVER call this method directly, as it relies on sender() - */ - void animationComplete(); - - /** - * NEVER call this method directly, as it relies on sender() - */ - void elementAnimationComplete(); - private: void init();