[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.
This commit is contained in:
Lukas Brübach 2026-11-08 21:34:48 +01:00
parent d073a007bb
commit 9162dcdede
3 changed files with 56 additions and 10 deletions

View file

@ -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()) {

View file

@ -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<CardState *> &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<CardState *> &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<CardState *> &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<void()> &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)

View file

@ -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<void()> &action);
QLabel *createSectionHeader(const QString &text, QWidget *parent);
QLabel *createNoteLabel(const QString &text, QWidget *parent);