mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-11 12:33:25 -08:00
Address review findings
This commit is contained in:
parent
178d392361
commit
7c90211247
13 changed files with 229 additions and 67 deletions
|
|
@ -169,10 +169,28 @@ SettingsCache::SettingsCache()
|
||||||
cardsDisplaySettings = new CardsDisplaySettings(settingsPath, this);
|
cardsDisplaySettings = new CardsDisplaySettings(settingsPath, this);
|
||||||
interfaceSettings = new InterfaceSettings(settingsPath, this);
|
interfaceSettings = new InterfaceSettings(settingsPath, this);
|
||||||
if (hadExistingSettings) {
|
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);
|
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);
|
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);
|
deckEditorSettings = new DeckEditorSettings(settingsPath, this);
|
||||||
pathsSettings = new PathsSettings(settingsPath, this);
|
pathsSettings = new PathsSettings(settingsPath, this);
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,13 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||||
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundShuffleFrequencyChanged,
|
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundShuffleFrequencyChanged,
|
||||||
this, &HomeWidget::onBackgroundShuffleFrequencyChanged);
|
this, &HomeWidget::onBackgroundShuffleFrequencyChanged);
|
||||||
|
|
||||||
tutorialController = new TutorialController(this);
|
tutorialController = new TutorialController(TutorialId::Home, this);
|
||||||
auto sequence = TutorialSequence();
|
auto sequence = TutorialSequence();
|
||||||
sequence.addStep({connectButton, "Connect to a server to play here!"});
|
sequence.addStep({connectButton, tr("Connect to a server to play here!")});
|
||||||
auto vdeStep = TutorialStep{visualDeckEditorButton, "Create a new deck from cards in the database here!"};
|
auto vdeStep = TutorialStep{visualDeckEditorButton, tr("Create a new deck from cards in the database here!")};
|
||||||
vdeStep.requiresInteraction = true;
|
vdeStep.requiresInteraction = true;
|
||||||
vdeStep.allowClickThrough = 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.validationTiming = ValidationTiming::OnSignal;
|
||||||
vdeStep.autoAdvanceOnValid = true;
|
vdeStep.autoAdvanceOnValid = true;
|
||||||
vdeStep.validator = []() { return true; };
|
vdeStep.validator = []() { return true; };
|
||||||
|
|
@ -114,14 +114,14 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||||
};
|
};
|
||||||
|
|
||||||
sequence.addStep(vdeStep);
|
sequence.addStep(vdeStep);
|
||||||
sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."});
|
sequence.addStep({visualDeckStorageButton, tr("Browse the decks in your local collection.")});
|
||||||
sequence.addStep({visualDatabaseDisplayButton, "View the card database here."});
|
sequence.addStep({visualDatabaseDisplayButton, tr("View the card database here.")});
|
||||||
sequence.addStep(
|
sequence.addStep(
|
||||||
{edhrecButton, "Browse EDHRec, an external service designed to provide card recommendations for decks."});
|
{edhrecButton, tr("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 "
|
sequence.addStep({archidektButton, tr("Browse Archidekt, an external service that allows users to store "
|
||||||
"decklists and import them to your local collection."});
|
"decklists and import them to your local collection.")});
|
||||||
sequence.addStep({replayButton, "View replays of your past games here."});
|
sequence.addStep({replayButton, tr("View replays of your past games here.")});
|
||||||
sequence.addStep({exitButton, "Exit the application."});
|
sequence.addStep({exitButton, tr("Exit the application.")});
|
||||||
tutorialController->addSequence(sequence);
|
tutorialController->addSequence(sequence);
|
||||||
|
|
||||||
// Lambda is cleaner to read than overloading this
|
// Lambda is cleaner to read than overloading this
|
||||||
|
|
@ -171,7 +171,7 @@ void HomeWidget::retranslateUi()
|
||||||
void HomeWidget::showEvent(QShowEvent *event)
|
void HomeWidget::showEvent(QShowEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::showEvent(event);
|
QWidget::showEvent(event);
|
||||||
if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) {
|
if (!tutorialStarted && !SettingsCache::instance().userInterface().getHomeTutorialCompleted()) {
|
||||||
tutorialStarted = true;
|
tutorialStarted = true;
|
||||||
// Start on next event loop iteration so everything is fully painted
|
// Start on next event loop iteration so everything is fully painted
|
||||||
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });
|
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,10 @@ void BubbleWidget::setText(const QString &text)
|
||||||
void BubbleWidget::setProgress(int stepNum, int totalSteps, int overallStep, int overallTotal)
|
void BubbleWidget::setProgress(int stepNum, int totalSteps, int overallStep, int overallTotal)
|
||||||
{
|
{
|
||||||
// Per-sequence progress
|
// 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
|
// 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();
|
progressLabel->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,21 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
|
|
||||||
TutorialController::TutorialController(QWidget *_tutorializedWidget)
|
namespace
|
||||||
: QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget)
|
|
||||||
{
|
{
|
||||||
|
/// 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 = new TutorialOverlay(tutorializedWidget->window());
|
||||||
|
|
||||||
tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint);
|
tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint);
|
||||||
|
|
@ -22,12 +34,14 @@ TutorialController::TutorialController(QWidget *_tutorializedWidget)
|
||||||
connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep);
|
connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep);
|
||||||
connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence);
|
connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence);
|
||||||
connect(tutorialOverlay, &TutorialOverlay::prevSequence, this, &TutorialController::prevSequence);
|
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);
|
connect(tutorialOverlay, &TutorialOverlay::targetClicked, this, &TutorialController::handleTargetClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
TutorialController::~TutorialController()
|
TutorialController::~TutorialController()
|
||||||
{
|
{
|
||||||
|
liveControllers.removeAll(this);
|
||||||
|
|
||||||
// The overlay is parented to the top-level window, which outlives the widget
|
// 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
|
// this controller is attached to (e.g. a closed tab), so it must be cleaned
|
||||||
// up explicitly when the controller goes away without exitTutorial().
|
// up explicitly when the controller goes away without exitTutorial().
|
||||||
|
|
@ -185,6 +199,9 @@ void TutorialController::prevSequence()
|
||||||
|
|
||||||
void TutorialController::exitTutorial()
|
void TutorialController::exitTutorial()
|
||||||
{
|
{
|
||||||
|
if (tutorialCompleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
cleanupValidationMonitoring();
|
cleanupValidationMonitoring();
|
||||||
if (tutorialOverlay) {
|
if (tutorialOverlay) {
|
||||||
tutorialOverlay->hide();
|
tutorialOverlay->hide();
|
||||||
|
|
@ -194,10 +211,46 @@ void TutorialController::exitTutorial()
|
||||||
currentSequence = -1;
|
currentSequence = -1;
|
||||||
currentStep = -1;
|
currentStep = -1;
|
||||||
tutorialCompleted = true;
|
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();
|
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()
|
void TutorialController::runExitForCurrentStep()
|
||||||
{
|
{
|
||||||
if (currentSequence < 0 || currentSequence >= sequences.size()) {
|
if (currentSequence < 0 || currentSequence >= sequences.size()) {
|
||||||
|
|
@ -269,7 +322,7 @@ void TutorialController::showStep()
|
||||||
if (!step.customInteractionHint.isEmpty()) {
|
if (!step.customInteractionHint.isEmpty()) {
|
||||||
tutorialOverlay->setInteractionHint(step.customInteractionHint);
|
tutorialOverlay->setInteractionHint(step.customInteractionHint);
|
||||||
} else if (step.requiresInteraction) {
|
} else if (step.requiresInteraction) {
|
||||||
tutorialOverlay->setInteractionHint("👆 Click the highlighted area to continue");
|
tutorialOverlay->setInteractionHint(tr("👆 Click the highlighted area to continue"));
|
||||||
} else {
|
} else {
|
||||||
tutorialOverlay->setInteractionHint("");
|
tutorialOverlay->setInteractionHint("");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@ enum class ValidationTiming
|
||||||
Manual // Only validate when explicitly triggered
|
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;
|
class TutorialController;
|
||||||
|
|
||||||
struct TutorialStep
|
struct TutorialStep
|
||||||
|
|
@ -67,7 +75,7 @@ class TutorialController : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TutorialController(QWidget *_tutorializedWidget);
|
TutorialController(TutorialId _tutorialId, QWidget *_tutorializedWidget);
|
||||||
~TutorialController() override;
|
~TutorialController() override;
|
||||||
|
|
||||||
void addSequence(const TutorialSequence &seq);
|
void addSequence(const TutorialSequence &seq);
|
||||||
|
|
@ -85,6 +93,7 @@ public slots:
|
||||||
void prevSequence();
|
void prevSequence();
|
||||||
void runExitForCurrentStep();
|
void runExitForCurrentStep();
|
||||||
void exitTutorial();
|
void exitTutorial();
|
||||||
|
void skipTutorials(); // Close the tutorial on every live screen and mark all three done
|
||||||
void handleTargetClicked(); // Handle clicks on highlighted widget
|
void handleTargetClicked(); // Handle clicks on highlighted widget
|
||||||
void attemptAdvance(); // Try to advance with validation
|
void attemptAdvance(); // Try to advance with validation
|
||||||
void checkValidation(); // Check validation for OnChange timing
|
void checkValidation(); // Check validation for OnChange timing
|
||||||
|
|
@ -96,6 +105,7 @@ private:
|
||||||
void setupValidationMonitoring(); // Setup automatic validation checking
|
void setupValidationMonitoring(); // Setup automatic validation checking
|
||||||
void cleanupValidationMonitoring(); // Cleanup validation watchers
|
void cleanupValidationMonitoring(); // Cleanup validation watchers
|
||||||
|
|
||||||
|
TutorialId tutorialId;
|
||||||
QWidget *tutorializedWidget;
|
QWidget *tutorializedWidget;
|
||||||
TutorialOverlay *tutorialOverlay;
|
TutorialOverlay *tutorialOverlay;
|
||||||
QList<TutorialSequence> sequences;
|
QList<TutorialSequence> sequences;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent)
|
||||||
QHBoxLayout *barLayout = new QHBoxLayout(controlBar);
|
QHBoxLayout *barLayout = new QHBoxLayout(controlBar);
|
||||||
barLayout->setContentsMargins(8, 4, 8, 4);
|
barLayout->setContentsMargins(8, 4, 8, 4);
|
||||||
|
|
||||||
titleLabel = new QLabel("Tutorial", controlBar);
|
titleLabel = new QLabel(tr("Tutorial"), controlBar);
|
||||||
titleLabel->setStyleSheet("color:white; font-weight:bold;");
|
titleLabel->setStyleSheet("color:white; font-weight:bold;");
|
||||||
barLayout->addWidget(titleLabel);
|
barLayout->addWidget(titleLabel);
|
||||||
barLayout->addStretch();
|
barLayout->addStretch();
|
||||||
|
|
@ -52,11 +52,11 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent)
|
||||||
return b;
|
return b;
|
||||||
};
|
};
|
||||||
|
|
||||||
QPushButton *prevSeq = mkBtn("⏮", "Previous chapter");
|
QPushButton *prevSeq = mkBtn("⏮", tr("Previous chapter"));
|
||||||
QPushButton *prev = mkBtn("◀", "Previous step");
|
QPushButton *prev = mkBtn("◀", tr("Previous step"));
|
||||||
nextButton = mkBtn("▶", "Next step");
|
nextButton = mkBtn("▶", tr("Next step"));
|
||||||
nextSeqButton = mkBtn("⏭", "Next chapter");
|
nextSeqButton = mkBtn("⏭", tr("Next chapter"));
|
||||||
QPushButton *close = mkBtn("✕", "Exit tutorial");
|
QPushButton *close = mkBtn("✕", tr("Exit tutorial"));
|
||||||
|
|
||||||
barLayout->addWidget(prevSeq);
|
barLayout->addWidget(prevSeq);
|
||||||
barLayout->addWidget(prev);
|
barLayout->addWidget(prev);
|
||||||
|
|
@ -121,12 +121,12 @@ void TutorialOverlay::setInteractive(bool interactive, bool clickThrough)
|
||||||
|
|
||||||
if (nextButton) {
|
if (nextButton) {
|
||||||
nextButton->setEnabled(!interactive);
|
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) {
|
if (nextSeqButton) {
|
||||||
nextSeqButton->setEnabled(!interactive);
|
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();
|
updateMask();
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||||
|
|
||||||
QTimer::singleShot(0, this, &TabGame::loadLayout);
|
QTimer::singleShot(0, this, &TabGame::loadLayout);
|
||||||
|
|
||||||
tutorialController = new TutorialController(this);
|
tutorialController = new TutorialController(TutorialId::Game, this);
|
||||||
|
|
||||||
TutorialSequence lobbySequence;
|
TutorialSequence lobbySequence;
|
||||||
|
|
||||||
|
|
@ -202,7 +202,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||||
void TabGame::showEvent(QShowEvent *event)
|
void TabGame::showEvent(QShowEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::showEvent(event);
|
QWidget::showEvent(event);
|
||||||
if (!tutorialController || tutorialStarted || SettingsCache::instance().userInterface().getTutorialCompleted()) {
|
if (!tutorialController || tutorialStarted ||
|
||||||
|
SettingsCache::instance().userInterface().getGameTutorialCompleted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only start once all sequences are registered so advancing can't run past the
|
// 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.
|
// No sequence building (and no widget access) once the tutorial is done.
|
||||||
if (SettingsCache::instance().userInterface().getTutorialCompleted()) {
|
if (SettingsCache::instance().userInterface().getGameTutorialCompleted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra
|
||||||
loadLayout();
|
loadLayout();
|
||||||
filterDockWidget->setHidden(true);
|
filterDockWidget->setHidden(true);
|
||||||
cardDatabaseDockWidget->setHidden(true);
|
cardDatabaseDockWidget->setHidden(true);
|
||||||
tutorialController = new TutorialController(this);
|
tutorialController = new TutorialController(TutorialId::DeckEditor, this);
|
||||||
createTutorialSequences();
|
createTutorialSequences();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,31 +88,31 @@ void TabDeckEditorVisual::createTutorialSequences()
|
||||||
|
|
||||||
auto sequence = TutorialSequence();
|
auto sequence = TutorialSequence();
|
||||||
|
|
||||||
sequence.addStep({tabContainer->tabBar(),
|
sequence.addStep(
|
||||||
"The Visual Deck Editor has multiple different functionalities.\n\nYou can cycle "
|
{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."});
|
"through them by using these tabs.\n\nLet's start with the Visual Deck View.")});
|
||||||
sequence.addStep({tabContainer->visualDeckView,
|
sequence.addStep({tabContainer->visualDeckView,
|
||||||
"The cards in your deck will be displayed here, allowing for an easy overview.\n\nLet's try "
|
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!",
|
"adding some now, so you can see it in action!"),
|
||||||
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }});
|
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }});
|
||||||
|
|
||||||
tutorialController->addSequence(sequence);
|
tutorialController->addSequence(sequence);
|
||||||
|
|
||||||
auto vdeSequence = tabContainer->visualDeckView->addTutorialSteps();
|
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);
|
tutorialController->addSequence(vdeSequence);
|
||||||
|
|
||||||
auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps();
|
auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps();
|
||||||
vddSequence.steps.prepend(
|
vddSequence.steps.prepend(
|
||||||
{tabContainer->visualDatabaseDisplay,
|
{tabContainer->visualDatabaseDisplay,
|
||||||
"You can view the database here, either as card images or in the old table display "
|
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!",
|
"style.\n\nAdditionally, there are many powerful and easy to use filters available.\n\nLet's dive in!"),
|
||||||
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }});
|
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }});
|
||||||
|
|
||||||
tutorialController->addSequence(vddSequence);
|
tutorialController->addSequence(vddSequence);
|
||||||
|
|
||||||
auto analyticsSequence = tabContainer->deckAnalytics->generateTutorialSequence();
|
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;
|
TutorialStep analyticsConclusionStep;
|
||||||
analyticsConclusionStep.targetWidget = tabContainer->tabBar();
|
analyticsConclusionStep.targetWidget = tabContainer->tabBar();
|
||||||
|
|
@ -150,7 +150,7 @@ void TabDeckEditorVisual::createTutorialSequences()
|
||||||
void TabDeckEditorVisual::showEvent(QShowEvent *ev)
|
void TabDeckEditorVisual::showEvent(QShowEvent *ev)
|
||||||
{
|
{
|
||||||
QWidget::showEvent(ev);
|
QWidget::showEvent(ev);
|
||||||
if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) {
|
if (!tutorialStarted && !SettingsCache::instance().userInterface().getDeckEditorTutorialCompleted()) {
|
||||||
tutorialStarted = true;
|
tutorialStarted = true;
|
||||||
// Start on next event loop iteration so everything is fully painted
|
// Start on next event loop iteration so everything is fully painted
|
||||||
QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); });
|
QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); });
|
||||||
|
|
|
||||||
|
|
@ -194,13 +194,13 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
||||||
TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
|
TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
|
||||||
{
|
{
|
||||||
auto sequence = TutorialSequence();
|
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;
|
TutorialStep displayModeStep;
|
||||||
displayModeStep.targetWidget = displayModeButton;
|
displayModeStep.targetWidget = displayModeButton;
|
||||||
displayModeStep.text = tr("You can change back to the old table display-style with this button.");
|
displayModeStep.text = tr("You can change back to the old table display-style with this button.");
|
||||||
displayModeStep.allowClickThrough = true;
|
displayModeStep.allowClickThrough = true;
|
||||||
sequence.addStep(displayModeStep);
|
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;
|
TutorialStep setFilterStep;
|
||||||
setFilterStep.targetWidget = filterContainer->getSetFilterWidget();
|
setFilterStep.targetWidget = filterContainer->getSetFilterWidget();
|
||||||
|
|
|
||||||
|
|
@ -464,20 +464,20 @@ void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected,
|
||||||
TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
|
TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
|
||||||
{
|
{
|
||||||
TutorialSequence sequence;
|
TutorialSequence sequence;
|
||||||
sequence.name = "Adding Cards to Your Deck";
|
sequence.name = tr("Adding Cards to Your Deck");
|
||||||
|
|
||||||
TutorialStep introStep;
|
TutorialStep introStep;
|
||||||
introStep.targetWidget = displayOptionsAndSearch;
|
introStep.targetWidget = displayOptionsAndSearch;
|
||||||
introStep.text = "There are two ways of adding cards to your deck:\n\n"
|
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"
|
"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 "
|
"This is helpful if you already know which card you would like to add "
|
||||||
"and will provide name suggestions as you type.\n\n"
|
"and will provide name suggestions as you type.\n\n"
|
||||||
"We'll look at the second way, through the database tab, later.";
|
"We'll look at the second way, through the database tab, later.");
|
||||||
sequence.addStep(introStep);
|
sequence.addStep(introStep);
|
||||||
|
|
||||||
TutorialStep searchStep;
|
TutorialStep searchStep;
|
||||||
searchStep.targetWidget = searchBar;
|
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.allowClickThrough = true;
|
||||||
searchStep.requiresInteraction = true;
|
searchStep.requiresInteraction = true;
|
||||||
searchStep.autoAdvanceOnValid = true;
|
searchStep.autoAdvanceOnValid = true;
|
||||||
|
|
@ -485,14 +485,14 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
|
||||||
searchStep.validator = [this]() {
|
searchStep.validator = [this]() {
|
||||||
return CardDatabaseManager::query()->getCard({searchBar->text()}) != ExactCard();
|
return CardDatabaseManager::query()->getCard({searchBar->text()}) != ExactCard();
|
||||||
};
|
};
|
||||||
searchStep.validationHint = "Please enter a valid card name.";
|
searchStep.validationHint = tr("Please enter a valid card name.");
|
||||||
searchStep.customInteractionHint = "✏️ Type a valid card name to continue";
|
searchStep.customInteractionHint = tr("✏️ Type a valid card name to continue");
|
||||||
|
|
||||||
sequence.addStep(searchStep);
|
sequence.addStep(searchStep);
|
||||||
|
|
||||||
TutorialStep addStep;
|
TutorialStep addStep;
|
||||||
addStep.targetWidget = searchPushButton;
|
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.allowClickThrough = true;
|
||||||
addStep.requiresInteraction = true;
|
addStep.requiresInteraction = true;
|
||||||
addStep.autoAdvanceOnValid = true;
|
addStep.autoAdvanceOnValid = true;
|
||||||
|
|
@ -506,8 +506,8 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
|
||||||
|
|
||||||
TutorialStep organizationStep;
|
TutorialStep organizationStep;
|
||||||
organizationStep.targetWidget = this;
|
organizationStep.targetWidget = this;
|
||||||
organizationStep.text = "Let's look at how cards are organized and displayed now.\n\nWe'll add some random cards "
|
organizationStep.text = tr("Let's look at how cards are organized and displayed now.\n\nWe'll add some random "
|
||||||
"from the database to your deck, so you can see it in action properly.";
|
"cards from the database to your deck, so you can see it in action properly.");
|
||||||
organizationStep.onExit = [this]() {
|
organizationStep.onExit = [this]() {
|
||||||
// getCardList() counts distinct card nodes, not copies, so once every random pick only bumps an
|
// 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
|
// 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;
|
TutorialStep hoverStep;
|
||||||
hoverStep.targetWidget = this;
|
hoverStep.targetWidget = this;
|
||||||
hoverStep.text = "Great! Take some time to explore these new cards in the current display mode.\n\nYou can select "
|
hoverStep.text = tr("Great! Take some time to explore these new cards in the current display mode.\n\nYou can "
|
||||||
"a card by clicking on it with the left mouse button.\nYou can select multiple cards by holding "
|
"select a card by clicking on it with the left mouse button.\nYou can select multiple cards "
|
||||||
"down CTRL or Shift.\nYou can clear the current selection by clicking on an area without a "
|
"by holding down CTRL or Shift.\nYou can clear the current selection by clicking on an area "
|
||||||
"card.\nDouble-clicking a card will move it between main and sideboard.\nRight-clicking a card "
|
"without a card.\nDouble-clicking a card will move it between main and sideboard.\n"
|
||||||
"will remove it from the deck.\n\nYou can hover over a card to see a zoomed version of it.";
|
"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;
|
hoverStep.allowClickThrough = true;
|
||||||
|
|
||||||
sequence.addStep(hoverStep);
|
sequence.addStep(hoverStep);
|
||||||
|
|
@ -538,9 +539,9 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps()
|
||||||
TutorialStep conclusionStep;
|
TutorialStep conclusionStep;
|
||||||
conclusionStep.targetWidget = this;
|
conclusionStep.targetWidget = this;
|
||||||
conclusionStep.text =
|
conclusionStep.text =
|
||||||
"Great!\n\nNow that you've learned about all the different ways of displaying the cards in "
|
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 "
|
"your deck, it's time to move on to searching for new cards for your deck in style and ease.\n\nYou can "
|
||||||
"on this screen to play around with the display options and advance when you are ready.";
|
"stay on this screen to play around with the display options and advance when you are ready.");
|
||||||
conclusionStep.allowClickThrough = true;
|
conclusionStep.allowClickThrough = true;
|
||||||
|
|
||||||
sequence.addStep(conclusionStep);
|
sequence.addStep(conclusionStep);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
#include "libcockatrice/utility/playmat_params.h"
|
#include "libcockatrice/utility/playmat_params.h"
|
||||||
|
|
||||||
#include <QChar>
|
#include <QChar>
|
||||||
#include <QDir>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
@ -253,6 +251,21 @@ bool InterfaceSettings::getTutorialCompleted() const
|
||||||
return getValue("tutorialCompleted", QString(), QString(), false).toBool();
|
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)
|
void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus)
|
||||||
{
|
{
|
||||||
setValue(_useTearOffMenus, "useTearOffMenus");
|
setValue(_useTearOffMenus, "useTearOffMenus");
|
||||||
|
|
@ -481,3 +494,18 @@ void InterfaceSettings::setTutorialCompleted(bool value)
|
||||||
{
|
{
|
||||||
setValue(value, "tutorialCompleted");
|
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");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ public:
|
||||||
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
|
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
|
||||||
[[nodiscard]] QStringList getUserListExpandedSections() const override;
|
[[nodiscard]] QStringList getUserListExpandedSections() const override;
|
||||||
[[nodiscard]] bool getTutorialCompleted() const;
|
[[nodiscard]] bool getTutorialCompleted() const;
|
||||||
|
[[nodiscard]] bool getHomeTutorialCompleted() const;
|
||||||
|
[[nodiscard]] bool getDeckEditorTutorialCompleted() const;
|
||||||
|
[[nodiscard]] bool getGameTutorialCompleted() const;
|
||||||
|
|
||||||
void setUseTearOffMenus(bool _useTearOffMenus);
|
void setUseTearOffMenus(bool _useTearOffMenus);
|
||||||
void setCardViewInitialRowsMax(int _cardViewInitialRowsMax);
|
void setCardViewInitialRowsMax(int _cardViewInitialRowsMax);
|
||||||
|
|
@ -98,6 +101,9 @@ public:
|
||||||
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
|
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
|
||||||
void setUserListExpandedSections(const QStringList §ions);
|
void setUserListExpandedSections(const QStringList §ions);
|
||||||
void setTutorialCompleted(bool value);
|
void setTutorialCompleted(bool value);
|
||||||
|
void setHomeTutorialCompleted(bool value);
|
||||||
|
void setDeckEditorTutorialCompleted(bool value);
|
||||||
|
void setGameTutorialCompleted(bool value);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void useTearOffMenusChanged(bool state);
|
void useTearOffMenusChanged(bool state);
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,51 @@ TEST_F(SettingsDefaultsTest, Interface_BuddyConnectNotificationsEnabled_Default)
|
||||||
ASSERT_EQ(s.getBuddyConnectNotificationsEnabled(), true);
|
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 ---
|
// --- DeckEditorSettings ---
|
||||||
|
|
||||||
TEST_F(SettingsDefaultsTest, DeckEditor_OpenDeckInNewTab_Default)
|
TEST_F(SettingsDefaultsTest, DeckEditor_OpenDeckInNewTab_Default)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue