mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-09 09:33:57 -07:00
[App/Theme] Palette Editor (#6877)
* [App/Theme] Palette Editor Took 1 minute Took 1 hour 47 minutes Took 6 seconds Took 3 minutes Took 5 minutes Took 3 minutes * Add oracle, add palette files and configs. Took 10 minutes * Fix a stupid include mistake, thanks IDE Took 3 minutes Took 20 seconds * Includes. Took 4 minutes * Fix ampersand not displaying correctly. Took 14 minutes * Longer variable names. Took 10 minutes Took 5 seconds * Change ampersand everywhere Took 23 seconds * Doxygen properly. Took 1 minute * Remove namespace, fold I/O into structs. Took 12 minutes * Remove namespace, fold I/O into structs. Took 33 seconds * Alphabetize. Took 35 seconds * Lint. Took 49 seconds * Add a combo box to quick switch settings. Took 19 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
989a5be23b
commit
117ea543c5
23 changed files with 1874 additions and 232 deletions
72
cockatrice/src/interface/palette_editor/color_button.cpp
Normal file
72
cockatrice/src/interface/palette_editor/color_button.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "color_button.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPainter>
|
||||
|
||||
ColorButton::ColorButton(QWidget *parent) : QToolButton(parent)
|
||||
{
|
||||
setFixedSize(52, 24);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setToolTip(tr("Click to pick a color"));
|
||||
connect(this, &QToolButton::clicked, this, &ColorButton::pickColor);
|
||||
}
|
||||
|
||||
void ColorButton::setColor(const QColor &c)
|
||||
{
|
||||
if (color == c) {
|
||||
return;
|
||||
}
|
||||
color = c;
|
||||
updateSwatch();
|
||||
emit colorChanged(c);
|
||||
}
|
||||
|
||||
void ColorButton::pickColor()
|
||||
{
|
||||
QColor chosen = QColorDialog::getColor(color, this, tr("Pick colour"), QColorDialog::ShowAlphaChannel);
|
||||
if (chosen.isValid()) {
|
||||
setColor(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorButton::updateSwatch()
|
||||
{
|
||||
QPixmap pixmap(size() * devicePixelRatioF());
|
||||
pixmap.setDevicePixelRatio(devicePixelRatioF());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Checkerboard for alpha
|
||||
const int cellSize = 4;
|
||||
for (int y = 0; y < height(); y += cellSize) {
|
||||
for (int x = 0; x < width(); x += cellSize) {
|
||||
painter.fillRect(x, y, cellSize, cellSize,
|
||||
((x / cellSize + y / cellSize) % 2) ? QColor(180, 180, 180) : Qt::white);
|
||||
}
|
||||
}
|
||||
|
||||
// Color fill
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(color);
|
||||
painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3);
|
||||
|
||||
// Border
|
||||
QColor border = palette().color(QPalette::Shadow);
|
||||
border.setAlpha(180);
|
||||
painter.setPen(QPen(border, 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1), 3, 3);
|
||||
|
||||
// Hex label — black or white for contrast
|
||||
int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue();
|
||||
painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200));
|
||||
QFont f = font();
|
||||
f.setPixelSize(8);
|
||||
painter.setFont(f);
|
||||
painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper());
|
||||
|
||||
setIcon(QIcon(pixmap));
|
||||
setIconSize(size());
|
||||
setText({});
|
||||
}
|
||||
30
cockatrice/src/interface/palette_editor/color_button.h
Normal file
30
cockatrice/src/interface/palette_editor/color_button.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef COCKATRICE_COLOR_BUTTON_H
|
||||
#define COCKATRICE_COLOR_BUTTON_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QToolButton>
|
||||
|
||||
class ColorButton : public QToolButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ColorButton(QWidget *parent = nullptr);
|
||||
|
||||
QColor getColor() const
|
||||
{
|
||||
return color;
|
||||
}
|
||||
void setColor(const QColor &c);
|
||||
|
||||
signals:
|
||||
void colorChanged(const QColor &color);
|
||||
|
||||
private slots:
|
||||
void pickColor();
|
||||
|
||||
private:
|
||||
void updateSwatch();
|
||||
QColor color;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COLOR_BUTTON_H
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
#include "palette_editor_dialog.h"
|
||||
|
||||
#include "../theme_manager.h"
|
||||
#include "palette_generator.h"
|
||||
#include "palette_grid_widget.h"
|
||||
#include "quick_setup_panel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFrame>
|
||||
#include <QGuiApplication>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QStyleHints>
|
||||
#include <QTimer>
|
||||
|
||||
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
|
||||
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
|
||||
{
|
||||
setMinimumSize(740, 220);
|
||||
setupUi();
|
||||
|
||||
// Load both scheme configs upfront so switching is instant
|
||||
loadSchemes();
|
||||
|
||||
loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
|
||||
|
||||
schemeComboBox->blockSignals(true);
|
||||
schemeComboBox->setCurrentText(loadedScheme);
|
||||
schemeComboBox->blockSignals(false);
|
||||
|
||||
paletteGrid->loadPalette(workingConfig[loadedScheme]);
|
||||
seedAccentFromScheme(loadedScheme);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::setupUi()
|
||||
{
|
||||
auto *root = new QVBoxLayout(this);
|
||||
root->setSpacing(0);
|
||||
root->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Header
|
||||
header = new QWidget;
|
||||
header->setAutoFillBackground(true);
|
||||
{
|
||||
QPalette hp = header->palette();
|
||||
hp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(108));
|
||||
header->setPalette(hp);
|
||||
}
|
||||
auto *headerLayout = new QHBoxLayout(header);
|
||||
headerLayout->setContentsMargins(12, 8, 12, 8);
|
||||
|
||||
titleLabel = new QLabel(this);
|
||||
titleLabel->setTextFormat(Qt::RichText);
|
||||
|
||||
editingLabel = new QLabel(this);
|
||||
|
||||
schemeComboBox = new QComboBox;
|
||||
schemeComboBox->addItems({"Light", "Dark"});
|
||||
schemeComboBox->setFixedWidth(90);
|
||||
|
||||
headerLayout->addWidget(titleLabel);
|
||||
headerLayout->addStretch();
|
||||
headerLayout->addWidget(editingLabel);
|
||||
headerLayout->addWidget(schemeComboBox);
|
||||
root->addWidget(header);
|
||||
|
||||
auto makeSeparator = [&]() {
|
||||
auto *sep = new QFrame;
|
||||
sep->setFrameShape(QFrame::HLine);
|
||||
sep->setFrameShadow(QFrame::Plain);
|
||||
sep->setFixedHeight(1);
|
||||
return sep;
|
||||
};
|
||||
|
||||
root->addWidget(makeSeparator());
|
||||
|
||||
// Quick Setup panel
|
||||
quickSetupPanel = new QuickSetupPanel;
|
||||
quickSetupPanel->setAutoFillBackground(true);
|
||||
|
||||
QPalette sp = quickSetupPanel->palette();
|
||||
sp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(102));
|
||||
quickSetupPanel->setPalette(sp);
|
||||
|
||||
root->addWidget(quickSetupPanel);
|
||||
root->addWidget(makeSeparator());
|
||||
|
||||
// Toggle button — acts as a section header for the advanced area
|
||||
paletteGridToggleButton = new QPushButton(this);
|
||||
paletteGridToggleButton->setCheckable(true);
|
||||
paletteGridToggleButton->setChecked(false);
|
||||
paletteGridToggleButton->setFlat(true);
|
||||
paletteGridToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }"
|
||||
"QPushButton:checked { }");
|
||||
root->addWidget(paletteGridToggleButton);
|
||||
|
||||
// Separator + grid start hidden; revealed by the toggle
|
||||
paletteGridSeparator = makeSeparator();
|
||||
paletteGridSeparator->setVisible(false);
|
||||
root->addWidget(paletteGridSeparator);
|
||||
|
||||
paletteGrid = new PaletteGridWidget;
|
||||
paletteGrid->setVisible(false);
|
||||
root->addWidget(paletteGrid, 1);
|
||||
|
||||
// Footer
|
||||
root->addWidget(makeSeparator());
|
||||
footer = new QWidget;
|
||||
footer->setAutoFillBackground(true);
|
||||
{
|
||||
QPalette fp = footer->palette();
|
||||
fp.setColor(QPalette::Window, qApp->palette().color(QPalette::Window).darker(104));
|
||||
footer->setPalette(fp);
|
||||
}
|
||||
auto *footerLayout = new QHBoxLayout(footer);
|
||||
footerLayout->setContentsMargins(12, 8, 12, 8);
|
||||
|
||||
revertButton = new QPushButton(this);
|
||||
|
||||
buttonBox = new QDialogButtonBox;
|
||||
resetBtn = buttonBox->addButton(tr("Reset"), QDialogButtonBox::ResetRole);
|
||||
applyBtn = buttonBox->addButton(tr("Apply"), QDialogButtonBox::ApplyRole);
|
||||
saveBtn = buttonBox->addButton(tr("Save && Apply"), QDialogButtonBox::AcceptRole);
|
||||
closeBtn = buttonBox->addButton(QDialogButtonBox::Close);
|
||||
|
||||
footerLayout->addWidget(revertButton);
|
||||
footerLayout->addStretch();
|
||||
footerLayout->addWidget(buttonBox);
|
||||
root->addWidget(footer);
|
||||
|
||||
// Connections
|
||||
connect(schemeComboBox, &QComboBox::currentTextChanged, this, &PaletteEditorDialog::onSchemeChanged);
|
||||
connect(quickSetupPanel, &QuickSetupPanel::generateRequested, this, &PaletteEditorDialog::onGenerateFromAccent);
|
||||
connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault);
|
||||
connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset);
|
||||
connect(applyBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onApply);
|
||||
connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave);
|
||||
connect(closeBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
connect(paletteGridToggleButton, &QPushButton::toggled, this, [this](bool open) {
|
||||
paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette"));
|
||||
paletteGridSeparator->setVisible(open);
|
||||
paletteGrid->setVisible(open);
|
||||
|
||||
if (open) {
|
||||
setMinimumHeight(680);
|
||||
resize(width(), 680);
|
||||
} else {
|
||||
setMinimumHeight(220);
|
||||
adjustSize(); // shrinks to fit just the visible content
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Palette Editor — %1").arg(themeName));
|
||||
titleLabel->setText(tr("<b>Palette Editor</b> · %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"));
|
||||
}
|
||||
|
||||
schemeComboBox->setToolTip(tr("Switch between the light and dark palette files"));
|
||||
editingLabel->setText(tr("Editing:"));
|
||||
paletteGridToggleButton->setText(tr("▶ Edit Palette"));
|
||||
paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks"));
|
||||
revertButton->setText(tr("↺ Revert to theme default"));
|
||||
|
||||
resetBtn->setText(tr("Reset"));
|
||||
applyBtn->setText(tr("Apply"));
|
||||
saveBtn->setText(tr("Save && Apply"));
|
||||
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette"));
|
||||
applyBtn->setToolTip(tr("Preview this palette without saving to disk"));
|
||||
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower()));
|
||||
|
||||
if (themeDirPath.isEmpty()) {
|
||||
saveBtn->setEnabled(false);
|
||||
saveBtn->setToolTip(tr("Cannot save: this theme has no directory on disk"));
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::loadSchemes()
|
||||
{
|
||||
const QStringList schemes = {"Light", "Dark"};
|
||||
for (const QString &scheme : schemes) {
|
||||
PaletteConfig cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
|
||||
|
||||
if (!cfg.hasPalette()) {
|
||||
cfg = PaletteConfig::fromDefault(themeDirPath, scheme);
|
||||
}
|
||||
|
||||
if (!cfg.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<QPalette::ColorRole>(i);
|
||||
if (role != QPalette::NoRole) {
|
||||
cfg.colors[group][role] = appPal.color(group, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
savedConfig[scheme] = cfg;
|
||||
workingConfig[scheme] = cfg;
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme)
|
||||
{
|
||||
QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight);
|
||||
if (seed.isValid()) {
|
||||
quickSetupPanel->setAccentColor(seed);
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onSchemeChanged(const QString &scheme)
|
||||
{
|
||||
// Snapshot unsaved edits for the scheme we're leaving
|
||||
if (!loadedScheme.isEmpty()) {
|
||||
workingConfig[loadedScheme] = paletteGrid->currentPaletteConfig();
|
||||
}
|
||||
|
||||
loadedScheme = scheme;
|
||||
paletteGrid->loadPalette(workingConfig.value(scheme));
|
||||
seedAccentFromScheme(scheme);
|
||||
onApply();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensity)
|
||||
{
|
||||
PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, loadedScheme);
|
||||
workingConfig[loadedScheme] = cfg;
|
||||
paletteGrid->loadPalette(cfg);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onApply()
|
||||
{
|
||||
themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onSave()
|
||||
{
|
||||
if (loadedScheme.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
||||
ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
globalCfg.colorScheme = loadedScheme;
|
||||
globalCfg.save(themeDirPath);
|
||||
|
||||
savedConfig[loadedScheme] = cfg;
|
||||
workingConfig[loadedScheme] = cfg;
|
||||
themeManager->reloadCurrentTheme();
|
||||
accept();
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onReset()
|
||||
{
|
||||
workingConfig[loadedScheme] = savedConfig[loadedScheme];
|
||||
paletteGrid->loadPalette(savedConfig[loadedScheme]);
|
||||
}
|
||||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
workingConfig[loadedScheme] = def;
|
||||
paletteGrid->loadPalette(def);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::PaletteChange) {
|
||||
QTimer::singleShot(0, this, &PaletteEditorDialog::refreshChromePalettes);
|
||||
}
|
||||
|
||||
QDialog::changeEvent(e);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::refreshChromePalettes()
|
||||
{
|
||||
const QPalette base = qApp->palette();
|
||||
|
||||
if (header) {
|
||||
QPalette hp = header->palette();
|
||||
hp.setColor(QPalette::Window, base.color(QPalette::Window).darker(108));
|
||||
header->setPalette(hp);
|
||||
header->update();
|
||||
}
|
||||
if (footer) {
|
||||
QPalette fp = footer->palette();
|
||||
fp.setColor(QPalette::Window, base.color(QPalette::Window).darker(104));
|
||||
footer->setPalette(fp);
|
||||
footer->update();
|
||||
}
|
||||
if (quickSetupPanel) {
|
||||
QPalette sp = quickSetupPanel->palette();
|
||||
sp.setColor(QPalette::Window, base.color(QPalette::Window).darker(102));
|
||||
quickSetupPanel->setPalette(sp);
|
||||
quickSetupPanel->update();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
#define COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
#include <QMap>
|
||||
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QPushButton;
|
||||
class PaletteGridWidget;
|
||||
class QuickSetupPanel;
|
||||
|
||||
class PaletteEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr);
|
||||
void loadSchemes();
|
||||
|
||||
private slots:
|
||||
void onSave();
|
||||
void onApply();
|
||||
void onReset();
|
||||
void onRevertToDefault();
|
||||
void onSchemeChanged(const QString &scheme);
|
||||
void onGenerateFromAccent(const QColor &accent, int intensity);
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void retranslateUi();
|
||||
void refreshChromePalettes();
|
||||
void loadScheme(const QString &scheme); // snapshot current, switch, load
|
||||
void seedAccentFromScheme(const QString &scheme);
|
||||
|
||||
// Sub-widgets
|
||||
QWidget *header;
|
||||
QLabel *titleLabel;
|
||||
QLabel *editingLabel;
|
||||
QuickSetupPanel *quickSetupPanel = nullptr;
|
||||
PaletteGridWidget *paletteGrid = nullptr;
|
||||
QPushButton *paletteGridToggleButton = nullptr;
|
||||
QFrame *paletteGridSeparator = nullptr;
|
||||
QWidget *footer;
|
||||
QComboBox *schemeComboBox = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
QPushButton *resetBtn = nullptr;
|
||||
QPushButton *applyBtn = nullptr;
|
||||
QPushButton *saveBtn = nullptr;
|
||||
QPushButton *closeBtn = nullptr;
|
||||
QPushButton *revertButton = nullptr;
|
||||
|
||||
// State
|
||||
QString themeDirPath;
|
||||
QString themeName;
|
||||
QString loadedScheme;
|
||||
|
||||
QMap<QString, PaletteConfig> workingConfig;
|
||||
QMap<QString, PaletteConfig> savedConfig;
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent *e) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H
|
||||
200
cockatrice/src/interface/palette_editor/palette_generator.cpp
Normal file
200
cockatrice/src/interface/palette_editor/palette_generator.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "palette_generator.h"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// PaletteGenerator::fromAccent
|
||||
//
|
||||
// Three intensity bands:
|
||||
// 0–30 Subtle — Highlight / links / BrightText take on the hue;
|
||||
// backgrounds stay neutral grey.
|
||||
// 30–70 Accented — Button, ToolTipBase, AlternateBase, shading tint in;
|
||||
// backgrounds get a faint hue wash.
|
||||
// 70–100 Chromatic— Window and Base pick up real saturation; the whole
|
||||
// application reads as that colour, text stays readable.
|
||||
//
|
||||
// Key principles:
|
||||
// • Quadratic saturation curve: low end feels truly subtle, top is vivid.
|
||||
// • Each role has its own saturation ceiling; buttons always pop above window.
|
||||
// • Base stays near-white / near-black regardless of intensity for legibility.
|
||||
// • Button text contrast is computed from the real button color, not assumed.
|
||||
// • Tooltip blends from classic yellow to hue-tinted above intensity ~25.
|
||||
// • 3D shading ladder (Light→Shadow) carries the same hue for coherence.
|
||||
// • Disabled: text → mid-gray, backgrounds → match Window.
|
||||
// • Inactive Highlight fades to a near-bg tone so it doesn't compete.
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
namespace PaletteGenerator
|
||||
{
|
||||
|
||||
PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme)
|
||||
{
|
||||
PaletteConfig cfg;
|
||||
const bool dark = scheme.compare("Dark", Qt::CaseInsensitive) == 0;
|
||||
const double t = intensity / 100.0; // 0.0 – 1.0
|
||||
|
||||
int h = accent.hslHue();
|
||||
const bool achromatic = (h < 0);
|
||||
if (achromatic) {
|
||||
h = 0;
|
||||
}
|
||||
|
||||
// Saturation budgets
|
||||
// Quadratic ease-in means the subtle end is genuinely subtle and the
|
||||
// full end is bold without being garish.
|
||||
auto sat = [&](double maxSat) -> int {
|
||||
if (achromatic) {
|
||||
return 0;
|
||||
}
|
||||
return qRound(maxSat * t * t);
|
||||
};
|
||||
|
||||
const int satWindow = sat(dark ? 80.0 : 90.0);
|
||||
const int satBase = sat(dark ? 25.0 : 20.0); // text areas stay near-white/black
|
||||
const int satAlt = sat(dark ? 90.0 : 100.0);
|
||||
const int satButton = sat(dark ? 120.0 : 130.0); // buttons pop above the bg
|
||||
const int satTooltip = sat(dark ? 90.0 : 80.0);
|
||||
const int satHighlight = achromatic ? 0 : qRound(accent.hslSaturation() * (0.45 + 0.55 * t));
|
||||
const int satShadeHi = sat(dark ? 60.0 : 50.0); // Light / Midlight
|
||||
const int satShadeLo = sat(dark ? 90.0 : 70.0); // Mid / Dark
|
||||
|
||||
// Per-role lightness
|
||||
// Nudge lightness slightly as saturation rises to compensate for the
|
||||
// Helmholtz-Kohlrausch effect (saturated colors look lighter/heavier).
|
||||
const int winL = dark ? (28 + qRound(t * 8)) : (242 - qRound(t * 6));
|
||||
const int baseL = dark ? (43 + qRound(t * 6)) : 252;
|
||||
const int altL = dark ? (36 + qRound(t * 9)) : (234 - qRound(t * 5));
|
||||
const int btnL = dark ? (56 + qRound(t * 10)) : (230 - qRound(t * 10));
|
||||
const int tipL = dark ? (52 + qRound(t * 8)) : (248 - qRound(t * 6));
|
||||
|
||||
// Highlight color
|
||||
QColor hl;
|
||||
if (achromatic) {
|
||||
hl = dark ? QColor(105, 105, 105) : QColor(95, 95, 95);
|
||||
} else if (dark) {
|
||||
int L = qBound(105, accent.lightness() + qRound(45.0 * (1.0 - t)), 215);
|
||||
hl = QColor::fromHsl(h, qMin(255, satHighlight), L);
|
||||
} else {
|
||||
int L = qBound(50, accent.lightness() - qRound(25.0 * t), 180);
|
||||
hl = QColor::fromHsl(h, qMin(255, satHighlight), L);
|
||||
}
|
||||
const double hlLuma = 0.299 * hl.red() + 0.587 * hl.green() + 0.114 * hl.blue();
|
||||
const QColor hlText = (hlLuma > 135) ? Qt::black : Qt::white;
|
||||
|
||||
// Local helpers
|
||||
using CR = QPalette::ColorRole;
|
||||
using CG = QPalette::ColorGroup;
|
||||
|
||||
auto hsl = [&](int lightness, int s) -> QColor {
|
||||
return QColor::fromHsl(h, qBound(0, s, 255), qBound(0, lightness, 255));
|
||||
};
|
||||
|
||||
auto textOn = [](const QColor &bg) -> QColor {
|
||||
double luma = 0.299 * bg.red() + 0.587 * bg.green() + 0.114 * bg.blue();
|
||||
return (luma > 135) ? Qt::black : Qt::white;
|
||||
};
|
||||
|
||||
auto set3 = [&](CR role, QColor active, QColor disabled, QColor inactive) {
|
||||
cfg.colors[CG::Active][role] = active;
|
||||
cfg.colors[CG::Disabled][role] = disabled;
|
||||
cfg.colors[CG::Inactive][role] = inactive;
|
||||
};
|
||||
|
||||
auto setAll = [&](CR role, QColor c) { set3(role, c, c, c); };
|
||||
|
||||
// Tooltip: blend classic yellow → hue-tinted above t≈0.20
|
||||
QColor bg_tip;
|
||||
if (achromatic || t < 0.20) {
|
||||
bg_tip = QColor(255, 255, 220);
|
||||
} else {
|
||||
QColor tinted = hsl(tipL, satTooltip);
|
||||
double blend = qMin(1.0, (t - 0.20) / 0.55);
|
||||
QColor yellow(255, 255, 220);
|
||||
bg_tip = QColor(qRound(yellow.red() * (1.0 - blend) + tinted.red() * blend),
|
||||
qRound(yellow.green() * (1.0 - blend) + tinted.green() * blend),
|
||||
qRound(yellow.blue() * (1.0 - blend) + tinted.blue() * blend));
|
||||
}
|
||||
|
||||
// Backgrounds
|
||||
const QColor bg_win = hsl(winL, satWindow);
|
||||
const QColor bg_base = hsl(baseL, satBase);
|
||||
const QColor bg_alt = hsl(altL, satAlt);
|
||||
const QColor bg_btn = hsl(btnL, satButton);
|
||||
|
||||
set3(CR::Window, bg_win, bg_win, bg_win);
|
||||
set3(CR::Base, bg_base, bg_win, bg_base);
|
||||
set3(CR::AlternateBase, bg_alt, bg_alt, bg_alt);
|
||||
set3(CR::Button, bg_btn, bg_win, bg_btn);
|
||||
set3(CR::ToolTipBase, bg_tip, bg_tip, bg_tip);
|
||||
|
||||
// Foreground text
|
||||
const QColor winText = dark ? Qt::white : Qt::black;
|
||||
const QColor disText = dark ? QColor(157, 157, 157) : QColor(120, 120, 120);
|
||||
const QColor disBtnText = dark ? QColor(120, 120, 120) : QColor(150, 150, 150);
|
||||
|
||||
set3(CR::WindowText, winText, disText, winText);
|
||||
set3(CR::Text, winText, disText, winText);
|
||||
set3(CR::ButtonText, textOn(bg_btn), disBtnText, textOn(bg_btn));
|
||||
setAll(CR::ToolTipText, textOn(bg_tip));
|
||||
|
||||
const QColor phAlpha = dark ? QColor(255, 255, 255, 110) : QColor(0, 0, 0, 110);
|
||||
const QColor phDis = dark ? QColor(255, 255, 255, 70) : QColor(0, 0, 0, 70);
|
||||
set3(CR::PlaceholderText, phAlpha, phDis, phAlpha);
|
||||
|
||||
// Highlight / selection
|
||||
const QColor inactiveHl = hsl(winL + (dark ? 14 : -10), satWindow);
|
||||
cfg.colors[CG::Active][CR::Highlight] = hl;
|
||||
cfg.colors[CG::Disabled][CR::Highlight] = inactiveHl;
|
||||
cfg.colors[CG::Inactive][CR::Highlight] = inactiveHl;
|
||||
cfg.colors[CG::Active][CR::HighlightedText] = hlText;
|
||||
cfg.colors[CG::Disabled][CR::HighlightedText] = disText;
|
||||
cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black;
|
||||
|
||||
// BrightText
|
||||
QColor bright;
|
||||
if (achromatic) {
|
||||
bright = dark ? Qt::white : Qt::black;
|
||||
} else if (dark) {
|
||||
bright = QColor::fromHsl(h, qMin(255, satHighlight + 25), qMin(235, hl.lightness() + 50));
|
||||
} else {
|
||||
bright = Qt::white;
|
||||
}
|
||||
setAll(CR::BrightText, bright);
|
||||
|
||||
// Links
|
||||
QColor link, linkV;
|
||||
if (achromatic) {
|
||||
link = dark ? QColor(100, 200, 255) : QColor(0, 0, 210);
|
||||
linkV = dark ? QColor(200, 100, 255) : QColor(128, 0, 180);
|
||||
} else if (dark) {
|
||||
link = QColor::fromHsl(h, qMin(255, satHighlight), qMin(230, hl.lightness() + 75));
|
||||
linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMin(215, hl.lightness() + 55));
|
||||
} else {
|
||||
link = QColor::fromHsl(h, qMin(255, satHighlight), qMax(40, hl.lightness() - 75));
|
||||
linkV = QColor::fromHsl((h + 30) % 360, qMin(255, satHighlight), qMax(30, hl.lightness() - 95));
|
||||
}
|
||||
set3(CR::Link, link, dark ? QColor(48, 140, 198) : QColor(0, 0, 255), link);
|
||||
set3(CR::LinkVisited, linkV, dark ? QColor(180, 80, 255) : QColor(255, 0, 255), linkV);
|
||||
|
||||
// 3D / frame shading
|
||||
if (dark) {
|
||||
setAll(CR::Light, hsl(115, qMin(255, satShadeHi)));
|
||||
setAll(CR::Midlight, hsl(82, qMin(255, satShadeHi)));
|
||||
setAll(CR::Mid, hsl(37, satShadeLo));
|
||||
setAll(CR::Dark, hsl(22, satShadeLo));
|
||||
setAll(CR::Shadow, Qt::black);
|
||||
} else {
|
||||
setAll(CR::Light, Qt::white);
|
||||
setAll(CR::Midlight, hsl(226, qMin(255, satShadeHi)));
|
||||
setAll(CR::Mid, hsl(158, satShadeLo));
|
||||
setAll(CR::Dark, hsl(148, satShadeLo));
|
||||
// Shadow stays neutral — tinting it makes the UI look bruised
|
||||
cfg.colors[CG::Active][CR::Shadow] = QColor(105, 105, 105);
|
||||
cfg.colors[CG::Disabled][CR::Shadow] = Qt::black;
|
||||
cfg.colors[CG::Inactive][CR::Shadow] = QColor(105, 105, 105);
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
} // namespace PaletteGenerator
|
||||
16
cockatrice/src/interface/palette_editor/palette_generator.h
Normal file
16
cockatrice/src/interface/palette_editor/palette_generator.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef COCKATRICE_PALETTE_GENERATOR_H
|
||||
#define COCKATRICE_PALETTE_GENERATOR_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
|
||||
// All QPalette roles are derived from a single accent color and an
|
||||
// intensity value (0-100). See the .cpp for the full band breakdown.
|
||||
namespace PaletteGenerator
|
||||
{
|
||||
PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &scheme);
|
||||
} // namespace PaletteGenerator
|
||||
|
||||
#endif // COCKATRICE_PALETTE_GENERATOR_H
|
||||
179
cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
Normal file
179
cockatrice/src/interface/palette_editor/palette_grid_widget.cpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#include "palette_grid_widget.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")},
|
||||
};
|
||||
|
||||
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);
|
||||
colorButtons[group][role] = btn;
|
||||
grid->addWidget(btn, row + 1, col + 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
|
||||
{
|
||||
PaletteConfig cfg;
|
||||
for (auto group : ALL_GROUPS) {
|
||||
for (auto role : allRoles()) {
|
||||
cfg.colors[group][role] = colorButtons[group][role]->getColor();
|
||||
}
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
#define COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
#include "color_button.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QPalette>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
class QScrollArea;
|
||||
// Scrollable grid of ColorButtons — one per (ColorGroup × ColorRole) cell.
|
||||
// Owns the load/read round-trip for PaletteConfig but has no file I/O itself.
|
||||
class PaletteGridWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PaletteGridWidget(QWidget *parent = nullptr);
|
||||
|
||||
void loadPalette(const PaletteConfig &cfg);
|
||||
PaletteConfig currentPaletteConfig() const;
|
||||
|
||||
private:
|
||||
void buildGrid(QWidget *host);
|
||||
void changeEvent(QEvent *e);
|
||||
void refreshChromePalettes();
|
||||
|
||||
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
|
||||
QScrollArea *scroll;
|
||||
QWidget *gridHost;
|
||||
QVBoxLayout *layout;
|
||||
QVector<QLabel *> headerLabels;
|
||||
QVector<QWidget *> rowShadeWidgets;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PALETTE_GRID_WIDGET_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "quick_setup_panel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
|
||||
QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(12, 8, 12, 8);
|
||||
layout->setSpacing(10);
|
||||
|
||||
heading = new QLabel(this);
|
||||
heading->setTextFormat(Qt::RichText);
|
||||
|
||||
accentLabel = new QLabel(this);
|
||||
accentButton = new ColorButton(this);
|
||||
accentButton->setColor(QColor(20, 140, 60));
|
||||
|
||||
intensityLabel = new QLabel(this);
|
||||
|
||||
labelLow = new QLabel(this);
|
||||
labelHigh = new QLabel(this);
|
||||
QFont small = labelLow->font();
|
||||
small.setPointSizeF(small.pointSizeF() * 0.82);
|
||||
labelLow->setFont(small);
|
||||
labelHigh->setFont(small);
|
||||
QPalette dimmed = labelLow->palette();
|
||||
dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid));
|
||||
labelLow->setPalette(dimmed);
|
||||
labelHigh->setPalette(dimmed);
|
||||
|
||||
intensitySlider = new QSlider(Qt::Horizontal, this);
|
||||
intensitySlider->setRange(0, 100);
|
||||
intensitySlider->setValue(70);
|
||||
intensitySlider->setFixedWidth(160);
|
||||
|
||||
intensityPercentageLabel = new QLabel(this);
|
||||
intensityPercentageLabel->setFixedWidth(34);
|
||||
intensityPercentageLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
generateButton = new QPushButton(this);
|
||||
|
||||
layout->addWidget(heading);
|
||||
layout->addSpacing(6);
|
||||
layout->addWidget(accentLabel);
|
||||
layout->addWidget(accentButton);
|
||||
layout->addSpacing(12);
|
||||
layout->addWidget(intensityLabel);
|
||||
layout->addWidget(labelLow);
|
||||
layout->addWidget(intensitySlider);
|
||||
layout->addWidget(labelHigh);
|
||||
layout->addWidget(intensityPercentageLabel);
|
||||
layout->addStretch();
|
||||
layout->addWidget(generateButton);
|
||||
|
||||
connect(intensitySlider, &QSlider::valueChanged, this,
|
||||
[this](int v) { intensityPercentageLabel->setText(tr("%1%").arg(v)); });
|
||||
connect(generateButton, &QPushButton::clicked, this,
|
||||
[this] { emit generateRequested(accentButton->getColor(), intensitySlider->value()); });
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void QuickSetupPanel::retranslateUi()
|
||||
{
|
||||
heading->setText(tr("<b>Quick Setup</b>"));
|
||||
heading->setToolTip(tr("Generate all palette roles automatically from a single accent colour"));
|
||||
accentLabel->setText(tr("Accent:"));
|
||||
accentButton->setToolTip(tr("Primary hue. Used directly for highlights and links.\n"
|
||||
"At high intensity it also tints buttons and backgrounds."));
|
||||
intensityLabel->setText(tr("Intensity:"));
|
||||
labelLow->setText(tr("Subtle"));
|
||||
labelHigh->setText(tr("Full colour"));
|
||||
intensitySlider->setToolTip(tr("0–30 Subtle tint — only highlights and links change hue\n"
|
||||
"30–70 Accented — buttons, tooltips, and borders join in\n"
|
||||
"70–100 Full colour — backgrounds, everything"));
|
||||
intensityPercentageLabel->setText(tr("70%"));
|
||||
|
||||
generateButton->setText(tr("Generate ↓"));
|
||||
generateButton->setToolTip(tr("Derive all palette roles from the accent colour above.\n"
|
||||
"Fine-tune individual colours in the grid afterwards."));
|
||||
}
|
||||
|
||||
QColor QuickSetupPanel::accentColor() const
|
||||
{
|
||||
return accentButton->getColor();
|
||||
}
|
||||
|
||||
int QuickSetupPanel::intensity() const
|
||||
{
|
||||
return intensitySlider->value();
|
||||
}
|
||||
|
||||
void QuickSetupPanel::setAccentColor(const QColor &c)
|
||||
{
|
||||
accentButton->setColor(c);
|
||||
}
|
||||
94
cockatrice/src/interface/palette_editor/quick_setup_panel.h
Normal file
94
cockatrice/src/interface/palette_editor/quick_setup_panel.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#ifndef COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
#define COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
|
||||
#include "color_button.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QPushButton;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QSlider;
|
||||
|
||||
/**
|
||||
* @class QuickSetupPanel
|
||||
* @brief Provides a compact "Quick Setup" interface for generating theme palettes.
|
||||
*
|
||||
* The panel contains:
|
||||
* - an accent color picker,
|
||||
* - an intensity slider,
|
||||
* - and a generate button.
|
||||
*
|
||||
* When the user clicks the generate button, the panel emits
|
||||
* generateRequested() with the currently selected accent color
|
||||
* and intensity value.
|
||||
*
|
||||
* Typically used together with PaletteGenerator::fromAccent()
|
||||
* to quickly generate color schemes from a chosen accent color.
|
||||
*/
|
||||
class QuickSetupPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the quick setup panel.
|
||||
*
|
||||
* @param parent Optional parent widget.
|
||||
*/
|
||||
explicit QuickSetupPanel(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Retranslates all user-visible strings.
|
||||
*
|
||||
* Intended to be called when the application language changes.
|
||||
*/
|
||||
void retranslateUi();
|
||||
|
||||
/**
|
||||
* @brief Returns the currently selected accent color.
|
||||
*
|
||||
* @return The selected accent color.
|
||||
*/
|
||||
QColor accentColor() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the current intensity slider value.
|
||||
*
|
||||
* @return The selected intensity value.
|
||||
*/
|
||||
int intensity() const;
|
||||
|
||||
/**
|
||||
* @brief Updates the displayed accent color.
|
||||
*
|
||||
* Used by the parent dialog when switching schemes to keep
|
||||
* the color swatch synchronized with the active palette.
|
||||
*
|
||||
* @param c The new accent color.
|
||||
*/
|
||||
void setAccentColor(const QColor &c);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when the user requests palette generation.
|
||||
*
|
||||
* @param accent The selected accent color.
|
||||
* @param intensity The selected intensity value.
|
||||
*/
|
||||
void generateRequested(QColor accent, int intensity);
|
||||
|
||||
private:
|
||||
QHBoxLayout *layout;
|
||||
QLabel *heading;
|
||||
QLabel *accentLabel;
|
||||
ColorButton *accentButton;
|
||||
QLabel *intensityLabel;
|
||||
QLabel *labelLow;
|
||||
QLabel *labelHigh;
|
||||
QSlider *intensitySlider;
|
||||
QLabel *intensityPercentageLabel;
|
||||
QPushButton *generateButton;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_QUICK_SETUP_PANEL_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue