[Fonts] Restore DeckList Serif default and live-update all role readers (#7541)

* [Fonts] Restore DeckList Serif default and live-update all role readers

* [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.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-11-04 07:57:55 +01:00 • committed by GitHub
parent 13e7a3baaf
commit a791389c6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 140 additions and 18 deletions

View file

@ -79,6 +79,18 @@ ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&ShortcutTreeView::refreshShortcuts); &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() void ShortcutTreeView::populateShortcutsModel()

View file

@ -41,6 +41,12 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent)
connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow);
connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow);
connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); 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() FilterTreeModel::~FilterTreeModel()

View file

@ -241,8 +241,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI
painter->setPen(QColor(Qt::white)); painter->setPen(QColor(Qt::white));
// Family comes from the DeckList theme role; pixel sizes/weights unchanged. // Family comes from the DeckList theme role; pixel sizes/weights unchanged.
QFont f = themeManager->appFont(AppFont::DeckList); QFont f = themeManager->deckListFont();
f.setStyleHint(QFont::Serif);
f.setPixelSize(24); f.setPixelSize(24);
f.setWeight(QFont::Bold); f.setWeight(QFont::Bold);
painter->setFont(f); painter->setFont(f);
@ -294,8 +293,7 @@ QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
int DeckViewCardContainer::getCardTypeTextWidth() const int DeckViewCardContainer::getCardTypeTextWidth() const
{ {
// Family comes from the DeckList theme role; pixel size/weight unchanged. // Family comes from the DeckList theme role; pixel size/weight unchanged.
QFont f = themeManager->appFont(AppFont::DeckList); QFont f = themeManager->deckListFont();
f.setStyleHint(QFont::Serif);
f.setPixelSize(16); f.setPixelSize(16);
f.setWeight(QFont::Bold); f.setWeight(QFont::Bold);
QFontMetrics fm(f); QFontMetrics fm(f);
@ -368,6 +366,15 @@ void DeckViewCardContainer::setWidth(qreal _width)
DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0) 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() DeckViewScene::~DeckViewScene()

View file

@ -1,6 +1,7 @@
#include "game_scene.h" #include "game_scene.h"
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../interface/theme_manager.h"
#include "animated_item.h" #include "animated_item.h"
#include "board/abstract_card_item.h" #include "board/abstract_card_item.h"
#include "board/arrow_item.h" #include "board/arrow_item.h"
@ -64,6 +65,14 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
addItem(phasesToolbar); addItem(phasesToolbar);
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this, connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this,
&GameScene::rearrange); &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(); rearrange();
} }

View file

@ -581,7 +581,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
QTextBlockFormat blockFormat; QTextBlockFormat blockFormat;
QTextCharFormat charFormat; QTextCharFormat charFormat;
// Family comes from the DeckList theme role; sizes/weights unchanged. // 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.setFontPointSize(11);
charFormat.setFontWeight(QFont::Bold); charFormat.setFontWeight(QFont::Bold);
cursor->insertBlock(blockFormat, charFormat); cursor->insertBlock(blockFormat, charFormat);
@ -595,7 +595,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
const AbstractDecklistCardNode *card = cards[i]; const AbstractDecklistCardNode *card = cards[i];
QTextCharFormat cellCharFormat; QTextCharFormat cellCharFormat;
cellCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); cellCharFormat.setFontFamilies({themeManager->deckListFont().family()});
cellCharFormat.setFontPointSize(9); cellCharFormat.setFontPointSize(9);
QTextTableCell cell = table->cellAt(i, 0); QTextTableCell cell = table->cellAt(i, 0);
@ -617,7 +617,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
QTextBlockFormat blockFormat; QTextBlockFormat blockFormat;
QTextCharFormat charFormat; QTextCharFormat charFormat;
charFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); charFormat.setFontFamilies({themeManager->deckListFont().family()});
charFormat.setFontPointSize(14); charFormat.setFontPointSize(14);
charFormat.setFontWeight(QFont::Bold); charFormat.setFontWeight(QFont::Bold);
@ -646,15 +646,17 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
QTextDocument doc; QTextDocument doc;
// Family comes from the DeckList theme role; the print sizes/weights below stay unchanged. // Family comes from the DeckList theme role; the print sizes/weights below stay unchanged.
QFont font = themeManager->appFont(AppFont::DeckList); // Print output is a one-shot snapshot and intentionally does not subscribe
font.setStyleHint(QFont::Serif); // to fontsChanged (there is no live view to repaint); reprinting picks up
// the current role font.
QFont font = themeManager->deckListFont();
doc.setDefaultFont(font); doc.setDefaultFont(font);
QTextCursor cursor(&doc); QTextCursor cursor(&doc);
QTextBlockFormat headerBlockFormat; QTextBlockFormat headerBlockFormat;
QTextCharFormat headerCharFormat; QTextCharFormat headerCharFormat;
headerCharFormat.setFontFamilies({themeManager->appFont(AppFont::DeckList).family()}); headerCharFormat.setFontFamilies({themeManager->deckListFont().family()});
headerCharFormat.setFontPointSize(16); headerCharFormat.setFontPointSize(16);
headerCharFormat.setFontWeight(QFont::Bold); headerCharFormat.setFontWeight(QFont::Bold);

View file

