* Improve drag & drop behavior This patch tweaks the drag & drop behavior (in particular, the grid placement) to be more intuitive. More precisely, with this patch the drag & drop will: - Only use the "hot spot" (i.e. position of the cursor on the card) for zones where the card is actually displayed around the cursor (in particular, not on the table where the card snaps to the grid). - Use better boundaries computed with respect to the center of the card (rather than its top left corner) for determining which grid cell a card should go to - Align behavior of the preview and the actual effect when overflow of the 3-card stacks occurs - Avoid visual glitches where the cursor ends up outside of the card or at incorrect offsets when moving the mouse too fast (which translates to overflows of the hot spot computation) * Address review comments - Use simpler computation for restricting hotSpot range - Prevent dropping cards onto full 3-card slots
468 lines
13 KiB
C++
468 lines
13 KiB
C++
#include "carditem.h"
|
|
|
|
#include "arrowitem.h"
|
|
#include "carddatabase.h"
|
|
#include "carddragitem.h"
|
|
#include "cardzone.h"
|
|
#include "gamescene.h"
|
|
#include "main.h"
|
|
#include "pb/serverinfo_card.pb.h"
|
|
#include "player.h"
|
|
#include "settingscache.h"
|
|
#include "tab_game.h"
|
|
#include "tablezone.h"
|
|
#include "zoneviewzone.h"
|
|
|
|
#include <QApplication>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QMenu>
|
|
#include <QPainter>
|
|
|
|
CardItem::CardItem(Player *_owner,
|
|
const QString &_name,
|
|
int _cardid,
|
|
bool _revealedCard,
|
|
QGraphicsItem *parent,
|
|
CardZone *_zone)
|
|
: AbstractCardItem(_name, _owner, _cardid, parent), zone(_zone), revealedCard(_revealedCard), 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()
|
|
{
|
|
prepareDelete();
|
|
|
|
if (scene())
|
|
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
|
|
|
|
delete cardMenu;
|
|
delete ptMenu;
|
|
delete moveMenu;
|
|
|
|
deleteDragItem();
|
|
}
|
|
|
|
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();
|
|
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()
|
|
{
|
|
attacking = false;
|
|
facedown = false;
|
|
counters.clear();
|
|
pt.clear();
|
|
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());
|
|
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
|
|
zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false);
|
|
}
|
|
|
|
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())) {
|
|
bool hideCard = false;
|
|
if (zone && zone->getIsView()) {
|
|
auto *view = static_cast<ZoneViewZone *>(zone);
|
|
if (view->getRevealZone() && !view->getWriteableRevealZone())
|
|
hideCard = true;
|
|
}
|
|
if (zone && hideCard) {
|
|
zone->removeCard(this);
|
|
} else {
|
|
playCard(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) && (SettingsCache::instance().getDoubleClickToPlay()) &&
|
|
(event->buttons() == Qt::LeftButton)) {
|
|
if (revealedCard)
|
|
zone->removeCard(this);
|
|
else
|
|
playCard(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 QGraphicsItem::itemChange(change, value);
|
|
}
|