Compare commits

...

15 commits

Author SHA1 Message Date
Lukas Brübach
f47813dfe4 [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.
2026-11-01 17:34:14 +01:00
Lukas Brübach
5a5671e047 [Fonts] Polish font editor accessibility and note text 2026-11-01 17:25:03 +01:00
Lukas Brübach
86fbf5032e [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.
2026-11-01 17:23:51 +01:00
Lukas Brübach
239642b8a0 [Fonts] Restore DeckList Serif default and live-update all role readers 2026-11-01 17:18:21 +01:00
Lukas Brübach
64d7e28192 [Fonts] Fix font preview leaks and reuse default-font loader 2026-11-01 17:10:05 +01:00
Lukas Brübach
4b616a17e8 [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.
2026-11-01 17:03:17 +01:00
Lukas Brübach
f6b8edc50c [Fonts] Drop unused lambda captures in font-refresh handlers 2026-11-01 14:12:57 +01:00
Lukas Brübach
b5250b440b [Fonts] Migrate DeckList and InterfaceDefault surfaces to theme roles 2026-11-01 14:12:57 +01:00
Lukas Brübach
1407a2e142 [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.
2026-11-01 14:12:35 +01:00
Lukas Brübach
ee2e0a9fa0 [Fonts] Migrate CardText surfaces to theme font role 2026-11-01 14:06:33 +01:00
Lukas Brübach
145f7adf73 [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.
2026-11-01 14:06:05 +01:00
Lukas Brübach
2a3a31c24c [Fonts] Migrate ChatLog surfaces to theme font role 2026-11-01 13:59:37 +01:00
Lukas Brübach
ecc3ecd4ea [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.
2026-11-01 13:59:16 +01:00
Lukas Brübach
c0aeaf72e8 [Fonts] Add per-purpose font section to palette editor 2026-11-01 13:53:56 +01:00
Lukas Brübach
c63a359c3d [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.
2026-11-01 13:53:21 +01:00
95 changed files with 1668 additions and 217 deletions

View file

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

View file

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

View file

@ -1,5 +1,6 @@
#include "shortcut_treeview.h"
#include "../../interface/theme_manager.h"
#include "cache_settings.h"
#include "shortcuts_settings.h"
@ -78,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()
@ -93,7 +106,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);

View file

@ -1,5 +1,7 @@
#include "filter_tree_model.h"
#include "../interface/theme_manager.h"
#include <QFlags>
#include <QFont>
#include <QString>
@ -18,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()
@ -196,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;
}

View file

@ -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"
@ -35,7 +36,6 @@
#include <QtGlobal>
#include <algorithm>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/debug_settings.h>
#include <qnamespace.h>
@ -135,8 +135,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());
@ -146,7 +146,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);

View file

@ -1,5 +1,7 @@
#include "abstract_graphics_item.h"
#include "../../interface/theme_manager.h"
#include <QBrush>
#include <QFont>
#include <QFontMetrics>
@ -20,7 +22,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
painter->save();
QString numStr = QString::number(number);
QFont font("Serif");
QFont font = themeManager->cardTextFont();
font.setPixelSize(fontSize);
font.setWeight(QFont::Bold);

View file

@ -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 <QFont>
@ -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->cardTextFont();
f.setPixelSize(qMax((int)(radius * scaleFactor), 10));
f.setWeight(QFont::Bold);
painter->setPen(Qt::black);

View file

@ -240,8 +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");
f.setStyleHint(QFont::Serif);
// Family comes from the DeckList theme role; pixel sizes/weights unchanged.
QFont f = themeManager->deckListFont();
f.setPixelSize(24);
f.setWeight(QFont::Bold);
painter->setFont(f);
@ -292,8 +292,8 @@ QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
int DeckViewCardContainer::getCardTypeTextWidth() const
{
QFont f("Serif");
f.setStyleHint(QFont::Serif);
// Family comes from the DeckList theme role; pixel size/weight unchanged.
QFont f = themeManager->deckListFont();
f.setPixelSize(16);
f.setWeight(QFont::Bold);
QFontMetrics fm(f);
@ -366,6 +366,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()

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

@ -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 <QAction>
#include <QBrush>
#include <QColor>
#include <QEvent>
#include <QFont>
#include <QGraphicsScene>
#include <QGridLayout>
#include <QLabel>
@ -24,6 +26,21 @@
#include <libcockatrice/utility/qt_utils.h>
#include <qnamespace.h>
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
@ -75,15 +92,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 +116,28 @@ 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()
{
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<QLabel *>(tallyLayout->itemAt(i)->widget())) {
// 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)
@ -219,9 +257,8 @@ QSize GameView::rebuildTallyLabels(const QList<TallyRow> &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 +268,13 @@ QSize GameView::rebuildTallyLabels(const QList<TallyRow> &entries)
for (const TallyRow &entry : entries) {
auto *nameLabel = new QLabel(entry.name, tallyContainer);
nameLabel->setStyleSheet(nameStyle);
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);
countLabel->setFont(deckOverlayFont(14, true));
countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
tallyLayout->addWidget(countLabel, row, 1);

View file

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

View file

@ -4,6 +4,7 @@
#include "../../game/player/player_info.h"
#include "../../game/player/player_logic.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->cardTextFont();
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);

View file

@ -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->deckListFont().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->deckListFont().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->deckListFont().family()});
charFormat.setFontPointSize(14);
charFormat.setFontWeight(QFont::Bold);
@ -640,14 +645,18 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
{
QTextDocument doc;
QFont font("Serif");
font.setStyleHint(QFont::Serif);
// 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->deckListFont();
doc.setDefaultFont(font);
QTextCursor cursor(&doc);
QTextBlockFormat headerBlockFormat;
QTextCharFormat headerCharFormat;
headerCharFormat.setFontFamilies({themeManager->deckListFont().family()});
headerCharFormat.setFontPointSize(16);
headerCharFormat.setFontWeight(QFont::Bold);

View file

@ -1,5 +1,7 @@
#include "color_button.h"
#include "../theme_manager.h"
#include <QBrush>
#include <QColorDialog>
#include <QFont>
@ -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());

View file

@ -0,0 +1,179 @@
#include "font_grid_widget.h"
#include "../theme_manager.h"
#include <QApplication>
#include <QFont>
#include <QFontComboBox>
#include <QGridLayout>
#include <QLabel>
#include <QMetaEnum>
#include <QSpinBox>
#include <QtGlobal>
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 FALLBACK_FONT_SIZE = 10;
static const QMap<AppFont::Role, const char *> 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);
grid->setColumnStretch(3, 0);
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(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(); });
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);
connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, sizeSpin, nullptr};
}
retranslateUi();
}
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<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
const FontRow &row = fontRows[role];
// Roles absent from the file resolve to the startup font, not to the
// live preview: appFont() would return the previewed config, so Reset
// and a first load would both show the edited font instead of the
// theme's real default.
const QFont font = cfg.fonts.value(role, themeManager->defaultAppFont(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));
if (role == AppFont::CardText && row.minSpin != nullptr) {
int minSize = cfg.cardTextMinSize > 0 ? cfg.cardTextMinSize : themeManager->cardTextMinSize();
row.minSpin->setValue(qBound(CARD_TEXT_MIN_SIZE, minSize, MAX_FONT_SIZE));
}
}
blockSignals(false);
}
FontConfig FontGridWidget::currentFontConfig() const
{
FontConfig cfg;
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(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;
if (role == AppFont::CardText && row.minSpin != nullptr) {
cfg.cardTextMinSize = row.minSpin->value();
}
}
return cfg;
}
void FontGridWidget::retranslateUi()
{
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(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.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"));
}
}

View file

@ -0,0 +1,44 @@
#ifndef COCKATRICE_FONT_GRID_WIDGET_H
#define COCKATRICE_FONT_GRID_WIDGET_H
#include "../theme_config.h"
#include <QMap>
#include <QWidget>
#include <qtmetamacros.h>
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;
QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor
};
QMap<AppFont::Role, FontRow> fontRows;
};
#endif // COCKATRICE_FONT_GRID_WIDGET_H

View file

@ -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"
@ -40,6 +41,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";
@ -126,6 +128,33 @@ 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);
fontNoteLabel->setWordWrap(true);
fontSectionLayout->addWidget(fontNoteLabel);
fontGrid = new FontGridWidget(fontSection);
fontSectionLayout->addWidget(fontGrid);
fontSection->setVisible(false);
root->addWidget(fontSection);
// Footer
root->addWidget(makeSeparator());
footer = new QWidget;
@ -158,6 +187,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] {
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);
@ -167,15 +200,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()
@ -190,12 +240,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 (shared by Light and Dark schemes)").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);
@ -229,6 +283,15 @@ void PaletteEditorDialog::loadSchemes()
}
}
void PaletteEditorDialog::loadFonts()
{
savedFontConfig = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName);
fontGrid->blockSignals(true);
fontGrid->loadFonts(savedFontConfig);
fontGrid->blockSignals(false);
fontsDirty = false;
}
void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme)
{
QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight);
@ -259,6 +322,9 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi
void PaletteEditorDialog::onApply()
{
themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme);
if (fontsDirty) {
themeManager->previewFonts(fontGrid->currentFontConfig());
}
}
void PaletteEditorDialog::onSave()
@ -309,14 +375,38 @@ void PaletteEditorDialog::onSave()
savedConfig[it.key()] = it.value();
}
// Persist the shared per-theme fonts when they changed.
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;
}
fontsDirty = false;
}
themeManager->reloadCurrentTheme();
accept();
}
void PaletteEditorDialog::onReset()
{
const bool hadFontPreview = fontsDirty;
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);
fontsDirty = false;
if (hadFontPreview) {
themeManager->previewFonts(savedFontConfig);
}
seedAccentFromScheme(loadedScheme);
}
@ -351,6 +441,27 @@ 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);
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)

View file

@ -15,9 +15,11 @@ class QComboBox;
class QDialogButtonBox;
class QPushButton;
class PaletteGridWidget;
class FontGridWidget;
class QuickSetupPanel;
class QFrame;
class QWidget;
class QCloseEvent;
class PaletteEditorDialog : public QDialog
{
@ -25,6 +27,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();
@ -38,8 +41,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 +54,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,10 +78,18 @@ private:
QMap<QString, PaletteConfig> workingConfig;
QMap<QString, PaletteConfig> 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;
// 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;
protected:
void changeEvent(QEvent *e) override;
};
#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H
#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H

View file

@ -77,6 +77,18 @@ PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
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)
@ -102,7 +114,8 @@ void PaletteGridWidget::buildGrid(QWidget *host)
auto *label = new QLabel(groupHeaders[col], host);
label->setAlignment(Qt::AlignCenter);
label->setToolTip(groupTips[col]);
QFont f = label->font();
// Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont f = themeManager->appFont(AppFont::InterfaceDefault);
f.setBold(true);
label->setFont(f);
label->setAutoFillBackground(true);
@ -149,7 +162,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"));
QFont appHeaderFont = appHeader->font();
QFont appHeaderFont = themeManager->appFont(AppFont::InterfaceDefault);
appHeaderFont.setBold(true);
appHeader->setFont(appHeaderFont);
appHeader->setAutoFillBackground(true);

View file

@ -34,6 +34,10 @@ private:
void changeEvent(QEvent *e);
void refreshChromePalettes();
private slots:
void applyInterfaceDefaultFont();
private:
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
QMap<AppColor::Role, ColorButton *> appColorButtons;
QScrollArea *scroll;

View file

@ -1,5 +1,6 @@
#include "quick_setup_panel.h"
#include "../theme_manager.h"
#include "color_button.h"
#include <QApplication>
@ -28,10 +29,15 @@ QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent)
labelLow = new QLabel(this);
labelHigh = new QLabel(this);
QFont small = labelLow->font();
small.setPointSizeF(small.pointSizeF() * 0.82);
labelLow->setFont(small);
labelHigh->setFont(small);
// Family comes from the InterfaceDefault theme role; the *0.82 size stays caller-side.
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);

View file

