[App] Add onboarding wizard (#7064)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions

* [App] Add onboarding wizard

Took 10 minutes

Took 3 minutes

Took 7 minutes

Took 9 minutes

Took 2 minutes

Took 1 minute


Took 7 minutes

* Adjust CI

Took 14 minutes

Took 56 seconds

Took 2 seconds

Took 3 seconds

* Adjust CI again

Took 14 minutes

Took 2 seconds

* Comments and fixes

Took 9 seconds


Took 1 minute

* Rebase.

Took 5 minutes

Took 50 seconds

Took 15 seconds

* Comments.

Took 7 minutes

* CI lol

Took 3 minutes

* CI again lol

Took 4 minutes

* Drop some settings, add some new ones.

Took 19 minutes

* Resize when expanding section

Took 4 minutes

Took 3 minutes

Took 7 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-24 23:05:58 +02:00 committed by GitHub
parent 815c5987b4
commit 2b7b4e8168
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 3207 additions and 336 deletions

View file

@ -0,0 +1,231 @@
#include "theme_setup_page.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/palette_editor/palette_generator.h"
#include "../../interface/palette_editor/quick_setup_panel.h"
#include "../../interface/theme_manager.h"
#include "../../interface/widgets/general/background_sources.h"
#include "libcockatrice/settings/appearance_settings.h"
#include <QComboBox>
#include <QDir>
#include <QFile>
#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMessageBox>
#include <QVBoxLayout>
#include <libcockatrice/settings/paths_settings.h>
#include <libcockatrice/settings/personal_settings.h>
ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent)
{
themeCombo = new QComboBox(this);
schemeCombo = new QComboBox(this);
schemeCombo->addItem(tr("Light"), QStringLiteral("Light"));
schemeCombo->addItem(tr("Dark"), QStringLiteral("Dark"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
schemeCombo->addItem(tr("Match system"), QStringLiteral("System"));
#endif
quickSetupPanel = new QuickSetupPanel(this);
connect(themeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onThemeChanged);
connect(schemeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onSchemeChanged);
connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &ThemeSetupPage::onGenerateFromAccent);
homeTabBackgroundCombo = new QComboBox(this);
for (const auto &entry : BackgroundSources::all()) {
homeTabBackgroundCombo->addItem(QObject::tr(entry.trKey), QVariant::fromValue(entry.type));
}
connect(homeTabBackgroundCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ThemeSetupPage::onHomeTabBackgroundChanged);
// Keep the scheme combo honest when the *theme* changes underneath it
// (switching theme reloads that theme's own stored colorScheme), and
// opportunistically seed a palette for themes that ship none at all.
// Mirrors AppearanceSettingsPage's identical listener for the combo-sync
// half of this.
connect(themeManager, &ThemeManager::themeChanged, this, [this] {
const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir);
const QString current = cfg.colorScheme;
schemeCombo->blockSignals(true);
const int idx = schemeCombo->findData(current);
schemeCombo->setCurrentIndex(idx >= 0 ? idx : 0);
schemeCombo->blockSignals(false);
maybeAutoGeneratePalette();
});
auto *form = new QFormLayout;
form->addRow(tr("Theme:"), themeCombo);
form->addRow(tr("Appearance:"), schemeCombo);
form->addRow(tr("Home screen background:"), homeTabBackgroundCombo);
accentGroup = new QGroupBox(this);
auto *accentLayout = new QVBoxLayout(accentGroup);
accentLayout->addWidget(quickSetupPanel);
auto *layout = new QVBoxLayout(this);
layout->addLayout(form);
layout->addWidget(accentGroup);
layout->addStretch();
retranslateUi();
}
void ThemeSetupPage::initializePage()
{
themeCombo->blockSignals(true);
themeCombo->clear();
const QString currentTheme = SettingsCache::instance().getThemeName();
for (const QString &name : themeManager->getAvailableThemes().keys()) {
themeCombo->addItem(name);
}
const int idx = themeCombo->findText(currentTheme);
themeCombo->setCurrentIndex(idx >= 0 ? idx : 0);
themeCombo->blockSignals(false);
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
const ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
schemeCombo->blockSignals(true);
const int schemeIdx = schemeCombo->findData(cfg.colorScheme);
schemeCombo->setCurrentIndex(schemeIdx >= 0 ? schemeIdx : 0);
schemeCombo->blockSignals(false);
homeTabBackgroundCombo->blockSignals(true);
QString homeTabSource = SettingsCache::instance().appearance().getHomeTabBackgroundSource();
int homeTabIdx = homeTabBackgroundCombo->findData(BackgroundSources::fromId(homeTabSource));
homeTabBackgroundCombo->setCurrentIndex(homeTabIdx >= 0 ? homeTabIdx : 0);
homeTabBackgroundCombo->blockSignals(false);
// Opening the page must not touch the running application's palette:
// previews and auto-generation only happen in response to the user
// actually changing a control, never on mere page visibility.
paletteDirty = false;
}
QString ThemeSetupPage::currentScheme() const
{
return schemeCombo->currentData().toString();
}
QString ThemeSetupPage::resolvedScheme() const
{
const QString scheme = currentScheme();
if (scheme.isEmpty() || scheme == QStringLiteral("System")) {
return themeManager->isDarkMode(themeManager->getCurrentThemePath()) ? "Dark" : "Light";
}
return scheme;
}
void ThemeSetupPage::onThemeChanged(int index)
{
if (index < 0) {
return;
}
paletteDirty = false;
SettingsCache::instance().setThemeName(themeCombo->itemText(index));
// Scheme-combo sync and auto-generation both happen via the
// ThemeManager::themeChanged listener above, triggered by setThemeName.
}
void ThemeSetupPage::onSchemeChanged()
{
themeManager->setColorScheme(currentScheme());
}
void ThemeSetupPage::onHomeTabBackgroundChanged(int index)
{
if (index < 0) {
return;
}
auto type = homeTabBackgroundCombo->currentData().value<BackgroundSources::Type>();
SettingsCache::instance().appearance().setHomeTabBackgroundSource(BackgroundSources::toId(type));
}
void ThemeSetupPage::onGenerateFromAccent(const QColor &accent, int intensity)
{
PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, resolvedScheme());
themeManager->previewPalette(cfg, resolvedScheme());
paletteDirty = true;
}
void ThemeSetupPage::maybeAutoGeneratePalette()
{
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
const QString scheme = resolvedScheme();
if (PaletteConfig::fromScheme(dirPath, scheme).hasPalette() ||
PaletteConfig::fromDefault(dirPath, scheme).hasPalette()) {
return; // theme already has something real to show -- leave it alone
}
// The theme+scheme combination has nothing saved and nothing shipped, and
// the user just switched to it. Rather than leaving a flat, unstyled look,
// seed one from whatever accent QuickSetupPanel currently holds and mark
// it dirty so it's written to disk if the user moves on. Only ever reached
// through user interaction (theme/scheme change, accent drag) -- never on
// page open.
PaletteConfig generated =
PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme);
themeManager->previewPalette(generated, scheme);
paletteDirty = true;
}
bool ThemeSetupPage::validatePage()
{
if (paletteDirty) {
const QString scheme = resolvedScheme();
PaletteConfig cfg =
PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme);
if (!ThemeManager::commitPalette(writableThemeDir(), scheme, cfg)) {
QMessageBox::warning(this, tr("Save failed"),
tr("Could not write the theme palette to:\n%1").arg(writableThemeDir()));
return false;
}
themeManager->reloadCurrentTheme();
}
return true;
}
QString ThemeSetupPage::writableThemeDir() const
{
// Built-in themes resolve to the read-only system themes directory;
// palette edits must go to the user themes directory instead, exactly
// as PaletteEditorDialog does.
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
if (!dirPath.isEmpty()) {
const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
QFile f(probe);
if (f.open(QIODevice::WriteOnly)) {
f.close();
f.remove();
return dirPath;
}
}
return QDir(SettingsCache::instance().paths().getThemesPath())
.absoluteFilePath(SettingsCache::instance().getThemeName());
}
bool ThemeSetupPage::isSkippable() const
{
return true;
}
QString ThemeSetupPage::stepTitle() const
{
return tr("Pick a Look");
}
QString ThemeSetupPage::stepSubtitle() const
{
return tr("You can fine-tune every colour later from Settings → Appearance.");
}
void ThemeSetupPage::retranslateUi()
{
accentGroup->setTitle(tr("Accent colour (optional)"));
}