2 * Copyright 2007 Aaron Seigo <aseigo@kde.org>
3 * 2007 Alexis Ménard <darktears31@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Library General Public License as
7 * published by the Free Software Foundation; either version 2, or
8 * (at your option) any later version.
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 Library General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include <QGraphicsItem>
26 #include <KConfigGroup>
28 #include <KServiceTypeTrader>
29 #include <KGlobalSettings>
32 #include "widgets/widget.h"
37 static const qreal MIN_TICK_RATE
= 40;
43 Phase::Animation animation
;
44 Phase::CurveShape curve
;
51 struct ElementAnimationState
55 Phase::CurveShape curve
;
56 Phase::ElementAnimation animation
;
69 Phase::CurveShape curve
;
70 Phase::Movement movement
;
79 struct CustomAnimationState
81 Phase::CurveShape curve
;
104 qDeleteAll(animatedItems
);
105 qDeleteAll(animatedElements
);
106 qDeleteAll(movingItems
);
108 QMutableMapIterator
<AnimId
, CustomAnimationState
*> it(customAnims
);
109 while (it
.hasNext()) {
110 delete it
.value()->slot
;
115 // Animator is a QObject
116 // and we don't own the items
119 qreal
calculateProgress(int frames
, int currentFrame
)
121 if (!(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects
)) {
125 qreal progress
= frames
;
126 progress
= currentFrame
/ progress
;
127 progress
= qMin(qreal(1.0), qMax(qreal(0.0), progress
));
131 void performAnimation(qreal amount
, const AnimationState
* state
)
133 switch (state
->animation
) {
135 animator
->appear(amount
, state
->item
);
137 case Phase::Disappear
:
138 animator
->disappear(amount
, state
->item
);
143 case Phase::Activate
:
144 animator
->activate(amount
, state
->item
);
149 void performMovement(qreal amount
, const MovementState
* state
)
151 switch (state
->movement
) {
153 case Phase::FastSlideIn
:
154 //kDebug() << "performMovement, SlideIn";
155 animator
->slideIn(amount
, state
->item
, state
->start
, state
->destination
);
157 case Phase::SlideOut
:
158 case Phase::FastSlideOut
:
159 //kDebug() << "performMovement, SlideOut";
160 animator
->slideOut(amount
, state
->item
, state
->start
, state
->destination
);
170 //TODO: eventually perhaps we should allow multiple animations simulataneously
171 // which would imply changing this to a QMap<QGraphicsItem*, QList<QTimeLine*> >
172 // and really making the code fun ;)
173 QMap
<QGraphicsItem
*, AnimationState
*> animatedItems
;
174 QMap
<QGraphicsItem
*, MovementState
*> movingItems
;
175 QMap
<AnimId
, ElementAnimationState
*> animatedElements
;
176 QMap
<AnimId
, CustomAnimationState
*> customAnims
;
185 K_GLOBAL_STATIC( PhaseSingleton
, privateSelf
)
189 return &privateSelf
->self
;
193 Phase::Phase(QObject
* parent
)
205 void Phase::animatedItemDestroyed(QObject
* o
)
207 //kDebug() << "testing for" << (void*)o;
208 QMutableMapIterator
<QGraphicsItem
*, AnimationState
*> it(d
->animatedItems
);
209 while (it
.hasNext()) {
211 //kDebug() << "comparing against" << it.value()->qobj;
212 if (it
.value()->qobj
== o
) {
213 kDebug() << "found deleted animated item";
220 void Phase::movingItemDestroyed(QObject
* o
)
222 QMutableMapIterator
<QGraphicsItem
*, MovementState
*> it(d
->movingItems
);
223 while (it
.hasNext()) {
225 if (it
.value()->qobj
== o
) {
232 void Phase::animatedElementDestroyed(QObject
* o
)
234 QMutableMapIterator
<AnimId
, ElementAnimationState
*> it(d
->animatedElements
);
235 while (it
.hasNext()) {
237 if (it
.value()->qobj
== o
) {
244 void Phase::customAnimReceiverDestroyed(QObject
* o
)
246 QMutableMapIterator
<AnimId
, CustomAnimationState
*> it(d
->customAnims
);
247 while (it
.hasNext()) {
248 if (it
.next().value()->receiver
== o
) {
249 delete it
.value()->slot
;
256 void Phase::animateItem(QGraphicsItem
* item
, Animation animation
)
259 // get rid of any existing animations on this item.
260 //TODO: shoudl we allow multiple anims per item?
261 QMap
<QGraphicsItem
*, AnimationState
*>::iterator it
= d
->animatedItems
.find(item
);
262 if (it
!= d
->animatedItems
.end()) {
264 d
->animatedItems
.erase(it
);
267 int frames
= d
->animator
->framesPerSecond(animation
);
270 // evidently this animator doesn't have an implementation
271 // for this Animation
275 AnimationState
* state
= new AnimationState
;
277 state
->animation
= animation
;
278 state
->curve
= d
->animator
->curve(animation
);
279 //TODO: variance in times based on the value of animation
280 state
->frames
= frames
/ 3;
281 state
->currentFrame
= 0;
282 state
->interval
= d
->animator
->duration(animation
) / state
->frames
;
283 state
->interval
= (state
->interval
/ MIN_TICK_RATE
) * MIN_TICK_RATE
;
284 state
->currentInterval
= state
->interval
;
285 state
->qobj
= dynamic_cast<QObject
*>(item
);
288 //kDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!! got us an object!";
289 disconnect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(animatedItemDestroyed(QObject
*)));
290 connect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(animatedItemDestroyed(QObject
*)));
293 d
->animatedItems
[item
] = state
;
294 d
->performAnimation(0, state
);
297 d
->timerId
= startTimer(MIN_TICK_RATE
);
302 void Phase::moveItem(QGraphicsItem
* item
, Movement movement
, const QPoint
&destination
)
305 QMap
<QGraphicsItem
*, MovementState
*>::iterator it
= d
->movingItems
.find(item
);
306 if (it
!= d
->movingItems
.end()) {
308 d
->movingItems
.erase(it
);
311 int frames
= d
->animator
->framesPerSecond(movement
);
313 // evidently this animator doesn't have an implementation
314 // for this Animation
318 MovementState
* state
= new MovementState
;
319 state
->destination
= destination
;
320 state
->start
= item
->pos().toPoint();
322 state
->movement
= movement
;
323 state
->curve
= d
->animator
->curve(movement
);
324 //TODO: variance in times based on the value of animation
325 state
->frames
= frames
/ 2;
326 state
->currentFrame
= 0;
327 state
->interval
= d
->animator
->duration(movement
) / state
->frames
;
328 state
->interval
= (state
->interval
/ MIN_TICK_RATE
) * MIN_TICK_RATE
;
329 state
->currentInterval
= state
->interval
;
330 state
->qobj
= dynamic_cast<QObject
*>(item
);
333 disconnect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(movingItemDestroyed(QObject
*)));
334 connect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(movingItemDestroyed(QObject
*)));
337 d
->movingItems
[item
] = state
;
338 d
->performMovement(0, state
);
341 d
->timerId
= startTimer(MIN_TICK_RATE
);
346 Phase::AnimId
Phase::customAnimation(int frames
, int duration
, Phase::CurveShape curve
,
347 QObject
* receiver
, const char* slot
)
349 if (frames
< 1 || duration
< 1 || !receiver
|| !slot
) {
353 CustomAnimationState
*state
= new CustomAnimationState
;
354 state
->id
= ++d
->animId
;
355 state
->frames
= frames
;
356 state
->currentFrame
= 0;
357 state
->curve
= curve
;
358 state
->interval
= duration
/ qreal(state
->frames
);
359 state
->interval
= qMax( 1, state
->interval
);
360 state
->interval
= (state
->interval
/ MIN_TICK_RATE
) * MIN_TICK_RATE
;
361 state
->currentInterval
= state
->interval
;
362 state
->receiver
= receiver
;
363 state
->slot
= qstrdup(slot
);
365 d
->customAnims
[state
->id
] = state
;
367 disconnect(receiver
, SIGNAL(destroyed(QObject
*)),
368 this, SLOT(customAnimReceiverDestroyed(QObject
*)));
369 connect(receiver
, SIGNAL(destroyed(QObject
*)),
370 this, SLOT(customAnimReceiverDestroyed(QObject
*)));
372 QMetaObject::invokeMethod(receiver
, slot
, Q_ARG(qreal
, 0));
375 d
->timerId
= startTimer(MIN_TICK_RATE
);
382 void Phase::stopCustomAnimation(AnimId id
)
384 QMap
<AnimId
, CustomAnimationState
*>::iterator it
= d
->customAnims
.find(id
);
385 if (it
!= d
->customAnims
.end()) {
386 delete [] it
.value()->slot
;
388 d
->customAnims
.erase(it
);
390 //kDebug() << "stopCustomAnimation(AnimId " << id << ") done";
393 Phase::AnimId
Phase::animateElement(QGraphicsItem
*item
, ElementAnimation animation
)
395 //kDebug() << "startElementAnimation(AnimId " << animation << ")";
396 ElementAnimationState
*state
= new ElementAnimationState
;
398 state
->curve
= d
->animator
->curve(animation
);
399 state
->animation
= animation
;
400 //TODO: variance in times based on the value of animation
401 state
->frames
= d
->animator
->framesPerSecond(animation
) / 5;
402 state
->currentFrame
= 0;
403 state
->interval
= d
->animator
->duration(animation
) / state
->frames
;
404 state
->interval
= (state
->interval
/ MIN_TICK_RATE
) * MIN_TICK_RATE
;
405 state
->currentInterval
= state
->interval
;
406 state
->id
= ++d
->animId
;
407 state
->qobj
= dynamic_cast<QObject
*>(item
);
410 disconnect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(animatedElementDestroyed(QObject
*)));
411 connect(state
->qobj
, SIGNAL(destroyed(QObject
*)), this, SLOT(animatedElementDestroyed(QObject
*)));
414 //kDebug() << "animateElement " << animation << ", interval: " << state->interval << ", frames: " << state->frames;
415 bool needTimer
= true;
416 if (state
->frames
< 1) {
418 state
->currentFrame
= 1;
422 d
->animatedElements
[state
->id
] = state
;
423 // nasty hack because QGraphicsItem::update isn't virtual!
424 // FIXME: remove in 4.1 as we will no longer need the caching in Plasma::Widget with Qt 4.4
425 Plasma::Widget
*widget
= dynamic_cast<Plasma::Widget
*>(state
->item
);
429 state
->item
->update();
432 //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state->id;
433 if (needTimer
&& !d
->timerId
) {
434 // start a 20fps timer;
435 //TODO: should be started at the maximum frame rate needed only?
436 d
->timerId
= startTimer(MIN_TICK_RATE
);
442 void Phase::stopElementAnimation(AnimId id
)
444 QMap
<AnimId
, ElementAnimationState
*>::iterator it
= d
->animatedElements
.find(id
);
445 if (it
!= d
->animatedElements
.end()) {
447 d
->animatedElements
.erase(it
);
449 //kDebug() << "stopElementAnimation(AnimId " << id << ") done";
452 void Phase::setAnimationPixmap(AnimId id
, const QPixmap
&pixmap
)
454 QMap
<AnimId
, ElementAnimationState
*>::iterator it
= d
->animatedElements
.find(id
);
456 if (it
== d
->animatedElements
.end()) {
457 kDebug() << "Phase::setAnimationPixmap(" << id
<< ") found no entry for it!";
461 it
.value()->pixmap
= pixmap
;
464 QPixmap
Phase::animationResult(AnimId id
)
466 QMap
<AnimId
, ElementAnimationState
*>::const_iterator it
= d
->animatedElements
.find(id
);
468 if (it
== d
->animatedElements
.constEnd()) {
469 //kDebug() << "Phase::animationResult(" << id << ") found no entry for it!";
473 ElementAnimationState
* state
= it
.value();
474 qreal progress
= state
->frames
;
475 //kDebug() << "Phase::animationResult(" << id << " at " << progress;
476 progress
= state
->currentFrame
/ progress
;
477 progress
= qMin(qreal(1.0), qMax(qreal(0.0), progress
));
478 //kDebug() << "Phase::animationResult(" << id << " at " << progress;
480 switch (state
->animation
) {
482 return d
->animator
->elementAppear(progress
, state
->pixmap
);
484 case ElementDisappear
:
485 return d
->animator
->elementDisappear(progress
, state
->pixmap
);
489 return state
->pixmap
;
492 void Phase::timerEvent(QTimerEvent
*event
)
495 bool animationsRemain
= false;
496 int elapsed
= MIN_TICK_RATE
;
497 if (d
->time
.elapsed() > elapsed
) {
498 elapsed
= d
->time
.elapsed();
501 //kDebug() << "timeEvent, elapsed time: " << elapsed;
503 foreach (AnimationState
* state
, d
->animatedItems
) {
504 if (state
->currentInterval
<= elapsed
) {
505 // we need to step forward!
506 state
->currentFrame
+= (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects
) ?
507 qMax(1, elapsed
/ state
->interval
) : state
->frames
- state
->currentFrame
;
509 if (state
->currentFrame
< state
->frames
) {
510 qreal progress
= d
->calculateProgress(state
->frames
, state
->currentFrame
);
511 d
->performAnimation(progress
, state
);
512 state
->currentInterval
= state
->interval
;
513 //TODO: calculate a proper interval based on the curve
514 state
->interval
*= 1 - progress
;
515 animationsRemain
= true;
517 d
->performAnimation(1, state
);
518 d
->animatedItems
.erase(d
->animatedItems
.find(state
->item
));
519 emit
animationComplete(state
->item
, state
->animation
);
523 state
->currentInterval
-= elapsed
;
524 animationsRemain
= true;
528 foreach (MovementState
* state
, d
->movingItems
) {
529 if (state
->currentInterval
<= elapsed
) {
530 // we need to step forward!
531 state
->currentFrame
+= (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects
) ?
532 qMax(1, elapsed
/ state
->interval
) : state
->frames
- state
->currentFrame
;
534 if (state
->currentFrame
< state
->frames
) {
535 d
->performMovement(d
->calculateProgress(state
->frames
, state
->currentFrame
), state
);
536 //TODO: calculate a proper interval based on the curve
537 state
->currentInterval
= state
->interval
;
538 animationsRemain
= true;
540 d
->performMovement(1, state
);
541 d
->movingItems
.erase(d
->movingItems
.find(state
->item
));
542 emit
movementComplete(state
->item
);
546 state
->currentInterval
-= elapsed
;
547 animationsRemain
= true;
551 foreach (ElementAnimationState
* state
, d
->animatedElements
) {
552 if (state
->currentFrame
== state
->frames
) {
553 //kDebug() << "skipping" << state->id << "as its already at frame" << state->currentFrame << "of" << state->frames;
554 // since we keep element animations around until they are
555 // removed, we will end up with finished animations in the queue;
557 //TODO: should we move them to a separate QMap?
561 if (state
->currentInterval
<= elapsed
) {
562 // we need to step forward!
563 /*kDebug() << "stepping forwards element anim " << state->id << " from " << state->currentFrame
564 << " by " << qMax(1, elapsed / state->interval) << " to "
565 << state->currentFrame + qMax(1, elapsed / state->interval) << endl;*/
566 state
->currentFrame
+= (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects
) ?
567 qMax(1, elapsed
/ state
->interval
) : state
->frames
- state
->currentFrame
;
568 // nasty hack because QGraphicsItem::update isn't virtual!
569 // FIXME: remove in 4.1 as we will no longer need the caching in Plasma::Widget with Qt 4.4
570 Plasma::Widget
*widget
= dynamic_cast<Plasma::Widget
*>(state
->item
);
574 state
->item
->update();
577 if (state
->currentFrame
< state
->frames
) {
578 state
->currentInterval
= state
->interval
;
579 //TODO: calculate a proper interval based on the curve
580 state
->interval
*= 1 - d
->calculateProgress(state
->frames
, state
->currentFrame
);
581 animationsRemain
= true;
583 d
->animatedElements
.remove(state
->id
);
584 emit
elementAnimationComplete(state
->id
);
588 state
->currentInterval
-= elapsed
;
589 animationsRemain
= true;
593 foreach (CustomAnimationState
*state
, d
->customAnims
) {
594 if (state
->currentInterval
<= elapsed
) {
596 state
->currentFrame
+= (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects
) ?
597 qMax(1, elapsed
/ state
->interval
) : state
->frames
- state
->currentFrame
;
598 /*kDebug() << "custom anim for" << state->receiver << "to slot" << state->slot
599 << "with interval of" << state->interval << "at frame" << state->currentFrame;*/
601 if (state
->currentFrame
< state
->frames
) {
602 //kDebug () << "not the final frame";
603 //TODO: calculate a proper interval based on the curve
604 state
->currentInterval
= state
->interval
;
605 animationsRemain
= true;
607 QMetaObject::invokeMethod(state
->receiver
, state
->slot
,
609 d
->calculateProgress(state
->frames
, state
->currentFrame
)));
611 QMetaObject::invokeMethod(state
->receiver
, state
->slot
, Q_ARG(qreal
, 1));
612 d
->customAnims
.erase(d
->customAnims
.find(state
->id
));
613 emit
customAnimationComplete(state
->id
);
614 delete [] state
->slot
;
618 state
->currentInterval
-= elapsed
;
619 animationsRemain
= true;
623 if (!animationsRemain
&& d
->timerId
) {
624 killTimer(d
->timerId
);
631 KConfig
c("plasmarc");
632 KConfigGroup
cg(&c
, "Phase");
633 QString pluginName
= cg
.readEntry("animator", "default");
635 if (!pluginName
.isEmpty()) {
636 QString constraint
= QString("[X-KDE-PluginInfo-Name] == '%1'").arg(pluginName
);
637 KService::List offers
= KServiceTypeTrader::self()->query("Plasma/Animator", constraint
);
639 if (!offers
.isEmpty()) {
641 d
->animator
= offers
.first()->createInstance
<Plasma::Animator
>(0, QVariantList(), &error
);
643 kDebug() << "Could not load requested animator " << offers
.first() << ". Error given: " << error
;
649 d
->animator
= new Animator(this);
653 } // namespace Plasma