@ -308,7 +308,7 @@ QPalette PaletteConfig::apply(QPalette base) const
bool FontConfig::hasFonts() const
{
return !fonts.isEmpty();
return !fonts.isEmpty() || cardTextMinSize != 1;
}
QString FontConfig::toToml() const
@ -334,6 +334,10 @@ 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 (entries.isEmpty()) {
return {};
}
@ -401,6 +405,22 @@ FontConfig FontConfig::fromFile(const QString &filePath)
value = value.mid(1, value.size() - 2).trimmed();
}
// CardText floor: bare integer, e.g. CardTextMinSize = 2. Tolerate
// quoting like the family entries; skip invalid lines.
QString minKey = key;
if (minKey.startsWith("AppFont::")) {
minKey = minKey.mid(QStringLiteral("AppFont::").size());
}
if (minKey.compare("CardTextMinSize", Qt::CaseInsensitive) == 0) {
bool ok = false;
int minSize = value.trimmed().toInt(&ok);
if (!ok || minSize <= 0) {
continue;
}
cfg.cardTextMinSize = minSize;
continue;
}
int comma = value.lastIndexOf(',');
if (comma < 0) {

View file

@ -84,15 +84,20 @@ 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<AppFont::Role, QFont> fonts;
int cardTextMinSize = 1;
bool operator==(const FontConfig &rhs) const
{
return fonts == rhs.fonts;
return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize;
}
bool operator!=(const FontConfig &rhs) const
{

View file

@ -12,6 +12,7 @@
#include <QFile>
#include <QFileInfo>
#include <QFlags>
#include <QFontDatabase>
#include <QGuiApplication>
#include <QIODevice>
#include <QLatin1Char>
@ -127,8 +128,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] {
@ -448,6 +451,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()) {
@ -520,18 +535,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();
@ -644,11 +656,81 @@ 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) and
// every other role resolves to the app font. The surfaces that hardcoded a
// different family ask for it explicitly: counter painters through
// cardTextFont(), the deck view headers and printed deck list through
// deckListFont(). Only the family basis is fixed there; sizes stay
// caller-side.
if (role == AppFont::ChatLog) {
// ChatView never set a font before the theme role existed, so an unset
// ChatLog must keep resolving to the application font. The report
// dialogs that used to hardcode monospace ask for it explicitly
// (report_utils::reportLogFont) instead of through this fallback.
return qApp->font();
}
// No per-role scaling: an unset role resolves to the application font, so
// with no fonts.toml present the result is identical to before. The counter
// painters that used to hardcode Serif ask for it through cardTextFont(),
// and the two deck-list sites that did through deckListFont().
return qApp->font();
}
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);
serifFont.setPointSize(12);
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
// captured at startup, which is what the theme falls back to once the
// editor closes.
return defaultFont;
}
int ThemeManager::cardTextMaxSize() const
{
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();
@ -701,20 +783,15 @@ 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()) {
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();

View file

@ -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<QBrush, Role::MaxRole + 1> brushes;
QStringMap availableThemes;
@ -128,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-<scheme>.toml and updates the
* theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave
* and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */
@ -143,8 +150,29 @@ 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 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;
/** @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()
* reflects a live preview while the editor is open. */
QFont defaultAppFont(AppFont::Role role) const;
/** @brief CardText max size: stored 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);

View file

@ -1,5 +1,6 @@
#include "art_crop_attribution.h"
#include "../../theme_manager.h"
#include "libcockatrice/card/printing/printing_info.h"
#include <QColor>
@ -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);

View file

@ -1,5 +1,7 @@
#include "card_info_picture_with_text_overlay_widget.h"
#include "../../theme_manager.h"
#include <QBrush>
#include <QChar>
#include <QFont>
@ -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);
}
/**
@ -84,6 +87,7 @@ void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
{
fontSize = size > 0 ? size : 1;
hasExplicitFontSize = true;
update();
}
@ -97,6 +101,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,9 +179,13 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
return;
}
// Calculate the optimal font size
QFont font = painter.font();
int optimalFontSize = fontSize; // Start with the user-defined font size
// Calculate the optimal font size. The family comes from the CardText
// 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);
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();
@ -188,7 +203,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
}
--optimalFontSize;
} while (optimalFontSize > 1);
} while (optimalFontSize > themeMin);
// Apply the calculated font size
painter.setFont(font);
@ -215,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);
@ -284,8 +299,10 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size();
// Get the font metrics for the overlay text
QFont font;
font.setPointSize(fontSize);
QFont font = themeManager->appFont(AppFont::CardText);
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

View file

@ -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,
@ -57,6 +60,7 @@ private:
QColor textColor;
QColor outlineColor;
int fontSize;
bool hasExplicitFontSize = false;
Qt::Alignment textAlignment;
bool highlighted;
};

View file

@ -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 <QDropEvent>
#include <QEvent>
#include <QFlags>
#include <QFont>
#include <QFrame>
#include <QHBoxLayout>
#include <QMimeData>
@ -98,9 +100,17 @@ 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.
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); }")

View file

@ -1,5 +1,7 @@
#include "deck_list_style_proxy.h"
#include "../../theme_manager.h"
#include <QBrush>
#include <QColor>
#include <QFont>
@ -8,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);
@ -20,7 +32,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;
}

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

@ -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"
@ -7,7 +8,6 @@
#include <QAbstractItemView>
#include <QFont>
#include <QFontDatabase>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
@ -70,13 +70,8 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent)
auto *chatGroup = new QGroupBox(tr("Chat Log Context"));
chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true);
QFont monoFont("monospace");
monoFont.setStyleHint(QFont::Monospace);
const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (systemPointSize > 0) {
monoFont.setPointSize(systemPointSize);
}
chatLogEdit->setFont(monoFont);
applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &DlgMyReports::applyChatLogFont);
auto *chatLayout = new QVBoxLayout(chatGroup);
chatLayout->setContentsMargins(4, 4, 4, 4);
chatLayout->addWidget(chatLogEdit);
@ -129,6 +124,11 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent)
refreshList();
}
void DlgMyReports::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void DlgMyReports::refreshList()
{
selectedReportIdBeforeRefresh = selectedReportId;

View file

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

View file

@ -1,12 +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 <QComboBox>
#include <QDialogButtonBox>
#include <QFont>
#include <QFontDatabase>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
@ -92,13 +93,8 @@ DlgReportUser::DlgReportUser(AbstractClient *_client,
auto *chatLayout = new QVBoxLayout(chatGroup);
chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true);
QFont monoFont("monospace");
monoFont.setStyleHint(QFont::Monospace);
const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (systemPointSize > 0) {
monoFont.setPointSize(systemPointSize);
}
chatLogEdit->setFont(monoFont);
applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &DlgReportUser::applyChatLogFont);
if (!_autoChatLog.isEmpty()) {
chatLogEdit->setPlainText(_autoChatLog);
} else {
@ -127,6 +123,11 @@ DlgReportUser::DlgReportUser(AbstractClient *_client,
layout->addWidget(buttonBox);
}
void DlgReportUser::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void DlgReportUser::actSubmit()
{
const QString description = descriptionEdit->toPlainText().trimmed();

View file

@ -27,6 +27,7 @@ public:
private slots:
void actSubmit();
void reportResponse(const Response &response);
void applyChatLogFont();
private:
static QString categoryLabel(const QString &key);

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

@ -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 <QAbstractButton>
#include <QDateTime>
@ -34,10 +35,15 @@ 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 = titleLabel->font();
titleFont.setBold(true);
titleFont.setPointSize(titleFont.pointSize() + 2);
titleLabel->setFont(titleFont);
// Family comes from the InterfaceDefault theme role; the +2 size and bold stay caller-side.
auto applyTitleFont = [titleLabel]() {
QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault);
titleFont.setBold(true);
titleFont.setPointSize(titleFont.pointSize() + 2);
titleLabel->setFont(titleFont);
};
applyTitleFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont);
mainLayout->addWidget(titleLabel);
if (!serverText.isEmpty()) {

View file

@ -1,9 +1,11 @@
#include "banner_widget.h"
#include "../../../pixel_map_generator.h"
#include "../../../theme_manager.h"
#include <QBrush>
#include <QColor>
#include <QFont>
#include <QHBoxLayout>
#include <QLinearGradient>
#include <QPainter>
@ -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.setPixelSize(24);
bannerFont.setWeight(QFont::Bold);
bannerLabel->setFont(bannerFont);
}
void BannerWidget::setClickable(bool _clickable)
{
clickable = _clickable;

View file

@ -48,6 +48,7 @@ signals:
private slots:
void toggleBuddyVisibility() const;
void updateDropdownIconState() const;
void applyInterfaceDefaultFont();
};
#endif // BANNER_WIDGET_H

View file

@ -1,5 +1,7 @@
#include "bar_chart_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush>
#include <QFlags>
#include <QFont>
@ -19,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)
@ -146,7 +152,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);

View file

@ -1,5 +1,7 @@
#include "segmented_bar_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush>
#include <QFont>
#include <QLinearGradient>
@ -22,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
@ -95,7 +101,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);

View file

@ -1,5 +1,7 @@
#include "dynamic_font_size_label.h"
#include "../../interface/theme_manager.h"
#include <QFont>
#include <QFontMetricsF>
#include <QObject>
@ -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();

View file

@ -1,5 +1,6 @@
#include "dynamic_font_size_push_button.h"
#include "../../interface/theme_manager.h"
#include "dynamic_font_size_label.h"
#include <QBrush>
@ -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

View file

@ -1,6 +1,9 @@
#include "home_styled_button.h"
#include "../../theme_manager.h"
#include <QBrush>
#include <QFont>
#include <QFontMetrics>
#include <QPainter>
#include <QPainterPath>
@ -21,6 +24,18 @@ HomeStyledButton::HomeStyledButton(const QString &text, QPair<QColor, QColor> _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.setPixelSize(34);
buttonFont.setWeight(QFont::Bold);
setFont(buttonFont);
}
void HomeStyledButton::updateStylesheet(const QPair<QColor, QColor> &colors)
@ -50,9 +65,6 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair<QColor, QColor> &
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;

View file

@ -24,6 +24,9 @@ public:
public slots:
void paintEvent(QPaintEvent *event) override;
private slots:
void applyInterfaceDefaultFont();
private:
QPair<QColor, QColor> gradientColors;
};

View file

@ -263,7 +263,6 @@ QGroupBox *HomeWidget::createButtons()
QGroupBox *box = new QGroupBox(this);
box->setStyleSheet(R"(
QGroupBox {
font-size: 20px;
color: white; /* Title text color */
background: transparent;
}
@ -274,6 +273,14 @@ QGroupBox *HomeWidget::createButtons()
subcontrol-position: top center; /* or top left / right */
}
)");
// Family comes from the InterfaceDefault theme role; the 20px size stays caller-side.
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);
@ -438,7 +445,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);
@ -451,7 +460,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);

