Move card_item and related to src/game/board (#5867)

* move files

* update includes

* update cmake
This commit is contained in:
RickyRister 2025-04-20 07:37:52 -07:00 committed by GitHub
parent 6b39f6f6fa
commit 44ac782978
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 38 additions and 38 deletions

View file

@ -0,0 +1,75 @@
#include "abstract_card_drag_item.h"
#include "../../settings/cache_settings.h"
#include <QCursor>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
static const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
static const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
const QPointF &_hotSpot,
AbstractCardDragItem *parentDrag)
: QGraphicsItem(), item(_item), hotSpot(_hotSpot)
{
if (parentDrag) {
parentDrag->addChildDrag(this);
setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000);
} else {
hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast<qreal>(CARD_WIDTH - 1)),
qBound(0.0, hotSpot.y(), static_cast<qreal>(CARD_HEIGHT - 1))};
setCursor(Qt::ClosedHandCursor);
setZValue(2000000007);
}
if (item->getTapped())
setTransform(QTransform()
.translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
.rotate(90)
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
}
AbstractCardDragItem::~AbstractCardDragItem()
{
for (int i = 0; i < childDrags.size(); i++)
delete childDrags[i];
}
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
item->paint(painter, option, widget);
// adds a mask to the card so it looks like the card hasnt been placed yet
painter->fillPath(shape(), GHOST_MASK);
}
void AbstractCardDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->accept();
updatePosition(event->scenePos());
}
void AbstractCardDragItem::addChildDrag(AbstractCardDragItem *child)
{
childDrags << child;
}

View file

@ -0,0 +1,51 @@
#ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H
#include "abstract_card_item.h"
class QGraphicsScene;
class CardZone;
class CardInfo;
class AbstractCardDragItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
protected:
AbstractCardItem *item;
QPointF hotSpot;
QList<AbstractCardDragItem *> childDrags;
public:
enum
{
Type = typeCardDrag
};
int type() const override
{
return Type;
}
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
~AbstractCardDragItem() override;
QRectF boundingRect() const override
{
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
}
QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
AbstractCardItem *getItem() const
{
return item;
}
QPointF getHotSpot() const
{
return hotSpot;
}
void addChildDrag(AbstractCardDragItem *child);
virtual void updatePosition(const QPointF &cursorScenePos) = 0;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -0,0 +1,337 @@
#include "abstract_card_item.h"
#include "../../client/ui/picture_loader/picture_loader.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_database.h"
#include "../cards/card_database_manager.h"
#include "../game_scene.h"
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <algorithm>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent,
const QString &_name,
const QString &_providerId,
Player *_owner,
int _id)
: ArrowTarget(_owner, parent), id(_id), name(_name), providerId(_providerId), tapped(false), facedown(false),
tapAngle(0), bgColor(Qt::transparent), isHovered(false), realZValue(0)
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
}
AbstractCardItem::~AbstractCardItem()
{
emit deleteCardInfoPopup(name);
}
QRectF AbstractCardItem::boundingRect() const
{
return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
}
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardItem::pixmapUpdated()
{
update();
emit sigPixmapUpdated();
}
void AbstractCardItem::refreshCardInfo()
{
info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(name, providerId);
if (!info && !name.isEmpty()) {
QVariantHash properties = QVariantHash();
info = CardInfo::newInstance(name, "", true, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
CardInfoPerSetMap(), false, false, -1, false);
}
if (info.data()) {
connect(info.data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
}
cacheBgColor();
update();
}
void AbstractCardItem::setRealZValue(qreal _zValue)
{
realZValue = _zValue;
setZValue(_zValue);
}
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
{
return QSizeF(painter->combinedTransform().map(QLineF(0, 0, boundingRect().width(), 0)).length(),
painter->combinedTransform().map(QLineF(0, 0, 0, boundingRect().height())).length());
}
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
const int MAX_FONT_SIZE = SettingsCache::instance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());
int scale = resetPainterTransform(painter);
painter->translate(totalBoundingRect.width() / 2, totalBoundingRect.height() / 2);
painter->rotate(angle);
painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2);
QFont f;
f.setPixelSize(fontSize * scale);
painter->setFont(f);
}
void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle)
{
qreal scaleFactor = translatedSize.width() / boundingRect().width();
QPixmap translatedPixmap;
bool paintImage = true;
if (facedown || name.isEmpty()) {
// never reveal card color, always paint the card back
PictureLoader::getCardBackPixmap(translatedPixmap, translatedSize.toSize());
} else {
// don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) {
PictureLoader::getPixmap(translatedPixmap, info, translatedSize.toSize());
if (translatedPixmap.isNull())
paintImage = false;
} else {
paintImage = false;
}
}
painter->save();
if (paintImage) {
painter->save();
painter->setClipPath(shape());
painter->drawPixmap(boundingRect(), translatedPixmap, QRectF({0, 0}, translatedPixmap.size()));
painter->restore();
} else {
painter->setBrush(bgColor);
painter->drawPath(shape());
}
if (translatedPixmap.isNull() || SettingsCache::instance().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr;
if (facedown)
nameStr = "# " + QString::number(id);
else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
nameStr = prefix + name;
}
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr);
painter->restore();
}
painter->restore();
}
void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
painter->save();
QSizeF translatedSize = getTranslatedSize(painter);
paintPicture(painter, translatedSize, tapAngle);
painter->setRenderHint(QPainter::Antialiasing, false);
if (isSelected() || isHovered) {
QPen pen;
if (isHovered)
pen.setColor(Qt::yellow);
if (isSelected())
pen.setColor(Qt::red);
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
}
painter->restore();
}
void AbstractCardItem::setName(const QString &_name)
{
if (name == _name)
return;
emit deleteCardInfoPopup(name);
if (info)
disconnect(info.data(), nullptr, this, nullptr);
name = _name;
refreshCardInfo();
}
void AbstractCardItem::setProviderId(const QString &_providerId)
{
if (providerId == _providerId) {
return;
}
emit deleteCardInfoPopup(name);
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
providerId = _providerId;
refreshCardInfo();
}
void AbstractCardItem::setHovered(bool _hovered)
{
if (isHovered == _hovered)
return;
if (_hovered)
processHoverEvent();
isHovered = _hovered;
setZValue(_hovered ? 2000000004 : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0);
update();
}
void AbstractCardItem::setColor(const QString &_color)
{
color = _color;
cacheBgColor();
update();
}
void AbstractCardItem::cacheBgColor()
{
QChar colorChar;
if (color.isEmpty()) {
if (info)
colorChar = info->getColorChar();
} else {
colorChar = color.at(0);
}
switch (colorChar.toLower().toLatin1()) {
case 'b':
bgColor = QColor(0, 0, 0);
break;
case 'u':
bgColor = QColor(0, 140, 180);
break;
case 'w':
bgColor = QColor(255, 250, 140);
break;
case 'r':
bgColor = QColor(230, 0, 0);
break;
case 'g':
bgColor = QColor(0, 160, 0);
break;
case 'm':
bgColor = QColor(250, 190, 30);
break;
default:
bgColor = QColor(230, 230, 230);
break;
}
}
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{
if (tapped == _tapped)
return;
tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate)
static_cast<GameScene *>(scene())->registerAnimationItem(this);
else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
.rotate(tapAngle)
.translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
update();
}
}
void AbstractCardItem::setFaceDown(bool _facedown)
{
facedown = _facedown;
update();
}
void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) {
emit cardShiftClicked(name);
} else if ((event->modifiers() & Qt::ControlModifier)) {
setSelected(!isSelected());
} else if (!isSelected()) {
scene()->clearSelection();
setSelected(true);
}
if (event->button() == Qt::LeftButton)
setCursor(Qt::ClosedHandCursor);
else if (event->button() == Qt::MiddleButton)
emit showCardInfoPopup(event->screenPos(), name, providerId);
event->accept();
}
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::MiddleButton)
emit deleteCardInfoPopup(name);
// This function ensures the parent function doesn't mess around with our selection.
event->accept();
}
void AbstractCardItem::processHoverEvent()
{
emit hovered(this);
}
QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemSelectedHasChanged) {
update();
return value;
} else
return ArrowTarget::itemChange(change, value);
}

