diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index f0e82532b..b2bf20d4b 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -15,6 +15,7 @@ #include #include #include +#include GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) : QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0) @@ -32,8 +33,6 @@ GameScene::~GameScene() delete animationTimer; // DO NOT call clearViews() here - // clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults - // deleteLater() deletes the zoneView without allowing it to send signals for (const auto &zoneView : zoneViews) { zoneView->deleteLater(); } @@ -41,13 +40,14 @@ GameScene::~GameScene() void GameScene::retranslateUi() { - for (int i = 0; i < zoneViews.size(); ++i) - zoneViews[i]->retranslateUi(); + for (ZoneViewWidget *view : zoneViews) + view->retranslateUi(); } void GameScene::addPlayer(Player *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName(); + players << player->getGraphicsItem(); addItem(player->getGraphicsItem()); connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); @@ -55,7 +55,9 @@ void GameScene::addPlayer(Player *player) void GameScene::removePlayer(Player *player) { - qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::removePlayer name=" << player->getPlayerInfo()->getName(); + qCInfo(GameScenePlayerAdditionRemovalLog) + << "GameScene::removePlayer name=" << player->getPlayerInfo()->getName(); + for (ZoneViewWidget *zone : zoneViews) { if (zone->getPlayer() == player) { zone->close(); @@ -74,104 +76,50 @@ void GameScene::adjustPlayerRotation(int rotationAdjustment) void GameScene::rearrange() { - playersByColumn.clear(); - - // Create the list of players playing, noting the first player's index. - QList playersPlaying; int firstPlayerIndex = 0; - bool firstPlayerFound = false; - QListIterator playersIter(players); - while (playersIter.hasNext()) { - Player *p = playersIter.next()->getPlayer(); - if (p && !p->getConceded()) { - playersPlaying.append(p); - if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) { - firstPlayerIndex = playersPlaying.size() - 1; - firstPlayerFound = true; - } - } - } + auto playersPlaying = collectActivePlayers(firstPlayerIndex); + playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex); - // Rotate the players playing list so that first player is first, then - // adjust by the additional rotation setting. - if (!playersPlaying.isEmpty()) { - int totalRotation = firstPlayerIndex + playerRotation; - while (totalRotation < 0) - totalRotation += playersPlaying.size(); - for (int i = 0; i < totalRotation; ++i) { - playersPlaying.append(playersPlaying.takeFirst()); - } - } + int columns = determineColumnCount(playersPlaying.size()); + QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns); - const int playersCount = playersPlaying.size(); - const int columns = playersCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2; - const int rows = qCeil((qreal)playersCount / columns); - qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing; - QList columnWidth; + phasesToolbar->setHeight(sceneSize.height()); + setSceneRect(0, 0, sceneSize.width(), sceneSize.height()); - QListIterator playersPlayingIter(playersPlaying); - for (int col = 0; col < columns; ++col) { - playersByColumn.append(QList()); - columnWidth.append(0); - qreal thisColumnHeight = -playerAreaSpacing; - const int rowsInColumn = rows - (playersCount % columns) * col; // only correct for max. 2 cols - for (int j = 0; j < rowsInColumn; ++j) { - Player *player = playersPlayingIter.next(); - if (col == 0) - playersByColumn[col].prepend(player->getGraphicsItem()); - else - playersByColumn[col].append(player->getGraphicsItem()); - thisColumnHeight += player->getGraphicsItem()->boundingRect().height() + playerAreaSpacing; - if (player->getGraphicsItem()->boundingRect().width() > columnWidth[col]) - columnWidth[col] = player->getGraphicsItem()->boundingRect().width(); - } - if (thisColumnHeight > sceneHeight) - sceneHeight = thisColumnHeight; - sceneWidth += columnWidth[col] + playerAreaSpacing; - } - - phasesToolbar->setHeight(sceneHeight); - qreal phasesWidth = phasesToolbar->getWidth(); - sceneWidth += phasesWidth; - - qreal x = phasesWidth; - for (int col = 0; col < columns; ++col) { - qreal y = 0; - for (int row = 0; row < playersByColumn[col].size(); ++row) { - PlayerGraphicsItem *player = playersByColumn[col][row]; - player->setPos(x, y); - player->setMirrored(row != rows - 1); - y += player->boundingRect().height() + playerAreaSpacing; - } - x += columnWidth[col] + playerAreaSpacing; - } - - setSceneRect(sceneRect().x(), sceneRect().y(), sceneWidth, sceneHeight); processViewSizeChange(viewSize); } +// ---------- Zone Views ---------- + void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed) { for (auto &view : zoneViews) { ZoneViewZone *temp = view->getZone(); - if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player && + if (temp->getLogic()->getName() == zoneName && + temp->getLogic()->getPlayer() == player && qobject_cast(temp->getLogic())->getNumberCards() == numberCards) { view->close(); } } ZoneViewWidget *item = - new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false, false, {}, isReversed); + new ZoneViewWidget( + player, + player->getZones().value(zoneName), + numberCards, + false, + false, {}, isReversed); + zoneViews.append(item); connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView); addItem(item); - if (zoneName == "grave") { + + if (zoneName == "grave") item->setPos(360, 100); - } else if (zoneName == "rfg") { + else if (zoneName == "rfg") item->setPos(380, 120); - } else { + else item->setPos(340, 80); - } } void GameScene::addRevealedZoneView(Player *player, @@ -204,6 +152,8 @@ void GameScene::closeMostRecentZoneView() zoneViews.last()->close(); } +// ---------- View Transforms ---------- + QTransform GameScene::getViewTransform() const { return views().at(0)->transform(); @@ -214,83 +164,35 @@ QTransform GameScene::getViewportTransform() const return views().at(0)->viewportTransform(); } +// ---------- View Size ---------- + void GameScene::processViewSizeChange(const QSize &newSize) { viewSize = newSize; - qreal newRatio = ((qreal)newSize.width()) / newSize.height(); - qreal minWidth = 0; - QList minWidthByColumn; - for (int col = 0; col < playersByColumn.size(); ++col) { - minWidthByColumn.append(0); - for (int row = 0; row < playersByColumn[col].size(); ++row) { - qreal w = playersByColumn[col][row]->getMinimumWidth(); - if (w > minWidthByColumn[col]) - minWidthByColumn[col] = w; - } - minWidth += minWidthByColumn[col]; - } - minWidth += phasesToolbar->getWidth(); + QList minWidthByColumn = calculateMinWidthByColumn(); + qreal minWidth = std::accumulate(minWidthByColumn.begin(), + minWidthByColumn.end(), phasesToolbar->getWidth()); - qreal minRatio = minWidth / sceneRect().height(); - qreal newWidth; - if (minRatio > newRatio) { - // Aspect ratio is dominated by table width. - newWidth = minWidth; - } else { - // Aspect ratio is dominated by window dimensions. - newWidth = newRatio * sceneRect().height(); - } + qreal newWidth = calculateNewSceneWidth(newSize, minWidth); setSceneRect(0, 0, newWidth, sceneRect().height()); - qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size(); - qreal newx = phasesToolbar->getWidth(); - for (int col = 0; col < playersByColumn.size(); ++col) { - for (int row = 0; row < playersByColumn[col].size(); ++row) { - playersByColumn[col][row]->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn); - playersByColumn[col][row]->setPos(newx, playersByColumn[col][row]->y()); - } - newx += minWidthByColumn[col] + extraWidthPerColumn; - } + resizeColumnsAndPlayers(minWidthByColumn, newWidth); } +// ---------- Hover Handling ---------- + void GameScene::updateHover(const QPointF &scenePos) { - QList itemList = - items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); + auto itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); - // Search for the topmost zone and ignore all cards not belonging to that zone. - CardZone *zone = 0; - for (int i = 0; i < itemList.size(); ++i) - if ((zone = qgraphicsitem_cast(itemList[i]))) - break; - - CardItem *maxZCard = 0; - if (zone) { - qreal maxZ = -1; - for (int i = 0; i < itemList.size(); ++i) { - CardItem *card = qgraphicsitem_cast(itemList[i]); - if (!card) - continue; - if (card->getAttachedTo()) { - if (card->getAttachedTo()->getZone() != zone->getLogic()) - continue; - } else if (card->getZone() != zone->getLogic()) - continue; - - if (card->getRealZValue() > maxZ) { - maxZ = card->getRealZValue(); - maxZCard = card; - } - } - } - if (hoveredCard && (maxZCard != hoveredCard)) - hoveredCard->setHovered(false); - if (maxZCard && (maxZCard != hoveredCard)) - maxZCard->setHovered(true); - hoveredCard = maxZCard; + CardZone *zone = findTopmostZone(itemList); + CardItem *topCard = zone ? findTopmostCardInZone(itemList, zone) : nullptr; + updateHoveredCard(topCard); } +// ---------- Event Handling ---------- + bool GameScene::event(QEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseMove) @@ -325,6 +227,8 @@ void GameScene::unregisterAnimationItem(AbstractCardItem *card) animationTimer->stop(); } +// ---------- Rubber Band ---------- + void GameScene::startRubberBand(const QPointF &selectionOrigin) { emit sigStartRubberBand(selectionOrigin); @@ -339,3 +243,175 @@ void GameScene::stopRubberBand() { emit sigStopRubberBand(); } + +// ================================================================== +// Private Helpers +// ================================================================== + +QList GameScene::collectActivePlayers(int &firstPlayerIndex) const +{ + QList activePlayers; + firstPlayerIndex = 0; + bool firstPlayerFound = false; + + for (auto *pgItem : players) { + Player *p = pgItem->getPlayer(); + if (p && !p->getConceded()) { + activePlayers.append(p); + if (!firstPlayerFound && p->getPlayerInfo()->getLocal()) { + firstPlayerIndex = activePlayers.size() - 1; + firstPlayerFound = true; + } + } + } + return activePlayers; +} + +QList GameScene::rotatePlayers(const QList &players, int firstPlayerIndex) const +{ + QList rotated = players; + if (!rotated.isEmpty()) { + int totalRotation = firstPlayerIndex + playerRotation; + while (totalRotation < 0) + totalRotation += rotated.size(); + for (int i = 0; i < totalRotation; ++i) + rotated.append(rotated.takeFirst()); + } + return rotated; +} + +int GameScene::determineColumnCount(int playerCount) const +{ + return playerCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2; +} + +QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns) +{ + playersByColumn.clear(); + + int rows = qCeil((qreal)playersPlaying.size() / columns); + qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing; + QList columnWidth; + + QListIterator playersIter(playersPlaying); + for (int col = 0; col < columns; ++col) { + playersByColumn.append(QList()); + columnWidth.append(0); + qreal thisColumnHeight = -playerAreaSpacing; + int rowsInColumn = rows - (playersPlaying.size() % columns) * col; // for 2 cols + + for (int j = 0; j < rowsInColumn; ++j) { + Player *player = playersIter.next(); + if (col == 0) + playersByColumn[col].prepend(player->getGraphicsItem()); + else + playersByColumn[col].append(player->getGraphicsItem()); + + auto *pgItem = player->getGraphicsItem(); + thisColumnHeight += pgItem->boundingRect().height() + playerAreaSpacing; + columnWidth[col] = std::max(columnWidth[col], (int)pgItem->boundingRect().width()); + } + + sceneHeight = std::max(sceneHeight, thisColumnHeight); + sceneWidth += columnWidth[col] + playerAreaSpacing; + } + + qreal phasesWidth = phasesToolbar->getWidth(); + sceneWidth += phasesWidth; + + // Position players + qreal x = phasesWidth; + for (int col = 0; col < columns; ++col) { + qreal y = 0; + for (int row = 0; row < playersByColumn[col].size(); ++row) { + PlayerGraphicsItem *player = playersByColumn[col][row]; + player->setPos(x, y); + player->setMirrored(row != rows - 1); + y += player->boundingRect().height() + playerAreaSpacing; + } + x += columnWidth[col] + playerAreaSpacing; + } + + return QSizeF(sceneWidth, sceneHeight); +} + +QList GameScene::calculateMinWidthByColumn() const +{ + QList minWidthByColumn; + for (const auto &col : playersByColumn) { + qreal maxWidth = 0; + for (PlayerGraphicsItem *player : col) + maxWidth = std::max(maxWidth, player->getMinimumWidth()); + minWidthByColumn.append(maxWidth); + } + return minWidthByColumn; +} + +qreal GameScene::calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const +{ + qreal newRatio = (qreal)newSize.width() / newSize.height(); + qreal minRatio = minWidth / sceneRect().height(); + + if (minRatio > newRatio) { + return minWidth; // dominated by table width + } else { + return newRatio * sceneRect().height(); // dominated by window dimensions + } +} + +void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qreal newWidth) +{ + qreal minWidth = std::accumulate(minWidthByColumn.begin(), minWidthByColumn.end(), phasesToolbar->getWidth()); + + qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size(); + qreal newx = phasesToolbar->getWidth(); + + for (int col = 0; col < playersByColumn.size(); ++col) { + for (PlayerGraphicsItem *player : playersByColumn[col]) { + player->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn); + player->setPos(newx, player->y()); + } + newx += minWidthByColumn[col] + extraWidthPerColumn; + } +} + +CardZone *GameScene::findTopmostZone(const QList &items) const +{ + for (QGraphicsItem *item : items) + if (auto *zone = qgraphicsitem_cast(item)) + return zone; + return nullptr; +} + +CardItem *GameScene::findTopmostCardInZone(const QList &items, CardZone *zone) const +{ + CardItem *maxZCard = nullptr; + qreal maxZ = -1; + + for (QGraphicsItem *item : items) { + CardItem *card = qgraphicsitem_cast(item); + if (!card) + continue; + + if (card->getAttachedTo()) { + if (card->getAttachedTo()->getZone() != zone->getLogic()) + continue; + } else if (card->getZone() != zone->getLogic()) + continue; + + if (card->getRealZValue() > maxZ) { + maxZ = card->getRealZValue(); + maxZCard = card; + } + } + return maxZCard; +} + +void GameScene::updateHoveredCard(CardItem *newCard) +{ + if (hoveredCard && (newCard != hoveredCard)) + hoveredCard->setHovered(false); + if (newCard && (newCard != hoveredCard)) + newCard->setHovered(true); + hoveredCard = newCard; +} diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 32721d0b0..c1d8d5967 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -50,6 +50,16 @@ public: void startRubberBand(const QPointF &selectionOrigin); void resizeRubberBand(const QPointF &cursorPoint); void stopRubberBand(); + QList collectActivePlayers(int &firstPlayerIndex) const; + QList rotatePlayers(const QList &players, int firstPlayerIndex) const; + int determineColumnCount(int playerCount) const; + QSizeF computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns); + QList calculateMinWidthByColumn() const; + qreal calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const; + void resizeColumnsAndPlayers(const QList &minWidthByColumn, qreal newWidth); + CardZone *findTopmostZone(const QList &items) const; + CardItem *findTopmostCardInZone(const QList &items, CardZone *zone) const; + void updateHoveredCard(CardItem *newCard); void registerAnimationItem(AbstractCardItem *item); void unregisterAnimationItem(AbstractCardItem *card);