diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index a79c5873e..cc121cfb2 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -12,6 +12,7 @@ #include "libcockatrice/card/printing/exact_card.h" #include "libcockatrice/utility/card_ref.h" +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +44,7 @@ class QSizeF; AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id) : ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0), - bgColor(Qt::transparent), isHovered(false), realZValue(0) + bgColor(Qt::transparent), isHovered(false), realZValue(0), pendingSelectionCollapse(false) { setCursor(Qt::OpenHandCursor); setFlag(ItemIsSelectable); @@ -344,6 +346,16 @@ void AbstractCardItem::setFaceDown(bool _facedown) void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + cancelPendingSelectionCollapse(); + if (scene() != nullptr) { + // A new press starts a new gesture: no card may collapse the selection out from under it afterwards. + for (QGraphicsItem *selectedItem : scene()->selectedItems()) { + auto *selectedCard = qgraphicsitem_cast(selectedItem); + if (selectedCard != nullptr && selectedCard != this) { + selectedCard->cancelPendingSelectionCollapse(); + } + } + } if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) { emit cardShiftClicked(cardRef.name); } else if ((event->modifiers() & Qt::ControlModifier)) { @@ -351,6 +363,10 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } else if (!isSelected()) { scene()->clearSelection(); setSelected(true); + } else if (event->button() == Qt::LeftButton && scene()->selectedItems().count() > 1) { + // Part of a multi-selection; collapse only if this turns out to be a click rather than a drag, + // so that dragging the whole selection keeps it intact. + pendingSelectionCollapse = true; } if (event->button() == Qt::LeftButton) { setCursor(Qt::ClosedHandCursor); @@ -362,6 +378,32 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + if (pendingSelectionCollapse) { + pendingSelectionCollapse = false; + bool dragged = (event->screenPos() - event->buttonDownScreenPos(event->button())).manhattanLength() >= + 2 * QApplication::startDragDistance(); + if (!dragged) { + if (doubleClickActsOnSelection()) { + // A double-click may still arrive and must act on the whole multi-selection, so collapse only + // once the double-click window has passed without one. The generation guard (and the receiver + // context) drops the collapse if the item is gone, deselected, or a double-click cancelled it; + // the count guard drops it if the selection changed meanwhile (rubber band, ctrl-toggle). + const int generation = ++selectionCollapseGeneration; + const int armedSelectionCount = scene()->selectedItems().count(); + QTimer::singleShot(QApplication::doubleClickInterval(), this, [this, generation, armedSelectionCount] { + if (generation == selectionCollapseGeneration && scene() != nullptr && isSelected() && + scene()->selectedItems().count() == armedSelectionCount && armedSelectionCount > 1) { + scene()->clearSelection(); + setSelected(true); + } + }); + } else { + scene()->clearSelection(); + setSelected(true); + } + } + } + if (event->button() == Qt::MiddleButton) { emit deleteCardInfoPopup(cardRef.name); } @@ -370,6 +412,17 @@ void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) event->accept(); } +void AbstractCardItem::cancelPendingSelectionCollapse() +{ + pendingSelectionCollapse = false; + ++selectionCollapseGeneration; +} + +bool AbstractCardItem::doubleClickActsOnSelection() const +{ + return false; +} + void AbstractCardItem::processHoverEvent() { emit hovered(this); diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.h b/cockatrice/src/game_graphics/board/abstract_card_item.h index 385f9c32f..e2b150ce8 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.h +++ b/cockatrice/src/game_graphics/board/abstract_card_item.h @@ -41,6 +41,8 @@ protected: private: bool isHovered; qreal realZValue; + bool pendingSelectionCollapse; + int selectionCollapseGeneration = 0; ///< Guards the deferred collapse timer against stale firings private slots: void pixmapUpdated(); @@ -130,6 +132,8 @@ public: } void setFaceDown(bool _facedown); void processHoverEvent(); + /** @brief Drop a pending collapse and disarm any deferred collapse timer. */ + void cancelPendingSelectionCollapse(); void deleteCardInfoPopup() { emit deleteCardInfoPopup(cardRef.name); @@ -144,6 +148,16 @@ protected: 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 diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index a04d86c15..1901a634f 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -518,6 +518,9 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { + // The preceding release deferred its multi-selection collapse for exactly this: the double-click plays + // every selected card, so the collapse must not run. + cancelPendingSelectionCollapse(); if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) && (SettingsCache::instance().userInterface().getDoubleClickToPlay())) { handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); @@ -525,6 +528,11 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) event->accept(); } +bool CardItem::doubleClickActsOnSelection() const +{ + return SettingsCache::instance().userInterface().getDoubleClickToPlay(); +} + bool CardItem::animationEvent() { int rotation = ROTATION_DEGREES_PER_FRAME; diff --git a/cockatrice/src/game_graphics/board/card_item.h b/cockatrice/src/game_graphics/board/card_item.h index df79ef378..0737d7f9c 100644 --- a/cockatrice/src/game_graphics/board/card_item.h +++ b/cockatrice/src/game_graphics/board/card_item.h @@ -175,6 +175,7 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + bool doubleClickActsOnSelection() const override; }; #endif diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index c86bc9925..630530792 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -182,6 +182,15 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event) QList result; QList sel = scene()->selectedItems(); + // The double-click goes to the view, not the card, so cancel any pending + // selection collapse on the cards before reading the selection. + for (auto *item : sel) { + if (auto *c = dynamic_cast(item)) { + c->cancelPendingSelectionCollapse(); + } + } + sel = scene()->selectedItems(); + for (int i = 0; i < sel.size(); i++) { auto *c = static_cast(sel.at(i)); auto *zone = static_cast(c->parentItem()); diff --git a/cockatrice/src/game_graphics/deckview/deck_view.h b/cockatrice/src/game_graphics/deckview/deck_view.h index 3b5b4b6f4..31cb0f87f 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.h +++ b/cockatrice/src/game_graphics/deckview/deck_view.h @@ -49,6 +49,10 @@ public: { return originZone; } + bool doubleClickActsOnSelection() const override + { + return true; + } protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 1c92d74ae..cb59b3d7d 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -127,6 +127,7 @@ void GameView::startRubberBand(const QPointF &_selectionOrigin) selectionOrigin = _selectionOrigin; previousBandRect = QRect(); rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); + rubberBand->raise(); rubberBand->show(); }