mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-26 00:23:55 -07:00
Read user path, then fall through to system, only write to user.
Took 1 hour 21 minutes Took 5 seconds Took 40 seconds
This commit is contained in:
parent
e28f31c93e
commit
c3ebaefd85
4 changed files with 147 additions and 81 deletions
|
|
@ -19,13 +19,15 @@
|
|||
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
|
||||
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
|
||||
{
|
||||
userThemeDirPath = ThemeManager::userThemeDirFor(themeName);
|
||||
|
||||
setMinimumSize(740, 220);
|
||||
setupUi();
|
||||
|
||||
// Load both scheme configs upfront so switching is instant
|
||||
loadSchemes();
|
||||
|
||||
loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
|
||||
loadedScheme = themeManager->isDarkMode(themeDirPath, userThemeDirPath) ? "Dark" : "Light";
|
||||
|
||||
schemeComboBox->blockSignals(true);
|
||||
schemeComboBox->setCurrentText(loadedScheme);
|
||||
|
|
@ -33,7 +35,6 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
|
|||
|
||||
paletteGrid->loadPalette(workingConfig[loadedScheme]);
|
||||
seedAccentFromScheme(loadedScheme);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
|
|
@ -162,15 +163,12 @@ 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"));
|
||||
}
|
||||
const bool hasUserOverride =
|
||||
QFile::exists(QDir(userThemeDirPath).absoluteFilePath(PaletteConfig::fileName("Light"))) ||
|
||||
QFile::exists(QDir(userThemeDirPath).absoluteFilePath(PaletteConfig::fileName("Dark")));
|
||||
revertButton->setEnabled(hasUserOverride);
|
||||
revertButton->setToolTip(hasUserOverride ? tr("Delete your custom palette and restore the shipped defaults")
|
||||
: tr("No custom palette overrides exist for this theme"));
|
||||
|
||||
schemeComboBox->setToolTip(tr("Switch between the light and dark palette files"));
|
||||
editingLabel->setText(tr("Editing:"));
|
||||
|
|
@ -195,8 +193,11 @@ void PaletteEditorDialog::loadSchemes()
|
|||
{
|
||||
const QStringList schemes = {"Light", "Dark"};
|
||||
for (const QString &scheme : schemes) {
|
||||
PaletteConfig cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
|
||||
|
||||
// user customisation → system palette → system default → app palette
|
||||
PaletteConfig cfg = PaletteConfig::fromScheme(userThemeDirPath, scheme);
|
||||
if (!cfg.hasPalette()) {
|
||||
cfg = PaletteConfig::fromScheme(themeDirPath, scheme);
|
||||
}
|
||||
if (!cfg.hasPalette()) {
|
||||
cfg = PaletteConfig::fromDefault(themeDirPath, scheme);
|
||||
}
|
||||
|
|
@ -258,15 +259,17 @@ void PaletteEditorDialog::onSave()
|
|||
|
||||
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));
|
||||
if (!ThemeManager::savePaletteConfig(userThemeDirPath, loadedScheme, cfg)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Save failed"),
|
||||
tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), userThemeDirPath));
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the active scheme in the user dir — never touch the system (read-only) dir
|
||||
ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
globalCfg.colorScheme = loadedScheme;
|
||||
globalCfg.save(themeDirPath);
|
||||
globalCfg.save(userThemeDirPath);
|
||||
|
||||
savedConfig[loadedScheme] = cfg;
|
||||
workingConfig[loadedScheme] = cfg;
|
||||
|
|
@ -282,14 +285,45 @@ void PaletteEditorDialog::onReset()
|
|||
|
||||
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));
|
||||
const QString filePath = QDir(userThemeDirPath).absoluteFilePath(PaletteConfig::fileName(loadedScheme));
|
||||
|
||||
if (!QFile::exists(filePath)) {
|
||||
// Button should already be disabled in this state, but be defensive
|
||||
return;
|
||||
}
|
||||
|
||||
const auto reply =
|
||||
QMessageBox::question(this, tr("Revert to default?"),
|
||||
tr("This will permanently delete your custom \"%1\" palette for \"%2\".\n\nContinue?")
|
||||
.arg(loadedScheme, themeName),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (reply != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile::remove(filePath);
|
||||
|
||||
// Reload via fallthrough: system palette → system default → app palette
|
||||
PaletteConfig def = PaletteConfig::fromScheme(themeDirPath, loadedScheme);
|
||||
if (!def.hasPalette()) {
|
||||
def = PaletteConfig::fromDefault(themeDirPath, loadedScheme);
|
||||
}
|
||||
if (!def.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) {
|
||||
def.colors[group][role] = appPal.color(group, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
savedConfig[loadedScheme] = def;
|
||||
workingConfig[loadedScheme] = def;
|
||||
paletteGrid->loadPalette(def);
|
||||
retranslateUi(); // update button enabled state
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::changeEvent(QEvent *e)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ private:
|
|||
QPushButton *revertButton = nullptr;
|
||||
|
||||
// State
|
||||
QString userThemeDirPath;
|
||||
QString themeDirPath;
|
||||
QString themeName;
|
||||
QString loadedScheme;
|
||||
|
|
|
|||
|
|
@ -113,21 +113,26 @@ void ThemeManager::ensureThemeDirectoryExists()
|
|||
}
|
||||
}
|
||||
|
||||
bool ThemeManager::isDarkMode(const QString &themeDirPath)
|
||||
bool ThemeManager::isDarkMode(const QString &themeDirPath, const QString &userDirPath)
|
||||
{
|
||||
ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
} else if (themeConfig.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
bool osDark = (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
|
||||
#else
|
||||
bool osDark = false;
|
||||
#endif
|
||||
return osDark;
|
||||
// User override takes precedence over system config
|
||||
for (const QString &path : {userDirPath, themeDirPath}) {
|
||||
if (path.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
ThemeConfig cfg = ThemeConfig::fromThemeDir(path);
|
||||
if (cfg.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (cfg.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
return (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ThemeManager::isBuiltInTheme()
|
||||
|
|
@ -137,42 +142,49 @@ bool ThemeManager::isBuiltInTheme()
|
|||
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME;
|
||||
}
|
||||
|
||||
QString ThemeManager::userThemeDirFor(const QString &themeName)
|
||||
{
|
||||
return QDir(SettingsCache::instance().getThemesPath()).absoluteFilePath(themeName);
|
||||
}
|
||||
|
||||
QStringMap &ThemeManager::getAvailableThemes()
|
||||
{
|
||||
QDir dir;
|
||||
availableThemes.clear();
|
||||
|
||||
// load themes from user profile dir
|
||||
dir.setPath(SettingsCache::instance().getThemesPath());
|
||||
|
||||
// add default value
|
||||
availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default"));
|
||||
|
||||
availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion"));
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
}
|
||||
|
||||
// load themes from cockatrice system dir
|
||||
dir.setPath(qApp->applicationDirPath() +
|
||||
// ── 1. System themes (read-only, shipped with the application) ──────────
|
||||
QDir sysDir(qApp->applicationDirPath() +
|
||||
#ifdef Q_OS_MAC
|
||||
"/../Resources/themes"
|
||||
#elif defined(Q_OS_WIN)
|
||||
"/themes"
|
||||
#else // linux
|
||||
#else
|
||||
"/../share/cockatrice/themes"
|
||||
#endif
|
||||
);
|
||||
for (const QString &name : sysDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
availableThemes.insert(name, sysDir.absoluteFilePath(name));
|
||||
}
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
// ── 2. User-only themes (AppData) ────────────────────────────────────────
|
||||
// We only add themes that don't already exist in the system directory.
|
||||
// Customisations to system themes are handled via the fallthrough read
|
||||
// logic (userThemeDirFor → system path); we intentionally keep the system
|
||||
// path in the map so shipped assets are always locatable.
|
||||
QDir userDir(SettingsCache::instance().getThemesPath());
|
||||
for (const QString &name : userDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(name)) {
|
||||
availableThemes.insert(name, userDir.absoluteFilePath(name));
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Ensure built-in sentinels always exist (dev builds without install)
|
||||
if (!availableThemes.contains(NONE_THEME_NAME)) {
|
||||
availableThemes.insert(NONE_THEME_NAME, userDir.absoluteFilePath("Default"));
|
||||
}
|
||||
if (!availableThemes.contains(FUSION_THEME_NAME)) {
|
||||
availableThemes.insert(FUSION_THEME_NAME, userDir.absoluteFilePath("Fusion"));
|
||||
}
|
||||
|
||||
return availableThemes;
|
||||
}
|
||||
|
||||
|
|
@ -244,12 +256,14 @@ bool ThemeManager::savePaletteConfig(const QString &themeDirPath, const QString
|
|||
|
||||
void ThemeManager::setColorScheme(const QString &scheme)
|
||||
{
|
||||
const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
const QString themeName = SettingsCache::instance().getThemeName();
|
||||
const QString dirPath = getAvailableThemes().value(themeName);
|
||||
const QString userDirPath = userThemeDirFor(themeName);
|
||||
|
||||
// Read base config from system; write the scheme override to user dir
|
||||
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
|
||||
cfg.colorScheme = scheme;
|
||||
|
||||
cfg.save(dirPath);
|
||||
cfg.save(userDirPath);
|
||||
reloadCurrentTheme();
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +276,13 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem
|
|||
{
|
||||
const QString themeName = SettingsCache::instance().getThemeName();
|
||||
const QString dirPath = getAvailableThemes().value(themeName);
|
||||
const ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
const QString userDirPath = userThemeDirFor(themeName);
|
||||
|
||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(userDirPath);
|
||||
if (themeCfg.colorScheme.isEmpty() && themeCfg.styleName.isEmpty()) {
|
||||
themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
}
|
||||
|
||||
applyStyleAndPalette(themeName, themeCfg, cfg, scheme);
|
||||
}
|
||||
|
||||
|
|
@ -325,50 +345,59 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
|
|||
}
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
vvoid ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
const QString themeName = SettingsCache::instance().getThemeName();
|
||||
const QString dirPath = getAvailableThemes().value(themeName); // system path
|
||||
const QString userDirPath = userThemeDirFor(themeName); // user override path
|
||||
currentThemePath = dirPath;
|
||||
QDir dir(dirPath);
|
||||
|
||||
// CSS
|
||||
if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||
} else {
|
||||
// CSS — user override first, then system
|
||||
const auto tryLoadCss = [](const QString &path) -> bool {
|
||||
QDir d(path);
|
||||
if (!path.isEmpty() && d.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + d.absoluteFilePath(STYLE_CSS_NAME));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!tryLoadCss(userDirPath) && !tryLoadCss(dirPath)) {
|
||||
qApp->setStyleSheet("");
|
||||
}
|
||||
|
||||
// load theme.cfg for style + scheme preference
|
||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
// ThemeConfig — user override first, then system
|
||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(userDirPath);
|
||||
if (themeCfg.colorScheme.isEmpty() && themeCfg.styleName.isEmpty()) {
|
||||
themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
}
|
||||
|
||||
// Resolve active scheme:
|
||||
// theme.cfg says Dark/Light → use that
|
||||
// theme.cfg says System or is absent → follow the OS
|
||||
QString activeScheme = isDarkMode(dirPath) ? "Dark" : "Light";
|
||||
const QString activeScheme = isDarkMode(dirPath, userDirPath) ? "Dark" : "Light";
|
||||
|
||||
// ── Load palette: custom first, then theme default ────────────────────
|
||||
PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
|
||||
// Palette — user customisation → system palette → system default
|
||||
PaletteConfig palette = PaletteConfig::fromScheme(userDirPath, activeScheme);
|
||||
if (!palette.hasPalette()) {
|
||||
palette = PaletteConfig::fromScheme(dirPath, activeScheme);
|
||||
}
|
||||
if (!palette.hasPalette()) {
|
||||
palette = PaletteConfig::fromDefault(dirPath, activeScheme);
|
||||
}
|
||||
|
||||
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
|
||||
|
||||
// Search paths — user assets shadow system assets, both fall through to builtins
|
||||
QStringList resources;
|
||||
if (QDir(userDirPath).exists()) {
|
||||
resources << userDirPath;
|
||||
}
|
||||
if (!dirPath.isEmpty()) {
|
||||
resources << dir.absolutePath();
|
||||
resources << dirPath;
|
||||
}
|
||||
resources << DEFAULT_RESOURCE_PATHS;
|
||||
|
||||
QDir::setSearchPaths("theme", resources);
|
||||
|
||||
brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
for (auto &brushCache : brushesCache) {
|
||||
brushCache.clear();
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ protected:
|
|||
public:
|
||||
bool isBuiltInTheme();
|
||||
bool isDarkMode(const QString &themeDirPath);
|
||||
static bool isDarkMode(const QString &themeDirPath, const QString &userDirPath = {});
|
||||
static QString userThemeDirFor(const QString &themeName);
|
||||
QStringMap &getAvailableThemes();
|
||||
// Returns the path to the currently active theme directory (empty = default)
|
||||
QString getCurrentThemePath() const
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue