diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
index 1b9be1dd4..79875f0b0 100644
--- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
+++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
@@ -19,13 +19,15 @@
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
{
+ userThemeDirPath = ThemeManager::userThemeDirFor(themeName);
+
setMinimumSize(740, 220);
setupUi();
// Load both scheme configs upfront so switching is instant
loadSchemes();
- loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
+ loadedScheme = themeManager->isDarkMode(themeDirPath, userThemeDirPath) ? "Dark" : "Light";
schemeComboBox->blockSignals(true);
schemeComboBox->setCurrentText(loadedScheme);
@@ -33,7 +35,6 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
paletteGrid->loadPalette(workingConfig[loadedScheme]);
seedAccentFromScheme(loadedScheme);
-
retranslateUi();
}
@@ -157,20 +158,22 @@ void PaletteEditorDialog::setupUi()
});
}
+void PaletteEditorDialog::updateUserOverrideState()
+{
+ const bool hasUserOverride =
+ QFile::exists(QDir(userThemeDirPath).absoluteFilePath(PaletteConfig::fileName(loadedScheme)));
+
+ revertButton->setEnabled(hasUserOverride);
+ revertButton->setToolTip(hasUserOverride ? tr("Delete your custom palette and restore the shipped defaults")
+ : tr("No custom palette overrides exist for this scheme"));
+}
+
void PaletteEditorDialog::retranslateUi()
{
setWindowTitle(tr("Palette Editor — %1").arg(themeName));
titleLabel->setText(tr("Palette Editor · %1").arg(themeName));
- // Revert button only makes sense when the theme ships default palette files
- const bool hasDefault = PaletteConfig::fromDefault(themeDirPath, "Light").hasPalette() ||
- PaletteConfig::fromDefault(themeDirPath, "Dark").hasPalette();
- revertButton->setEnabled(hasDefault);
- if (!hasDefault) {
- revertButton->setToolTip(tr("This theme ships no default palette files"));
- } else {
- revertButton->setToolTip(tr("Replace current colours with the theme author's defaults"));
- }
+ updateUserOverrideState();
schemeComboBox->setToolTip(tr("Switch between the light and dark palette files"));
editingLabel->setText(tr("Editing:"));
@@ -195,8 +198,11 @@ void PaletteEditorDialog::loadSchemes()
{
const QStringList schemes = {"Light", "Dark"};
for (const QString &scheme : schemes) {
- PaletteConfig cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
-
+ // user customisation → system palette → system default → app palette
+ PaletteConfig cfg = PaletteConfig::fromScheme(userThemeDirPath, scheme);
+ if (!cfg.hasPalette()) {
+ cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
+ }
if (!cfg.hasPalette()) {
cfg = PaletteConfig::fromDefault(themeDirPath, scheme);
}
@@ -235,6 +241,9 @@ void PaletteEditorDialog::onSchemeChanged(const QString &scheme)
loadedScheme = scheme;
paletteGrid->loadPalette(workingConfig.value(scheme));
seedAccentFromScheme(scheme);
+
+ updateUserOverrideState();
+
onApply();
}
@@ -258,15 +267,17 @@ void PaletteEditorDialog::onSave()
PaletteConfig cfg = paletteGrid->currentPaletteConfig();
- if (!ThemeManager::savePaletteConfig(themeDirPath, loadedScheme, cfg)) {
- QMessageBox::warning(this, tr("Save failed"),
- tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), themeDirPath));
+ if (!ThemeManager::savePaletteConfig(userThemeDirPath, loadedScheme, cfg)) {
+ QMessageBox::warning(
+ this, tr("Save failed"),
+ tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), userThemeDirPath));
return;
}
- ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath);
+ // Record the active scheme in the user dir — never touch the system (read-only) dir
+ ThemeConfig globalCfg = themeManager->effectiveThemeConfig(themeName);
globalCfg.colorScheme = loadedScheme;
- globalCfg.save(themeDirPath);
+ globalCfg.save(userThemeDirPath);
savedConfig[loadedScheme] = cfg;
workingConfig[loadedScheme] = cfg;
@@ -282,14 +293,50 @@ void PaletteEditorDialog::onReset()
void PaletteEditorDialog::onRevertToDefault()
{
- PaletteConfig def = PaletteConfig::fromDefault(themeDirPath, loadedScheme);
- if (!def.hasPalette()) {
- QMessageBox::information(this, tr("No default found"),
- tr("No default palette file found for the \"%1\" scheme.").arg(loadedScheme));
+ const QString filePath = QDir(userThemeDirPath).absoluteFilePath(PaletteConfig::fileName(loadedScheme));
+
+ if (!QFile::exists(filePath)) {
+ // Button should already be disabled in this state, but be defensive
return;
}
+
+ const auto reply =
+ QMessageBox::question(this, tr("Revert to default?"),
+ tr("This will permanently delete your custom \"%1\" palette for \"%2\".\n\nContinue?")
+ .arg(loadedScheme, themeName),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+ if (reply != QMessageBox::Yes) {
+ return;
+ }
+
+ QFile::remove(filePath);
+
+ // Reload via fallthrough: system palette → system default → app palette
+ PaletteConfig def = PaletteConfig::fromScheme(themeDirPath, loadedScheme);
+ if (!def.hasPalette()) {
+ def = PaletteConfig::fromDefault(themeDirPath, loadedScheme);
+ }
+ if (!def.hasPalette()) {
+ const QPalette appPal = qApp->palette();
+ for (auto group : {QPalette::Active, QPalette::Disabled, QPalette::Inactive}) {
+ for (int i = 0; i < QPalette::NColorRoles; ++i) {
+ auto role = static_cast(i);
+ if (role != QPalette::NoRole) {
+ def.colors[group][role] = appPal.color(group, role);
+ }
+ }
+ }
+ }
+
+ savedConfig[loadedScheme] = def;
workingConfig[loadedScheme] = def;
- paletteGrid->loadPalette(def);
+
+ ThemeConfig globalCfg = themeManager->effectiveThemeConfig(themeName);
+ globalCfg.colorScheme = loadedScheme;
+ globalCfg.save(userThemeDirPath);
+
+ themeManager->reloadCurrentTheme();
+ accept();
}
void PaletteEditorDialog::changeEvent(QEvent *e)
diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h
index cec4c1700..024c2632a 100644
--- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h
+++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h
@@ -31,6 +31,7 @@ private slots:
private:
void setupUi();
+ void updateUserOverrideState();
void retranslateUi();
void refreshChromePalettes();
void loadScheme(const QString &scheme); // snapshot current, switch, load
@@ -54,6 +55,7 @@ private:
QPushButton *revertButton = nullptr;
// State
+ QString userThemeDirPath;
QString themeDirPath;
QString themeName;
QString loadedScheme;
diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp
index b79c91265..a8d068daa 100644
--- a/cockatrice/src/interface/theme_config.cpp
+++ b/cockatrice/src/interface/theme_config.cpp
@@ -248,10 +248,6 @@ PaletteConfig PaletteConfig::fromDefault(const QString &themeDirPath, const QStr
PaletteConfig cfg =
fromFile(dir.absoluteFilePath(wantDark ? "palette-default-dark.toml" : "palette-default-light.toml"));
- if (!cfg.hasPalette()) {
- cfg = fromFile(dir.absoluteFilePath(wantDark ? "palette-default-light.toml" : "palette-default-dark.toml"));
- }
-
return cfg;
}
diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp
index 086845fe6..47b921945 100644
--- a/cockatrice/src/interface/theme_manager.cpp
+++ b/cockatrice/src/interface/theme_manager.cpp
@@ -97,6 +97,13 @@ ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
defaultStyleName = "windowsvista";
}
ensureThemeDirectoryExists();
+
+ // Capture the QPA-initialised palette before we ever call qApp->setPalette().
+ // Once setPalette() is called, is_app_palette is locked true and neither
+ // setStyle() nor colorSchemeChanged will update it automatically.
+ // This snapshot is our guaranteed-clean base for applyStyleAndPalette.
+ systemPalette = qApp->palette();
+
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot);
#endif
@@ -113,21 +120,26 @@ void ThemeManager::ensureThemeDirectoryExists()
}
}
-bool ThemeManager::isDarkMode(const QString &themeDirPath)
+bool ThemeManager::isDarkMode(const QString &themeDirPath, const QString &userDirPath)
{
- ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath);
- if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
- return true;
- } else if (themeConfig.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
- return false;
- } else {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
- bool osDark = (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
-#else
- bool osDark = false;
-#endif
- return osDark;
+ // User override takes precedence over system config
+ for (const QString &path : {userDirPath, themeDirPath}) {
+ if (path.isEmpty()) {
+ continue;
+ }
+ ThemeConfig cfg = ThemeConfig::fromThemeDir(path);
+ if (cfg.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ if (cfg.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
+ return false;
+ }
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ return (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
+#else
+ return false;
+#endif
}
bool ThemeManager::isBuiltInTheme()
@@ -137,45 +149,87 @@ bool ThemeManager::isBuiltInTheme()
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME;
}
+QString ThemeManager::userThemeDirFor(const QString &themeName)
+{
+ return QDir(SettingsCache::instance().getThemesPath()).absoluteFilePath(themeName);
+}
+
QStringMap &ThemeManager::getAvailableThemes()
{
- QDir dir;
availableThemes.clear();
- // load themes from user profile dir
- dir.setPath(SettingsCache::instance().getThemesPath());
-
- // add default value
- availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default"));
-
- availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion"));
-
- for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
- if (!availableThemes.contains(themeName)) {
- availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
- }
- }
-
- // load themes from cockatrice system dir
- dir.setPath(qApp->applicationDirPath() +
+ // ── 1. System themes (read-only, shipped with the application) ──────────
+ QDir sysDir(qApp->applicationDirPath() +
#ifdef Q_OS_MAC
"/../Resources/themes"
#elif defined(Q_OS_WIN)
"/themes"
-#else // linux
+#else
"/../share/cockatrice/themes"
#endif
);
+ for (const QString &name : sysDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
+ availableThemes.insert(name, sysDir.absoluteFilePath(name));
+ }
- for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
- if (!availableThemes.contains(themeName)) {
- availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
+ // ── 2. User-only themes (AppData) ────────────────────────────────────────
+ // We only add themes that don't already exist in the system directory.
+ // Customisations to system themes are handled via the fallthrough read
+ // logic (userThemeDirFor → system path); we intentionally keep the system
+ // path in the map so shipped assets are always locatable.
+ QDir userDir(SettingsCache::instance().getThemesPath());
+ for (const QString &name : userDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
+ if (!availableThemes.contains(name)) {
+ availableThemes.insert(name, userDir.absoluteFilePath(name));
}
}
+ // ── 3. Ensure built-in sentinels always exist (dev builds without install)
+ if (!availableThemes.contains(NONE_THEME_NAME)) {
+ availableThemes.insert(NONE_THEME_NAME, userDir.absoluteFilePath("Default"));
+ }
+ if (!availableThemes.contains(FUSION_THEME_NAME)) {
+ availableThemes.insert(FUSION_THEME_NAME, userDir.absoluteFilePath("Fusion"));
+ }
+
return availableThemes;
}
+ThemeConfig ThemeManager::effectiveThemeConfig(const QString &themeName)
+{
+ const QString dirPath = getAvailableThemes().value(themeName);
+ const QString userDirPath = userThemeDirFor(themeName);
+
+ ThemeConfig userCfg = ThemeConfig::fromThemeDir(userDirPath);
+ ThemeConfig systemCfg = ThemeConfig::fromThemeDir(dirPath);
+
+ ThemeConfig result = systemCfg;
+
+ // User values override system values on a per-field basis
+ if (!userCfg.colorScheme.isEmpty()) {
+ result.colorScheme = userCfg.colorScheme;
+ }
+
+ if (!userCfg.styleName.isEmpty()) {
+ result.styleName = userCfg.styleName;
+ }
+
+ return result;
+}
+
+void ThemeManager::setStyleName(const QString &style)
+{
+ const QString themeName = SettingsCache::instance().getThemeName();
+ const QString userDirPath = userThemeDirFor(themeName);
+
+ ThemeConfig cfg = effectiveThemeConfig(themeName);
+
+ cfg.styleName = style;
+ cfg.save(userDirPath);
+
+ reloadCurrentTheme();
+}
+
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
{
QBrush brush;
@@ -244,12 +298,14 @@ bool ThemeManager::savePaletteConfig(const QString &themeDirPath, const QString
void ThemeManager::setColorScheme(const QString &scheme)
{
- const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
- ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
+ const QString themeName = SettingsCache::instance().getThemeName();
+ const QString userDirPath = userThemeDirFor(themeName);
+
+ ThemeConfig cfg = effectiveThemeConfig(themeName);
cfg.colorScheme = scheme;
+ cfg.save(userDirPath);
- cfg.save(dirPath);
reloadCurrentTheme();
}
@@ -261,8 +317,8 @@ void ThemeManager::reloadCurrentTheme()
void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &scheme)
{
const QString themeName = SettingsCache::instance().getThemeName();
- const QString dirPath = getAvailableThemes().value(themeName);
- const ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
+
+ ThemeConfig themeCfg = effectiveThemeConfig(themeName);
applyStyleAndPalette(themeName, themeCfg, cfg, scheme);
}
@@ -295,7 +351,7 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
}
#endif
} else {
- base = qApp->palette();
+ base = systemPalette;
}
// Overlay custom palette colours
@@ -327,48 +383,54 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
void ThemeManager::themeChangedSlot()
{
- QString themeName = SettingsCache::instance().getThemeName();
- QString dirPath = getAvailableThemes().value(themeName);
+ const QString themeName = SettingsCache::instance().getThemeName();
+ const QString dirPath = getAvailableThemes().value(themeName); // system path
+ const QString userDirPath = userThemeDirFor(themeName); // user override path
currentThemePath = dirPath;
- QDir dir(dirPath);
- // CSS
- if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
- qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
- } else {
+ // CSS — user override first, then system
+ const auto tryLoadCss = [](const QString &path) -> bool {
+ QDir d(path);
+ if (!path.isEmpty() && d.exists(STYLE_CSS_NAME)) {
+ qApp->setStyleSheet("file:///" + d.absoluteFilePath(STYLE_CSS_NAME));
+ return true;
+ }
+ return false;
+ };
+ if (!tryLoadCss(userDirPath) && !tryLoadCss(dirPath)) {
qApp->setStyleSheet("");
}
- // load theme.cfg for style + scheme preference
- ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
+ // ThemeConfig — user override first, then system
+ ThemeConfig themeCfg = effectiveThemeConfig(themeName);
- // Resolve active scheme:
- // theme.cfg says Dark/Light → use that
- // theme.cfg says System or is absent → follow the OS
- QString activeScheme = isDarkMode(dirPath) ? "Dark" : "Light";
+ const QString activeScheme = isDarkMode(dirPath, userDirPath) ? "Dark" : "Light";
- // ── Load palette: custom first, then theme default ────────────────────
- PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
+ // Palette — user customisation → system palette → system default
+ PaletteConfig palette = PaletteConfig::fromScheme(userDirPath, activeScheme);
+ if (!palette.hasPalette()) {
+ palette = PaletteConfig::fromScheme(dirPath, activeScheme);
+ }
if (!palette.hasPalette()) {
palette = PaletteConfig::fromDefault(dirPath, activeScheme);
}
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
+ // Search paths — user assets shadow system assets, both fall through to builtins
QStringList resources;
+ if (QDir(userDirPath).exists()) {
+ resources << userDirPath;
+ }
if (!dirPath.isEmpty()) {
- resources << dir.absolutePath();
+ resources << dirPath;
}
resources << DEFAULT_RESOURCE_PATHS;
-
QDir::setSearchPaths("theme", resources);
brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
-
brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
-
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
-
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
for (auto &brushCache : brushesCache) {
brushCache.clear();
@@ -379,6 +441,20 @@ void ThemeManager::themeChangedSlot()
emit themeChanged();
}
+void ThemeManager::onColorSchemeChanged()
+{
+ // qApp->palette() is locked (is_app_palette = true), so the QPA won't push
+ // the new OS palette through automatically. style->polish(QPalette&) queries
+ // GetSysColor on Windows — adequate for light mode, imperfect for Win11 dark
+ // mode (which uses a different API), but better than a stale light snapshot.
+ QPalette fresh;
+ qApp->style()->polish(fresh);
+ if (fresh.color(QPalette::Window).isValid()) {
+ systemPalette = fresh;
+ }
+ themeChangedSlot();
+}
+
static QString roleBgName(ThemeManager::Role role)
{
switch (role) {
diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h
index b9e764d08..191c5927e 100644
--- a/cockatrice/src/interface/theme_manager.h
+++ b/cockatrice/src/interface/theme_manager.h
@@ -42,6 +42,7 @@ public:
};
private:
+ QPalette systemPalette;
QString defaultStyleName;
QString currentThemePath;
std::array brushes;
@@ -63,7 +64,11 @@ protected:
public:
bool isBuiltInTheme();
bool isDarkMode(const QString &themeDirPath);
+ static bool isDarkMode(const QString &themeDirPath, const QString &userDirPath = {});
+ static QString userThemeDirFor(const QString &themeName);
QStringMap &getAvailableThemes();
+ ThemeConfig effectiveThemeConfig(const QString &themeName);
+ void setStyleName(const QString &styleName);
// Returns the path to the currently active theme directory (empty = default)
QString getCurrentThemePath() const
{
@@ -85,6 +90,7 @@ public:
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
protected slots:
void themeChangedSlot();
+ void onColorSchemeChanged();
signals:
void themeChanged();
};
diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp
index 2d32f3ce1..78a171c0b 100644
--- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp
+++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
AppearanceSettingsPage::AppearanceSettingsPage()
@@ -31,32 +32,33 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &AppearanceSettingsPage::themeBoxChanged);
connect(&openThemeButton, &QPushButton::clicked, this, &AppearanceSettingsPage::openThemeLocation);
+ // Populate Scheme
+
schemeCombo.addItem(tr("Light"), QStringLiteral("Light"));
schemeCombo.addItem(tr("Dark"), QStringLiteral("Dark"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
schemeCombo.addItem(tr("System"), QStringLiteral("System"));
#endif
- // Seed from whatever the current theme already has saved
- const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
- const ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
- const QString current = cfg.colorScheme;
- const int seedIdx = schemeCombo.findData(current);
- schemeCombo.setCurrentIndex(seedIdx >= 0 ? seedIdx : 0);
-
connect(&schemeCombo, &QComboBox::currentIndexChanged, this,
[this] { themeManager->setColorScheme(schemeCombo.currentData().toString()); });
- connect(themeManager, &ThemeManager::themeChanged, this, [this, dirPath] {
- const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
- const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir);
- const QString current = cfg.colorScheme;
+ // Populate Style
- schemeCombo.blockSignals(true);
- const int idx = schemeCombo.findData(current);
- schemeCombo.setCurrentIndex(idx >= 0 ? idx : 0);
- schemeCombo.blockSignals(false);
- });
+ styleComboBox.addItem(tr("Default"), QString());
+
+ for (const QString &style : QStyleFactory::keys()) {
+ styleComboBox.addItem(style, style);
+ }
+
+ connect(&styleComboBox, &QComboBox::currentIndexChanged, this,
+ [this] { themeManager->setStyleName(styleComboBox.currentData().toString()); });
+
+ // Refresh theme and style
+
+ reloadThemeSettings();
+
+ connect(themeManager, &ThemeManager::themeChanged, this, &AppearanceSettingsPage::reloadThemeSettings);
connect(&editPaletteButton, &QPushButton::clicked, this, &AppearanceSettingsPage::editPalette);
@@ -66,7 +68,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
themeGrid->addWidget(&openThemeButton, 1, 1);
themeGrid->addWidget(&schemeComboLabel, 2, 0);
themeGrid->addWidget(&schemeCombo, 2, 1);
- themeGrid->addWidget(&editPaletteButton, 3, 1);
+ themeGrid->addWidget(&styleLabel, 3, 0);
+ themeGrid->addWidget(&styleComboBox, 3, 1);
+ themeGrid->addWidget(&editPaletteButton, 4, 1);
themeGroupBox = new QGroupBox;
themeGroupBox->setLayout(themeGrid);
@@ -320,6 +324,19 @@ void AppearanceSettingsPage::openThemeLocation()
}
}
+void AppearanceSettingsPage::reloadThemeSettings()
+{
+ const ThemeConfig cfg = themeManager->effectiveThemeConfig(SettingsCache::instance().getThemeName());
+
+ schemeCombo.blockSignals(true);
+ schemeCombo.setCurrentIndex(std::max(0, schemeCombo.findData(cfg.colorScheme)));
+ schemeCombo.blockSignals(false);
+
+ styleComboBox.blockSignals(true);
+ styleComboBox.setCurrentIndex(qMax(0, styleComboBox.findData(cfg.styleName)));
+ styleComboBox.blockSignals(false);
+}
+
void AppearanceSettingsPage::editPalette()
{
PaletteEditorDialog dlg(themeManager->getCurrentThemePath(), SettingsCache::instance().getThemeName(), this);
@@ -390,6 +407,7 @@ void AppearanceSettingsPage::retranslateUi()
themeLabel.setText(tr("Current theme:"));
openThemeButton.setText(tr("Open themes folder"));
schemeComboLabel.setText(tr("Active theme palette:"));
+ styleLabel.setText(tr("Active theme style:"));
editPaletteButton.setText(tr("Edit theme palette"));
homeTabGroupBox->setTitle(tr("Home tab settings"));
diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h
index 9ed27be4d..be2b3da42 100644
--- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h
+++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h
@@ -17,6 +17,7 @@ class AppearanceSettingsPage : public AbstractSettingsPage
private slots:
void themeBoxChanged(int index);
void openThemeLocation();
+ void reloadThemeSettings();
void editPalette();
void updateHomeTabSettingsVisibility();
void showShortcutsChanged(QT_STATE_CHANGED_T enabled);
@@ -31,6 +32,8 @@ private:
QPushButton openThemeButton;
QLabel schemeComboLabel;
QComboBox schemeCombo;
+ QLabel styleLabel;
+ QComboBox styleComboBox;
QPushButton editPaletteButton;
QLabel homeTabBackgroundSourceLabel;
QComboBox homeTabBackgroundSourceBox;
diff --git a/cockatrice/themes/Fabric/palette-default-dark.toml b/cockatrice/themes/Fabric/palette-default-dark.toml
new file mode 100644
index 000000000..3ee174a2f
--- /dev/null
+++ b/cockatrice/themes/Fabric/palette-default-dark.toml
@@ -0,0 +1,63 @@
+[Palette]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff000000
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
+[Palette.Disabled]
+WindowText = #ff9d9d9d
+Button = #ff1c1c1c
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ff9d9d9d
+BrightText = #ffb4dd8b
+ButtonText = #ff787878
+Base = #ff1c1c1c
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff9d9d9d
+Link = #ff308cc6
+LinkVisited = #ffb450ff
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #46ffffff
+
+[Palette.Inactive]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ffffffff
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
diff --git a/cockatrice/themes/Fabric/theme.cfg b/cockatrice/themes/Fabric/theme.cfg
new file mode 100644
index 000000000..d2016a238
--- /dev/null
+++ b/cockatrice/themes/Fabric/theme.cfg
@@ -0,0 +1,5 @@
+[Appearance]
+ColorScheme = Light
+
+[Style]
+Name = Default
diff --git a/cockatrice/themes/Leather/palette-default-dark.toml b/cockatrice/themes/Leather/palette-default-dark.toml
new file mode 100644
index 000000000..3ee174a2f
--- /dev/null
+++ b/cockatrice/themes/Leather/palette-default-dark.toml
@@ -0,0 +1,63 @@
+[Palette]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff000000
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
+[Palette.Disabled]
+WindowText = #ff9d9d9d
+Button = #ff1c1c1c
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ff9d9d9d
+BrightText = #ffb4dd8b
+ButtonText = #ff787878
+Base = #ff1c1c1c
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff9d9d9d
+Link = #ff308cc6
+LinkVisited = #ffb450ff
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #46ffffff
+
+[Palette.Inactive]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ffffffff
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
diff --git a/cockatrice/themes/Leather/theme.cfg b/cockatrice/themes/Leather/theme.cfg
new file mode 100644
index 000000000..d2016a238
--- /dev/null
+++ b/cockatrice/themes/Leather/theme.cfg
@@ -0,0 +1,5 @@
+[Appearance]
+ColorScheme = Light
+
+[Style]
+Name = Default
diff --git a/cockatrice/themes/Plasma/palette-default-dark.toml b/cockatrice/themes/Plasma/palette-default-dark.toml
new file mode 100644
index 000000000..3ee174a2f
--- /dev/null
+++ b/cockatrice/themes/Plasma/palette-default-dark.toml
@@ -0,0 +1,63 @@
+[Palette]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff000000
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
+[Palette.Disabled]
+WindowText = #ff9d9d9d
+Button = #ff1c1c1c
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ff9d9d9d
+BrightText = #ffb4dd8b
+ButtonText = #ff787878
+Base = #ff1c1c1c
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff9d9d9d
+Link = #ff308cc6
+LinkVisited = #ffb450ff
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #46ffffff
+
+[Palette.Inactive]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ffffffff
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
diff --git a/cockatrice/themes/Plasma/theme.cfg b/cockatrice/themes/Plasma/theme.cfg
new file mode 100644
index 000000000..d2016a238
--- /dev/null
+++ b/cockatrice/themes/Plasma/theme.cfg
@@ -0,0 +1,5 @@
+[Appearance]
+ColorScheme = Light
+
+[Style]
+Name = Default
diff --git a/cockatrice/themes/VelvetMarble/palette-default-dark.toml b/cockatrice/themes/VelvetMarble/palette-default-dark.toml
new file mode 100644
index 000000000..3ee174a2f
--- /dev/null
+++ b/cockatrice/themes/VelvetMarble/palette-default-dark.toml
@@ -0,0 +1,63 @@
+[Palette]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff000000
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
+[Palette.Disabled]
+WindowText = #ff9d9d9d
+Button = #ff1c1c1c
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ff9d9d9d
+BrightText = #ffb4dd8b
+ButtonText = #ff787878
+Base = #ff1c1c1c
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ff9d9d9d
+Link = #ff308cc6
+LinkVisited = #ffb450ff
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #46ffffff
+
+[Palette.Inactive]
+WindowText = #ffffffff
+Button = #ff383838
+Light = #ff737373
+Midlight = #ff525252
+Dark = #ff161616
+Mid = #ff252525
+Text = #ffffffff
+BrightText = #ffb4dd8b
+ButtonText = #ffffffff
+Base = #ff2b2b2b
+Window = #ff1c1c1c
+Shadow = #ff000000
+HighlightedText = #ffffffff
+Link = #ffcde4b6
+LinkVisited = #ff99d999
+AlternateBase = #ff242424
+ToolTipBase = #ffffffdc
+ToolTipText = #ff000000
+PlaceholderText = #6effffff
+
diff --git a/cockatrice/themes/VelvetMarble/theme.cfg b/cockatrice/themes/VelvetMarble/theme.cfg
new file mode 100644
index 000000000..d2016a238
--- /dev/null
+++ b/cockatrice/themes/VelvetMarble/theme.cfg
@@ -0,0 +1,5 @@
+[Appearance]
+ColorScheme = Light
+
+[Style]
+Name = Default