mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
[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-<scheme>.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
This commit is contained in:
parent
1c93309952
commit
7da67c2a3f
11 changed files with 195 additions and 12 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "palette_grid_widget.h"
|
||||
|
||||
#include "../theme_manager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
|
@ -45,6 +47,11 @@ static const QMap<QPalette::ColorRole, const char *> ROLE_DESCRIPTIONS = {
|
|||
{QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")},
|
||||
};
|
||||
|
||||
static const QMap<AppColor::Role, const char *> APP_ROLE_DESCRIPTIONS = {
|
||||
{AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")},
|
||||
{AppColor::AccentSoft, QT_TR_NOOP("Lighted, desaturated accent (e.g. home-tab button gradient end)")},
|
||||
};
|
||||
|
||||
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
scroll = new QScrollArea(this);
|
||||
|
|
@ -122,6 +129,45 @@ 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<AppColor::Role>();
|
||||
|
||||
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);
|
||||
|
||||
for (int i = 0; i < appEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppColor::Role>(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 +212,16 @@ void PaletteGridWidget::loadPalette(const PaletteConfig &cfg)
|
|||
colorButtons[group][role]->setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
|
||||
for (int i = 0; i < appEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppColor::Role>(i);
|
||||
QColor color = cfg.appColors.value(role);
|
||||
if (!color.isValid()) {
|
||||
color = themeManager->appColor(role);
|
||||
}
|
||||
appColorButtons[role]->setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
|
||||
|
|
@ -176,5 +232,12 @@ PaletteConfig PaletteGridWidget::currentPaletteConfig() const
|
|||
cfg.colors[group][role] = colorButtons[group][role]->getColor();
|
||||
}
|
||||
}
|
||||
|
||||
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
|
||||
for (int i = 0; i < appEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppColor::Role>(i);
|
||||
cfg.appColors[role] = appColorButtons[role]->getColor();
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ private:
|
|||
void refreshChromePalettes();
|
||||
|
||||
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
|
||||
QMap<AppColor::Role, ColorButton *> appColorButtons;
|
||||
QScrollArea *scroll;
|
||||
QWidget *gridHost;
|
||||
QVBoxLayout *layout;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,24 @@ QString PaletteConfig::toToml() const
|
|||
out += "\n";
|
||||
}
|
||||
|
||||
if (!appColors.isEmpty()) {
|
||||
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
|
||||
|
||||
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<QPalette::ColorRole>();
|
||||
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
|
||||
|
||||
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<AppColor::Role>(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<QPalette::ColorRole>(roleInt)] = color;
|
||||
}
|
||||
cfg.colors[currentGroup][static_cast<QPalette::ColorRole>(roleInt)] = color;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,25 @@
|
|||
|
||||
#include <QColor>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPalette>
|
||||
#include <QString>
|
||||
|
||||
// Application-specific color roles, layered on top of the fixed QPalette role
|
||||
// set. Stored in the same palette-<scheme>.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,6 +37,7 @@ struct ThemeConfig
|
|||
struct PaletteConfig
|
||||
{
|
||||
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, QColor>> colors;
|
||||
QMap<AppColor::Role, QColor> appColors;
|
||||
|
||||
bool hasPalette() const;
|
||||
QString toToml() const;
|
||||
|
|
|
|||
|
|
@ -416,6 +416,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 +430,37 @@ 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; before that the nearest
|
||||
// accent is the selection highlight, which Accent defaults to when unset.
|
||||
const QColor accent = qApp->palette().color(QPalette::Active,
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
QPalette::Accent);
|
||||
#else
|
||||
QPalette::Highlight);
|
||||
#endif
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ private:
|
|||
QString currentThemePath;
|
||||
std::array<QBrush, Role::MaxRole + 1> brushes;
|
||||
QStringMap availableThemes;
|
||||
QMap<AppColor::Role, QColor> currentAppColors;
|
||||
/*
|
||||
Internal cache for multiple backgrounds
|
||||
*/
|
||||
|
|
@ -115,12 +116,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;
|
||||
|
|
|
|||
|
|
@ -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,23 +106,27 @@ void HomeWidget::loadBackgroundSourceDeck()
|
|||
backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList();
|
||||
}
|
||||
|
||||
static bool isDefaultBackgroundAndTheme()
|
||||
static bool usesThemeBackground()
|
||||
{
|
||||
QString sourceId = SettingsCache::instance().appearance().getHomeTabBackgroundSource();
|
||||
return themeManager->isBuiltInTheme() && BackgroundSources::fromId(sourceId) == BackgroundSources::Theme;
|
||||
return BackgroundSources::fromId(sourceId) == BackgroundSources::Theme;
|
||||
}
|
||||
|
||||
static QPair<QColor, QColor> paletteDerivedButtonColors()
|
||||
{
|
||||
return {themeManager->appColor(AppColor::AccentStrong), themeManager->appColor(AppColor::AccentSoft)};
|
||||
}
|
||||
|
||||
QPair<QColor, QColor> 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;
|
||||
if (usesThemeBackground()) {
|
||||
// Static theme background: follow the theme's accent colors.
|
||||
return paletteDerivedButtonColors();
|
||||
} else {
|
||||
return extractDominantColors(background);
|
||||
}
|
||||
|
|
@ -130,7 +135,7 @@ QPair<QColor, QColor> HomeWidget::determineButtonColor() const
|
|||
return extractDominantColors(background);
|
||||
}
|
||||
|
||||
return defaultColor;
|
||||
return paletteDerivedButtonColors();
|
||||
}
|
||||
|
||||
void HomeWidget::setRandomCard(ExactCard &newCard)
|
||||
|
|
|
|||
|
|
@ -61,3 +61,7 @@ ToolTipBase = #ffffffdc
|
|||
ToolTipText = #ff000000
|
||||
PlaceholderText = #6effffff
|
||||
|
||||
|
||||
[AppColors]
|
||||
AccentStrong = #ff148c3c
|
||||
AccentSoft = #ff78c850
|
||||
|
|
|
|||
|
|
@ -67,3 +67,7 @@ ToolTipText = #ffd4d4d4
|
|||
PlaceholderText = #80ffffff
|
||||
Accent = #ff1e1e1e
|
||||
|
||||
|
||||
[AppColors]
|
||||
AccentStrong = #ff148c3c
|
||||
AccentSoft = #ff78c850
|
||||
|
|
|
|||
|
|
@ -67,3 +67,7 @@ ToolTipText = #ff000000
|
|||
PlaceholderText = #80000000
|
||||
Accent = #fff0f0f0
|
||||
|
||||
|
||||
[AppColors]
|
||||
AccentStrong = #ff148c3c
|
||||
AccentSoft = #ff78c850
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue