diff --git a/CMakeLists.txt b/CMakeLists.txt index 888c44b2f..c10e1db68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -project("Cockatrice" VERSION 3.0.2) +project("Cockatrice" VERSION 3.1.0) # Set release name if not provided via env/cmake var if(NOT DEFINED GIT_TAG_RELEASENAME) diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 6d37bdd92..1b9be1dd4 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -25,7 +25,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Load both scheme configs upfront so switching is instant loadSchemes(); - //loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; + loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; schemeComboBox->blockSignals(true); schemeComboBox->setCurrentText(loadedScheme); @@ -247,7 +247,7 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi void PaletteEditorDialog::onApply() { - //themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); + themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); } void PaletteEditorDialog::onSave() @@ -258,11 +258,11 @@ void PaletteEditorDialog::onSave() PaletteConfig cfg = paletteGrid->currentPaletteConfig(); - /*if (!ThemeManager::savePaletteConfig(themeDirPath, loadedScheme, cfg)) { + 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)); return; - }*/ + } ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath); globalCfg.colorScheme = loadedScheme; @@ -270,7 +270,7 @@ void PaletteEditorDialog::onSave() savedConfig[loadedScheme] = cfg; workingConfig[loadedScheme] = cfg; - //themeManager->reloadCurrentTheme(); + themeManager->reloadCurrentTheme(); accept(); } diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index a0d2031e4..086845fe6 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -19,9 +19,7 @@ #include #define NONE_THEME_NAME "Default" -#define FUSION_THEME_NAME "Fusion (System Default)" -#define FUSION_THEME_NAME_LIGHT "Fusion (Light)" -#define FUSION_THEME_NAME_DARK "Fusion (Dark)" +#define FUSION_THEME_NAME "Fusion" #define STYLE_CSS_NAME "style.css" #define HANDZONE_BG_NAME "handzone" #define PLAYERZONE_BG_NAME "playerzone" @@ -51,8 +49,9 @@ struct PaletteColorInfo // Iterate through all color roles (excluding NoRole and NColorRoles) for (int r = 0; r < QPalette::NColorRoles; ++r) { auto role = static_cast(r); - if (role == QPalette::NoRole) + if (role == QPalette::NoRole) { continue; + } PaletteColorInfo info; info.group = group; @@ -78,8 +77,9 @@ struct PaletteColorInfo for (int r = 0; r < QPalette::NColorRoles; ++r) { auto role = static_cast(r); - if (role == QPalette::NoRole) + if (role == QPalette::NoRole) { continue; + } QColor color = palette.color(group, role); qInfo().nospace() << qPrintable(QString("%1").arg(roleEnum.valueToKey(role), -20)) << " : " @@ -92,7 +92,7 @@ struct PaletteColorInfo ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { defaultStyleName = qApp->style()->objectName(); - // FIXME workaround for windows11 style being broken + //! \todo Workaround for windows11 style being broken. if (defaultStyleName == "windows11") { defaultStyleName = "windowsvista"; } @@ -113,37 +113,28 @@ void ThemeManager::ensureThemeDirectoryExists() } } -bool ThemeManager::isDarkMode() +bool ThemeManager::isDarkMode(const QString &themeDirPath) { - auto themeName = SettingsCache::instance().getThemeName(); - // Explicit Dark Mode - if (themeName == FUSION_THEME_NAME_LIGHT || themeName.endsWith("(Light)")) { + 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; - } - // Explicit Light Mode - if (themeName == FUSION_THEME_NAME_DARK || themeName.endsWith("(Dark)")) { - return true; - } - - // Auto detection on compatible Qt versions -#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) - if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark && - (themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName.endsWith("(System Default)"))) { - return true; } else { - return false; - } +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + bool osDark = (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark); +#else + bool osDark = false; #endif - // Default to light mode - return false; + return osDark; + } } bool ThemeManager::isBuiltInTheme() { const auto themeName = SettingsCache::instance().getThemeName(); - return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME || themeName == FUSION_THEME_NAME_LIGHT || - themeName == FUSION_THEME_NAME_DARK; + return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME; } QStringMap &ThemeManager::getAvailableThemes() @@ -151,15 +142,13 @@ QStringMap &ThemeManager::getAvailableThemes() QDir dir; availableThemes.clear(); - // add default value - availableThemes.insert(NONE_THEME_NAME, QString()); - // load themes from user profile dir dir.setPath(SettingsCache::instance().getThemesPath()); - availableThemes.insert(FUSION_THEME_NAME, dir.filePath("Fusion (System Default)")); - availableThemes.insert(FUSION_THEME_NAME_LIGHT, dir.filePath("Fusion (Light)")); - availableThemes.insert(FUSION_THEME_NAME_DARK, dir.filePath("Fusion (Dark)")); + // 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)) { @@ -215,192 +204,112 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) return brush; } -static inline QPalette createDarkGreenFusionPalette() +ThemeConfig ThemeManager::loadGlobalConfig(const QString &themeDirPath) { - QPalette p = QStyleFactory::create("Fusion")->standardPalette(); - - // ---------- Core backgrounds ---------- - p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e - p.setColor(QPalette::Base, QColor(45, 45, 45)); // #ff2d2d2d - p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); // #ff353535 - p.setColor(QPalette::Button, QColor(60, 60, 60)); // #ff3c3c3c - p.setColor(QPalette::ToolTipBase, QColor(60, 60, 60)); // #ff3c3c3c - - // ---------- Core text ---------- - p.setColor(QPalette::WindowText, Qt::white); // #ffffffff - p.setColor(QPalette::Text, Qt::white); // #ffffffff - p.setColor(QPalette::ButtonText, Qt::white); // #ffffffff - p.setColor(QPalette::ToolTipText, QColor(212, 212, 212)); // #ffd4d4d4 - p.setColor(QPalette::PlaceholderText, QColor(255, 255, 255, 128)); // #80ffffff - - // ---------- Selection / focus ---------- - const QColor highlight(20, 140, 60); // #ff148c3c - p.setColor(QPalette::Highlight, highlight); - p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff - - // ---------- Links ---------- - p.setColor(QPalette::Link, QColor(0, 246, 82)); // #ff00f652 - p.setColor(QPalette::LinkVisited, QColor(0, 211, 70)); // #ff00d346 - - // ---------- Accent (Qt 6) ---------- -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Accent, QColor(0, 211, 70)); // #ff00d346 -#endif - - // ---------- Bright text ---------- - p.setColor(QPalette::BrightText, QColor(0, 246, 82)); // #ff00f652 - - // ---------- 3D / frame shading ---------- - p.setColor(QPalette::Light, QColor(120, 120, 120)); // #ff787878 - p.setColor(QPalette::Midlight, QColor(90, 90, 90)); // #ff5a5a5a - p.setColor(QPalette::Mid, QColor(40, 40, 40)); // #ff282828 - p.setColor(QPalette::Dark, QColor(30, 30, 30)); // #ff1e1e1e - p.setColor(QPalette::Shadow, Qt::black); // #ff000000 - - // ---------- Disabled state ---------- - const QColor disabledText(157, 157, 157); // #ff9d9d9d - p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Text, disabledText); - p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Base, QColor(30, 30, 30)); - p.setColor(QPalette::Disabled, QPalette::Window, QColor(30, 30, 30)); - p.setColor(QPalette::Disabled, QPalette::Link, QColor(48, 140, 198)); // #ff308cc6 - p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); // #ffff00ff - p.setColor(QPalette::Disabled, QPalette::ToolTipBase, QColor(255, 255, 220)); - p.setColor(QPalette::Disabled, QPalette::ToolTipText, Qt::black); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); -#endif - - // ---------- Inactive state ---------- - p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(30, 30, 30)); - p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::white); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Inactive, QPalette::Accent, QColor(30, 30, 30)); -#endif - - return p; + return ThemeConfig::fromThemeDir(themeDirPath); } -static inline QPalette createLightGreenFusionPalette() +bool ThemeManager::saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg) { - QPalette p = QStyleFactory::create("Fusion")->standardPalette(); - - // ---------- Core backgrounds ---------- - p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0 - p.setColor(QPalette::Base, Qt::white); // #ffffffff - p.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); // #ffe9e7e3 - p.setColor(QPalette::Button, QColor(240, 240, 240)); // #fff0f0f0 - p.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); // #ffffffdc - - // ---------- Core text ---------- - p.setColor(QPalette::WindowText, Qt::black); // #ff000000 - p.setColor(QPalette::Text, Qt::black); // #ff000000 - p.setColor(QPalette::ButtonText, Qt::black); // #ff000000 - p.setColor(QPalette::ToolTipText, Qt::black); // #ff000000 - p.setColor(QPalette::PlaceholderText, QColor(0, 0, 0, 128)); // #80000000 - - // ---------- Selection / focus ---------- - const QColor highlight(20, 140, 60); // #ff148c3c - p.setColor(QPalette::Highlight, highlight); - p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff - - // ---------- Links ---------- - p.setColor(QPalette::Link, QColor(13, 95, 40)); // #ff0d5f28 - p.setColor(QPalette::LinkVisited, QColor(8, 64, 27)); // #ff08401b - - // ---------- Accent (Qt 6) ---------- -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Accent, QColor(16, 117, 50)); // #ff107532 -#endif - - // ---------- Bright text ---------- - p.setColor(QPalette::BrightText, Qt::white); // #ffffffff - - // ---------- 3D / frame shading ---------- - p.setColor(QPalette::Light, Qt::white); // #ffffffff - p.setColor(QPalette::Midlight, QColor(227, 227, 227)); // #ffe3e3e3 - p.setColor(QPalette::Mid, QColor(160, 160, 160)); // #ffa0a0a0 - p.setColor(QPalette::Dark, QColor(160, 160, 160)); // #ffa0a0a0 - p.setColor(QPalette::Shadow, QColor(105, 105, 105)); // #ff696969 - - // ---------- Disabled state ---------- - const QColor disabledText(120, 120, 120); // #ff787878 - p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Text, disabledText); - p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); - p.setColor(QPalette::Disabled, QPalette::Base, QColor(240, 240, 240)); - p.setColor(QPalette::Disabled, QPalette::Window, QColor(240, 240, 240)); - p.setColor(QPalette::Disabled, QPalette::Midlight, QColor(247, 247, 247)); - p.setColor(QPalette::Disabled, QPalette::AlternateBase, QColor(247, 247, 247)); - p.setColor(QPalette::Disabled, QPalette::Shadow, Qt::black); - p.setColor(QPalette::Disabled, QPalette::Link, QColor(0, 0, 255)); - p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); -#endif - - // ---------- Inactive state ---------- - p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(240, 240, 240)); - p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::black); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) - p.setColor(QPalette::Inactive, QPalette::Accent, QColor(240, 240, 240)); -#endif - - return p; + return cfg.save(themeDirPath); } -void ThemeManager::themeChangedSlot() +PaletteConfig ThemeManager::loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme) { - QString themeName = SettingsCache::instance().getThemeName(); - qCInfo(ThemeManagerLog) << "Theme changed:" << themeName; + if (themeDirPath.isEmpty()) { + return {}; + } + return PaletteConfig::fromScheme(themeDirPath, colorScheme); +} - QString dirPath = getAvailableThemes().value(themeName); - QDir dir = dirPath; - - // css - if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) { - qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME)); - } else { - qApp->setStyleSheet(""); +bool ThemeManager::savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) +{ + if (themeDirPath.isEmpty()) { + return false; } - QStyle *newStyle = nullptr; - QPalette newPalette; + QDir dir(themeDirPath); + if (!dir.exists()) { + dir.mkpath("."); + } - if (themeName == FUSION_THEME_NAME) { - newStyle = QStyleFactory::create("Fusion"); + QFile f(dir.absoluteFilePath(PaletteConfig::fileName(colorScheme))); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) - // Start from Fusion's own palette so dark mode is handled correctly, - // then apply any tweaks on top of it. - newPalette = newStyle->standardPalette(); - if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { - newPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + QTextStream(&f) << cfg.toToml(); + return true; +} + +void ThemeManager::setColorScheme(const QString &scheme) +{ + const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); + ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); + + cfg.colorScheme = scheme; + + cfg.save(dirPath); + reloadCurrentTheme(); +} + +void ThemeManager::reloadCurrentTheme() +{ + themeChangedSlot(); +} + +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); + applyStyleAndPalette(themeName, themeCfg, cfg, scheme); +} + +void ThemeManager::applyStyleAndPalette(const QString &themeName, + const ThemeConfig &themeCfg, + const PaletteConfig &palCfg, + const QString &activeScheme) +{ + QString styleName = themeCfg.styleName; + if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) { + if (themeName == FUSION_THEME_NAME) { + styleName = "Fusion"; + } else { + styleName = defaultStyleName; } -#else - newPalette = qApp->palette(); -#endif - } else if (themeName == FUSION_THEME_NAME_LIGHT) { - newStyle = QStyleFactory::create("Fusion"); - newPalette = createLightGreenFusionPalette(); - } else if (themeName == FUSION_THEME_NAME_DARK) { - newStyle = QStyleFactory::create("Fusion"); - newPalette = createDarkGreenFusionPalette(); - } else { - newStyle = QStyleFactory::create(defaultStyleName); - // Use the style's default palette. - newPalette = newStyle->standardPalette(); } - // Apply palette FIRST. - qApp->setPalette(newPalette); - // Then apply style. - qApp->setStyle(newStyle); + QStyle *style = QStyleFactory::create(styleName); + if (!style) { + style = QStyleFactory::create(defaultStyleName); + } + + // Base palette + QPalette base; + if (styleName.compare("Fusion", Qt::CaseInsensitive) == 0) { + base = style->standardPalette(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + if (activeScheme == "Dark") { + base.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + } +#endif + } else { + base = qApp->palette(); + } + + // Overlay custom palette colours + if (palCfg.hasPalette()) { + base = palCfg.apply(base); + } + + // Palette BEFORE style — setStyle() triggers a synchronous repolish of all + // widgets immediately. If the palette isn't set yet at that point, every + // widget gets polished against the stale colours, requiring a second apply + // to fully resolve. Setting palette first means setStyle's repolish cascade + // already sees the correct colours. + qApp->setPalette(base); + qApp->setStyle(style); // Force every widget to re-polish and repaint immediately rather than // waiting for natural expose events, which produces a patchwork of old @@ -410,34 +319,57 @@ void ThemeManager::themeChangedSlot() // palette (WA_SetPalette not set). Calling it unconditionally would clobber // intentional per-widget palette customisations across the whole app. for (QWidget *widget : qApp->allWidgets()) { - if (widget->isVisible()) { - newStyle->unpolish(widget); - newStyle->polish(widget); - - widget->update(); - } + style->unpolish(widget); + style->polish(widget); + widget->update(); } +} - if (dirPath.isEmpty()) { - // set default values - QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS); - brushes[Role::Hand] = HANDZONE_BG_DEFAULT; - brushes[Role::Table] = TABLEZONE_BG_DEFAULT; - brushes[Role::Player] = PLAYERZONE_BG_DEFAULT; - brushes[Role::Stack] = STACKZONE_BG_DEFAULT; +void ThemeManager::themeChangedSlot() +{ + QString themeName = SettingsCache::instance().getThemeName(); + QString dirPath = getAvailableThemes().value(themeName); + currentThemePath = dirPath; + QDir dir(dirPath); + + // CSS + if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) { + qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME)); } else { - // resources - QStringList resources; - resources << dir.absolutePath() << DEFAULT_RESOURCE_PATHS; - QDir::setSearchPaths("theme", resources); - - // zones bg - dir.cd("zones"); - 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); + qApp->setStyleSheet(""); } + + // load theme.cfg for style + scheme preference + ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath); + + // 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"; + + // ── Load palette: custom first, then theme default ──────────────────── + PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme); + if (!palette.hasPalette()) { + palette = PaletteConfig::fromDefault(dirPath, activeScheme); + } + + applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); + + QStringList resources; + if (!dirPath.isEmpty()) { + resources << dir.absolutePath(); + } + 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(); } @@ -487,4 +419,4 @@ QBrush ThemeManager::getExtraBgBrush(Role role, int zoneId) } return brushCache.value(zoneId); -} \ No newline at end of file +} diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 79b47f304..b9e764d08 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -1,12 +1,14 @@ /** -* @file theme_manager.h + * @file theme_manager.h * @ingroup CoreSettings - * @brief TODO: Document this. */ +//! \todo Document this file. #ifndef THEMEMANAGER_H #define THEMEMANAGER_H +#include "theme_config.h" + #include #include #include @@ -41,6 +43,7 @@ public: private: QString defaultStyleName; + QString currentThemePath; std::array brushes; QStringMap availableThemes; /* @@ -52,20 +55,40 @@ protected: void ensureThemeDirectoryExists(); QBrush loadBrush(QString fileName, QColor fallbackColor); QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush); + void applyStyleAndPalette(const QString &themeName, + const ThemeConfig &themeCfg, + const PaletteConfig &palCfg, + const QString &activeScheme); public: bool isBuiltInTheme(); - bool isDarkMode(); + bool isDarkMode(const QString &themeDirPath); QStringMap &getAvailableThemes(); + // Returns the path to the currently active theme directory (empty = default) + QString getCurrentThemePath() const + { + return currentThemePath; + } + // Load the global theme settings (style + color scheme preference) + static ThemeConfig loadGlobalConfig(const QString &themeDirPath); + static bool saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg); + + // Load/save per-scheme palette colors + static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); + static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); + void setColorScheme(const QString &scheme); + + void reloadCurrentTheme(); + void previewPalette(const PaletteConfig &cfg, const QString &scheme); QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); protected slots: void themeChangedSlot(); - signals: - void themeChanged(); +signals: + void themeChanged(); }; extern ThemeManager *themeManager; -#endif \ No newline at end of file +#endif 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 3d399a195..2d32f3ce1 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -31,7 +31,7 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&themeBox, qOverload(&QComboBox::currentIndexChanged), this, &AppearanceSettingsPage::themeBoxChanged); connect(&openThemeButton, &QPushButton::clicked, this, &AppearanceSettingsPage::openThemeLocation); - /*schemeCombo.addItem(tr("Light"), QStringLiteral("Light")); + 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")); @@ -58,18 +58,15 @@ AppearanceSettingsPage::AppearanceSettingsPage() schemeCombo.blockSignals(false); }); - connect(&editPaletteButton, &QPushButton::clicked, this, &AppearanceSettingsPage::editPalette);*/ - schemeComboLabel.setHidden(true); - schemeCombo.setHidden(true); - editPaletteButton.setHidden(true); + connect(&editPaletteButton, &QPushButton::clicked, this, &AppearanceSettingsPage::editPalette); auto *themeGrid = new QGridLayout; themeGrid->addWidget(&themeLabel, 0, 0); themeGrid->addWidget(&themeBox, 0, 1); themeGrid->addWidget(&openThemeButton, 1, 1); - //themeGrid->addWidget(&schemeComboLabel, 2, 0); - //themeGrid->addWidget(&schemeCombo, 2, 1); - //themeGrid->addWidget(&editPaletteButton, 3, 1); + themeGrid->addWidget(&schemeComboLabel, 2, 0); + themeGrid->addWidget(&schemeCombo, 2, 1); + themeGrid->addWidget(&editPaletteButton, 3, 1); themeGroupBox = new QGroupBox; themeGroupBox->setLayout(themeGrid); @@ -325,8 +322,8 @@ void AppearanceSettingsPage::openThemeLocation() void AppearanceSettingsPage::editPalette() { - //PaletteEditorDialog dlg(themeManager->getCurrentThemePath(), SettingsCache::instance().getThemeName(), this); - //dlg.exec(); + PaletteEditorDialog dlg(themeManager->getCurrentThemePath(), SettingsCache::instance().getThemeName(), this); + dlg.exec(); } void AppearanceSettingsPage::updateHomeTabSettingsVisibility() diff --git a/cockatrice/themes/CMakeLists.txt b/cockatrice/themes/CMakeLists.txt index 3e07c54dd..577977551 100644 --- a/cockatrice/themes/CMakeLists.txt +++ b/cockatrice/themes/CMakeLists.txt @@ -2,7 +2,7 @@ # # add themes subfolders -set(defthemes Fabric Leather Plasma VelvetMarble) +set(defthemes Default Fabric Fusion Leather Plasma VelvetMarble) if(UNIX) if(APPLE) diff --git a/cockatrice/themes/Default/palette-default-dark.toml b/cockatrice/themes/Default/palette-default-dark.toml new file mode 100644 index 000000000..3ee174a2f --- /dev/null +++ b/cockatrice/themes/Default/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/Default/theme.cfg b/cockatrice/themes/Default/theme.cfg new file mode 100644 index 000000000..d2016a238 --- /dev/null +++ b/cockatrice/themes/Default/theme.cfg @@ -0,0 +1,5 @@ +[Appearance] +ColorScheme = Light + +[Style] +Name = Default diff --git a/cockatrice/themes/Fusion/palette-default-dark.toml b/cockatrice/themes/Fusion/palette-default-dark.toml new file mode 100644 index 000000000..c1d83a4cd --- /dev/null +++ b/cockatrice/themes/Fusion/palette-default-dark.toml @@ -0,0 +1,69 @@ +[Palette] +WindowText = #ffffffff +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ffffffff +BrightText = #ff00f652 +ButtonText = #ffffffff +Base = #ff2d2d2d +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff00f652 +LinkVisited = #ff00d346 +AlternateBase = #ff353535 +ToolTipBase = #ff3c3c3c +ToolTipText = #ffd4d4d4 +PlaceholderText = #80ffffff +Accent = #ff00d346 + +[Palette.Disabled] +WindowText = #ff9d9d9d +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ff9d9d9d +BrightText = #ff00f652 +ButtonText = #ff9d9d9d +Base = #ff1e1e1e +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff308cc6 +LinkVisited = #ffff00ff +AlternateBase = #ff353535 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80ffffff +Accent = #ff9d9d9d + +[Palette.Inactive] +WindowText = #ffffffff +Button = #ff3c3c3c +Light = #ff787878 +Midlight = #ff5a5a5a +Dark = #ff1e1e1e +Mid = #ff282828 +Text = #ffffffff +BrightText = #ff00f652 +ButtonText = #ffffffff +Base = #ff2d2d2d +Window = #ff1e1e1e +Shadow = #ff000000 +Highlight = #ff1e1e1e +HighlightedText = #ffffffff +Link = #ff00f652 +LinkVisited = #ff00d346 +AlternateBase = #ff353535 +ToolTipBase = #ff3c3c3c +ToolTipText = #ffd4d4d4 +PlaceholderText = #80ffffff +Accent = #ff1e1e1e + diff --git a/cockatrice/themes/Fusion/palette-default-light.toml b/cockatrice/themes/Fusion/palette-default-light.toml new file mode 100644 index 000000000..86c41be78 --- /dev/null +++ b/cockatrice/themes/Fusion/palette-default-light.toml @@ -0,0 +1,69 @@ +[Palette] +WindowText = #ff000000 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #ffe3e3e3 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff000000 +BrightText = #ffffffff +ButtonText = #ff000000 +Base = #ffffffff +Window = #fff0f0f0 +Shadow = #ff696969 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #ff107532 + +[Palette.Disabled] +WindowText = #ff787878 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #fff7f7f7 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff787878 +BrightText = #ffffffff +ButtonText = #ff787878 +Base = #fff0f0f0 +Window = #fff0f0f0 +Shadow = #ff000000 +Highlight = #ff148c3c +HighlightedText = #ffffffff +Link = #ff0000ff +LinkVisited = #ffff00ff +AlternateBase = #fff7f7f7 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #ff787878 + +[Palette.Inactive] +WindowText = #ff000000 +Button = #fff0f0f0 +Light = #ffffffff +Midlight = #ffe3e3e3 +Dark = #ffa0a0a0 +Mid = #ffa0a0a0 +Text = #ff000000 +BrightText = #ffffffff +ButtonText = #ff000000 +Base = #ffffffff +Window = #fff0f0f0 +Shadow = #ff696969 +Highlight = #fff0f0f0 +HighlightedText = #ff000000 +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 +Accent = #fff0f0f0 + diff --git a/cockatrice/themes/Fusion/theme.cfg b/cockatrice/themes/Fusion/theme.cfg new file mode 100644 index 000000000..9b38e505e --- /dev/null +++ b/cockatrice/themes/Fusion/theme.cfg @@ -0,0 +1,5 @@ +[Appearance] +ColorScheme = Dark + +[Style] +Name = Fusion