View file

@ -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"
@ -31,10 +32,8 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent)
bannerHost = new BannerHost(this);
titleLabel = new QLabel(this);
QFont titleFont = titleLabel->font();
titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4);
titleFont.setBold(true);
titleLabel->setFont(titleFont);
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &FirstRunWizard::applyInterfaceDefaultFont);
subtitleLabel = new QLabel(this);
subtitleLabel->setWordWrap(true);
@ -155,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];

View file

@ -55,6 +55,7 @@ private slots:
void goBack();
void skip();
void updateChrome();
void applyInterfaceDefaultFont();
private:
void addPage(FirstRunWizardPage *page);

View file

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

View file

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

View file

@ -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"
@ -78,6 +79,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()
@ -139,8 +141,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);
@ -151,6 +154,11 @@ void CardAmountWidget::adjustFontSize(int scalePercentage)
repaint();
}
void CardAmountWidget::applyCardTextFont()
{
adjustFontSize(cardSizeSlider->value());
}
/**
* @brief Updates the card count display in the widget.
*/

View file

@ -67,6 +67,7 @@ private slots:
void removePrintingSideboard();
void removePrintingTokensboard();
void adjustFontSize(int scalePercentage);
void applyCardTextFont();
};
#endif // CARD_AMOUNT_WIDGET_H

