Cockatrice/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
Lukas Brübach 7549f9c2cf
[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.
2026-09-12 18:00:28 +02:00

244 lines
No EOL
8.5 KiB
C++

#include "palette_grid_widget.h"
#include "../theme_manager.h"
#include <QApplication>
#include <QGridLayout>
#include <QLabel>
#include <QMetaEnum>
#include <QScrollArea>
#include <QTimer>
#include <QVBoxLayout>
static QList<QPalette::ColorRole> allRoles()
{
QList<QPalette::ColorRole> roles;
for (int i = 0; i < QPalette::NColorRoles; ++i) {
auto r = static_cast<QPalette::ColorRole>(i);
if (r != QPalette::NoRole) {
roles << r;
}
}
return roles;
}
static const QList<QPalette::ColorGroup> ALL_GROUPS = {QPalette::Active, QPalette::Disabled, QPalette::Inactive};
static const QMap<QPalette::ColorRole, const char *> ROLE_DESCRIPTIONS = {
{QPalette::Window, QT_TR_NOOP("Main window / dialog background")},
{QPalette::WindowText, QT_TR_NOOP("Text drawn on Window")},
{QPalette::Base, QT_TR_NOOP("Background for text input widgets")},
{QPalette::Text, QT_TR_NOOP("Text in input widgets")},
{QPalette::Button, QT_TR_NOOP("Button background")},
{QPalette::ButtonText, QT_TR_NOOP("Button label text")},
{QPalette::BrightText, QT_TR_NOOP("High-contrast text (e.g. checked items)")},
{QPalette::Highlight, QT_TR_NOOP("Selection / focus highlight")},
{QPalette::HighlightedText, QT_TR_NOOP("Text on top of Highlight")},
{QPalette::Link, QT_TR_NOOP("Unvisited hyperlink")},
{QPalette::LinkVisited, QT_TR_NOOP("Visited hyperlink")},
{QPalette::AlternateBase, QT_TR_NOOP("Alternating row background in views")},
{QPalette::ToolTipBase, QT_TR_NOOP("Tooltip background")},
{QPalette::ToolTipText, QT_TR_NOOP("Tooltip text")},
{QPalette::PlaceholderText, QT_TR_NOOP("Placeholder / hint text in inputs")},
{QPalette::Light, QT_TR_NOOP("Lighter than Button (3-D highlight)")},
{QPalette::Midlight, QT_TR_NOOP("Between Button and Light")},
{QPalette::Mid, QT_TR_NOOP("Between Button and Dark")},
{QPalette::Dark, QT_TR_NOOP("Darker than Button (3-D shadow)")},
{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("Lightened, desaturated accent (e.g. home-tab button gradient end)")},
};
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
{
scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
gridHost = new QWidget;
buildGrid(gridHost);
refreshChromePalettes();
scroll->setWidget(gridHost);
layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(scroll);
}
void PaletteGridWidget::buildGrid(QWidget *host)
{
QMetaEnum roleEnum = QMetaEnum::fromType<QPalette::ColorRole>();
auto *grid = new QGridLayout(host);
grid->setSpacing(3);
grid->setContentsMargins(12, 8, 12, 8);
grid->setColumnStretch(0, 1);
grid->setColumnStretch(1, 0);
grid->setColumnStretch(2, 0);
grid->setColumnStretch(3, 0);
// Column headers
const QStringList groupHeaders = {tr("Active"), tr("Disabled"), tr("Inactive")};
const QStringList groupTips = {
tr("Normal interactive state"),
tr("Widget is disabled / not interactive"),
tr("Window is in background / unfocused"),
};
for (int col = 0; col < 3; ++col) {
auto *label = new QLabel(groupHeaders[col], host);
label->setAlignment(Qt::AlignCenter);
label->setToolTip(groupTips[col]);
QFont f = label->font();
f.setBold(true);
label->setFont(f);
label->setAutoFillBackground(true);
label->setContentsMargins(4, 4, 4, 4);
grid->addWidget(label, 0, col + 1);
headerLabels.push_back(label);
}
// Role rows
const auto roles = allRoles();
for (int row = 0; row < roles.size(); ++row) {
auto role = roles[row];
const char *name = roleEnum.valueToKey(role);
// Alternating row shade
if (row % 2 == 0) {
for (int col = 0; col < 4; ++col) {
auto *shade = new QWidget(host);
shade->setAutoFillBackground(true);
grid->addWidget(shade, row + 1, col);
rowShadeWidgets.push_back(shade);
}
}
auto *label = new QLabel(QString(name), host);
label->setToolTip(ROLE_DESCRIPTIONS.value(role, {}));
label->setContentsMargins(4, 2, 8, 2);
grid->addWidget(label, row + 1, 0);
for (int col = 0; col < 3; ++col) {
auto group = ALL_GROUPS[col];
auto *btn = new ColorButton(host);
connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); });
colorButtons[group][role] = btn;
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);
headerLabels.append(appHeader);
for (int i = 0; i < appEnum.keyCount(); ++i) {
auto role = static_cast<AppColor::Role>(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)
{
if (e->type() == QEvent::PaletteChange) {
QTimer::singleShot(0, this, &PaletteGridWidget::refreshChromePalettes);
}
QWidget::changeEvent(e);
}
void PaletteGridWidget::refreshChromePalettes()
{
const QPalette base = qApp->palette();
const QColor alt = base.color(QPalette::AlternateBase);
// Header labels
for (auto *label : headerLabels) {
QPalette lp = label->palette();
lp.setColor(QPalette::Window, alt);
label->setPalette(lp);
label->update();
}
// Alternating row backgrounds
for (auto *shade : rowShadeWidgets) {
QPalette sp = shade->palette();
sp.setColor(QPalette::Window, alt);
shade->setPalette(sp);
shade->update();
}
}
void PaletteGridWidget::loadPalette(const PaletteConfig &cfg)
{
for (auto group : ALL_GROUPS) {
for (auto role : allRoles()) {
QColor color = cfg.colors.value(group).value(role);
if (!color.isValid()) {
color = qApp->palette().color(group, role);
}
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>(appEnum.value(i));
QColor color = cfg.appColors.value(role);
if (!color.isValid()) {
color = themeManager->appColor(role);
}
appColorButtons[role]->setColor(color);
}
}
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
{
PaletteConfig cfg;
for (auto group : ALL_GROUPS) {
for (auto role : allRoles()) {
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>(appEnum.value(i));
cfg.appColors[role] = appColorButtons[role]->getColor();
}
return cfg;
}