File indexing completed on 2024-05-12 08:00:33

0001 /*
0002  * Copyright (C) 2008-2009 Stephan Kulow <coolo@kde.org>
0003  * Copyright (C) 2008-2009 Parker Coates <coates@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU General Public License as
0007  * published by the Free Software Foundation; either version 2 of
0008  * the License, or (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "gameselectionscene.h"
0020 
0021 // own
0022 #include "dealerinfo.h"
0023 #include "kpat_debug.h"
0024 #include "renderer.h"
0025 // KF
0026 #include <KColorUtils>
0027 // Qt
0028 #include <QGraphicsObject>
0029 #include <QKeyEvent>
0030 #include <QPainter>
0031 #include <QPropertyAnimation>
0032 #include <QStandardPaths>
0033 #include <QStyleOptionGraphicsItem>
0034 // Std
0035 #include <cmath>
0036 
0037 namespace
0038 {
0039 const qreal boxPaddingRatio = 0.037;
0040 const qreal spacingRatio = 0.10;
0041 const qreal textToBoxHeightRatio = 1 / 6.0;
0042 const qreal textToBoxWidthRatio = 0.57;
0043 const int hoverTransitionDuration = 300;
0044 const int minimumFontSize = 5;
0045 }
0046 
0047 class GameSelectionScene::GameSelectionBox : public QGraphicsObject
0048 {
0049     Q_OBJECT
0050     Q_PROPERTY(qreal fade READ hoverFadeAmount WRITE setHoverFadeAmount)
0051 
0052 public:
0053     GameSelectionBox(const QString &name, int id)
0054         : m_label(name)
0055         , m_gameId(id)
0056         , m_anim(new QPropertyAnimation(this, "fade", this))
0057         , m_highlightFadeAmount(0)
0058         , m_previewPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kpat/previews/%1.png").arg(id)))
0059     {
0060         setAcceptHoverEvents(true);
0061         m_anim->setDuration(hoverTransitionDuration);
0062         m_anim->setStartValue(qreal(0.0));
0063         m_anim->setEndValue(qreal(1.0));
0064         m_anim->setEasingCurve(QEasingCurve::InOutSine);
0065     }
0066 
0067     void setSize(const QSize &size)
0068     {
0069         if (size != m_size) {
0070             m_size = size;
0071             m_preview = QPixmap();
0072         }
0073     }
0074 
0075     QRectF boundingRect() const override
0076     {
0077         return QRectF(QPointF(), m_size);
0078     }
0079 
0080     QString label() const
0081     {
0082         return m_label;
0083     }
0084 
0085     int id() const
0086     {
0087         return m_gameId;
0088     }
0089 
0090     void setHighlighted(bool highlighted)
0091     {
0092         if (highlighted) {
0093             m_anim->setDirection(QAbstractAnimation::Forward);
0094             if (m_anim->state() != QAbstractAnimation::Running)
0095                 m_anim->start();
0096         } else {
0097             m_anim->setDirection(QAbstractAnimation::Backward);
0098             if (m_anim->state() != QAbstractAnimation::Running)
0099                 m_anim->start();
0100         }
0101     }
0102 
0103     static bool lessThan(const GameSelectionBox *a, const GameSelectionBox *b)
0104     {
0105         return a->m_label < b->m_label;
0106     }
0107 
0108 Q_SIGNALS:
0109     void selected(int gameId);
0110     void hoverChanged(GameSelectionBox *box, bool hovered);
0111 
0112 protected:
0113     void mousePressEvent(QGraphicsSceneMouseEvent *event) override
0114     {
0115         Q_UNUSED(event)
0116         Q_EMIT selected(m_gameId);
0117     }
0118 
0119     void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
0120     {
0121         Q_UNUSED(event)
0122         Q_EMIT hoverChanged(this, true);
0123     }
0124 
0125     void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
0126     {
0127         Q_UNUSED(event)
0128         Q_EMIT hoverChanged(this, false);
0129     }
0130 
0131     qreal hoverFadeAmount() const
0132     {
0133         return m_highlightFadeAmount;
0134     }
0135 
0136     void setHoverFadeAmount(qreal amount)
0137     {
0138         m_highlightFadeAmount = amount;
0139         update();
0140     }
0141 
0142     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override
0143     {
0144         Q_UNUSED(option)
0145         Q_UNUSED(widget)
0146 
0147         Renderer *r = Renderer::self();
0148         int textAreaHeight = m_size.height() * textToBoxHeightRatio;
0149         int padding = boxPaddingRatio * m_size.width();
0150         QSize previewSize(m_size.height() - padding * 2, m_size.height() - padding * 2 - textAreaHeight);
0151         QRect textRect(0, 0, m_size.width(), textAreaHeight);
0152 
0153         if (m_highlightFadeAmount < 1)
0154             painter->drawPixmap(0, 0, r->spritePixmap(QStringLiteral("bubble"), m_size));
0155 
0156         if (m_highlightFadeAmount > 0) {
0157             if (m_highlightFadeAmount < 1) {
0158                 // Using QPainter::setOpacity is currently very inefficient, so to
0159                 // paint a semitransparent pixmap, we have to do some fiddling.
0160                 QPixmap transPix(m_size);
0161                 transPix.fill(Qt::transparent);
0162                 QPainter p(&transPix);
0163                 p.drawPixmap(0, 0, r->spritePixmap(QStringLiteral("bubble_hover"), m_size));
0164                 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0165                 p.fillRect(transPix.rect(), QColor(0, 0, 0, m_highlightFadeAmount * 255));
0166                 painter->drawPixmap(0, 0, transPix);
0167             } else {
0168                 painter->drawPixmap(0, 0, r->spritePixmap(QStringLiteral("bubble_hover"), m_size));
0169             }
0170         }
0171 
0172         // Draw game preview
0173         if (m_preview.isNull()) {
0174             QPixmap previewPix(m_previewPath);
0175             m_preview = previewPix.scaled(previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0176         }
0177         painter->drawPixmap((m_size.width() - previewSize.width()) / 2, padding + textAreaHeight, m_preview);
0178 
0179         // Draw label
0180         painter->setFont(scene()->font());
0181         painter->setPen(KColorUtils::mix(r->colorOfElement(QStringLiteral("bubble_text_color")),
0182                                          r->colorOfElement(QStringLiteral("bubble_hover_text_color")),
0183                                          m_highlightFadeAmount));
0184         painter->drawText(textRect, Qt::AlignCenter, m_label);
0185     }
0186 
0187 private:
0188     QString m_label;
0189     int m_gameId;
0190     QSize m_size;
0191     QPropertyAnimation *m_anim;
0192     qreal m_highlightFadeAmount;
0193     QPixmap m_preview;
0194     QString m_previewPath;
0195 };
0196 
0197 GameSelectionScene::GameSelectionScene(QObject *parent)
0198     : QGraphicsScene(parent)
0199     , m_selectionIndex(-1)
0200 {
0201     const auto games = DealerInfoList::self()->games();
0202     for (const DealerInfo *i : games) {
0203         GameSelectionBox *box = new GameSelectionBox(i->baseName(), i->baseId());
0204         m_boxes.append(box);
0205         addItem(box);
0206 
0207         connect(box, &GameSelectionBox::selected, this, &GameSelectionScene::gameSelected);
0208         connect(box, &GameSelectionBox::hoverChanged, this, &GameSelectionScene::boxHoverChanged);
0209     }
0210 
0211     std::sort(m_boxes.begin(), m_boxes.end(), GameSelectionBox::lessThan);
0212 }
0213 
0214 GameSelectionScene::~GameSelectionScene()
0215 {
0216 }
0217 
0218 void GameSelectionScene::resizeScene(const QSize &size)
0219 {
0220     int numBoxes = m_boxes.size();
0221     qreal boxAspect = Renderer::self()->aspectRatioOfElement(QStringLiteral("bubble"));
0222     qreal sceneAspect = qreal(size.width()) / size.height();
0223 
0224     // Determine the optimal number of rows/columns for the grid
0225     m_columns = 1;
0226     int numRows = 1;
0227     int bestNumRows = 1;
0228     qreal lowestError = 10e10;
0229     for (numRows = 1; numRows <= numBoxes; ++numRows) {
0230         m_columns = ceil(qreal(numBoxes) / numRows);
0231         int numNonEmptyRows = ceil(qreal(numBoxes) / m_columns);
0232         qreal gridAspect = boxAspect * m_columns / numNonEmptyRows;
0233         qreal error = gridAspect > sceneAspect ? gridAspect / sceneAspect : sceneAspect / gridAspect;
0234         if (error < lowestError) {
0235             lowestError = error;
0236             bestNumRows = numRows;
0237         }
0238     }
0239     numRows = bestNumRows;
0240     m_columns = ceil(qreal(numBoxes) / bestNumRows);
0241 
0242     // Calculate the box and grid dimensions
0243     qreal gridAspect = boxAspect * (m_columns + spacingRatio * (m_columns + 1)) / (numRows + spacingRatio * (numRows + 1));
0244     int boxWidth, boxHeight;
0245     if (gridAspect > sceneAspect) {
0246         boxWidth = size.width() / (m_columns + spacingRatio * (m_columns + 1));
0247         boxHeight = boxWidth / boxAspect;
0248     } else {
0249         boxHeight = size.height() / (numRows + spacingRatio * (numRows + 1));
0250         boxWidth = boxHeight * boxAspect;
0251     }
0252     int gridWidth = boxWidth * (m_columns + spacingRatio * (m_columns + 1));
0253     int gridHeight = boxHeight * (numRows + spacingRatio * (numRows + 1));
0254 
0255     int xOffset = (gridWidth - size.width()) / 2 - boxWidth * spacingRatio;
0256     int yOffset = (gridHeight - size.height()) / 2 - boxHeight * spacingRatio;
0257 
0258     // Set up the sceneRect so that the grid is centered
0259     setSceneRect(xOffset, yOffset, size.width(), size.height());
0260 
0261     QPixmap pix(1, 1);
0262     QPainter p(&pix);
0263     QFont f;
0264 
0265     // Initial font size estimate
0266     int pixelFontSize = boxHeight * (textToBoxHeightRatio - 1.5 * boxPaddingRatio);
0267     f.setPixelSize(pixelFontSize);
0268     p.setFont(f);
0269 
0270     qreal maxLabelWidth = boxWidth * textToBoxWidthRatio;
0271     int row = 0;
0272     int col = 0;
0273 
0274     for (GameSelectionBox *box : std::as_const(m_boxes)) {
0275         // Reduce font size until the label fits
0276         while (pixelFontSize > minimumFontSize && p.boundingRect(QRectF(), box->label()).width() > maxLabelWidth) {
0277             f.setPixelSize(--pixelFontSize);
0278             p.setFont(f);
0279         }
0280 
0281         // Position and size the boxes
0282         box->setPos(col * (boxWidth * (1 + spacingRatio)), row * (boxHeight * (1 + spacingRatio)));
0283         box->setSize(QSize(boxWidth, boxHeight));
0284 
0285         // Increment column and row
0286         ++col;
0287         if (col == m_columns) {
0288             col = 0;
0289             ++row;
0290         }
0291     }
0292 
0293     setFont(f);
0294 }
0295 
0296 void GameSelectionScene::keyReleaseEvent(QKeyEvent *event)
0297 {
0298     if (m_selectionIndex == -1) {
0299         m_selectionIndex = 0;
0300         m_boxes.at(m_selectionIndex)->setHighlighted(true);
0301     } else if (event->key() == Qt::Key_Up && m_selectionIndex / m_columns > 0) {
0302         m_boxes.at(m_selectionIndex)->setHighlighted(false);
0303         m_selectionIndex -= m_columns;
0304         m_boxes.at(m_selectionIndex)->setHighlighted(true);
0305     } else if (event->key() == Qt::Key_Down && m_selectionIndex + m_columns < m_boxes.size()) {
0306         m_boxes.at(m_selectionIndex)->setHighlighted(false);
0307         m_selectionIndex += m_columns;
0308         m_boxes.at(m_selectionIndex)->setHighlighted(true);
0309     } else if (event->key() == Qt::Key_Left && m_selectionIndex % m_columns > 0) {
0310         m_boxes.at(m_selectionIndex)->setHighlighted(false);
0311         --m_selectionIndex;
0312         m_boxes.at(m_selectionIndex)->setHighlighted(true);
0313     } else if (event->key() == Qt::Key_Right && m_selectionIndex % m_columns < m_columns - 1 && m_selectionIndex < m_boxes.size() - 1) {
0314         m_boxes.at(m_selectionIndex)->setHighlighted(false);
0315         ++m_selectionIndex;
0316         m_boxes.at(m_selectionIndex)->setHighlighted(true);
0317     }
0318 }
0319 
0320 void GameSelectionScene::keyPressEvent(QKeyEvent *event)
0321 {
0322     if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter || event->key() == Qt::Key_Space) && m_selectionIndex != -1) {
0323         Q_EMIT gameSelected(m_boxes.at(m_selectionIndex)->id());
0324     }
0325 }
0326 
0327 void GameSelectionScene::boxHoverChanged(GameSelectionScene::GameSelectionBox *box, bool hovered)
0328 {
0329     if (hovered) {
0330         if (m_selectionIndex != -1)
0331             m_boxes.at(m_selectionIndex)->setHighlighted(false);
0332 
0333         m_selectionIndex = m_boxes.indexOf(box);
0334         box->setHighlighted(true);
0335     } else {
0336         if (m_boxes.indexOf(box) == m_selectionIndex) {
0337             m_selectionIndex = -1;
0338             box->setHighlighted(false);
0339         }
0340     }
0341 }
0342 
0343 #include "gameselectionscene.moc"
0344 #include "moc_gameselectionscene.cpp"