File indexing completed on 2024-04-28 07:51:37
0001 /* 0002 This file is part of the KDE games kwin4 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 "kwin4view.h" 0009 0010 // own 0011 #include "displaygame.h" 0012 #include "displayintro.h" 0013 #include "kfourinline_debug.h" 0014 #include "reflectiongraphicsscene.h" 0015 #include "score.h" 0016 #include "spritenotify.h" 0017 // KDEGames 0018 #define USE_UNSTABLE_LIBKDEGAMESPRIVATE_API 0019 #include <libkdegamesprivate/kgame/kplayer.h> 0020 // Qt 0021 #include <QApplication> 0022 #include <QColor> 0023 #include <QElapsedTimer> 0024 #include <QEvent> 0025 // Std 0026 #include <cmath> 0027 0028 // How many time measurements for average 0029 #define MEASUREMENT_LIST_SIZE 50 0030 // How many warnings until reflections are switched off 0031 #define WARNING_MAX_COUNT 5 0032 // How many milliseconds rounding error 0033 #define MEASUREMENT_ROUNDING_ERROR 5 0034 0035 // Constructor for the view 0036 KWin4View::KWin4View(int updateTime, const QSize &size, ReflectionGraphicsScene *scene, ThemeManager *theme, QWidget *parent) 0037 : Themeable(QStringLiteral("theview"), theme) 0038 , QGraphicsView(scene, parent) 0039 { 0040 // Store attributes 0041 mScene = scene; 0042 mTheme = theme; 0043 mDefaultUpdateTime = updateTime; 0044 mSlowDownFactor = 1.0; 0045 mSlowCnt = 0; 0046 mReflectPhase = 0; 0047 0048 // We do not need scrolling so switch it off 0049 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0050 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0051 setFrameStyle(QFrame::NoFrame); 0052 setCacheMode(QGraphicsView::CacheBackground); 0053 // setAlignment(Qt::AlignHCenter); 0054 0055 // setViewportUpdateMode(QGraphicsView::FullViewportUpdate); 0056 setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); 0057 setOptimizationFlags(QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing); 0058 0059 viewport()->setMouseTracking(true); 0060 setMouseTracking(true); 0061 0062 // Choose a background color 0063 scene->setBackgroundBrush(QColor(0, 0, 128)); 0064 0065 mTimer = new QTimer(this); 0066 connect(mTimer, &QTimer::timeout, this, &KWin4View::updateAndAdvance); 0067 mTimer->start(mDefaultUpdateTime); 0068 0069 // Game status 0070 mIsRunning = false; 0071 0072 // Queue 0073 mThemeQueue.clear(); 0074 mThemeOffset.clear(); 0075 0076 // Set size and position of the view and the canvas: 0077 // they are resized once a level is loaded 0078 resize(size); 0079 scene->setSceneRect(0, 0, this->width(), this->height()); 0080 adjustSize(); 0081 0082 // Interact with user 0083 setInteractive(true); 0084 0085 // Scale theme 0086 mTheme->rescale(this->width(), QPoint(0, 0)); 0087 0088 // Start with the intro display 0089 mGameDisplay = nullptr; 0090 mIntroDisplay = nullptr; 0091 0092 // Reflections 0093 mReflectionSprite = new QGraphicsPixmapItem(); 0094 scene->addItem(mReflectionSprite); 0095 mReflectionSprite->setZValue(1000.0); 0096 mReflectionSprite->hide(); 0097 0098 // Debug 0099 mFrameSprite = new QGraphicsTextItem(); 0100 scene->addItem(mFrameSprite); 0101 mFrameSprite->setPos(QPointF(0.0, 0.0)); 0102 mFrameSprite->setZValue(1000.0); 0103 if (global_debug > 0) 0104 mFrameSprite->show(); 0105 else 0106 mFrameSprite->hide(); 0107 0108 // Skip the intro? 0109 if (!global_skip_intro) { 0110 mIntroDisplay = new DisplayIntro(scene, mTheme, this); 0111 connect(mIntroDisplay, &DisplayIntro::signalQuickStart, this, &KWin4View::signalQuickStart); 0112 mIntroDisplay->start(); 0113 } 0114 } 0115 0116 // Destruct the view object 0117 KWin4View::~KWin4View() 0118 { 0119 delete mIntroDisplay; 0120 delete mGameDisplay; 0121 if (global_debug > 0) 0122 qCDebug(KFOURINLINE_LOG) << "TRACKING" << hasMouseTracking() << "and" << viewport()->hasMouseTracking(); 0123 delete mFrameSprite; 0124 delete mReflectionSprite; 0125 } 0126 0127 // Main themeable function. Called for any theme change. 0128 void KWin4View::changeTheme() 0129 { 0130 if (global_debug > 0) 0131 qCDebug(KFOURINLINE_LOG) << "CHANGE THEME IN VIEW ... resetting slow counter"; 0132 mDrawTimes.clear(); 0133 mSlowDownFactor = 1.0; 0134 mSlowCnt = 0; 0135 mTimer->setInterval(int(mDefaultUpdateTime * mSlowDownFactor)); 0136 } 0137 0138 // Advance and update canvas/scene 0139 void KWin4View::updateAndAdvance() 0140 { 0141 // Time measurement (maybe remove static at some point) 0142 static bool first = true; 0143 static QElapsedTimer time; 0144 int elapsed = time.elapsed(); 0145 if (first) { 0146 elapsed = 0; 0147 first = false; 0148 } 0149 time.restart(); 0150 0151 // Time display 0152 mDrawTimes.append(elapsed); 0153 if (mDrawTimes.size() > MEASUREMENT_LIST_SIZE) 0154 mDrawTimes.removeFirst(); 0155 double avg = 0.0; 0156 for (int i = 0; i < mDrawTimes.size(); i++) 0157 avg += mDrawTimes[i]; 0158 avg /= mDrawTimes.size(); 0159 0160 // Set debug sprite 0161 if (global_debug > 0) { 0162 mFrameSprite->setPlainText(QStringLiteral("CurrentUpdate: %1 ms AverageUpdate%2 ms DefaultUpdate: %3*%4 ms") 0163 .arg(elapsed) 0164 .arg(int(avg)) 0165 .arg(mDefaultUpdateTime) 0166 .arg(mSlowDownFactor)); 0167 } 0168 0169 // Dynamic update of the graphics advance and update speed 0170 if (mDrawTimes.size() == MEASUREMENT_LIST_SIZE && avg > mDefaultUpdateTime * mSlowDownFactor + MEASUREMENT_ROUNDING_ERROR) { 0171 mSlowCnt++; 0172 qCDebug(KFOURINLINE_LOG) << "Warning " << mSlowCnt << " avg=" << avg; 0173 mDrawTimes.clear(); 0174 if (mSlowCnt > WARNING_MAX_COUNT) { 0175 mSlowDownFactor = double(MEASUREMENT_ROUNDING_ERROR + avg) / double(mDefaultUpdateTime); 0176 mSlowCnt = 0; 0177 mTimer->setInterval(int(mDefaultUpdateTime * mSlowDownFactor)); 0178 0179 qCDebug(KFOURINLINE_LOG) << "SLOW COMPUTER WARNING: Decreasing graphics update speed " << mDefaultUpdateTime * mSlowDownFactor 0180 << "ms. Maybe switch off reflections."; 0181 } 0182 } 0183 0184 // Scene advance 0185 scene()->advance(); 0186 // QGV takes care of updating dirty rects, no need to call update or the whole scene is dirtied and repainted 0187 // scene()->update(); 0188 0189 // ==================================================================================== 0190 // Reflections need to be done in the view otherwise the update's go wrong 0191 if (mReflectionRect.width() > 0 && mReflectionRect.height() > 0) { 0192 // Draw reflection in steps to save processing power 0193 if (mReflectPhase == 0) { 0194 const qreal dpr = qApp->devicePixelRatio(); 0195 const int deviceWidth = mReflectionRect.width() * dpr; 0196 const int deviceHeight = mReflectionRect.height() * dpr; 0197 mReflectPixmap = QPixmap(deviceWidth, deviceHeight); 0198 mReflectPixmap.fill(Qt::transparent); 0199 QPainter pixmapPainter(&mReflectPixmap); 0200 // pixmapPainter.fillRect(image.rect(),QBrush(Qt::red)); 0201 0202 // Turn on all optimizations 0203 pixmapPainter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false); 0204 pixmapPainter.setClipping(true); 0205 pixmapPainter.setWorldTransform(QTransform(1.0, 0.0, 0.0, -1.0, 0.0, mReflectPixmap.height())); 0206 const QRect source = QRect(mReflectionRect.x(), mReflectionRect.y() - mReflectionRect.height(), mReflectionRect.width(), mReflectionRect.height()); 0207 0208 bool vis = mReflectionSprite->isVisible(); 0209 mReflectionSprite->hide(); 0210 dynamic_cast<ReflectionGraphicsScene *>(scene())->setBackground(false); 0211 scene()->render(&pixmapPainter, mReflectPixmap.rect(), source, Qt::IgnoreAspectRatio); 0212 mReflectPixmap.setDevicePixelRatio(dpr); 0213 0214 dynamic_cast<ReflectionGraphicsScene *>(scene())->setBackground(true); 0215 if (vis) 0216 mReflectionSprite->show(); 0217 mReflectPhase = 1; 0218 } 0219 // Draw reflection in steps to save processing power 0220 else if (mReflectPhase == 1) { 0221 // Semi transparent 0222 QPainter pixmapPainter(&mReflectPixmap); 0223 pixmapPainter.setTransform(QTransform()); 0224 pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0225 pixmapPainter.drawPixmap(0, 0, mGradientPixmap); 0226 mReflectPhase = 2; 0227 } 0228 // Draw reflection in steps to save processing power 0229 else if (mReflectPhase == 2) { 0230 // Set to sprite 0231 mReflectionSprite->setPixmap(mReflectPixmap); 0232 mReflectionSprite->update(); 0233 mReflectPhase = 0; 0234 } 0235 } 0236 // ==================================================================================== 0237 } 0238 0239 // Define the reflection 0240 void KWin4View::setReflection(int x, int y, int width, int height) 0241 { 0242 mReflectionRect = QRect(x, y, width, height); 0243 0244 const qreal dpr = qApp->devicePixelRatio(); 0245 const int deviceWidth = width * dpr; 0246 const int deviceHeight = height * dpr; 0247 0248 QPoint p1, p2; 0249 p2.setY(deviceHeight); 0250 mGradient = QLinearGradient(p1, p2); 0251 mGradient.setColorAt(0, QColor(0, 0, 0, 100)); 0252 mGradient.setColorAt(1, Qt::transparent); 0253 0254 qCDebug(KFOURINLINE_LOG) << "Set reflection " << x << " " << y << " " << width << " " << height; 0255 0256 mGradientPixmap = QPixmap(deviceWidth, deviceHeight); 0257 mGradientPixmap.fill(Qt::transparent); 0258 QPainter p(&mGradientPixmap); 0259 p.fillRect(0, 0, deviceWidth, deviceHeight, mGradient); 0260 p.end(); 0261 mGradientPixmap.setDevicePixelRatio(dpr); 0262 0263 mReflectionSprite->setPos(x, y); 0264 if (width > 0 && height > 0) { 0265 mReflectionSprite->show(); 0266 } else { 0267 mReflectionSprite->hide(); 0268 } 0269 } 0270 0271 // QGV drawItems function (for debug time measurements) 0272 void KWin4View::drawItems(QPainter *painter, int numItems, QGraphicsItem *items[], const QStyleOptionGraphicsItem options[]) 0273 { 0274 QGraphicsView::drawItems(painter, numItems, items, options); 0275 } 0276 0277 // Stop intro display and init game display 0278 void KWin4View::initGame(Score *scoreData) 0279 { 0280 qCDebug(KFOURINLINE_LOG) << "KWin4View::initGame"; 0281 0282 // For better performance disable mouse tracking now 0283 viewport()->setMouseTracking(false); 0284 setMouseTracking(false); 0285 0286 delete mIntroDisplay; 0287 mIntroDisplay = nullptr; 0288 if (!mGameDisplay) { 0289 mGameDisplay = new DisplayGame(mScene, mTheme, this); 0290 } 0291 mGameDisplay->start(); 0292 0293 // Connect score and score sprite 0294 scoreData->setDisplay(mGameDisplay->score()); 0295 0296 mIsRunning = true; 0297 } 0298 0299 // End the game 0300 void KWin4View::endGame() 0301 { 0302 mIsRunning = false; 0303 mGameDisplay->displayEnd(); 0304 } 0305 0306 // Slot called by the framework when the view is resized. 0307 void KWin4View::resizeEvent(QResizeEvent *e) 0308 { 0309 if (global_debug > 2) 0310 qCDebug(KFOURINLINE_LOG) << "RESIZE EVENT" << e->size() << "oldSize=" << e->oldSize(); 0311 0312 // Test to prevent double resizing 0313 // if (QWidget::testAttribute(Qt::WA_PendingResizeEvent)) 0314 // { 0315 // return; 0316 // } 0317 0318 double diffW = double(e->oldSize().width() - e->size().width()); 0319 double diffH = double(e->oldSize().height() - e->size().height()); 0320 double delta = fabs(diffW) + fabs(diffH); 0321 0322 // Adapt the canvas size to the window size 0323 if (scene()) { 0324 scene()->setSceneRect(0, 0, e->size().width(), e->size().height()); 0325 } 0326 QSizeF size = QSizeF(e->size()); 0327 0328 // Rescale on minimum fitting aspect ratio either width or height limiting 0329 double width = 0.0; 0330 double aspect = size.width() / size.height(); 0331 QPoint offset; 0332 0333 // Scale width: 0334 // Ideal size would be: 'width'*'height' 0335 // Offset in width is (e->size().width()-width)/2, offset in height is zero 0336 if (aspect > mTheme->aspectRatio()) { 0337 width = e->size().height() * mTheme->aspectRatio(); 0338 offset = QPoint(int((e->size().width() - width) / 2.0), 0); 0339 } 0340 // Scale height: 0341 // 'height' = width/mTheme->aspectRatio() 0342 // Ideal size would be: 'width'*'height': 0343 // Offset in height is (e->size().height()-width/mTheme->aspectRatio())/2, offset in width is zero 0344 else { 0345 width = e->size().width(); // Scale height 0346 offset = QPoint(0, int((e->size().height() - width / mTheme->aspectRatio()) / 2.0)); 0347 } 0348 0349 // Pixel rescale 0350 double oldScale = mTheme->getScale(); 0351 0352 // resetTransform(); 0353 QTransform transform; 0354 if (width > oldScale) { 0355 transform.scale(double(width / oldScale), double(width / oldScale)); 0356 } 0357 setTransform(transform); 0358 0359 mThemeQueue.prepend(int(width)); 0360 mThemeOffset.prepend(offset); 0361 if (global_debug > 2) 0362 qCDebug(KFOURINLINE_LOG) << "Quequed resize, aspect=" << aspect << "theme aspect=" << mTheme->aspectRatio(); 0363 0364 long queueDelay = 0; 0365 if (delta < 15) 0366 queueDelay = 750; 0367 else if (delta < 35) 0368 queueDelay = 500; 0369 0370 QTimer::singleShot(queueDelay, this, &KWin4View::rescaleTheme); 0371 } 0372 0373 // Rescale the theme (update theme SVG graphics) from the theme list 0374 void KWin4View::rescaleTheme() 0375 { 0376 if (mThemeQueue.size() == 0) { 0377 if (global_debug > 2) 0378 qCDebug(KFOURINLINE_LOG) << "***************** Swallowing rescale event ***********************"; 0379 return; 0380 } 0381 0382 QElapsedTimer t; 0383 t.start(); 0384 0385 resetTransform(); 0386 0387 int width = mThemeQueue.first(); 0388 QPoint offset = mThemeOffset.first(); 0389 if (global_debug > 2) 0390 qCDebug(KFOURINLINE_LOG) << "Theme queue size=" << mThemeQueue.size() << "Rescale width to" << width << " offset " << offset; 0391 mThemeQueue.clear(); 0392 mThemeOffset.clear(); 0393 mTheme->rescale(width, offset); 0394 0395 if (global_debug > 2) 0396 qCDebug(KFOURINLINE_LOG) << "Time elapsed: " << t.elapsed() << "ms"; 0397 } 0398 0399 // This slot is called when a mouse key is pressed. As the mouse is used as 0400 // input for all players. It is called to generate a player move out of a mouse input, i.e. 0401 // it converts a QMouseEvent into a move for the game. 0402 void KWin4View::mouseInput(KGameIO *input, QDataStream &stream, QMouseEvent *mouse, bool *eatevent) 0403 { 0404 // Only react to mouse pressed not released 0405 if (mouse->type() != QEvent::MouseButtonPress) 0406 return; 0407 if (mouse->button() != Qt::LeftButton) 0408 return; 0409 if (!mIsRunning) 0410 return; 0411 0412 // Our player 0413 KPlayer *player = input->player(); 0414 if (!player->myTurn()) { 0415 // qCDebug(KFOURINLINE_LOG) <<" Kwin4View::TODO wrongPlayer"; 0416 // *eatevent=wrongPlayer(player,KGameIO::MouseIO); 0417 return; 0418 } 0419 0420 // Calculate movement position from mouse position 0421 int x = -1; 0422 if (mGameDisplay) 0423 x = mGameDisplay->mapMouseToMove(mouse->pos()); 0424 if (x < 0) 0425 return; 0426 0427 // Create a game move (pl id and move coordinate) 0428 qint32 move = x; 0429 qint32 pl = player->userId(); 0430 stream << pl << move; 0431 *eatevent = true; 0432 } 0433 0434 // This slot is called when a key event is received. It then produces a 0435 // valid move for the game. 0436 // This is analogous to the mouse event only it is called when a key is 0437 // pressed. 0438 void KWin4View::keyInput(KGameIO *input, QDataStream &stream, QKeyEvent *key, bool *eatevent) 0439 { 0440 // Ignore non running 0441 if (!mIsRunning) 0442 return; 0443 0444 // Ignore non key press 0445 if (key->type() != QEvent::KeyPress) 0446 return; 0447 0448 // Check key code 0449 int code = key->key(); 0450 if (code < Qt::Key_1 || code > Qt::Key_7) 0451 return; 0452 0453 // Our player 0454 KPlayer *player = input->player(); 0455 if (!player->myTurn()) { 0456 // qCDebug(KFOURINLINE_LOG) <<" Kwin4View::TODO wrongPlayer"; 0457 // *eatevent=wrongPlayer(player,KGameIO::KeyIO); 0458 return; 0459 } 0460 0461 // Create a valid game move (player id and movement position) 0462 qint32 move = code - Qt::Key_1; 0463 qint32 pl = player->userId(); 0464 stream << pl << move; 0465 *eatevent = true; 0466 } 0467 0468 // Displays a move on the game board. 0469 void KWin4View::displayMove(int x, int y, int color, int xarrow, int colorarrow, int no, bool animation) 0470 { 0471 mGameDisplay->displayArrow(xarrow, colorarrow); 0472 // animation only if no redo 0473 SpriteNotify *notify = mGameDisplay->displayPiece(x, y, color, no, animation); 0474 if (notify && animation) { 0475 QObject::disconnect(notify, &SpriteNotify::signalNotify, this, &KWin4View::moveDone); 0476 connect(notify, &SpriteNotify::signalNotify, this, &KWin4View::moveDone); 0477 } 0478 mGameDisplay->displayHint(0, 0, false); 0479 } 0480 0481 // Display a star of the given sprite number 0482 void KWin4View::displayStar(int x, int y, int no) 0483 { 0484 mGameDisplay->displayStar(x, y, no); 0485 } 0486 0487 // Display a hint on the board 0488 void KWin4View::displayHint(int x, int y) 0489 { 0490 mGameDisplay->displayHint(x, y, true); 0491 } 0492 0493 // Slot called when a sprite animation move is done. 0494 void KWin4View::moveDone(QGraphicsItem * /*item*/, int mode) 0495 { 0496 Q_EMIT signalMoveDone(mode); 0497 } 0498 0499 bool KWin4View::viewportEvent(QEvent *event) 0500 { 0501 if (mIntroDisplay) 0502 mIntroDisplay->viewEvent(event); 0503 return QGraphicsView::viewportEvent(event); 0504 } 0505 0506 #include "moc_kwin4view.cpp"