* 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
This commit is contained in:
Aaron J. Seigo 2007-06-25 06:01:34 +00:00
parent 1ba292c158
commit 45ee70aa26
2 changed files with 186 additions and 245 deletions

388
phase.cpp
View File

@ -19,7 +19,6 @@
#include "phase.h" #include "phase.h"
#include <QGraphicsItem> #include <QGraphicsItem>
#include <QTimeLine>
#include <KConfig> #include <KConfig>
#include <KConfigGroup> #include <KConfigGroup>
@ -31,18 +30,25 @@
namespace Plasma namespace Plasma
{ {
struct AnimationState struct AnimationState
{ {
QGraphicsItem* item; QGraphicsItem* item;
Phase::Animation animation; Phase::Animation animation;
Phase::CurveShape curve;
int interval;
int currentInterval;
int frames; int frames;
int currentFrame;
}; };
struct ElementAnimationState struct ElementAnimationState
{ {
QGraphicsItem* item; QGraphicsItem* item;
QTimeLine* timeline; Phase::CurveShape curve;
Phase::ElementAnimation animation; Phase::ElementAnimation animation;
int interval;
int currentInterval;
int frames; int frames;
int currentFrame; int currentFrame;
int id; int id;
@ -55,26 +61,47 @@ class Phase::Private
Private() Private()
: animator(0), : animator(0),
animId(0) animId(0),
timerId(0)
{ {
} }
~Private() ~Private()
{ {
// delete animator; Animator is a QObject qDeleteAll(animatedItems);
// TimeLine's are parented to us, and we don't own the items 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; Animator* animator;
int animId; int animId;
int timerId;
QTime time;
//TODO: eventually perhaps we should allow multiple animations simulataneously //TODO: eventually perhaps we should allow multiple animations simulataneously
// which would imply changing this to a QMap<QGraphicsItem*, QList<QTimeLine*> > // which would imply changing this to a QMap<QGraphicsItem*, QList<QTimeLine*> >
// and really making the code fun ;) // and really making the code fun ;)
QMap<QGraphicsItem*, QTimeLine*> theAnimated; QMap<QGraphicsItem*, AnimationState*> animatedItems;
QMap<QTimeLine*, AnimationState> animations; QMap<Phase::AnimId, ElementAnimationState*> animatedElements;
QMap<Phase::AnimId, ElementAnimationState> theAnimatedElements;
QMap<QTimeLine*, Phase::AnimId> elementAnimations;
}; };
class PhaseSingleton class PhaseSingleton
@ -111,133 +138,49 @@ void Phase::appletDestroyed(QObject* o)
return; return;
} }
QMap<QGraphicsItem*, QTimeLine*>::iterator it = d->theAnimated.find(item); QMap<QGraphicsItem*, AnimationState*>::iterator it = d->animatedItems.find(item);
if (it != d->animatedItems.end()) {
if (it == d->theAnimated.end()) {
return;
}
d->animations.erase(d->animations.find(it.value()));
d->theAnimated.erase(it);
delete it.value(); delete it.value();
d->animatedItems.erase(it);
}
} }
void Phase::animate(QGraphicsItem* item, Animation animation) void Phase::animateItem(QGraphicsItem* item, Animation animation)
{ {
QMap<QGraphicsItem*, QTimeLine*>::iterator it = d->theAnimated.find(item); // get rid of any existing animations on this item.
//TODO: shoudl we allow multiple anims per item?
if (it != d->theAnimated.end()) { QMap<QGraphicsItem*, AnimationState*>::iterator it = d->animatedItems.find(item);
if (it != d->animatedItems.end()) {
delete it.value(); 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); int frames = d->animator->framesPerSecond(animation);
if (frames < 1) { if (frames < 1) {
// evidently this animator doesn't have an implementation
// for this Animation
return; return;
} }
AnimationState state; AnimationState* state = new AnimationState;
state.item = item; state->item = item;
state.animation = animation; state->animation = animation;
state->curve = d->animator->curve(animation);
//TODO: variance in times based on the value of 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 d->animatedItems[item] = state;
QTimeLine* timeLine = new QTimeLine(333, this); d->performAnimation(0, state);
timeLine->setFrameRange(0, state.frames);
timeLine->setCurveShape(curveShape);
d->animations[timeLine] = state; if (!d->timerId) {
d->theAnimated[item] = timeLine; d->timerId = startTimer(40);
connect(timeLine, SIGNAL(frameChanged(int)), this, SLOT(advanceFrame(int))); d->time.restart();
connect(timeLine, SIGNAL(finished()), this, SLOT(animationComplete()));
timeLine->start();
advanceFrame(0, timeLine);
} }
void Phase::advanceFrame(int frame)
{
QTimeLine* timeLine = dynamic_cast<QTimeLine*>(sender());
if (!timeLine) {
//kDebug() << "Phase::advanceFrame found no timeLine!" << endl;
return;
}
advanceFrame(frame, timeLine);
}
void Phase::advanceFrame(int frame, QTimeLine* timeLine)
{
QMap<QTimeLine*, AnimationState>::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<QTimeLine*>(sender());
if (!tl) {
return;
}
QMap<QTimeLine*, AnimationState>::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<QGraphicsItem*, QTimeLine*>::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) 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; //kDebug() << "startElementAnimation(AnimId " << animation << ")" << endl;
//TODO: allow the animator to define this? //TODO: allow the animator to define this?
QTimeLine::CurveShape curveShape = QTimeLine::EaseInOutCurve; ElementAnimationState *state = new ElementAnimationState;
state->item = item;
ElementAnimationState state; state->curve = d->animator->curve(animation);
state.item = item; state->animation = animation;
state.animation = animation;
//TODO: variance in times based on the value of animation //TODO: variance in times based on the value of animation
state.frames = d->animator->framesPerSecond(animation) / 3; state->frames = d->animator->framesPerSecond(animation) / 5;
state.currentFrame = 0; state->currentFrame = 0;
state.id = ++d->animId; state->interval = 200 / state->frames;
state->interval = (state->interval / 40) * 40;
state->currentInterval = state->interval;
state->id = ++d->animId;
if (state.frames < 1) { //kDebug() << "animateElement " << animation << ", interval: " << state->interval << ", frames: " << state->frames << endl;
state.frames = 1; bool needTimer = true;
state.currentFrame = 1; if (state->frames < 1) {
state.timeline = 0; state->frames = 1;
} else { state->currentFrame = 1;
//TODO: variance in times based on the value of animation needTimer = false;
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();
} }
d->elementAnimations[state.timeline] = state.id; d->animatedElements[state->id] = state;
d->theAnimatedElements[state.id] = state; state->item->update();
//TODO: this is a bit wasteful; perhaps we should pass in state itself? //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state->id << endl;
advanceElementFrame(0, state.id); 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) void Phase::stopElementAnimation(AnimId id)
{ {
//kDebug() << "stopElementAnimation(AnimId " << id << ")" << endl; QMap<AnimId, ElementAnimationState*>::iterator it = d->animatedElements.find(id);
QMap<AnimId, ElementAnimationState>::iterator it = d->theAnimatedElements.find(id); if (it != d->animatedElements.end()) {
delete it.value();
if (it == d->theAnimatedElements.end()) { d->animatedElements.erase(it);
return;
} }
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; //kDebug() << "stopElementAnimation(AnimId " << id << ") done" << endl;
} }
void Phase::setAnimationPixmap(AnimId id, const QPixmap &pixmap) void Phase::setAnimationPixmap(AnimId id, const QPixmap &pixmap)
{ {
QMap<AnimId, ElementAnimationState>::iterator it = d->theAnimatedElements.find(id); QMap<AnimId, ElementAnimationState*>::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; kDebug() << "Phase::setAnimationPixmap(" << id << ") found no entry for it!" << endl;
return; return;
} }
it.value().pixmap = pixmap; it.value()->pixmap = pixmap;
} }
QPixmap Phase::animationResult(AnimId id) QPixmap Phase::animationResult(AnimId id)
{ {
QMap<AnimId, ElementAnimationState>::const_iterator it = d->theAnimatedElements.find(id); QMap<AnimId, ElementAnimationState*>::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; kDebug() << "Phase::animationResult(" << id << ") found no entry for it!" << endl;
return QPixmap(); return QPixmap();
} }
qreal progress = it.value().frames; ElementAnimationState* state = it.value();
qreal progress = state->frames;
//kDebug() << "Phase::animationResult(" << id << " at " << progress << endl; //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; //kDebug() << "Phase::animationResult(" << id << " at " << progress << endl;
switch (it.value().animation) { switch (state->animation) {
case ElementAppear: case ElementAppear:
return d->animator->elementAppear(progress, it.value().pixmap); return d->animator->elementAppear(progress, state->pixmap);
break; break;
case ElementDisappear: case ElementDisappear:
return d->animator->elementDisappear(progress, it.value().pixmap); return d->animator->elementDisappear(progress, state->pixmap);
break; break;
} }
return it.value().pixmap; return state->pixmap;
} }
void Phase::advanceElementFrame(int frame) void Phase::timerEvent(QTimerEvent *event)
{ {
QTimeLine* timeline = dynamic_cast<QTimeLine*>(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) { foreach (AnimationState* state, d->animatedItems) {
kDebug() << "Phase::advanceElementFrame found no timeLine!" << endl; if (state->currentInterval <= elapsed) {
return; // 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<QTimeLine*, Phase::AnimId>::const_iterator it = d->elementAnimations.find(timeline); foreach (ElementAnimationState* state, d->animatedElements) {
if (state->currentFrame == state->frames) {
if (it == d->elementAnimations.constEnd()) { // since we keep element animations around until they are
kDebug() << "Phase::advanceElementFrame found no entry in animations!" << endl; // removed, we will end up with finished animations in the queue;
return; // just skip them
//TODO: should we move them to a separate QMap?
continue;
} }
advanceElementFrame(frame, it.value()); 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;
}
} }
void Phase::advanceElementFrame(int frame, AnimId id) if (!animationsRemain && d->timerId) {
{ killTimer(d->timerId);
d->timerId = 0;
QMap<Phase::AnimId, ElementAnimationState>::iterator it2 = d->theAnimatedElements.find(id);
if (it2 == d->theAnimatedElements.end()) {
kDebug() << "Phase::advanceElementFrame found no entry in animations!" << endl;
return;
} }
it2.value().currentFrame = frame;
it2.value().item->update();
}
void Phase::elementAnimationComplete()
{
QTimeLine* tl = dynamic_cast<QTimeLine*>(sender());
if (!tl) {
return;
}
QMap<QTimeLine*, Phase::AnimId>::iterator it = d->elementAnimations.find(tl);
if (it == d->elementAnimations.end()) {
return;
}
//kDebug() << "elementAnimationComplete() " << it.value() << endl;
QMap<Phase::AnimId, ElementAnimationState>::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() void Phase::init()

41
phase.h
View File

@ -48,8 +48,8 @@ public:
enum ElementAnimation enum ElementAnimation
{ {
ElementAppear = 0, ElementAppear = 0 /*<< Animate the appearance of an element */,
ElementDisappear ElementDisappear /*<< Animate the disappearance of an element */
}; };
enum RenderOp enum RenderOp
@ -57,6 +57,14 @@ public:
RenderBackground = 0 /*<< Render the background of an item */ RenderBackground = 0 /*<< Render the background of an item */
}; };
enum CurveShape
{
EaseInCurve = 0,
EaseOutCurve,
EaseInOutCurve,
LinearCurve
};
typedef int AnimId; typedef int AnimId;
/** /**
@ -67,7 +75,10 @@ public:
explicit Phase(QObject * parent = 0); explicit Phase(QObject * parent = 0);
~Phase(); ~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 stopElementAnimation(AnimId id);
void setAnimationPixmap(AnimId id, const QPixmap &pixmap); void setAnimationPixmap(AnimId id, const QPixmap &pixmap);
QPixmap animationResult(AnimId id); QPixmap animationResult(AnimId id);
@ -75,32 +86,12 @@ public:
Q_SIGNALS: Q_SIGNALS:
void animationComplete(QGraphicsItem *item, Animation anim); void animationComplete(QGraphicsItem *item, Animation anim);
public Q_SLOTS: protected:
void animate(QGraphicsItem* item, Animation anim); void timerEvent(QTimerEvent *event);
void render(QGraphicsItem* item, QImage& image, RenderOp op);
protected Q_SLOTS: protected Q_SLOTS:
void appletDestroyed(QObject*); 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: private:
void init(); void init();