From 9162dcdede17904b839992e43447430b61f827c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= <44814898+BruebachL@users.noreply.github.com> Date: Sun, 8 Nov 2026 21:34:48 +0100 Subject: [PATCH] [Game] Dismiss the hover panel before opening a modal dialog The hover panel is a Qt::ToolTip window, i.e. a popup: it is always on top and holds the input grab. Actions on the panel that open a modal dialog (Set power/toughness, Set annotation, Set counter value, token creation) therefore opened the dialog behind the panel, where it could not be seen or activated. Hide the panel synchronously and defer the action to the next event-loop turn, so the popup grab is released before the dialog's event loop starts. The panel asks the scene to dismiss it through a new dismissRequested signal, keeping hover timers and the covered-card fade on the scene's side. Reported from the P/T overlay SET button; the same path covers the counter tab and the token tab. --- cockatrice/src/game_graphics/game_scene.cpp | 1 + .../widgets/cards/card_hover_widget.cpp | 53 +++++++++++++++---- .../widgets/cards/card_hover_widget.h | 12 +++++ 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index 8de4ddd91..b4c6e1690 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -1040,6 +1040,7 @@ void GameScene::showCardHoverPopup(CardItem *card) cardHoverWidget = new CardHoverWidget(viewport); connect(cardHoverWidget, &CardHoverWidget::hoverEntered, this, &GameScene::onHoverWidgetEntered); connect(cardHoverWidget, &CardHoverWidget::hoverLeft, this, &GameScene::onHoverWidgetLeft); + connect(cardHoverWidget, &CardHoverWidget::dismissRequested, this, &GameScene::hideCardHoverPopup); // Keep the popup anchored to its card while the view scrolls, zooms, or resizes if (!views().isEmpty()) { diff --git a/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp b/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp index 4e0f93480..27625db52 100644 --- a/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp @@ -407,10 +407,12 @@ void CardHoverWidget::createPTOverlay() a->actIncT(cardList()); } }); - btnSetPT = createOverlayButton("SET", tr("Set power/toughness..."), [getActions, cardList]() { - if (auto *a = getActions()) { - a->actRequestSetPTDialog(cardList()); - } + btnSetPT = createOverlayButton("SET", tr("Set power/toughness..."), [this, getActions, cardList]() { + runModalAction([getActions, cardList]() { + if (auto *a = getActions()) { + a->actRequestSetPTDialog(cardList()); + } + }); }); btnResetPT = createOverlayButton("RST", tr("Reset power/toughness"), [getActions, cardList]() { if (auto *a = getActions()) { @@ -885,7 +887,10 @@ void CardHoverWidget::buildMainTab(const QList &cardList, const QSt mainPage)); grid.addButton(createGridButton( tr("Set annotation..."), semanticActionIcon(QStringLiteral("icons/pencil"), AppColor::Marked), - [actions, cardList]() { actions->actRequestSetAnnotationDialog(cardList); }, mainPage)); + [this, actions, cardList]() { + runModalAction([actions, cardList]() { actions->actRequestSetAnnotationDialog(cardList); }); + }, + mainPage)); grid.addButton(createGridButton(tr("Turn Over"), semanticActionIcon(QStringLiteral("icons/swap"), AppColor::Hidden), invoke(cmFlip), mainPage)); @@ -1041,7 +1046,10 @@ void CardHoverWidget::buildCounterTab(const QList &cardList, const layout->addWidget(createSectionHeader(tr("Power / Toughness"), counterPage)); layout->addWidget(createSmallButton( - tr("Set power and toughness..."), [actions, cardList]() { actions->actRequestSetPTDialog(cardList); }, + tr("Set power and toughness..."), + [this, actions, cardList]() { + runModalAction([actions, cardList]() { actions->actRequestSetPTDialog(cardList); }); + }, counterPage)); layout->addWidget(createSmallButton( tr("Reset power and toughness"), [actions, cardList]() { actions->actResetPT(cardList); }, counterPage)); @@ -1065,7 +1073,10 @@ void CardHoverWidget::buildCounterTab(const QList &cardList, const tr("Remove"), [actions, cardList, i]() { actions->actRemoveCardCounter(cardList, i); }, counterPage)); } row->addWidget(createSmallButton( - tr("Set..."), [actions, cardList, i]() { actions->actRequestSetCardCounterDialog(cardList, i); }, + tr("Set..."), + [this, actions, cardList, i]() { + runModalAction([actions, cardList, i]() { actions->actRequestSetCardCounterDialog(cardList, i); }); + }, counterPage)); layout->addLayout(row); } @@ -1108,14 +1119,19 @@ void CardHoverWidget::buildTokenTab(CardItem *card, bool canModify) CardItem *source = card; layout->addWidget(createSmallButton( tr("Token: ") + relatedName, - [actions, source, captured]() { - actions->actRequestCreateRelatedFromRelationDialog(source->getState(), captured); + [this, actions, source, captured]() { + runModalAction([actions, source, captured]() { + actions->actRequestCreateRelatedFromRelationDialog(source->getState(), captured); + }); }, tokenPage)); } if (hasMulti && known > 0) { layout->addWidget(createSmallButton( - tr("All tokens"), [actions, card]() { actions->actCreateAllRelatedCards(card->getState()); }, + tr("All tokens"), + [this, actions, card]() { + runModalAction([actions, card]() { actions->actCreateAllRelatedCards(card->getState()); }); + }, tokenPage)); } anyToken = known > 0; @@ -1613,6 +1629,23 @@ bool CardHoverWidget::isClosing() const return fadeOut->state() == QAbstractAnimation::Running; } +void CardHoverWidget::runModalAction(const std::function &action) +{ + // The panel is a Qt::ToolTip (Qt::Popup | Qt::Sheet): it is always on top and + // holds an input grab. A modal dialog opened while it is still up lands behind + // it and cannot be activated. Ask the scene to dismiss the panel so its hover + // timers stop and the covered card fades back in, then drop the window + // synchronously to release the grab. + emit dismissRequested(); + fadeIn->stop(); + fadeOut->stop(); + hide(); + + // Defer the dialog to the next event-loop turn: the popup grab is not fully + // released until after the current mouse event returns. + QTimer::singleShot(0, this, action); +} + // ---------- Event filter ---------- bool CardHoverWidget::eventFilter(QObject *obj, QEvent *event) diff --git a/cockatrice/src/interface/widgets/cards/card_hover_widget.h b/cockatrice/src/interface/widgets/cards/card_hover_widget.h index e8366b124..acd9d4cc6 100644 --- a/cockatrice/src/interface/widgets/cards/card_hover_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_hover_widget.h @@ -81,6 +81,8 @@ protected: signals: void hoverEntered(); void hoverLeft(); + /** @brief Asks the scene to dismiss the panel before a modal dialog opens. */ + void dismissRequested(); private slots: void onCardChanged(); @@ -167,6 +169,16 @@ private: QWidget *makeStackPage(QVBoxLayout *&layout); void showPage(int index); + /** + * @brief Dismisses the panel, then runs an action that opens a modal dialog. + * + * A Qt::ToolTip window is always on top and holds the input grab, so a modal + * dialog opened from inside the panel lands behind it and cannot be + * activated. Hide the panel synchronously, then defer the action so the grab + * is released before the dialog's event loop starts. + */ + void runModalAction(const std::function &action); + QLabel *createSectionHeader(const QString &text, QWidget *parent); QLabel *createNoteLabel(const QString &text, QWidget *parent);