View file

@ -0,0 +1,119 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../cards/card_info.h"
#include "arrow_target.h"
class Player;
const int CARD_WIDTH = 72;
const int CARD_HEIGHT = 102;
class AbstractCardItem : public ArrowTarget
{
Q_OBJECT
protected:
CardInfoPtr info;
int id;
QString name;
QString providerId;
bool tapped;
bool facedown;
int tapAngle;
QString color;
QColor bgColor;
private:
bool isHovered;
qreal realZValue;
private slots:
void pixmapUpdated();
public slots:
void refreshCardInfo();
signals:
void hovered(AbstractCardItem *card);
void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId);
void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated();
void cardShiftClicked(QString cardName);
public:
enum
{
Type = typeCard
};
int type() const override
{
return Type;
}
explicit AbstractCardItem(QGraphicsItem *parent = nullptr,
const QString &_name = QString(),
const QString &_providerId = QString(),
Player *_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;
CardInfoPtr getInfo() const
{
return info;
}
int getId() const
{
return id;
}
void setId(int _id)
{
id = _id;
}
QString getName() const
{
return name;
}
void setName(const QString &_name = QString());
QString getProviderId() const
{
return providerId;
}
void setProviderId(const QString &_providerId = QString());
qreal getRealZValue() const
{
return realZValue;
}
void setRealZValue(qreal _zValue);
void setHovered(bool _hovered);
QString getColor() const
{
return color;
}
void setColor(const QString &_color);
bool getTapped() const
{
return tapped;
}
void setTapped(bool _tapped, bool canAnimate = false);
bool getFaceDown() const
{
return facedown;
}
void setFaceDown(bool _facedown);
void processHoverEvent();
void deleteCardInfoPopup()
{
emit deleteCardInfoPopup(name);
}
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();
};
#endif

View file

@ -3,10 +3,10 @@
#include "../../settings/cache_settings.h"
#include "../cards/card_info.h"
#include "../cards/card_item.h"
#include "../player/player.h"
#include "../player/player_target.h"
#include "../zones/card_zone.h"
#include "card_item.h"
#include "color.h"
#include "pb/command_attach_card.pb.h"
#include "pb/command_create_arrow.pb.h"

View file

@ -0,0 +1,118 @@
#include "card_drag_item.h"
#include "../game_scene.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_item.h"
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
CardDragItem::CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _faceDown,
AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0)
{
}
void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
AbstractCardDragItem::paint(painter, option, widget);
if (occupied)
painter->fillPath(shape(), QColor(200, 0, 0, 100));
}
void CardDragItem::updatePosition(const QPointF &cursorScenePos)
{
QList<QGraphicsItem *> colliding =
scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder,
static_cast<GameScene *>(scene())->getViewTransform());
CardZone *cardZone = 0;
ZoneViewZone *zoneViewZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) {
CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i));
if (!cardZone)
cardZone = temp;
if (!zoneViewZone)
zoneViewZone = qobject_cast<ZoneViewZone *>(temp);
}
CardZone *cursorZone = 0;
if (zoneViewZone)
cursorZone = zoneViewZone;
else if (cardZone)
cursorZone = cardZone;
if (!cursorZone)
return;
currentZone = cursorZone;
QPointF zonePos = currentZone->scenePos();
QPointF cursorPosInZone = cursorScenePos - zonePos;
// If we are on a Table, we center the card around the cursor, because we
// snap it into place and no longer see it being dragged.
//
// For other zones (where we do display the card under the cursor), we use
// the hotspot to feel like the card was dragged at the corresponding
// position.
TableZone *tableZone = qobject_cast<TableZone *>(cursorZone);
QPointF closestGridPoint;
if (tableZone)
closestGridPoint = tableZone->closestGridPoint(cursorPosInZone);
else
closestGridPoint = cursorPosInZone - hotSpot;
QPointF newPos = zonePos + closestGridPoint;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++)
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
setPos(newPos);
bool newOccupied = false;
TableZone *table = qobject_cast<TableZone *>(cursorZone);
if (table)
if (table->getCardFromCoords(closestGridPoint))
newOccupied = true;
if (newOccupied != occupied) {
occupied = newOccupied;
update();
}
}
}
void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
QGraphicsScene *sc = scene();
QPointF sp = pos();
sc->removeItem(this);
QList<CardDragItem *> dragItemList;
CardZone *startZone = static_cast<CardItem *>(item)->getZone();
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone))) {
if (!occupied) {
dragItemList.append(this);
}
for (int i = 0; i < childDrags.size(); i++) {
CardDragItem *c = static_cast<CardDragItem *>(childDrags[i]);
if (!occupied && !(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone)) &&
!c->occupied) {
dragItemList.append(c);
}
sc->removeItem(c);
}
}
if (currentZone) {
currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint());
}
event->accept();
}

View file

@ -0,0 +1,38 @@
#ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H
#include "abstract_card_drag_item.h"
class CardItem;
class CardDragItem : public AbstractCardDragItem
{
Q_OBJECT
private:
int id;
bool faceDown;
bool occupied;
CardZone *currentZone;
public:
CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _faceDown,
AbstractCardDragItem *parentDrag = 0);
int getId() const
{
return id;
}
bool getFaceDown() const
{
return faceDown;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void updatePosition(const QPointF &cursorScenePos) override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -0,0 +1,490 @@
#include "card_item.h"
#include "../../client/tabs/tab_game.h"
#include "../../settings/cache_settings.h"
#include "../cards/card_info.h"
#include "../game_scene.h"
#include "../player/player.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "arrow_item.h"
#include "card_drag_item.h"
#include "pb/serverinfo_card.pb.h"
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QPainter>
CardItem::CardItem(Player *_owner,
QGraphicsItem *parent,
const QString &_name,
const QString &_providerId,
int _cardid,
CardZone *_zone)
: AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), attacking(false),
destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
{
owner->addCard(this);
cardMenu = new QMenu;
ptMenu = new QMenu;
moveMenu = new QMenu;
retranslateUi();
}
CardItem::~CardItem()
{
delete cardMenu;
delete ptMenu;
delete moveMenu;
}
void CardItem::prepareDelete()
{
if (owner != nullptr) {
if (owner->getCardMenu() == cardMenu) {
owner->setCardMenu(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
owner = nullptr;
}
while (!attachedCards.isEmpty()) {
attachedCards.first()->setZone(nullptr); // so that it won't try to call reorganizeCards()
attachedCards.first()->setAttachedTo(nullptr);
}
if (attachedTo != nullptr) {
attachedTo->removeAttachedCard(this);
attachedTo = nullptr;
}
}
void CardItem::deleteLater()
{
prepareDelete();
if (scene())
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
AbstractCardItem::deleteLater();
}
void CardItem::setZone(CardZone *_zone)
{
zone = _zone;
}
void CardItem::retranslateUi()
{
moveMenu->setTitle(tr("&Move to"));
ptMenu->setTitle(tr("&Power / toughness"));
}
void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->save();
AbstractCardItem::paint(painter, option, widget);
int i = 0;
QMapIterator<int, int> counterIterator(counters);
while (counterIterator.hasNext()) {
counterIterator.next();
QColor _color;
_color.setHsv(counterIterator.key() * 60, 150, 255);
paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter);
++i;
}
QSizeF translatedSize = getTranslatedSize(painter);
qreal scaleFactor = translatedSize.width() / boundingRect().width();
if (!pt.isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && info && pt == info->getPowTough()) {
painter->setPen(Qt::white);
} else {
painter->setPen(QColor(255, 150, 0)); // dark orange
}
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignRight | Qt::AlignBottom, pt);
painter->restore();
}
if (!annotation.isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
painter->setPen(Qt::white);
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor,
translatedSize.height() - 8 * scaleFactor),
Qt::AlignCenter | Qt::TextWrapAnywhere, annotation);
painter->restore();
}
if (getBeingPointedAt()) {
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
}
if (doesntUntap) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
QPen pen;
pen.setColor(Qt::magenta);
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
painter->restore();
}
painter->restore();
}
void CardItem::setAttacking(bool _attacking)
{
attacking = _attacking;
update();
}
void CardItem::setCounter(int _id, int _value)
{
if (_value)
counters.insert(_id, _value);
else
counters.remove(_id);
update();
}
void CardItem::setAnnotation(const QString &_annotation)
{
annotation = _annotation;
update();
}
void CardItem::setDoesntUntap(bool _doesntUntap)
{
doesntUntap = _doesntUntap;
update();
}
void CardItem::setPT(const QString &_pt)
{
pt = _pt;
update();
}
void CardItem::setAttachedTo(CardItem *_attachedTo)
{
if (attachedTo != nullptr) {
attachedTo->removeAttachedCard(this);
}
gridPoint.setX(-1);
attachedTo = _attachedTo;
if (attachedTo != nullptr) {
setParentItem(attachedTo->getZone());
attachedTo->addAttachedCard(this);
if (zone != attachedTo->getZone()) {
attachedTo->getZone()->reorganizeCards();
}
} else {
setParentItem(zone);
}
if (zone != nullptr) {
zone->reorganizeCards();
}
}
void CardItem::resetState(bool keepAnnotations)
{
attacking = false;
facedown = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
attachedTo = 0;
attachedCards.clear();
setTapped(false, false);
setDoesntUntap(false);
if (scene())
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
update();
}
void CardItem::processCardInfo(const ServerInfo_Card &_info)
{
counters.clear();
const int counterListSize = _info.counter_list_size();
for (int i = 0; i < counterListSize; ++i) {
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
counters.insert(counterInfo.id(), counterInfo.value());
}
setId(_info.id());
setProviderId(QString::fromStdString(_info.provider_id()));
setName(QString::fromStdString(_info.name()));
setAttacking(_info.attacking());
setFaceDown(_info.face_down());
setPT(QString::fromStdString(_info.pt()));
setAnnotation(QString::fromStdString(_info.annotation()));
setColor(QString::fromStdString(_info.color()));
setTapped(_info.tapped());
setDestroyOnZoneChange(_info.destroy_on_zone_change());
setDoesntUntap(_info.doesnt_untap());
}
CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown)
{
deleteDragItem();
dragItem = new CardDragItem(this, _id, _pos, faceDown);
dragItem->setVisible(false);
scene()->addItem(dragItem);
dragItem->updatePosition(_scenePos);
dragItem->setVisible(true);
return dragItem;
}
void CardItem::deleteDragItem()
{
if (dragItem) {
dragItem->deleteLater();
}
dragItem = nullptr;
}
void CardItem::drawArrow(const QColor &arrowColor)
{
if (static_cast<TabGame *>(owner->parent())->getSpectator())
return;
Player *arrowOwner = static_cast<TabGame *>(owner->parent())->getActiveLocalPlayer();
ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor);
scene()->addItem(arrow);
arrow->grabMouse();
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr || card == this)
continue;
if (card->getZone() != zone)
continue;
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor);
scene()->addItem(childArrow);
arrow->addChildArrow(childArrow);
}
}
void CardItem::drawAttachArrow()
{
if (static_cast<TabGame *>(owner->parent())->getSpectator())
return;
auto *arrow = new ArrowAttachItem(this);
scene()->addItem(arrow);
arrow->grabMouse();
for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr)
continue;
if (card->getZone() != zone)
continue;
ArrowAttachItem *childArrow = new ArrowAttachItem(card);
scene()->addItem(childArrow);
arrow->addChildArrow(childArrow);
}
}
void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (event->buttons().testFlag(Qt::RightButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() <
2 * QApplication::startDragDistance())
return;
QColor arrowColor = Qt::red;
if (event->modifiers().testFlag(Qt::ControlModifier))
arrowColor = Qt::yellow;
else if (event->modifiers().testFlag(Qt::AltModifier))
arrowColor = Qt::blue;
else if (event->modifiers().testFlag(Qt::ShiftModifier))
arrowColor = Qt::green;
drawArrow(arrowColor);
} else if (event->buttons().testFlag(Qt::LeftButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance())
return;
if (zone->getIsView()) {
const ZoneViewZone *view = static_cast<const ZoneViewZone *>(zone);
if (view->getRevealZone() && !view->getWriteableRevealZone())
return;
} else if (!owner->getLocalOrJudge())
return;
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
// Use the buttonDownPos to align the hot spot with the position when
// the user originally clicked
createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), facedown || forceFaceDown);
dragItem->grabMouse();
int childIndex = 0;
for (const auto &item : scene()->selectedItems()) {
CardItem *card = static_cast<CardItem *>(item);
if ((card == this) || (card->getZone() != zone))
continue;
++childIndex;
QPointF childPos;
if (zone->getHasCardAttr())
childPos = card->pos() - pos();
else
childPos = QPointF(childIndex * CARD_WIDTH / 2, 0);
CardDragItem *drag =
new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
}
setCursor(Qt::OpenHandCursor);
}
void CardItem::playCard(bool faceDown)
{
// Do nothing if the card belongs to another player
if (!owner->getLocalOrJudge())
return;
TableZone *tz = qobject_cast<TableZone *>(zone);
if (tz)
tz->toggleTapped();
else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
faceDown ? zone->getPlayer()->actPlayFacedown() : zone->getPlayer()->actPlay();
} else {
zone->getPlayer()->playCard(this, faceDown);
}
}
}
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
*/
static bool isUnwritableRevealZone(CardZone *zone)
{
if (zone && zone->getIsView()) {
if (auto *view = static_cast<ZoneViewZone *>(zone)) {
return view->getRevealZone() && !view->getWriteableRevealZone();
}
}
return false;
}
/**
* This method is called when a "click to play" is done on the card.
* This is either triggered by a single click or double click, depending on the settings.
*
* @param shiftHeld if the shift key was held during the click
*/
void CardItem::handleClickedToPlay(bool shiftHeld)
{
if (isUnwritableRevealZone(zone)) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
zone->getPlayer()->actHide();
} else {
zone->removeCard(this);
}
} else {
playCard(shiftHeld);
}
}
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
if (cardMenu != nullptr && !cardMenu->isEmpty() && owner != nullptr) {
cardMenu->popup(event->screenPos());
return;
}
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
if (owner != nullptr) { // cards without owner will be deleted
setCursor(Qt::OpenHandCursor);
}
AbstractCardItem::mouseReleaseEvent(event);
}
void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) &&
(SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
event->accept();
}
bool CardItem::animationEvent()
{
int rotation = ROTATION_DEGREES_PER_FRAME;
bool animationIncomplete = true;
if (!tapped)
rotation *= -1;
tapAngle += rotation;
if (tapped && (tapAngle > 90)) {
tapAngle = 90;
animationIncomplete = false;
}
if (!tapped && (tapAngle < 0)) {
tapAngle = 0;
animationIncomplete = false;
}
setTransform(QTransform()
.translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
.rotate(tapAngle)
.translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
setHovered(false);
update();
return animationIncomplete;
}
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if ((change == ItemSelectedHasChanged) && owner != nullptr) {
if (value == true) {
owner->setCardMenu(cardMenu);
owner->getGame()->setActiveCard(this);
} else if (owner->getCardMenu() == cardMenu) {
owner->setCardMenu(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
}
return AbstractCardItem::itemChange(change, value);
}

View file

@ -0,0 +1,166 @@
#ifndef CARDITEM_H
#define CARDITEM_H
#include "abstract_card_item.h"
#include "server_card.h"
class CardDatabase;
class CardDragItem;
class CardZone;
class ServerInfo_Card;
class Player;
class QAction;
class QColor;
const int MAX_COUNTERS_ON_CARD = 999;
const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
{
Q_OBJECT
private:
CardZone *zone;
bool attacking;
QMap<int, int> counters;
QString annotation;
QString pt;
bool destroyOnZoneChange;
bool doesntUntap;
QPoint gridPoint;
CardDragItem *dragItem;
CardItem *attachedTo;
QList<CardItem *> attachedCards;
QMenu *cardMenu, *ptMenu, *moveMenu;
void prepareDelete();
void handleClickedToPlay(bool shiftHeld);
public slots:
void deleteLater();
public:
enum
{
Type = typeCard
};
int type() const override
{
return Type;
}
explicit CardItem(Player *_owner,
QGraphicsItem *parent = nullptr,
const QString &_name = QString(),
const QString &_providerId = QString(),
int _cardid = -1,
CardZone *_zone = nullptr);
~CardItem() override;
void retranslateUi();
CardZone *getZone() const
{
return zone;
}
void setZone(CardZone *_zone);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QPoint getGridPoint() const
{
return gridPoint;
}
void setGridPoint(const QPoint &_gridPoint)
{
gridPoint = _gridPoint;
}
QPoint getGridPos() const
{
return gridPoint;
}
Player *getOwner() const
{
return owner;
}
void setOwner(Player *_owner)
{
owner = _owner;
}
bool getAttacking() const
{
return attacking;
}
void setAttacking(bool _attacking);
const QMap<int, int> &getCounters() const
{
return counters;
}
void setCounter(int _id, int _value);
QString getAnnotation() const
{
return annotation;
}
void setAnnotation(const QString &_annotation);
bool getDoesntUntap() const
{
return doesntUntap;
}
void setDoesntUntap(bool _doesntUntap);
QString getPT() const
{
return pt;
}
void setPT(const QString &_pt);
bool getDestroyOnZoneChange() const
{
return destroyOnZoneChange;
}
void setDestroyOnZoneChange(bool _destroy)
{
destroyOnZoneChange = _destroy;
}
CardItem *getAttachedTo() const
{
return attachedTo;
}
void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardItem *card)
{
attachedCards.append(card);
}
void removeAttachedCard(CardItem *card)
{
attachedCards.removeOne(card);
}
const QList<CardItem *> &getAttachedCards() const
{
return attachedCards;
}
void resetState(bool keepAnnotations = false);
void processCardInfo(const ServerInfo_Card &_info);
QMenu *getCardMenu() const
{
return cardMenu;
}
QMenu *getPTMenu() const
{
return ptMenu;
}
QMenu *getMoveMenu() const
{
return moveMenu;
}
bool animationEvent();
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
void drawAttachArrow();
void playCard(bool faceDown);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

View file

@ -0,0 +1,156 @@
#include "card_list.h"
#include "../cards/card_info.h"
#include "card_item.h"
#include <QDebug>
#include <algorithm>
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
{
}
/**
* @brief Finds the CardItem with the given id in the list.
* If contentsKnown is false, then this just returns the first element of the list.
*
* @param cardId The id of the card to find.
*
* @returns A pointer to the CardItem, or a nullptr if not found.
*/
CardItem *CardList::findCard(const int cardId) const
{
if (!contentsKnown && !empty()) {
return at(0);
} else {
for (auto *cardItem : *this) {
if (cardItem->getId() == cardId) {
return cardItem;
}
}
}
return nullptr;
}
/**
* @brief sorts the list by using string comparison on properties extracted from the CardItem
* The cards are compared using each property in order.
* If two cards have the same value for a property, then the next property in the list is used.
*
* @param option the option to compare the cards by, in order of usage.
*/
void CardList::sortBy(const QList<SortOption> &option)
{
// early return if we know we won't be sorting
if (option.isEmpty()) {
return;
}
auto comparator = [&option](CardItem *a, CardItem *b) {
for (auto prop : option) {
auto extractor = getExtractorFor(prop);
QString t1 = extractor(a);
QString t2 = extractor(b);
if (t1 != t2) {
return t1 < t2;
}
}
return false;
};
std::sort(begin(), end(), comparator);
}
/**
* Creates a String for the card such that when sorting cards using that string, it will result in the
* following sort order:
* - Unrecognized colors
* - Land cards
* - Colorless cards
* - Monocolor cards, in wubrg order
* - Monocolor cards of any custom colors
* - 2C cards (no internal order)
* - 3C cards (no internal order)
* - 4C cards (no internal order)
* - 5C cards (no internal order)
*
* @param c The card info
* @param appendAtEnd For multicolor cards, whether to also append the entire color string at the end.
*/
static QString getColorSortString(CardInfoPtr c, bool appendAtEnd)
{
QString colors = c->getColors();
switch (colors.size()) {
case 0: {
if (c->getCardType().contains("Land")) {
return "a_land";
} else {
return "b_colorless";
}
}
case 1:
// force wubrg order
switch (colors.at(0).toLatin1()) {
case 'W':
return "c_W";
case 'U':
return "d_U";
case 'B':
return "e_B";
case 'R':
return "f_R";
case 'G':
return "g_G";
default:
// handle any custom colors
return QString("h_%1").arg(colors.at(0));
}
default:
return QString("i%1_%2").arg(colors.size()).arg(appendAtEnd ? colors : "");
}
}
/**
* @brief returns the function that extracts the given property from the CardItem.
*/
std::function<QString(CardItem *)> CardList::getExtractorFor(SortOption option)
{
switch (option) {
case NoSort:
return [](CardItem *) { return ""; };
case SortByMainType:
return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getMainCardType() : ""; };
case SortByManaValue:
// getCmc returns the int as a string. We pad with 0's so that string comp also works on it
return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCmc().rightJustified(4, '0') : ""; };
case SortByColorGrouping:
return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), false) : ""; };
case SortByName:
return [](CardItem *c) { return c->getName(); };
case SortByType:
return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCardType() : ""; };
case SortByManaCost:
return [](CardItem *c) {
auto info = c->getInfo();
if (!info)
return QString("");
// calculation copied from CardDatabaseModel.
// we pad the cmc and also append the mana cost to the end so same cmc cards still have a sort order
return QString("%1%2").arg(info->getCmc(), 4, QChar('0')).arg(info->getManaCost());
};
case SortByColors:
return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), true) : ""; };
case SortByPt:
// do the same padding trick as above
return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getPowTough().rightJustified(10, '0') : ""; };
case SortBySet:
return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getSetsNames() : ""; };
case SortByPrinting:
return [](CardItem *c) { return c->getProviderId(); };
}
// this line should never be reached
qCWarning(CardListLog) << "cardlist.cpp: Could not find extractor for SortOption" << option;
return [](CardItem *) { return ""; };
}

View file

@ -0,0 +1,49 @@
#ifndef CARDLIST_H
#define CARDLIST_H
#include <QList>
#include <QLoggingCategory>
inline Q_LOGGING_CATEGORY(CardListLog, "card_list");
class CardItem;
class CardList : public QList<CardItem *>
{
protected:
bool contentsKnown;
public:
enum SortOption
{
NoSort,
// Options that are used by groupBy
// Should partition all cards into a reasonable number of buckets
SortByMainType,
SortByManaValue,
SortByColorGrouping,
// Options that are used by sortBy
// We don't care about buckets; we want as many distinct values as possible.
SortByName,
SortByType,
SortByManaCost,
SortByColors,
SortByPt,
SortBySet,
SortByPrinting
};
explicit CardList(bool _contentsKnown);
CardItem *findCard(const int cardId) const;
bool getContentsKnown() const
{
return contentsKnown;
}
void sortBy(const QList<SortOption> &options);
static std::function<QString(CardItem *)> getExtractorFor(SortOption option);
};
#endif