Use pointer-to-member signal connections for tutorial validation

Took 10 minutes
This commit is contained in:
Lukas Brübach 2026-08-09 03:30:57 +02:00
parent 569b531113
commit 9c0c480b2e
7 changed files with 56 additions and 36 deletions

View file

@ -60,8 +60,10 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
vdeStep.validationTiming = ValidationTiming::OnSignal; vdeStep.validationTiming = ValidationTiming::OnSignal;
vdeStep.autoAdvanceOnValid = true; vdeStep.autoAdvanceOnValid = true;
vdeStep.validator = []() { return true; }; vdeStep.validator = []() { return true; };
vdeStep.signalSource = visualDeckEditorButton; vdeStep.signalHook = [this](TutorialController *controller) {
vdeStep.signalName = SIGNAL(clicked()); return connect(visualDeckEditorButton, &QAbstractButton::clicked, controller,
&TutorialController::checkValidation);
};
sequence.addStep(vdeStep); sequence.addStep(vdeStep);
sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."}); sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."});

View file

@ -294,10 +294,8 @@ void TutorialController::setupValidationMonitoring()
const auto &step = sequences[currentSequence].steps[currentStep]; const auto &step = sequences[currentSequence].steps[currentStep];
// Handle OnSignal validation - connect to any custom signal // Handle OnSignal validation - connect to any custom signal
if (step.validationTiming == ValidationTiming::OnSignal && step.validator) { if (step.validationTiming == ValidationTiming::OnSignal && step.signalHook) {
if (step.signalSource && step.signalName) { validationConnection = step.signalHook(this);
validationConnection = connect(step.signalSource, step.signalName, this, SLOT(checkValidation()));
}
return; return;
} }

View file

@ -15,6 +15,8 @@ enum class ValidationTiming
Manual // Only validate when explicitly triggered Manual // Only validate when explicitly triggered
}; };
class TutorialController;
struct TutorialStep struct TutorialStep
{ {
QWidget *targetWidget = nullptr; QWidget *targetWidget = nullptr;
@ -35,9 +37,11 @@ struct TutorialStep
// Custom interaction hint (overrides default "Click to continue") // Custom interaction hint (overrides default "Click to continue")
QString customInteractionHint = nullptr; QString customInteractionHint = nullptr;
// Signal-based validation (for ValidationTiming::OnSignal) // Establishes the connection used for signal-based validation (ValidationTiming::OnSignal).
QObject *signalSource = nullptr; // Object that emits the signal // Called with the controller so call sites can connect a specific signal to
const char *signalName = nullptr; // Signal to connect to (use SIGNAL() macro) // TutorialController::checkValidation using pointer-to-member, which is checked at
// compile time unlike string-based SIGNAL() connections.
std::function<QMetaObject::Connection(TutorialController *)> signalHook = nullptr;
}; };
struct TutorialSequence struct TutorialSequence

View file

