File indexing completed on 2024-04-21 04:05:23

0001 /*
0002     This file is part of the KDE games lskat 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 "cardsprite.h"
0009 #include "lskat_debug.h"
0010 
0011 // General includes
0012 #include <cmath>
0013 
0014 // Qt includes
0015 #include <QGraphicsScene>
0016 #include <QTransform>
0017 
0018 // KF includes
0019 #include <KConfigGroup>
0020 
0021 // Delay for card turning animation [ms]
0022 #define ANIM_CNT_TURNING          20.0
0023 // Time in [ms] for a shuffle move
0024 #define SHUFFLEMOVE_TIME 100.0
0025 
0026 // Theme manager stuff
0027 #define THEME_ID "card"
0028 
0029 // Constructor for the view
0030 CardSprite::CardSprite(const Suite suite, const CardType cardtype, ThemeManager *theme,
0031                        int advancePeriod, QGraphicsScene *scene)
0032           : Themable(QStringLiteral(THEME_ID), theme), QGraphicsPixmapItem(nullptr)
0033 
0034 {
0035     scene->addItem(this);
0036     mAnimationState = Idle;
0037     mCurrentFrame   = -1; // Frame will be set to backside
0038     mAdvancePeriod  = advancePeriod;
0039     mSuite          = suite;
0040     mCardType       = cardtype;
0041     mFrames.clear();
0042 
0043     // Redraw
0044     if (theme) theme->updateTheme(this);
0045 }
0046 
0047 // Main themable function. Called for any theme change. The sprites needs to
0048 // resize and redraw here.
0049 void CardSprite::changeTheme()
0050 {
0051     // Get scaling change
0052     double oldscale = this->getScale();
0053     double scale    = thememanager()->getScale();
0054     Themable::setScale(scale);
0055 
0056     // Retrieve theme data from configuration
0057     KConfigGroup config = thememanager()->config(id());
0058     double width        = config.readEntry("width", 1.0);
0059     width *= scale;
0060     mWidth = width; // Store for later use
0061 
0062     // Z-value by program only
0063     // Nothing to do
0064 
0065     // Animation
0066     int startFrame      = config.readEntry("start-frame", 0);
0067     int endFrame        = config.readEntry("end-frame", 0);
0068 
0069     // Pos py program only
0070     setPos(x() * scale / oldscale, y() * scale / oldscale);
0071 
0072     // Frames (loaded on demand)
0073     mFrames.clear();
0074     mHotspots.clear();
0075     const int frameCount = endFrame - startFrame + 1;
0076     mFrames.reserve(frameCount);
0077     mHotspots.reserve(frameCount);
0078     QPixmap nullPixmap;
0079     for (int i = startFrame; i <= endFrame; i++)
0080     {
0081         mFrames.append(nullPixmap);
0082         mHotspots.append(QPointF(0.0, 0.0));
0083     }
0084 
0085     // Start with backside to save calculation time
0086     if (mCurrentFrame < 0) mCurrentFrame = endFrame;
0087 
0088     // Set pixmap to sprite
0089     setFrame(mCurrentFrame, true);
0090     update();
0091 }
0092 
0093 // Stop all movement and animation
0094 void CardSprite::stop()
0095 {
0096     mAnimationState = Idle;
0097     mTime           = 0.0;
0098     setBackside();
0099 }
0100 
0101 // Set the current animation mode of this sprite
0102 void CardSprite::setTurning(bool front)
0103 {
0104     mFrontFlag      = front;
0105     mAnimationState = Turning;
0106     mTime           = 0.0;
0107 }
0108 
0109 // Set target position and calculate moving speed.
0110 void CardSprite::calcTargetAndSpeed(QPointF pos, double time)
0111 {
0112     // Convert time from [ms] to advance cycles
0113     time = time / mAdvancePeriod;
0114 
0115     double scale = getScale();
0116     mMoveTarget  = pos;
0117     // Calculate move speed so that the duration for the move
0118     // is fixed
0119     double dx    = mMoveTarget.x() - x() / scale;
0120     double dy    = mMoveTarget.y() - y() / scale;
0121     double angle = atan2(dy, dx);
0122     mMoveSpeedX  = cos(angle) * sqrt(dx * dx + dy * dy) / time;
0123     mMoveSpeedY  = sin(angle) * sqrt(dx * dx + dy * dy) / time;
0124 }
0125 
0126 // Move the sprite to the given relative position
0127 void CardSprite::setPosition(QPointF pos)
0128 {
0129     mStart       = pos;
0130     setPos(mStart.x() * getScale(), mStart.y() * getScale());
0131 }
0132 
0133 // Move the sprite slowly to the target area. Stop movement if it arrived there.
0134 void CardSprite::setMove(QPointF pos, double time)
0135 {
0136     mAnimationState = Moving;
0137     mTime           = 0.0;
0138     calcTargetAndSpeed(pos, time);
0139 }
0140 
0141 // Move the sprite slowly to the target area. Stop
0142 // movement and remove sprite if it arrived there.
0143 void CardSprite::setRemove(QPointF pos, double time)
0144 {
0145     mAnimationState = Removing;
0146     calcTargetAndSpeed(pos, time);
0147 }
0148 
0149 // Delay before moving, then move the sprite fast to the
0150 // target area. Stop movement and depending on the last
0151 // argument turn backside/frontside sprite if it arrived there.
0152 void CardSprite::setShuffleMove(QPointF pos, double delay, bool front)
0153 {
0154     setBackside();
0155     calcTargetAndSpeed(pos, SHUFFLEMOVE_TIME);
0156     mAnimationState = ShuffleMove;
0157     mTime           = delay;
0158     mFrontFlag      = front;
0159 }
0160 
0161 // Display the card front pixmap image
0162 void CardSprite::setFrontside()
0163 {
0164     // Choose card front frame
0165     setFrame(0);
0166 }
0167 
0168 // Display the card back pixmap image
0169 void CardSprite::setBackside()
0170 {
0171     // Choose card back frame (last one in the animation sequence)
0172     setFrame(mFrames.size() - 1);
0173 }
0174 
0175 int CardSprite::count()
0176 {
0177     return mFrames.count();
0178 }
0179 
0180 // Set a new bitmap into the sprite. If the number is the same as the
0181 // current one, nothing is done.
0182 void CardSprite::setFrame(int no, bool force)
0183 {
0184     if (!force && no == mCurrentFrame) return;
0185     if (no < 0 || no >= mFrames.count()) return;
0186 
0187     // Calculate Pixmap (only done if necessary)
0188     calcFrame(no);
0189 
0190     // Set frame pixmap
0191     QPixmap pixmap = mFrames.at(no);
0192     setPixmap(pixmap);
0193 
0194     // Translation
0195     QPoint offset = thememanager()->getOffset();
0196     resetTransform();
0197     setTransform(QTransform::fromTranslate(mHotspots[no].x() + offset.x(), mHotspots[no].y() + offset.y()), true);
0198 
0199     mCurrentFrame = no;
0200     update();
0201 }
0202 
0203 // Calculate a pixmap for a frame. Only calculates it if it
0204 // is not previously stored to avoid double calculations.
0205 void CardSprite::calcFrame(int no)
0206 {
0207     QPixmap pixmap = mFrames.at(no);
0208     // Check whether frame is already loaded
0209     if (pixmap.isNull())
0210     {
0211         double dx = 0.0;
0212         double dy = 0.0;
0213         // Frontside
0214         if (no == 0)
0215         {
0216             pixmap = thememanager()->getCard(mSuite, mCardType, mWidth);
0217         }
0218         // Backside
0219         else if (no >= mFrames.count() - 1)
0220         {
0221             pixmap = thememanager()->getCardback(mWidth);
0222         }
0223         // Animation
0224         else
0225         {
0226             QPixmap front = thememanager()->getCard(mSuite, mCardType, mWidth);
0227             QPixmap back  = thememanager()->getCardback(mWidth);
0228             pixmap        = createCard(front, back, no, mFrames.count());
0229             dx            = (front.width() - pixmap.width()) / 2.0;
0230             dy            = (front.height() - pixmap.height()) / 2.0;
0231         }
0232         mFrames[no]   = pixmap;
0233         mHotspots[no] = QPointF(dx, dy);
0234     }
0235 }
0236 
0237 // Perform a move by a delta given by the sprites velocity.
0238 // Returns true if the target position is reached
0239 bool CardSprite::deltaMove()
0240 {
0241     // Calculate difference vector
0242     double scale = getScale();
0243     double dx    = mMoveTarget.x() - x() / scale;
0244     double dy    = mMoveTarget.y() - y() / scale;
0245 
0246     // Check arrival at target
0247     if (dx * dx + dy * dy < mMoveSpeedX * mMoveSpeedX + mMoveSpeedY * mMoveSpeedY)
0248     {
0249         setPosition(mMoveTarget);
0250         return true;
0251     }
0252     // Move towards target by given velocity
0253     else
0254     {
0255         setPosition(QPointF(x() / scale + mMoveSpeedX, y() / scale + mMoveSpeedY));
0256         return false;
0257     }
0258 }
0259 
0260 // CanvasItem advance method
0261 void CardSprite::advance(int phase)
0262 {
0263     // Ignore phase 0 (collisions)
0264     if (phase == 0)
0265     {
0266         QGraphicsItem::advance(phase);
0267         return;
0268     }
0269 
0270     // Turn a card
0271     if (mAnimationState == Turning)
0272     {
0273         mTime += mAdvancePeriod;
0274         // Turn delay counter
0275         if (mTime >= ANIM_CNT_TURNING)
0276         {
0277             mTime = 0.0;
0278             // Check whether animation is over
0279             if ((mFrontFlag && frame() == 0) ||
0280                 (!mFrontFlag && frame() == mFrames.size() - 1))
0281             {
0282                 mAnimationState = Idle;
0283             }
0284             else
0285             {
0286                 if (mFrontFlag) setFrame(frame() - 1);
0287                 else setFrame(frame() + 1);
0288             }
0289         }
0290     }// end if Turning
0291 
0292     // Move a card
0293     else if (mAnimationState == Moving)
0294     {
0295         // Perform a move by a delta given by the sprites velocity.
0296         if (deltaMove())
0297         {
0298             mAnimationState = Idle;
0299         }
0300     }// end if Moving
0301 
0302     // Move a card
0303     else if (mAnimationState == Removing)
0304     {
0305         // Perform a move by a delta given by the sprites velocity.
0306         if (deltaMove())
0307         {
0308             // Turn to backside
0309             setTurning(false);
0310         }
0311     }// end if Removing
0312 
0313     // Shuffle move a card
0314     else if (mAnimationState == ShuffleMove)
0315     {
0316         // First delay move until counter is run down
0317         if (mTime > 0.0)
0318         {
0319             mTime -= mAdvancePeriod;
0320         }
0321         // Then move to target position
0322         else
0323         {
0324             // Perform a move by a delta given by the sprites velocity.
0325             if (deltaMove())
0326             {
0327                 if (mFrontFlag) mAnimationState = Turning;
0328                 else mAnimationState = Idle;
0329                 mTime = 0.0;
0330             }
0331         }
0332     }// end if ShuffleMove
0333 
0334     QGraphicsItem::advance(phase);
0335 }
0336 
0337 // Create turn animation, i.e. card combined out of backside and frontside
0338 QPixmap CardSprite::createCard(const QPixmap &front, const QPixmap &back, int curNo, int count)
0339 {
0340     int halfCount = count / 2;
0341     // Turn the frontside of the card 0..90 degree
0342     if (curNo < halfCount)
0343     {
0344         QTransform m;
0345         // Get an angle eps..90 deg for the values i is running
0346         double angle = (double)curNo / (double)halfCount * 90.0;
0347         // Conversion to rad
0348         angle = angle / 180.0 * M_PI;
0349         // Scale pixmap to simulate rotating card
0350         m.scale(cos(angle), 1.0);
0351         QPixmap pm = front.transformed(m, Qt::SmoothTransformation);
0352         return pm;
0353     }
0354 
0355     // Turn the backside of the card 90..eps degree
0356     else
0357     {
0358         QTransform m;
0359         // Get an angle 0..90 deg for the values i is running
0360         double angle = 90.0 - ((double)(curNo - halfCount + 1) / (double)halfCount * 90.0);
0361         // Conversion to rad
0362         angle = angle / 180.0 * M_PI;
0363         // Scale pixmap to simulate rotating card
0364         m.scale(cos(angle), 1.0);
0365         QPixmap pm = back.transformed(m, Qt::SmoothTransformation);
0366         return pm;
0367     }
0368 }