File indexing completed on 2023-09-24 08:17:45

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     QPixmap nullPixmap;
0076     for (int i = startFrame; i <= endFrame; i++)
0077     {
0078         mFrames.append(nullPixmap);
0079         mHotspots.append(QPointF(0.0, 0.0));
0080     }
0081 
0082     // Start with backside to save calculation time
0083     if (mCurrentFrame < 0) mCurrentFrame = endFrame;
0084 
0085     // Set pixmap to sprite
0086     setFrame(mCurrentFrame, true);
0087     update();
0088 }
0089 
0090 // Stop all movement and animation
0091 void CardSprite::stop()
0092 {
0093     mAnimationState = Idle;
0094     mTime           = 0.0;
0095     setBackside();
0096 }
0097 
0098 // Set the current animation mode of this sprite
0099 void CardSprite::setTurning(bool front)
0100 {
0101     mFrontFlag      = front;
0102     mAnimationState = Turning;
0103     mTime           = 0.0;
0104 }
0105 
0106 // Set target position and calculate moving speed.
0107 void CardSprite::calcTargetAndSpeed(QPointF pos, double time)
0108 {
0109     // Convert time from [ms] to advance cycles
0110     time = time / mAdvancePeriod;
0111 
0112     double scale = getScale();
0113     mMoveTarget  = pos;
0114     // Calculate move speed so that the duration for the move
0115     // is fixed
0116     double dx    = mMoveTarget.x() - x() / scale;
0117     double dy    = mMoveTarget.y() - y() / scale;
0118     double angle = atan2(dy, dx);
0119     mMoveSpeedX  = cos(angle) * sqrt(dx * dx + dy * dy) / time;
0120     mMoveSpeedY  = sin(angle) * sqrt(dx * dx + dy * dy) / time;
0121 }
0122 
0123 // Move the sprite to the given relative position
0124 void CardSprite::setPosition(QPointF pos)
0125 {
0126     mStart       = pos;
0127     setPos(mStart.x() * getScale(), mStart.y() * getScale());
0128 }
0129 
0130 // Move the sprite slowly to the target area. Stop movement if it arrived there.
0131 void CardSprite::setMove(QPointF pos, double time)
0132 {
0133     mAnimationState = Moving;
0134     mTime           = 0.0;
0135     calcTargetAndSpeed(pos, time);
0136 }
0137 
0138 // Move the sprite slowly to the target area. Stop
0139 // movement and remove sprite if it arrived there.
0140 void CardSprite::setRemove(QPointF pos, double time)
0141 {
0142     mAnimationState = Removing;
0143     calcTargetAndSpeed(pos, time);
0144 }
0145 
0146 // Delay before moving, then move the sprite fast to the
0147 // target area. Stop movement and depending on the last
0148 // argument turn backside/frontside sprite if it arrived there.
0149 void CardSprite::setShuffleMove(QPointF pos, double delay, bool front)
0150 {
0151     setBackside();
0152     calcTargetAndSpeed(pos, SHUFFLEMOVE_TIME);
0153     mAnimationState = ShuffleMove;
0154     mTime           = delay;
0155     mFrontFlag      = front;
0156 }
0157 
0158 // Display the card front pixmap image
0159 void CardSprite::setFrontside()
0160 {
0161     // Choose card front frame
0162     setFrame(0);
0163 }
0164 
0165 // Display the card back pixmap image
0166 void CardSprite::setBackside()
0167 {
0168     // Choose card back frame (last one in the animation sequence)
0169     setFrame(mFrames.size() - 1);
0170 }
0171 
0172 int CardSprite::count()
0173 {
0174     return mFrames.count();
0175 }
0176 
0177 // Set a new bitmap into the sprite. If the number is the same as the
0178 // current one, nothing is done.
0179 void CardSprite::setFrame(int no, bool force)
0180 {
0181     if (!force && no == mCurrentFrame) return;
0182     if (no < 0 || no >= mFrames.count()) return;
0183 
0184     // Calculate Pixmap (only done if necessary)
0185     calcFrame(no);
0186 
0187     // Set frame pixmap
0188     QPixmap pixmap = mFrames.at(no);
0189     setPixmap(pixmap);
0190 
0191     // Translation
0192     QPoint offset = thememanager()->getOffset();
0193     resetTransform();
0194     setTransform(QTransform::fromTranslate(mHotspots[no].x() + offset.x(), mHotspots[no].y() + offset.y()), true);
0195 
0196     mCurrentFrame = no;
0197     update();
0198 }
0199 
0200 // Calculate a pixmap for a frame. Only calculates it if it
0201 // is not previously stored to avoid double calculations.
0202 void CardSprite::calcFrame(int no)
0203 {
0204     QPixmap pixmap = mFrames.at(no);
0205     // Check whether frame is already loaded
0206     if (pixmap.isNull())
0207     {
0208         double dx = 0.0;
0209         double dy = 0.0;
0210         // Frontside
0211         if (no == 0)
0212         {
0213             pixmap = thememanager()->getCard(mSuite, mCardType, mWidth);
0214         }
0215         // Backside
0216         else if (no >= mFrames.count() - 1)
0217         {
0218             pixmap = thememanager()->getCardback(mWidth);
0219         }
0220         // Animation
0221         else
0222         {
0223             QPixmap front = thememanager()->getCard(mSuite, mCardType, mWidth);
0224             QPixmap back  = thememanager()->getCardback(mWidth);
0225             pixmap        = createCard(front, back, no, mFrames.count());
0226             dx            = (front.width() - pixmap.width()) / 2.0;
0227             dy            = (front.height() - pixmap.height()) / 2.0;
0228         }
0229         mFrames[no]   = pixmap;
0230         mHotspots[no] = QPointF(dx, dy);
0231     }
0232 }
0233 
0234 // Perform a move by a delta given by the sprites velocity.
0235 // Returns true if the target position is reached
0236 bool CardSprite::deltaMove()
0237 {
0238     // Calculate difference vector
0239     double scale = getScale();
0240     double dx    = mMoveTarget.x() - x() / scale;
0241     double dy    = mMoveTarget.y() - y() / scale;
0242 
0243     // Check arrival at target
0244     if (dx * dx + dy * dy < mMoveSpeedX * mMoveSpeedX + mMoveSpeedY * mMoveSpeedY)
0245     {
0246         setPosition(mMoveTarget);
0247         return true;
0248     }
0249     // Move towards target by given velocity
0250     else
0251     {
0252         setPosition(QPointF(x() / scale + mMoveSpeedX, y() / scale + mMoveSpeedY));
0253         return false;
0254     }
0255 }
0256 
0257 // CanvasItem advance method
0258 void CardSprite::advance(int phase)
0259 {
0260     // Ignore phase 0 (collisions)
0261     if (phase == 0)
0262     {
0263         QGraphicsItem::advance(phase);
0264         return;
0265     }
0266 
0267     // Turn a card
0268     if (mAnimationState == Turning)
0269     {
0270         mTime += mAdvancePeriod;
0271         // Turn delay counter
0272         if (mTime >= ANIM_CNT_TURNING)
0273         {
0274             mTime = 0.0;
0275             // Check whether animation is over
0276             if ((mFrontFlag && frame() == 0) ||
0277                 (!mFrontFlag && frame() == mFrames.size() - 1))
0278             {
0279                 mAnimationState = Idle;
0280             }
0281             else
0282             {
0283                 if (mFrontFlag) setFrame(frame() - 1);
0284                 else setFrame(frame() + 1);
0285             }
0286         }
0287     }// end if Turning
0288 
0289     // Move a card
0290     else if (mAnimationState == Moving)
0291     {
0292         // Perform a move by a delta given by the sprites velocity.
0293         if (deltaMove())
0294         {
0295             mAnimationState = Idle;
0296         }
0297     }// end if Moving
0298 
0299     // Move a card
0300     else if (mAnimationState == Removing)
0301     {
0302         // Perform a move by a delta given by the sprites velocity.
0303         if (deltaMove())
0304         {
0305             // Turn to backside
0306             setTurning(false);
0307         }
0308     }// end if Removing
0309 
0310     // Shuffle move a card
0311     else if (mAnimationState == ShuffleMove)
0312     {
0313         // First delay move until counter is run down
0314         if (mTime > 0.0)
0315         {
0316             mTime -= mAdvancePeriod;
0317         }
0318         // Then move to target position
0319         else
0320         {
0321             // Perform a move by a delta given by the sprites velocity.
0322             if (deltaMove())
0323             {
0324                 if (mFrontFlag) mAnimationState = Turning;
0325                 else mAnimationState = Idle;
0326                 mTime = 0.0;
0327             }
0328         }
0329     }// end if ShuffleMove
0330 
0331     QGraphicsItem::advance(phase);
0332 }
0333 
0334 // Create turn animation, i.e. card combined out of backside and frontside
0335 QPixmap CardSprite::createCard(const QPixmap &front, const QPixmap &back, int curNo, int count)
0336 {
0337     int halfCount = count / 2;
0338     // Turn the frontside of the card 0..90 degree
0339     if (curNo < halfCount)
0340     {
0341         QTransform m;
0342         // Get an angle eps..90 deg for the values i is running
0343         double angle = (double)curNo / (double)halfCount * 90.0;
0344         // Conversion to rad
0345         angle = angle / 180.0 * M_PI;
0346         // Scale pixmap to simulate rotating card
0347         m.scale(cos(angle), 1.0);
0348         QPixmap pm = front.transformed(m, Qt::SmoothTransformation);
0349         return pm;
0350     }
0351 
0352     // Turn the backside of the card 90..eps degree
0353     else
0354     {
0355         QTransform m;
0356         // Get an angle 0..90 deg for the values i is running
0357         double angle = 90.0 - ((double)(curNo - halfCount + 1) / (double)halfCount * 90.0);
0358         // Conversion to rad
0359         angle = angle / 180.0 * M_PI;
0360         // Scale pixmap to simulate rotating card
0361         m.scale(cos(angle), 1.0);
0362         QPixmap pm = back.transformed(m, Qt::SmoothTransformation);
0363         return pm;
0364     }
0365 }