@ -660,10 +660,12 @@ QFont ThemeManager::appFont(AppFont::Role role) const
} }
// Role fallbacks preserve the pre-theme defaults when no fonts.toml is // 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 // present: an unset role resolves to the application font. Surfaces that
// identical to before. The report dialogs that used to hardcode monospace // hardcoded a different family ask for it explicitly: counter painters
// ask for it explicitly (report_utils::reportLogFont), and the counter // through cardTextFont(), the deck view headers and printed deck list
// painters that used to hardcode Serif ask for it through cardTextFont(). // through deckListFont(), the report dialogs through
// report_utils::reportLogFont(). Only the family basis is fixed there;
// sizes stay caller-side.
return qApp->font(); return qApp->font();
} }
@ -682,6 +684,16 @@ QFont ThemeManager::cardTextFont() const
return serifFont; 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 QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const
{ {
// No per-role scaling and no preview: an unset role resolves to the font // No per-role scaling and no preview: an unset role resolves to the font

View file

@ -150,14 +150,20 @@ public:
// when present, otherwise a palette-accent-derived fallback. // when present, otherwise a palette-accent-derived fallback.
QColor appColor(AppColor::Role role) const; QColor appColor(AppColor::Role role) const;
/** @brief Resolves an application font role: the theme's stored [Fonts] /** @brief Resolves an application font role: the theme's stored [Fonts]
* value when present, otherwise a per-role fallback (serif basis * value when present, otherwise the application font. Roles that
* for CardText, the application font for the remaining roles). */ * had a historical hardcoded family ask for it through
* cardTextFont() and deckListFont() instead. */
QFont appFont(AppFont::Role role) const; QFont appFont(AppFont::Role role) const;
/** @brief Whether the active theme defines this role in fonts.toml. */ /** @brief Whether the active theme defines this role in fonts.toml. */
bool hasAppFont(AppFont::Role role) const; bool hasAppFont(AppFont::Role role) const;
/** @brief The CardText role with its historical Serif fallback. Used by the /** @brief The CardText role with its historical Serif fallback. Used by the
* counter painters, which were Serif before the role existed. */ * counter painters, which were Serif before the role existed. */
QFont cardTextFont() const; 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, /** @brief Resolves an application font role against the startup font only,
* ignoring any previewed config in currentFonts. Editors that show * ignoring any previewed config in currentFonts. Editors that show
* "what this role resolves to" must use this, because appFont() * "what this role resolves to" must use this, because appFont()

View file

@ -10,6 +10,16 @@
#include <libcockatrice/models/deck_list/deck_list_model.h> #include <libcockatrice/models/deck_list/deck_list_model.h>
#include <qnamespace.h> #include <qnamespace.h>
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 QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
{ {
QModelIndex src = mapToSource(index); QModelIndex src = mapToSource(index);

View file

@ -8,7 +8,7 @@ class DeckListStyleProxy : public QIdentityProxyModel
{ {
Q_OBJECT Q_OBJECT
public: public:
using QIdentityProxyModel::QIdentityProxyModel; explicit DeckListStyleProxy(QObject *parent = nullptr);
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
}; };

View file

@ -7,6 +7,7 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../pixel_map_generator.h" #include "../../pixel_map_generator.h"
#include "../interface/theme_manager.h"
#include "../settings_page/abstract_settings_page.h" #include "../settings_page/abstract_settings_page.h"
#include "../settings_page/appearance_settings_page.h" #include "../settings_page/appearance_settings_page.h"
#include "../settings_page/deck_editor_settings_page.h" #include "../settings_page/deck_editor_settings_page.h"
@ -203,6 +204,10 @@ void SettingsView::setupUi()
searchDelegate->setPageIcons(icons); searchDelegate->setPageIcons(icons);
searchResultsView->viewport()->update(); 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 // Build search index after pages are created
buildSearchIndex(); buildSearchIndex();

View file

@ -21,6 +21,10 @@
BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent) BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent)
{ {
setMouseTracking(true); 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<BarData> &newBars) void BarChartWidget::setBars(const QVector<BarData> &newBars)

View file

@ -24,6 +24,10 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector<Segment> segments,
setMinimumWidth(36); setMinimumWidth(36);
setMaximumWidth(50); setMaximumWidth(50);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); 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 QSize SegmentedBarWidget::sizeHint() const

View file

@ -519,6 +519,19 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
userTree->viewport()->update(); userTree->viewport()->update();
userTree->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; QVBoxLayout *vbox = new QVBoxLayout;

View file

@ -101,6 +101,20 @@ TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_clien
central->setLayout(mainLayout); central->setLayout(mainLayout);
setCentralWidget(central); 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(); retranslateUi();
} }

View file

@ -51,7 +51,12 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c
titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)));
}; };
applyTitleFont(); 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; auto *headerLayout = new QHBoxLayout;
headerLayout->addWidget(titleLabel); headerLayout->addWidget(titleLabel);

View file

@ -1,5 +1,6 @@
#include "card_completer_styler.h" #include "card_completer_styler.h"
#include "../../interface/theme_manager.h"
#include "../cards/card_info_picture_enlarged_widget.h" #include "../cards/card_info_picture_enlarged_widget.h"
#include "card_completer_delegate.h" #include "card_completer_delegate.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
@ -73,6 +74,10 @@ CardCompleterStyler::CardCompleterStyler(QCompleter *completer, QObject *parent)
popup->viewport()->setMouseTracking(true); 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->installEventFilter(this);
popup->viewport()->installEventFilter(this); popup->viewport()->installEventFilter(this);

View file

@ -28,6 +28,10 @@ DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QSt
// Adjust widget size // Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); 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 QSize DeckPreviewTagAdditionWidget::sizeHint() const

View file

@ -43,6 +43,10 @@ DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const
// Adjust widget size // Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); 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 close button to the remove signal
// connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed); // connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed);
} }