Cockatrice/cockatrice/src/game_graphics/board/arrow_item.h
unf0rce 80986266b9
Fix orphaned arrows when the draw shortcut repeats (#6789)
Holding or spamming the draw-arrow shortcut fires the action again
before the previous local arrow has been placed. Every new arrow takes
the mouse grab from the previous one, so the previous arrow never
receives the release that deletes it. If the cursor had passed over a
card meanwhile, the orphan stayed visible on the board.

Track the in-progress arrow on the CardItem that started it and cancel
it, together with any child arrows, before starting a new one. The
attach-arrow shortcut (Ctrl+Alt+A) uses the same path, so it no longer
leaks arrows either.

Fixes #4329.

Co-authored-by: mb <mbaecker@unforce.de>
2026-11-07 16:53:53 +01:00

148 lines
3.7 KiB
C++

#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "../animated_item.h"
#include "arrow_target.h"
#include "graphics_item_type.h"
#include <QColor>
#include <QElapsedTimer>
#include <QGraphicsItem>
#include <QList>
#include <QMetaObject>
#include <QObject>
#include <QPainterPath>
#include <QPointF>
#include <QPointer>
#include <QRectF>
#include <QSharedPointer>
#include <QtGlobal>
#include <libcockatrice/game/board/arrow_data.h>
#include <qtmetamacros.h>
class CardItem;
class PlayerLogic;
template <class T> class QSharedPointer;
class ArrowItem : public QObject, public QGraphicsItem, public IAnimatedItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int creatorId, int id);
private:
QPainterPath path;
QPainterPath bodyPath;
QPainterPath headPath;
QPainterPath shaftOutlinePath;
QPainterPath centerLine;
qreal headBaseFraction = 1.0;
QElapsedTimer animationClock;
qreal strokeDurationMs = 0;
qreal glowFadeDurationMs = 0;
qreal drawProgress = 1.0;
qreal glowAlpha = 0.0;
bool animationStarted = false;
static constexpr qreal glowExtent = 12.0;
protected:
QSharedPointer<const ArrowData> data;
QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem;
bool targetLocked = false;
bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
enum
{
Type = typeArrow
};
[[nodiscard]] int type() const override
{
return Type;
}
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
~ArrowItem() override;
void onTargetDestroyed();
void delArrow();
/// Abandons an in-progress drag arrow, discarding any child arrows with it.
virtual void cancel();
void updatePath();
void updatePath(const QPointF &endPoint);
void startDrawAnimation();
bool animationEvent() override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
{
return path.boundingRect().adjusted(-glowExtent, -glowExtent, glowExtent, glowExtent);
}
[[nodiscard]] QPainterPath shape() const override
{
return path;
}
[[nodiscard]] int getId() const
{
return data->id;
}
[[nodiscard]] int getCreatorId() const
{
return data->creatorId;
}
[[nodiscard]] ArrowTarget *getStartItem() const
{
return startItem;
}
[[nodiscard]] ArrowTarget *getTargetItem() const
{
return targetItem;
}
void setTargetLocked(bool _targetLocked)
{
targetLocked = _targetLocked;
}
};
class ArrowDragItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
int deleteInPhase;
QList<QPointer<ArrowDragItem>> childArrows;
QMetaObject::Connection positionConnection;
public:
ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *child);
void cancel() override;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
class ArrowAttachItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
QList<QPointer<ArrowAttachItem>> childArrows;
QMetaObject::Connection positionConnection;
public:
explicit ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *child);
void cancel() override;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif