Address review findings

This commit is contained in:
Lukas Brübach 2026-11-08 14:43:20 +01:00
parent 178d392361
commit 7c90211247
13 changed files with 229 additions and 67 deletions

View file

@ -169,10 +169,28 @@ SettingsCache::SettingsCache()
cardsDisplaySettings = new CardsDisplaySettings(settingsPath, this);
interfaceSettings = new InterfaceSettings(settingsPath, this);
if (hadExistingSettings) {
// Installs that predate the tutorial have no tutorial keys at all. Seed each
// per-tutorial key from the legacy flag, but never overwrite a key that is already
// there. Fresh installs keep the false defaults so every tutorial runs.
QSettings iface(settingsPath + "interface.ini", QSettings::IniFormat);
if (!iface.contains("interface/tutorialCompleted")) {
const bool hasLegacyCompleted = iface.contains("interface/tutorialCompleted");
const bool hasHomeCompleted = iface.contains("interface/tutorialHomeCompleted");
const bool hasDeckEditorCompleted = iface.contains("interface/tutorialDeckEditorCompleted");
const bool hasGameCompleted = iface.contains("interface/tutorialGameCompleted");
if (!hasLegacyCompleted) {
// No legacy key means an install from before the tutorial existed: treat it as done.
interfaceSettings->setTutorialCompleted(true);
}
const bool legacyCompleted = interfaceSettings->getTutorialCompleted();
if (!hasHomeCompleted) {
interfaceSettings->setHomeTutorialCompleted(legacyCompleted);
}
if (!hasDeckEditorCompleted) {
interfaceSettings->setDeckEditorTutorialCompleted(legacyCompleted);
}
if (!hasGameCompleted) {
interfaceSettings->setGameTutorialCompleted(legacyCompleted);
}
}
deckEditorSettings = new DeckEditorSettings(settingsPath, this);
pathsSettings = new PathsSettings(settingsPath, this);

View file

@ -98,13 +98,13 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundShuffleFrequencyChanged,
this, &HomeWidget::onBackgroundShuffleFrequencyChanged);
tutorialController = new TutorialController(this);
tutorialController = new TutorialController(TutorialId::Home, this);
auto sequence = TutorialSequence();
sequence.addStep({connectButton, "Connect to a server to play here!"});
auto vdeStep = TutorialStep{visualDeckEditorButton, "Create a new deck from cards in the database here!"};
sequence.addStep({connectButton, tr("Connect to a server to play here!")});
auto vdeStep = TutorialStep{visualDeckEditorButton, tr("Create a new deck from cards in the database here!")};
vdeStep.requiresInteraction = true;
vdeStep.allowClickThrough = true;
vdeStep.validationHint = "Open the deck editor to try it out!";
vdeStep.validationHint = tr("Open the deck editor to try it out!");
vdeStep.validationTiming = ValidationTiming::OnSignal;
vdeStep.autoAdvanceOnValid = true;
vdeStep.validator = []() { return true; };
@ -114,14 +114,14 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
};
sequence.addStep(vdeStep);
sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."});
sequence.addStep({visualDatabaseDisplayButton, "View the card database here."});
sequence.addStep({visualDeckStorageButton, tr("Browse the decks in your local collection.")});
sequence.addStep({visualDatabaseDisplayButton, tr("View the card database here.")});
sequence.addStep(
{edhrecButton, "Browse EDHRec, an external service designed to provide card recommendations for decks."});
sequence.addStep({archidektButton, "Browse Archidekt, an external service that allows users to store "
"decklists and import them to your local collection."});
sequence.addStep({replayButton, "View replays of your past games here."});
sequence.addStep({exitButton, "Exit the application."});
{edhrecButton, tr("Browse EDHRec, an external service designed to provide card recommendations for decks.")});
sequence.addStep({archidektButton, tr("Browse Archidekt, an external service that allows users to store "
"decklists and import them to your local collection.")});
sequence.addStep({replayButton, tr("View replays of your past games here.")});
sequence.addStep({exitButton, tr("Exit the application.")});
tutorialController->addSequence(sequence);
// Lambda is cleaner to read than overloading this
@ -171,7 +171,7 @@ void HomeWidget::retranslateUi()
void HomeWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) {
if (!tutorialStarted && !SettingsCache::instance().userInterface().getHomeTutorialCompleted()) {
tutorialStarted = true;
// Start on next event loop iteration so everything is fully painted
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });

View file

@ -68,10 +68,10 @@ void BubbleWidget::setText(const QString &text)
void BubbleWidget::setProgress(int stepNum, int totalSteps, int overallStep, int overallTotal)
{
// Per-sequence progress
counterLabel->setText(QString("Step %1 of %2").arg(stepNum).arg(totalSteps));
counterLabel->setText(tr("Step %1 of %2").arg(stepNum).arg(totalSteps));
// Overall progress across all sequences
progressLabel->setText(QString("(%1 of %2 total)").arg(overallStep).arg(overallTotal));
progressLabel->setText(tr("(%1 of %2 total)").arg(overallStep).arg(overallTotal));
progressLabel->show();
}

View file

@ -10,9 +10,21 @@
#include <QTimer>
#include <libcockatrice/settings/interface_settings.h>
TutorialController::TutorialController(QWidget *_tutorializedWidget)
: QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget)
namespace
{
/// Every controller that currently exists, across all screens, so the overlay's X can end
/// all of them at once. Only touched from the GUI thread, which creates and destroys them.
QList<TutorialController *> liveControllers;
/// True while skipTutorials() closes the controllers, so a nested call is a no-op.
bool skippingTutorials = false;
} // namespace
TutorialController::TutorialController(TutorialId _tutorialId, QWidget *_tutorializedWidget)
: QObject(_tutorializedWidget), tutorialId(_tutorialId), tutorializedWidget(_tutorializedWidget)
{
liveControllers.append(this);
tutorialOverlay = new TutorialOverlay(tutorializedWidget->window());
tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint);
@ -22,12 +34,14 @@ TutorialController::TutorialController(QWidget *_tutorializedWidget)
connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep);
connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence);
connect(tutorialOverlay, &TutorialOverlay::prevSequence, this, &TutorialController::prevSequence);
connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::exitTutorial);
connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::skipTutorials);
connect(tutorialOverlay, &TutorialOverlay::targetClicked, this, &TutorialController::handleTargetClicked);
}
TutorialController::~TutorialController()
{
liveControllers.removeAll(this);
// The overlay is parented to the top-level window, which outlives the widget
// this controller is attached to (e.g. a closed tab), so it must be cleaned
// up explicitly when the controller goes away without exitTutorial().
@ -185,6 +199,9 @@ void TutorialController::prevSequence()
void TutorialController::exitTutorial()
{
if (tutorialCompleted) {
return;
}
cleanupValidationMonitoring();
if (tutorialOverlay) {
tutorialOverlay->hide();
@ -194,10 +211,46 @@ void TutorialController::exitTutorial()
currentSequence = -1;
currentStep = -1;
tutorialCompleted = true;
SettingsCache::instance().userInterface().setTutorialCompleted(true);
// Only this screen's tutorial is done; the others keep their own state.
auto &iface = SettingsCache::instance().userInterface();
switch (tutorialId) {
case TutorialId::Home:
iface.setHomeTutorialCompleted(true);
break;
case TutorialId::DeckEditor:
iface.setDeckEditorTutorialCompleted(true);
break;
case TutorialId::Game:
iface.setGameTutorialCompleted(true);
break;
}
deleteLater();
}
void TutorialController::skipTutorials()
{
if (skippingTutorials) {
return;
}
skippingTutorials = true;
// The X means "I am done with the tutorial", not "close this one window", so every
// screen is marked done — including screens with no live controller right now.
auto &iface = SettingsCache::instance().userInterface();
iface.setHomeTutorialCompleted(true);
iface.setDeckEditorTutorialCompleted(true);
iface.setGameTutorialCompleted(true);
// exitTutorial() only schedules deletion, so iterating a copy cannot dangle.
const QList<TutorialController *> controllers = liveControllers;
for (TutorialController *controller : controllers) {
controller->exitTutorial();
}
skippingTutorials = false;
}
void TutorialController::runExitForCurrentStep()
{
if (currentSequence < 0 || currentSequence >= sequences.size()) {
@ -269,7 +322,7 @@ void TutorialController::showStep()
if (!step.customInteractionHint.isEmpty()) {
tutorialOverlay->setInteractionHint(step.customInteractionHint);
} else if (step.requiresInteraction) {
tutorialOverlay->setInteractionHint("👆 Click the highlighted area to continue");
tutorialOverlay->setInteractionHint(tr("👆 Click the highlighted area to continue"));
} else {
tutorialOverlay->setInteractionHint("");
}

View file

@ -16,6 +16,14 @@ enum class ValidationTiming
Manual // Only validate when explicitly triggered
};
/// Which screen a controller belongs to; each tutorial tracks its own completion flag.
enum class TutorialId
{
Home,
DeckEditor,
Game
};
class TutorialController;
struct TutorialStep
@ -67,7 +75,7 @@ class TutorialController : public QObject
Q_OBJECT
public:
explicit TutorialController(QWidget *_tutorializedWidget);
TutorialController(TutorialId _tutorialId, QWidget *_tutorializedWidget);
~TutorialController() override;
void addSequence(const TutorialSequence &seq);
@ -85,6 +93,7 @@ public slots:
void prevSequence();
void runExitForCurrentStep();
void exitTutorial();
void skipTutorials(); // Close the tutorial on every live screen and mark all three done
void handleTargetClicked(); // Handle clicks on highlighted widget
void attemptAdvance(); // Try to advance with validation
void checkValidation(); // Check validation for OnChange timing
@ -96,6 +105,7 @@ private:
void setupValidationMonitoring(); // Setup automatic validation checking
void cleanupValidationMonitoring(); // Cleanup validation watchers
TutorialId tutorialId;
QWidget *tutorializedWidget;
TutorialOverlay *tutorialOverlay;
QList<TutorialSequence> sequences;

View file

@ -41,7 +41,7 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent)
QHBoxLayout *barLayout = new QHBoxLayout(controlBar);
barLayout->setContentsMargins(8, 4, 8, 4);
titleLabel = new QLabel("Tutorial", controlBar);
titleLabel = new QLabel(tr("Tutorial"), controlBar);
titleLabel->setStyleSheet("color:white; font-weight:bold;");
barLayout->addWidget(titleLabel);
barLayout->addStretch();
@ -52,11 +52,11 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent)
return b;
};
QPushButton *prevSeq = mkBtn("⏮", "Previous chapter");
QPushButton *prev = mkBtn("◀", "Previous step");
nextButton = mkBtn("▶", "Next step");
nextSeqButton = mkBtn("⏭", "Next chapter");
QPushButton *close = mkBtn("✕", "Exit tutorial");
QPushButton *prevSeq = mkBtn("⏮", tr("Previous chapter"));
QPushButton *prev = mkBtn("◀", tr("Previous step"));
nextButton = mkBtn("▶", tr("Next step"));
nextSeqButton = mkBtn("⏭", tr("Next chapter"));
QPushButton *close = mkBtn("✕", tr("Exit tutorial"));
barLayout->addWidget(prevSeq);
barLayout->addWidget(prev);
@ -121,12 +121,12 @@ void TutorialOverlay::setInteractive(bool interactive, bool clickThrough)
if (nextButton) {
nextButton->setEnabled(!interactive);
nextButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next step");
nextButton->setToolTip(interactive ? tr("Complete the highlighted action to continue") : tr("Next step"));
}
if (nextSeqButton) {
nextSeqButton->setEnabled(!interactive);
nextSeqButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next chapter");
nextSeqButton->setToolTip(interactive ? tr("Complete the highlighted action to continue") : tr("Next chapter"));
}
updateMask();

View file

@ -189,7 +189,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
QTimer::singleShot(0, this, &TabGame::loadLayout);
tutorialController = new TutorialController(this);
tutorialController = new TutorialController(TutorialId::Game, this);
TutorialSequence lobbySequence;
@ -202,7 +202,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
void TabGame::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
if (!tutorialController || tutorialStarted || SettingsCache::instance().userInterface().getTutorialCompleted()) {
if (!tutorialController || tutorialStarted ||
SettingsCache::instance().userInterface().getGameTutorialCompleted()) {
return;
}
// Only start once all sequences are registered so advancing can't run past the
@ -223,7 +224,7 @@ void TabGame::finishTutorialInitialization()
}
// No sequence building (and no widget access) once the tutorial is done.
if (SettingsCache::instance().userInterface().getTutorialCompleted()) {
if (SettingsCache::instance().userInterface().getGameTutorialCompleted()) {
return;
}

View file

@ -76,7 +76,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra
loadLayout();
filterDockWidget->setHidden(true);
cardDatabaseDockWidget->setHidden(true);
tutorialController = new TutorialController(this);
tutorialController = new TutorialController(TutorialId::DeckEditor, this);
createTutorialSequences();
}
@ -88,31 +88,31 @@ void TabDeckEditorVisual::createTutorialSequences()
auto sequence = TutorialSequence();
sequence.addStep({tabContainer->tabBar(),
"The Visual Deck Editor has multiple different functionalities.\n\nYou can cycle "
"through them by using these tabs.\n\nLet's start with the Visual Deck View."});
sequence.addStep(
{tabContainer->tabBar(), tr("The Visual Deck Editor has multiple different functionalities.\n\nYou can cycle "
"through them by using these tabs.\n\nLet's start with the Visual Deck View.")});
sequence.addStep({tabContainer->visualDeckView,
"The cards in your deck will be displayed here, allowing for an easy overview.\n\nLet's try "
"adding some now, so you can see it in action!",
tr("The cards in your deck will be displayed here, allowing for an easy overview.\n\nLet's try "
"adding some now, so you can see it in action!"),
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }});
tutorialController->addSequence(sequence);
auto vdeSequence = tabContainer->visualDeckView->addTutorialSteps();
vdeSequence.addStep({tabContainer->tabBar(), "Let's look at the database tab now."});
vdeSequence.addStep({tabContainer->tabBar(), tr("Let's look at the database tab now.")});
tutorialController->addSequence(vdeSequence);
auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps();
vddSequence.steps.prepend(
{tabContainer->visualDatabaseDisplay,
"You can view the database here, either as card images or in the old table display "
"style.\n\nAdditionally, there are many powerful and easy to use filters available.\n\nLet's dive in!",
tr("You can view the database here, either as card images or in the old table display "
"style.\n\nAdditionally, there are many powerful and easy to use filters available.\n\nLet's dive in!"),
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }});
tutorialController->addSequence(vddSequence);
auto analyticsSequence = tabContainer->deckAnalytics->generateTutorialSequence();
analyticsSequence.steps.prepend({tabContainer->tabBar(), "Let's look at the analytics tab now."});
analyticsSequence.steps.prepend({tabContainer->tabBar(), tr("Let's look at the analytics tab now.")});
TutorialStep analyticsConclusionStep;
analyticsConclusionStep.targetWidget = tabContainer->tabBar();
@ -150,7 +150,7 @@ void TabDeckEditorVisual::createTutorialSequences()
void TabDeckEditorVisual::showEvent(QShowEvent *ev)
{
QWidget::showEvent(ev);
if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) {
if (!tutorialStarted && !SettingsCache::instance().userInterface().getDeckEditorTutorialCompleted()) {
tutorialStarted = true;
// Start on next event loop iteration so everything is fully painted
QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); });

