File indexing completed on 2024-05-19 04:04:54
0001 /* 0002 This file is part of the game 'KJumpingCube' 0003 0004 SPDX-FileCopyrightText: 1998-2000 Matthias Kiefer <matthias.kiefer@gmx.de> 0005 SPDX-FileCopyrightText: 2012-2013 Ian Wadhan <iandw.au@gmail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "kcubeboxwidget.h" 0011 0012 #include <KGameTheme> 0013 0014 #include <QTimer> 0015 #include <QLabel> 0016 #include <QPainter> 0017 0018 #include <assert.h> 0019 #include <QStandardPaths> 0020 0021 #include "prefs.h" 0022 0023 #include "kjumpingcube_debug.h" 0024 0025 KCubeBoxWidget::KCubeBoxWidget (const int d, QWidget *parent) 0026 : QWidget (parent), 0027 m_side (d), 0028 m_popup (new QLabel (this)) 0029 { 0030 qCDebug(KJUMPINGCUBE_LOG) << "CONSTRUCT KCubeBoxWidget: side" << m_side; 0031 cubes.clear(); 0032 init(); 0033 } 0034 0035 KCubeBoxWidget::~KCubeBoxWidget() 0036 { 0037 } 0038 0039 bool KCubeBoxWidget::loadSettings() 0040 { 0041 qCDebug(KJUMPINGCUBE_LOG) << "LOAD VIEW SETTINGS"; 0042 bool reColorCubes = ((color1 != Prefs::color1()) || 0043 (color2 != Prefs::color2()) || 0044 (color0 != Prefs::color0())); 0045 0046 color1 = Prefs::color1(); 0047 color2 = Prefs::color2(); 0048 color0 = Prefs::color0(); 0049 0050 if (Prefs::animationNone()) { 0051 cascadeAnimation = None; 0052 } 0053 else if (Prefs::animationDelay() || (Prefs::animationSpeed() <= 1)) { 0054 cascadeAnimation = Darken; 0055 } 0056 else if (Prefs::animationBlink()) { 0057 cascadeAnimation = RapidBlink; 0058 } 0059 else if (Prefs::animationSpread()) { 0060 cascadeAnimation = Scatter; 0061 } 0062 0063 animationTime = Prefs::animationSpeed() * 150; 0064 0065 // NOTE: When the box-size (Prefs::cubeDim()) changes, Game::newGame() calls 0066 // KCubeBoxWidget::loadSettings() first, then KCubeBoxWidget::setDim(). 0067 0068 if (reColorCubes) { 0069 makeStatusPixmaps (sWidth); // Make new status pixmaps. 0070 makeSVGCubes (cubeSize); 0071 setColors (); 0072 } 0073 return reColorCubes; 0074 } 0075 0076 void KCubeBoxWidget::reset() // Called if a player wins or requests New game. 0077 { 0078 for (KCubeWidget * cube : std::as_const(cubes)) { 0079 cube->reset(); 0080 } 0081 0082 KCubeWidget::enableClicks(true); 0083 currentAnimation = None; 0084 } 0085 0086 void KCubeBoxWidget::displayCube (int index, Player owner, int value) 0087 { 0088 cubes.at(index)->setOwner (owner); 0089 cubes.at(index)->setValue (value); 0090 } 0091 0092 void KCubeBoxWidget::highlightCube (int index, bool highlight) 0093 { 0094 if (highlight) { 0095 cubes.at(index)->setDark(); 0096 } 0097 else { 0098 cubes.at(index)->setNeutral(); 0099 } 0100 } 0101 0102 void KCubeBoxWidget::timedCubeHighlight (int index) 0103 { 0104 if (m_highlighted > 0) { 0105 highlightDone(); 0106 } 0107 cubes.at(index)->setDark(); 0108 m_highlighted = index; 0109 m_highlightTimer->start(); 0110 } 0111 0112 void KCubeBoxWidget::highlightDone() 0113 { 0114 cubes.at(m_highlighted)->setNeutral(); 0115 m_highlightTimer->stop(); 0116 m_highlighted = -1; 0117 } 0118 0119 void KCubeBoxWidget::setColors () 0120 { 0121 for (KCubeWidget * cube : std::as_const(cubes)) { 0122 cube->updateColors(); 0123 } 0124 } 0125 0126 void KCubeBoxWidget::setDim(int d) 0127 { 0128 if (d != m_side) { 0129 m_side = d; 0130 initCubes(); 0131 reCalculateGraphics (width(), height()); 0132 reset(); 0133 } 0134 } 0135 0136 /* ***************************************************************** ** 0137 ** slots ** 0138 ** ***************************************************************** */ 0139 0140 void KCubeBoxWidget::setWaitCursor() 0141 { 0142 setCursor (Qt::BusyCursor); 0143 } 0144 0145 void KCubeBoxWidget::setNormalCursor() 0146 { 0147 setCursor (Qt::PointingHandCursor); 0148 } 0149 0150 bool KCubeBoxWidget::checkClick (int x, int y) 0151 { 0152 /* IDW TODO - Remove this from the view OR rewrite it as a MouseEvent(). 0153 * 0154 // IDW TODO - Write a new mouse-click event for KCubeBoxWidget? Remove the 0155 // one that KCubeWidget has? 0156 */ 0157 qCDebug(KJUMPINGCUBE_LOG) << "Emit mouseClick (" << x << y << ")"; 0158 Q_EMIT mouseClick (x, y); 0159 return false; 0160 } 0161 0162 /* ***************************************************************** ** 0163 ** initializing functions ** 0164 ** ***************************************************************** */ 0165 void KCubeBoxWidget::init() 0166 { 0167 currentAnimation = None; 0168 animationSteps = 12; 0169 animationCount = 0; 0170 0171 setMinimumSize (200, 200); 0172 color1 = Prefs::color1(); // Set preferred colors. 0173 color2 = Prefs::color2(); 0174 color0 = Prefs::color0(); 0175 0176 KGameTheme theme((QByteArray())); 0177 theme.readFromDesktopFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("themes/default.desktop"))); 0178 svg.load (theme.graphicsPath()); 0179 0180 initCubes(); 0181 0182 animationTime = Prefs::animationSpeed() * 150; 0183 animationTimer = new QTimer(this); 0184 0185 m_highlightTimer = new QTimer(this); 0186 m_highlightTimer->setInterval (1500); 0187 m_highlighted = -1; 0188 0189 connect(animationTimer, &QTimer::timeout, this, &KCubeBoxWidget::nextAnimationStep); 0190 connect(m_highlightTimer, &QTimer::timeout, this, &KCubeBoxWidget::highlightDone); 0191 setNormalCursor(); 0192 setPopup(); 0193 } 0194 0195 void KCubeBoxWidget::initCubes() 0196 { 0197 qDeleteAll (cubes); 0198 cubes.clear(); 0199 0200 int nCubes = m_side * m_side; 0201 for (int n = 0; n < nCubes; n++) { 0202 KCubeWidget * cube = new KCubeWidget (this); 0203 cubes.append (cube); 0204 cube->setCoordinates (n / m_side, n % m_side, m_side - 1); 0205 cube->setPixmaps (&elements); 0206 connect(cube, &KCubeWidget::clicked, this, &KCubeBoxWidget::checkClick); 0207 cube->show(); 0208 } 0209 } 0210 0211 void KCubeBoxWidget::makeStatusPixmaps (const int width) 0212 { 0213 qreal d, p; 0214 QImage status (width, width, QImage::Format_ARGB32_Premultiplied); 0215 QPainter s (&status); 0216 sWidth = width; 0217 0218 d = width/4.0; 0219 p = width/2.0; 0220 status.fill (0); 0221 svg.render (&s, QStringLiteral("player_1")); 0222 colorImage (status, color1, width); 0223 svg.render (&s, QStringLiteral("lighting")); 0224 svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d)); 0225 status1 = QPixmap::fromImage (status); 0226 0227 d = width/5.0; 0228 p = width/3.0; 0229 status.fill (0); 0230 svg.render (&s, QStringLiteral("player_2")); 0231 colorImage (status, color2, width); 0232 svg.render (&s, QStringLiteral("lighting")); 0233 svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d)); 0234 svg.render (&s, QStringLiteral("pip"), QRectF (p + p - d/2.0, p + p - d/2.0, d, d)); 0235 s.end(); 0236 status2 = QPixmap::fromImage (status); 0237 } 0238 0239 void KCubeBoxWidget::makeSVGBackground (const int w, const int h) 0240 { 0241 QImage img (w, h, QImage::Format_ARGB32_Premultiplied); 0242 QPainter p (&img); 0243 img.fill (0); 0244 svg.render (&p, QStringLiteral("background")); 0245 p.end(); 0246 background = QPixmap::fromImage (img); 0247 } 0248 0249 void KCubeBoxWidget::makeSVGCubes (const int width) 0250 { 0251 QImage img (width, width, QImage::Format_ARGB32_Premultiplied); 0252 QPainter q; // Paints whole faces of the dice. 0253 0254 QImage pip (width/7, width/7, QImage::Format_ARGB32_Premultiplied); 0255 QPainter r; // Paints the pips on the faces of the dice. 0256 0257 QRectF rect (0, 0, width, width); 0258 qreal pc = 20.0; // % radius on corners. 0259 elements.clear(); 0260 for (int i = FirstElement; i <= LastElement; i++) { 0261 q.begin(&img); 0262 q.setPen (Qt::NoPen); 0263 if (i == Pip) { 0264 pip.fill (0); 0265 } 0266 else { 0267 img.fill (0); 0268 } 0269 0270 // NOTE: "neutral", "player_1" and "player_2" from file "default.svg" cause 0271 // odd effects at the corners. You get a cleaner look if they are omitted. 0272 0273 switch (i) { 0274 case Neutral: 0275 // svg.render (&q, "neutral"); 0276 q.setBrush (color0); 0277 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize); 0278 svg.render (&q, QStringLiteral("lighting")); 0279 break; 0280 case Player1: 0281 // svg.render (&q, "player_1"); 0282 q.setBrush (color1); 0283 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize); 0284 svg.render (&q, QStringLiteral("lighting")); 0285 break; 0286 case Player2: 0287 // svg.render (&q, "player_2"); 0288 q.setBrush (color2); 0289 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize); 0290 svg.render (&q, QStringLiteral("lighting")); 0291 break; 0292 case Pip: 0293 r.begin(&pip); 0294 svg.render (&r, QStringLiteral("pip")); 0295 r.end(); 0296 break; 0297 case BlinkLight: 0298 svg.render (&q, QStringLiteral("blink_light")); 0299 break; 0300 case BlinkDark: 0301 svg.render (&q, QStringLiteral("blink_dark")); 0302 break; 0303 default: 0304 break; 0305 } 0306 q.end(); 0307 elements.append 0308 ((i == Pip) ? QPixmap::fromImage (pip) : QPixmap::fromImage (img)); 0309 } 0310 } 0311 0312 void KCubeBoxWidget::colorImage (QImage & img, const QColor & c, const int w) 0313 { 0314 QRgb rgba = c.rgba(); 0315 for (int i = 0; i < w; i++) { 0316 for (int j = 0; j < w; j++) { 0317 if (img.pixel (i, j) != 0) { 0318 img.setPixel (i, j, rgba); 0319 } 0320 } 0321 } 0322 } 0323 0324 void KCubeBoxWidget::paintEvent (QPaintEvent * /* event unused */) 0325 { 0326 QPainter p (this); 0327 p.drawPixmap (0, 0, background); 0328 } 0329 0330 void KCubeBoxWidget::resizeEvent (QResizeEvent * event) 0331 { 0332 reCalculateGraphics (event->size().width(), event->size().height()); 0333 } 0334 0335 void KCubeBoxWidget::reCalculateGraphics (const int w, const int h) 0336 { 0337 int boxSize = qMin(w, h); 0338 int frameWidth = boxSize / 30; 0339 // qCDebug(KJUMPINGCUBE_LOG) << "boxSize" << boxSize << "frameWidth" << frameWidth; 0340 boxSize = boxSize - (2 * frameWidth); 0341 cubeSize = (boxSize / m_side); 0342 boxSize = (cubeSize * m_side); 0343 topLeft.setX ((w - boxSize)/2); 0344 topLeft.setY ((h - boxSize)/2); 0345 0346 // qCDebug(KJUMPINGCUBE_LOG) << "Dimension:" << m_side << "cubeSize:" << cubeSize << "topLeft:" << topLeft; 0347 makeSVGBackground (w, h); 0348 makeSVGCubes (cubeSize); 0349 for (int x = 0; x < m_side; x++) { 0350 for (int y = 0; y < m_side; y++) { 0351 int index = x * m_side + y; 0352 cubes.at (index)->move ( 0353 topLeft.x() + (x * cubeSize), 0354 topLeft.y() + (y * cubeSize)); 0355 cubes.at (index)->resize (cubeSize, cubeSize); 0356 } 0357 } 0358 setPopup(); 0359 } 0360 0361 QSize KCubeBoxWidget::sizeHint() const 0362 { 0363 return QSize(400,400); 0364 } 0365 0366 /* ***************************************************************** ** 0367 ** other private functions ** 0368 ** ***************************************************************** */ 0369 0370 void KCubeBoxWidget::startAnimation (bool cascading, int index) 0371 { 0372 int interval = 0; 0373 m_index = index; 0374 currentAnimation = cascading ? cascadeAnimation : ComputerMove; 0375 switch (currentAnimation) { 0376 case None: 0377 animationCount = 0; 0378 return; // Should never happen. 0379 break; 0380 case ComputerMove: 0381 interval = 150 + (Prefs::animationSpeed() - 1) * 50; // 150-600 msec. 0382 animationCount = 4; 0383 cubes.at (index)->setLight(); 0384 break; 0385 case Darken: 0386 interval = animationTime; 0387 animationCount = 1; 0388 cubes.at (index)->setDark(); 0389 break; 0390 case RapidBlink: 0391 interval = 60 + Prefs::animationSpeed() * 30; // 120-360 msec. 0392 animationCount = 4; 0393 cubes.at (index)->setLight(); 0394 break; 0395 case Scatter: 0396 interval = (animationTime + animationSteps/2) / animationSteps; 0397 animationCount = animationSteps; 0398 break; 0399 } 0400 animationTimer->setInterval (interval); 0401 animationTimer->start(); 0402 } 0403 0404 void KCubeBoxWidget::nextAnimationStep() 0405 { 0406 animationCount--; 0407 if (animationCount < 1) { 0408 animationTimer->stop(); // Finish normally. 0409 cubes.at (m_index)->setNeutral(); 0410 currentAnimation = None; 0411 Q_EMIT animationDone (m_index); 0412 return; 0413 } 0414 switch (currentAnimation) { 0415 case None: 0416 return; // Should not happen. 0417 break; 0418 case ComputerMove: 0419 case RapidBlink: 0420 if (animationCount%2 == 1) { // Set light or dark phase. 0421 cubes.at (m_index)->setDark(); 0422 } 0423 else { 0424 cubes.at (m_index)->setLight(); 0425 } 0426 break; 0427 case Darken: 0428 break; // Should never happen (1 tick). 0429 case Scatter: 0430 int step = animationSteps - animationCount; 0431 if (step <= 2) { // Set the animation phase. 0432 cubes.at (m_index)->shrink(1.0 - step * 0.3); 0433 } 0434 else if (step < 7) { 0435 cubes.at (m_index)->expand((step - 2) * 0.2); 0436 } 0437 else if (step == 7) { 0438 cubes.at (m_index)->expand(1.2); 0439 scatterDots (0); 0440 } 0441 else { 0442 scatterDots (step - 7); 0443 } 0444 break; 0445 } 0446 } 0447 0448 void KCubeBoxWidget::scatterDots (int step) 0449 { 0450 Player player = cubes.at(m_index)->owner(); 0451 int d = m_side - 1; 0452 int x = m_index / m_side; 0453 int y = m_index % m_side; 0454 if (x > 0) cubes.at (m_index - m_side)->migrateDot (+1, 0, step, player); 0455 if (x < d) cubes.at (m_index + m_side)->migrateDot (-1, 0, step, player); 0456 if (y > 0) cubes.at (m_index - 1) ->migrateDot ( 0, +1, step, player); 0457 if (y < d) cubes.at (m_index + 1) ->migrateDot ( 0, -1, step, player); 0458 } 0459 0460 int KCubeBoxWidget::killAnimation() 0461 { 0462 if (animationTimer->isActive()) { 0463 animationTimer->stop(); // Stop current animation immediately. 0464 } 0465 return m_index; 0466 } 0467 0468 const QPixmap & KCubeBoxWidget::playerPixmap (const int p) 0469 { 0470 return ((p == 1) ? status1 : status2); 0471 } 0472 0473 void KCubeBoxWidget::setPopup() 0474 { 0475 QFont f; 0476 f.setPixelSize ((int) (height() * 0.04 + 0.5)); 0477 f.setWeight (QFont::Bold); 0478 f.setStretch (QFont::Expanded); 0479 m_popup->setStyleSheet(QStringLiteral("QLabel { color : rgba(255, 255, 255, 75%); }")); 0480 m_popup->setFont (f); 0481 m_popup->resize (width(), (int) (height() * 0.08 + 0.5)); 0482 m_popup->setAlignment (Qt::AlignCenter); 0483 } 0484 0485 void KCubeBoxWidget::showPopup (const QString & message) 0486 { 0487 m_popup->setText (message); 0488 m_popup->move ((this->width() - m_popup->width()) / 2, 0489 (this->height() - m_popup->height()) / 2 + 0490 (cubes.at (0)->height() / 5)); 0491 m_popup->raise(); 0492 m_popup->show(); 0493 update(); 0494 } 0495 0496 void KCubeBoxWidget::hidePopup() 0497 { 0498 m_popup->hide(); 0499 update(); 0500 } 0501 0502 #include "moc_kcubeboxwidget.cpp"