View file

@ -1,5 +1,8 @@
#include "printing_selector_placeholder_widget.h"
#include "../interface/theme_manager.h"
#include <QFont>
#include <QLabel>
#include <QSizePolicy>
#include <QVBoxLayout>
@ -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.setPixelSize(14);
textLabel->setFont(placeholderFont);
}

View file

@ -14,6 +14,9 @@ class PrintingSelectorPlaceholderWidget : public QWidget
public:
explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr);
private slots:
void applyInterfaceDefaultFont();
private:
QVBoxLayout *mainLayout;
QLabel *imageLabel;

View file

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

View file

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

View file

@ -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);
@ -92,6 +91,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:"));

View file

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

View file

@ -141,7 +141,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));
@ -153,7 +154,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,
@ -322,7 +323,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);
@ -351,7 +353,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);
@ -368,7 +370,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
@ -397,6 +400,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);
}
}
@ -476,15 +480,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.setPixelSize(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.setPixelSize(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.setPixelSize(11);
gamesStatus->setFont(statusFont);
gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}"
"QListView::item:selected{background:%2;}")
@ -492,9 +505,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.setPixelSize(10);
closeBtn->setFont(closeFont);
header->update();
}
@ -509,13 +525,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.setPixelSize(11);
b->setFont(btnFont);
return b;
}

View file

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

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;
@ -1496,7 +1509,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

View file

