diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp index db33e2c84..50ba0b1fe 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp @@ -1,70 +1,168 @@ #include "tutorial_controller.h" +#include + TutorialController::TutorialController(QWidget *_tutorializedWidget) : QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget) { - tutorialOverlay = new TutorialOverlay(nullptr); // nullptr parent because we overlay this on top of *everything* - - // Stay above everything - tutorialOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + tutorialOverlay = new TutorialOverlay(tutorializedWidget->window()); + // Make it frameless + translucent + tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint); tutorialOverlay->setAttribute(Qt::WA_TranslucentBackground); - // Hide until started + // hide until start tutorialOverlay->hide(); - connect(tutorialOverlay, &TutorialOverlay::nextStep, this, &TutorialController::progressTutorial); - + connect(tutorialOverlay, &TutorialOverlay::nextStep, this, &TutorialController::nextStep); connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, [this]() { tutorialOverlay->hide(); }); } -void TutorialController::addStep(const TutorialStep &step) +void TutorialController::addSequence(const TutorialSequence &seq) { - steps.append(step); + sequences.append(seq); } void TutorialController::start() { - if (steps.isEmpty()) { + if (sequences.isEmpty()) { return; } - // Align ourselves and show - QPoint topLeft = tutorializedWidget->mapToGlobal(QPoint(0, 0)); - QSize windowSize = tutorializedWidget->size(); + QTimer::singleShot(0, this, [this]() { + QWidget *win = tutorializedWidget->window(); + tutorialOverlay->parentResized(); + tutorialOverlay->setGeometry(QRect(QPoint(0, 0), win->size())); + tutorialOverlay->show(); + tutorialOverlay->raise(); + tutorialOverlay->parentResized(); - tutorialOverlay->setGeometry(QRect(topLeft, windowSize)); - tutorialOverlay->show(); - tutorialOverlay->raise(); + currentSequence = 0; + currentStep = 0; + showStep(); + }); +} + +void TutorialController::nextStep() +{ + // advance within sequence + currentStep++; + + if (currentSequence < 0) { + return; // defensive in case we haven't started yet + } + + if (currentStep >= sequences[currentSequence].steps.size()) { + // advance to next sequence + nextSequence(); + return; + } - // Start the tutorial - currentStep = 0; showStep(); } -void TutorialController::progressTutorial() +void TutorialController::prevStep() { - currentStep++; - if (currentStep >= steps.size()) { - tutorialOverlay->hide(); + if (currentSequence < 0) { + return; // defensive in case we haven't started yet + } + + if (currentStep == 0) { + prevSequence(); return; } + + currentStep--; showStep(); } +void TutorialController::nextSequence() +{ + if (currentSequence < 0) { + return; + } + + // run exit for the last step of the current sequence (showStep handles previous onExit, + // but ensure we run it here because we're jumping sequence) + // We'll increment sequence and then call showStep which will call the onEnter for the new step. + currentSequence++; + currentStep = 0; + + if (currentSequence >= sequences.size()) { + exitTutorial(); + return; + } + + showStep(); +} + +void TutorialController::prevSequence() +{ + if (currentSequence <= 0) { + // already at first sequence -> stay + return; + } + + // go to last step of previous sequence + currentSequence--; + currentStep = sequences[currentSequence].steps.size() - 1; + showStep(); +} + +void TutorialController::exitTutorial() +{ + // Run onExit for the current step if present + if (currentSequence >= 0 && currentStep >= 0 && currentSequence < sequences.size() && + currentStep < sequences[currentSequence].steps.size()) { + const auto &curStep = sequences[currentSequence].steps[currentStep]; + if (curStep.onExit) { + curStep.onExit(); + } + } + + tutorialOverlay->hide(); + + // reset indices so start() can be called again cleanly + currentSequence = -1; + currentStep = -1; +} + void TutorialController::showStep() { - const auto &step = steps[currentStep]; + // bounds checks + if (currentSequence < 0 || currentSequence >= sequences.size()) { + return; + } + const auto &seq = sequences[currentSequence]; + if (currentStep < 0 || currentStep >= seq.steps.size()) { + return; + } - // Run any action associated with previous steps - if (currentStep < 0) { - const auto &previousStep = steps[currentStep - 1]; + // run onExit for the previous step (including if previous step was in previous sequence) + if (!(currentSequence == 0 && currentStep == 0)) { + int prevSeq = currentSequence; + int prevStepIndex = currentStep - 1; + if (prevStepIndex < 0) { + // previous is last step of previous sequence + prevSeq = currentSequence - 1; + if (prevSeq >= 0) { + prevStepIndex = sequences[prevSeq].steps.size() - 1; + } else { + prevStepIndex = -1; + } + } - if (previousStep.onExit) { - previousStep.onExit(); + if (prevSeq >= 0 && prevStepIndex >= 0) { + const auto &previousStep = sequences[prevSeq].steps[prevStepIndex]; + if (previousStep.onExit) { + previousStep.onExit(); + } } } + // current step + const auto &step = seq.steps[currentStep]; + // Run any action associated with this step if (step.onEnter) { step.onEnter(); diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h index 0e6bf7020..4bdc70d34 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h @@ -11,8 +11,19 @@ struct TutorialStep { QWidget *targetWidget; QString text; - std::function onEnter; // Optional function to run when this step starts - std::function onExit; // Optional function to run when step ends + std::function onEnter = nullptr; // Optional function to run when this step starts + std::function onExit = nullptr; // Optional function to run when step ends +}; + +struct TutorialSequence +{ + QString name; + QVector steps; + + void addStep(const TutorialStep &step) + { + steps.append(step); + } }; class TutorialController : public QObject @@ -20,17 +31,22 @@ class TutorialController : public QObject Q_OBJECT public slots: - void progressTutorial(); + void start(); + void nextStep(); + void prevStep(); + void nextSequence(); + void prevSequence(); + void exitTutorial(); public: explicit TutorialController(QWidget *_tutorializedWidget); - void addStep(const TutorialStep &step); - void start(); + void addSequence(const TutorialSequence &step); private: QWidget *tutorializedWidget; - QVector steps; + QVector sequences; + int currentSequence = -1; int currentStep = -1; TutorialOverlay *tutorialOverlay; diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp index db2ec5ac4..f6c83ce79 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp @@ -7,7 +7,7 @@ #include #include -TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent) +TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window) { setAttribute(Qt::WA_TransparentForMouseEvents, false); setAttribute(Qt::WA_NoSystemBackground, true); @@ -16,7 +16,13 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent) // This ensures the overlay stays exactly over the parent if (parent) { - setGeometry(parent->rect()); + QRect r = parent->rect(); + + // convert the parent’s rect to screen coordinates + QPoint globalTopLeft = parent->mapToGlobal(QPoint(0, 0)); + r.moveTopLeft(globalTopLeft); + + setGeometry(r); } } @@ -99,7 +105,10 @@ QRect TutorialOverlay::computeBubbleRect(const QRect &hole) const void TutorialOverlay::parentResized() { if (parentWidget()) { - setGeometry(parentWidget()->rect()); + QRect r = parentWidget()->rect(); + QPoint globalTopLeft = parentWidget()->mapToGlobal(QPoint(0, 0)); + r.moveTopLeft(globalTopLeft); + setGeometry(r); } }