/** * @file abstract_card_item.h * @ingroup GameGraphicsCards * @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic. */ #ifndef ABSTRACTCARDITEM_H #define ABSTRACTCARDITEM_H #include "../animated_item.h" #include "arrow_target.h" #include "graphics_item_type.h" #include #include #include #include #include #include #include class PlayerLogic; class CardInfo; class QPainter; class QPoint; class QPropertyAnimation; class AbstractCardItem : public ArrowTarget, public IAnimatedItem { Q_OBJECT Q_PROPERTY(qreal cardScale READ getCardScale WRITE setCardScale) Q_PROPERTY(qreal cardOpacity READ getCardOpacity WRITE setCardOpacity) protected: CardState *state; int tapAngle; QColor bgColor; private: bool isHovered; bool isHoverHighlighted; qreal realZValue; bool pendingSelectionCollapse; int selectionCollapseGeneration = 0; ///< Guards the deferred collapse timer against stale firings QPropertyAnimation *hoverZoom = nullptr; QPropertyAnimation *opacityAnim = nullptr; private slots: void pixmapUpdated(); void onCardInfoChanged(); void onCardRefChanged(const CardRef &oldCardRef, const CardRef &newCardRef); void onTappedChanged(bool newTapped, bool canAnimate); public slots: void refreshCardInfo(); signals: void hovered(AbstractCardItem *card); void showCardInfoPopup(const QPoint &pos, const CardRef &cardRef); void deleteCardInfoPopup(QString cardName); void sigPixmapUpdated(); void cardShiftClicked(QString cardName); void rightClicked(AbstractCardItem *card, QPoint screenPos); void playSelected(AbstractCardItem *card); void playSelectedFaceDown(AbstractCardItem *card); void hideSelected(AbstractCardItem *card); void selectionChanged(AbstractCardItem *card, bool selected); void tappedChanged(bool tapped); void faceDownChanged(bool faceDown); /** @brief Emitted when the hover shrink-out animation runs to completion while unhovered. */ void hoverShrinkFinished(AbstractCardItem *card); /** * @brief Emitted when the hover zoom changes this card's scene footprint. * * Zones (and anything else that dodges the zoomed card) draw from the live footprint at * paint time but are cached: they need this to invalidate, or a stale hole is left behind. * Also emitted once when the hover ends, with both rects equal, because the hole * disappears without the card changing size. */ void hoverFootprintChanged(const QRectF &oldFootprint, const QRectF &newFootprint); public: enum { Type = typeCard }; int type() const override { return Type; } explicit AbstractCardItem(QGraphicsItem *parent = nullptr, const CardRef &cardRef = {}, PlayerLogic *_owner = nullptr, int _id = -1); ~AbstractCardItem() override; QRectF boundingRect() const override; QPainterPath shape() const override; QSizeF getTranslatedSize(QPainter *painter) const; void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; [[nodiscard]] CardState *getState() const { return state; } ExactCard getCard() const { return state->getCard(); } const CardInfo &getCardInfo() const; int getId() const { return state->getId(); } void setId(int _id) { state->setId(_id); } QString getName() const { return state->getCardRef().name; } QString getProviderId() const { return state->getCardRef().providerId; } void setCardRef(const CardRef &_cardRef) { state->setCardRef(_cardRef); } CardRef getCardRef() const { return state->getCardRef(); } qreal getRealZValue() const { return realZValue; } void setRealZValue(qreal _zValue); void setHovered(bool _hovered); /** * @brief Shows the hover outline and stacking lift, plus the historical 1.1x hover * scale whenever no zoom trigger is configured. * * The board zoom is trigger-gated; the hover feedback (card info, outline, raised * stacking) still follows the cursor immediately on contact. With the zoom trigger * off and the panel not on hover, the old 1.1x enlargement (gated by scaleCards) * stays in effect so upgrading does not silently drop it. */ void setHoverHighlighted(bool highlighted); qreal getCardScale() const; void setCardScale(qreal scale); qreal getCardOpacity() const; void setCardOpacity(qreal opacity); /** @brief Fades the card out under the hover panel, or back in when it closes. */ void fadeCardOut(); void fadeCardIn(); bool getIsHovered() const { return isHovered; } [[nodiscard]] QString getColor() const { return state->getColor(); } void setColor(const QString &_color); bool getTapped() const { return state->getTapped(); } void setTapped(bool _tapped, bool canAnimate = false) { state->setTapped(_tapped, canAnimate); } bool getFaceDown() const { return state->getFaceDown(); } void setFaceDown(bool _facedown) { state->setFaceDown(_facedown); } void processHoverEvent(); /** @brief Drop a pending collapse and disarm any deferred collapse timer. */ void cancelPendingSelectionCollapse(); void deleteCardInfoPopup() { emit deleteCardInfoPopup(state->getCardRef().name); } /** @brief Default: no per-tick animation. Subclasses override to animate. */ bool animationEvent() override; protected: void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle); void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; void cacheBgColor(); /** * @brief Whether a double-click on this item acts on the whole selection. * * When true, a release without drag defers the multi-selection collapse * until the double-click window passes, so a following double-click still * sees every selected card. Subclasses that handle double-clicks on the * selection must return true here and call * cancelPendingSelectionCollapse() from their double-click handler. */ virtual bool doubleClickActsOnSelection() const; }; #endif