diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp index b5c2cf328..c0dabbd06 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp @@ -14,34 +14,16 @@ BubbleWidget::BubbleWidget(QWidget *parent) : QFrame(parent) counterLabel = new QLabel(this); counterLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - closeButton = new QPushButton("✕", this); - closeButton->setFixedSize(20, 20); - textLabel = new QLabel(this); textLabel->setWordWrap(true); textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); textLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); textLabel->setStyleSheet("color:black;"); // guard against global styles - // nav buttons - previousSequenceButton = new QPushButton("<<", this); - previousStepButton = new QPushButton("<", this); - nextStepButton = new QPushButton(">", this); - nextSequenceButton = new QPushButton(">>", this); - - QHBoxLayout *navLayout = new QHBoxLayout; - navLayout->addStretch(); - navLayout->addWidget(previousSequenceButton); - navLayout->addWidget(previousStepButton); - navLayout->addWidget(nextStepButton); - navLayout->addWidget(nextSequenceButton); - // Layout layout->addWidget(counterLabel, 0, 0, Qt::AlignLeft | Qt::AlignVCenter); layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 1); - layout->addWidget(closeButton, 0, 2, Qt::AlignRight); layout->addWidget(textLabel, 1, 0, 1, 3); - layout->addLayout(navLayout, 2, 0, 1, 3); // Make column 1 take extra space so text gets room to expand/wrap layout->setColumnStretch(1, 1); 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 04854918a..3cf809602 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.h @@ -11,11 +11,6 @@ class BubbleWidget : public QFrame public: QLabel *textLabel; QLabel *counterLabel; - QPushButton *closeButton; - QPushButton *previousSequenceButton; - QPushButton *previousStepButton; - QPushButton *nextStepButton; - QPushButton *nextSequenceButton; BubbleWidget(QWidget *parent); void setText(const QString &text); diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp index d15d7839f..b58c7cb97 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp @@ -9,21 +9,18 @@ #include #include #include +#include TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window) { - setAttribute(Qt::WA_TransparentForMouseEvents, false); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); + setAttribute(Qt::WA_TransparentForMouseEvents, false); setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); if (parent) { parent->installEventFilter(this); - - QRect r = parent->rect(); - QPoint globalTopLeft = parent->mapToGlobal(QPoint(0, 0)); - r.moveTopLeft(globalTopLeft); - setGeometry(r); + parentResized(); } // ---- Control bar ------------------------------------------------- @@ -68,6 +65,16 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window) // ---- Bubble ------------------------------------------------------ bubble = new BubbleWidget(this); bubble->hide(); + + controlBar->hide(); +} + +void TutorialOverlay::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + // Queue geometry correction after the window is fully shown + QMetaObject::invokeMethod(this, [this]() { parentResized(); }, Qt::QueuedConnection); } void TutorialOverlay::setTitle(const QString &title) @@ -91,21 +98,9 @@ void TutorialOverlay::setTargetWidget(QWidget *w) if (targetWidget) targetWidget->installEventFilter(this); - updateHoleRect(); recomputeLayout(); } -void TutorialOverlay::updateHoleRect() -{ - if (!targetWidget || !targetWidget->isVisible()) { - cachedHoleRect = QRect(); - } else { - QPoint globalTopLeft = targetWidget->mapToGlobal(QPoint(0, 0)); - QPoint localTopLeft = mapFromGlobal(globalTopLeft); - cachedHoleRect = QRect(localTopLeft, targetWidget->size()).adjusted(-6, -6, 6, 6); - } -} - void TutorialOverlay::setText(const QString &t) { tutorialText = t; @@ -114,6 +109,16 @@ void TutorialOverlay::setText(const QString &t) recomputeLayout(); } +QRect TutorialOverlay::currentHoleRect() const +{ + if (!targetWidget || !targetWidget->isVisible()) + return QRect(); + + QPoint globalTopLeft = targetWidget->mapToGlobal(QPoint(0, 0)); + QPoint localTopLeft = mapFromGlobal(globalTopLeft); + return QRect(localTopLeft, targetWidget->size()).adjusted(-6, -6, 6, 6); +} + void TutorialOverlay::resizeEvent(QResizeEvent *) { recomputeLayout(); @@ -125,9 +130,13 @@ bool TutorialOverlay::eventFilter(QObject *obj, QEvent *event) parentResized(); } - if (obj == targetWidget && (event->type() == QEvent::Show || event->type() == QEvent::Hide)) { - updateHoleRect(); - recomputeLayout(); + if (obj == targetWidget) { + if (event->type() == QEvent::Show) { + // Defer layout recalculation to give Qt time to finalize geometry + QMetaObject::invokeMethod(this, [this]() { recomputeLayout(); }, Qt::QueuedConnection); + } else if (event->type() == QEvent::Hide || event->type() == QEvent::Move || event->type() == QEvent::Resize) { + recomputeLayout(); + } } return QWidget::eventFilter(obj, event); @@ -138,86 +147,101 @@ void TutorialOverlay::parentResized() if (!parentWidget()) return; - QRect r = parentWidget()->rect(); - QPoint globalTopLeft = parentWidget()->mapToGlobal(QPoint(0, 0)); + QWidget *w = parentWidget(); + + // Get the parent rect in local coordinates + QRect r = w->rect(); + + // Map top-left to global coordinates + QPoint globalTopLeft = w->mapToGlobal(QPoint(0, 0)); + + // Set overlay geometry in screen coords, exactly matching the parent widget r.moveTopLeft(globalTopLeft); setGeometry(r); - updateHoleRect(); recomputeLayout(); } -// Recompute layout for bubble and control bar void TutorialOverlay::recomputeLayout() { - updateHoleRect(); + QRect hole = currentHoleRect(); - if (cachedHoleRect.isEmpty()) { - bubble->hide(); - controlBar->hide(); + if (hole.isEmpty()) { + if (bubble) { + bubble->hide(); + } + if (controlBar) { + controlBar->hide(); + } hide(); return; } show(); raise(); - bubble->show(); - controlBar->show(); // ---- Bubble ---- QSize bsize = bubble->sizeHint().expandedTo(QSize(160, 60)); - highlightBubbleRect = computeBubbleRect(cachedHoleRect, bsize); + highlightBubbleRect = computeBubbleRect(hole, bsize); bubble->setGeometry(highlightBubbleRect); + bubble->show(); bubble->raise(); // ---- Control bar ---- controlBar->adjustSize(); + controlBar->show(); + const int margin = 8; QRect r = rect(); - QList positions = { - {r.right() - controlBar->width() - margin, r.bottom() - controlBar->height() - margin}, // bottom-right - {r.right() - controlBar->width() - margin, margin}, // top-right - {margin, r.bottom() - controlBar->height() - margin}, // bottom-left - {margin, margin} // top-left - }; + QList positions = {{r.right() - controlBar->width() - margin, r.bottom() - controlBar->height() - margin}, + {r.right() - controlBar->width() - margin, margin}, + {margin, r.bottom() - controlBar->height() - margin}, + {margin, margin}}; for (const QPoint &pos : positions) { QRect proposed(pos, controlBar->size()); - if (!proposed.intersects(cachedHoleRect)) { + if (!proposed.intersects(hole)) { controlBar->move(pos); break; } } + controlBar->raise(); + update(); } QRect TutorialOverlay::computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const { const int margin = 16; QRect r = rect(); + QRect bubble; if (hole.isEmpty()) { - return QRect(r.center().x() - bubbleSize.width() / 2, r.center().y() - bubbleSize.height() / 2, - bubbleSize.width(), bubbleSize.height()); + bubble = QRect(r.center() - QPoint(bubbleSize.width() / 2, bubbleSize.height() / 2), bubbleSize); + } else { + bubble = QRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height()); + + if (!r.contains(bubble)) + bubble.moveLeft(hole.left() - margin - bubbleSize.width()); + + if (!r.contains(bubble)) { + bubble.moveLeft(hole.center().x() - bubbleSize.width() / 2); + bubble.moveTop(hole.top() - margin - bubbleSize.height()); + } + + if (!r.contains(bubble)) + bubble.moveTop(hole.bottom() + margin); } - // Prefer right, left, top, bottom - QRect tryRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height()); - if (r.contains(tryRect)) - return tryRect; + // Final clamp to overlay bounds - ensure min <= max for qBound + int maxLeft = qMax(r.left(), r.right() - bubble.width()); + int maxTop = qMax(r.top(), r.bottom() - bubble.height()); - tryRect.moveLeft(hole.left() - margin - bubbleSize.width()); - if (r.contains(tryRect)) - return tryRect; + bubble.moveLeft(qBound(r.left(), bubble.left(), maxLeft)); + bubble.moveTop(qBound(r.top(), bubble.top(), maxTop)); - tryRect.moveLeft(hole.center().x() - bubbleSize.width() / 2); - tryRect.moveTop(hole.top() - margin - bubbleSize.height()); - if (r.contains(tryRect)) - return tryRect; - - tryRect.moveTop(hole.bottom() + margin); - return tryRect; + return bubble; } void TutorialOverlay::paintEvent(QPaintEvent *) @@ -227,9 +251,10 @@ void TutorialOverlay::paintEvent(QPaintEvent *) p.fillRect(rect(), QColor(0, 0, 0, 160)); - if (!cachedHoleRect.isEmpty()) { + QRect hole = currentHoleRect(); + if (!hole.isEmpty()) { QPainterPath holePath; - holePath.addRoundedRect(cachedHoleRect, 8, 8); + holePath.addRoundedRect(hole, 8, 8); p.setCompositionMode(QPainter::CompositionMode_Clear); p.fillPath(holePath, Qt::transparent); } diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h index 5f45f8645..6b250a146 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.h @@ -10,6 +10,8 @@ class QFrame; class TutorialOverlay : public QWidget { Q_OBJECT +public slots: + void showEvent(QShowEvent *event) override; public: explicit TutorialOverlay(QWidget *parent = nullptr); @@ -17,6 +19,7 @@ public: void setTargetWidget(QWidget *w); void updateHoleRect(); void setText(const QString &t); + QRect currentHoleRect() const; void setTitle(const QString &title); void setBlocking(bool blockInput);