View file

@ -194,13 +194,13 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
{
auto sequence = TutorialSequence();
sequence.addStep({colorFilterWidget, "Filter the database by colors with these controls"});
sequence.addStep({colorFilterWidget, tr("Filter the database by colors with these controls")});
TutorialStep displayModeStep;
displayModeStep.targetWidget = displayModeButton;
displayModeStep.text = tr("You can change back to the old table display-style with this button.");
displayModeStep.allowClickThrough = true;
sequence.addStep(displayModeStep);
sequence.addStep({filterContainer, "Use these controls for quick access to common filters."});
sequence.addStep({filterContainer, tr("Use these controls for quick access to common filters.")});
TutorialStep setFilterStep;
setFilterStep.targetWidget = filterContainer->getSetFilterWidget();

View file

@ -464,20 +464,20 @@ void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected,
TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
{
TutorialSequence sequence;
sequence.name = "Adding Cards to Your Deck";
sequence.name = tr("Adding Cards to Your Deck");
TutorialStep introStep;
introStep.targetWidget = displayOptionsAndSearch;
introStep.text = "There are two ways of adding cards to your deck:\n\n"
"The first is by using the quick search bar in the deck view tab.\n"
"This is helpful if you already know which card you would like to add "
"and will provide name suggestions as you type.\n\n"
"We'll look at the second way, through the database tab, later.";
introStep.text = tr("There are two ways of adding cards to your deck:\n\n"
"The first is by using the quick search bar in the deck view tab.\n"
"This is helpful if you already know which card you would like to add "
"and will provide name suggestions as you type.\n\n"
"We'll look at the second way, through the database tab, later.");
sequence.addStep(introStep);
TutorialStep searchStep;
searchStep.targetWidget = searchBar;
searchStep.text = "Let's try it out now!\nType the name of a card into the search bar.";
searchStep.text = tr("Let's try it out now!\nType the name of a card into the search bar.");
searchStep.allowClickThrough = true;
searchStep.requiresInteraction = true;
searchStep.autoAdvanceOnValid = true;
@ -485,14 +485,14 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
searchStep.validator = [this]() {
return CardDatabaseManager::query()->getCard({searchBar->text()}) != ExactCard();
};
searchStep.validationHint = "Please enter a valid card name.";
searchStep.customInteractionHint = "✏️ Type a valid card name to continue";
searchStep.validationHint = tr("Please enter a valid card name.");
searchStep.customInteractionHint = tr("✏️ Type a valid card name to continue");
sequence.addStep(searchStep);
TutorialStep addStep;
addStep.targetWidget = searchPushButton;
addStep.text = "Click this button to add the card to your deck.";
addStep.text = tr("Click this button to add the card to your deck.");
addStep.allowClickThrough = true;
addStep.requiresInteraction = true;
addStep.autoAdvanceOnValid = true;
@ -506,8 +506,8 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
TutorialStep organizationStep;
organizationStep.targetWidget = this;
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.";
organizationStep.text = tr("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.");
organizationStep.onExit = [this]() {
// 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
@ -524,11 +524,12 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
TutorialStep hoverStep;
hoverStep.targetWidget = this;
hoverStep.text = "Great! Take some time to explore these new cards in the current display mode.\n\nYou can select "
"a card by clicking on it with the left mouse button.\nYou can select multiple cards by holding "
"down CTRL or Shift.\nYou can clear the current selection by clicking on an area without a "
"card.\nDouble-clicking a card will move it between main and sideboard.\nRight-clicking a card "
"will remove it from the deck.\n\nYou can hover over a card to see a zoomed version of it.";
hoverStep.text = tr("Great! Take some time to explore these new cards in the current display mode.\n\nYou can "
"select a card by clicking on it with the left mouse button.\nYou can select multiple cards "
"by holding down CTRL or Shift.\nYou can clear the current selection by clicking on an area "
"without a card.\nDouble-clicking a card will move it between main and sideboard.\n"
"Right-clicking a card will remove it from the deck.\n\nYou can hover over a card to see a "
"zoomed version of it.");
hoverStep.allowClickThrough = true;
sequence.addStep(hoverStep);
@ -538,9 +539,9 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
TutorialStep conclusionStep;
conclusionStep.targetWidget = this;
conclusionStep.text =
"Great!\n\nNow that you've learned about all the different ways of displaying the cards in "
"your deck, it's time to move on to searching for new cards for your deck in style and ease.\n\nYou can stay "
"on this screen to play around with the display options and advance when you are ready.";
tr("Great!\n\nNow that you've learned about all the different ways of displaying the cards in "
"your deck, it's time to move on to searching for new cards for your deck in style and ease.\n\nYou can "
"stay on this screen to play around with the display options and advance when you are ready.");
conclusionStep.allowClickThrough = true;
sequence.addStep(conclusionStep);

View file

@ -5,8 +5,6 @@
#include "libcockatrice/utility/playmat_params.h"
#include <QChar>
#include <QDir>
#include <QFileInfo>
#include <QList>
#include <QVariant>
#include <QtGlobal>
@ -253,6 +251,21 @@ bool InterfaceSettings::getTutorialCompleted() const
return getValue("tutorialCompleted", QString(), QString(), false).toBool();
}
bool InterfaceSettings::getHomeTutorialCompleted() const
{
return getValue("tutorialHomeCompleted", QString(), QString(), false).toBool();
}
bool InterfaceSettings::getDeckEditorTutorialCompleted() const
{
return getValue("tutorialDeckEditorCompleted", QString(), QString(), false).toBool();
}
bool InterfaceSettings::getGameTutorialCompleted() const
{
return getValue("tutorialGameCompleted", QString(), QString(), false).toBool();
}
void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus)
{
setValue(_useTearOffMenus, "useTearOffMenus");
@ -481,3 +494,18 @@ void InterfaceSettings::setTutorialCompleted(bool value)
{
setValue(value, "tutorialCompleted");
}
void InterfaceSettings::setHomeTutorialCompleted(bool value)
{
setValue(value, "tutorialHomeCompleted");
}
void InterfaceSettings::setDeckEditorTutorialCompleted(bool value)
{
setValue(value, "tutorialDeckEditorCompleted");
}
void InterfaceSettings::setGameTutorialCompleted(bool value)
{
setValue(value, "tutorialGameCompleted");
}

View file

@ -58,6 +58,9 @@ public:
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
[[nodiscard]] QStringList getUserListExpandedSections() const override;
[[nodiscard]] bool getTutorialCompleted() const;
[[nodiscard]] bool getHomeTutorialCompleted() const;
[[nodiscard]] bool getDeckEditorTutorialCompleted() const;
[[nodiscard]] bool getGameTutorialCompleted() const;
void setUseTearOffMenus(bool _useTearOffMenus);
void setCardViewInitialRowsMax(int _cardViewInitialRowsMax);
@ -98,6 +101,9 @@ public:
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
void setUserListExpandedSections(const QStringList &sections);
void setTutorialCompleted(bool value);
void setHomeTutorialCompleted(bool value);
void setDeckEditorTutorialCompleted(bool value);
void setGameTutorialCompleted(bool value);
signals:
void useTearOffMenusChanged(bool state);

View file

@ -543,6 +543,51 @@ TEST_F(SettingsDefaultsTest, Interface_BuddyConnectNotificationsEnabled_Default)
ASSERT_EQ(s.getBuddyConnectNotificationsEnabled(), true);
}
TEST_F(SettingsDefaultsTest, Interface_HomeTutorialCompleted_Default)
{
InterfaceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getHomeTutorialCompleted(), false);
}
TEST_F(SettingsDefaultsTest, Interface_HomeTutorialCompleted_SetAndGet)
{
InterfaceSettings s(settingsPath, nullptr);
s.setHomeTutorialCompleted(true);
ASSERT_EQ(s.getHomeTutorialCompleted(), true);
s.setHomeTutorialCompleted(false);
ASSERT_EQ(s.getHomeTutorialCompleted(), false);
}
TEST_F(SettingsDefaultsTest, Interface_DeckEditorTutorialCompleted_Default)
{
InterfaceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getDeckEditorTutorialCompleted(), false);
}
TEST_F(SettingsDefaultsTest, Interface_DeckEditorTutorialCompleted_SetAndGet)
{
InterfaceSettings s(settingsPath, nullptr);
s.setDeckEditorTutorialCompleted(true);
ASSERT_EQ(s.getDeckEditorTutorialCompleted(), true);
s.setDeckEditorTutorialCompleted(false);
ASSERT_EQ(s.getDeckEditorTutorialCompleted(), false);
}
TEST_F(SettingsDefaultsTest, Interface_GameTutorialCompleted_Default)
{
InterfaceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getGameTutorialCompleted(), false);
}
TEST_F(SettingsDefaultsTest, Interface_GameTutorialCompleted_SetAndGet)
{
InterfaceSettings s(settingsPath, nullptr);
s.setGameTutorialCompleted(true);
ASSERT_EQ(s.getGameTutorialCompleted(), true);
s.setGameTutorialCompleted(false);
ASSERT_EQ(s.getGameTutorialCompleted(), false);
}
// --- DeckEditorSettings ---
TEST_F(SettingsDefaultsTest, DeckEditor_OpenDeckInNewTab_Default)