diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index b2bf20d4b..d9e403f11 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -17,6 +17,15 @@ #include #include +/** + * @brief Constructs the GameScene. + * @param _phasesToolbar Toolbar widget for phases. + * @param parent Optional parent QObject. + * + * Initializes the animation timer, adds the phases toolbar to the scene, + * and connects to settings changes for multi-column layout. + * Finally, calls rearrange() to layout players initially. + */ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) : QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0) { @@ -32,18 +41,27 @@ GameScene::~GameScene() { delete animationTimer; - // DO NOT call clearViews() here + // Zone views are deleted asynchronously. for (const auto &zoneView : zoneViews) { zoneView->deleteLater(); } } +/** + * @brief Updates localized text in all zone views. + */ void GameScene::retranslateUi() { for (ZoneViewWidget *view : zoneViews) view->retranslateUi(); } +/** + * @brief Adds a player to the scene and stores their graphics item. + * @param player Player to add. + * + * Connects to the player's sizeChanged signal to recompute layout on resize. + */ void GameScene::addPlayer(Player *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName(); @@ -53,10 +71,15 @@ void GameScene::addPlayer(Player *player) connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); } +/** + * @brief Removes a player from the scene. + * @param player Player to remove. + * + * Closes any zone views associated with the player and recomputes layout. + */ 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) { @@ -68,12 +91,29 @@ void GameScene::removePlayer(Player *player) rearrange(); } +/** + * @brief Adjusts the global rotation offset for player layout. + * @param rotationAdjustment Number of positions to rotate. + * + * Recomputes player layout after applying rotation. + */ void GameScene::adjustPlayerRotation(int rotationAdjustment) { playerRotation += rotationAdjustment; rearrange(); } +/** + * @brief Recomputes the layout of players and the scene size. + * + * Steps: + * 1. Collect active players who haven't conceded. + * 2. Rotate player list based on first local player and rotation offset. + * 3. Determine number of columns. + * 4. Compute scene size and layout. + * 5. Update toolbar height and scene rectangle. + * 6. Adjust columns and player positions to match view size. + */ void GameScene::rearrange() { int firstPlayerIndex = 0; @@ -91,24 +131,28 @@ void GameScene::rearrange() // ---------- Zone Views ---------- +/** + * @brief Toggles a zone view for a player. + * @param player Player owning the zone. + * @param zoneName Name of the zone. + * @param numberCards Number of cards visible in the view. + * @param isReversed Whether the zone view is reversed. + * + * If an identical view exists, it is closed. Otherwise, a new ZoneViewWidget is created + * and positioned based on zone type. + */ 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); @@ -122,6 +166,13 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb item->setPos(340, 80); } +/** + * @brief Adds a revealed zone view (for shown cards). + * @param player Owning player. + * @param zone Zone logic. + * @param cardList List of cards to show. + * @param withWritePermission Whether edits are allowed. + */ void GameScene::addRevealedZoneView(Player *player, CardZoneLogic *zone, const QList &cardList, @@ -134,18 +185,28 @@ void GameScene::addRevealedZoneView(Player *player, item->setPos(600, 80); } +/** + * @brief Removes a zone view widget from the scene. + * @param item Zone view to remove. + */ void GameScene::removeZoneView(ZoneViewWidget *item) { zoneViews.removeOne(item); removeItem(item); } +/** + * @brief Closes all zone views. + */ void GameScene::clearViews() { while (!zoneViews.isEmpty()) zoneViews.first()->close(); } +/** + * @brief Closes the most recently added zone view. + */ void GameScene::closeMostRecentZoneView() { if (!zoneViews.isEmpty()) @@ -166,13 +227,21 @@ QTransform GameScene::getViewportTransform() const // ---------- View Size ---------- +/** + * @brief Handles view resize and redistributes player positions. + * @param newSize New view size. + * + * Steps: + * 1. Compute minimum width per column from player items. + * 2. Determine new scene width respecting aspect ratio. + * 3. Resize columns and reposition players proportionally. + */ void GameScene::processViewSizeChange(const QSize &newSize) { viewSize = newSize; QList minWidthByColumn = calculateMinWidthByColumn(); - qreal minWidth = std::accumulate(minWidthByColumn.begin(), - minWidthByColumn.end(), phasesToolbar->getWidth()); + qreal minWidth = std::accumulate(minWidthByColumn.begin(), minWidthByColumn.end(), phasesToolbar->getWidth()); qreal newWidth = calculateNewSceneWidth(newSize, minWidth); setSceneRect(0, 0, newWidth, sceneRect().height()); @@ -244,10 +313,15 @@ void GameScene::stopRubberBand() emit sigStopRubberBand(); } -// ================================================================== -// Private Helpers -// ================================================================== +// ---------- Player Layout Helpers ---------- +/** + * @brief Collects all active (non-conceded) players. + * @param firstPlayerIndex Output index of first local player. + * @return List of active players. + * + * Used to determine rotation and layout order. + */ QList GameScene::collectActivePlayers(int &firstPlayerIndex) const { QList activePlayers; @@ -267,9 +341,17 @@ QList GameScene::collectActivePlayers(int &firstPlayerIndex) const return activePlayers; } -QList GameScene::rotatePlayers(const QList &players, int firstPlayerIndex) const +/** + * @brief Rotates the list of players for layout. + * @param players Original list of players. + * @param firstPlayerIndex Index of first local player. + * @return Rotated list. + * + * Applies rotation offset and ensures the list wraps correctly. + */ +QList GameScene::rotatePlayers(const QList &activePlayers, int firstPlayerIndex) const { - QList rotated = players; + QList rotated = activePlayers; if (!rotated.isEmpty()) { int totalRotation = firstPlayerIndex + playerRotation; while (totalRotation < 0) @@ -280,11 +362,24 @@ QList GameScene::rotatePlayers(const QList &players, int fir return rotated; } -int GameScene::determineColumnCount(int playerCount) const +int GameScene::determineColumnCount(int playerCount) { return playerCount < SettingsCache::instance().getMinPlayersForMultiColumnLayout() ? 1 : 2; } +/** + * @brief Computes layout positions and scene size based on players and columns. + * @param playersPlaying List of active players. + * @param columns Number of columns to split into. + * @return Calculated scene size. + * + * Logic: + * - Determine rows per column (rounding up). + * - Calculate column widths based on widest player item. + * - Accumulate scene width and height. + * - Position players in columns with spacing. + * - Mirror graphics for visual balance. + */ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns) { playersByColumn.clear(); @@ -298,7 +393,7 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &players playersByColumn.append(QList()); columnWidth.append(0); qreal thisColumnHeight = -playerAreaSpacing; - int rowsInColumn = rows - (playersPlaying.size() % columns) * col; // for 2 cols + int rowsInColumn = rows - (playersPlaying.size() % columns) * col; // Adjust rows for uneven columns for (int j = 0; j < rowsInColumn; ++j) { Player *player = playersIter.next(); @@ -319,14 +414,14 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &players qreal phasesWidth = phasesToolbar->getWidth(); sceneWidth += phasesWidth; - // Position players + // Position players horizontally and vertically 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); + player->setMirrored(row != rows - 1); // Mirror all except bottom-most y += player->boundingRect().height() + playerAreaSpacing; } x += columnWidth[col] + playerAreaSpacing; @@ -335,6 +430,10 @@ QSizeF GameScene::computeSceneSizeAndPlayerLayout(const QList &players return QSizeF(sceneWidth, sceneHeight); } +/** + * @brief Computes the minimum width for each column based on player minimum widths. + * @return List of minimum widths per column. + */ QList GameScene::calculateMinWidthByColumn() const { QList minWidthByColumn; @@ -347,18 +446,32 @@ QList GameScene::calculateMinWidthByColumn() const return minWidthByColumn; } +/** + * @brief Calculates new scene width considering window aspect ratio. + * @param newSize View size. + * @param minWidth Minimum width needed to fit all players. + * @return Scene width respecting window and content. + */ 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 + return minWidth; // Table dominates width } else { - return newRatio * sceneRect().height(); // dominated by window dimensions + return newRatio * sceneRect().height(); // Window ratio dominates } } +/** + * @brief Resizes columns and distributes extra width to players. + * @param minWidthByColumn Minimum widths per column. + * @param newWidth Total scene width. + * + * Extra width is distributed evenly across columns. Each player item is + * notified to adjust internal layout for the new column width. + */ void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qreal newWidth) { qreal minWidth = std::accumulate(minWidthByColumn.begin(), minWidthByColumn.end(), phasesToolbar->getWidth()); @@ -375,7 +488,7 @@ void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qr } } -CardZone *GameScene::findTopmostZone(const QList &items) const +CardZone *GameScene::findTopmostZone(const QList &items) { for (QGraphicsItem *item : items) if (auto *zone = qgraphicsitem_cast(item)) @@ -383,7 +496,7 @@ CardZone *GameScene::findTopmostZone(const QList &items) const return nullptr; } -CardItem *GameScene::findTopmostCardInZone(const QList &items, CardZone *zone) const +CardItem *GameScene::findTopmostCardInZone(const QList &items, CardZone *zone) { CardItem *maxZCard = nullptr; qreal maxZ = -1; diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index c1d8d5967..f7ccf19fd 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -1,3 +1,11 @@ +/** + * @file game_scene.h + * @brief Represents the main game scene, managing players, zones, animations, and layout. + * + * The GameScene handles the graphical arrangement of players, cards, and zones. + * It calculates positions based on the number of players, columns, and rotations. + */ + #ifndef GAMESCENE_H #define GAMESCENE_H @@ -22,64 +30,172 @@ class ServerInfo_Card; class PhasesToolbar; class QBasicTimer; +/** + * @class GameScene + * @brief Manages the game board display including players, zones, cards, and animations. + * + * GameScene handles: + * - Dynamic arrangement of players in columns/rows. + * - Player rotation adjustments. + * - Zone views for cards (e.g., graveyard, library, revealed zones). + * - Hover and animation handling for cards. + * - Scene resizing and responsive layout. + */ class GameScene : public QGraphicsScene { Q_OBJECT private: - static const int playerAreaSpacing = 5; + static const int playerAreaSpacing = 5; ///< Space between player areas - PhasesToolbar *phasesToolbar; - QList players; - QList> playersByColumn; - QList zoneViews; - QSize viewSize; - QPointer hoveredCard; - QBasicTimer *animationTimer; - QSet cardsToAnimate; - int playerRotation; + PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases + QList players; ///< All player graphics items + QList> playersByColumn; ///< Players organized by column + QList zoneViews; ///< Active zone view widgets + QSize viewSize; ///< Current view size + QPointer hoveredCard; ///< Currently hovered card + QBasicTimer *animationTimer; ///< Timer for card animations + QSet cardsToAnimate; ///< Cards currently animating + int playerRotation; ///< Rotation offset for player layout + + /** + * @brief Updates which card is currently hovered based on scene coordinates. + * @param scenePos Scene position of the cursor. + */ void updateHover(const QPointF &scenePos); public: explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); ~GameScene() override; + + /** Updates UI text for all zone views. */ void retranslateUi(); + + /** Processes changes in the view size and adjusts layout accordingly. */ void processViewSizeChange(const QSize &newSize); + + /** Returns the current transform of the main view. */ QTransform getViewTransform() const; + + /** Returns the viewport transform of the main view. */ QTransform getViewportTransform() const; + /** Starts a selection rubber band. */ void startRubberBand(const QPointF &selectionOrigin); + + /** Resizes the rubber band to follow the cursor. */ void resizeRubberBand(const QPointF &cursorPoint); + + /** Stops the rubber band selection. */ void stopRubberBand(); + + /** + * @brief Collects active players who have not conceded. + * @param firstPlayerIndex Output parameter indicating the first local player index. + * @return List of active players. + * + * This is used to determine player order and layout. + */ QList collectActivePlayers(int &firstPlayerIndex) const; + + /** + * @brief Rotates the list of players based on first player and rotation offset. + * @param players Original list of players. + * @param firstPlayerIndex Index of the first local player. + * @return Rotated list of players. + */ QList rotatePlayers(const QList &players, int firstPlayerIndex) const; - int determineColumnCount(int playerCount) const; + + /** + * @brief Determines the number of columns to display players in. + * @param playerCount Total number of active players. + * @return Number of columns (1 or 2). + */ + static int determineColumnCount(int playerCount); + + /** + * @brief Computes the total scene size and arranges players. + * @param playersPlaying List of active players. + * @param columns Number of columns to arrange players into. + * @return Calculated scene size. + * + * This function: + * - Splits players into columns and rows. + * - Calculates maximum column width and scene height. + * - Positions players within columns with spacing. + * - Mirrors player graphics when appropriate. + */ QSizeF computeSceneSizeAndPlayerLayout(const QList &playersPlaying, int columns); + + /** Calculates minimum width required for each column based on player minimum widths. */ QList calculateMinWidthByColumn() const; + + /** + * @brief Calculates the new scene width based on window size and minimum required width. + * @param newSize New view size. + * @param minWidth Minimum scene width needed to fit all players. + * @return New scene width after layout calculations. + */ qreal calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const; + + /** + * @brief Resizes columns and positions players horizontally within the scene. + * @param minWidthByColumn Minimum width of each column. + * @param newWidth Total scene width to distribute. + * + * Extra width is evenly distributed across columns to fill available space. + */ void resizeColumnsAndPlayers(const QList &minWidthByColumn, qreal newWidth); - CardZone *findTopmostZone(const QList &items) const; - CardItem *findTopmostCardInZone(const QList &items, CardZone *zone) const; + + /** Finds the topmost card zone under the cursor. */ + static CardZone *findTopmostZone(const QList &items); + + /** Finds the topmost card in a given zone, considering attachments and Z-order. */ + static CardItem *findTopmostCardInZone(const QList &items, CardZone *zone); + + /** Updates hovered card highlighting. */ void updateHoveredCard(CardItem *newCard); + /** Registers a card for animation updates. */ void registerAnimationItem(AbstractCardItem *item); + + /** Unregisters a card from animation updates. */ void unregisterAnimationItem(AbstractCardItem *card); + public slots: + /** Toggles the display of a player's zone view. */ void toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed = false); + + /** Adds a zone view showing revealed cards. */ void addRevealedZoneView(Player *player, CardZoneLogic *zone, const QList &cardList, bool withWritePermission); + + /** Removes a zone view widget from the scene. */ void removeZoneView(ZoneViewWidget *item); + + /** Adds a player to the scene and connects signals for layout updates. */ void addPlayer(Player *player); + + /** Removes a player from the scene and updates layout. */ void removePlayer(Player *player); + + /** Closes all active zone views. */ void clearViews(); + + /** Closes the most recently opened zone view. */ void closeMostRecentZoneView(); + + /** Adjusts player layout rotation and recomputes positions. */ void adjustPlayerRotation(int rotationAdjustment); + + /** Recomputes the scene layout. */ void rearrange(); protected: - bool event(QEvent *event) override; - void timerEvent(QTimerEvent *event) override; + bool event(QEvent *event) override; ///< Handles hover updates. + void timerEvent(QTimerEvent *event) override; ///< Handles animations. + signals: void sigStartRubberBand(const QPointF &selectionOrigin); void sigResizeRubberBand(const QPointF &cursorPoint);