[Tutorial] Fix review blockers: null VDS guard, overlay QPointer, bounded fill, upgrade seeding

This commit is contained in:
Lukas Brübach 2026-10-11 15:10:43 +02:00
parent 89ef10c862
commit bfc970b6a9
6 changed files with 33 additions and 5 deletions

View file

@ -112,7 +112,10 @@ TutorialSequence DeckViewContainer::generateTutorialSequence()
{this, tr("There are multiple ways to select a deck:\n\n- From a local file" {this, tr("There are multiple ways to select a deck:\n\n- From a local file"
"\n- From the contents of your clipboard\nFrom an external online service")}); "\n- From the contents of your clipboard\nFrom an external online service")});
visualDeckStorageWidget->generateTutorialSequence(deckViewContainerSequence); // The VDS widget only exists when "show visual deck storage in game" is on.
if (visualDeckStorageWidget) {
visualDeckStorageWidget->generateTutorialSequence(deckViewContainerSequence);
}
return deckViewContainerSequence; return deckViewContainerSequence;
} }

View file

@ -98,7 +98,7 @@ private:
QWidget *tutorializedWidget; QWidget *tutorializedWidget;
TutorialOverlay *tutorialOverlay; TutorialOverlay *tutorialOverlay;
QVector<TutorialSequence> sequences; QList<TutorialSequence> sequences;
bool tutorialCompleted = false; bool tutorialCompleted = false;

View file

@ -1,6 +1,7 @@
#ifndef TUTORIAL_OVERLAY_H #ifndef TUTORIAL_OVERLAY_H
#define TUTORIAL_OVERLAY_H #define TUTORIAL_OVERLAY_H
#include <QPointer>
#include <QWidget> #include <QWidget>
class QFrame; class QFrame;
@ -49,7 +50,9 @@ private:
void recomputeLayout(); void recomputeLayout();
QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const; QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const;
QWidget *targetWidget = nullptr; // QPointer: the target can be deleted while the overlay lives on (e.g. toggling
// the in-game VDS setting deleteLater()s it), so never hold a raw pointer across steps.
QPointer<QWidget> targetWidget;
QFrame *controlBar = nullptr; QFrame *controlBar = nullptr;
QLabel *titleLabel = nullptr; QLabel *titleLabel = nullptr;
QPushButton *nextButton = nullptr; QPushButton *nextButton = nullptr;

View file

@ -172,6 +172,11 @@ void TabGame::finishTutorialInitialization()
tutorialInitialized = true; tutorialInitialized = true;
} }
// No sequence building (and no widget access) once the tutorial is done.
if (SettingsCache::instance().userInterface().getTutorialCompleted()) {
return;
}
auto deckViewSequence = deckViewContainers.first()->generateTutorialSequence(); auto deckViewSequence = deckViewContainers.first()->generateTutorialSequence();
tutorialController->addSequence(deckViewSequence); tutorialController->addSequence(deckViewSequence);
@ -328,7 +333,7 @@ void TabGame::finishTutorialInitialization()
tutorialController->addSequence(gamePlaySequence); tutorialController->addSequence(gamePlaySequence);
if (tutorialStarted || SettingsCache::instance().userInterface().getTutorialCompleted()) { if (tutorialStarted) {
return; return;
} }
// The tab may have been shown before the local player joined; start now that // The tab may have been shown before the local player joined; start now that

View file

@ -507,8 +507,14 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
organizationStep.text = "Let's look at how cards are organized and displayed now.\n\nWe'll add some random cards " organizationStep.text = "Let's look at how cards are organized and displayed now.\n\nWe'll add some random cards "
"from the database to your deck, so you can see it in action properly."; "from the database to your deck, so you can see it in action properly.";
organizationStep.onExit = [this]() { organizationStep.onExit = [this]() {
while (deckListModel->getDeckList()->getCardList().size() < 60) { // getCardList() counts distinct card nodes, not copies, so once every random pick only bumps an
// existing node's count the size never grows. With fewer than ~60 distinct cards in the database
// this would loop forever, so bound the attempts to always terminate.
static constexpr int MaxFillAttempts = 600;
int attempts = 0;
while (deckListModel->getDeckList()->getCardList().size() < 60 && attempts < MaxFillAttempts) {
deckListModel->addCard(CardDatabaseManager::query()->getRandomCard(), DECK_ZONE_MAIN); deckListModel->addCard(CardDatabaseManager::query()->getRandomCard(), DECK_ZONE_MAIN);
++attempts;
} }
}; };

View file

@ -1,5 +1,8 @@
#include "interface_settings.h" #include "interface_settings.h"
#include <QDir>
#include <QFileInfo>
InterfaceSettings::InterfaceSettings(const QString &settingPath, QObject *parent) InterfaceSettings::InterfaceSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "interface.ini", "interface", QString(), parent) : SettingsManager(settingPath + "interface.ini", "interface", QString(), parent)
{ {
@ -157,6 +160,14 @@ bool InterfaceSettings::getShowGameSelectorFilterToolbar() const
bool InterfaceSettings::getTutorialCompleted() const bool InterfaceSettings::getTutorialCompleted() const
{ {
// Upgrading users have settings files but no tutorial key yet: treat the tutorial as seen so they are
// not overlaid on upgrade. Genuinely new installs have no settings files at all, so they still get it.
if (!getSettings().contains("interface/tutorialCompleted")) {
QDir settingsDir = QFileInfo(settingPath).dir();
if (!settingsDir.entryList({"*.ini"}, QDir::Files).isEmpty()) {
return true;
}
}
return getValue("tutorialCompleted", QString(), QString(), false).toBool(); return getValue("tutorialCompleted", QString(), QString(), false).toBool();
} }