mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-01-13 13:37:36 -08:00
Compare commits
7 Commits
tooomm-dox
...
first-run-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b718c28dd5 | ||
|
|
b20d0bcb4f | ||
|
|
2c2c2a639b | ||
|
|
f7fe686634 | ||
|
|
6d44b00260 | ||
|
|
fdb547c0e2 | ||
|
|
fe61f5c7d4 |
@@ -197,6 +197,9 @@ set(cockatrice_SOURCES
|
|||||||
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
src/interface/widgets/general/layout_containers/flow_widget.cpp
|
||||||
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
|
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
|
||||||
src/interface/widgets/general/layout_containers/overlap_widget.cpp
|
src/interface/widgets/general/layout_containers/overlap_widget.cpp
|
||||||
|
src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp
|
||||||
|
src/interface/widgets/general/tutorial/tutorial_controller.cpp
|
||||||
|
src/interface/widgets/general/tutorial/tutorial_overlay.cpp
|
||||||
src/interface/widgets/menus/deck_editor_menu.cpp
|
src/interface/widgets/menus/deck_editor_menu.cpp
|
||||||
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
||||||
src/interface/widgets/printing_selector/card_amount_widget.cpp
|
src/interface/widgets/printing_selector/card_amount_widget.cpp
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "../../window_main.h"
|
#include "../../window_main.h"
|
||||||
#include "background_sources.h"
|
#include "background_sources.h"
|
||||||
#include "home_styled_button.h"
|
#include "home_styled_button.h"
|
||||||
|
#include "tutorial/tutorial_controller.h"
|
||||||
|
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
@@ -44,6 +45,30 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
|||||||
&HomeWidget::initializeBackgroundFromSource);
|
&HomeWidget::initializeBackgroundFromSource);
|
||||||
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this,
|
connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this,
|
||||||
&HomeWidget::onBackgroundShuffleFrequencyChanged);
|
&HomeWidget::onBackgroundShuffleFrequencyChanged);
|
||||||
|
|
||||||
|
tutorialController = new TutorialController(this);
|
||||||
|
auto sequence = TutorialSequence();
|
||||||
|
sequence.addStep({connectButton, "Connect to a server to play here!"});
|
||||||
|
sequence.addStep({visualDeckEditorButton, "Create a new deck from cards in the database here!"});
|
||||||
|
sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."});
|
||||||
|
sequence.addStep({visualDatabaseDisplayButton, "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."});
|
||||||
|
tutorialController->addSequence(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeWidget::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
if (!tutorialStarted) {
|
||||||
|
tutorialStarted = true;
|
||||||
|
// Start on next event loop iteration so everything is fully painted
|
||||||
|
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeWidget::initializeBackgroundFromSource()
|
void HomeWidget::initializeBackgroundFromSource()
|
||||||
@@ -184,29 +209,29 @@ QGroupBox *HomeWidget::createButtons()
|
|||||||
connectButton = new HomeStyledButton("Connect/Play", gradientColors);
|
connectButton = new HomeStyledButton("Connect/Play", gradientColors);
|
||||||
boxLayout->addWidget(connectButton);
|
boxLayout->addWidget(connectButton);
|
||||||
|
|
||||||
auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
|
visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
|
||||||
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
|
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,
|
||||||
[this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); });
|
[this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); });
|
||||||
boxLayout->addWidget(visualDeckEditorButton);
|
boxLayout->addWidget(visualDeckEditorButton);
|
||||||
auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
|
visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors);
|
||||||
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,
|
connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor,
|
||||||
[this] { tabSupervisor->actTabVisualDeckStorage(true); });
|
[this] { tabSupervisor->actTabVisualDeckStorage(true); });
|
||||||
boxLayout->addWidget(visualDeckStorageButton);
|
boxLayout->addWidget(visualDeckStorageButton);
|
||||||
auto visualDatabaseDisplayButton = new HomeStyledButton(tr("Browse Card Database"), gradientColors);
|
visualDatabaseDisplayButton = new HomeStyledButton(tr("Browse Card Database"), gradientColors);
|
||||||
connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor,
|
connect(visualDatabaseDisplayButton, &QPushButton::clicked, tabSupervisor,
|
||||||
&TabSupervisor::addVisualDatabaseDisplayTab);
|
&TabSupervisor::addVisualDatabaseDisplayTab);
|
||||||
boxLayout->addWidget(visualDatabaseDisplayButton);
|
boxLayout->addWidget(visualDatabaseDisplayButton);
|
||||||
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
|
||||||
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
|
||||||
boxLayout->addWidget(edhrecButton);
|
boxLayout->addWidget(edhrecButton);
|
||||||
auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
|
archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
|
||||||
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
|
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
|
||||||
boxLayout->addWidget(archidektButton);
|
boxLayout->addWidget(archidektButton);
|
||||||
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
|
||||||
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
|
||||||
boxLayout->addWidget(replaybutton);
|
boxLayout->addWidget(replaybutton);
|
||||||
if (qobject_cast<MainWindow *>(tabSupervisor->parentWidget())) {
|
if (qobject_cast<MainWindow *>(tabSupervisor->parentWidget())) {
|
||||||
auto exitButton = new HomeStyledButton(tr("Quit"), gradientColors);
|
exitButton = new HomeStyledButton(tr("Quit"), gradientColors);
|
||||||
connect(exitButton, &QPushButton::clicked, qobject_cast<MainWindow *>(tabSupervisor->parentWidget()),
|
connect(exitButton, &QPushButton::clicked, qobject_cast<MainWindow *>(tabSupervisor->parentWidget()),
|
||||||
&MainWindow::actExit);
|
&MainWindow::actExit);
|
||||||
boxLayout->addWidget(exitButton);
|
boxLayout->addWidget(exitButton);
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ public:
|
|||||||
HomeWidget(QWidget *parent, TabSupervisor *tabSupervisor);
|
HomeWidget(QWidget *parent, TabSupervisor *tabSupervisor);
|
||||||
void updateRandomCard();
|
void updateRandomCard();
|
||||||
QPair<QColor, QColor> extractDominantColors(const QPixmap &pixmap);
|
QPair<QColor, QColor> extractDominantColors(const QPixmap &pixmap);
|
||||||
|
HomeStyledButton *connectButton;
|
||||||
|
HomeStyledButton *visualDeckEditorButton;
|
||||||
|
HomeStyledButton *visualDeckStorageButton;
|
||||||
|
HomeStyledButton *visualDatabaseDisplayButton;
|
||||||
|
HomeStyledButton *edhrecButton;
|
||||||
|
HomeStyledButton *archidektButton;
|
||||||
|
HomeStyledButton *replaybutton;
|
||||||
|
HomeStyledButton *exitButton;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
void initializeBackgroundFromSource();
|
void initializeBackgroundFromSource();
|
||||||
void onBackgroundShuffleFrequencyChanged();
|
void onBackgroundShuffleFrequencyChanged();
|
||||||
void updateBackgroundProperties();
|
void updateBackgroundProperties();
|
||||||
@@ -39,11 +48,12 @@ private:
|
|||||||
QTimer *cardChangeTimer;
|
QTimer *cardChangeTimer;
|
||||||
TabSupervisor *tabSupervisor;
|
TabSupervisor *tabSupervisor;
|
||||||
QPixmap background;
|
QPixmap background;
|
||||||
|
TutorialController *tutorialController;
|
||||||
|
bool tutorialStarted = false;
|
||||||
CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr;
|
CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr;
|
||||||
DeckList backgroundSourceDeck;
|
DeckList backgroundSourceDeck;
|
||||||
QPixmap overlay;
|
QPixmap overlay;
|
||||||
QPair<QColor, QColor> gradientColors;
|
QPair<QColor, QColor> gradientColors;
|
||||||
HomeStyledButton *connectButton;
|
|
||||||
|
|
||||||
void loadBackgroundSourceDeck();
|
void loadBackgroundSourceDeck();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "tutorial_bubble_widget.h"
|
||||||
|
|
||||||
|
BubbleWidget::BubbleWidget(QWidget *parent) : QFrame(parent)
|
||||||
|
{
|
||||||
|
setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
|
||||||
|
setStyleSheet("QFrame { background:white; border-radius:8px; }"
|
||||||
|
"QLabel { color:black; }");
|
||||||
|
|
||||||
|
QGridLayout *layout = new QGridLayout(this);
|
||||||
|
layout->setContentsMargins(12, 10, 12, 10);
|
||||||
|
layout->setHorizontalSpacing(8);
|
||||||
|
layout->setVerticalSpacing(8);
|
||||||
|
|
||||||
|
counterLabel = new QLabel(this);
|
||||||
|
counterLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||||
|
|
||||||
|
textLabel = new QLabel(this);
|
||||||
|
textLabel->setWordWrap(true);
|
||||||
|
textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
textLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||||
|
textLabel->setStyleSheet("color:black;"); // guard against global styles
|
||||||
|
|
||||||
|
// Layout
|
||||||
|
layout->addWidget(counterLabel, 0, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 1);
|
||||||
|
layout->addWidget(textLabel, 1, 0, 1, 3);
|
||||||
|
|
||||||
|
// Make column 1 take extra space so text gets room to expand/wrap
|
||||||
|
layout->setColumnStretch(1, 1);
|
||||||
|
|
||||||
|
// sensible default maximum width for bubble so text will wrap
|
||||||
|
setMaximumWidth(420);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BubbleWidget::setText(const QString &text)
|
||||||
|
{
|
||||||
|
textLabel->setText(text);
|
||||||
|
update();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||||
|
#define COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
class BubbleWidget : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QLabel *textLabel;
|
||||||
|
QLabel *counterLabel;
|
||||||
|
|
||||||
|
BubbleWidget(QWidget *parent);
|
||||||
|
void setText(const QString &text);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#include "tutorial_controller.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
TutorialController::TutorialController(QWidget *_tutorializedWidget)
|
||||||
|
: QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget)
|
||||||
|
{
|
||||||
|
tutorialOverlay = new TutorialOverlay(tutorializedWidget->window());
|
||||||
|
|
||||||
|
// Make it frameless + translucent
|
||||||
|
tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint);
|
||||||
|
tutorialOverlay->setAttribute(Qt::WA_TranslucentBackground);
|
||||||
|
|
||||||
|
// hide until start
|
||||||
|
tutorialOverlay->hide();
|
||||||
|
|
||||||
|
connect(tutorialOverlay, &TutorialOverlay::nextStep, this, &TutorialController::nextStep);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialController::addSequence(const TutorialSequence &seq)
|
||||||
|
{
|
||||||
|
sequences.append(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialController::start()
|
||||||
|
{
|
||||||
|
if (sequences.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialController::prevStep()
|
||||||
|
{
|
||||||
|
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
|
||||||
|
currentStep = 0;
|
||||||
|
showStep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSequence--;
|
||||||
|
currentStep = 0;
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
// bounds checks
|
||||||
|
if (currentSequence < 0 || currentSequence >= sequences.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &seq = sequences[currentSequence];
|
||||||
|
if (currentStep < 0 || currentStep >= seq.steps.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (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();
|
||||||
|
}
|
||||||
|
|
||||||
|
tutorialOverlay->setTargetWidget(step.targetWidget);
|
||||||
|
tutorialOverlay->setText(step.text);
|
||||||
|
tutorialOverlay->parentResized();
|
||||||
|
tutorialOverlay->raise();
|
||||||
|
tutorialOverlay->update();
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||||
|
#define COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||||
|
|
||||||
|
#include "tutorial_overlay.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
struct TutorialStep
|
||||||
|
{
|
||||||
|
QWidget *targetWidget;
|
||||||
|
QString text;
|
||||||
|
std::function<void()> onEnter = nullptr; // Optional function to run when this step starts
|
||||||
|
std::function<void()> onExit = nullptr; // Optional function to run when step ends
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TutorialSequence
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QVector<TutorialStep> steps;
|
||||||
|
|
||||||
|
void addStep(const TutorialStep &step)
|
||||||
|
{
|
||||||
|
steps.append(step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TutorialController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void start();
|
||||||
|
void nextStep();
|
||||||
|
void prevStep();
|
||||||
|
void nextSequence();
|
||||||
|
void prevSequence();
|
||||||
|
void exitTutorial();
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TutorialController(QWidget *_tutorializedWidget);
|
||||||
|
|
||||||
|
void addSequence(const TutorialSequence &step);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget *tutorializedWidget;
|
||||||
|
QVector<TutorialSequence> sequences;
|
||||||
|
int currentSequence = -1;
|
||||||
|
int currentStep = -1;
|
||||||
|
|
||||||
|
TutorialOverlay *tutorialOverlay;
|
||||||
|
|
||||||
|
void showStep();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_TUTORIAL_CONTROLLER_H
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
#include "tutorial_overlay.h"
|
||||||
|
|
||||||
|
#include "tutorial_bubble_widget.h"
|
||||||
|
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPainterPath>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
|
||||||
|
TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window)
|
||||||
|
{
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||||
|
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent->installEventFilter(this);
|
||||||
|
parentResized();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Control bar -------------------------------------------------
|
||||||
|
controlBar = new QFrame(this);
|
||||||
|
controlBar->setStyleSheet(
|
||||||
|
"QFrame { background: rgba(30,30,30,200); border-radius: 6px; }"
|
||||||
|
"QPushButton { padding: 6px 10px; border: 1px solid #aaa; border-radius: 4px; background:#f5f5f5; }"
|
||||||
|
"QPushButton:hover { background:#eaeaea; }");
|
||||||
|
|
||||||
|
QHBoxLayout *barLayout = new QHBoxLayout(controlBar);
|
||||||
|
barLayout->setContentsMargins(8, 4, 8, 4);
|
||||||
|
|
||||||
|
titleLabel = new QLabel("Tutorial", controlBar);
|
||||||
|
titleLabel->setStyleSheet("color:white; font-weight:bold;");
|
||||||
|
barLayout->addWidget(titleLabel);
|
||||||
|
barLayout->addStretch();
|
||||||
|
|
||||||
|
auto mkBtn = [&](const QString &t, const QString &tip) {
|
||||||
|
QPushButton *b = new QPushButton(t, controlBar);
|
||||||
|
b->setToolTip(tip);
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
|
QPushButton *prevSeq = mkBtn("⏮", "Previous chapter");
|
||||||
|
QPushButton *prev = mkBtn("◀", "Previous step");
|
||||||
|
QPushButton *next = mkBtn("▶", "Next step");
|
||||||
|
QPushButton *nextSeq = mkBtn("⏭", "Next chapter");
|
||||||
|
QPushButton *close = mkBtn("✕", "Exit tutorial");
|
||||||
|
|
||||||
|
barLayout->addWidget(prevSeq);
|
||||||
|
barLayout->addWidget(prev);
|
||||||
|
barLayout->addWidget(next);
|
||||||
|
barLayout->addWidget(nextSeq);
|
||||||
|
barLayout->addWidget(close);
|
||||||
|
|
||||||
|
connect(prev, &QPushButton::clicked, this, &TutorialOverlay::prevStep);
|
||||||
|
connect(next, &QPushButton::clicked, this, &TutorialOverlay::nextStep);
|
||||||
|
connect(prevSeq, &QPushButton::clicked, this, &TutorialOverlay::prevSequence);
|
||||||
|
connect(nextSeq, &QPushButton::clicked, this, &TutorialOverlay::nextSequence);
|
||||||
|
connect(close, &QPushButton::clicked, this, &TutorialOverlay::skipTutorial);
|
||||||
|
|
||||||
|
// ---- Bubble ------------------------------------------------------
|
||||||
|
bubble = new BubbleWidget(this);
|
||||||
|
bubble->hide();
|
||||||
|
|
||||||
|
controlBar->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
|
||||||
|
// Queue geometry correction after the window is fully shown
|
||||||
|
QMetaObject::invokeMethod(this, [this]() { parentResized(); }, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::setTitle(const QString &title)
|
||||||
|
{
|
||||||
|
titleLabel->setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::setBlocking(bool block)
|
||||||
|
{
|
||||||
|
blockInput = block;
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents, !blockInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::setTargetWidget(QWidget *w)
|
||||||
|
{
|
||||||
|
if (targetWidget)
|
||||||
|
targetWidget->removeEventFilter(this);
|
||||||
|
|
||||||
|
targetWidget = w;
|
||||||
|
|
||||||
|
if (targetWidget)
|
||||||
|
targetWidget->installEventFilter(this);
|
||||||
|
|
||||||
|
recomputeLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::setText(const QString &t)
|
||||||
|
{
|
||||||
|
tutorialText = t;
|
||||||
|
bubble->setText(t);
|
||||||
|
bubble->adjustSize();
|
||||||
|
recomputeLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect TutorialOverlay::currentHoleRect() const
|
||||||
|
{
|
||||||
|
if (!targetWidget || !targetWidget->isVisible())
|
||||||
|
return QRect();
|
||||||
|
|
||||||
|
QPoint globalTopLeft = targetWidget->mapToGlobal(QPoint(0, 0));
|
||||||
|
QPoint localTopLeft = mapFromGlobal(globalTopLeft);
|
||||||
|
return QRect(localTopLeft, targetWidget->size()).adjusted(-6, -6, 6, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::resizeEvent(QResizeEvent *)
|
||||||
|
{
|
||||||
|
recomputeLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TutorialOverlay::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if (obj == parentWidget() && (event->type() == QEvent::Resize || event->type() == QEvent::Move)) {
|
||||||
|
parentResized();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == targetWidget) {
|
||||||
|
if (event->type() == QEvent::Show) {
|
||||||
|
// Defer layout recalculation to give Qt time to finalize geometry
|
||||||
|
QMetaObject::invokeMethod(this, [this]() { recomputeLayout(); }, Qt::QueuedConnection);
|
||||||
|
} else if (event->type() == QEvent::Hide || event->type() == QEvent::Move || event->type() == QEvent::Resize) {
|
||||||
|
recomputeLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QWidget::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::parentResized()
|
||||||
|
{
|
||||||
|
if (!parentWidget())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QWidget *w = parentWidget();
|
||||||
|
|
||||||
|
// Get the parent rect in local coordinates
|
||||||
|
QRect r = w->rect();
|
||||||
|
|
||||||
|
// Map top-left to global coordinates
|
||||||
|
QPoint globalTopLeft = w->mapToGlobal(QPoint(0, 0));
|
||||||
|
|
||||||
|
// Set overlay geometry in screen coords, exactly matching the parent widget
|
||||||
|
r.moveTopLeft(globalTopLeft);
|
||||||
|
setGeometry(r);
|
||||||
|
|
||||||
|
recomputeLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::recomputeLayout()
|
||||||
|
{
|
||||||
|
QRect hole = currentHoleRect();
|
||||||
|
|
||||||
|
if (hole.isEmpty()) {
|
||||||
|
if (bubble) {
|
||||||
|
bubble->hide();
|
||||||
|
}
|
||||||
|
if (controlBar) {
|
||||||
|
controlBar->hide();
|
||||||
|
}
|
||||||
|
hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
show();
|
||||||
|
raise();
|
||||||
|
|
||||||
|
// ---- Bubble ----
|
||||||
|
QSize bsize = bubble->sizeHint().expandedTo(QSize(160, 60));
|
||||||
|
highlightBubbleRect = computeBubbleRect(hole, bsize);
|
||||||
|
bubble->setGeometry(highlightBubbleRect);
|
||||||
|
bubble->show();
|
||||||
|
bubble->raise();
|
||||||
|
|
||||||
|
// ---- Control bar ----
|
||||||
|
controlBar->adjustSize();
|
||||||
|
controlBar->show();
|
||||||
|
|
||||||
|
const int margin = 8;
|
||||||
|
QRect r = rect();
|
||||||
|
|
||||||
|
QList<QPoint> positions = {{r.right() - controlBar->width() - margin, r.bottom() - controlBar->height() - margin},
|
||||||
|
{r.right() - controlBar->width() - margin, margin},
|
||||||
|
{margin, r.bottom() - controlBar->height() - margin},
|
||||||
|
{margin, margin}};
|
||||||
|
|
||||||
|
for (const QPoint &pos : positions) {
|
||||||
|
QRect proposed(pos, controlBar->size());
|
||||||
|
if (!proposed.intersects(hole)) {
|
||||||
|
controlBar->move(pos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controlBar->raise();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect TutorialOverlay::computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const
|
||||||
|
{
|
||||||
|
const int margin = 16;
|
||||||
|
QRect r = rect();
|
||||||
|
QRect bubble;
|
||||||
|
|
||||||
|
if (hole.isEmpty()) {
|
||||||
|
bubble = QRect(r.center() - QPoint(bubbleSize.width() / 2, bubbleSize.height() / 2), bubbleSize);
|
||||||
|
} else {
|
||||||
|
bubble = QRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height());
|
||||||
|
|
||||||
|
if (!r.contains(bubble))
|
||||||
|
bubble.moveLeft(hole.left() - margin - bubbleSize.width());
|
||||||
|
|
||||||
|
if (!r.contains(bubble)) {
|
||||||
|
bubble.moveLeft(hole.center().x() - bubbleSize.width() / 2);
|
||||||
|
bubble.moveTop(hole.top() - margin - bubbleSize.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.contains(bubble))
|
||||||
|
bubble.moveTop(hole.bottom() + margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final clamp to overlay bounds - ensure min <= max for qBound
|
||||||
|
int maxLeft = qMax(r.left(), r.right() - bubble.width());
|
||||||
|
int maxTop = qMax(r.top(), r.bottom() - bubble.height());
|
||||||
|
|
||||||
|
bubble.moveLeft(qBound(r.left(), bubble.left(), maxLeft));
|
||||||
|
bubble.moveTop(qBound(r.top(), bubble.top(), maxTop));
|
||||||
|
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TutorialOverlay::paintEvent(QPaintEvent *)
|
||||||
|
{
|
||||||
|
QPainter p(this);
|
||||||
|
p.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
p.fillRect(rect(), QColor(0, 0, 0, 160));
|
||||||
|
|
||||||
|
QRect hole = currentHoleRect();
|
||||||
|
if (!hole.isEmpty()) {
|
||||||
|
QPainterPath holePath;
|
||||||
|
holePath.addRoundedRect(hole, 8, 8);
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||||
|
p.fillPath(holePath, Qt::transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef COCKATRICE_TUTORIAL_OVERLAY_H
|
||||||
|
#define COCKATRICE_TUTORIAL_OVERLAY_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class BubbleWidget;
|
||||||
|
class QLabel;
|
||||||
|
class QFrame;
|
||||||
|
|
||||||
|
class TutorialOverlay : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public slots:
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TutorialOverlay(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setTargetWidget(QWidget *w);
|
||||||
|
void updateHoleRect();
|
||||||
|
void setText(const QString &t);
|
||||||
|
QRect currentHoleRect() const;
|
||||||
|
void setTitle(const QString &title);
|
||||||
|
void setBlocking(bool blockInput);
|
||||||
|
|
||||||
|
void parentResized();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void nextStep();
|
||||||
|
void prevStep();
|
||||||
|
void nextSequence();
|
||||||
|
void prevSequence();
|
||||||
|
void skipTutorial();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
void resizeEvent(QResizeEvent *) override;
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const;
|
||||||
|
void recomputeLayout();
|
||||||
|
|
||||||
|
QWidget *targetWidget = nullptr;
|
||||||
|
|
||||||
|
BubbleWidget *bubble = nullptr;
|
||||||
|
QFrame *controlBar = nullptr;
|
||||||
|
|
||||||
|
QLabel *titleLabel = nullptr;
|
||||||
|
|
||||||
|
QString tutorialText;
|
||||||
|
|
||||||
|
QRect cachedHoleRect;
|
||||||
|
QRect highlightBubbleRect;
|
||||||
|
|
||||||
|
bool blockInput = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_TUTORIAL_OVERLAY_H
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "../../interface/pixel_map_generator.h"
|
#include "../../interface/pixel_map_generator.h"
|
||||||
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
#include "../../interface/widgets/cards/card_info_frame_widget.h"
|
||||||
#include "../../interface/widgets/deck_analytics/deck_analytics_widget.h"
|
#include "../../interface/widgets/deck_analytics/deck_analytics_widget.h"
|
||||||
|
#include "../../interface/widgets/general/tutorial/tutorial_controller.h"
|
||||||
#include "../../interface/widgets/visual_deck_editor/visual_deck_editor_widget.h"
|
#include "../../interface/widgets/visual_deck_editor/visual_deck_editor_widget.h"
|
||||||
#include "../tab_deck_editor.h"
|
#include "../tab_deck_editor.h"
|
||||||
#include "../tab_supervisor.h"
|
#include "../tab_supervisor.h"
|
||||||
@@ -50,7 +51,31 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra
|
|||||||
refreshShortcuts();
|
refreshShortcuts();
|
||||||
|
|
||||||
loadLayout();
|
loadLayout();
|
||||||
cardDatabaseDockWidget->setHidden(true);
|
cardDatabaseDockWidget->setHidden(true); tutorialController = new TutorialController(this);
|
||||||
|
|
||||||
|
auto sequence = TutorialSequence();
|
||||||
|
|
||||||
|
sequence.addStep({tabContainer->visualDeckView, "View your deck here.",
|
||||||
|
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }});
|
||||||
|
sequence.addStep({printingSelectorDockWidget, "Change the printings in your deck here."});
|
||||||
|
|
||||||
|
tutorialController->addSequence(sequence);
|
||||||
|
|
||||||
|
auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps();
|
||||||
|
vddSequence.steps.prepend({tabContainer->visualDatabaseDisplay, "View the database here",
|
||||||
|
[this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }});
|
||||||
|
|
||||||
|
tutorialController->addSequence(vddSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabDeckEditorVisual::showEvent(QShowEvent *ev)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(ev);
|
||||||
|
if (!tutorialStarted) {
|
||||||
|
tutorialStarted = true;
|
||||||
|
// Start on next event loop iteration so everything is fully painted
|
||||||
|
QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Creates the central frame containing the tab container. */
|
/** @brief Creates the central frame containing the tab container. */
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../tab.h"
|
#include "../tab.h"
|
||||||
#include "tab_deck_editor_visual_tab_widget.h"
|
#include "tab_deck_editor_visual_tab_widget.h"
|
||||||
|
|
||||||
|
class TutorialController;
|
||||||
/**
|
/**
|
||||||
* @class TabDeckEditorVisual
|
* @class TabDeckEditorVisual
|
||||||
* @ingroup DeckEditorTabs
|
* @ingroup DeckEditorTabs
|
||||||
@@ -55,7 +56,12 @@ class TabDeckEditorVisual : public AbstractTabDeckEditor
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
TutorialController *tutorialController = nullptr;
|
||||||
|
bool tutorialStarted = false;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
void showEvent(QShowEvent *ev) override;
|
||||||
/**
|
/**
|
||||||
* @brief Load the editor layout from settings.
|
* @brief Load the editor layout from settings.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "../../../filters/syntax_help.h"
|
#include "../../../filters/syntax_help.h"
|
||||||
#include "../../pixel_map_generator.h"
|
#include "../../pixel_map_generator.h"
|
||||||
#include "../cards/card_info_picture_with_text_overlay_widget.h"
|
#include "../cards/card_info_picture_with_text_overlay_widget.h"
|
||||||
|
#include "../general/tutorial/tutorial_controller.h"
|
||||||
#include "../quick_settings/settings_button_widget.h"
|
#include "../quick_settings/settings_button_widget.h"
|
||||||
#include "../utility/custom_line_edit.h"
|
#include "../utility/custom_line_edit.h"
|
||||||
#include "visual_database_display_color_filter_widget.h"
|
#include "visual_database_display_color_filter_widget.h"
|
||||||
@@ -194,6 +195,28 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||||||
retranslateUi();
|
retranslateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps()
|
||||||
|
{
|
||||||
|
auto sequence = TutorialSequence();
|
||||||
|
sequence.addStep({colorFilterWidget, "Filter the database by colors with these controls"});
|
||||||
|
sequence.addStep({displayModeButton, "You can change back to the old table display-style with this button."});
|
||||||
|
sequence.addStep({filterContainer, "Use these controls for quick access to common filters."});
|
||||||
|
sequence.addStep(
|
||||||
|
{quickFilterSaveLoadWidget, "This button will let you save and load all currently applied filters to files."});
|
||||||
|
sequence.addStep({quickFilterNameWidget,
|
||||||
|
"This button will let you apply name filters. Optionally, you can import every card in "
|
||||||
|
"your deck as a name filter and then save this as a filter using the save/load button "
|
||||||
|
"to make your own quick access collections!"});
|
||||||
|
sequence.addStep({mainTypeFilterWidget, "Use these buttons to quickly filter by card types."});
|
||||||
|
sequence.addStep({quickFilterSubTypeWidget, "This button will let you apply filters for card sub-types."});
|
||||||
|
sequence.addStep(
|
||||||
|
{quickFilterSetWidget,
|
||||||
|
"This button will let you apply filters for card sets. You can also filter to the X most recent sets. "
|
||||||
|
"Filtering to a set will display all printings of a card within that set."});
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
void VisualDatabaseDisplayWidget::initialize()
|
void VisualDatabaseDisplayWidget::initialize()
|
||||||
{
|
{
|
||||||
databaseLoadIndicator->setVisible(false);
|
databaseLoadIndicator->setVisible(false);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "../cards/card_size_widget.h"
|
#include "../cards/card_size_widget.h"
|
||||||
#include "../general/layout_containers/flow_widget.h"
|
#include "../general/layout_containers/flow_widget.h"
|
||||||
#include "../general/layout_containers/overlap_control_widget.h"
|
#include "../general/layout_containers/overlap_control_widget.h"
|
||||||
|
#include "../general/tutorial/tutorial_controller.h"
|
||||||
#include "../utility/custom_line_edit.h"
|
#include "../utility/custom_line_edit.h"
|
||||||
#include "visual_database_display_color_filter_widget.h"
|
#include "visual_database_display_color_filter_widget.h"
|
||||||
#include "visual_database_display_filter_save_load_widget.h"
|
#include "visual_database_display_filter_save_load_widget.h"
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
#include <qscrollarea.h>
|
#include <qscrollarea.h>
|
||||||
|
|
||||||
|
class TutorialController;
|
||||||
inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display");
|
inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display");
|
||||||
|
|
||||||
class VisualDatabaseDisplayWidget : public QWidget
|
class VisualDatabaseDisplayWidget : public QWidget
|
||||||
@@ -60,6 +62,7 @@ public:
|
|||||||
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
|
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
TutorialSequence addTutorialSteps();
|
||||||
void searchModelChanged();
|
void searchModelChanged();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
Reference in New Issue
Block a user