From d34bb8188408cb827d5cb5e67ef614becd25bce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:00:57 +0200 Subject: [PATCH 01/17] [Fonts] Add AppFont roles and FontConfig fonts.toml round-trip --- cockatrice/src/interface/theme_config.cpp | 148 +++++++++++++++- cockatrice/src/interface/theme_config.h | 49 +++++- tests/CMakeLists.txt | 9 + tests/font_config_test.cpp | 195 ++++++++++++++++++++++ 4 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 tests/font_config_test.cpp diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 652e322c8..54143d7d3 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -304,4 +304,150 @@ QPalette PaletteConfig::apply(QPalette base) const } return base; -} \ No newline at end of file +} + +bool FontConfig::hasFonts() const +{ + return !fonts.isEmpty(); +} + +QString FontConfig::toToml() const +{ + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString entries; + + for (auto it = fonts.cbegin(); it != fonts.cend(); ++it) { + const char *roleName = roleEnum.valueToKey(it.key()); + + if (!roleName) { + continue; + } + + QString family = it.value().family().trimmed(); + int pointSize = it.value().pointSize(); + + if (family.isEmpty() || pointSize <= 0) { + continue; + } + + entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize); + } + + if (entries.isEmpty()) { + return {}; + } + + return "[Fonts]\n" + entries + "\n"; +} + +QString FontConfig::fileName() +{ + return "fonts.toml"; +} + +FontConfig FontConfig::fromFile(const QString &filePath) +{ + FontConfig cfg; + + QFile f(filePath); + + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return cfg; + } + + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString currentSection; + + QTextStream in(&f); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + continue; + } + + if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) { + continue; + } + + int eq = line.indexOf('='); + + if (eq < 0) { + continue; + } + + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + + // Strip inline comments if preceded by whitespace + for (int i = 1; i < value.size(); ++i) { + if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) { + value = value.left(i).trimmed(); + break; + } + } + + // Strip surrounding quotes, if any + if (value.size() >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) { + value = value.mid(1, value.size() - 2).trimmed(); + } + + int comma = value.lastIndexOf(','); + + if (comma < 0) { + continue; + } + + QString family = value.left(comma).trimmed(); + + // Tolerate a partially-quoted family, e.g. "Inter", 10 + if (family.size() >= 2 && + ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) { + family = family.mid(1, family.size() - 2).trimmed(); + } + + bool ok = false; + int pointSize = value.mid(comma + 1).trimmed().toInt(&ok); + + if (!ok || family.isEmpty() || pointSize <= 0) { + continue; + } + + if (key.startsWith("AppFont::")) { + key = key.mid(QStringLiteral("AppFont::").size()); + } + + int roleInt = roleEnum.keyToValue(key.toUtf8().constData()); + + if (roleInt < 0) { + continue; + } + + // Canonical family + point size only: a fresh QFont carries default + // (non-bold, non-italic) weight/style, so nothing caller-side leaks in. + // No validation against installed fonts; Qt substitution applies. + QFont font(family); + font.setPointSize(pointSize); + cfg.fonts[static_cast(roleInt)] = font; + } + + return cfg; +} + +FontConfig FontConfig::fromThemeDir(const QString &themeDirPath) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + + return fromFile(QDir(themeDirPath).absoluteFilePath(fileName())); +} diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 8b91613f5..84601a2bf 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -2,6 +2,7 @@ #define COCKATRICE_THEME_CONFIG_H #include +#include #include #include #include @@ -23,6 +24,23 @@ enum Role Q_ENUM_NS(Role) } // namespace AppColor +// Application-specific font roles, one configurable family + point size per +// purpose. Stored in a shared fonts.toml under a [Fonts] section (one file per +// theme, not split by light/dark). Only family and point size are stored; +// weight/italic are stripped on load and stay caller-side concerns. +namespace AppFont +{ +Q_NAMESPACE +enum Role +{ + InterfaceDefault, + ChatLog, + CardText, + DeckList, +}; +Q_ENUM_NS(Role) +} // namespace AppFont + struct ThemeConfig { QString colorScheme; @@ -60,4 +78,33 @@ struct PaletteConfig QPalette apply(QPalette base) const; }; -#endif // COCKATRICE_THEME_CONFIG_H \ No newline at end of file +/** @brief Per-purpose font configuration, stored as family + point size only. + * + * Serialized to a shared fonts.toml file with a [Fonts] section, e.g. + * @code + * [Fonts] + * InterfaceDefault = "Inter, 10" + * @endcode + */ +struct FontConfig +{ + QMap fonts; + + bool operator==(const FontConfig &rhs) const + { + return fonts == rhs.fonts; + } + bool operator!=(const FontConfig &rhs) const + { + return !(*this == rhs); + } + bool hasFonts() const; + QString toToml() const; + + static QString fileName(); + + static FontConfig fromFile(const QString &filePath); + static FontConfig fromThemeDir(const QString &themeDirPath); +}; + +#endif // COCKATRICE_THEME_CONFIG_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6acd225c2..39a3cbe97 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -412,6 +412,15 @@ cockatrice_add_test( INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src ) +cockatrice_add_test( + NAME + font_config_test + SOURCES + ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/theme_config.cpp + font_config_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/cockatrice/src +) cockatrice_add_test( NAME deck_hash_performance_test diff --git a/tests/font_config_test.cpp b/tests/font_config_test.cpp new file mode 100644 index 000000000..c8b1fb10d --- /dev/null +++ b/tests/font_config_test.cpp @@ -0,0 +1,195 @@ +#include "interface/theme_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// Writes content to a file inside tmp, creating parents as needed. +QString writeTempFile(QTemporaryDir &tmp, const QString &relativePath, const QString &content) +{ + const QString filePath = QDir(tmp.path()).absoluteFilePath(relativePath); + EXPECT_TRUE(QDir().mkpath(QFileInfo(filePath).absolutePath())); + QFile file(filePath); + EXPECT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&file); + out << content; + file.close(); + return filePath; +} + +QFont makeFont(const QString &family, int pointSize) +{ + QFont font(family); + font.setPointSize(pointSize); + return font; +} +} // namespace + +TEST(FontConfigTest, FileNameIsFontsToml) +{ + EXPECT_EQ(FontConfig::fileName(), QStringLiteral("fonts.toml")); +} + +TEST(FontConfigTest, EmptyByDefault) +{ + FontConfig cfg; + EXPECT_FALSE(cfg.hasFonts()); + EXPECT_TRUE(cfg.toToml().isEmpty()); + EXPECT_EQ(cfg, FontConfig()); + EXPECT_EQ(cfg, FontConfig::fromFile("/nonexistent/path/fonts.toml")); + EXPECT_EQ(cfg, FontConfig::fromThemeDir(QString())); +} + +TEST(FontConfigTest, RoundTripPreservesFamilyAndSize) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + cfg.fonts[AppFont::ChatLog] = makeFont("JetBrains Mono", 9); + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); + cfg.fonts[AppFont::DeckList] = makeFont("Roboto", 12); + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].family(), QStringLiteral("Inter")); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].pointSize(), 10); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("JetBrains Mono")); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 9); + EXPECT_EQ(loaded.fonts[AppFont::CardText].family(), QStringLiteral("Noto Serif")); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 11); + EXPECT_EQ(loaded.fonts[AppFont::DeckList].family(), QStringLiteral("Roboto")); + EXPECT_EQ(loaded.fonts[AppFont::DeckList].pointSize(), 12); +} + +TEST(FontConfigTest, RoundTripStripsWeightAndItalic) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + QFont styled("Inter"); + styled.setPointSize(10); + styled.setBold(true); + styled.setItalic(true); + + FontConfig cfg; + cfg.fonts[AppFont::ChatLog] = styled; + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + ASSERT_TRUE(loaded.fonts.contains(AppFont::ChatLog)); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("Inter")); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 10); + EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].bold()); + EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].italic()); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("Inter", 10)); +} + +TEST(FontConfigTest, ToTomlFormat) +{ + FontConfig cfg; + cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + + const QString toml = cfg.toToml(); + EXPECT_TRUE(toml.contains(QStringLiteral("[Fonts]"))); + EXPECT_TRUE(toml.contains(QStringLiteral("InterfaceDefault"))); + EXPECT_TRUE(toml.contains(QStringLiteral("\"Inter, 10\""))); +} + +TEST(FontConfigTest, MalformedLinesAreSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", + "# a comment\n" + "; another comment\n" + "\n" + "StrayBeforeSection = \"Inter, 10\"\n" + "\n" + "[Other]\n" + "ChatLog = \"ShouldBeIgnored, 10\"\n" + "\n" + "[Fonts]\n" + "InterfaceDefault = \"Inter, 10\"\n" + "ChatLog = \"JetBrains Mono\", 9 # inline comment\n" + "CardText = Noto Serif, 11\n" + "AppFont::DeckList = \"Roboto, 12\"\n" + "UnknownRole = \"Inter, 10\"\n" + "QPalette::Window = \"Inter, 10\"\n" + "NoComma = \"Inter\"\n" + "EmptyFamily = \", 10\"\n" + "ZeroSize = \"Inter, 0\"\n" + "NegativeSize = \"Inter, -3\"\n" + "NotANumber = \"Inter, big\"\n" + "NotAnEntry\n"); + + const FontConfig loaded = FontConfig::fromFile(filePath); + + ASSERT_TRUE(loaded.hasFonts()); + EXPECT_EQ(loaded.fonts.size(), 4); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault], makeFont("Inter", 10)); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("JetBrains Mono", 9)); + EXPECT_EQ(loaded.fonts[AppFont::CardText], makeFont("Noto Serif", 11)); + EXPECT_EQ(loaded.fonts[AppFont::DeckList], makeFont("Roboto", 12)); +} + +TEST(FontConfigTest, FromThemeDirReadsFontsToml) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString themeDir = QDir(tmp.path()).absoluteFilePath("MyTheme"); + ASSERT_TRUE(QDir().mkpath(themeDir)); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); + writeTempFile(tmp, "MyTheme/fonts.toml", cfg.toToml()); + + const FontConfig loaded = FontConfig::fromThemeDir(themeDir); + EXPECT_EQ(loaded, cfg); + + // A theme directory without fonts.toml loads as empty. + const QString bareDir = QDir(tmp.path()).absoluteFilePath("BareTheme"); + ASSERT_TRUE(QDir().mkpath(bareDir)); + EXPECT_FALSE(FontConfig::fromThemeDir(bareDir).hasFonts()); +} + +TEST(FontConfigTest, InequalityComparesRoles) +{ + FontConfig left; + left.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + + FontConfig right = left; + EXPECT_EQ(left, right); + EXPECT_FALSE(left != right); + + right.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 11); + EXPECT_NE(left, right); + + right = left; + right.fonts[AppFont::ChatLog] = makeFont("Inter", 10); + EXPECT_NE(left, right); +} + +// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and +// the fallback then links the bare gtest library without gtest_main. +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 134c830d42af4a12befeb449f77a380f6be628c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:03:59 +0200 Subject: [PATCH 02/17] [Fonts] Wire FontConfig into ThemeManager with appFont and preview --- cockatrice/src/interface/theme_manager.cpp | 79 ++++++++++++++++++++++ cockatrice/src/interface/theme_manager.h | 10 +++ 2 files changed, 89 insertions(+) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 893cdc795..dc7e815a2 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -440,6 +440,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 +516,27 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem applyStyleAndPalette(themeName, themeCfg, cfg, scheme); } +void ThemeManager::previewFonts(const FontConfig &cfg) +{ + currentFonts = cfg; + + const auto it = cfg.fonts.constFind(AppFont::InterfaceDefault); + if (it != cfg.fonts.constEnd()) { + qApp->setFont(it.value()); + + // Repolish every widget immediately so the preview applies without + // waiting for natural expose events, mirroring applyStyleAndPalette. + QStyle *style = qApp->style(); + for (QWidget *widget : qApp->allWidgets()) { + style->unpolish(widget); + style->polish(widget); + widget->update(); + } + } + + emit fontsChanged(); +} + void ThemeManager::applyStyleAndPalette(const QString &themeName, const ThemeConfig &themeCfg, const PaletteConfig &palCfg, @@ -591,6 +640,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 +703,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..d59212e92 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 @@ -51,6 +52,7 @@ private: std::array brushes; QStringMap availableThemes; QMap currentAppColors; + FontConfig currentFonts; /* Internal cache for multiple backgrounds */ @@ -105,6 +107,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 +137,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 +153,7 @@ protected slots: signals: void themeChanged(); void paletteChanged(); + void fontsChanged(); }; extern ThemeManager *themeManager; From 4a60b2fde27f06a2c9f1b2f0122fa724fe8c8dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 13:53:21 +0100 Subject: [PATCH 03/17] [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. --- cockatrice/src/interface/theme_manager.cpp | 19 +++++++++---------- cockatrice/src/interface/theme_manager.h | 4 ++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index dc7e815a2..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] { @@ -520,18 +522,15 @@ 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()); - - // Repolish every widget immediately so the preview applies without - // waiting for natural expose events, mirroring applyStyleAndPalette. - QStyle *style = qApp->style(); - for (QWidget *widget : qApp->allWidgets()) { - style->unpolish(widget); - style->polish(widget); - widget->update(); - } + } else { + // A config without InterfaceDefault means "no override": restore the + // startup font instead of leaving the last preview applied. + qApp->setFont(defaultFont); } emit fontsChanged(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index d59212e92..c8fba2eb4 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -48,6 +48,10 @@ 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; From b57cd77986b7ce8c9284065f92215f0754201a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:12:16 +0200 Subject: [PATCH 04/17] [Fonts] Add per-purpose font section to palette editor --- cockatrice/CMakeLists.txt | 1 + .../palette_editor/font_grid_widget.cpp | 124 ++++++++++++++++++ .../palette_editor/font_grid_widget.h | 43 ++++++ .../palette_editor/palette_editor_dialog.cpp | 108 +++++++++++++-- .../palette_editor/palette_editor_dialog.h | 12 ++ cockatrice/src/interface/theme_manager.cpp | 16 +++ cockatrice/src/interface/theme_manager.h | 3 + 7 files changed, 297 insertions(+), 10 deletions(-) create mode 100644 cockatrice/src/interface/palette_editor/font_grid_widget.cpp create mode 100644 cockatrice/src/interface/palette_editor/font_grid_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 642d998a0..d5e383d95 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -154,6 +154,7 @@ set(cockatrice_SOURCES src/interface/palette_editor/palette_generator.cpp src/interface/palette_editor/quick_setup_panel.cpp src/interface/palette_editor/palette_grid_widget.cpp + src/interface/palette_editor/font_grid_widget.cpp src/interface/palette_editor/palette_editor_dialog.cpp src/interface/widgets/cards/additional_info/color_identity_widget.cpp src/interface/widgets/cards/additional_info/deck_color_identity.cpp diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp new file mode 100644 index 000000000..c3841b95f --- /dev/null +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -0,0 +1,124 @@ +#include "font_grid_widget.h" + +#include "../theme_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr int MIN_FONT_SIZE = 6; +static constexpr int MAX_FONT_SIZE = 72; +static constexpr int FALLBACK_FONT_SIZE = 10; + +static const QMap APP_FONT_DESCRIPTIONS = { + {AppFont::InterfaceDefault, QT_TR_NOOP("Default font for menus, dialogs and widgets")}, + {AppFont::ChatLog, QT_TR_NOOP("Font for chat messages and logs")}, + {AppFont::CardText, QT_TR_NOOP("Font for text rendered on cards")}, + {AppFont::DeckList, QT_TR_NOOP("Font for deck lists and the deck editor")}, +}; + +static QString roleDisplayName(AppFont::Role role) +{ + switch (role) { + case AppFont::InterfaceDefault: + return FontGridWidget::tr("Interface default"); + case AppFont::ChatLog: + return FontGridWidget::tr("Chat log"); + case AppFont::CardText: + return FontGridWidget::tr("Card text"); + case AppFont::DeckList: + return FontGridWidget::tr("Deck list"); + default: + return QString(); + } +} + +FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) +{ + auto *grid = new QGridLayout(this); + grid->setSpacing(3); + grid->setContentsMargins(12, 8, 12, 8); + grid->setColumnStretch(0, 0); + grid->setColumnStretch(1, 1); + grid->setColumnStretch(2, 0); + + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + + auto *label = new QLabel(this); + grid->addWidget(label, i, 0); + + auto *familyCombo = new QFontComboBox(this); + grid->addWidget(familyCombo, i, 1); + connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); }); + + auto *sizeSpin = new QSpinBox(this); + sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); + grid->addWidget(sizeSpin, i, 2); + connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); }); + + fontRows[role] = {label, familyCombo, sizeSpin}; + } + + retranslateUi(); +} + +void FontGridWidget::loadFonts(const FontConfig &cfg) +{ + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows[role]; + + // Roles absent from the file resolve to the theme's effective font, so + // unset roles visibly show the system default instead of blanks. + const QFont font = cfg.fonts.value(role, themeManager->appFont(role)); + row.familyCombo->setCurrentFont(font); + + int size = font.pointSize(); + if (size <= 0) { + size = qApp->font().pointSize(); + } + if (size <= 0) { + size = FALLBACK_FONT_SIZE; + } + row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE)); + } +} + +FontConfig FontGridWidget::currentFontConfig() const +{ + FontConfig cfg; + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows.value(role); + + // Canonical family + point size only: a fresh QFont carries default + // (non-bold, non-italic) weight/style, so nothing combo-side leaks in. + QFont font(row.familyCombo->currentFont().family()); + font.setPointSize(row.sizeSpin->value()); + cfg.fonts[role] = font; + } + return cfg; +} + +void FontGridWidget::retranslateUi() +{ + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows.value(role); + + row.label->setText(roleDisplayName(role)); + row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); + row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); + row.sizeSpin->setToolTip(tr("Point size")); + } +} diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.h b/cockatrice/src/interface/palette_editor/font_grid_widget.h new file mode 100644 index 000000000..8c80306d3 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.h @@ -0,0 +1,43 @@ +#ifndef COCKATRICE_FONT_GRID_WIDGET_H +#define COCKATRICE_FONT_GRID_WIDGET_H + +#include "../theme_config.h" + +#include +#include +#include + +class QFontComboBox; +class QGridLayout; +class QLabel; +class QSpinBox; + +// Per-role font editors — one (family, point size) row per AppFont::Role. +// Owns the load/read round-trip for FontConfig but has no file I/O, mirroring +// PaletteGridWidget. Only family and point size are edited; weight and italic +// stay caller-side concerns and are stripped when reading back. +class FontGridWidget : public QWidget +{ + Q_OBJECT +public: + explicit FontGridWidget(QWidget *parent = nullptr); + + void loadFonts(const FontConfig &cfg); + FontConfig currentFontConfig() const; + void retranslateUi(); + +signals: + void fontsChanged(); + +private: + struct FontRow + { + QLabel *label = nullptr; + QFontComboBox *familyCombo = nullptr; + QSpinBox *sizeSpin = nullptr; + }; + + QMap fontRows; +}; + +#endif // COCKATRICE_FONT_GRID_WIDGET_H diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index b6271291d..63de3f487 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -2,6 +2,7 @@ #include "../theme_config.h" #include "../theme_manager.h" +#include "font_grid_widget.h" #include "palette_generator.h" #include "palette_grid_widget.h" #include "quick_setup_panel.h" @@ -41,6 +42,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Load both scheme configs upfront so switching is instant loadSchemes(); + loadFonts(); loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; @@ -125,6 +127,32 @@ void PaletteEditorDialog::setupUi() paletteGrid->setVisible(false); root->addWidget(paletteGrid, 1); + // Font section — sibling to the palette grid, sharing its collapsible + // advanced area pattern. Fonts are shared per theme, not split by scheme. + fontToggleButton = new QPushButton(this); + fontToggleButton->setCheckable(true); + fontToggleButton->setChecked(false); + fontToggleButton->setFlat(true); + fontToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }" + "QPushButton:checked { }"); + root->addWidget(fontToggleButton); + + fontSeparator = makeSeparator(); + fontSeparator->setVisible(false); + root->addWidget(fontSeparator); + + fontSection = new QWidget; + auto *fontSectionLayout = new QVBoxLayout(fontSection); + fontSectionLayout->setContentsMargins(0, 0, 0, 0); + fontSectionLayout->setSpacing(0); + fontNoteLabel = new QLabel(fontSection); + fontNoteLabel->setContentsMargins(12, 4, 12, 0); + fontSectionLayout->addWidget(fontNoteLabel); + fontGrid = new FontGridWidget(fontSection); + fontSectionLayout->addWidget(fontGrid); + fontSection->setVisible(false); + root->addWidget(fontSection); + // Footer root->addWidget(makeSeparator()); footer = new QWidget; @@ -157,6 +185,7 @@ void PaletteEditorDialog::setupUi() connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply); connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent); connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); }); + connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] { autoApplyTimer->start(); }); connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault); connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset); connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave); @@ -166,15 +195,32 @@ void PaletteEditorDialog::setupUi() 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 - } + updateAdvancedHeight(); }); + connect(fontToggleButton, &QPushButton::toggled, this, [this](bool open) { + fontToggleButton->setText(open ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); + fontSeparator->setVisible(open); + fontSection->setVisible(open); + updateAdvancedHeight(); + }); +} + +void PaletteEditorDialog::updateAdvancedHeight() +{ + int height = 220; + if (paletteGridToggleButton->isChecked()) { + height += 460; + } + if (fontToggleButton->isChecked()) { + height += 200; + } + setMinimumHeight(height); + + if (paletteGrid->isVisible() || fontSection->isVisible()) { + resize(width(), height); + } else { + adjustSize(); // shrinks to fit just the visible content + } } void PaletteEditorDialog::retranslateUi() @@ -189,12 +235,16 @@ void PaletteEditorDialog::retranslateUi() editingLabel->setText(tr("Editing:")); paletteGridToggleButton->setText(tr("▶ Edit Palette")); paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks")); + fontToggleButton->setText(fontSection->isVisible() ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); + fontToggleButton->setToolTip(tr("Show or hide the per-purpose font settings for this theme")); + fontNoteLabel->setText(tr("Fonts belong to theme %1").arg(themeName)); + fontGrid->retranslateUi(); revertButton->setText(tr("↺ Revert to theme default")); resetBtn->setText(tr("Reset")); saveBtn->setText(tr("Save")); - resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); - saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); + resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette and fonts")); + saveBtn->setToolTip(tr("Write palette-%1.toml and fonts.toml, then reload the theme").arg(loadedScheme.toLower())); if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) { saveBtn->setEnabled(false); @@ -228,6 +278,19 @@ void PaletteEditorDialog::loadSchemes() } } +void PaletteEditorDialog::loadFonts() +{ + // Same user-dir > system-dir fallback ThemeManager uses: the shipped + // fonts.toml may live in the system theme directory rather than the + // resolved (user) theme directory. + FontConfig cfg = ThemeManager::loadFontConfig(themeDirPath); + if (!cfg.hasFonts()) { + cfg = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); + } + savedFontConfig = cfg; + fontGrid->loadFonts(cfg); +} + void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) { QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight); @@ -258,6 +321,7 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi void PaletteEditorDialog::onApply() { themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); + themeManager->previewFonts(fontGrid->currentFontConfig()); } void PaletteEditorDialog::onSave() @@ -308,6 +372,17 @@ void PaletteEditorDialog::onSave() savedConfig[it.key()] = it.value(); } + // Persist the shared per-theme fonts when they changed. + const FontConfig fontCfg = fontGrid->currentFontConfig(); + if (fontCfg != savedFontConfig) { + if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) { + QMessageBox::warning(this, tr("Save failed"), + tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir)); + return; + } + savedFontConfig = fontCfg; + } + themeManager->reloadCurrentTheme(); accept(); } @@ -316,6 +391,7 @@ void PaletteEditorDialog::onReset() { workingConfig[loadedScheme] = savedConfig[loadedScheme]; paletteGrid->loadPalette(savedConfig[loadedScheme]); + fontGrid->loadFonts(savedFontConfig); seedAccentFromScheme(loadedScheme); } @@ -350,6 +426,18 @@ void PaletteEditorDialog::onRevertToDefault() workingConfig[loadedScheme] = def; paletteGrid->loadPalette(def); seedAccentFromScheme(loadedScheme); + + // Revert the shared per-theme fonts the same way: delete the user-written + // fonts.toml so the theme falls back to its shipped default, then reflect + // the resolved fonts in the editor. + QFile::remove(QDir(saveDir).absoluteFilePath(FontConfig::fileName())); + + // Reload the live theme so the font revert takes effect immediately. + themeManager->reloadCurrentTheme(); + + const FontConfig fontDef = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); + savedFontConfig = fontDef; + fontGrid->loadFonts(fontDef); } void PaletteEditorDialog::changeEvent(QEvent *e) diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h index e29a70cb0..0a9d47c6c 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h @@ -15,6 +15,7 @@ class QComboBox; class QDialogButtonBox; class QPushButton; class PaletteGridWidget; +class FontGridWidget; class QuickSetupPanel; class QFrame; class QWidget; @@ -38,8 +39,10 @@ private: void setupUi(); void retranslateUi(); void refreshChromePalettes(); + void updateAdvancedHeight(); void loadScheme(const QString &scheme); // snapshot current, switch, load void seedAccentFromScheme(const QString &scheme); + void loadFonts(); // shared per theme, not split by light/dark // Sub-widgets QWidget *header; @@ -49,6 +52,11 @@ private: PaletteGridWidget *paletteGrid = nullptr; QPushButton *paletteGridToggleButton = nullptr; QFrame *paletteGridSeparator = nullptr; + FontGridWidget *fontGrid = nullptr; + QPushButton *fontToggleButton = nullptr; + QFrame *fontSeparator = nullptr; + QWidget *fontSection = nullptr; + QLabel *fontNoteLabel = nullptr; QWidget *footer; QComboBox *schemeComboBox = nullptr; QDialogButtonBox *buttonBox = nullptr; @@ -68,6 +76,10 @@ private: QMap workingConfig; QMap savedConfig; + // Fonts are shared per theme (fonts.toml, not split by light/dark), so a + // single saved config suffices; the live edits are read from fontGrid. + FontConfig savedFontConfig; + QTimer *autoApplyTimer = nullptr; protected: diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index b23f15a2b..d1b7a3db8 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -450,6 +450,18 @@ FontConfig ThemeManager::loadFontConfig(const QString &themeDirPath) return FontConfig::fromThemeDir(themeDirPath); } +FontConfig ThemeManager::loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName) +{ + FontConfig cfg = FontConfig::fromThemeDir(themeDirPath); + if (!cfg.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. + cfg = FontConfig::fromThemeDir(QDir(systemThemesBasePath()).absoluteFilePath(themeName)); + } + return cfg; +} + bool ThemeManager::saveFontConfig(const QString &themeDirPath, const FontConfig &cfg) { if (themeDirPath.isEmpty()) { @@ -717,6 +729,10 @@ void ThemeManager::themeChangedSlot() const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault); if (fontIt != currentFonts.fonts.constEnd()) { qApp->setFont(fontIt.value()); + } else { + // No theme font: restore the pristine application font captured at + // startup, so a reverted preview cannot leak into the live theme. + qApp->setFont(defaultFont); } emit fontsChanged(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index c8fba2eb4..6635b7128 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -132,6 +132,9 @@ public: // theme directory when it is absent from the resolved (user) directory. static PaletteConfig loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme); + // Load the theme's shipped default fonts, falling back to the system theme + // directory when fonts.toml is absent from the resolved (user) directory. + static FontConfig loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName); /** @brief Writes cfg to disk as the theme's palette-.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. */ From 77ed6550f94d9f6feef23ade8c4330b63d3a4e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 13:59:16 +0100 Subject: [PATCH 05/17] [Fonts] Make the editor's fallback and Reset preview-independent - Add ThemeManager::defaultAppFont(), which resolves an unset role against the startup font instead of currentFonts, and use it in FontGridWidget::loadFonts so the grid never shows the live preview as the theme's default. - Reload the live theme in PaletteEditorDialog::onReset before re-reading the grid, so Reset restores the saved fonts before loadFonts resolves fallbacks. --- .../src/interface/palette_editor/font_grid_widget.cpp | 9 ++++++--- .../interface/palette_editor/palette_editor_dialog.cpp | 4 ++++ cockatrice/src/interface/theme_manager.cpp | 8 ++++++++ cockatrice/src/interface/theme_manager.h | 5 +++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp index c3841b95f..0f01fbe09 100644 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -76,9 +76,12 @@ void FontGridWidget::loadFonts(const FontConfig &cfg) auto role = static_cast(fontEnum.value(i)); const FontRow &row = fontRows[role]; - // Roles absent from the file resolve to the theme's effective font, so - // unset roles visibly show the system default instead of blanks. - const QFont font = cfg.fonts.value(role, themeManager->appFont(role)); + // Roles absent from the file resolve the same way the runtime does: an unset + // role falls back to the config's InterfaceDefault, and only then to the + // startup font. appFont() would return the live preview instead, so Reset and + // a first load must not measure against it. + const QFont fallback = cfg.fonts.value(AppFont::InterfaceDefault, themeManager->defaultAppFont(role)); + const QFont font = cfg.fonts.value(role, fallback); row.familyCombo->setCurrentFont(font); int size = font.pointSize(); diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 63de3f487..09c81df5d 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -391,6 +391,10 @@ void PaletteEditorDialog::onReset() { workingConfig[loadedScheme] = savedConfig[loadedScheme]; paletteGrid->loadPalette(savedConfig[loadedScheme]); + // Restore the live theme before re-reading the grid: loadFonts resolves + // unset roles, and the preview has currentFonts pointing at the edited + // config until the theme is reloaded. + themeManager->reloadCurrentTheme(); fontGrid->loadFonts(savedFontConfig); seedAccentFromScheme(loadedScheme); } diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index d1b7a3db8..bf275c7ac 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -663,6 +663,14 @@ QFont ThemeManager::appFont(AppFont::Role role) const return qApp->font(); } +QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const +{ + // No per-role scaling and no preview: an unset role resolves to the font + // captured at startup, which is what the theme falls back to once the + // editor closes. + return defaultFont; +} + void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 6635b7128..37a70f755 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -152,6 +152,11 @@ public: /** @brief Resolves an application font role: the theme's stored [Fonts] * value when present, otherwise the application font. */ QFont appFont(AppFont::Role role) const; + /** @brief Resolves an application font role against the startup font only, + * ignoring any previewed config in currentFonts. Editors that show + * "what this role resolves to" must use this, because appFont() + * reflects a live preview while the editor is open. */ + QFont defaultAppFont(AppFont::Role role) const; QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); From 1bdd43d921c46bc1351b1586207914ab7f3ada18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:21:14 +0200 Subject: [PATCH 06/17] [Fonts] Migrate ChatLog surfaces to theme font role --- cockatrice/src/interface/theme_manager.cpp | 21 +++++++++++++++++-- cockatrice/src/interface/theme_manager.h | 4 +++- .../widgets/dialogs/dlg_my_reports.cpp | 17 +++++++++++++-- .../widgets/dialogs/dlg_my_reports.h | 1 + .../widgets/dialogs/dlg_report_user.cpp | 16 +++++++++++++- .../widgets/dialogs/dlg_report_user.h | 1 + .../widgets/server/chat_view/chat_view.cpp | 14 +++++++++++++ .../widgets/server/chat_view/chat_view.h | 1 + .../src/interface/widgets/tabs/tab_report.cpp | 16 +++++++++++++- .../src/interface/widgets/tabs/tab_report.h | 1 + 10 files changed, 85 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index bf275c7ac..9bbac163a 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -658,8 +659,24 @@ QFont ThemeManager::appFont(AppFont::Role role) const 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. + // Role fallbacks preserve the pre-theme defaults when no fonts.toml is + // present: chat logs rendered monospace (matching the report dialogs), + // card text from a serif basis, everything else from the app font. + if (role == AppFont::ChatLog) { + QFont monoFont(QStringLiteral("monospace")); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + return monoFont; + } + if (role == AppFont::CardText) { + return QFont(QStringLiteral("Serif")); + } + + // No per-role scaling: every other unset role resolves to the application + // font, so with no fonts.toml present the result is identical to before. return qApp->font(); } diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 37a70f755..4a782bc9f 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -150,7 +150,9 @@ public: // 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. */ + * value when present, otherwise a per-role fallback (monospace for + * ChatLog, serif basis for CardText, the application font for the + * remaining roles). */ QFont appFont(AppFont::Role role) const; /** @brief Resolves an application font role against the startup font only, * ignoring any previewed config in currentFonts. Editors that show diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp index 2a2758ba5..833ba7e22 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp @@ -1,5 +1,6 @@ #include "dlg_my_reports.h" +#include "../interface/theme_manager.h" #include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" @@ -24,7 +25,6 @@ #include #include #include -#include #include #include @@ -69,7 +69,8 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) auto *chatGroup = new QGroupBox(tr("Chat Log Context")); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &DlgMyReports::applyChatLogFont); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -122,6 +123,18 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) refreshList(); } +void DlgMyReports::applyChatLogFont() +{ + // Family comes from the ChatLog theme role; size stays caller-side as before. + QFont monoFont = themeManager->appFont(AppFont::ChatLog); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + chatLogEdit->setFont(monoFont); +} + void DlgMyReports::refreshList() { selectedReportIdBeforeRefresh = selectedReportId; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h index dc9f90d2d..90772f051 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h @@ -29,6 +29,7 @@ private slots: void reportDetailsResponse(const Response &response); void addComment(); void addCommentResponse(const Response &response); + void applyChatLogFont(); private: void loadReportDetails(const ServerInfo_Report &report); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp index 3ef45020b..2a186af84 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp @@ -1,5 +1,6 @@ #include "dlg_report_user.h" +#include "../interface/theme_manager.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" @@ -91,7 +92,8 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, auto *chatLayout = new QVBoxLayout(chatGroup); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &DlgReportUser::applyChatLogFont); if (!_autoChatLog.isEmpty()) { chatLogEdit->setPlainText(_autoChatLog); } else { @@ -120,6 +122,18 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, layout->addWidget(buttonBox); } +void DlgReportUser::applyChatLogFont() +{ + // Family comes from the ChatLog theme role; size stays caller-side as before. + QFont monoFont = themeManager->appFont(AppFont::ChatLog); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + chatLogEdit->setFont(monoFont); +} + void DlgReportUser::actSubmit() { const QString description = descriptionEdit->toPlainText().trimmed(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h index c7fce055d..0f2fa5080 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h @@ -27,6 +27,7 @@ public: private slots: void actSubmit(); void reportResponse(const Response &response); + void applyChatLogFont(); private: static QString categoryLabel(const QString &key); diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index 037a4a1e5..c57f65352 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -4,6 +4,7 @@ #include "../../client/sound_engine.h" #include "../../interface/card_localization.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../../tabs/tab_supervisor.h" #include "../user/user_context_menu.h" #include "../user/user_list_manager.h" @@ -63,6 +64,9 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette); + applyChatFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &ChatView::applyChatFont); + userContextMenu = new UserContextMenu(tabSupervisor, this, game); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); @@ -108,6 +112,16 @@ void ChatView::adjustColorsToPalette() QTimer::singleShot(0, this, &ChatView::refreshBlockColors); } +void ChatView::applyChatFont() +{ + // Family comes from the ChatLog theme role; the message char formats carry + // no family of their own, so they (and new content) follow the widget and + // document default fonts. Weights/italics stay caller-side as before. + const QFont chatFont = themeManager->appFont(AppFont::ChatLog); + setFont(chatFont); + document()->setDefaultFont(chatFont); +} + void ChatView::refreshBlockColors() { QTextDocument *doc = document(); diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 3fa7224e5..a2e2f33dd 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -111,6 +111,7 @@ private slots: void openLink(const QUrl &link); void actMessageClicked(); void adjustColorsToPalette(); + void applyChatFont(); void refreshBlockColors(); void onScrollBarRangeChanged(); void onScrollBarValueChanged(int value); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 988c3e7d9..4101290d3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -1,5 +1,6 @@ #include "tab_report.h" +#include "../interface/theme_manager.h" #include "../server/user/user_context_menu.h" #include "../utility/report_utils.h" #include "abstract_client.h" @@ -137,7 +138,8 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T chatGroup = new QGroupBox; chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &TabReport::applyChatLogFont); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -269,6 +271,18 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T refreshList(); } +void TabReport::applyChatLogFont() +{ + // Family comes from the ChatLog theme role; size stays caller-side as before. + QFont monoFont = themeManager->appFont(AppFont::ChatLog); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + chatLogEdit->setFont(monoFont); +} + void TabReport::retranslateUi() { userContextMenu->retranslateUi(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.h b/cockatrice/src/interface/widgets/tabs/tab_report.h index 32ebe1a3f..43e778f4b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.h +++ b/cockatrice/src/interface/widgets/tabs/tab_report.h @@ -62,6 +62,7 @@ private slots: void statsResponse(const Response &response); void reopenReport(); void reopenResponse(const Response &response); + void applyChatLogFont(); private: int selectedReportId() const; From 4e78edf794bcd85ea20b245810a807b161f1ddc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 14:06:05 +0100 Subject: [PATCH 07/17] [Fonts] Keep ChatView on the app font and share the report log font - ChatLog's unset fallback is qApp->font() again: ChatView never set a font before the role existed, so an unset role must not switch chat and the game log to monospace at the system GeneralFont size. - Move the monospace fallback the report dialogs used before into report_utils::reportLogFont(), which returns the theme's ChatLog role when it is set and the old monospace otherwise. tab_report, dlg_my_reports and dlg_report_user now share it, so they cannot drift and they honour a themed size instead of overriding it. --- cockatrice/src/interface/theme_manager.cpp | 19 ++++++++----------- cockatrice/src/interface/theme_manager.h | 7 ++++--- .../widgets/dialogs/dlg_my_reports.cpp | 10 ++-------- .../widgets/dialogs/dlg_report_user.cpp | 11 +++-------- .../src/interface/widgets/tabs/tab_report.cpp | 10 ++-------- .../widgets/utility/report_utils.cpp | 16 ++++++++++++++++ .../interface/widgets/utility/report_utils.h | 7 +++++++ 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 9bbac163a..8d538c134 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -660,17 +660,9 @@ QFont ThemeManager::appFont(AppFont::Role role) const } // Role fallbacks preserve the pre-theme defaults when no fonts.toml is - // present: chat logs rendered monospace (matching the report dialogs), - // card text from a serif basis, everything else from the app font. - if (role == AppFont::ChatLog) { - QFont monoFont(QStringLiteral("monospace")); - monoFont.setStyleHint(QFont::Monospace); - const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); - if (systemPointSize > 0) { - monoFont.setPointSize(systemPointSize); - } - return monoFont; - } + // present: card text starts from a serif basis, everything else from the + // application font. The report dialogs that used to hardcode monospace ask + // for it explicitly (report_utils::reportLogFont). if (role == AppFont::CardText) { return QFont(QStringLiteral("Serif")); } @@ -680,6 +672,11 @@ QFont ThemeManager::appFont(AppFont::Role role) const return qApp->font(); } +bool ThemeManager::hasAppFont(AppFont::Role role) const +{ + return currentFonts.fonts.contains(role); +} + QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const { // No per-role scaling and no preview: an unset role resolves to the font diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 4a782bc9f..aba91faf7 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -150,10 +150,11 @@ public: // 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 a per-role fallback (monospace for - * ChatLog, serif basis for CardText, the application font for the - * remaining roles). */ + * value when present, otherwise a per-role fallback (serif basis + * for CardText, the application font for the remaining roles). */ QFont appFont(AppFont::Role role) const; + /** @brief Whether the active theme defines this role in fonts.toml. */ + bool hasAppFont(AppFont::Role role) const; /** @brief Resolves an application font role against the startup font only, * ignoring any previewed config in currentFonts. Editors that show * "what this role resolves to" must use this, because appFont() diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp index 833ba7e22..a7387a068 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp @@ -7,6 +7,7 @@ #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include +#include #include #include #include @@ -125,14 +126,7 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) void DlgMyReports::applyChatLogFont() { - // Family comes from the ChatLog theme role; size stays caller-side as before. - QFont monoFont = themeManager->appFont(AppFont::ChatLog); - monoFont.setStyleHint(QFont::Monospace); - const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); - if (systemPointSize > 0) { - monoFont.setPointSize(systemPointSize); - } - chatLogEdit->setFont(monoFont); + chatLogEdit->setFont(report_utils::reportLogFont()); } void DlgMyReports::refreshList() diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp index 2a186af84..c7c32469b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp @@ -1,11 +1,13 @@ #include "dlg_report_user.h" #include "../interface/theme_manager.h" +#include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" #include #include +#include #include #include #include @@ -124,14 +126,7 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, void DlgReportUser::applyChatLogFont() { - // Family comes from the ChatLog theme role; size stays caller-side as before. - QFont monoFont = themeManager->appFont(AppFont::ChatLog); - monoFont.setStyleHint(QFont::Monospace); - const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); - if (systemPointSize > 0) { - monoFont.setPointSize(systemPointSize); - } - chatLogEdit->setFont(monoFont); + chatLogEdit->setFont(report_utils::reportLogFont()); } void DlgReportUser::actSubmit() diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 4101290d3..7986286b3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -273,14 +274,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T void TabReport::applyChatLogFont() { - // Family comes from the ChatLog theme role; size stays caller-side as before. - QFont monoFont = themeManager->appFont(AppFont::ChatLog); - monoFont.setStyleHint(QFont::Monospace); - const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); - if (systemPointSize > 0) { - monoFont.setPointSize(systemPointSize); - } - chatLogEdit->setFont(monoFont); + chatLogEdit->setFont(report_utils::reportLogFont()); } void TabReport::retranslateUi() diff --git a/cockatrice/src/interface/widgets/utility/report_utils.cpp b/cockatrice/src/interface/widgets/utility/report_utils.cpp index d7fc083c6..df2400b14 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.cpp +++ b/cockatrice/src/interface/widgets/utility/report_utils.cpp @@ -1,5 +1,6 @@ #include "report_utils.h" +#include "../../theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +21,20 @@ namespace report_utils { +QFont reportLogFont() +{ + if (themeManager->hasAppFont(AppFont::ChatLog)) { + return themeManager->appFont(AppFont::ChatLog); + } + QFont monoFont(QStringLiteral("monospace")); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + return monoFont; +} + namespace { QColor reportStatusColor(const QString &status) diff --git a/cockatrice/src/interface/widgets/utility/report_utils.h b/cockatrice/src/interface/widgets/utility/report_utils.h index 679989486..32cc19eac 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.h +++ b/cockatrice/src/interface/widgets/utility/report_utils.h @@ -1,6 +1,7 @@ #ifndef REPORT_UTILS_H #define REPORT_UTILS_H +#include #include #include @@ -11,6 +12,12 @@ class ServerInfo_Report; namespace report_utils { +/** @brief Font for the report chat-log views: the theme's ChatLog role when it + * sets one, otherwise the monospace font these views used before the + * role existed. Shared by the three report surfaces so they cannot + * drift apart. */ +QFont reportLogFont(); + QString formatReportCategory(const QString &category); QString formatReportTime(qint64 secondsSinceEpoch); From 2593824e939894259522276c812209ce5854ebac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:27:51 +0200 Subject: [PATCH 08/17] [Fonts] Migrate CardText surfaces to theme font role --- .../board/abstract_card_item.cpp | 3 ++- .../board/abstract_graphics_item.cpp | 4 +++- .../game_graphics/board/counter_general.cpp | 3 ++- .../game_graphics/player/player_target.cpp | 5 ++-- .../widgets/cards/art_crop_attribution.cpp | 4 ++++ ..._info_picture_with_text_overlay_widget.cpp | 18 +++++++++++--- ...rd_info_picture_with_text_overlay_widget.h | 3 +++ .../all_zones_card_amount_widget.cpp | 12 ++++++++-- .../all_zones_card_amount_widget.h | 3 +++ .../printing_selector/card_amount_widget.cpp | 12 ++++++++-- .../printing_selector/card_amount_widget.h | 1 + ...dekt_deck_preview_image_display_widget.cpp | 23 ++++++++++++------ ...hidekt_deck_preview_image_display_widget.h | 3 +++ ...i_response_card_details_display_widget.cpp | 16 ++++++++++++- ...api_response_card_details_display_widget.h | 3 +++ ...ponse_commander_details_display_widget.cpp | 24 ++++++++++++++++--- ...esponse_commander_details_display_widget.h | 3 +++ .../utility/card_completer_delegate.cpp | 7 +++--- 18 files changed, 121 insertions(+), 26 deletions(-) diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index 0e8b6ced3..84e062cd7 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/card_localization.h" #include "../../interface/card_picture_loader/card_picture_loader.h" +#include "../../interface/theme_manager.h" #include "../card_dimensions.h" #include "../game_scene.h" #include "../z_values.h" @@ -154,7 +155,7 @@ void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &transla painter->rotate(angle); painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2); - QFont f; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(fontSize * scale); painter->setFont(f); diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 50e80d359..e847e878d 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -1,5 +1,7 @@ #include "abstract_graphics_item.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -20,7 +22,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, painter->save(); QString numStr = QString::number(number); - QFont font("Serif"); + QFont font = themeManager->appFont(AppFont::CardText); font.setPixelSize(fontSize); font.setWeight(QFont::Bold); diff --git a/cockatrice/src/game_graphics/board/counter_general.cpp b/cockatrice/src/game_graphics/board/counter_general.cpp index 4a8bbd6a4..b06a98e95 100644 --- a/cockatrice/src/game_graphics/board/counter_general.cpp +++ b/cockatrice/src/game_graphics/board/counter_general.cpp @@ -1,6 +1,7 @@ #include "counter_general.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "abstract_graphics_item.h" #include @@ -41,7 +42,7 @@ void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * / painter->drawPixmap(QPoint(0, 0), pixmap); if (value) { - QFont f("Serif"); + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(qMax((int)(radius * scaleFactor), 10)); f.setWeight(QFont::Bold); painter->setPen(Qt::black); diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index 1cd97e7b5..2e3fdffdf 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -2,6 +2,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../board/abstract_counter.h" #include "../board/abstract_graphics_item.h" #include "../board/arrow_target.h" @@ -76,7 +77,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* QRectF translatedRect = path.controlPointRect(); QSize translatedSize = translatedRect.size().toSize(); - QFont font("Serif"); + QFont font = themeManager->appFont(AppFont::CardText); font.setWeight(QFont::Bold); font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9)); painter->setFont(font); @@ -226,7 +227,7 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o name = name.mid(0, 10) + "..."; } - QFont font; + QFont font = themeManager->appFont(AppFont::CardText); font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9)); painter->setFont(font); painter->setPen(Qt::white); diff --git a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp index c4b347047..8475a0ed9 100644 --- a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp +++ b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp @@ -1,5 +1,6 @@ #include "art_crop_attribution.h" +#include "../../theme_manager.h" #include "libcockatrice/card/printing/printing_info.h" #include @@ -35,7 +36,10 @@ QRectF paintArtAttribution(QPainter &painter, painter.save(); + // Family comes from the CardText theme role; the incoming painter size + // stays the basis for the scale factor exactly as before. QFont font = painter.font(); + font.setFamily(themeManager->appFont(AppFont::CardText).family()); font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale)); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 04fe9ab69..56fe0bda4 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -1,5 +1,7 @@ #include "card_info_picture_with_text_overlay_widget.h" +#include "../../theme_manager.h" + #include #include #include @@ -43,6 +45,7 @@ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidg fontSize(fontSize), textAlignment(alignment), highlighted(false) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + connect(themeManager, &ThemeManager::fontsChanged, this, &CardInfoPictureWithTextOverlayWidget::applyCardTextFont); } /** @@ -97,6 +100,13 @@ void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment update(); } +void CardInfoPictureWithTextOverlayWidget::applyCardTextFont() +{ + // The overlay paint reads the CardText role font at paint time, so a + // theme font change only needs a repaint here. + update(); +} + void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted) { if (highlighted == _highlighted) { @@ -168,8 +178,10 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) return; } - // Calculate the optimal font size - QFont font = painter.font(); + // Calculate the optimal font size. The family comes from the CardText + // theme role; the fontSize member stays the starting size and the + // shrink-to-fit logic below is unchanged. + QFont font = themeManager->appFont(AppFont::CardText); int optimalFontSize = fontSize; // Start with the user-defined font size const QFontMetrics baseMetrics(font); int textWidth = pixmapRect.width(); @@ -284,7 +296,7 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size(); // Get the font metrics for the overlay text - QFont font; + QFont font = themeManager->appFont(AppFont::CardText); font.setPointSize(fontSize); const QFontMetrics fontMetrics(font); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index 6c547dc13..41019f543 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -47,6 +47,9 @@ protected: void mousePressEvent(QMouseEvent *event) override; [[nodiscard]] QSize minimumSizeHint() const override; +private slots: + void applyCardTextFont(); + private: void drawOutlinedText(QPainter &painter, const QRect &textRect, diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index bad88ddbb..13a43bd47 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -1,5 +1,6 @@ #include "all_zones_card_amount_widget.h" +#include "../../theme_manager.h" #include "../general/display/shadow_background_label.h" #include "card_amount_widget.h" #include "libcockatrice/card/card_info.h" @@ -67,6 +68,7 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, buttonBoxSideboard->setVisible(!isToken); connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); + connect(themeManager, &ThemeManager::fontsChanged, this, &AllZonesCardAmountWidget::applyCardTextFont); QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); @@ -90,8 +92,9 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font labels - QFont zoneLabelFont = zoneLabelMainboard->font(); + // Update the font labels. The family comes from the CardText theme + // role; the slider-driven point size stays as before. + QFont zoneLabelFont = themeManager->appFont(AppFont::CardText); zoneLabelFont.setPointSize(newFontSize); zoneLabelMainboard->setFont(zoneLabelFont); zoneLabelSideboard->setFont(zoneLabelFont); @@ -101,6 +104,11 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } +void AllZonesCardAmountWidget::applyCardTextFont() +{ + adjustFontSize(cardSizeSlider->value()); +} + void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount) { buttonBoxMainboard->setAmount(mainboardAmount); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index cefabe293..3213cf16b 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -36,6 +36,9 @@ public slots: void adjustFontSize(int scalePercentage); void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); +private slots: + void applyCardTextFont(); + private: QVBoxLayout *layout; QSlider *cardSizeSlider; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index eae49d7ef..7f25ea1f4 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,5 +1,6 @@ #include "card_amount_widget.h" +#include "../../theme_manager.h" #include "../deck_editor/deck_state_manager.h" #include "../general/display/dynamic_font_size_push_button.h" #include "deck_list_model.h" @@ -77,6 +78,7 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); + connect(themeManager, &ThemeManager::fontsChanged, this, &CardAmountWidget::applyCardTextFont); } int CardAmountWidget::getAmount() @@ -138,8 +140,9 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font for card count label - QFont cardCountFont = cardCountInZone->font(); + // Update the font for card count label. The family comes from the + // CardText theme role; the slider-driven point size stays as before. + QFont cardCountFont = themeManager->appFont(AppFont::CardText); cardCountFont.setPointSize(newFontSize); cardCountInZone->setFont(cardCountFont); @@ -150,6 +153,11 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } +void CardAmountWidget::applyCardTextFont() +{ + adjustFontSize(cardSizeSlider->value()); +} + /** * @brief Updates the card count display in the widget. */ diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 6f5889fc7..cef872157 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -67,6 +67,7 @@ private slots: void removePrintingSideboard(); void removePrintingTokensboard(); void adjustFontSize(int scalePercentage); + void applyCardTextFont(); }; #endif // CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp index 97383534f..3720aeb55 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp @@ -1,5 +1,6 @@ #include "archidekt_deck_preview_image_display_widget.h" +#include "../../../../../theme_manager.h" #include "../../../../general/display/shadow_background_label.h" #include @@ -17,13 +18,9 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q bottomLeftLabel = new ShadowBackgroundLabel(this, ""); bottomRightLabel = new ShadowBackgroundLabel(this, ""); - QFont f; - f.setBold(true); - f.setPointSize(16); - topLeftLabel->setFont(f); - topRightLabel->setFont(f); - bottomLeftLabel->setFont(f); - bottomRightLabel->setFont(f); + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont); // Raise so labels appear above image topLeftLabel->raise(); @@ -32,6 +29,18 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q bottomRightLabel->raise(); } +void ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont() +{ + // Family comes from the CardText theme role; size and weight stay caller-side as before. + QFont f = themeManager->appFont(AppFont::CardText); + f.setBold(true); + f.setPointSize(16); + topLeftLabel->setFont(f); + topRightLabel->setFont(f); + bottomLeftLabel->setFont(f); + bottomRightLabel->setFont(f); +} + void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) { aspectRatio = ratio; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h index 991f27b6a..41585e851 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h @@ -60,6 +60,9 @@ protected: */ void resizeEvent(QResizeEvent *event) override; +private slots: + void applyCardTextFont(); + private: float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image }; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index b2ddace01..c4ef7af0c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -1,5 +1,6 @@ #include "edhrec_api_response_card_details_display_widget.h" +#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../../../../general/display/background_plate_widget.h" @@ -9,6 +10,7 @@ #include "edhrec_api_response_card_synergy_display_widget.h" #include "libcockatrice/card/database/card_database_querier.h" +#include #include #include #include @@ -32,7 +34,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi nameLabel = new QLabel(this); nameLabel->setText(toDisplay.name); nameLabel->setAlignment(Qt::AlignHCenter); - nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); @@ -86,3 +90,13 @@ void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() { emit requestUrl(toDisplay.url); } + +void EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont() +{ + // Family comes from the CardText theme role; the 20px size and bold + // weight previously carried by the stylesheet stay caller-side. + QFont cardFont = themeManager->appFont(AppFont::CardText); + cardFont.setPointSize(20); + cardFont.setWeight(QFont::Bold); + nameLabel->setFont(cardFont); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index d738f4685..8a3c8fba4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -30,6 +30,9 @@ public slots: signals: void requestUrl(QString url); +private slots: + void applyCardTextFont(); + private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index 2ff22f2b3..f7d146e60 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -1,5 +1,6 @@ #include "edhrec_api_response_commander_details_display_widget.h" +#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../api_response/cards/edhrec_commander_api_response_commander_details.h" @@ -8,6 +9,7 @@ #include "edhrec_commander_api_response_navigation_widget.h" #include "libcockatrice/card/database/card_database_querier.h" +#include #include #include #include @@ -48,15 +50,16 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderName = new QLabel(this); commanderName->setText(commanderDetails.getName()); commanderName->setAlignment(Qt::AlignCenter); - commanderName->setStyleSheet("font-size: 28px; font-weight: bold"); label = new QLabel(this); label->setAlignment(Qt::AlignCenter); - label->setStyleSheet("font-size: 16px"); salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); - salt->setStyleSheet("font-size: 16px"); + + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); @@ -83,3 +86,18 @@ void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi() label->setText(commanderDetails.getLabel()); salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt())); } + +void EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont() +{ + // Family comes from the CardText theme role; the point sizes and the + // bold weight previously carried by the stylesheets stay caller-side. + QFont nameFont = themeManager->appFont(AppFont::CardText); + nameFont.setPointSize(28); + nameFont.setWeight(QFont::Bold); + commanderName->setFont(nameFont); + + QFont detailsFont = themeManager->appFont(AppFont::CardText); + detailsFont.setPointSize(16); + label->setFont(detailsFont); + salt->setFont(detailsFont); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 197263512..99e6b3d43 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -30,6 +30,9 @@ public: QString baseUrl); void retranslateUi(); +private slots: + void applyCardTextFont(); + private: EdhrecCommanderApiResponseCommanderDetails commanderDetails; QHBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp index a629df43c..0090e9d65 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp @@ -1,5 +1,6 @@ #include "card_completer_delegate.h" +#include "../../interface/theme_manager.h" #include "../../pixel_map_generator.h" #include "../cards/additional_info/mana_cost_widget.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -182,7 +183,7 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt p->setBrush(Qt::NoBrush); p->drawEllipse(pip.adjusted(1, 1, -1, -1)); - QFont f = QtUtils::boldFont(p->font()); + QFont f = QtUtils::boldFont(themeManager->appFont(AppFont::CardText)); f.setPixelSize(qMax(radius - 1, 7)); p->setFont(f); @@ -429,7 +430,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem const QRect nameRect = showInfoBand ? QRect(textLeft, rect.top() + 2, textWidth, 20) : QRect(textLeft, rect.top() + 2, textWidth, rect.height() - 4); - QFont f = option.font; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(13); f.setBold(true); @@ -451,7 +452,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem if (showInfoBand) { const QRect infoRect(textLeft, rect.top() + 22, textWidth, rect.height() - 24); - QFont f = option.font; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(10); painter->setFont(f); From a7e151d8717ee0a1e678e1814e5c6be53ac90116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 14:12:35 +0100 Subject: [PATCH 09/17] [Fonts] Keep CardText call sites on the app font and fix px sizes - CardText's unset fallback is qApp->font() again: only the three counter painters (paintNumberEllipse, GeneralCounter, PlayerCounter) were Serif before the role existed, and they now ask for it through ThemeManager::cardTextFont(). Card names, player names, counters, the overlay, the Archidekt and EDHREC labels and the completer delegate keep the app font they used before. - Use setPixelSize for the EDHREC card name (20px) and commander name (28px) and details (16px) sizes: the stylesheets said px, and setPointSize made them about a third larger. --- .../board/abstract_graphics_item.cpp | 2 +- .../game_graphics/board/counter_general.cpp | 2 +- .../game_graphics/player/player_target.cpp | 2 +- cockatrice/src/interface/theme_manager.cpp | 23 +++++++++++-------- cockatrice/src/interface/theme_manager.h | 3 +++ ...i_response_card_details_display_widget.cpp | 5 ++-- ...ponse_commander_details_display_widget.cpp | 9 ++++---- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index e847e878d..3a740aa98 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -22,7 +22,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, painter->save(); QString numStr = QString::number(number); - QFont font = themeManager->appFont(AppFont::CardText); + QFont font = themeManager->cardTextFont(); font.setPixelSize(fontSize); font.setWeight(QFont::Bold); diff --git a/cockatrice/src/game_graphics/board/counter_general.cpp b/cockatrice/src/game_graphics/board/counter_general.cpp index b06a98e95..23618e6bf 100644 --- a/cockatrice/src/game_graphics/board/counter_general.cpp +++ b/cockatrice/src/game_graphics/board/counter_general.cpp @@ -42,7 +42,7 @@ void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * / painter->drawPixmap(QPoint(0, 0), pixmap); if (value) { - QFont f = themeManager->appFont(AppFont::CardText); + QFont f = themeManager->cardTextFont(); f.setPixelSize(qMax((int)(radius * scaleFactor), 10)); f.setWeight(QFont::Bold); painter->setPen(Qt::black); diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index 2e3fdffdf..0d10ba4ab 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -77,7 +77,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* QRectF translatedRect = path.controlPointRect(); QSize translatedSize = translatedRect.size().toSize(); - QFont font = themeManager->appFont(AppFont::CardText); + QFont font = themeManager->cardTextFont(); font.setWeight(QFont::Bold); font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9)); painter->setFont(font); diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 8d538c134..fdd6bbc30 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -660,15 +660,10 @@ QFont ThemeManager::appFont(AppFont::Role role) const } // Role fallbacks preserve the pre-theme defaults when no fonts.toml is - // present: card text starts from a serif basis, everything else from the - // application font. The report dialogs that used to hardcode monospace ask - // for it explicitly (report_utils::reportLogFont). - if (role == AppFont::CardText) { - return QFont(QStringLiteral("Serif")); - } - - // No per-role scaling: every other unset role resolves to the application - // font, so with no fonts.toml present the result is identical to before. + // present: an unset role resolves to the application font, so the result is + // identical to before. The report dialogs that used to hardcode monospace + // ask for it explicitly (report_utils::reportLogFont), and the counter + // painters that used to hardcode Serif ask for it through cardTextFont(). return qApp->font(); } @@ -677,6 +672,16 @@ bool ThemeManager::hasAppFont(AppFont::Role role) const return currentFonts.fonts.contains(role); } +QFont ThemeManager::cardTextFont() const +{ + if (hasAppFont(AppFont::CardText)) { + return appFont(AppFont::CardText); + } + QFont serifFont(QStringLiteral("Serif")); + serifFont.setStyleHint(QFont::Serif); + return serifFont; +} + QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const { // No per-role scaling and no preview: an unset role resolves to the font diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index aba91faf7..35cc36d04 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -155,6 +155,9 @@ public: QFont appFont(AppFont::Role role) const; /** @brief Whether the active theme defines this role in fonts.toml. */ bool hasAppFont(AppFont::Role role) const; + /** @brief The CardText role with its historical Serif fallback. Used by the + * counter painters, which were Serif before the role existed. */ + QFont cardTextFont() const; /** @brief Resolves an application font role against the startup font only, * ignoring any previewed config in currentFonts. Editors that show * "what this role resolves to" must use this, because appFont() diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index c4ef7af0c..2b76de973 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -94,9 +94,10 @@ void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() void EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont() { // Family comes from the CardText theme role; the 20px size and bold - // weight previously carried by the stylesheet stay caller-side. + // weight previously carried by the stylesheet stay caller-side. Pixel + // size, not point size: the stylesheet said 20px, and 20pt is ~27px. QFont cardFont = themeManager->appFont(AppFont::CardText); - cardFont.setPointSize(20); + cardFont.setPixelSize(20); cardFont.setWeight(QFont::Bold); nameLabel->setFont(cardFont); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index f7d146e60..df0319f88 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -89,15 +89,16 @@ void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi() void EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont() { - // Family comes from the CardText theme role; the point sizes and the - // bold weight previously carried by the stylesheets stay caller-side. + // Family comes from the CardText theme role; the sizes and the bold + // weight previously carried by the stylesheets stay caller-side. Pixel + // sizes, not point sizes: the stylesheets said 28px and 16px. QFont nameFont = themeManager->appFont(AppFont::CardText); - nameFont.setPointSize(28); + nameFont.setPixelSize(28); nameFont.setWeight(QFont::Bold); commanderName->setFont(nameFont); QFont detailsFont = themeManager->appFont(AppFont::CardText); - detailsFont.setPointSize(16); + detailsFont.setPixelSize(16); label->setFont(detailsFont); salt->setFont(detailsFont); } From d26a7b213fa6c13aae7e581307f0d56558e3c81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:47:37 +0200 Subject: [PATCH 10/17] [Fonts] Migrate DeckList and InterfaceDefault surfaces to theme roles --- .../src/client/settings/shortcut_treeview.cpp | 4 +- cockatrice/src/filters/filter_tree_model.cpp | 5 +- .../src/game_graphics/deckview/deck_view.cpp | 6 +- cockatrice/src/game_graphics/game_view.cpp | 56 ++++++++++++++++--- cockatrice/src/game_graphics/game_view.h | 1 + .../src/interface/deck_loader/deck_loader.cpp | 9 ++- .../interface/palette_editor/color_button.cpp | 6 +- .../palette_editor/palette_grid_widget.cpp | 17 +++++- .../palette_editor/palette_grid_widget.h | 4 ++ .../palette_editor/quick_setup_panel.cpp | 10 +++- .../deck_analytics/resizable_panel.cpp | 13 ++++- .../deck_editor/deck_list_style_proxy.cpp | 5 +- .../dialogs/dlg_shared_decks_preview.cpp | 10 +++- .../widgets/general/display/banner_widget.cpp | 16 +++++- .../widgets/general/display/banner_widget.h | 1 + .../display/charts/bars/bar_chart_widget.cpp | 5 +- .../charts/bars/segmented_bar_widget.cpp | 5 +- .../display/dynamic_font_size_label.cpp | 13 ++++- .../display/dynamic_font_size_push_button.cpp | 7 ++- .../widgets/general/home_styled_button.cpp | 18 +++++- .../widgets/general/home_styled_button.h | 3 + .../interface/widgets/general/home_widget.cpp | 16 +++++- .../widgets/onboarding/first_run_wizard.cpp | 16 +++++- .../widgets/onboarding/first_run_wizard.h | 1 + .../printing_selector_placeholder_widget.cpp | 15 ++++- .../printing_selector_placeholder_widget.h | 3 + .../widgets/server/user/user_info_box.cpp | 17 ++++-- .../widgets/server/user/user_info_box.h | 1 + .../widgets/server/user/user_info_popup.cpp | 44 +++++++++++---- .../widgets/server/user/user_list_painter.cpp | 9 ++- .../widgets/server/user/user_list_widget.cpp | 2 +- .../settings_search_delegate.cpp | 6 +- ...api_response_bracket_navigation_widget.cpp | 25 +++++++-- ...r_api_response_bracket_navigation_widget.h | 3 + ..._api_response_budget_navigation_widget.cpp | 25 +++++++-- ...er_api_response_budget_navigation_widget.h | 3 + ...mmander_api_response_navigation_widget.cpp | 19 ++++++- ...commander_api_response_navigation_widget.h | 3 + .../interface/widgets/tabs/tab_developer.cpp | 4 +- .../widgets/tabs/tab_public_decks.cpp | 7 ++- .../visual_deck_editor_placeholder_widget.cpp | 15 ++++- .../visual_deck_editor_placeholder_widget.h | 3 + .../deck_preview_tag_addition_widget.cpp | 7 ++- .../deck_preview_tag_display_widget.cpp | 7 ++- 44 files changed, 391 insertions(+), 74 deletions(-) diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index 4a5bafb01..d81589f34 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -1,5 +1,6 @@ #include "shortcut_treeview.h" +#include "../../interface/theme_manager.h" #include "cache_settings.h" #include "shortcuts_settings.h" @@ -93,7 +94,8 @@ void ShortcutTreeView::populateShortcutsModel() curParent = parentItems.value(group); } else { curParent = new QStandardItem(group); - static QFont font = curParent->font(); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setBold(true); curParent->setFont(font); parentItems.insert(group, curParent); diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp index 9b55ce2fd..467c2564b 100644 --- a/cockatrice/src/filters/filter_tree_model.cpp +++ b/cockatrice/src/filters/filter_tree_model.cpp @@ -1,5 +1,7 @@ #include "filter_tree_model.h" +#include "../interface/theme_manager.h" + #include #include #include @@ -202,7 +204,8 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::FontRole: if (!node->isLeaf()) { - QFont f; + // Family comes from the DeckList theme role; bold stays caller-side. + QFont f = themeManager->appFont(AppFont::DeckList); f.setBold(true); return f; } diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index 6e0b0eaaa..63ee48e57 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -240,7 +240,8 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); painter->setPen(QColor(Qt::white)); - QFont f("Serif"); + // Family comes from the DeckList theme role; pixel sizes/weights unchanged. + QFont f = themeManager->appFont(AppFont::DeckList); f.setStyleHint(QFont::Serif); f.setPixelSize(24); f.setWeight(QFont::Bold); @@ -292,7 +293,8 @@ QList> DeckViewCardContainer::getRowsAndCols() const int DeckViewCardContainer::getCardTypeTextWidth() const { - QFont f("Serif"); + // Family comes from the DeckList theme role; pixel size/weight unchanged. + QFont f = themeManager->appFont(AppFont::DeckList); f.setStyleHint(QFont::Serif); f.setPixelSize(16); f.setWeight(QFont::Bold); diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index cb59b3d7d..346f515e4 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,12 +2,14 @@ #include "../client/settings/cache_settings.h" #include "../client/settings/shortcuts_settings.h" +#include "../interface/theme_manager.h" #include "game_scene.h" #include #include #include #include +#include #include #include #include @@ -75,15 +77,14 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); const QString baseProperties = "color: white; " - "font-family: monospace; " "background-color: rgba(0, 0, 0, 160); " "border-radius: 3px; " "padding: 1px 2px; " "white-space: pre;"; - const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;"; - const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;"; - const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;"; + const QString dragCountLabelStyle = baseProperties; + const QString totalCountLabelStyle = baseProperties; + const QString subtypeTallyLabelStyle = baseProperties; dragCountLabel = new QLabel(this); dragCountLabel->setStyleSheet(dragCountLabelStyle); @@ -100,6 +101,41 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par tallyLayout->setContentsMargins(2, 2, 2, 2); tallyLayout->setSpacing(2); tallyContainer->hide(); + + applyDeckListFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &GameView::applyDeckListFont); +} + +void GameView::applyDeckListFont() +{ + // Family comes from the DeckList theme role; the pixel sizes and bold + // weights previously carried by the stylesheets stay caller-side. + QFont dragFont = themeManager->appFont(AppFont::DeckList); + dragFont.setPointSize(14); + dragFont.setWeight(QFont::Bold); + dragCountLabel->setFont(dragFont); + + QFont totalFont = themeManager->appFont(AppFont::DeckList); + totalFont.setPointSize(16); + totalFont.setWeight(QFont::Bold); + totalCountLabel->setFont(totalFont); + + QFont tallyFont = themeManager->appFont(AppFont::DeckList); + tallyFont.setPointSize(12); + tallyContainer->setFont(tallyFont); + for (int i = 0; i < tallyLayout->count(); ++i) { + if (auto *label = qobject_cast(tallyLayout->itemAt(i)->widget())) { + QFont labelFont = themeManager->appFont(AppFont::DeckList); + // Count column (odd positions) keeps the 14px bold emphasis. + if (i % 2 == 1) { + labelFont.setPointSize(14); + labelFont.setWeight(QFont::Bold); + } else { + labelFont.setPointSize(12); + } + label->setFont(labelFont); + } + } } void GameView::resizeEvent(QResizeEvent *event) @@ -219,9 +255,8 @@ QSize GameView::rebuildTallyLabels(const QList &entries) { clearTallyLabels(); - const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); - const QString countStyle = - QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;"); + const QString nameStyle = QStringLiteral("color: white; background: transparent;"); + const QString countStyle = QStringLiteral("color: white; background: transparent;"); int totalHeight = 0; int maxNameWidth = 0; @@ -231,11 +266,18 @@ QSize GameView::rebuildTallyLabels(const QList &entries) for (const TallyRow &entry : entries) { auto *nameLabel = new QLabel(entry.name, tallyContainer); nameLabel->setStyleSheet(nameStyle); + QFont nameFont = themeManager->appFont(AppFont::DeckList); + nameFont.setPointSize(12); + nameLabel->setFont(nameFont); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(nameLabel, row, 0); auto *countLabel = new QLabel(entry.value, tallyContainer); countLabel->setStyleSheet(countStyle); + QFont countFont = themeManager->appFont(AppFont::DeckList); + countFont.setPointSize(14); + countFont.setWeight(QFont::Bold); + countLabel->setFont(countFont); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(countLabel, row, 1); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index e57267734..0fca224b2 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -50,6 +50,7 @@ private slots: void refreshShortcuts(); void updateTotalSelectionCount(const QSize &viewSize = QSize()); void setFocusDisabled(bool disabled); + void applyDeckListFont(); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index d413a6c04..d5c7de9a4 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -1,5 +1,6 @@ #include "deck_loader.h" +#include "../theme_manager.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/deck_list/tree/abstract_deck_list_card_node.h" @@ -579,6 +580,8 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode if (!cards.isEmpty()) { QTextBlockFormat blockFormat; QTextCharFormat charFormat; + // Family comes from the DeckList theme role; sizes/weights unchanged. + charFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); charFormat.setFontPointSize(11); charFormat.setFontWeight(QFont::Bold); cursor->insertBlock(blockFormat, charFormat); @@ -592,6 +595,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode const AbstractDecklistCardNode *card = cards[i]; QTextCharFormat cellCharFormat; + cellCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); cellCharFormat.setFontPointSize(9); QTextTableCell cell = table->cellAt(i, 0); @@ -613,6 +617,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode QTextBlockFormat blockFormat; QTextCharFormat charFormat; + charFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); charFormat.setFontPointSize(14); charFormat.setFontWeight(QFont::Bold); @@ -640,7 +645,8 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) { QTextDocument doc; - QFont font("Serif"); + // Family comes from the DeckList theme role; the print sizes/weights below stay unchanged. + QFont font = themeManager->appFont(AppFont::DeckList); font.setStyleHint(QFont::Serif); doc.setDefaultFont(font); @@ -648,6 +654,7 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) QTextBlockFormat headerBlockFormat; QTextCharFormat headerCharFormat; + headerCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); headerCharFormat.setFontPointSize(16); headerCharFormat.setFontWeight(QFont::Bold); diff --git a/cockatrice/src/interface/palette_editor/color_button.cpp b/cockatrice/src/interface/palette_editor/color_button.cpp index a7db23d31..ae9bd6297 100644 --- a/cockatrice/src/interface/palette_editor/color_button.cpp +++ b/cockatrice/src/interface/palette_editor/color_button.cpp @@ -1,5 +1,7 @@ #include "color_button.h" +#include "../theme_manager.h" + #include #include #include @@ -21,6 +23,7 @@ ColorButton::ColorButton(QWidget *parent) : QToolButton(parent) setCursor(Qt::PointingHandCursor); setToolTip(tr("Click to pick a color")); connect(this, &QToolButton::clicked, this, &ColorButton::pickColor); + connect(themeManager, &ThemeManager::fontsChanged, this, &ColorButton::updateSwatch); } void ColorButton::setColor(const QColor &c) @@ -73,7 +76,8 @@ void ColorButton::updateSwatch() // 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(); + // Family comes from the InterfaceDefault theme role; the 8px size stays caller-side. + QFont f = themeManager->appFont(AppFont::InterfaceDefault); f.setPixelSize(8); painter.setFont(f); painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper()); diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp index 463c4038d..392f01d0d 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -76,6 +76,18 @@ PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) layout = QtUtils::makeLayout(this); layout->addWidget(scroll); + + connect(themeManager, &ThemeManager::fontsChanged, this, &PaletteGridWidget::applyInterfaceDefaultFont); +} + +void PaletteGridWidget::applyInterfaceDefaultFont() +{ + // The editor's own chrome follows the InterfaceDefault theme role; bold stays caller-side. + for (auto *label : headerLabels) { + QFont headerFont = themeManager->appFont(AppFont::InterfaceDefault); + headerFont.setBold(true); + label->setFont(headerFont); + } } void PaletteGridWidget::buildGrid(QWidget *host) @@ -101,7 +113,8 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *label = new QLabel(groupHeaders[col], host); label->setAlignment(Qt::AlignCenter); label->setToolTip(groupTips[col]); - label->setFont(QtUtils::boldFont(label->font())); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + label->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); label->setAutoFillBackground(true); label->setContentsMargins(4, 4, 4, 4); grid->addWidget(label, 0, col + 1); @@ -146,7 +159,7 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *appHeader = new QLabel(tr("App colors"), host); appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); - appHeader->setFont(QtUtils::boldFont(appHeader->font())); + appHeader->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); appHeader->setAutoFillBackground(true); appHeader->setContentsMargins(4, 4, 4, 4); grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h index f1ff41ec2..7c527a064 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -34,6 +34,10 @@ private: void changeEvent(QEvent *e); void refreshChromePalettes(); +private slots: + void applyInterfaceDefaultFont(); + +private: QMap> colorButtons; QMap appColorButtons; QScrollArea *scroll; diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp index 04f98dcc3..612dcc2cf 100644 --- a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp @@ -1,5 +1,6 @@ #include "quick_setup_panel.h" +#include "../theme_manager.h" #include "color_button.h" #include @@ -28,10 +29,17 @@ QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent) labelLow = new QLabel(this); labelHigh = new QLabel(this); - QFont small = labelLow->font(); + // Family comes from the InterfaceDefault theme role; the *0.82 size stays caller-side. + QFont small = themeManager->appFont(AppFont::InterfaceDefault); small.setPointSizeF(small.pointSizeF() * 0.82); labelLow->setFont(small); labelHigh->setFont(small); + connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { + QFont refreshedSmall = themeManager->appFont(AppFont::InterfaceDefault); + refreshedSmall.setPointSizeF(refreshedSmall.pointSizeF() * 0.82); + labelLow->setFont(refreshedSmall); + labelHigh->setFont(refreshedSmall); + }); QPalette dimmed = labelLow->palette(); dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid)); labelLow->setPalette(dimmed); diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index 85dba6468..98df65113 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -1,5 +1,6 @@ #include "resizable_panel.h" +#include "../interface/theme_manager.h" #include "abstract_analytics_panel_widget.h" #include "libcockatrice/utility/qt_utils.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -93,9 +95,18 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid dragButton->setStyleSheet(QString("QPushButton { " "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); " - "border: none; color: %3; font-size: 10px; }" + "border: none; color: %3; }" "QPushButton:hover { background: %4; }") .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); + // Family comes from the InterfaceDefault theme role; the 10px size stays caller-side. + QFont dragFont = themeManager->appFont(AppFont::InterfaceDefault); + dragFont.setPointSize(10); + dragButton->setFont(dragFont); + connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { + QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); + refreshedFont.setPointSize(10); + dragButton->setFont(refreshedFont); + }); resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " "stop:0 %1, stop:1 %2); }") diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 1e1aefe58..59b7fff79 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -1,5 +1,7 @@ #include "deck_list_style_proxy.h" +#include "../../theme_manager.h" + #include #include #include @@ -20,7 +22,8 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const bool isCard = src.data(DeckRoles::IsCardRole).toBool(); if (role == Qt::FontRole && !isCard) { - QFont f; + // Family comes from the DeckList theme role; bold stays caller-side. + QFont f = themeManager->appFont(AppFont::DeckList); f.setBold(true); return f; } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp index d29aee27f..c0552491d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp @@ -2,6 +2,7 @@ #include "../deck_share/shared_deck_preview_widget.h" #include "../general/layout_containers/flow_widget.h" +#include "../interface/theme_manager.h" #include #include @@ -35,9 +36,16 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent, // QLabels render AutoText and markup would otherwise be shown as rich text. auto *titleLabel = new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this); - QFont titleFont = QtUtils::boldFont(titleLabel->font()); + // Family comes from the InterfaceDefault theme role; the +2 size and bold stay caller-side. + QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); titleFont.setPointSize(titleFont.pointSize() + 2); titleLabel->setFont(titleFont); + connect(themeManager, &ThemeManager::fontsChanged, this, [this, titleLabel]() { + QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); + refreshedFont.setBold(true); + refreshedFont.setPointSize(refreshedFont.pointSize() + 2); + titleLabel->setFont(refreshedFont); + }); mainLayout->addWidget(titleLabel); if (!serverText.isEmpty()) { diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index baf46bbba..b4b23aacc 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -1,9 +1,11 @@ #include "banner_widget.h" #include "../../../pixel_map_generator.h" +#include "../../../theme_manager.h" #include #include +#include #include #include #include @@ -28,7 +30,9 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation if (transparency > 50) { textColor = " color: white;"; } - bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); + bannerLabel->setStyleSheet(textColor); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &BannerWidget::applyInterfaceDefaultFont); layout->addWidget(iconLabel); layout->addWidget(bannerLabel); @@ -56,6 +60,16 @@ void BannerWidget::setText(const QString &text) const bannerLabel->setText(text); } +void BannerWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size and + // bold weight previously carried by the stylesheet stay caller-side. + QFont bannerFont = themeManager->appFont(AppFont::InterfaceDefault); + bannerFont.setPointSize(24); + bannerFont.setWeight(QFont::Bold); + bannerLabel->setFont(bannerFont); +} + void BannerWidget::setClickable(bool _clickable) { clickable = _clickable; diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.h b/cockatrice/src/interface/widgets/general/display/banner_widget.h index 06556089b..d46250bdb 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.h +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.h @@ -48,6 +48,7 @@ signals: private slots: void toggleBuddyVisibility() const; void updateDropdownIconState() const; + void applyInterfaceDefaultFont(); }; #endif // BANNER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp index 484ac861d..cd2a3702d 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -1,5 +1,7 @@ #include "bar_chart_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -146,7 +148,8 @@ void BarChartWidget::paintEvent(QPaintEvent *) // draw label below bar QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H); - QFont f = p.font(); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont f = themeManager->appFont(AppFont::InterfaceDefault); f.setBold(true); p.setFont(f); p.setPen(Qt::black); diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp index 5a3130964..d6c75f3bd 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -1,5 +1,7 @@ #include "segmented_bar_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -95,7 +97,8 @@ void SegmentedBarWidget::paintEvent(QPaintEvent *) // draw label QRect labelRect(0, h - LABEL_H, w, LABEL_H); - QFont f = p.font(); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont f = themeManager->appFont(AppFont::InterfaceDefault); f.setBold(true); p.setFont(f); p.setPen(Qt::black); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp index 0609eb16a..598c70fe2 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp @@ -1,5 +1,7 @@ #include "dynamic_font_size_label.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -28,9 +30,13 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) // QElapsedTimer timer; // timer.start(); - QFont newFont = font(); + // Family follows the InterfaceDefault theme role; the fitted point size + // stays computed from the widget geometry as before. + QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); float fontSize = getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); + // Preserve the caller's bold emphasis, which the role font does not carry. + newFont.setBold(font().bold()); setFont(newFont); // qDebug() << "Font size set to" << fontSize; @@ -40,7 +46,10 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text) { - QFont font = widget->font(); + // Measure with the InterfaceDefault family so the fit matches the painted font. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); + font.setBold(widget->font().bold()); + font.setPointSizeF(widget->font().pointSizeF()); const QRect widgetRect = widget->contentsRect(); const float widgetWidth = widgetRect.width(); const float widgetHeight = widgetRect.height(); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp index 25e8ca48f..9b5f5bdda 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp @@ -1,5 +1,6 @@ #include "dynamic_font_size_push_button.h" +#include "../../interface/theme_manager.h" #include "dynamic_font_size_label.h" #include @@ -20,10 +21,12 @@ void DynamicFontSizePushButton::paintEvent(QPaintEvent *event) // Call the base class paintEvent to preserve any other painting behavior QPushButton::paintEvent(event); - // Adjust the font size dynamically based on the text - QFont newFont = font(); + // Adjust the font size dynamically based on the text. Family follows the + // InterfaceDefault theme role; the fitted size stays computed as before. + QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); + newFont.setBold(font().bold()); setFont(newFont); // Get painter for custom painting diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index 7f23827bc..bbd1f8c04 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -1,6 +1,9 @@ #include "home_styled_button.h" +#include "../../theme_manager.h" + #include +#include #include #include #include @@ -21,6 +24,18 @@ HomeStyledButton::HomeStyledButton(const QString &text, QPair _g setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setMinimumHeight(25); setStyleSheet(generateButtonStylesheet(gradientColors)); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &HomeStyledButton::applyInterfaceDefaultFont); +} + +void HomeStyledButton::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 34px size and + // bold weight previously carried by the stylesheet stay caller-side. + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.setPointSize(34); + buttonFont.setWeight(QFont::Bold); + setFont(buttonFont); } void HomeStyledButton::updateStylesheet(const QPair &colors) @@ -50,9 +65,6 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair & return QString(R"( QPushButton { - font-size: 34px; - font-weight: bold; - font-family: sans-serif, "Segoe UI", "Helvetica Neue"; padding: 30px; color: white; border: 2px solid %1; diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.h b/cockatrice/src/interface/widgets/general/home_styled_button.h index 55ac5550f..5756db5a1 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.h +++ b/cockatrice/src/interface/widgets/general/home_styled_button.h @@ -24,6 +24,9 @@ public: public slots: void paintEvent(QPaintEvent *event) override; +private slots: + void applyInterfaceDefaultFont(); + private: QPair gradientColors; }; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 5905b2c40..ec902d726 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -289,7 +289,6 @@ QGroupBox *HomeWidget::createButtons() QGroupBox *box = new QGroupBox(this); box->setStyleSheet(R"( QGroupBox { - font-size: 20px; color: white; /* Title text color */ background: transparent; } @@ -300,6 +299,15 @@ QGroupBox *HomeWidget::createButtons() subcontrol-position: top center; /* or top left / right */ } )"); + // Family comes from the InterfaceDefault theme role; the 20px size stays caller-side. + QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault); + boxFont.setPointSize(20); + box->setFont(boxFont); + connect(themeManager, &ThemeManager::fontsChanged, this, [this, box]() { + QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); + refreshedFont.setPointSize(20); + box->setFont(refreshedFont); + }); box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *boxLayout = new QVBoxLayout; boxLayout->setAlignment(Qt::AlignHCenter); @@ -463,7 +471,9 @@ void HomeWidget::paintEvent(QPaintEvent *event) constexpr int margin = 15; constexpr qreal attributionMargin = 4.0; - QFont attributionFont = painter.font(); + // Family comes from the InterfaceDefault theme role; the 14px sizes and + // bold weights stay caller-side as before. + QFont attributionFont = themeManager->appFont(AppFont::InterfaceDefault); attributionFont.setPointSize(14); attributionFont.setBold(true); painter.setFont(attributionFont); @@ -476,7 +486,7 @@ void HomeWidget::paintEvent(QPaintEvent *event) // Card name bubble above the attribution (when enabled). if (!cardName.isEmpty() && SettingsCache::instance().appearance().getHomeTabDisplayCardName()) { - QFont font = painter.font(); + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setPointSize(14); font.setBold(true); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp index c4298d6b6..e69687c54 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp @@ -1,5 +1,6 @@ #include "first_run_wizard.h" +#include "../interface/theme_manager.h" #include "first_run_wizard_page.h" #include "pages/account_setup_page.h" #include "pages/card_database_setup_page.h" @@ -32,9 +33,8 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent) bannerHost = new BannerHost(this); titleLabel = new QLabel(this); - QFont titleFont = QtUtils::boldFont(titleLabel->font()); - titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); - titleLabel->setFont(titleFont); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &FirstRunWizard::applyInterfaceDefaultFont); subtitleLabel = new QLabel(this); subtitleLabel->setWordWrap(true); @@ -154,6 +154,16 @@ void FirstRunWizard::updateChrome() } } +void FirstRunWizard::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the *1.4 size + // multiplier and bold weight stay caller-side as before. + QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault); + titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); + titleFont.setBold(true); + titleLabel->setFont(titleFont); +} + void FirstRunWizard::goNext() { FirstRunWizardPage *page = pages[currentIndex]; diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h index 812985a9f..a5fa3b4bd 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h @@ -55,6 +55,7 @@ private slots: void goBack(); void skip(); void updateChrome(); + void applyInterfaceDefaultFont(); private: void addPage(FirstRunWizardPage *page); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp index 7f9ad66c8..d9d261be5 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp @@ -1,5 +1,8 @@ #include "printing_selector_placeholder_widget.h" +#include "../interface/theme_manager.h" + +#include #include #include #include @@ -30,10 +33,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); - font-size: 14px; padding: 10px; } )"); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -44,4 +49,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa void PrintingSelectorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Select a card to view its available printings")); +} + +void PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. + QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); + placeholderFont.setPointSize(14); + textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h index 6702d325a..9e6e47bcb 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h @@ -14,6 +14,9 @@ class PrintingSelectorPlaceholderWidget : public QWidget public: explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr); +private slots: + void applyInterfaceDefaultFont(); + private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp index fa7698037..997001c50 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp @@ -1,6 +1,7 @@ #include "user_info_box.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../../interface/widgets/dialogs/dlg_edit_avatar.h" #include "../../interface/widgets/dialogs/dlg_edit_password.h" #include "../../interface/widgets/dialogs/dlg_edit_user.h" @@ -39,10 +40,8 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), client(_client), editable(_editable) { - QFont nameFont = nameLabel.font(); - nameFont.setBold(true); - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); - nameLabel.setFont(nameFont); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoBox::applyInterfaceDefaultFont); auto *userIconAndNameLayout = new QHBoxLayout; userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter); @@ -91,6 +90,16 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren retranslateUi(); } +void UserInfoBox::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the *1.5 size + // multiplier and bold weight stay caller-side as before. + QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); + nameFont.setBold(true); + nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); + nameLabel.setFont(nameFont); +} + void UserInfoBox::retranslateUi() { realNameLabel1.setText(tr("Real Name:")); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.h b/cockatrice/src/interface/widgets/server/user/user_info_box.h index 6bc571a37..98919b027 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.h @@ -48,6 +48,7 @@ private slots: void processEditResponse(const Response &r); void processPasswordResponse(const Response &r); void processAvatarResponse(const Response &r); + void applyInterfaceDefaultFont(); void actEdit(); void actEditInternal(const Response &r); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index efb3b2e31..6b6205166 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -142,7 +142,8 @@ public: p->drawEllipse(QRectF(rect.left() + 9, rect.top() + (rect.height() - 8) / 2.0, 8, 8)); // Game title (bold, elided) - QFont tf = option.font; + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont tf = themeManager->appFont(AppFont::InterfaceDefault); tf.setBold(true); p->setFont(tf); p->setPen(pal.color(QPalette::Text)); @@ -154,7 +155,7 @@ public: // Player count const bool full = game.player_count() >= game.max_players(); - p->setFont(option.font); + p->setFont(themeManager->appFont(AppFont::InterfaceDefault)); p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text)); p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()), Qt::AlignVCenter | Qt::AlignRight, @@ -323,7 +324,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) const int tx = ax + AvatarSize + AvatarToTextGap; const int tw = rect.width() - tx - 8; - QFont nf = font(); + // Family comes from the InterfaceDefault theme role; the *1.12 size and bold stay caller-side. + QFont nf = themeManager->appFont(AppFont::InterfaceDefault); nf.setBold(true); nf.setPointSizeF(nf.pointSizeF() * 1.12); p.setFont(nf); @@ -352,7 +354,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) } if (!badge.text.isEmpty()) { - QFont bf = font(); + QFont bf = themeManager->appFont(AppFont::InterfaceDefault); bf.setPointSizeF(bf.pointSizeF() * 0.70); bf.setBold(true); p.setFont(bf); @@ -369,7 +371,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) // The painter font at this point depends on whether a badge was drawn // (badge font vs username font), so pin an explicit font for the pill. - p.setFont(font()); + // Family follows the InterfaceDefault theme role like the rest of the popup chrome. + p.setFont(themeManager->appFont(AppFont::InterfaceDefault)); // Only show the attribution when there is actually art on screen: on a // cache miss cardArt is null and the pill would float over the plain @@ -398,6 +401,7 @@ UserInfoPopup::UserInfoPopup(TabSupervisor *_ts, // Restyle the popup chrome when the theme or its color scheme changes. if (themeManager) { connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme); + connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoPopup::applyTheme); } } @@ -475,15 +479,24 @@ void UserInfoPopup::applyTheme() separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border))); - gamesLabel->setStyleSheet(QStringLiteral("color:%1; font-size:11px; font-weight:bold; background:transparent;") - .arg(colorStr(theme.subText))); + gamesLabel->setStyleSheet(QStringLiteral("color:%1; background:transparent;").arg(colorStr(theme.subText))); + QFont gamesLabelFont = themeManager->appFont(AppFont::InterfaceDefault); + gamesLabelFont.setPointSize(11); + gamesLabelFont.setWeight(QFont::Bold); + gamesLabel->setFont(gamesLabelFont); - refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;font-size:14px;background:transparent;}" + refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;background:transparent;}" "QPushButton:hover{color:%2;}") .arg(colorStr(theme.subText), colorStr(theme.text))); + QFont refreshFont = themeManager->appFont(AppFont::InterfaceDefault); + refreshFont.setPointSize(14); + refreshBtn->setFont(refreshFont); - gamesStatus->setStyleSheet(QStringLiteral("color:%1; font-size:11px; padding:10px; background:transparent;") - .arg(colorStr(theme.statusText))); + gamesStatus->setStyleSheet( + QStringLiteral("color:%1; padding:10px; background:transparent;").arg(colorStr(theme.statusText))); + QFont statusFont = themeManager->appFont(AppFont::InterfaceDefault); + statusFont.setPointSize(11); + gamesStatus->setFont(statusFont); gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}" "QListView::item:selected{background:%2;}") @@ -491,9 +504,12 @@ void UserInfoPopup::applyTheme() closeBtn->setStyleSheet( QStringLiteral("QPushButton{background:%1;color:%2;" - "border:none;border-radius:11px;font-size:10px;}" + "border:none;border-radius:11px;}" "QPushButton:hover{color:%3;background:%4;}") .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); + QFont closeFont = themeManager->appFont(AppFont::InterfaceDefault); + closeFont.setPointSize(10); + closeBtn->setFont(closeFont); header->update(); } @@ -508,13 +524,17 @@ static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); b->setStyleSheet(QStringLiteral("QPushButton{" " background:%1;color:%2;border:1px solid %3;" - " border-radius:4px;font-size:11px;padding:0 4px;" + " border-radius:4px;padding:0 4px;" "}" "QPushButton:hover{background:%4;color:%5;}" "QPushButton:pressed{background:%6;}" "QPushButton:disabled{color:%7;border-color:%3;}") .arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover), colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); + // Family comes from the InterfaceDefault theme role; the 11px size stays caller-side. + QFont btnFont = themeManager->appFont(AppFont::InterfaceDefault); + btnFont.setPointSize(11); + b->setFont(btnFont); return b; } diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index 08eaaaff7..acb91351b 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -1,6 +1,7 @@ #include "user_list_painter.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include @@ -292,7 +293,9 @@ void UserListPainter::drawUserName(QPainter *painter, bool online, const Style &style) { - QFont nameFont = option.font; + Q_UNUSED(option); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); nameFont.setBold(true); painter->setFont(nameFont); @@ -351,7 +354,9 @@ void UserListPainter::drawBadges(QPainter *painter, return; } - QFont badgeFont = option.font; + Q_UNUSED(option); + // Family comes from the InterfaceDefault theme role; the *0.68 size and bold stay caller-side. + QFont badgeFont = themeManager->appFont(AppFont::InterfaceDefault); badgeFont.setPointSizeF(badgeFont.pointSizeF() * 0.68); badgeFont.setBold(true); painter->setFont(badgeFont); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index a20b39b82..48cd6167f 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -1492,7 +1492,7 @@ QTreeWidgetItem *UserListWidget::createSectionItem(Section section) // They act as collapsible section headers once they have focus. divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - QFont font = userTree->font(); + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setBold(true); divider->setFont(0, font); // A little taller than a plain text row so the header reads as a section diff --git a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp index bf35b7557..67354c9d7 100644 --- a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp +++ b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp @@ -5,6 +5,7 @@ */ #include "settings_search_delegate.h" +#include "../interface/theme_manager.h" #include "settings_search_model.h" #include @@ -88,7 +89,8 @@ void SettingsSearchDelegate::paint(QPainter *painter, QRect textRect = contentRect.adjusted(iconOffset, 0, 0, 0); // Breadcrumb: "Page > Group" - QFont breadcrumbFont = option.font; + // Family comes from the InterfaceDefault theme role; the -1 size and bold stay caller-side. + QFont breadcrumbFont = themeManager->appFont(AppFont::InterfaceDefault); breadcrumbFont.setPointSize(breadcrumbFont.pointSize() - 1); breadcrumbFont.setBold(true); @@ -113,7 +115,7 @@ void SettingsSearchDelegate::paint(QPainter *painter, yPos += 20; // Setting label - QFont labelFont = option.font; + QFont labelFont = themeManager->appFont(AppFont::InterfaceDefault); labelFont.setPointSize(labelFont.pointSize() + 1); labelFont.setBold(isSelected); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp index b26a7e574..9324bcef7 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -1,6 +1,9 @@ #include "edhrec_commander_api_response_bracket_navigation_widget.h" +#include "../../interface/theme_manager.h" + #include +#include #include #include #include @@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); QPushButton *optionButton = new QPushButton(label, this); optionButton->setMinimumHeight(84); - optionButton->setStyleSheet("font-size: 24px"); + optionButton->setStyleSheet(QString()); gameChangerButtons[option] = optionButton; layout->addWidget(optionButton, 2, i); connect(optionButton, &QPushButton::clicked, this, [=, this]() { @@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra }); } + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont); + updateOptionButtonSelection(gameChangerButtons, ""); retranslateUi(); @@ -94,8 +101,18 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelect const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - it.value()->setStyleSheet(it.key() == selectedKey - ? "background-color: lightblue; font-weight: bold; font-size: 24px;" - : "font-size: 24px"); + const bool selected = (it.key() == selectedKey); + it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.setPointSize(24); + buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); + it.value()->setFont(buttonFont); } +} + +void EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size stays + // caller-side and the selected button keeps its bold emphasis. + updateOptionButtonSelection(gameChangerButtons, selectedGameChanger); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h index a741b6696..aa4e61ff1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h @@ -37,6 +37,9 @@ private: QMap gameChangerButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); + +private slots: + void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp index 48704d010..b31394549 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -1,6 +1,9 @@ #include "edhrec_commander_api_response_budget_navigation_widget.h" +#include "../../interface/theme_manager.h" + #include +#include #include #include #include @@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); QPushButton *btn = new QPushButton(label, this); btn->setMinimumHeight(84); - btn->setStyleSheet("font-size: 24px"); + btn->setStyleSheet(QString()); budgetButtons[option] = btn; layout->addWidget(btn, 4, i); connect(btn, &QPushButton::clicked, this, [=, this]() { @@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg }); } + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont); + updateOptionButtonSelection(budgetButtons, ""); retranslateUi(); @@ -96,8 +103,18 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelecti const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - it.value()->setStyleSheet(it.key() == selectedKey - ? "background-color: lightblue; font-weight: bold; font-size: 24px;" - : "font-size: 24px"); + const bool selected = (it.key() == selectedKey); + it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.setPointSize(24); + buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); + it.value()->setFont(buttonFont); } +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size stays + // caller-side and the selected button keeps its bold emphasis. + updateOptionButtonSelection(budgetButtons, selectedBudget); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h index 8013d1d6b..d727225a5 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h @@ -37,6 +37,9 @@ private: QMap budgetButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); + +private slots: + void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 220dcfbda..6b46e07f5 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -1,9 +1,11 @@ #include "edhrec_commander_api_response_navigation_widget.h" +#include "../../interface/theme_manager.h" #include "../../tab_edhrec_main.h" #include "edhrec_commander_api_response_bracket_navigation_widget.h" #include "edhrec_commander_api_response_budget_navigation_widget.h" +#include #include #include #include @@ -30,10 +32,14 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation comboPushButton = new QPushButton(this); comboPushButton->setMinimumHeight(84); - comboPushButton->setStyleSheet("font-size: 24px"); + comboPushButton->setStyleSheet(QString()); averageDeckPushButton = new QPushButton(this); averageDeckPushButton->setMinimumHeight(84); - averageDeckPushButton->setStyleSheet("font-size: 24px"); + averageDeckPushButton->setStyleSheet(QString()); + + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont); layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); @@ -61,6 +67,15 @@ void EdhrecCommanderApiResponseNavigationWidget::retranslateUi() averageDeckPushButton->setText(tr("Average Deck")); } +void EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size stays caller-side. + QFont navFont = themeManager->appFont(AppFont::InterfaceDefault); + navFont.setPointSize(24); + comboPushButton->setFont(navFont); + averageDeckPushButton->setFont(navFont); +} + QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) { if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index 737558625..456228554 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -35,6 +35,9 @@ public slots: signals: void requestUrl(QString url); +private slots: + void applyInterfaceDefaultFont(); + private: QGridLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp index d641a1ea5..acb9baefe 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp @@ -6,6 +6,7 @@ #include "tab_developer.h" +#include "../interface/theme_manager.h" #include "libcockatrice/protocol/pb/response.pb.h" #include @@ -155,7 +156,8 @@ void TabDeveloper::appendSeparatorRow(const QString §ionTitle) const int row = statsTable->rowCount(); statsTable->insertRow(row); auto *labelItem = new QTableWidgetItem(sectionTitle); - auto font = labelItem->font(); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setBold(true); labelItem->setFont(font); labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable); diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index ce37ce440..16a53a8b3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -4,6 +4,7 @@ #include "../../deck_loader/deck_loader.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" +#include "../interface/theme_manager.h" #include "../visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h" #include "../visual_deck_storage/deck_preview/public_deck_preview_widget.h" #include "../visual_deck_storage/remote_public_decks_model.h" @@ -45,7 +46,11 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize(); titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); - titleLabel->setFont(QtUtils::boldFont(titleLabel->font())); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { + titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + }); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp index 76c3d0908..8269c4802 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp @@ -1,5 +1,8 @@ #include "visual_deck_editor_placeholder_widget.h" +#include "../interface/theme_manager.h" + +#include #include #include #include @@ -28,10 +31,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); - font-size: 14px; padding: 10px; } )"); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -42,4 +47,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa void VisualDeckEditorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here")); +} + +void VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. + QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); + placeholderFont.setPointSize(14); + textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h index 84a3142d8..794be671e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h @@ -15,6 +15,9 @@ public: explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr); void retranslateUi(); +private slots: + void applyInterfaceDefaultFont(); + private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index 82ed6191c..cc809cc71 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_tag_addition_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -65,8 +67,9 @@ void DeckPreviewTagAdditionWidget::paintEvent(QPaintEvent *event) painter.setPen(borderPen); painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width - // Calculate font size based on widget height - QFont font = painter.font(); + // Calculate font size based on widget height. Family comes from the + // InterfaceDefault theme role; the height-derived size stays as before. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 font.setPointSize(fontSize); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index e62570241..e946f6e27 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_tag_display_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -117,8 +119,9 @@ void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event) painter.setPen(borderPen); painter.drawRect(rect().adjusted(0, 0, -1, -1)); - // Calculate font size based on widget height - QFont font = painter.font(); + // Calculate font size based on widget height. Family comes from the + // InterfaceDefault theme role; the height-derived size stays as before. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 font.setPointSize(fontSize); painter.setFont(font); From 60f7710d9f2c21d22dc83119ac6c4e718f2cdf49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Wed, 21 Oct 2026 05:11:01 +0200 Subject: [PATCH 11/17] [Fonts] Drop unused lambda captures in font-refresh handlers --- .../src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp | 2 +- cockatrice/src/interface/widgets/general/home_widget.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp index c0552491d..914615533 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp @@ -40,7 +40,7 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent, QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); titleFont.setPointSize(titleFont.pointSize() + 2); titleLabel->setFont(titleFont); - connect(themeManager, &ThemeManager::fontsChanged, this, [this, titleLabel]() { + connect(themeManager, &ThemeManager::fontsChanged, this, [titleLabel]() { QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); refreshedFont.setBold(true); refreshedFont.setPointSize(refreshedFont.pointSize() + 2); diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index ec902d726..72aee8b48 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -303,7 +303,7 @@ QGroupBox *HomeWidget::createButtons() QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault); boxFont.setPointSize(20); box->setFont(boxFont); - connect(themeManager, &ThemeManager::fontsChanged, this, [this, box]() { + connect(themeManager, &ThemeManager::fontsChanged, this, [box]() { QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); refreshedFont.setPointSize(20); box->setFont(refreshedFont); From 28c200e9504381cedb4dd4df6ca4bc12bfb4ed3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 17:03:17 +0100 Subject: [PATCH 12/17] [Fonts] Fix pixel sizes and game overlay fonts from review - Set the sizes the stylesheets carried with setPixelSize() rather than setPointSize(): home buttons (34), banner (24), home group box (20), the placeholder labels (14), the analytics drag handle (10), the EDHREC navigation buttons (24), the user info popup (11/14/11/10/11) and the game overlays (14/16/12). Points at 96 DPI were ~33% larger. - The drag/total counters and tally columns keep the monospace family the stylesheets set, so digit widths stop changing as counts tick and white-space: pre stays aligned. - applyDeckListFont() drops the cached tally rows before re-measuring, so previewing a larger DeckList font no longer leaves the tally clipped. - Tally labels and the constructor/fontsChanged pairs in the shared-deck preview, quick setup, analytics panel, home box and public decks build their font once in a local lambda instead of twice. --- cockatrice/src/game_graphics/game_view.cpp | 64 +++++++++---------- cockatrice/src/game_graphics/game_view.h | 2 +- .../palette_editor/quick_setup_panel.cpp | 18 +++--- .../deck_analytics/resizable_panel.cpp | 15 ++--- .../dialogs/dlg_shared_decks_preview.cpp | 16 ++--- .../widgets/general/display/banner_widget.cpp | 2 +- .../widgets/general/home_styled_button.cpp | 2 +- .../interface/widgets/general/home_widget.cpp | 15 ++--- .../printing_selector_placeholder_widget.cpp | 2 +- .../widgets/server/user/user_info_popup.cpp | 10 +-- ...api_response_bracket_navigation_widget.cpp | 2 +- ..._api_response_budget_navigation_widget.cpp | 2 +- ...mmander_api_response_navigation_widget.cpp | 2 +- .../widgets/tabs/tab_public_decks.cpp | 7 +- .../visual_deck_editor_placeholder_widget.cpp | 2 +- 15 files changed, 76 insertions(+), 85 deletions(-) diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 346f515e4..7a51fc97f 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -26,6 +26,21 @@ #include #include +namespace +{ +QFont deckOverlayFont(int pixelSize, bool bold) +{ + // The drag/total counters and the tally columns are in-game overlays, not + // deck list text: keep the monospace family the stylesheets used to set so + // digit widths stay stable as counts tick and `white-space: pre` lines up. + QFont font(QStringLiteral("monospace")); + font.setStyleHint(QFont::Monospace); + font.setPixelSize(pixelSize); + font.setBold(bold); + return font; +} +} // namespace + // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. class SelectionRubberBand : public QRubberBand @@ -102,40 +117,26 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par tallyLayout->setSpacing(2); tallyContainer->hide(); - applyDeckListFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, &GameView::applyDeckListFont); + applyDeckOverlayFonts(); } -void GameView::applyDeckListFont() +void GameView::applyDeckOverlayFonts() { - // Family comes from the DeckList theme role; the pixel sizes and bold - // weights previously carried by the stylesheets stay caller-side. - QFont dragFont = themeManager->appFont(AppFont::DeckList); - dragFont.setPointSize(14); - dragFont.setWeight(QFont::Bold); - dragCountLabel->setFont(dragFont); - - QFont totalFont = themeManager->appFont(AppFont::DeckList); - totalFont.setPointSize(16); - totalFont.setWeight(QFont::Bold); - totalCountLabel->setFont(totalFont); - - QFont tallyFont = themeManager->appFont(AppFont::DeckList); - tallyFont.setPointSize(12); - tallyContainer->setFont(tallyFont); + dragCountLabel->setFont(deckOverlayFont(14, true)); + totalCountLabel->setFont(deckOverlayFont(16, true)); + tallyContainer->setFont(deckOverlayFont(12, false)); for (int i = 0; i < tallyLayout->count(); ++i) { if (auto *label = qobject_cast(tallyLayout->itemAt(i)->widget())) { - QFont labelFont = themeManager->appFont(AppFont::DeckList); - // Count column (odd positions) keeps the 14px bold emphasis. - if (i % 2 == 1) { - labelFont.setPointSize(14); - labelFont.setWeight(QFont::Bold); - } else { - labelFont.setPointSize(12); - } - label->setFont(labelFont); + // Count column (odd positions) keeps the bold emphasis. + const bool count = i % 2 == 1; + label->setFont(deckOverlayFont(count ? 14 : 12, count)); } } + + // The container keeps the size computed from the previous fonts, so force + // rebuildTallyLabels() to re-measure it instead of reusing cachedTallyRows. + cachedTallyRows.clear(); + updateTotalSelectionCount(); } void GameView::resizeEvent(QResizeEvent *event) @@ -266,18 +267,13 @@ QSize GameView::rebuildTallyLabels(const QList &entries) for (const TallyRow &entry : entries) { auto *nameLabel = new QLabel(entry.name, tallyContainer); nameLabel->setStyleSheet(nameStyle); - QFont nameFont = themeManager->appFont(AppFont::DeckList); - nameFont.setPointSize(12); - nameLabel->setFont(nameFont); + nameLabel->setFont(deckOverlayFont(12, false)); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(nameLabel, row, 0); auto *countLabel = new QLabel(entry.value, tallyContainer); countLabel->setStyleSheet(countStyle); - QFont countFont = themeManager->appFont(AppFont::DeckList); - countFont.setPointSize(14); - countFont.setWeight(QFont::Bold); - countLabel->setFont(countFont); + countLabel->setFont(deckOverlayFont(14, true)); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); tallyLayout->addWidget(countLabel, row, 1); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 0fca224b2..99a73a0a8 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -50,7 +50,7 @@ private slots: void refreshShortcuts(); void updateTotalSelectionCount(const QSize &viewSize = QSize()); void setFocusDisabled(bool disabled); - void applyDeckListFont(); + void applyDeckOverlayFonts(); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp index 612dcc2cf..00eac3253 100644 --- a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp @@ -30,16 +30,14 @@ QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent) labelLow = new QLabel(this); labelHigh = new QLabel(this); // Family comes from the InterfaceDefault theme role; the *0.82 size stays caller-side. - QFont small = themeManager->appFont(AppFont::InterfaceDefault); - small.setPointSizeF(small.pointSizeF() * 0.82); - labelLow->setFont(small); - labelHigh->setFont(small); - connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { - QFont refreshedSmall = themeManager->appFont(AppFont::InterfaceDefault); - refreshedSmall.setPointSizeF(refreshedSmall.pointSizeF() * 0.82); - labelLow->setFont(refreshedSmall); - labelHigh->setFont(refreshedSmall); - }); + auto applySmallFont = [this]() { + QFont small = themeManager->appFont(AppFont::InterfaceDefault); + small.setPointSizeF(small.pointSizeF() * 0.82); + labelLow->setFont(small); + labelHigh->setFont(small); + }; + applySmallFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applySmallFont); QPalette dimmed = labelLow->palette(); dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid)); labelLow->setPalette(dimmed); diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index 98df65113..df2a645f1 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -99,14 +99,13 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid "QPushButton:hover { background: %4; }") .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); // Family comes from the InterfaceDefault theme role; the 10px size stays caller-side. - QFont dragFont = themeManager->appFont(AppFont::InterfaceDefault); - dragFont.setPointSize(10); - dragButton->setFont(dragFont); - connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { - QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); - refreshedFont.setPointSize(10); - dragButton->setFont(refreshedFont); - }); + auto applyDragFont = [this]() { + QFont dragFont = themeManager->appFont(AppFont::InterfaceDefault); + dragFont.setPixelSize(10); + dragButton->setFont(dragFont); + }; + applyDragFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applyDragFont); resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " "stop:0 %1, stop:1 %2); }") diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp index 914615533..49bacd081 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp @@ -37,15 +37,13 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent, auto *titleLabel = new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this); // Family comes from the InterfaceDefault theme role; the +2 size and bold stay caller-side. - QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); - titleFont.setPointSize(titleFont.pointSize() + 2); - titleLabel->setFont(titleFont); - connect(themeManager, &ThemeManager::fontsChanged, this, [titleLabel]() { - QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); - refreshedFont.setBold(true); - refreshedFont.setPointSize(refreshedFont.pointSize() + 2); - titleLabel->setFont(refreshedFont); - }); + auto applyTitleFont = [titleLabel]() { + QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); + titleFont.setPointSize(titleFont.pointSize() + 2); + titleLabel->setFont(titleFont); + }; + applyTitleFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont); mainLayout->addWidget(titleLabel); if (!serverText.isEmpty()) { diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index b4b23aacc..5cb62db0f 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -65,7 +65,7 @@ void BannerWidget::applyInterfaceDefaultFont() // Family comes from the InterfaceDefault theme role; the 24px size and // bold weight previously carried by the stylesheet stay caller-side. QFont bannerFont = themeManager->appFont(AppFont::InterfaceDefault); - bannerFont.setPointSize(24); + bannerFont.setPixelSize(24); bannerFont.setWeight(QFont::Bold); bannerLabel->setFont(bannerFont); } diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index bbd1f8c04..9c31d3815 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -33,7 +33,7 @@ void HomeStyledButton::applyInterfaceDefaultFont() // Family comes from the InterfaceDefault theme role; the 34px size and // bold weight previously carried by the stylesheet stay caller-side. QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPointSize(34); + buttonFont.setPixelSize(34); buttonFont.setWeight(QFont::Bold); setFont(buttonFont); } diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 72aee8b48..5d7670c1f 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -300,14 +300,13 @@ QGroupBox *HomeWidget::createButtons() } )"); // Family comes from the InterfaceDefault theme role; the 20px size stays caller-side. - QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault); - boxFont.setPointSize(20); - box->setFont(boxFont); - connect(themeManager, &ThemeManager::fontsChanged, this, [box]() { - QFont refreshedFont = themeManager->appFont(AppFont::InterfaceDefault); - refreshedFont.setPointSize(20); - box->setFont(refreshedFont); - }); + auto applyBoxFont = [box]() { + QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault); + boxFont.setPixelSize(20); + box->setFont(boxFont); + }; + applyBoxFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applyBoxFont); box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *boxLayout = new QVBoxLayout; boxLayout->setAlignment(Qt::AlignHCenter); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp index d9d261be5..225c3e21d 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp @@ -55,6 +55,6 @@ void PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont() { // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); - placeholderFont.setPointSize(14); + placeholderFont.setPixelSize(14); textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index 6b6205166..f6e6508e2 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -481,7 +481,7 @@ void UserInfoPopup::applyTheme() gamesLabel->setStyleSheet(QStringLiteral("color:%1; background:transparent;").arg(colorStr(theme.subText))); QFont gamesLabelFont = themeManager->appFont(AppFont::InterfaceDefault); - gamesLabelFont.setPointSize(11); + gamesLabelFont.setPixelSize(11); gamesLabelFont.setWeight(QFont::Bold); gamesLabel->setFont(gamesLabelFont); @@ -489,13 +489,13 @@ void UserInfoPopup::applyTheme() "QPushButton:hover{color:%2;}") .arg(colorStr(theme.subText), colorStr(theme.text))); QFont refreshFont = themeManager->appFont(AppFont::InterfaceDefault); - refreshFont.setPointSize(14); + refreshFont.setPixelSize(14); refreshBtn->setFont(refreshFont); gamesStatus->setStyleSheet( QStringLiteral("color:%1; padding:10px; background:transparent;").arg(colorStr(theme.statusText))); QFont statusFont = themeManager->appFont(AppFont::InterfaceDefault); - statusFont.setPointSize(11); + statusFont.setPixelSize(11); gamesStatus->setFont(statusFont); gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}" @@ -508,7 +508,7 @@ void UserInfoPopup::applyTheme() "QPushButton:hover{color:%3;background:%4;}") .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); QFont closeFont = themeManager->appFont(AppFont::InterfaceDefault); - closeFont.setPointSize(10); + closeFont.setPixelSize(10); closeBtn->setFont(closeFont); header->update(); @@ -533,7 +533,7 @@ static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); // Family comes from the InterfaceDefault theme role; the 11px size stays caller-side. QFont btnFont = themeManager->appFont(AppFont::InterfaceDefault); - btnFont.setPointSize(11); + btnFont.setPixelSize(11); b->setFont(btnFont); return b; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp index 9324bcef7..749476619 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -104,7 +104,7 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelect const bool selected = (it.key() == selectedKey); it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPointSize(24); + buttonFont.setPixelSize(24); buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); it.value()->setFont(buttonFont); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp index b31394549..27cd1c51f 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -106,7 +106,7 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelecti const bool selected = (it.key() == selectedKey); it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); - buttonFont.setPointSize(24); + buttonFont.setPixelSize(24); buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal); it.value()->setFont(buttonFont); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 6b46e07f5..57070a5e1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -71,7 +71,7 @@ void EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont() { // Family comes from the InterfaceDefault theme role; the 24px size stays caller-side. QFont navFont = themeManager->appFont(AppFont::InterfaceDefault); - navFont.setPointSize(24); + navFont.setPixelSize(24); comboPushButton->setFont(navFont); averageDeckPushButton->setFont(navFont); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index 16a53a8b3..499f0d966 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -47,10 +47,11 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); // Family comes from the InterfaceDefault theme role; bold stays caller-side. - titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); - connect(themeManager, &ThemeManager::fontsChanged, this, [this]() { + auto applyTitleFont = [this]() { titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); - }); + }; + applyTitleFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp index 8269c4802..d3c087bc6 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp @@ -53,6 +53,6 @@ void VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont() { // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); - placeholderFont.setPointSize(14); + placeholderFont.setPixelSize(14); textLabel->setFont(placeholderFont); } \ No newline at end of file From ad860eb932e48c0006dced3cacdc126c89a0f94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 09:57:01 +0200 Subject: [PATCH 13/17] [Fonts] Fix font preview leaks and reuse default-font loader --- .../palette_editor/font_grid_widget.cpp | 4 ++ .../palette_editor/palette_editor_dialog.cpp | 48 ++++++++++++------- .../palette_editor/palette_editor_dialog.h | 7 ++- cockatrice/src/interface/theme_manager.cpp | 11 +---- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp index 0f01fbe09..20451afbc 100644 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -71,6 +71,9 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) void FontGridWidget::loadFonts(const FontConfig &cfg) { + // Suppress fontsChanged while materializing fallback rows so the initial + // load never reads as a user edit (mirrors paletteGrid blocking). + blockSignals(true); QMetaEnum fontEnum = QMetaEnum::fromType(); for (int i = 0; i < fontEnum.keyCount(); ++i) { auto role = static_cast(fontEnum.value(i)); @@ -93,6 +96,7 @@ void FontGridWidget::loadFonts(const FontConfig &cfg) } row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE)); } + blockSignals(false); } FontConfig FontGridWidget::currentFontConfig() const diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 09c81df5d..e1c9015d9 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -185,7 +185,10 @@ void PaletteEditorDialog::setupUi() connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply); connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent); connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); }); - connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] { autoApplyTimer->start(); }); + connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] { + fontsDirty = true; + autoApplyTimer->start(); + }); connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault); connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset); connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave); @@ -280,15 +283,9 @@ void PaletteEditorDialog::loadSchemes() void PaletteEditorDialog::loadFonts() { - // Same user-dir > system-dir fallback ThemeManager uses: the shipped - // fonts.toml may live in the system theme directory rather than the - // resolved (user) theme directory. - FontConfig cfg = ThemeManager::loadFontConfig(themeDirPath); - if (!cfg.hasFonts()) { - cfg = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); - } - savedFontConfig = cfg; - fontGrid->loadFonts(cfg); + savedFontConfig = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); + fontGrid->loadFonts(savedFontConfig); + fontsDirty = false; } void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) @@ -321,7 +318,9 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi void PaletteEditorDialog::onApply() { themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); - themeManager->previewFonts(fontGrid->currentFontConfig()); + if (fontsDirty) { + themeManager->previewFonts(fontGrid->currentFontConfig()); + } } void PaletteEditorDialog::onSave() @@ -373,14 +372,17 @@ void PaletteEditorDialog::onSave() } // Persist the shared per-theme fonts when they changed. - const FontConfig fontCfg = fontGrid->currentFontConfig(); - if (fontCfg != savedFontConfig) { - if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) { - QMessageBox::warning(this, tr("Save failed"), - tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir)); - return; + if (fontsDirty) { + const FontConfig fontCfg = fontGrid->currentFontConfig(); + if (fontCfg != savedFontConfig) { + if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) { + QMessageBox::warning(this, tr("Save failed"), + tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir)); + return; + } + savedFontConfig = fontCfg; } - savedFontConfig = fontCfg; + fontsDirty = false; } themeManager->reloadCurrentTheme(); @@ -396,6 +398,7 @@ void PaletteEditorDialog::onReset() // config until the theme is reloaded. themeManager->reloadCurrentTheme(); fontGrid->loadFonts(savedFontConfig); + fontsDirty = false; seedAccentFromScheme(loadedScheme); } @@ -442,6 +445,15 @@ void PaletteEditorDialog::onRevertToDefault() const FontConfig fontDef = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); savedFontConfig = fontDef; fontGrid->loadFonts(fontDef); + fontsDirty = false; +} + +void PaletteEditorDialog::reject() +{ + // Discard any unsaved live preview (palette and fonts) so closing without + // Save restores the active theme instead of leaking the preview globally. + themeManager->reloadCurrentTheme(); + QDialog::reject(); } void PaletteEditorDialog::changeEvent(QEvent *e) diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h index 0a9d47c6c..5e47536b5 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h @@ -26,6 +26,7 @@ class PaletteEditorDialog : public QDialog public: explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr); void loadSchemes(); + void reject() override; private slots: void onSave(); @@ -79,6 +80,10 @@ private: // Fonts are shared per theme (fonts.toml, not split by light/dark), so a // single saved config suffices; the live edits are read from fontGrid. FontConfig savedFontConfig; + // Set only by user-driven fontsChanged after the initial load completes. + // Gates both previewFonts and the fonts.toml write so a palette-only Save + // never materializes fallback fonts to disk. + bool fontsDirty = false; QTimer *autoApplyTimer = nullptr; @@ -86,4 +91,4 @@ protected: void changeEvent(QEvent *e) override; }; -#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H \ No newline at end of file +#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index fdd6bbc30..d2580a18d 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -742,16 +742,7 @@ 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); - } - } + FontConfig fonts = loadDefaultFontConfig(dirPath, themeName); currentFonts = fonts; const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault); if (fontIt != currentFonts.fonts.constEnd()) { From 17061980cc0456e8ce2e5927f006042b653f1db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 10:06:16 +0200 Subject: [PATCH 14/17] [Fonts] Restore DeckList Serif default and live-update all role readers --- .../src/client/settings/shortcut_treeview.cpp | 12 ++++++++++++ cockatrice/src/filters/filter_tree_model.cpp | 6 ++++++ .../src/game_graphics/deckview/deck_view.cpp | 12 ++++++++++++ cockatrice/src/game_graphics/game_scene.cpp | 9 +++++++++ .../src/interface/deck_loader/deck_loader.cpp | 3 +++ cockatrice/src/interface/theme_manager.cpp | 5 +++-- cockatrice/src/interface/theme_manager.h | 5 +++-- .../widgets/deck_editor/deck_list_style_proxy.cpp | 10 ++++++++++ .../widgets/deck_editor/deck_list_style_proxy.h | 2 +- .../interface/widgets/dialogs/settings_view.cpp | 5 +++++ .../display/charts/bars/bar_chart_widget.cpp | 4 ++++ .../display/charts/bars/segmented_bar_widget.cpp | 4 ++++ .../widgets/server/user/user_list_widget.cpp | 13 +++++++++++++ .../src/interface/widgets/tabs/tab_developer.cpp | 14 ++++++++++++++ .../interface/widgets/tabs/tab_public_decks.cpp | 7 ++++++- .../widgets/utility/card_completer_styler.cpp | 5 +++++ .../deck_preview_tag_addition_widget.cpp | 4 ++++ .../deck_preview_tag_display_widget.cpp | 4 ++++ 18 files changed, 118 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index d81589f34..2501798cb 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -79,6 +79,18 @@ ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent) connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &ShortcutTreeView::refreshShortcuts); + + // Group headers store their font on the item; re-apply the role family on + // font changes (setFont emits itemChanged, which repaints the rows). + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + QFont groupFont = themeManager->appFont(AppFont::InterfaceDefault); + groupFont.setBold(true); + for (int row = 0; row < shortcutsModel->rowCount(); ++row) { + if (auto *groupItem = shortcutsModel->item(row)) { + groupItem->setFont(groupFont); + } + } + }); } void ShortcutTreeView::populateShortcutsModel() diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp index 467c2564b..66fe39821 100644 --- a/cockatrice/src/filters/filter_tree_model.cpp +++ b/cockatrice/src/filters/filter_tree_model.cpp @@ -41,6 +41,12 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent) connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); + // FontRole data reads the DeckList role live; rebroadcast so attached + // views re-query instead of waiting for an incidental repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + }); } FilterTreeModel::~FilterTreeModel() diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index 63ee48e57..0ab91f7dd 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -368,6 +368,15 @@ void DeckViewCardContainer::setWidth(qreal _width) DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0) { + // Zone headers read the DeckList role at paint time and cache their + // rendering, so touch every item to invalidate the caches and pick up the + // new family on font changes (containers cannot connect this themselves: + // QGraphicsItem is not a QObject). + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + for (auto *item : items()) { + item->update(); + } + }); } DeckViewScene::~DeckViewScene() @@ -579,6 +588,9 @@ DeckView::DeckView(QWidget *parent) : QGraphicsView(parent) connect(deckViewScene, &DeckViewScene::sceneRectChanged, this, &DeckView::updateSceneRect); connect(deckViewScene, &DeckViewScene::newCardAdded, this, &DeckView::newCardAdded); connect(deckViewScene, &DeckViewScene::sideboardPlanChanged, this, &DeckView::sideboardPlanChanged); + // The scene already touches its items on fontsChanged; repaint the + // viewport here so the new DeckList family shows without interaction. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { viewport()->update(); }); } void DeckView::resizeEvent(QResizeEvent *event) diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index db1040006..1f780b795 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -1,6 +1,7 @@ #include "game_scene.h" #include "../client/settings/cache_settings.h" +#include "../interface/theme_manager.h" #include "animated_item.h" #include "board/abstract_card_item.h" #include "board/arrow_item.h" @@ -64,6 +65,14 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) addItem(phasesToolbar); connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this, &GameScene::rearrange); + // CardText items (P/T boxes, counters, player names) read the role at + // paint time and cache their rendering, so touch every item to invalidate + // the caches and pick up the new family on font changes. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + for (auto *item : items()) { + item->update(); + } + }); rearrange(); } diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index d5c7de9a4..e862270af 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -646,6 +646,9 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) QTextDocument doc; // Family comes from the DeckList theme role; the print sizes/weights below stay unchanged. + // Print output is a one-shot snapshot and intentionally does not subscribe + // to fontsChanged (there is no live view to repaint); reprinting picks up + // the current role font. QFont font = themeManager->appFont(AppFont::DeckList); font.setStyleHint(QFont::Serif); doc.setDefaultFont(font); diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index d2580a18d..a10078812 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -662,8 +662,9 @@ QFont ThemeManager::appFont(AppFont::Role role) const // Role fallbacks preserve the pre-theme defaults when no fonts.toml is // present: an unset role resolves to the application font, so the result is // identical to before. The report dialogs that used to hardcode monospace - // ask for it explicitly (report_utils::reportLogFont), and the counter - // painters that used to hardcode Serif ask for it through cardTextFont(). + // ask for it explicitly (report_utils::reportLogFont); the counter painters + // and deck lists that used to hardcode Serif ask for it through + // cardTextFont() and deckListFont(). return qApp->font(); } diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 35cc36d04..9d86eacce 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -150,8 +150,9 @@ public: // 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 a per-role fallback (serif basis - * for CardText, the application font for the remaining roles). */ + * value when present, otherwise the application font. Roles that + * had a historical hardcoded family ask for it through + * cardTextFont() and deckListFont() instead. */ QFont appFont(AppFont::Role role) const; /** @brief Whether the active theme defines this role in fonts.toml. */ bool hasAppFont(AppFont::Role role) const; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 59b7fff79..a9c9cad7a 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -10,6 +10,16 @@ #include #include +DeckListStyleProxy::DeckListStyleProxy(QObject *parent) : QIdentityProxyModel(parent) +{ + // FontRole data reads the DeckList role live; rebroadcast so attached + // views re-query instead of waiting for an incidental repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + }); +} + QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { QModelIndex src = mapToSource(index); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h index e4d1de4f4..3149da6de 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h @@ -8,7 +8,7 @@ class DeckListStyleProxy : public QIdentityProxyModel { Q_OBJECT public: - using QIdentityProxyModel::QIdentityProxyModel; + explicit DeckListStyleProxy(QObject *parent = nullptr); [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; }; diff --git a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp index d22a8d0b3..14d16b92c 100644 --- a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp +++ b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp @@ -7,6 +7,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../pixel_map_generator.h" +#include "../interface/theme_manager.h" #include "../settings_page/abstract_settings_page.h" #include "../settings_page/appearance_settings_page.h" #include "../settings_page/deck_editor_settings_page.h" @@ -199,6 +200,10 @@ void SettingsView::setupUi() searchDelegate->setPageIcons(icons); searchResultsView->viewport()->update(); }); + // The search delegate reads the InterfaceDefault role at paint time, so a + // font change only needs a repaint, mirroring the theme change above. + connect(themeManager, &ThemeManager::fontsChanged, searchResultsView, + [this] { searchResultsView->viewport()->update(); }); // Build search index after pages are created buildSearchIndex(); diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp index cd2a3702d..6bb1dcd62 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -21,6 +21,10 @@ BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent) { setMouseTracking(true); + + // Labels read the InterfaceDefault role at paint time, so a font change + // only needs a repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } void BarChartWidget::setBars(const QVector &newBars) diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp index d6c75f3bd..359d56772 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -24,6 +24,10 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector segments, setMinimumWidth(36); setMaximumWidth(50); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + // The label reads the InterfaceDefault role at paint time, so a font + // change only needs a repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } QSize SegmentedBarWidget::sizeHint() const diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 48cd6167f..34e542e02 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -519,6 +519,19 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->viewport()->update(); userTree->update(); }); + // Divider headers store their font on the item, so re-apply the role + // family here; the painter reads the role live and needs only the + // repaint below. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + QFont dividerFont = themeManager->appFont(AppFont::InterfaceDefault); + dividerFont.setBold(true); + for (auto it = sectionItems.constBegin(); it != sectionItems.constEnd(); ++it) { + it.value()->setFont(0, dividerFont); + it.value()->setSizeHint(0, QSize(0, QFontMetrics(dividerFont).height() + 16)); + } + userTree->viewport()->update(); + userTree->update(); + }); } QVBoxLayout *vbox = new QVBoxLayout; diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp index acb9baefe..024cd46f0 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp @@ -101,6 +101,20 @@ TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_clien central->setLayout(mainLayout); setCentralWidget(central); + // Separator rows store their font on the item; re-apply the role family on + // font changes (value cells keep the default font and need only a repaint). + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + QFont separatorFont = themeManager->appFont(AppFont::InterfaceDefault); + separatorFont.setBold(true); + for (int row = 0; row < statsTable->rowCount(); ++row) { + auto *labelItem = statsTable->item(row, 0); + if (labelItem && !(labelItem->flags() & Qt::ItemIsSelectable)) { + labelItem->setFont(separatorFont); + } + } + statsTable->viewport()->update(); + }); + retranslateUi(); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index 499f0d966..dc14b6e4a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -51,7 +51,12 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); }; applyTitleFont(); - connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont); + connect(themeManager, &ThemeManager::fontsChanged, this, [this, applyTitleFont] { + applyTitleFont(); + // Preview cells paint with the live role font; repaint the grid so the + // new family shows without waiting for a scroll or hover. + flowWidget->update(); + }); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp index 8f1941412..1a17bb8be 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp @@ -1,5 +1,6 @@ #include "card_completer_styler.h" +#include "../../interface/theme_manager.h" #include "../cards/card_info_picture_enlarged_widget.h" #include "card_completer_delegate.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -73,6 +74,10 @@ CardCompleterStyler::CardCompleterStyler(QCompleter *completer, QObject *parent) popup->viewport()->setMouseTracking(true); + // The delegate reads the CardText role at paint time, so a font change + // only needs a repaint of the popup. + connect(themeManager, &ThemeManager::fontsChanged, popup, [popup] { popup->viewport()->update(); }); + popup->installEventFilter(this); popup->viewport()->installEventFilter(this); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index cc809cc71..30e99f401 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -28,6 +28,10 @@ DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QSt // Adjust widget size setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + // The tag text reads the InterfaceDefault role at paint time, so a font + // change only needs a repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } QSize DeckPreviewTagAdditionWidget::sizeHint() const diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index e946f6e27..34809da2e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -43,6 +43,10 @@ DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const // Adjust widget size setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + // The tag text reads the InterfaceDefault role at paint time, so a font + // change only needs a repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); + // Connect close button to the remove signal // connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed); } From 97ae1b93db69ee236e1fa25d4d139db3c1e3a110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 17:23:51 +0100 Subject: [PATCH 15/17] [Fonts] Keep the DeckList fallback on the application font DeckList is read by the deck editor headers, the filter tree and the deck view too, not only by the two surfaces that hardcoded Serif. Resolving the unset role to Serif in appFont() made every user without a fonts.toml see Serif outside the deck list. - appFont(DeckList) falls back to the application font again. - ThemeManager::deckListFont() resolves the role and keeps the Serif fallback for the two historical sites: the deck view zone headers and the printed deck list. - DeckView no longer repaints its viewport on fontsChanged; touching the scene items already schedules the repaint in every attached view. --- .../src/game_graphics/deckview/deck_view.cpp | 9 ++------ .../src/interface/deck_loader/deck_loader.cpp | 11 +++++----- cockatrice/src/interface/theme_manager.cpp | 21 ++++++++++++++----- cockatrice/src/interface/theme_manager.h | 5 +++++ 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index 0ab91f7dd..f62d33335 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -241,8 +241,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI painter->setPen(QColor(Qt::white)); // Family comes from the DeckList theme role; pixel sizes/weights unchanged. - QFont f = themeManager->appFont(AppFont::DeckList); - f.setStyleHint(QFont::Serif); + QFont f = themeManager->deckListFont(); f.setPixelSize(24); f.setWeight(QFont::Bold); painter->setFont(f); @@ -294,8 +293,7 @@ QList> DeckViewCardContainer::getRowsAndCols() const int DeckViewCardContainer::getCardTypeTextWidth() const { // Family comes from the DeckList theme role; pixel size/weight unchanged. - QFont f = themeManager->appFont(AppFont::DeckList); - f.setStyleHint(QFont::Serif); + QFont f = themeManager->deckListFont(); f.setPixelSize(16); f.setWeight(QFont::Bold); QFontMetrics fm(f); @@ -588,9 +586,6 @@ DeckView::DeckView(QWidget *parent) : QGraphicsView(parent) connect(deckViewScene, &DeckViewScene::sceneRectChanged, this, &DeckView::updateSceneRect); connect(deckViewScene, &DeckViewScene::newCardAdded, this, &DeckView::newCardAdded); connect(deckViewScene, &DeckViewScene::sideboardPlanChanged, this, &DeckView::sideboardPlanChanged); - // The scene already touches its items on fontsChanged; repaint the - // viewport here so the new DeckList family shows without interaction. - connect(themeManager, &ThemeManager::fontsChanged, this, [this] { viewport()->update(); }); } void DeckView::resizeEvent(QResizeEvent *event) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index e862270af..daa5dc98a 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -581,7 +581,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode QTextBlockFormat blockFormat; QTextCharFormat charFormat; // Family comes from the DeckList theme role; sizes/weights unchanged. - charFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); + charFormat.setFontFamilies({themeManager->deckListFont().family()}); charFormat.setFontPointSize(11); charFormat.setFontWeight(QFont::Bold); cursor->insertBlock(blockFormat, charFormat); @@ -595,7 +595,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode const AbstractDecklistCardNode *card = cards[i]; QTextCharFormat cellCharFormat; - cellCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); + cellCharFormat.setFontFamilies({themeManager->deckListFont().family()}); cellCharFormat.setFontPointSize(9); QTextTableCell cell = table->cellAt(i, 0); @@ -617,7 +617,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode QTextBlockFormat blockFormat; QTextCharFormat charFormat; - charFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); + charFormat.setFontFamilies({themeManager->deckListFont().family()}); charFormat.setFontPointSize(14); charFormat.setFontWeight(QFont::Bold); @@ -649,15 +649,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) // Print output is a one-shot snapshot and intentionally does not subscribe // to fontsChanged (there is no live view to repaint); reprinting picks up // the current role font. - QFont font = themeManager->appFont(AppFont::DeckList); - font.setStyleHint(QFont::Serif); + QFont font = themeManager->deckListFont(); doc.setDefaultFont(font); QTextCursor cursor(&doc); QTextBlockFormat headerBlockFormat; QTextCharFormat headerCharFormat; - headerCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); + headerCharFormat.setFontFamilies({themeManager->deckListFont().family()}); headerCharFormat.setFontPointSize(16); headerCharFormat.setFontWeight(QFont::Bold); diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index a10078812..f120af8e4 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -660,11 +660,12 @@ QFont ThemeManager::appFont(AppFont::Role role) const } // Role fallbacks preserve the pre-theme defaults when no fonts.toml is - // present: an unset role resolves to the application font, so the result is - // identical to before. The report dialogs that used to hardcode monospace - // ask for it explicitly (report_utils::reportLogFont); the counter painters - // and deck lists that used to hardcode Serif ask for it through - // cardTextFont() and deckListFont(). + // present: an unset role resolves to the application font. Surfaces that + // hardcoded a different family ask for it explicitly: counter painters + // through cardTextFont(), the deck view headers and printed deck list + // through deckListFont(), the report dialogs through + // report_utils::reportLogFont(). Only the family basis is fixed there; + // sizes stay caller-side. return qApp->font(); } @@ -683,6 +684,16 @@ QFont ThemeManager::cardTextFont() const return serifFont; } +QFont ThemeManager::deckListFont() const +{ + if (hasAppFont(AppFont::DeckList)) { + return appFont(AppFont::DeckList); + } + QFont serifFont(QStringLiteral("Serif")); + serifFont.setStyleHint(QFont::Serif); + return serifFont; +} + QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const { // No per-role scaling and no preview: an unset role resolves to the font diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 9d86eacce..ffe1043b5 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -159,6 +159,11 @@ public: /** @brief The CardText role with its historical Serif fallback. Used by the * counter painters, which were Serif before the role existed. */ QFont cardTextFont() const; + /** @brief The DeckList role with its historical Serif fallback. Used by the + * two surfaces that hardcoded Serif before the role existed (the + * deck view zone headers and the printed deck list); every other + * DeckList reader falls back to the application font. */ + QFont deckListFont() const; /** @brief Resolves an application font role against the startup font only, * ignoring any previewed config in currentFonts. Editors that show * "what this role resolves to" must use this, because appFont() From 45ec061e4ecfd66c7bdf20d7ee93bad7b64cd3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 10:13:21 +0200 Subject: [PATCH 16/17] [Fonts] Polish font editor accessibility and note text --- .../src/interface/palette_editor/font_grid_widget.cpp | 7 ++++++- .../src/interface/palette_editor/palette_editor_dialog.cpp | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp index 20451afbc..44ad59551 100644 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -63,6 +63,8 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) grid->addWidget(sizeSpin, i, 2); connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); }); + label->setBuddy(familyCombo); + fontRows[role] = {label, familyCombo, sizeSpin}; } @@ -126,6 +128,9 @@ void FontGridWidget::retranslateUi() row.label->setText(roleDisplayName(role)); row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); - row.sizeSpin->setToolTip(tr("Point size")); + row.familyCombo->setAccessibleName(roleDisplayName(role)); + row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role))); + row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role))); + row.sizeSpin->setSuffix(tr(" pt")); } } diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index e1c9015d9..5bcfbb251 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -147,6 +147,7 @@ void PaletteEditorDialog::setupUi() fontSectionLayout->setSpacing(0); fontNoteLabel = new QLabel(fontSection); fontNoteLabel->setContentsMargins(12, 4, 12, 0); + fontNoteLabel->setWordWrap(true); fontSectionLayout->addWidget(fontNoteLabel); fontGrid = new FontGridWidget(fontSection); fontSectionLayout->addWidget(fontGrid); @@ -240,7 +241,7 @@ void PaletteEditorDialog::retranslateUi() paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks")); fontToggleButton->setText(fontSection->isVisible() ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); fontToggleButton->setToolTip(tr("Show or hide the per-purpose font settings for this theme")); - fontNoteLabel->setText(tr("Fonts belong to theme %1").arg(themeName)); + fontNoteLabel->setText(tr("Fonts belong to theme %1 (shared by Light and Dark schemes)").arg(themeName)); fontGrid->retranslateUi(); revertButton->setText(tr("↺ Revert to theme default")); From 159485bc462caca3acd11c052fc2691237fd9223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 16 Oct 2026 11:27:31 +0200 Subject: [PATCH 17/17] [Fonts] Retire max font size setting into theme CardText min/max range The per-card text size lives in the theme now, so a theme can pick the size it was designed for instead of every user overriding it globally. - CardText stores family + max point size; CardTextMinSize stores the overlay shrink-to-fit floor (omitted from fonts.toml when 1). - ThemeManager::cardTextMaxSize()/cardTextMinSize() resolve the range with the retired defaults (12 max, 1 min), and cardTextMaxSize() falls back to the CardText role size that #7541 made live. - The overlay widget starts at the explicit size when one is set (deck preview), else the theme max, and shrinks down to the theme floor. - The appearance page's "Maximum font size" spin is gone; the range is edited in the theme's font grid, whose min/max spins now keep the saved range consistent with each other. - Migration: appearance/maxFontSize is seeded into every user theme folder and into the active theme's folder (created when the theme is a shipped one that only exists in the system directory). The retired game/maxfontsize -> appearance/maxFontSize mapping stays, so users still on global.ini keep their value through the chain. --- .../src/client/settings/cache_settings.cpp | 3 + .../board/abstract_card_item.cpp | 5 +- .../palette_editor/font_grid_widget.cpp | 74 ++++++- .../palette_editor/font_grid_widget.h | 1 + cockatrice/src/interface/theme_config.cpp | 35 +++- cockatrice/src/interface/theme_config.h | 10 +- cockatrice/src/interface/theme_manager.cpp | 24 +++ cockatrice/src/interface/theme_manager.h | 5 + ..._info_picture_with_text_overlay_widget.cpp | 17 +- ...rd_info_picture_with_text_overlay_widget.h | 1 + .../appearance_settings_page.cpp | 10 - .../settings_page/appearance_settings_page.h | 2 - .../settings/appearance_settings.cpp | 10 - .../settings/appearance_settings.h | 2 - .../settings/settings_migration.cpp | 184 +++++++++++++++++ .../settings/settings_migration.h | 9 + tests/font_config_test.cpp | 149 ++++++++++++++ tests/settings/settings_defaults_test.cpp | 13 -- tests/settings/settings_migration_test.cpp | 192 ++++++++++++++++++ 19 files changed, 697 insertions(+), 49 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 08eccf1e4..e1acb67da 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -137,6 +137,9 @@ SettingsCache::SettingsCache() // before that rename (idempotent, safe to run on every startup, portable builds included). SettingsMigration::migrateGameFiltersToCamelCase(settingsPath); + // Retire appearance/maxFontSize into per-theme CardText max (idempotent, safe every startup). + SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath); + shortcutsSettings = new ShortcutsSettings(settingsPath, this); cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this); serversSettings = new ServersSettings(settingsPath, this); diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index 84e062cd7..a688cafcf 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -144,8 +143,8 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle) { - const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize(); - const int fontSize = std::max(9, MAX_FONT_SIZE); + const int minSize = std::max(1, themeManager->cardTextMinSize()); + const int fontSize = std::max(minSize, std::max(1, themeManager->cardTextMaxSize())); QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect()); diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp index 44ad59551..c4b81de86 100644 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -13,6 +13,8 @@ static constexpr int MIN_FONT_SIZE = 6; static constexpr int MAX_FONT_SIZE = 72; +static constexpr int CARD_TEXT_MIN_SIZE = 1; +static constexpr int CARD_TEXT_MAX_SIZE = 12; static constexpr int FALLBACK_FONT_SIZE = 10; static const QMap APP_FONT_DESCRIPTIONS = { @@ -46,6 +48,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) grid->setColumnStretch(0, 0); grid->setColumnStretch(1, 1); grid->setColumnStretch(2, 0); + grid->setColumnStretch(3, 0); QMetaEnum fontEnum = QMetaEnum::fromType(); for (int i = 0; i < fontEnum.keyCount(); ++i) { @@ -58,6 +61,30 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) grid->addWidget(familyCombo, i, 1); connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); }); + if (role == AppFont::CardText) { + auto *minSpin = new QSpinBox(this); + minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE); + grid->addWidget(minSpin, i, 2); + connect(minSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); }); + + auto *maxSpin = new QSpinBox(this); + maxSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); + grid->addWidget(maxSpin, i, 3); + connect(maxSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); }); + + // Keep the range consistent: otherwise min 20 / max 12 saves as-is + // and cardTextMaxSize() silently clamps the max up to the min, so + // the max spin shows a size that is never used. Moving one spin + // pulls the other's bound along. + connect(minSpin, &QSpinBox::valueChanged, this, [maxSpin](int value) { maxSpin->setMinimum(value); }); + connect(maxSpin, &QSpinBox::valueChanged, this, [minSpin](int value) { minSpin->setMaximum(value); }); + + label->setBuddy(familyCombo); + + fontRows[role] = {label, familyCombo, maxSpin, minSpin}; + continue; + } + auto *sizeSpin = new QSpinBox(this); sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); grid->addWidget(sizeSpin, i, 2); @@ -65,7 +92,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent) label->setBuddy(familyCombo); - fontRows[role] = {label, familyCombo, sizeSpin}; + fontRows[role] = {label, familyCombo, sizeSpin, nullptr}; } retranslateUi(); @@ -89,6 +116,37 @@ void FontGridWidget::loadFonts(const FontConfig &cfg) const QFont font = cfg.fonts.value(role, fallback); row.familyCombo->setCurrentFont(font); + if (role == AppFont::CardText && row.minSpin != nullptr) { + // A previous load may have narrowed the linked bounds (min 20 pulls + // max up to 20), which would clamp the values written below. Reset + // both to the full range first; the valueChanged lambdas re-link + // them as the values are set. + row.minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE); + row.sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); + + // The max spin is a size-only override. Without CardTextMaxSize it + // follows the CardText family size, and without CardText at all it + // must match cardTextMaxSize()'s default rather than the system + // startup size, which currentFontConfig() would otherwise bake in. + const int fallbackMax = cfg.fonts.contains(role) ? font.pointSize() : CARD_TEXT_MAX_SIZE; + int maxSize = cfg.cardTextMaxSize > 0 ? cfg.cardTextMaxSize : fallbackMax; + if (maxSize <= 0) { + maxSize = CARD_TEXT_MAX_SIZE; + } + int minSize = cfg.cardTextMinSize > 0 ? cfg.cardTextMinSize : themeManager->cardTextMinSize(); + minSize = qBound(CARD_TEXT_MIN_SIZE, minSize, MAX_FONT_SIZE); + maxSize = qBound(MIN_FONT_SIZE, maxSize, MAX_FONT_SIZE); + if (maxSize < minSize) { + maxSize = minSize; + } + + // Max first, then min: each value re-applies the linked bound, so + // the pair ends up consistent instead of clamping the smaller one. + row.sizeSpin->setValue(maxSize); + row.minSpin->setValue(minSize); + continue; + } + int size = font.pointSize(); if (size <= 0) { size = qApp->font().pointSize(); @@ -114,6 +172,11 @@ FontConfig FontGridWidget::currentFontConfig() const QFont font(row.familyCombo->currentFont().family()); font.setPointSize(row.sizeSpin->value()); cfg.fonts[role] = font; + + if (role == AppFont::CardText && row.minSpin != nullptr) { + cfg.cardTextMinSize = row.minSpin->value(); + cfg.cardTextMaxSize = row.sizeSpin->value(); + } } return cfg; } @@ -129,6 +192,15 @@ void FontGridWidget::retranslateUi() row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); row.familyCombo->setAccessibleName(roleDisplayName(role)); + if (role == AppFont::CardText && row.minSpin != nullptr) { + row.minSpin->setToolTip(tr("Minimum card text size")); + row.minSpin->setAccessibleName(tr("Minimum card text size")); + row.minSpin->setSuffix(tr(" pt")); + row.sizeSpin->setToolTip(tr("Maximum card text size")); + row.sizeSpin->setAccessibleName(tr("Maximum card text size")); + row.sizeSpin->setSuffix(tr(" pt")); + continue; + } row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role))); row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role))); row.sizeSpin->setSuffix(tr(" pt")); diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.h b/cockatrice/src/interface/palette_editor/font_grid_widget.h index 8c80306d3..07ccfa868 100644 --- a/cockatrice/src/interface/palette_editor/font_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.h @@ -35,6 +35,7 @@ private: QLabel *label = nullptr; QFontComboBox *familyCombo = nullptr; QSpinBox *sizeSpin = nullptr; + QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor }; QMap fontRows; diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 54143d7d3..a75330db4 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -308,7 +308,7 @@ QPalette PaletteConfig::apply(QPalette base) const bool FontConfig::hasFonts() const { - return !fonts.isEmpty(); + return !fonts.isEmpty() || cardTextMinSize != 1 || cardTextMaxSize > 0; } QString FontConfig::toToml() const @@ -334,6 +334,14 @@ QString FontConfig::toToml() const entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize); } + if (cardTextMinSize != 1) { + entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMinSize"), -20).arg(cardTextMinSize); + } + + if (cardTextMaxSize > 0) { + entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(cardTextMaxSize); + } + if (entries.isEmpty()) { return {}; } @@ -401,6 +409,31 @@ FontConfig FontConfig::fromFile(const QString &filePath) value = value.mid(1, value.size() - 2).trimmed(); } + // CardText size bounds: bare integers, e.g. CardTextMinSize = 2. + // Tolerate quoting like the family entries; skip invalid lines. + QString sizeKey = key; + if (sizeKey.startsWith("AppFont::")) { + sizeKey = sizeKey.mid(QStringLiteral("AppFont::").size()); + } + if (sizeKey.compare("CardTextMinSize", Qt::CaseInsensitive) == 0) { + bool ok = false; + int minSize = value.trimmed().toInt(&ok); + if (!ok || minSize <= 0) { + continue; + } + cfg.cardTextMinSize = minSize; + continue; + } + if (sizeKey.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) { + bool ok = false; + int maxSize = value.trimmed().toInt(&ok); + if (!ok || maxSize <= 0) { + continue; + } + cfg.cardTextMaxSize = maxSize; + continue; + } + int comma = value.lastIndexOf(','); if (comma < 0) { diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 84601a2bf..ebad7ef46 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -84,15 +84,23 @@ struct PaletteConfig * @code * [Fonts] * InterfaceDefault = "Inter, 10" + * CardText = "Noto Serif, 12" + * CardTextMinSize = 1 * @endcode + * CardText stores family + MAX size; cardTextMinSize stores the overlay + * shrink-to-fit floor (omitted from TOML when 1). */ struct FontConfig { QMap fonts; + int cardTextMinSize = 1; + /// Optional size-only maximum for CardText, used by the retired maxFontSize + /// migration so seeding it does not have to pin a family. + int cardTextMaxSize = 0; bool operator==(const FontConfig &rhs) const { - return fonts == rhs.fonts; + return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize && cardTextMaxSize == rhs.cardTextMaxSize; } bool operator!=(const FontConfig &rhs) const { diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index f120af8e4..e23284393 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -681,6 +681,7 @@ QFont ThemeManager::cardTextFont() const } QFont serifFont(QStringLiteral("Serif")); serifFont.setStyleHint(QFont::Serif); + serifFont.setPointSize(12); return serifFont; } @@ -702,6 +703,29 @@ QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const return defaultFont; } +int ThemeManager::cardTextMaxSize() const +{ + if (currentFonts.cardTextMaxSize > 0) { + return currentFonts.cardTextMaxSize; + } + const auto it = currentFonts.fonts.constFind(AppFont::CardText); + if (it != currentFonts.fonts.constEnd()) { + const int size = it.value().pointSize(); + if (size > 0) { + return size; + } + } + return 12; +} + +int ThemeManager::cardTextMinSize() const +{ + if (currentFonts.cardTextMinSize > 0) { + return currentFonts.cardTextMinSize; + } + return 1; +} + void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index ffe1043b5..0cd44125e 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -169,6 +169,11 @@ public: * "what this role resolves to" must use this, because appFont() * reflects a live preview while the editor is open. */ QFont defaultAppFont(AppFont::Role role) const; + /** @brief CardText max size: stored CardTextMaxSize, else the CardText + * point size, else 12. */ + int cardTextMaxSize() const; + /** @brief CardText shrink-to-fit floor: stored CardTextMinSize, else 1. */ + int cardTextMinSize() const; QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 56fe0bda4..4111a9cec 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -87,6 +87,7 @@ void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color) void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size) { fontSize = size > 0 ? size : 1; + hasExplicitFontSize = true; update(); } @@ -179,10 +180,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) } // Calculate the optimal font size. The family comes from the CardText - // theme role; the fontSize member stays the starting size and the - // shrink-to-fit logic below is unchanged. + // theme role; the start size is the explicit size when set (e.g. deck + // preview 24), else the theme max, shrinking down to the theme min floor. QFont font = themeManager->appFont(AppFont::CardText); - int optimalFontSize = fontSize; // Start with the user-defined font size + const int themeMin = std::max(1, themeManager->cardTextMinSize()); + const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize())); + int optimalFontSize = hasExplicitFontSize ? std::max(1, fontSize) : themeMax; const QFontMetrics baseMetrics(font); int textWidth = pixmapRect.width(); @@ -200,7 +203,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) } --optimalFontSize; - } while (optimalFontSize > 1); + } while (optimalFontSize > themeMin); // Apply the calculated font size painter.setFont(font); @@ -227,7 +230,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height(); // Adjust font size if the total text height exceeds the pixmap height - while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) { + while (totalTextHeight > pixmapRect.height() && optimalFontSize > themeMin) { --optimalFontSize; font.setPointSize(optimalFontSize); painter.setFont(font); @@ -297,7 +300,9 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const // Get the font metrics for the overlay text QFont font = themeManager->appFont(AppFont::CardText); - font.setPointSize(fontSize); + const int themeMin = std::max(1, themeManager->cardTextMinSize()); + const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize())); + font.setPointSize(hasExplicitFontSize ? std::max(1, fontSize) : themeMax); const QFontMetrics fontMetrics(font); // Calculate the height required for the text diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index 41019f543..c4a967498 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -60,6 +60,7 @@ private: QColor textColor; QColor outlineColor; int fontSize; + bool hasExplicitFontSize = false; Qt::Alignment textAlignment; bool highlighted; }; diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp index de5f67b32..d2214a5af 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -283,20 +283,11 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(), &CardsDisplaySettings::setRoundCardCorners); - connect(&maxFontSizeForCardsEdit, qOverload(&QSpinBox::valueChanged), &settings.appearance(), - &AppearanceSettings::setMaxFontSize); - maxFontSizeForCardsEdit.setValue(settings.appearance().getMaxFontSize()); - maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit); - maxFontSizeForCardsEdit.setMinimum(9); - maxFontSizeForCardsEdit.setMaximum(100); - auto *cardsGrid = new QGridLayout; cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); - cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1); - cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1); cardsGroupBox = new QGroupBox; cardsGroupBox->setLayout(cardsGrid); @@ -657,7 +648,6 @@ void AppearanceSettingsPage::retranslateUi() autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); cardScalingCheckBox.setText(tr("Scale cards on mouse over")); roundCardCornersCheckBox.setText(tr("Use rounded card corners")); - maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:")); cardLayoutGroupBox->setTitle(tr("Card layout")); verticalCardOverlapPercentLabel.setText( diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h index f919a2a2f..8d008dfeb 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h @@ -71,8 +71,6 @@ private: QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox cardScalingCheckBox; QCheckBox roundCardCornersCheckBox; - QLabel maxFontSizeForCardsLabel; - QSpinBox maxFontSizeForCardsEdit; QLabel verticalCardOverlapPercentLabel; QSpinBox verticalCardOverlapPercentBox; diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp index 14969dae6..4de87991c 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp @@ -33,16 +33,6 @@ void AppearanceSettings::setStyleUserList(bool _styleUserList) emit styleUserListChanged(); } -int AppearanceSettings::getMaxFontSize() const -{ - return getValue("maxFontSize", QString(), QString(), 12).toInt(); -} - -void AppearanceSettings::setMaxFontSize(int _max) -{ - setValue(_max, "maxFontSize"); -} - QString AppearanceSettings::getHomeTabBackgroundSource() const { return getValue("homeTabBackgroundSource", QString(), QString(), "themed").toString(); diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h index 9d15caddd..1e0823ae1 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h @@ -24,8 +24,6 @@ public: void setThemeName(const QString &_themeName); [[nodiscard]] bool getStyleUserList() const; void setStyleUserList(bool _styleUserList); - [[nodiscard]] int getMaxFontSize() const; - void setMaxFontSize(int _max); [[nodiscard]] QString getHomeTabBackgroundSource() const; void setHomeTabBackgroundSource(const QString &_backgroundSource); [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const; diff --git a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp index d9652cde3..3be3c0bf2 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -395,6 +396,10 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl { const QMap appearanceKeyMap = { {"theme/name", "appearance/themeName"}, + // Retired into the theme's CardText size, but still read here so users + // who have not migrated past global.ini keep their card text size: + // SettingsCache runs migrateSettingsFromGlobalIni() before + // migrateAppearanceMaxFontSizeToTheme(). {"game/maxfontsize", "appearance/maxFontSize"}, {"home/background", "appearance/homeTabBackgroundSource"}, {"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"}, @@ -424,6 +429,181 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl } } +static QString themesPathForSettings(const QString &settingsPath) +{ + QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat); + const QString configured = pathsIni.value("paths/themes").toString().trimmed(); + if (!configured.isEmpty()) { + return configured; + } + // Default layout: /themes/ sibling of /settings/. + return QDir::cleanPath(QDir(settingsPath).absoluteFilePath("../themes")); +} + +static bool fontsTomlHasExplicitCardTextMax(const QString &filePath) +{ + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + QString currentSection; + QTextStream in(&f); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + continue; + } + if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) { + continue; + } + int eq = line.indexOf('='); + if (eq < 0) { + continue; + } + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + for (int i = 1; i < value.size(); ++i) { + if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) { + value = value.left(i).trimmed(); + break; + } + } + if (value.size() >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) { + value = value.mid(1, value.size() - 2).trimmed(); + } + if (key.startsWith("AppFont::")) { + key = key.mid(QStringLiteral("AppFont::").size()); + } + if (key.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) { + return true; + } + if (key.compare("CardText", Qt::CaseInsensitive) != 0) { + continue; + } + int comma = value.lastIndexOf(','); + if (comma < 0) { + continue; + } + QString family = value.left(comma).trimmed(); + if (family.size() >= 2 && + ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) { + family = family.mid(1, family.size() - 2).trimmed(); + } + bool ok = false; + int pointSize = value.mid(comma + 1).trimmed().toInt(&ok); + if (!ok || family.isEmpty() || pointSize <= 0) { + continue; + } + return true; + } + return false; +} + +static bool seedCardTextMaxIntoThemeDir(const QString &themeDirPath, int maxSize) +{ + if (themeDirPath.isEmpty() || maxSize <= 0) { + return false; + } + QDir dir(themeDirPath); + if (!dir.exists()) { + return false; + } + const QString filePath = dir.absoluteFilePath("fonts.toml"); + if (fontsTomlHasExplicitCardTextMax(filePath)) { + return false; + } + + QString existing; + if (QFile::exists(filePath)) { + QFile in(filePath); + if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + existing = QString::fromUtf8(in.readAll()); + } + + // Size-only key: seeding the CardText family here would pin it to Serif + // for the whole board (names, player labels, the completer), which is not + // what the retired maxFontSize setting meant. Let the family keep its + // existing resolution and only constrain the size. + const QString entry = QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(maxSize); + + QString out; + if (existing.isEmpty()) { + out = "[Fonts]\n" + entry + "\n"; + } else if (existing.contains("[Fonts]", Qt::CaseInsensitive)) { + out = existing; + if (!out.endsWith('\n')) { + out += '\n'; + } + out += entry; + } else { + out = existing; + if (!out.endsWith('\n')) { + out += '\n'; + } + out += "\n[Fonts]\n" + entry + "\n"; + } + + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + QTextStream stream(&f); + stream << out; + return true; +} + +static bool appearanceMaxFontSizeMigrationPending(const QString &settingsPath) +{ + if (!QFile::exists(settingsPath + "appearance.ini")) { + return false; + } + QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat); + return appearanceIni.contains("appearance/maxFontSize"); +} + +bool SettingsMigration::migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath) +{ + if (!appearanceMaxFontSizeMigrationPending(settingsPath)) { + return false; + } + + QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat); + // Retired default was 12 in AppearanceSettings::getMaxFontSize; only + // non-default values are seeded, default users rely on the theme fallback. + const int legacy = appearanceIni.value("appearance/maxFontSize").toInt(); + if (legacy != 12 && legacy > 0) { + const QString themesPath = themesPathForSettings(settingsPath); + QDir themesDir(themesPath); + const QStringList themes = themesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + for (const QString &theme : themes) { + seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(theme), legacy); + } + + // Shipped themes (Fabric, Leather, ...) resolve to the system theme + // directory unless the user has already saved something for them, and + // System/Fusion only ever exist in the user directory. Seeding existing + // folders alone would therefore drop the retired size for every user on + // a stock theme, so create the active theme's user directory the same + // way ThemeManager::writableThemeDir() does for palette and font saves. + const QString activeTheme = appearanceIni.value("appearance/themeName").toString().trimmed(); + if (!activeTheme.isEmpty() && !themesDir.exists(activeTheme) && themesDir.mkpath(activeTheme)) { + seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(activeTheme), legacy); + } + } + + appearanceIni.remove("appearance/maxFontSize"); + appearanceIni.sync(); + return true; +} + static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni) { const QMap networkKeyMap = { @@ -821,6 +1001,10 @@ bool SettingsMigration::isMigrationPending(const QString &settingsPath) return true; } + if (appearanceMaxFontSizeMigrationPending(settingsPath)) { + return true; + } + QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat); if (personalIni.value("migration/legacy_complete", false).toBool()) { return false; diff --git a/libcockatrice_settings/libcockatrice/settings/settings_migration.h b/libcockatrice_settings/libcockatrice/settings/settings_migration.h index 0a706af98..7616cb83f 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_migration.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_migration.h @@ -18,6 +18,15 @@ public: */ static bool migrateGameFiltersToCamelCase(const QString &settingsPath); + /** + * Moves a non-default appearance/maxFontSize value into each user-writable theme + * directory lacking an explicit CardText max (creating/merging fonts.toml, never + * overwriting an explicit max), then deletes the legacy key. Default (12) or absent + * values only delete the key and create no files, since the ThemeManager fallback + * already covers them. Idempotent, safe to run on every startup. + */ + static bool migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath); + /** * Returns true when at least one of the two migrations would still do work, i.e. when a * backup of the settings directory is worth taking before they run. diff --git a/tests/font_config_test.cpp b/tests/font_config_test.cpp index c8b1fb10d..93c9cefb6 100644 --- a/tests/font_config_test.cpp +++ b/tests/font_config_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace { @@ -186,6 +187,154 @@ TEST(FontConfigTest, InequalityComparesRoles) EXPECT_NE(left, right); } +TEST(FontConfigTest, CardTextMinRoundTrip) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14); + cfg.cardTextMinSize = 3; + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + EXPECT_EQ(loaded.cardTextMinSize, 3); +} + +TEST(FontConfigTest, CardTextMinDefaultIsOmitted) +{ + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + EXPECT_EQ(cfg.cardTextMinSize, 1); + + const QString toml = cfg.toToml(); + EXPECT_TRUE(toml.contains(QStringLiteral("CardText"))); + EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMinSize"))); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", toml); + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1); +} + +TEST(FontConfigTest, MalformedMinIsSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", + "[Fonts]\n" + "CardText = \"Noto Serif, 14\"\n" + "CardTextMinSize = big\n"); + + // Each malformed value parses independently, so rewrite the file per case. + const QStringList badValues = {"big", "0", "-3", "", "Inter, 10"}; + for (const QString &bad : badValues) { + const QString badPath = writeTempFile( + tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMinSize = %1\n").arg(bad)); + const FontConfig loaded = FontConfig::fromFile(badPath); + EXPECT_EQ(loaded.cardTextMinSize, 1) << "bad value: " << bad.toStdString(); + ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText)); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + } + + // The valid file still loads. + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1); +} + +TEST(FontConfigTest, CardTextMaxRoundTrip) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14); + cfg.cardTextMaxSize = 18; + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.cardTextMaxSize, 18); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); +} + +TEST(FontConfigTest, CardTextMaxSizeWithoutFamily) +{ + // The retired maxFontSize setting migrates to a size-only key, so the + // family keeps its existing resolution instead of being pinned to Serif. + FontConfig cfg; + cfg.cardTextMaxSize = 18; + ASSERT_TRUE(cfg.hasFonts()); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_TRUE(loaded.fonts.isEmpty()); + EXPECT_EQ(loaded.cardTextMaxSize, 18); + EXPECT_EQ(loaded, cfg); +} + +TEST(FontConfigTest, CardTextMaxDefaultIsOmitted) +{ + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + EXPECT_EQ(cfg.cardTextMaxSize, 0); + + const QString toml = cfg.toToml(); + EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMaxSize"))); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", toml); + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMaxSize, 0); +} + +TEST(FontConfigTest, MalformedMaxIsSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QStringList badValues = {"big", "0", "-3", "", "Inter, 18"}; + for (const QString &bad : badValues) { + const QString badPath = writeTempFile( + tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMaxSize = %1\n").arg(bad)); + const FontConfig loaded = FontConfig::fromFile(badPath); + EXPECT_EQ(loaded.cardTextMaxSize, 0) << "bad value: " << bad.toStdString(); + ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText)); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + } +} + +TEST(FontConfigTest, InequalityComparesMinSize) +{ + FontConfig left; + left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + left.cardTextMinSize = 1; + + FontConfig right = left; + right.cardTextMinSize = 2; + EXPECT_NE(left, right); +} + +TEST(FontConfigTest, InequalityComparesMaxSize) +{ + FontConfig left; + left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + left.cardTextMaxSize = 12; + + FontConfig right = left; + right.cardTextMaxSize = 18; + EXPECT_NE(left, right); +} + // Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and // the fallback then links the bare gtest library without gtest_main. int main(int argc, char **argv) diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index aea7f1d54..156e52695 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -461,19 +461,6 @@ TEST_F(SettingsDefaultsTest, Appearance_StyleUserList_Default) ASSERT_EQ(s.getStyleUserList(), true); } -TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_Default) -{ - AppearanceSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getMaxFontSize(), 12); -} - -TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_SetAndGet) -{ - AppearanceSettings s(settingsPath, nullptr); - s.setMaxFontSize(14); - ASSERT_EQ(s.getMaxFontSize(), 14); -} - TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundSource_Default) { AppearanceSettings s(settingsPath, nullptr); diff --git a/tests/settings/settings_migration_test.cpp b/tests/settings/settings_migration_test.cpp index 2c4dcf7a9..4fea3c085 100644 --- a/tests/settings/settings_migration_test.cpp +++ b/tests/settings/settings_migration_test.cpp @@ -984,6 +984,198 @@ TEST_F(SettingsMigrationTest, IsNotMigrationPendingWithoutLegacyData) ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath)); } +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_NonDefaultSeedsThemeAndDeletesKey) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + + const QString fontsPath = themesPath + "/MyTheme/fonts.toml"; + ASSERT_TRUE(QFile::exists(fontsPath)); + QFile f(fontsPath); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("18")); + // Size-only: the migration must not pin the family, or every CardText + // surface switches to Serif for users who never chose it. + EXPECT_FALSE(content.contains("Serif")); + + ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_SeedsActiveStockTheme) +{ + // A stock theme resolves to the system directory, so there is no user theme + // folder to seed. The migration has to create the active theme's folder, + // otherwise the retired size is deleted without landing anywhere. + const QString themesPath = tempDir.path() + "/themes"; + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/themeName", "Fabric"); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + + const QString fontsPath = themesPath + "/Fabric/fonts.toml"; + ASSERT_TRUE(QFile::exists(fontsPath)); + QFile f(fontsPath); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_KeepsExistingActiveTheme) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/Fabric")); + { + QFile f(themesPath + "/Fabric/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nCardText = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/themeName", "Fabric"); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/Fabric/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("Inter, 10")); + EXPECT_FALSE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DefaultDeletesKeyWithoutFiles) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 12); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + ASSERT_FALSE(QFile::exists(themesPath + "/MyTheme/fonts.toml")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DoesNotOverwriteExplicitMax) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nCardText = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("10")); + EXPECT_FALSE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_MergesWithOtherRoles) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nInterfaceDefault = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 20); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("InterfaceDefault")); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("20")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_AbsentKeyDoesNothing) +{ + ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); +} + +TEST_F(SettingsMigrationTest, IsMigrationPendingWhenMaxFontSizePresent) +{ + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath)); +} + } // namespace int main(int argc, char **argv)