mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-07 18:43:26 -08:00
* [Theme] Carry Default theme directory customizations over to System The Default -> System rename rewrote the stored theme name but left the user profile's themes/Default directory behind, so anyone who had customized zone graphics, stylesheets or palettes there silently lost them. Migrate the directory alongside the name. Rename it when System does not exist yet, otherwise merge file by file without overwriting what System already has, then drop the legacy directory. Users who already ran the rename get their customizations back on the next start, and a hand-made Default folder of a user on another theme is left untouched. * [Tests] Give theme_migration_test an explicit main and gate it on WITH_CLIENT The build machines that cannot find GTest fall back to the downloaded googletest, which links the bare gtest library without gtest_main. Every other test defines its own main() for that reason; this one did not, so Debian 13, Fedora 44 and Ubuntu 26.04 failed to link it. The test compiles client sources, so -DTEST=1 -DWITH_CLIENT=0 has no way to build it. Gate the subdirectory on WITH_CLIENT like the other client-source test. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
151 lines
6.3 KiB
C++
151 lines
6.3 KiB
C++
/**
|
|
* @file theme_manager.h
|
|
* @ingroup CoreSettings
|
|
*/
|
|
//! \todo Document this file.
|
|
|
|
#ifndef THEMEMANAGER_H
|
|
#define THEMEMANAGER_H
|
|
|
|
#include "theme_config.h"
|
|
|
|
#include <QBrush>
|
|
#include <QDir>
|
|
#include <QLoggingCategory>
|
|
#include <QMap>
|
|
#include <QObject>
|
|
#include <QPixmap>
|
|
#include <QString>
|
|
#include <array>
|
|
|
|
inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager");
|
|
|
|
typedef QMap<QString, QString> QStringMap;
|
|
typedef QMap<int, QBrush> QBrushMap;
|
|
|
|
class QApplication;
|
|
|
|
class ThemeManager : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
ThemeManager(QObject *parent = nullptr);
|
|
|
|
enum Role
|
|
{
|
|
MinRole = 0,
|
|
Hand = MinRole,
|
|
Stack,
|
|
Table,
|
|
Player,
|
|
MaxRole = Player,
|
|
};
|
|
|
|
private:
|
|
QString defaultStyleName;
|
|
// Pristine application palette captured at startup, before any custom theme
|
|
// palette is applied. Used as the base when a theme supplies no palette, so
|
|
// switching away from a custom palette restores the original colours.
|
|
QPalette defaultPalette;
|
|
QString currentThemePath;
|
|
std::array<QBrush, Role::MaxRole + 1> brushes;
|
|
QStringMap availableThemes;
|
|
QMap<AppColor::Role, QColor> currentAppColors;
|
|
/*
|
|
Internal cache for multiple backgrounds
|
|
*/
|
|
std::array<QBrushMap, Role::MaxRole + 1> brushesCache;
|
|
|
|
protected:
|
|
void ensureThemeDirectoryExists();
|
|
QBrush loadBrush(QString fileName, QColor fallbackColor);
|
|
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
|
void applyStyleAndPalette(const QString &themeName,
|
|
const ThemeConfig &themeCfg,
|
|
const PaletteConfig &palCfg,
|
|
const QString &activeScheme);
|
|
|
|
public:
|
|
// 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);
|
|
// Carries a user profile's customised "Default" theme directory over to the
|
|
// "System" directory the theme was renamed to, so zone graphics,
|
|
// stylesheets, palettes and theme.cfg survive the rename. Renames the
|
|
// directory when "System" does not exist yet, otherwise merges file by
|
|
// file without overwriting what "System" already has. No-op unless the
|
|
// active theme is one of the two names, so a hand-made "Default" folder of
|
|
// a user on another theme is left alone.
|
|
static void migrateLegacyThemeDir(const QString &themesPath, const QString ¤tThemeName);
|
|
// 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;
|
|
// The resolved scheme of the currently active theme.
|
|
bool isDarkModeActive() const
|
|
{
|
|
return isDarkMode(currentThemePath);
|
|
}
|
|
QStringMap &getAvailableThemes();
|
|
// Returns the path to the currently active theme directory (empty = default)
|
|
QString getCurrentThemePath() const
|
|
{
|
|
return currentThemePath;
|
|
}
|
|
// Load the global theme settings (style + color scheme preference)
|
|
static ThemeConfig loadGlobalConfig(const QString &themeDirPath);
|
|
static bool saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg);
|
|
|
|
// Load/save per-scheme palette colors
|
|
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
|
|
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
|
|
// Resolve prefix to a scheme-qualified "theme:" path. Existence is probed
|
|
// internally across the formats themes may ship (.png/.jpg/.svg), so
|
|
// callers load the returned path directly. Prefers "<prefix>-<dark|light>"
|
|
// when a file exists at that stem, otherwise the plain "<prefix>" as the
|
|
// super fallback. The resolved scheme covers explicit light/dark as well
|
|
// as OS-resolved "system". Returns the path with its file extension when a
|
|
// match is found; unqualified assets keep working unchanged.
|
|
QString assetPath(QStringView prefix) const;
|
|
// Like assetPath, but resolves only the scheme-qualified variant
|
|
// ("<prefix>-<dark|light>.<ext>") and returns an empty string when no
|
|
// variant exists — it never falls back to the plain "<prefix>" asset.
|
|
// Callers that must distinguish "no authored variant" (e.g. to keep a
|
|
// legacy runtime fallback alive) should use this instead of assetPath.
|
|
QString schemeVariantPath(QStringView prefix) const;
|
|
// Load the theme's shipped default palette, falling back to the system
|
|
// theme directory when it is absent from the resolved (user) directory.
|
|
static PaletteConfig
|
|
loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme);
|
|
/** @brief Writes cfg to disk as the theme's palette-<scheme>.toml and updates the
|
|
* theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave
|
|
* and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */
|
|
static bool commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
|
|
void setColorScheme(const QString &scheme);
|
|
void setStyleName(const QString &styleName);
|
|
|
|
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;
|
|
|
|
#endif
|