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 }