From 6c1d1c7b5889880cf6098133c1bcd58e098fc089 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:16:06 +0200 Subject: [PATCH] [Client] Add [AppColors] application palette roles (#7279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Client] Add [AppColors] application palette roles The home-tab buttons' gradient over the static theme background was hardcoded, and accent-derived fallbacks could not be themed or edited: QPalette's role set is closed, so any application-specific color has to live in Cockatrice's own palette layer. - Add an AppColor::Role enum (AccentStrong / AccentSoft) stored on PaletteConfig and round-tripped from palette-.toml under a new [AppColors] section. - Cache the applied app colors in ThemeManager and expose appColor(Role) with a palette-accent-derived fallback; emit paletteChanged() from applyStyleAndPalette so previews, scheme switches and OS dark mode repaint palette-driven widgets. - Fill both app roles in PaletteGenerator::fromAccent and surface them as a dedicated section in the palette editor. - Drive the home-tab buttons from appColor() whenever the background source is the theme (any theme, not just built-ins). - Ship AccentStrong / AccentSoft values in the Fusion and Default default palettes so the static home-tab buttons keep their classic greens. # Conflicts: # cockatrice/src/interface/widgets/general/home_widget.cpp * [AppColors] Address review comments - PaletteEditorDialog::onSave(): compare whole PaletteConfig (colors and appColors) so a change to only AccentStrong/AccentSoft writes the file; add PaletteConfig::operator==. - appColor(): derive both roles from QPalette::Highlight unconditionally. The Fusion palettes pin Accent to near-Window values, and QPalette::Accent only exists on Qt 6.6+, so keying on it made identical themes render very differently across Qt versions. - themeChangedSlot(): merge the theme default's [AppColors] into a custom palette that predates the section instead of all-or-nothing per file; hasPalette() now counts an appColors-only file as a palette. - Add Default/palette-default-light.toml so the Default theme's Light scheme keeps the classic greens instead of falling back to the OS accent. - home_widget: restore the isBuiltInTheme() half of the Automatic condition; non-built-in themes extract button colors from their own background art. - palette_grid_widget: use appEnum.value(i) for the role cast (3 sites), append appHeader to headerLabels, fix the 'Lighted' typo. * [Themes] Route theme writes to the user themes directory setColorScheme()/setStyleName() and the palette editor wrote directly to the resolved theme directory, which for built-in themes is the read-only system (install) location. Changes therefore landed in the install dir and were lost on upgrade. Add ThemeManager::writableThemeDir(), which always resolves to the user themes directory, and route all theme writes through it. The palette editor reuses the same helper, dropping its private writability probe. * [Home] Replace 'Automatic' button color with explicit theme colors default The Automatic option gated on isBuiltInTheme(): built-in themes used the theme's accent colors, while non-built-in themes extracted colors from their own background art. That made the result depend on the theme's origin rather than what the user actually sees. Remove Automatic and expose two explicit choices: 'From theme colors' (always the theme's identity accents, now the default) and 'Extract from background' (always sample the painted background). Drop the now-unused isBuiltInTheme() helper. --------- Co-authored-by: Lukas Brübach --- .../palette_editor/palette_editor_dialog.cpp | 36 +-------- .../palette_editor/palette_generator.cpp | 11 +++ .../palette_editor/palette_grid_widget.cpp | 64 +++++++++++++++ .../palette_editor/palette_grid_widget.h | 1 + cockatrice/src/interface/theme_config.cpp | 47 +++++++++-- cockatrice/src/interface/theme_config.h | 25 ++++++ cockatrice/src/interface/theme_manager.cpp | 78 +++++++++++++++++-- cockatrice/src/interface/theme_manager.h | 17 +++- .../widgets/general/home_tab_button_color.h | 10 +-- .../interface/widgets/general/home_widget.cpp | 19 ++--- .../appearance_settings_page.cpp | 2 +- .../themes/Default/palette-default-dark.toml | 4 + .../themes/Default/palette-default-light.toml | 67 ++++++++++++++++ .../themes/Fusion/palette-default-dark.toml | 4 + .../themes/Fusion/palette-default-light.toml | 4 + 15 files changed, 324 insertions(+), 65 deletions(-) create mode 100644 cockatrice/themes/Default/palette-default-light.toml diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 9cde72c01..d5a168708 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -1,6 +1,5 @@ #include "palette_editor_dialog.h" -#include "../../client/settings/cache_settings.h" #include "../theme_manager.h" #include "palette_generator.h" #include "palette_grid_widget.h" @@ -11,31 +10,11 @@ #include #include #include -#include #include -#include #include -#include #include #include -#include #include -#include - -// Probe whether a directory is truly writable by trying to create and remove a -// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably -// on Windows where UAC VirtualStore can make a system dir appear writable). -static bool isDirReallyWritable(const QString &dirPath) -{ - const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test"); - QFile f(probe); - if (!f.open(QIODevice::WriteOnly)) { - return false; - } - f.close(); - f.remove(); - return true; -} PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent) : QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName) @@ -46,14 +25,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Resolve a writable directory for saving. Built-in (Default / Fusion) and // other read-only theme directories must be customised in the user-writable // themes directory; otherwise the write would fail or be lost on upgrade. - if (!themeDirPath.isEmpty() && isDirReallyWritable(themeDirPath)) { - saveDir = themeDirPath; - } else { - saveDir = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName); - if (!QDir().mkpath(saveDir)) { - qWarning() << "Failed to create palette save directory:" << saveDir; - } - } + saveDir = ThemeManager::writableThemeDir(themeName); // Load both scheme configs upfront so switching is instant loadSchemes(); @@ -214,7 +186,7 @@ void PaletteEditorDialog::retranslateUi() resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); - if (saveDir.isEmpty() || !isDirReallyWritable(saveDir)) { + if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) { saveBtn->setEnabled(false); saveBtn->setToolTip(tr("Cannot save: this theme has no writable directory")); } @@ -297,7 +269,7 @@ void PaletteEditorDialog::onSave() if (it.key() == loadedScheme) { continue; } - if (it.value().colors == savedConfig.value(it.key()).colors) { + if (it.value() == savedConfig.value(it.key())) { continue; } if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) { @@ -308,7 +280,7 @@ void PaletteEditorDialog::onSave() } // Commit the active scheme last so the global colour scheme matches. - if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) { + if (workingConfig[loadedScheme] != savedConfig.value(loadedScheme)) { if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) { QMessageBox::warning(this, tr("Save failed"), tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir)); diff --git a/cockatrice/src/interface/palette_editor/palette_generator.cpp b/cockatrice/src/interface/palette_editor/palette_generator.cpp index d30dd14f1..822e57250 100644 --- a/cockatrice/src/interface/palette_editor/palette_generator.cpp +++ b/cockatrice/src/interface/palette_editor/palette_generator.cpp @@ -150,6 +150,17 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch cfg.colors[CG::Disabled][CR::HighlightedText] = disText; cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black; + // Accent: same primary hue as Highlight, so palettes derived from a + // QuickSetup accent always carry a matching Accent role. +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + set3(CR::Accent, hl, disText, hl); +#endif + + // Application role colors: Strong tracks the primary accent, while Soft is + // the lightened, desaturated companion used for button-gradient highlights. + cfg.appColors[AppColor::AccentStrong] = hl; + cfg.appColors[AppColor::AccentSoft] = hsl(accent.lightness() + 60, qRound(accent.hslSaturation() * 70 / 100.0)); + // BrightText QColor bright; if (achromatic) { diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp index 67294cd98..97d28b731 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -1,5 +1,7 @@ #include "palette_grid_widget.h" +#include "../theme_manager.h" + #include #include #include @@ -45,6 +47,11 @@ static const QMap ROLE_DESCRIPTIONS = { {QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")}, }; +static const QMap APP_ROLE_DESCRIPTIONS = { + {AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")}, + {AppColor::AccentSoft, QT_TR_NOOP("Lightened, desaturated accent (e.g. home-tab button gradient end)")}, +}; + PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) { scroll = new QScrollArea(this); @@ -122,6 +129,46 @@ void PaletteGridWidget::buildGrid(QWidget *host) grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter); } } + + // Application color section: one ColorButton per role below the role grid. + // These are not tied to a color group, so a single button spans the row. + QMetaEnum appEnum = QMetaEnum::fromType(); + + const int appHeaderRow = roles.size() + 1; + + auto *appHeader = new QLabel(tr("App colors"), host); + appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); + QFont appHeaderFont = appHeader->font(); + appHeaderFont.setBold(true); + appHeader->setFont(appHeaderFont); + appHeader->setAutoFillBackground(true); + appHeader->setContentsMargins(4, 4, 4, 4); + grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); + headerLabels.append(appHeader); + + for (int i = 0; i < appEnum.keyCount(); ++i) { + auto role = static_cast(appEnum.value(i)); + const int row = appHeaderRow + 1 + i; + + if (i % 2 == 0) { + for (int col = 0; col < 4; ++col) { + auto *shade = new QWidget(host); + shade->setAutoFillBackground(true); + grid->addWidget(shade, row, col); + rowShadeWidgets.push_back(shade); + } + } + + auto *label = new QLabel(QString(appEnum.valueToKey(role)), host); + label->setToolTip(APP_ROLE_DESCRIPTIONS.value(role, {})); + label->setContentsMargins(4, 2, 8, 2); + grid->addWidget(label, row, 0); + + auto *btn = new ColorButton(host); + connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); }); + appColorButtons[role] = btn; + grid->addWidget(btn, row, 1, Qt::AlignHCenter | Qt::AlignVCenter); + } } void PaletteGridWidget::changeEvent(QEvent *e) @@ -166,6 +213,16 @@ void PaletteGridWidget::loadPalette(const PaletteConfig &cfg) colorButtons[group][role]->setColor(color); } } + + QMetaEnum appEnum = QMetaEnum::fromType(); + for (int i = 0; i < appEnum.keyCount(); ++i) { + auto role = static_cast(appEnum.value(i)); + QColor color = cfg.appColors.value(role); + if (!color.isValid()) { + color = themeManager->appColor(role); + } + appColorButtons[role]->setColor(color); + } } PaletteConfig PaletteGridWidget::currentPaletteConfig() const @@ -176,5 +233,12 @@ PaletteConfig PaletteGridWidget::currentPaletteConfig() const cfg.colors[group][role] = colorButtons[group][role]->getColor(); } } + + QMetaEnum appEnum = QMetaEnum::fromType(); + for (int i = 0; i < appEnum.keyCount(); ++i) { + auto role = static_cast(appEnum.value(i)); + cfg.appColors[role] = appColorButtons[role]->getColor(); + } + return cfg; } \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h index 1a665971a..77cbf1c62 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -31,6 +31,7 @@ private: void refreshChromePalettes(); QMap> colorButtons; + QMap appColorButtons; QScrollArea *scroll; QWidget *gridHost; QVBoxLayout *layout; diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 3c43c467d..4420eefe1 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -96,7 +96,7 @@ bool ThemeConfig::save(const QString &themeDirPath) const bool PaletteConfig::hasPalette() const { - return !colors.isEmpty(); + return !colors.isEmpty() || !appColors.isEmpty(); } QString PaletteConfig::toToml() const @@ -133,6 +133,24 @@ QString PaletteConfig::toToml() const out += "\n"; } + if (!appColors.isEmpty()) { + QMetaEnum appEnum = QMetaEnum::fromType(); + + out += "[AppColors]\n"; + + for (auto it = appColors.cbegin(); it != appColors.cend(); ++it) { + const char *roleName = appEnum.valueToKey(it.key()); + + if (!roleName) { + continue; + } + + out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb)); + } + + out += "\n"; + } + return out; } @@ -152,6 +170,7 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) } QMetaEnum roleEnum = QMetaEnum::fromType(); + QMetaEnum appEnum = QMetaEnum::fromType(); QString currentSection; QPalette::ColorGroup currentGroup = QPalette::Active; @@ -202,6 +221,26 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) } } + QColor color(value); + + if (!color.isValid()) { + continue; + } + + if (currentSection.compare("AppColors", Qt::CaseInsensitive) == 0) { + if (key.startsWith("AppColor::")) { + key = key.mid(10); + } + + int appRoleInt = appEnum.keyToValue(key.toUtf8().constData()); + + if (appRoleInt >= 0) { + cfg.appColors[static_cast(appRoleInt)] = color; + } + + continue; + } + if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) { continue; } @@ -216,11 +255,7 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) continue; } - QColor color(value); - - if (color.isValid()) { - cfg.colors[currentGroup][static_cast(roleInt)] = color; - } + cfg.colors[currentGroup][static_cast(roleInt)] = color; } return cfg; diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 07bf55b7a..567aeccda 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -3,9 +3,25 @@ #include #include +#include #include #include +// Application-specific color roles, layered on top of the fixed QPalette role +// set. Stored in the same palette-.toml under an [AppColors] section +// and editable from the palette editor, so theme authors can control colors +// beyond what Qt's palette can express. +namespace AppColor +{ +Q_NAMESPACE +enum Role +{ + AccentStrong, + AccentSoft, +}; +Q_ENUM_NS(Role) +} // namespace AppColor + struct ThemeConfig { QString colorScheme; @@ -21,7 +37,16 @@ struct ThemeConfig struct PaletteConfig { QMap> colors; + QMap appColors; + bool operator==(const PaletteConfig &rhs) const + { + return colors == rhs.colors && appColors == rhs.appColors; + } + bool operator!=(const PaletteConfig &rhs) const + { + return !(*this == rhs); + } bool hasPalette() const; QString toToml() const; diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 12c8fad2c..da9f14408 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -184,11 +185,32 @@ QString ThemeManager::assetPath(QStringView prefix) const return resolvedPlain.isEmpty() ? prefix.toString() : resolvedPlain; } -bool ThemeManager::isBuiltInTheme() +// Probe whether a directory is truly writable by trying to create and remove a +// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably +// on Windows where UAC VirtualStore can make a system dir appear writable). +bool ThemeManager::isDirReallyWritable(const QString &dirPath) { - const auto themeName = SettingsCache::instance().getThemeName(); + const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test"); + QFile f(probe); + if (!f.open(QIODevice::WriteOnly)) { + return false; + } + f.close(); + f.remove(); + return true; +} - return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME; +QString ThemeManager::writableThemeDir(const QString &themeName) +{ + // All theme writes go to the user themes directory regardless of whether + // the resolved (system) theme directory happens to be writable. Even when a + // write would succeed in-place, routing it to the user directory keeps the + // install intact and guarantees changes survive upgrades. + const QString dirPath = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName); + if (!QDir().mkpath(dirPath)) { + qWarning() << "Failed to create theme save directory:" << dirPath; + } + return dirPath; } // System (read-only) themes location, relative to the application binary. @@ -331,7 +353,7 @@ bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &col void ThemeManager::setColorScheme(const QString &scheme) { - const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); + const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName()); ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); cfg.colorScheme = scheme; @@ -342,7 +364,7 @@ void ThemeManager::setColorScheme(const QString &scheme) void ThemeManager::setStyleName(const QString &styleName) { - const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); + const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName()); ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); cfg.styleName = styleName; @@ -416,6 +438,8 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, qApp->setPalette(base); qApp->setStyle(style); + currentAppColors = palCfg.appColors; + // Force every widget to re-polish and repaint immediately rather than // waiting for natural expose events, which produces a patchwork of old // and new colours during a live preview. @@ -428,6 +452,35 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, style->polish(widget); widget->update(); } + + emit paletteChanged(); +} + +QColor ThemeManager::appColor(AppColor::Role role) const +{ + const auto it = currentAppColors.constFind(role); + if (it != currentAppColors.constEnd()) { + return it.value(); + } + + // QPalette::Accent was introduced in Qt 6.6 and several shipped palettes + // set it to a value barely distinguishable from Window, so it is not a + // reliable accent source. The selection highlight is the stable accent + // (Accent defaults to Highlight when unset), and deriving from it + // unconditionally keeps every Qt version rendering identically. + const QColor accent = qApp->palette().color(QPalette::Active, QPalette::Highlight); + + if (role == AppColor::AccentSoft) { + constexpr int SOFT_SATURATION_PERCENT = 70; + constexpr int SOFT_LIGHTNESS_OFFSET = 60; + + // Light end of the gradient: same hue, softened and lightened + return QColor::fromHsl(qMax(0, accent.hslHue()), + qBound(0, qRound(accent.hslSaturation() * SOFT_SATURATION_PERCENT / 100.0), 255), + qBound(0, accent.lightness() + SOFT_LIGHTNESS_OFFSET, 255)); + } + + return accent; } void ThemeManager::themeChangedSlot() @@ -464,8 +517,19 @@ void ThemeManager::themeChangedSlot() // ── Load palette: custom first, then theme default ──────────────────── PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme); - if (!palette.hasPalette()) { - palette = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme); + const PaletteConfig themeDefault = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme); + if (palette.hasPalette()) { + // A custom palette written before [AppColors] existed carries no app + // colors; merge the theme's shipped defaults so the identity colors + // survive (hasPalette() counts an app-colors-only file as a palette, + // so those are kept wholesale and never reach here empty). + for (auto it = themeDefault.appColors.cbegin(); it != themeDefault.appColors.cend(); ++it) { + if (!palette.appColors.contains(it.key())) { + palette.appColors.insert(it.key(), it.value()); + } + } + } else { + palette = themeDefault; } applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index ac35042a0..aadb38ee9 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -50,6 +50,7 @@ private: QString currentThemePath; std::array brushes; QStringMap availableThemes; + QMap currentAppColors; /* Internal cache for multiple backgrounds */ @@ -65,7 +66,16 @@ protected: const QString &activeScheme); public: - bool isBuiltInTheme(); + // Resolves the directory to write theme changes to for the given theme + // name. The resolved theme dir (user or system) is used when writable; + // read-only system themes fall back to the user themes directory, creating + // it if needed, so customisations never get lost on upgrade. + static QString writableThemeDir(const QString &themeName); + // Probe whether a directory is truly writable by trying to create and remove + // a temporary file. QFileInfo::isWritable() on a directory is unreliable + // (notably on Windows where UAC VirtualStore can make a system dir appear + // writable). + static bool isDirReallyWritable(const QString &dirPath); // Explicit color scheme of the theme: theme.cfg's ColorScheme setting // (Dark/Light), falling back to the OS color scheme when it is "System". bool isDarkMode(const QString &themeDirPath) const; @@ -115,12 +125,17 @@ public: void reloadCurrentTheme(); void previewPalette(const PaletteConfig &cfg, const QString &scheme); + // Resolves an application color role: the theme's stored [AppColors] value + // when present, otherwise a palette-accent-derived fallback. + QColor appColor(AppColor::Role role) const; + QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); protected slots: void themeChangedSlot(); signals: void themeChanged(); + void paletteChanged(); }; extern ThemeManager *themeManager; diff --git a/cockatrice/src/interface/widgets/general/home_tab_button_color.h b/cockatrice/src/interface/widgets/general/home_tab_button_color.h index 1550b57e7..b45bd7a92 100644 --- a/cockatrice/src/interface/widgets/general/home_tab_button_color.h +++ b/cockatrice/src/interface/widgets/general/home_tab_button_color.h @@ -11,8 +11,8 @@ namespace HomeTabButtonColor */ enum Source { - Automatic, ///< Extract color from background, or use theme color if no background - FromBackground, ///< Always extract color from background + FromThemeColors, ///< Use the theme's identity accent colors + FromBackground, ///< Extract colour from the background image }; struct Entry @@ -23,7 +23,7 @@ struct Entry inline QList all() { - static QList entries = {{Automatic, QT_TR_NOOP("Automatic")}, + static QList entries = {{FromThemeColors, QT_TR_NOOP("From theme colors")}, {FromBackground, QT_TR_NOOP("Extract from background")}}; return entries; @@ -33,12 +33,12 @@ inline QList all() * Safely converts an int into the corresponding Source. * * @param value The int value - * @return The Source. Returns Source::Automatic if the value is not within range + * @return The Source. Returns Source::FromThemeColors if the value is not within range */ inline Source intToSource(int value) { if (value > FromBackground) { - return Automatic; // default + return FromThemeColors; // default } return static_cast(value); diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 0d030b973..27fd065a5 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -61,6 +61,7 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) // Scheme flips (light/dark/system with an OS switch) fire on themeManager, // not on SettingsCache::themeChanged, so re-resolve the variant background. connect(themeManager, &ThemeManager::themeChanged, this, &HomeWidget::initializeBackgroundFromSource); + connect(themeManager, &ThemeManager::paletteChanged, this, &HomeWidget::updateButtonsToBackgroundColor); connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabButtonColorChanged, this, &HomeWidget::updateButtonsToBackgroundColor); } @@ -105,32 +106,24 @@ void HomeWidget::loadBackgroundSourceDeck() backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList(); } -static bool isDefaultBackgroundAndTheme() +static QPair paletteDerivedButtonColors() { - QString sourceId = SettingsCache::instance().appearance().getHomeTabBackgroundSource(); - return themeManager->isBuiltInTheme() && BackgroundSources::fromId(sourceId) == BackgroundSources::Theme; + return {themeManager->appColor(AppColor::AccentStrong), themeManager->appColor(AppColor::AccentSoft)}; } QPair HomeWidget::determineButtonColor() const { - static QPair defaultColor = {QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80)}; - auto colorSource = HomeTabButtonColor::intToSource(SettingsCache::instance().appearance().getHomeTabButtonColorSourceIndex()); switch (colorSource) { - case HomeTabButtonColor::Automatic: { - if (isDefaultBackgroundAndTheme()) { - return defaultColor; - } else { - return extractDominantColors(background); - } - } + case HomeTabButtonColor::FromThemeColors: + return paletteDerivedButtonColors(); case HomeTabButtonColor::FromBackground: return extractDominantColors(background); } - return defaultColor; + return paletteDerivedButtonColors(); } void HomeWidget::setRandomCard(ExactCard &newCard) 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 c8494f095..c45373757 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -510,7 +510,7 @@ void AppearanceSettingsPage::retranslateUi() homeTabDisplayCardNameCheckBox.setText(tr("Display card name of background in bottom right")); homeTabButtonColorSourceLabel.setText(tr("Home tab button color:")); homeTabButtonColorSourceBox.setToolTip( - tr("Automatic: extract from background if present, otherwise use theme default")); + tr("Use the theme's identity accent colors, or extract colors from the background image")); playmatGroupBox->setTitle(tr("Playmat settings")); playmatVisibilityLabel.setText(tr("Playmat visibility:")); diff --git a/cockatrice/themes/Default/palette-default-dark.toml b/cockatrice/themes/Default/palette-default-dark.toml index 3ee174a2f..e101935b4 100644 --- a/cockatrice/themes/Default/palette-default-dark.toml +++ b/cockatrice/themes/Default/palette-default-dark.toml @@ -61,3 +61,7 @@ ToolTipBase = #ffffffdc ToolTipText = #ff000000 PlaceholderText = #6effffff + +[AppColors] +AccentStrong = #ff148c3c +AccentSoft = #ff78c850 diff --git a/cockatrice/themes/Default/palette-default-light.toml b/cockatrice/themes/Default/palette-default-light.toml new file mode 100644 index 000000000..14c215cdf --- /dev/null +++ b/cockatrice/themes/Default/palette-default-light.toml @@ -0,0 +1,67 @@ +[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 +HighlightedText = #ffffffff +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 + +[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 +HighlightedText = #ffffffff +Link = #ff0000ff +LinkVisited = #ffff00ff +AlternateBase = #fff7f7f7 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 + +[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 +HighlightedText = #ff000000 +Link = #ff0d5f28 +LinkVisited = #ff08401b +AlternateBase = #ffe9e7e3 +ToolTipBase = #ffffffdc +ToolTipText = #ff000000 +PlaceholderText = #80000000 + + +[AppColors] +AccentStrong = #ff148c3c +AccentSoft = #ff78c850 \ No newline at end of file diff --git a/cockatrice/themes/Fusion/palette-default-dark.toml b/cockatrice/themes/Fusion/palette-default-dark.toml index c1d83a4cd..a988c2f14 100644 --- a/cockatrice/themes/Fusion/palette-default-dark.toml +++ b/cockatrice/themes/Fusion/palette-default-dark.toml @@ -67,3 +67,7 @@ ToolTipText = #ffd4d4d4 PlaceholderText = #80ffffff Accent = #ff1e1e1e + +[AppColors] +AccentStrong = #ff148c3c +AccentSoft = #ff78c850 diff --git a/cockatrice/themes/Fusion/palette-default-light.toml b/cockatrice/themes/Fusion/palette-default-light.toml index 86c41be78..b72b79b11 100644 --- a/cockatrice/themes/Fusion/palette-default-light.toml +++ b/cockatrice/themes/Fusion/palette-default-light.toml @@ -67,3 +67,7 @@ ToolTipText = #ff000000 PlaceholderText = #80000000 Accent = #fff0f0f0 + +[AppColors] +AccentStrong = #ff148c3c +AccentSoft = #ff78c850