From 54797452daf84604444f202edcf3f52072cdcd66 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:57:52 +0100 Subject: [PATCH] [Fonts] Wire FontConfig into ThemeManager with appFont and preview (#7535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Fonts] Wire FontConfig into ThemeManager with appFont and preview * [Fonts] Restore the startup font when a config has no InterfaceDefault - Capture the untouched application font next to the default palette and use it in previewFonts' else branch, so a config without InterfaceDefault restores the startup font instead of leaving the last preview applied. - Drop the unpolish/polish loop from previewFonts: QApplication::setFont already notifies every widget, and the loop ran on every editor auto-apply. --------- Co-authored-by: Lukas Brübach --- cockatrice/src/interface/theme_manager.cpp | 80 +++++++++++++++++++++- cockatrice/src/interface/theme_manager.h | 14 ++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 893cdc795..b23f15a2b 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -127,8 +127,10 @@ static QString usableDefaultStyle(const QString &style) ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { defaultStyleName = usableDefaultStyle(qApp->style()->objectName()); - // Capture the untouched application palette before any theme is applied. + // Capture the untouched application palette and font before any theme is + // applied. defaultPalette = qApp->palette(); + defaultFont = qApp->font(); ensureThemeDirectoryExists(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] { @@ -440,6 +442,34 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath return cfg; } +FontConfig ThemeManager::loadFontConfig(const QString &themeDirPath) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + return FontConfig::fromThemeDir(themeDirPath); +} + +bool ThemeManager::saveFontConfig(const QString &themeDirPath, const FontConfig &cfg) +{ + if (themeDirPath.isEmpty()) { + return false; + } + + QDir dir(themeDirPath); + if (!dir.exists()) { + dir.mkpath("."); + } + + QFile f(dir.absoluteFilePath(FontConfig::fileName())); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + + QTextStream(&f) << cfg.toToml(); + return true; +} + bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) { if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) { @@ -488,6 +518,24 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem applyStyleAndPalette(themeName, themeCfg, cfg, scheme); } +void ThemeManager::previewFonts(const FontConfig &cfg) +{ + currentFonts = cfg; + + // QApplication::setFont already sends a font change to every widget, so a + // repolish pass is not needed for fonts (unlike the palette path). + const auto it = cfg.fonts.constFind(AppFont::InterfaceDefault); + if (it != cfg.fonts.constEnd()) { + qApp->setFont(it.value()); + } else { + // A config without InterfaceDefault means "no override": restore the + // startup font instead of leaving the last preview applied. + qApp->setFont(defaultFont); + } + + emit fontsChanged(); +} + void ThemeManager::applyStyleAndPalette(const QString &themeName, const ThemeConfig &themeCfg, const PaletteConfig &palCfg, @@ -591,6 +639,18 @@ QColor ThemeManager::appColor(AppColor::Role role) const return accent; } +QFont ThemeManager::appFont(AppFont::Role role) const +{ + const auto it = currentFonts.fonts.constFind(role); + if (it != currentFonts.fonts.constEnd()) { + return it.value(); + } + + // No per-role scaling: every unset role resolves to the application font, + // so with no fonts.toml present the result is identical to before. + return qApp->font(); +} + void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); @@ -642,6 +702,24 @@ void ThemeManager::themeChangedSlot() applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); + // ── Load fonts: shared per theme, no light/dark split ─────────────────── + FontConfig fonts = loadFontConfig(dirPath); + if (!fonts.hasFonts()) { + // The shipped fonts.toml may live in the system theme directory rather + // than the resolved (user) theme directory, so built-in themes still + // get their curated fonts. + const QString sysFontPath = QDir(systemThemesBasePath()).absoluteFilePath(themeName); + if (sysFontPath != dirPath) { + fonts = loadFontConfig(sysFontPath); + } + } + currentFonts = fonts; + const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault); + if (fontIt != currentFonts.fonts.constEnd()) { + qApp->setFont(fontIt.value()); + } + emit fontsChanged(); + QStringList resources; if (!dirPath.isEmpty()) { resources << dir.absolutePath(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index c1ab65685..c8fba2eb4 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -47,10 +48,15 @@ private: // 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; + // Pristine application font captured at startup, before any theme font is + // applied. Restored when a theme (or a preview) supplies no InterfaceDefault + // font, so switching away from a themed font cannot leave it behind. + QFont defaultFont; QString currentThemePath; std::array brushes; QStringMap availableThemes; QMap currentAppColors; + FontConfig currentFonts; /* Internal cache for multiple backgrounds */ @@ -105,6 +111,9 @@ public: // 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); + // Load/save shared per-theme fonts (fonts.toml, not split by scheme) + static FontConfig loadFontConfig(const QString &themeDirPath); + static bool saveFontConfig(const QString &themeDirPath, const FontConfig &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 "-" @@ -132,10 +141,14 @@ public: void reloadCurrentTheme(); void previewPalette(const PaletteConfig &cfg, const QString &scheme); + void previewFonts(const FontConfig &cfg); // 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; + /** @brief Resolves an application font role: the theme's stored [Fonts] + * value when present, otherwise the application font. */ + QFont appFont(AppFont::Role role) const; QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); @@ -144,6 +157,7 @@ protected slots: signals: void themeChanged(); void paletteChanged(); + void fontsChanged(); }; extern ThemeManager *themeManager;