@ -284,20 +284,11 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(),
&CardsDisplaySettings::setRoundCardCorners);
connect(&maxFontSizeForCardsEdit, qOverload<int>(&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);
@ -658,7 +649,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(

View file

@ -71,8 +71,6 @@ private:
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox;
QLabel maxFontSizeForCardsLabel;
QSpinBox maxFontSizeForCardsEdit;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;

View file

@ -5,6 +5,7 @@
*/
#include "settings_search_delegate.h"
#include "../interface/theme_manager.h"
#include "settings_search_model.h"
#include <QBrush>
@ -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);

View file

@ -1,5 +1,6 @@
#include "archidekt_deck_preview_image_display_widget.h"
#include "../../../../../theme_manager.h"
#include "../../../../general/display/shadow_background_label.h"
#include <QFont>
@ -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;

View file

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

View file

@ -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 <QFont>
#include <QLabel>
#include <QMouseEvent>
#include <QObject>
@ -31,7 +33,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);
@ -93,3 +97,14 @@ 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. Pixel
// size, not point size: the stylesheet said 20px, and 20pt is ~27px.
QFont cardFont = themeManager->appFont(AppFont::CardText);
cardFont.setPixelSize(20);
cardFont.setWeight(QFont::Bold);
nameLabel->setFont(cardFont);
}

View file

@ -30,6 +30,9 @@ public slots:
signals:
void requestUrl(QString url);
private slots:
void applyCardTextFont();
private:
EdhrecApiResponseCardDetails toDisplay;
QVBoxLayout *layout;

View file

@ -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 <QFont>
#include <QHBoxLayout>
#include <QLabel>
#include <QObject>
@ -55,15 +57,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());
@ -90,3 +93,19 @@ 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 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.setPixelSize(28);
nameFont.setWeight(QFont::Bold);
commanderName->setFont(nameFont);
QFont detailsFont = themeManager->appFont(AppFont::CardText);
detailsFont.setPixelSize(16);
label->setFont(detailsFont);
salt->setFont(detailsFont);
}

View file

@ -30,6 +30,9 @@ public:
QString baseUrl);
void retranslateUi();
private slots:
void applyCardTextFont();
private:
EdhrecCommanderApiResponseCommanderDetails commanderDetails;
QHBoxLayout *layout;

View file

@ -1,6 +1,9 @@
#include "edhrec_commander_api_response_bracket_navigation_widget.h"
#include "../../interface/theme_manager.h"
#include <QChar>
#include <QFont>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
@ -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.setPixelSize(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);
}

View file

@ -37,6 +37,9 @@ private:
QMap<QString, QPushButton *> gameChangerButtons;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
private slots:
void applyInterfaceDefaultFont();
};
#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H

View file

@ -1,6 +1,9 @@
#include "edhrec_commander_api_response_budget_navigation_widget.h"
#include "../../interface/theme_manager.h"
#include <QChar>
#include <QFont>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
@ -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.setPixelSize(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);
}

View file

@ -37,6 +37,9 @@ private:
QMap<QString, QPushButton *> budgetButtons;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
private slots:
void applyInterfaceDefaultFont();
};
#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H

View file

@ -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 <QFont>
#include <QGridLayout>
#include <QObject>
#include <QPushButton>
@ -29,10 +31,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);
@ -68,6 +74,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.setPixelSize(24);
comboPushButton->setFont(navFont);
averageDeckPushButton->setFont(navFont);
}
QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl)
{
if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) {

View file

@ -35,6 +35,9 @@ public slots:
signals:
void requestUrl(QString url);
private slots:
void applyInterfaceDefaultFont();
private:
QGridLayout *layout;

View file

@ -6,6 +6,7 @@
#include "tab_developer.h"
#include "../interface/theme_manager.h"
#include "libcockatrice/protocol/pb/response.pb.h"
#include <QAbstractItemView>
@ -100,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();
}
@ -155,7 +170,8 @@ void TabDeveloper::appendSeparatorRow(const QString &sectionTitle)
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);

View file

@ -5,6 +5,7 @@
#include "../../deck_loader/loaded_deck.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"
@ -44,9 +45,17 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c
cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize();
titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this);
QFont titleFont = titleLabel->font();
titleFont.setBold(true);
titleLabel->setFont(titleFont);
// Family comes from the InterfaceDefault theme role; bold stays caller-side.
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();
};
applyThemeFonts();
connect(themeManager, &ThemeManager::fontsChanged, this, applyThemeFonts);
auto *headerLayout = new QHBoxLayout;
headerLayout->addWidget(titleLabel);

View file

@ -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"
@ -13,7 +14,6 @@
#include <QDateTime>
#include <QDebug>
#include <QFont>
#include <QFontDatabase>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
@ -138,13 +138,8 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
chatGroup = new QGroupBox;
chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true);
QFont monoFont("monospace");
monoFont.setStyleHint(QFont::Monospace);
const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (systemPointSize > 0) {
monoFont.setPointSize(systemPointSize);
}
chatLogEdit->setFont(monoFont);
applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &TabReport::applyChatLogFont);
auto *chatLayout = new QVBoxLayout(chatGroup);
chatLayout->setContentsMargins(4, 4, 4, 4);
chatLayout->addWidget(chatLogEdit);
@ -277,6 +272,11 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
refreshList();
}
void TabReport::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void TabReport::retranslateUi()
{
userContextMenu->retranslateUi();

View file

@ -62,6 +62,7 @@ private slots:
void statsResponse(const Response &response);
void reopenReport();
void reopenResponse(const Response &response);
void applyChatLogFont();
private:
int selectedReportId() const;

View file

@ -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"
@ -181,7 +182,7 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt
p->setBrush(Qt::NoBrush);
p->drawEllipse(pip.adjusted(1, 1, -1, -1));
QFont f = p->font();
QFont f = themeManager->appFont(AppFont::CardText);
f.setPixelSize(qMax(radius - 1, 7));
f.setBold(true);
@ -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);

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

@ -1,5 +1,6 @@
#include "report_utils.h"
#include "../../theme_manager.h"
#include "libcockatrice/protocol/pb/serverinfo_report.pb.h"
#include <QApplication>
@ -7,6 +8,7 @@
#include <QChar>
#include <QColor>
#include <QDateTime>
#include <QFontDatabase>
#include <QPalette>
#include <QSharedPointer>
#include <QTableWidget>
@ -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)

View file

@ -1,6 +1,7 @@
#ifndef REPORT_UTILS_H
#define REPORT_UTILS_H
#include <QFont>
#include <QString>
#include <QtGlobal>
@ -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);

View file

@ -1,5 +1,8 @@
#include "visual_deck_editor_placeholder_widget.h"
#include "../interface/theme_manager.h"
#include <QFont>
#include <QLabel>
#include <QVBoxLayout>
#include <qnamespace.h>
@ -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.setPixelSize(14);
textLabel->setFont(placeholderFont);
}

View file

@ -15,6 +15,9 @@ public:
explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr);
void retranslateUi();
private slots:
void applyInterfaceDefaultFont();
private:
QVBoxLayout *mainLayout;
QLabel *imageLabel;

View file

@ -1,5 +1,7 @@
#include "deck_preview_tag_addition_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush>
#include <QColor>
#include <QFlags>
@ -26,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
@ -65,8 +71,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);

View file

@ -1,5 +1,7 @@
#include "deck_preview_tag_display_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush>
#include <QColor>
#include <QFlags>
@ -41,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);
}
@ -117,8 +123,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);

View file

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

View file

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

View file

@ -17,6 +17,7 @@
#include <QSharedPointer>
#include <QStringList>
#include <QStringLiteral>
#include <QTextStream>
#include <QVariant>
#include <algorithm>
#include <utility>
@ -395,6 +396,10 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
{
const QMap<QString, QString> 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,175 @@ 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: <data>/themes/ sibling of <data>/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("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());
}
const QString entry =
QString("%1 = \"%2, %3\"\n").arg(QStringLiteral("CardText"), -20).arg(QStringLiteral("Serif")).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<QString, QString> networkKeyMap = {
@ -821,6 +995,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;

View file

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

View file

@ -10,6 +10,7 @@
#include <QTemporaryDir>
#include <QTextStream>
#include <gtest/gtest.h>
#include <string>
namespace
{
@ -186,6 +187,76 @@ 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, InequalityComparesMinSize)
{
FontConfig left;
left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
left.cardTextMinSize = 1;
FontConfig right = left;
right.cardTextMinSize = 2;
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)

View file

@ -408,19 +408,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);

View file

@ -984,6 +984,196 @@ 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("CardText"));
EXPECT_TRUE(content.contains("18"));
EXPECT_TRUE(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("CardText"));
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("CardText"));
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)