File indexing completed on 2024-05-12 04:04:49
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"