@ -186,8 +186,10 @@ void TabGame::finishTutorialInitialization()
loadDeckStep.requiresInteraction = true; loadDeckStep.requiresInteraction = true;
loadDeckStep.autoAdvanceOnValid = true; loadDeckStep.autoAdvanceOnValid = true;
loadDeckStep.validationTiming = ValidationTiming::OnSignal; loadDeckStep.validationTiming = ValidationTiming::OnSignal;
loadDeckStep.signalSource = game->getGameEventHandler(); loadDeckStep.signalHook = [this](TutorialController *controller) {
loadDeckStep.signalName = SIGNAL(logDeckSelect(PlayerLogic *, QString, int)); return connect(game->getGameEventHandler(), &GameEventHandler::logDeckSelect, controller,
&TutorialController::checkValidation);
};
loadDeckStep.validator = [] { return true; }; loadDeckStep.validator = [] { return true; };
deckSelectSequence.addStep(loadDeckStep); deckSelectSequence.addStep(loadDeckStep);
@ -199,8 +201,9 @@ void TabGame::finishTutorialInitialization()
readyUpStep.requiresInteraction = true; readyUpStep.requiresInteraction = true;
readyUpStep.autoAdvanceOnValid = true; readyUpStep.autoAdvanceOnValid = true;
readyUpStep.validationTiming = ValidationTiming::OnSignal; readyUpStep.validationTiming = ValidationTiming::OnSignal;
readyUpStep.signalSource = this; readyUpStep.signalHook = [this](TutorialController *controller) {
readyUpStep.signalName = SIGNAL(localPlayerReadyStateChanged(bool)); return connect(this, &TabGame::localPlayerReadyStateChanged, controller, &TutorialController::checkValidation);
};
readyUpStep.validator = [] { return true; }; readyUpStep.validator = [] { return true; };
deckSelectSequence.addStep(readyUpStep); deckSelectSequence.addStep(readyUpStep);
@ -256,10 +259,12 @@ void TabGame::finishTutorialInitialization()
lifeCounterStep.allowClickThrough = true; lifeCounterStep.allowClickThrough = true;
lifeCounterStep.autoAdvanceOnValid = true; lifeCounterStep.autoAdvanceOnValid = true;
lifeCounterStep.validationTiming = ValidationTiming::OnSignal; lifeCounterStep.validationTiming = ValidationTiming::OnSignal;
lifeCounterStep.signalSource = game->getPlayerManager() lifeCounterStep.signalHook = [this](TutorialController *controller) {
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId()) return connect(game->getPlayerManager()
->getPlayerEventHandler(); ->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
lifeCounterStep.signalName = SIGNAL(logSetCounter(PlayerLogic *, QString, int, int)); ->getPlayerEventHandler(),
&PlayerEventHandler::logSetCounter, controller, &TutorialController::checkValidation);
};
lifeCounterStep.validator = [this] { lifeCounterStep.validator = [this] {
auto counters = auto counters =
game->getPlayerManager()->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())->getCounters(); game->getPlayerManager()->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())->getCounters();
@ -283,10 +288,12 @@ void TabGame::finishTutorialInitialization()
diceRollStep.allowClickThrough = true; diceRollStep.allowClickThrough = true;
diceRollStep.autoAdvanceOnValid = true; diceRollStep.autoAdvanceOnValid = true;
diceRollStep.validationTiming = ValidationTiming::OnSignal; diceRollStep.validationTiming = ValidationTiming::OnSignal;
diceRollStep.signalSource = game->getPlayerManager() diceRollStep.signalHook = [this](TutorialController *controller) {
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId()) return connect(game->getPlayerManager()
->getPlayerEventHandler(); ->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
diceRollStep.signalName = SIGNAL(logRollDie(PlayerLogic *, int, const QList<uint> &)); ->getPlayerEventHandler(),
&PlayerEventHandler::logRollDie, controller, &TutorialController::checkValidation);
};
diceRollStep.validator = [] { return true; }; diceRollStep.validator = [] { return true; };
diceRollStep.validationHint = tr("Roll a dice using any of these methods."); diceRollStep.validationHint = tr("Roll a dice using any of these methods.");
@ -302,10 +309,12 @@ void TabGame::finishTutorialInitialization()
mulliganStep.allowClickThrough = true; mulliganStep.allowClickThrough = true;
mulliganStep.autoAdvanceOnValid = true; mulliganStep.autoAdvanceOnValid = true;
mulliganStep.validationTiming = ValidationTiming::OnSignal; mulliganStep.validationTiming = ValidationTiming::OnSignal;
mulliganStep.signalSource = game->getPlayerManager() mulliganStep.signalHook = [this](TutorialController *controller) {
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId()) return connect(game->getPlayerManager()
->getPlayerEventHandler(); ->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
mulliganStep.signalName = SIGNAL(logDrawCards(PlayerLogic *, int, bool)); ->getPlayerEventHandler(),
&PlayerEventHandler::logDrawCards, controller, &TutorialController::checkValidation);
};
mulliganStep.validator = [this] { mulliganStep.validator = [this] {
return game->getPlayerManager() return game->getPlayerManager()
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId()) ->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())

View file

