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

This commit is contained in:
Lukas Brübach 2026-10-16 10:06:16 +02:00
parent 42895e92a3
commit a7ab30c071
18 changed files with 118 additions and 8 deletions

View file

@ -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()

View file

@ -20,6 +20,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()

View file

@ -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)

View file

@ -10,6 +10,7 @@
#include "../game/player/player_logic.h"
#include "../game/zones/card_zone_logic.h"
#include "../game/zones/view_zone_logic.h"
#include "../interface/theme_manager.h"
#include "animated_item.h"
#include "board/abstract_card_item.h"
#include "board/arrow_item.h"
@ -63,6 +64,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();
}

View file

@ -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);

View file

@ -659,8 +659,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();
}

View file

@ -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;

View file

@ -10,6 +10,16 @@
#include <libcockatrice/models/deck_list/deck_list_model.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
{
QModelIndex src = mapToSource(index);

View file

@ -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;
};

View file

@ -7,6 +7,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../pixel_map_generator.h"
#include "../interface/theme_manager.h"
#include "../main.h"
#include "../settings_page/abstract_settings_page.h"
#include "../settings_page/appearance_settings_page.h"
@ -189,6 +190,10 @@ void DlgSettings::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();

View file

@ -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<BarData> &newBars)

View file

@ -24,6 +24,10 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector<Segment> 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

View file

@ -514,6 +514,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;

View file

@ -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();
}

View file

@ -46,13 +46,16 @@ 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.
auto applyTitleFont = [this]() {
auto applyThemeFonts = [this]() {
QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault);
titleFont.setBold(true);
titleLabel->setFont(titleFont);
// 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();
};
applyTitleFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont);
applyThemeFonts();
connect(themeManager, &ThemeManager::fontsChanged, this, applyThemeFonts);
auto *headerLayout = new QHBoxLayout;
headerLayout->addWidget(titleLabel);

View file

@ -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"
@ -72,6 +73,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);

View file

@ -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

View file

@ -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);
}