File indexing completed on 2024-04-28 04:02:06

0001 /*
0002     This file is part of the KDE games kwin4 program
0003     SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "introsprite.h"
0009 
0010 // own
0011 #include "kfourinline_debug.h"
0012 // Std
0013 #include <cmath>
0014 
0015 #define sign(x) ((x) > 0 ? 1 : ((x) < 0 ? -1 : 0))
0016 
0017 /**
0018  * Store an animation script command. The command represents certain possible
0019  * animation sub mentods. The possible methods are listed in the enum Cmd
0020  */
0021 class AnimationCommand
0022 {
0023 public:
0024     /**
0025      * The supported commands.
0026      */
0027     enum Cmd { SHOW, HIDE, PAUSE, POSITION, LINEAR_DURATION, CIRCLE_DURATION, MANHATTEN };
0028     /**
0029      * Construct an animation command
0030      */
0031     AnimationCommand()
0032     {
0033         cmd = PAUSE;
0034         duration = 0;
0035         radius = 0.0;
0036         velocity = 0.0;
0037     }
0038     /**
0039      * The command type.
0040      */
0041     Cmd cmd;
0042     /**
0043      * How long does the command take [ms].
0044      */
0045     int duration;
0046     /**
0047      * The start position [0..1, 0..1].
0048      */
0049     QPointF start;
0050     /**
0051      * The end position [0..1, 0..1].
0052      */
0053     QPointF end;
0054     /**
0055      * The radius  [0..1] (if necessary).
0056      */
0057     double radius;
0058     /**
0059      * The velocity [0...1000] (if necessary).
0060      */
0061     double velocity;
0062 };
0063 
0064 // Constructor for the sprite
0065 IntroSprite::IntroSprite(const QString &id, ThemeManager *theme, int no, QGraphicsScene *scene)
0066     : Themeable(id, theme)
0067     , PixmapSprite(no, scene)
0068 {
0069     hide();
0070     mDeltaT = 0;
0071     mDelayT = 0;
0072     mNo = no;
0073     mTime.restart();
0074     mDebugTime.restart();
0075     mStartAnimation = true;
0076     if (theme)
0077         theme->updateTheme(this);
0078 }
0079 
0080 // Destructor
0081 IntroSprite::~IntroSprite()
0082 {
0083     // Clear animation list (deletes objects)
0084     clearAnimation();
0085 }
0086 
0087 // Change the theme
0088 void IntroSprite::changeTheme()
0089 {
0090     PixmapSprite::changeTheme();
0091 }
0092 
0093 // Clear the animation script
0094 void IntroSprite::clearAnimation(bool restartTime)
0095 {
0096     qDeleteAll(mAnimList);
0097     mAnimList.clear();
0098 
0099     // Reset time?
0100     if (restartTime) {
0101         mTime.restart();
0102         mDeltaT = 0;
0103         mDelayT = 0;
0104     }
0105     mStartAnimation = true;
0106 }
0107 
0108 // Add a show sprite command
0109 AnimationCommand *IntroSprite::addShow()
0110 {
0111     AnimationCommand *cmd = new AnimationCommand();
0112     cmd->cmd = AnimationCommand::SHOW;
0113     cmd->duration = 0;
0114     cmd->start = previousStart();
0115     cmd->end = previousEnd();
0116     mAnimList.append(cmd);
0117     return cmd;
0118 }
0119 
0120 // Add a hide sprite command
0121 AnimationCommand *IntroSprite::addHide()
0122 {
0123     AnimationCommand *cmd = new AnimationCommand();
0124     cmd->cmd = AnimationCommand::HIDE;
0125     cmd->duration = 0;
0126     cmd->start = previousStart();
0127     cmd->end = previousEnd();
0128     mAnimList.append(cmd);
0129     return cmd;
0130 }
0131 
0132 // Add a relative pause
0133 AnimationCommand *IntroSprite::addPause(int duration)
0134 {
0135     AnimationCommand *cmd = new AnimationCommand();
0136     cmd->cmd = AnimationCommand::PAUSE;
0137     cmd->duration = duration;
0138     cmd->start = previousStart();
0139     cmd->end = previousEnd();
0140     mAnimList.append(cmd);
0141     return cmd;
0142 }
0143 
0144 // Add an absolute pause...wait until the given time
0145 AnimationCommand *IntroSprite::addWait(int duration)
0146 {
0147     int currentDuration = animationDuration();
0148     if (currentDuration < duration) {
0149         AnimationCommand *cmd = new AnimationCommand();
0150         cmd->cmd = AnimationCommand::PAUSE;
0151         cmd->duration = duration - currentDuration;
0152         cmd->start = previousStart();
0153         cmd->end = previousEnd();
0154         mAnimList.append(cmd);
0155         return cmd;
0156     }
0157     return nullptr;
0158 }
0159 
0160 // Add a set position command
0161 AnimationCommand *IntroSprite::addPosition(QPointF start)
0162 {
0163     AnimationCommand *cmd = new AnimationCommand();
0164     cmd->cmd = AnimationCommand::POSITION;
0165     cmd->duration = 0;
0166     cmd->start = start;
0167     cmd->end = start;
0168     mAnimList.append(cmd);
0169     return cmd;
0170 }
0171 
0172 // Add a linear movement
0173 AnimationCommand *IntroSprite::addLinear(QPointF start, QPointF end, int duration)
0174 {
0175     AnimationCommand *cmd = new AnimationCommand();
0176     cmd->cmd = AnimationCommand::LINEAR_DURATION;
0177     cmd->duration = duration;
0178     cmd->start = start;
0179     cmd->end = end;
0180     mAnimList.append(cmd);
0181     return cmd;
0182 }
0183 
0184 // Add a relative linear movement, that is, starting from the previous position
0185 AnimationCommand *IntroSprite::addRelativeLinear(QPointF end, int duration)
0186 {
0187     AnimationCommand *cmd = new AnimationCommand();
0188     cmd->cmd = AnimationCommand::LINEAR_DURATION;
0189     cmd->duration = duration;
0190     cmd->start = previousEnd();
0191     cmd->end = end;
0192     mAnimList.append(cmd);
0193     return cmd;
0194 }
0195 
0196 // Add a circle movement
0197 AnimationCommand *IntroSprite::addCircle(QPointF start, double radius, int duration)
0198 {
0199     AnimationCommand *cmd = new AnimationCommand();
0200     cmd->cmd = AnimationCommand::CIRCLE_DURATION;
0201     cmd->duration = duration;
0202     cmd->start = start;
0203     cmd->end = start;
0204     cmd->radius = radius;
0205     mAnimList.append(cmd);
0206     return cmd;
0207 }
0208 
0209 // Add a relative manhatten move
0210 AnimationCommand *IntroSprite::addRelativeManhatten(QPointF end, double velocity)
0211 {
0212     AnimationCommand *cmd = new AnimationCommand();
0213     cmd->cmd = AnimationCommand::MANHATTEN;
0214     cmd->start = previousEnd();
0215     cmd->end = end;
0216     cmd->velocity = velocity;
0217     qreal dtx = fabs(cmd->end.x() - cmd->start.x()) / cmd->velocity * 1000.0;
0218     qreal dty = fabs(cmd->end.y() - cmd->start.y()) / cmd->velocity * 1000.0;
0219     cmd->duration = int(dtx + dty + 1.0);
0220     mAnimList.append(cmd);
0221     return cmd;
0222 }
0223 
0224 // Retrieve the last commands start position or the current position if none exists
0225 QPointF IntroSprite::previousStart()
0226 {
0227     AnimationCommand *previous = mAnimList.last();
0228     if (previous != nullptr)
0229         return previous->start;
0230     else
0231         return QPointF(x() / getScale(), y() / getScale());
0232 }
0233 
0234 // Retrieve the last commands end position or the current position if none exists
0235 QPointF IntroSprite::previousEnd()
0236 {
0237     AnimationCommand *previous = mAnimList.last();
0238     if (previous != nullptr)
0239         return previous->end;
0240     return QPointF(x() / getScale(), y() / getScale());
0241 }
0242 
0243 // Retrieve the duration of the given animation command
0244 int IntroSprite::duration(AnimationCommand *anim)
0245 {
0246     return anim->duration;
0247 }
0248 
0249 // Retrieve the overall duration of the animation
0250 int IntroSprite::animationDuration()
0251 {
0252     int dura = 0;
0253     for (int i = 0; i < mAnimList.size(); ++i) {
0254         dura += mAnimList[i]->duration;
0255     }
0256     return dura;
0257 }
0258 
0259 // Globally delay the animation
0260 void IntroSprite::delayAnimation(int duration)
0261 {
0262     mDelayT += duration;
0263 }
0264 
0265 // CanvasItem advance method
0266 void IntroSprite::advance(int phase)
0267 {
0268     // Ignore phase 0 (collisions)
0269     if (phase == 0) {
0270         QGraphicsItem::advance(phase);
0271         return;
0272     }
0273 
0274     // No animation pending
0275     if (mAnimList.isEmpty()) {
0276         QGraphicsItem::advance(phase);
0277         return;
0278     }
0279 
0280     // First time animation start
0281     if (mStartAnimation) {
0282         mTime.restart();
0283         mStartAnimation = false;
0284     }
0285     int elapsed = mTime.elapsed() + mDeltaT - mDelayT;
0286     if (elapsed < 0)
0287         elapsed = 0;
0288 
0289     // Get the current/first command
0290     AnimationCommand *anim = mAnimList.first();
0291 
0292     // Execute commands who were passed in the time since the
0293     // last call
0294     while (anim != nullptr && anim->duration <= elapsed) {
0295         executeCmd(anim, anim->duration);
0296         mAnimList.removeFirst();
0297         elapsed -= anim->duration;
0298         delete anim;
0299         if (!mAnimList.isEmpty())
0300             anim = mAnimList.first();
0301         else
0302             anim = nullptr;
0303     }
0304 
0305     // Handle current command
0306     if (anim != nullptr) {
0307         executeCmd(anim, elapsed);
0308     }
0309 
0310     // Restart timer and store remaining time
0311     mTime.restart();
0312     mDeltaT = elapsed;
0313     mDelayT = 0;
0314 
0315     // Parent advance
0316     QGraphicsItem::advance(phase);
0317 }
0318 
0319 // Execute the given animation command. The given time is the time elapsed
0320 // since start of the animation sequence.
0321 void IntroSprite::executeCmd(AnimationCommand *anim, int elapsed)
0322 {
0323     if (anim == nullptr)
0324         return;
0325 
0326     // Scale
0327     double scale = this->getScale();
0328 
0329     // Pause command
0330     if (anim->cmd == AnimationCommand::PAUSE) {
0331         // Do nothing
0332     }
0333     // Show sprite command
0334     if (anim->cmd == AnimationCommand::SHOW) {
0335         show();
0336     }
0337     // Hide sprite command
0338     if (anim->cmd == AnimationCommand::HIDE) {
0339         hide();
0340     }
0341     // Set position command
0342     if (anim->cmd == AnimationCommand::POSITION) {
0343         qreal x = anim->end.x();
0344         qreal y = anim->end.y();
0345         setPos(x * scale, y * scale);
0346     }
0347     // Linear move command
0348     if (anim->cmd == AnimationCommand::LINEAR_DURATION) {
0349         double qt = double(elapsed) / double(anim->duration);
0350         qreal x = anim->start.x() + qt * (anim->end.x() - anim->start.x());
0351         qreal y = anim->start.y() + qt * (anim->end.y() - anim->start.y());
0352         setPos(x * scale, y * scale);
0353     }
0354     // Circle move command
0355     if (anim->cmd == AnimationCommand::CIRCLE_DURATION) {
0356         double qt = double(elapsed) / double(anim->duration);
0357         double sign = 1.0;
0358         if (anim->start.x() > 0.5)
0359             sign = -1.0; // Direction of turn
0360         qreal cx = anim->start.x();
0361         qreal cy = anim->start.y();
0362         qreal x = cx + anim->radius * sin(sign * qt * 2.0 * M_PI);
0363         qreal y = cy - anim->radius + anim->radius * cos(sign * qt * 2.0 * M_PI);
0364         setPos(x * scale, y * scale);
0365     }
0366     // Manhatten move command
0367     if (anim->cmd == AnimationCommand::MANHATTEN) {
0368         if ((fabs(anim->end.x() - x() / scale) <= 0.00001 && fabs(anim->end.y() - y() / scale) <= 0.00001) || (elapsed >= anim->duration)) {
0369             setPos(anim->end.x() * scale, anim->end.y() * scale);
0370         }
0371         // x-edge
0372         else if (fabs(anim->end.x() - x() / scale) > 0.00001) {
0373             qreal x = anim->start.x() + sign(anim->end.x() - anim->start.x()) * anim->velocity * double(elapsed) / 1000.0;
0374             // Finished?
0375             if ((anim->end.x() > anim->start.x() && x >= anim->end.x()) || (anim->end.x() < anim->start.x() && x <= anim->end.x())) {
0376                 x = anim->end.x();
0377             }
0378             setPos(x * scale, anim->start.y() * scale);
0379         }
0380         // y-edge
0381         else {
0382             qreal dtx = fabs(anim->end.x() - anim->start.x()) / anim->velocity * 1000.0;
0383             qreal y = anim->start.y() + sign(anim->end.y() - anim->start.y()) * anim->velocity * double(elapsed - dtx) / 1000.0;
0384             // Finished?
0385             if ((anim->end.y() > anim->start.y() && y >= anim->end.y()) || (anim->end.y() < anim->start.y() && y <= anim->end.y())) {
0386                 y = anim->end.y();
0387             }
0388             setPos(anim->end.x() * scale, y * scale);
0389         }
0390     }
0391 }