@ -156,8 +156,9 @@ TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
setFilterStep.requiresInteraction = true; setFilterStep.requiresInteraction = true;
setFilterStep.autoAdvanceOnValid = true; setFilterStep.autoAdvanceOnValid = true;
setFilterStep.validationTiming = ValidationTiming::OnSignal; setFilterStep.validationTiming = ValidationTiming::OnSignal;
setFilterStep.signalSource = filterModel; setFilterStep.signalHook = [this](TutorialController *controller) {
setFilterStep.signalName = SIGNAL(layoutChanged()); return connect(filterModel, &FilterTreeModel::layoutChanged, controller, &TutorialController::checkValidation);
};
setFilterStep.validator = [] { return true; }; setFilterStep.validator = [] { return true; };
sequence.addStep(setFilterStep); sequence.addStep(setFilterStep);
@ -177,9 +178,11 @@ TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
deckEditor->deckStateManager->clearDeck(); deckEditor->deckStateManager->clearDeck();
} }
}; };
explorationStep.signalSource = auto deckModel = QtUtils::findParentOfType<TabDeckEditorVisual>(this)->deckStateManager->getModel();
QtUtils::findParentOfType<TabDeckEditorVisual>(this)->deckStateManager->getModel(); explorationStep.signalHook = [deckModel](TutorialController *controller) {
explorationStep.signalName = SIGNAL(cardNodesChanged()); return connect(deckModel, &DeckListModel::cardNodesChanged, controller,
&TutorialController::checkValidation);
};
explorationStep.validator = [this] { explorationStep.validator = [this] {
if (QtUtils::findParentOfType<TabDeckEditorVisual>(this)) { if (QtUtils::findParentOfType<TabDeckEditorVisual>(this)) {
return QtUtils::findParentOfType<TabDeckEditorVisual>(this) return QtUtils::findParentOfType<TabDeckEditorVisual>(this)

View file

@ -145,8 +145,9 @@ TutorialSequence VisualDeckDisplayOptionsWidget::generateTutorialSequence(Tutori
displayTypeStep.allowClickThrough = true; displayTypeStep.allowClickThrough = true;
displayTypeStep.requiresInteraction = true; displayTypeStep.requiresInteraction = true;
displayTypeStep.validationTiming = ValidationTiming::OnSignal; displayTypeStep.validationTiming = ValidationTiming::OnSignal;
displayTypeStep.signalSource = displayTypeButton; displayTypeStep.signalHook = [this](TutorialController *controller) {
displayTypeStep.signalName = SIGNAL(clicked()); return connect(displayTypeButton, &QAbstractButton::clicked, controller, &TutorialController::checkValidation);
};
displayTypeStep.autoAdvanceOnValid = true; displayTypeStep.autoAdvanceOnValid = true;
displayTypeStep.validator = [] { return true; }; displayTypeStep.validator = [] { return true; };
@ -175,8 +176,10 @@ TutorialSequence VisualDeckDisplayOptionsWidget::generateTutorialSequence(Tutori
sortStep.requiresInteraction = true; sortStep.requiresInteraction = true;
sortStep.autoAdvanceOnValid = true; sortStep.autoAdvanceOnValid = true;
sortStep.validationTiming = ValidationTiming::OnSignal; sortStep.validationTiming = ValidationTiming::OnSignal;
sortStep.signalSource = this; sortStep.signalHook = [this](TutorialController *controller) {
sortStep.signalName = SIGNAL(sortCriteriaChanged(const QStringList &)); return connect(this, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, controller,
&TutorialController::checkValidation);
};
sortStep.validator = []() { return true; }; sortStep.validator = []() { return true; };
sequence.addStep(sortStep); sequence.addStep(sortStep);

View file

@ -495,8 +495,9 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
addStep.requiresInteraction = true; addStep.requiresInteraction = true;
addStep.autoAdvanceOnValid = true; addStep.autoAdvanceOnValid = true;
addStep.validationTiming = ValidationTiming::OnSignal; addStep.validationTiming = ValidationTiming::OnSignal;
addStep.signalSource = deckListModel; addStep.signalHook = [this](TutorialController *controller) {
addStep.signalName = SIGNAL(cardAddedAt(const QModelIndex &)); return connect(deckListModel, &DeckListModel::cardAddedAt, controller, &TutorialController::checkValidation);
};
addStep.validator = [this]() { return deckListModel->getCardNodes().size() >= 1; }; addStep.validator = [this]() { return deckListModel->getCardNodes().size() >= 1; };
sequence.addStep(addStep); sequence.addStep(addStep);