From e9e3515628ad73dc432721d7db4a7a0ddcccb891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sat, 23 May 2026 17:02:56 +0200 Subject: [PATCH] Follow parent window properly. Took 19 seconds --- .../general/tutorial/tutorial_bubble_widget.h | 4 + .../general/tutorial/tutorial_controller.cpp | 8 +- .../general/tutorial/tutorial_overlay.cpp | 162 +++++++++++------- .../general/tutorial/tutorial_overlay.h | 3 +- 4 files changed, 112 insertions(+), 65 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h index fea72413e..c64ed5428 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h @@ -17,6 +17,10 @@ public: void setProgress(int stepNum, int totalSteps, int overallStep, int overallTotal); void setInteractionHint(const QString &hint); void setValidationHint(const QString &hint); + QGridLayout *getLayout() const + { + return layout; + } private: void clearValidationHint(); diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp index 8839f7809..069b753aa 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp @@ -38,12 +38,8 @@ void TutorialController::start() QTimer::singleShot(0, this, [this]() { QWidget *win = tutorializedWidget->window(); - // Reparent to make absolutely sure - tutorialOverlay->setParent(win); - tutorialOverlay->setGeometry(0, 0, win->width(), win->height()); - - // Stack order - tutorialOverlay->stackUnder(nullptr); + tutorialOverlay->setParent(win); // triggers changeEvent and installs filter + tutorialOverlay->setGeometry(win->rect()); tutorialOverlay->show(); tutorialOverlay->raise(); diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp index 5fbc04f84..40e9607a1 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp @@ -22,8 +22,8 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_TransparentForMouseEvents, false); - - setAttribute(Qt::WA_OpaquePaintEvent, false); + setAttribute(Qt::WA_StaticContents, false); + setAttribute(Qt::WA_NoSystemBackground, true); setAutoFillBackground(false); if (parent) { @@ -81,14 +81,28 @@ void TutorialOverlay::showEvent(QShowEvent *event) QWidget::showEvent(event); if (parentWidget()) { - QWidget *parent = parentWidget(); - setGeometry(0, 0, parent->width(), parent->height()); + setGeometry(0, 0, parentWidget()->width(), parentWidget()->height()); } raise(); parentResized(); } +void TutorialOverlay::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::ParentChange) { + // Remove filter from old parent (Qt has already changed parentWidget() by now, + // so iterate sender list isn't easy — reinstall unconditionally on the new parent) + QWidget *p = parentWidget(); + if (p) { + p->installEventFilter(this); // safe to call multiple times + setGeometry(p->rect()); + raise(); + } + } + QWidget::changeEvent(event); +} + void TutorialOverlay::setTitle(const QString &title) { titleLabel->setText(title); @@ -107,23 +121,14 @@ void TutorialOverlay::setInteractive(bool interactive, bool clickThrough) if (nextButton) { nextButton->setEnabled(!interactive); - if (interactive) { - nextButton->setToolTip("Complete the highlighted action to continue"); - } else { - nextButton->setToolTip("Next step"); - } + nextButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next step"); } if (nextSeqButton) { nextSeqButton->setEnabled(!interactive); - if (interactive) { - nextSeqButton->setToolTip("Complete the highlighted action to continue"); - } else { - nextSeqButton->setToolTip("Next chapter"); - } + nextSeqButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next chapter"); } - // Update mask when clickThrough changes updateMask(); } @@ -154,6 +159,7 @@ void TutorialOverlay::setProgress(int stepNum, void TutorialOverlay::setTargetWidget(QWidget *w) { + if (targetWidget) { targetWidget->removeEventFilter(this); } @@ -169,6 +175,7 @@ void TutorialOverlay::setTargetWidget(QWidget *w) void TutorialOverlay::setText(const QString &t) { + tutorialText = t; bubble->setText(t); bubble->adjustSize(); @@ -181,56 +188,54 @@ QRect TutorialOverlay::currentHoleRect() const return QRect(); } - QPoint targetGlobal = targetWidget->mapToGlobal(QPoint(0, 0)); - QPoint targetInOverlay = mapFromGlobal(targetGlobal); + QPoint p = targetWidget->mapToGlobal(QPoint(0, 0)); + QPoint local = mapFromGlobal(p); - return QRect(targetInOverlay, targetWidget->size()).adjusted(-6, -6, 6, 6); + QRect r(local, targetWidget->size()); + r = r.adjusted(-6, -6, 6, 6); + + return r; } void TutorialOverlay::mousePressEvent(QMouseEvent *event) { QRect hole = currentHoleRect(); - // Check if click is in the highlighted area if (hole.contains(event->pos())) { - // For non-clickthrough steps, emit targetClicked for advancement + if (!allowClickThrough && isInteractive && !qobject_cast(targetWidget) && !qobject_cast(targetWidget) && !qobject_cast(targetWidget) && !qobject_cast(targetWidget)) { QTimer::singleShot(100, this, [this]() { emit targetClicked(); }); } - // If allowClickThrough, the mask ensures events pass through return; } - // Click outside highlighted area - block it event->accept(); } void TutorialOverlay::updateMask() { - if (allowClickThrough) { - QRect hole = currentHoleRect(); - if (!hole.isEmpty()) { - // Create a mask that excludes the hole area - QRegion fullRegion(rect()); - QRegion holeRegion(hole); - QRegion maskRegion = fullRegion.subtracted(holeRegion); - setMask(maskRegion); - } else { - clearMask(); - } - } else { - clearMask(); + clearMask(); + + if (!isVisible() || !parentWidget()) { + return; + } + if (!allowClickThrough) { + return; + } + + QRect hole = currentHoleRect(); + + if (!hole.isEmpty()) { + QRegion full(rect()); + QRegion cut(hole); + setMask(full.subtracted(cut)); } } bool TutorialOverlay::event(QEvent *event) { - // Update mask on any event that might change geometry - if (event->type() == QEvent::Move || event->type() == QEvent::Resize) { - updateMask(); - } return QWidget::event(event); } @@ -241,15 +246,29 @@ void TutorialOverlay::resizeEvent(QResizeEvent *) bool TutorialOverlay::eventFilter(QObject *obj, QEvent *event) { - if (obj == parentWidget() && (event->type() == QEvent::Resize || event->type() == QEvent::Move)) { - parentResized(); + if (obj == parentWidget()) { + switch (event->type()) { + case QEvent::Resize: + case QEvent::Move: // window dragged to another monitor / position + parentResized(); + break; + default: + break; + } } if (obj == targetWidget) { - if (event->type() == QEvent::Show) { - QMetaObject::invokeMethod(this, [this]() { recomputeLayout(); }, Qt::QueuedConnection); - } else if (event->type() == QEvent::Hide || event->type() == QEvent::Move || event->type() == QEvent::Resize) { - recomputeLayout(); + switch (event->type()) { + case QEvent::Show: + QMetaObject::invokeMethod(this, [this] { recomputeLayout(); }, Qt::QueuedConnection); + break; + case QEvent::Hide: + case QEvent::Move: + case QEvent::Resize: + recomputeLayout(); + break; + default: + break; } } @@ -262,22 +281,38 @@ void TutorialOverlay::parentResized() return; } - setGeometry(0, 0, parentWidget()->width(), parentWidget()->height()); + const QSize s = parentWidget()->size(); + + setGeometry(0, 0, s.width(), s.height()); + + clearMask(); recomputeLayout(); + + setAttribute(Qt::WA_NoSystemBackground, false); + setAttribute(Qt::WA_NoSystemBackground, true); + + update(); } void TutorialOverlay::recomputeLayout() { + if (!parentWidget()) { + return; + } + + resize(parentWidget()->window()->geometry().size()); + + bubble->adjustSize(); + QRect hole = currentHoleRect(); if (hole.isEmpty()) { - if (bubble) { - bubble->hide(); - } - if (controlBar) { - controlBar->hide(); - } + bubble->hide(); + controlBar->hide(); + hide(); + + update(); return; } @@ -286,9 +321,9 @@ void TutorialOverlay::recomputeLayout() QSize bsize = bubble->sizeHint().expandedTo(QSize(160, 60)); highlightBubbleRect = computeBubbleRect(hole, bsize); + bubble->setGeometry(highlightBubbleRect); bubble->show(); - bubble->raise(); controlBar->adjustSize(); controlBar->show(); @@ -301,28 +336,39 @@ void TutorialOverlay::recomputeLayout() {margin, r.bottom() - controlBar->height() - margin}, {margin, margin}}; + QRect placedRect; + for (const QPoint &pos : positions) { QRect proposed(pos, controlBar->size()); - if (!proposed.intersects(hole)) { - controlBar->move(pos); + + if (!proposed.intersects(hole) && !proposed.intersects(highlightBubbleRect)) { + placedRect = proposed; break; } } - controlBar->raise(); + if (!placedRect.isValid()) { + placedRect = QRect(QPoint(margin, margin), controlBar->size()); + } + + controlBar->move(placedRect.topLeft()); + controlBar->show(); + + updateMask(); update(); - updateMask(); // Update mask after layout changes } QRect TutorialOverlay::computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const { - const int margin = 16; QRect r = rect(); + QRect bubble; if (hole.isEmpty()) { bubble = QRect(r.center() - QPoint(bubbleSize.width() / 2, bubbleSize.height() / 2), bubbleSize); } else { + const int margin = 16; + bubble = QRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height()); if (!r.contains(bubble)) { diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h index eb92196e6..9bc183df9 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h @@ -26,6 +26,7 @@ public: void parentResized(); QRect currentHoleRect() const; + bool eventFilter(QObject *obj, QEvent *event) override; signals: void nextStep(); @@ -42,7 +43,7 @@ protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void updateMask(); - bool eventFilter(QObject *obj, QEvent *event) override; + void changeEvent(QEvent *event) override; private: void recomputeLayout();