File indexing completed on 2025-01-26 03:49:00

0001 /*
0002  *  Copyright (C) 2010 Parker Coates <coates@kde.org>
0003  *
0004  *  This program is free software; you can redistribute it and/or
0005  *  modify it under the terms of the GNU General Public License as
0006  *  published by the Free Software Foundation; either version 2 of
0007  *  the License, or (at your option) any later version.
0008  *
0009  *  This program is distributed in the hope that it will be useful,
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *  GNU General Public License for more details.
0013  *
0014  *  You should have received a copy of the GNU General Public License
0015  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
0016  *
0017  */
0018 
0019 #include "kcard.h"
0020 #include "kcard_p.h"
0021 
0022 // own
0023 #include "kabstractcarddeck.h"
0024 #include "kcardpile.h"
0025 // Qt
0026 #include <QPainter>
0027 #include <QPropertyAnimation>
0028 // Std
0029 #include <cmath>
0030 
0031 namespace
0032 {
0033 const qreal raisedZValue = 10000;
0034 }
0035 
0036 KCardAnimation::KCardAnimation(KCardPrivate *d, int duration, QPointF pos, qreal rotation, bool faceUp)
0037     : QAbstractAnimation(d)
0038     , d(d)
0039     , m_duration(duration)
0040     , m_x0(d->q->x())
0041     , m_y0(d->q->y())
0042     , m_rotation0(d->q->rotation())
0043     , m_flippedness0(d->flippedness())
0044     , m_xDelta(pos.x() - m_x0)
0045     , m_yDelta(pos.y() - m_y0)
0046     , m_rotationDelta(rotation - m_rotation0)
0047     , m_flippednessDelta((faceUp ? 1.0 : 0.0) - m_flippedness0)
0048 {
0049     qreal w = d->deck->cardWidth();
0050     qreal h = d->deck->cardHeight();
0051     qreal diagSquared = w * w + h * h;
0052     qreal distSquared = m_xDelta * m_xDelta + m_yDelta * m_yDelta;
0053 
0054     m_flipProgressFactor = qMax<qreal>(1, sqrt(distSquared / diagSquared));
0055 }
0056 
0057 int KCardAnimation::duration() const
0058 {
0059     return m_duration;
0060 }
0061 
0062 void KCardAnimation::updateCurrentTime(int msec)
0063 {
0064     qreal progress = qreal(msec) / m_duration;
0065     qreal flipProgress = qMin<qreal>(1, progress * m_flipProgressFactor);
0066 
0067     d->q->setPos(m_x0 + m_xDelta * progress, m_y0 + m_yDelta * progress);
0068     d->q->setRotation(m_rotation0 + m_rotationDelta * progress);
0069     d->setFlippedness(m_flippedness0 + m_flippednessDelta * flipProgress);
0070 }
0071 
0072 KCardPrivate::KCardPrivate(KCard *card)
0073     : QObject(card)
0074     , q(card)
0075 {
0076 }
0077 
0078 void KCardPrivate::setFlippedness(qreal flippedness)
0079 {
0080     if (flippedness == flipValue)
0081         return;
0082 
0083     if (flipValue < 0.5 && flippedness >= 0.5)
0084         q->setPixmap(frontPixmap);
0085     else if (flipValue >= 0.5 && flippedness < 0.5)
0086         q->setPixmap(backPixmap);
0087 
0088     flipValue = flippedness;
0089 
0090     qreal xOffset = deck->cardWidth() * (0.5 - qAbs(flippedness - 0.5));
0091     qreal xScale = qAbs(2 * flippedness - 1);
0092 
0093     q->setTransform(QTransform().translate(xOffset, 0).scale(xScale, 1));
0094 }
0095 
0096 qreal KCardPrivate::flippedness() const
0097 {
0098     return flipValue;
0099 }
0100 
0101 void KCardPrivate::setHighlightedness(qreal highlightedness)
0102 {
0103     highlightValue = highlightedness;
0104     q->update();
0105 }
0106 
0107 qreal KCardPrivate::highlightedness() const
0108 {
0109     return highlightValue;
0110 }
0111 
0112 KCard::KCard(quint32 id, KAbstractCardDeck *deck)
0113     : QObject()
0114     , QGraphicsPixmapItem()
0115     , d(new KCardPrivate(this))
0116 {
0117     d->id = id;
0118     d->deck = deck;
0119 
0120     d->faceUp = true;
0121     d->flipValue = d->faceUp ? 1 : 0;
0122     d->highlighted = false;
0123     d->highlightValue = d->highlighted ? 1 : 0;
0124 
0125     d->pile = nullptr;
0126 
0127     d->animation = nullptr;
0128 
0129     d->fadeAnimation = new QPropertyAnimation(d, "highlightedness", d);
0130     d->fadeAnimation->setDuration(150);
0131     d->fadeAnimation->setKeyValueAt(0, 0);
0132     d->fadeAnimation->setKeyValueAt(1, 1);
0133 }
0134 
0135 KCard::~KCard()
0136 {
0137     stopAnimation();
0138 
0139     // If the card is in a pile, remove it from there.
0140     if (pile())
0141         pile()->remove(this);
0142 }
0143 
0144 int KCard::type() const
0145 {
0146     return KCard::Type;
0147 }
0148 
0149 quint32 KCard::id() const
0150 {
0151     return d->id;
0152 }
0153 
0154 int KCard::rank() const
0155 {
0156     return d->deck->rankFromId(d->id);
0157 }
0158 
0159 int KCard::suit() const
0160 {
0161     return d->deck->suitFromId(d->id);
0162 }
0163 
0164 int KCard::color() const
0165 {
0166     return d->deck->colorFromId(d->id);
0167 }
0168 
0169 void KCard::setPile(KCardPile *pile)
0170 {
0171     d->pile = pile;
0172 }
0173 
0174 KCardPile *KCard::pile() const
0175 {
0176     return d->pile;
0177 }
0178 
0179 void KCard::setFaceUp(bool faceUp)
0180 {
0181     qreal flippedness = faceUp ? 1.0 : 0.0;
0182     if (d->faceUp != faceUp || d->flipValue != flippedness) {
0183         d->faceUp = faceUp;
0184         d->setFlippedness(flippedness);
0185     }
0186 }
0187 
0188 bool KCard::isFaceUp() const
0189 {
0190     return d->faceUp;
0191 }
0192 
0193 void KCard::animate(QPointF pos, qreal z, qreal rotation, bool faceUp, bool raised, int duration)
0194 {
0195     stopAnimation();
0196 
0197     if (duration > 0 && (qAbs(pos.x() - x()) > 2 || qAbs(pos.y() - y()) > 2 || qAbs(rotation - this->rotation()) > 2 || faceUp != d->faceUp)) {
0198         if (raised)
0199             raise();
0200 
0201         d->destZ = z;
0202         d->faceUp = faceUp;
0203 
0204         d->animation = new KCardAnimation(d, duration, pos, rotation, faceUp);
0205         connect(d->animation, &KCardAnimation::finished, this, &KCard::stopAnimation);
0206         d->animation->start();
0207         Q_EMIT animationStarted(this);
0208     } else {
0209         setPos(pos);
0210         setZValue(z);
0211         setRotation(rotation);
0212         setFaceUp(faceUp);
0213     }
0214 }
0215 
0216 bool KCard::isAnimated() const
0217 {
0218     return d->animation != nullptr;
0219 }
0220 
0221 void KCard::raise()
0222 {
0223     if (zValue() < raisedZValue)
0224         setZValue(raisedZValue + zValue());
0225 }
0226 
0227 void KCard::setHighlighted(bool flag)
0228 {
0229     if (flag != d->highlighted) {
0230         d->highlighted = flag;
0231 
0232         d->fadeAnimation->setDirection(flag ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
0233 
0234         if (d->fadeAnimation->state() != QAbstractAnimation::Running)
0235             d->fadeAnimation->start();
0236     }
0237 }
0238 
0239 bool KCard::isHighlighted() const
0240 {
0241     return d->highlighted;
0242 }
0243 
0244 void KCard::completeAnimation()
0245 {
0246     if (!d->animation)
0247         return;
0248 
0249     d->animation->disconnect(this);
0250     if (d->animation->state() != QAbstractAnimation::Stopped)
0251         d->animation->setCurrentTime(d->animation->duration());
0252 
0253     stopAnimation();
0254 }
0255 
0256 void KCard::stopAnimation()
0257 {
0258     if (!d->animation)
0259         return;
0260 
0261     delete d->animation;
0262     d->animation = nullptr;
0263 
0264     setZValue(d->destZ);
0265 
0266     Q_EMIT animationStopped(this);
0267 }
0268 
0269 void KCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0270 {
0271     Q_UNUSED(option);
0272     Q_UNUSED(widget);
0273 
0274     if (pixmap().size() != d->deck->cardSize() * pixmap().devicePixelRatio()) {
0275         QPixmap newPix = d->deck->cardPixmap(d->id, d->faceUp);
0276         if (d->faceUp)
0277             setFrontPixmap(newPix);
0278         else
0279             setBackPixmap(newPix);
0280     }
0281 
0282     // Enable smooth pixmap transformation only if the card is rotated. We
0283     // don't really need it otherwise and it slows down our flip animations.
0284     painter->setRenderHint(QPainter::SmoothPixmapTransform, int(rotation()) % 90);
0285 
0286     QPixmap pix = pixmap();
0287 
0288     if (d->highlightValue > 0) {
0289         QPainter p(&pix);
0290         p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
0291         p.fillRect(0, 0, pix.width(), pix.height(), QColor::fromRgbF(0, 0, 0, 0.5 * d->highlightValue));
0292     }
0293 
0294     painter->drawPixmap(0, 0, pix);
0295 }
0296 
0297 void KCard::setFrontPixmap(const QPixmap &pix)
0298 {
0299     d->frontPixmap = pix;
0300     if (d->flipValue >= 0.5)
0301         setPixmap(d->frontPixmap);
0302 }
0303 
0304 void KCard::setBackPixmap(const QPixmap &pix)
0305 {
0306     d->backPixmap = pix;
0307     if (d->flipValue < 0.5)
0308         setPixmap(d->backPixmap);
0309 }
0310 
0311 void KCard::setPixmap(const QPixmap &pix)
0312 {
0313     QGraphicsPixmapItem::setPixmap(pix);
0314 }
0315 
0316 #include "moc_kcard.cpp"
0317 #include "moc_kcard_p.cpp"